sodium-0.1.0.0: examples/poodle/Image.hs
{-# LANGUAGE DeriveDataTypeable #-}
module Image where
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Internal as B
import qualified Data.ByteString.Unsafe as B
import Graphics.Rendering.OpenGL as GL hiding (RGB, RGBA)
import qualified Graphics.Rendering.OpenGL as GL
import Codec.Image.STB
import Data.Bitmap.IO
import Data.Char
import Control.Exception
import Control.Monad
import Data.Typeable
import Data.Word
import Foreign
import Foreign.C
data Format = RGB | RGBA deriving (Eq, Show)
data TextureImage_ d = TextureImage !Int !Int !Int !Int Format d deriving Show
type TextureImage = TextureImage_ ByteString
data ImageException = ImageException String
deriving Typeable
instance Exception ImageException
instance Show ImageException where
showsPrec _ (ImageException err) = ("ImageException: "++) . (err++)
loadTexture :: FilePath
-> Bool -- ^ True to invert it
-> IO TextureImage
loadTexture path invert = do
eImg <- loadImage path
img <- case eImg of
Left err -> throwIO $ ImageException $
"Failed to load image file "++path++": "++err
Right img -> return img
withBitmap img $ \(width, height) components padding imgData0 -> do
fmt <- case components of
3 -> return RGB
4 -> return RGBA
_ -> throwIO $ ImageException $
"Failed to load image file "++path++": we only support RGB images"
let len = width * height * components
imgData <- mallocBytes len
B.memcpy imgData imgData0 (fromIntegral len)
when invert $ do
let row = width * bytesPerPixel fmt
row_sz = fromIntegral row
allocaBytes row $ \tempRow -> do
forM_ [0..height `div` 2] $ \y -> do
let r0 = row * y
s0 = row * (height - 1 - y)
B.memcpy tempRow (imgData `plusPtr` r0) row_sz
B.memcpy (imgData `plusPtr` r0) (imgData `plusPtr` s0) row_sz
B.memcpy (imgData `plusPtr` s0) tempRow row_sz
let tex = TextureImage width height width height fmt imgData
transparent <- hasTransparency tex
tex' <- if transparent then return tex else removeAlphaChannel tex
unsafeTextureToBS tex'
-- | Converts a buffer-based Texture to to a ByteString-based one. Frees
-- the input byte buffer.
unsafeTextureToBS :: TextureImage_ (Ptr Word8) -> IO TextureImage
unsafeTextureToBS (TextureImage iWidth iHeight pWidth pHeight fmt buf) = do
let bpp = bytesPerPixel fmt
sz = bpp * iWidth * iHeight
bytes <- peekArray sz buf
free buf
return $ TextureImage iWidth iHeight pWidth pHeight fmt (B.pack bytes)
bytesPerPixel :: Format -> Int
bytesPerPixel RGB = 3
bytesPerPixel RGBA = 4
hasTransparency :: TextureImage_ (Ptr Word8) -> IO Bool
hasTransparency (TextureImage _ _ _ _ RGB _) = return False
hasTransparency (TextureImage width height _ _ fmt@RGBA imgData) = h 0
where
limit = width * height
bpp = bytesPerPixel fmt
h ix | ix >= limit = return False
h ix = do
alpha <- peekByteOff imgData (ix * bpp + 3) :: IO Word8
if alpha < 255
then return True
else h (ix + 1)
removeAlphaChannel :: TextureImage_ (Ptr Word8) -> IO (TextureImage_ (Ptr Word8))
removeAlphaChannel tex@(TextureImage _ _ _ _ RGB _) = return tex
removeAlphaChannel (TextureImage iWidth iHeight pWidth pHeight fmt@RGBA imgData) = do
forM_ [1..limit-1] $ \ix -> do
r <- peekByteOff imgData (ix * bpp) :: IO Word8
pokeByteOff imgData (ix * 3) r
g <- peekByteOff imgData (ix * bpp + 1) :: IO Word8
pokeByteOff imgData (ix * 3 + 1) g
b <- peekByteOff imgData (ix * bpp + 2) :: IO Word8
pokeByteOff imgData (ix * 3 + 2) b
return (TextureImage iWidth iHeight pWidth pHeight RGB imgData)
where
limit = iWidth * iHeight
bpp = bytesPerPixel fmt
-- | Convert texture data into an OpenGL handle
createTexture :: TextureImage
-> IO TextureObject
createTexture (TextureImage iWid iHt _ _ fmt imgBS) = do
[texName] <- genObjectNames 1 -- generate our texture.
--rowAlignment Unpack $= 1
textureBinding Texture2D $= Just texName -- make our new texture the current texture.
generateMipmap Texture2D $= Enabled
B.unsafeUseAsCStringLen imgBS $ \(buf, len) -> do
let glFmt = case fmt of
RGB -> GL.RGB
RGBA -> GL.RGBA
pixels = PixelData glFmt UnsignedByte buf
build2DMipmaps Texture2D RGBA' (fromIntegral iWid) (fromIntegral iHt) pixels
GL.textureFilter GL.Texture2D $= ((GL.Linear', Nothing), GL.Linear')
return texName
-- | Ensure this label can be a valid filename
cleanLabel :: String -> String
cleanLabel =
scrunch .
map (\c -> if (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') then c else '_')
where
scrunch ('_':'_':cs) = scrunch ('_':cs)
scrunch (c:cs) = c:scrunch cs
scrunch [] = []