{-# LANGUAGE OverloadedStrings #-} module Graphics.Rendering.Rect.Image( Atlas, buildAtlas, Texture(..), atlasLookup) where import qualified Data.HashMap.Lazy as HM import Data.Text (Text) import Codec.Picture (DynamicImage(..), Image(..), PixelRGBA8(..), PixelF, pixelMap, generateImage) import Codec.Picture.Types (promoteImage, dynamicMap, convertImage) import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad (forM) import Data.Maybe (fromMaybe) import Typograffiti.GL import Graphics.GL.Core32 import Graphics.GL.Types import Graphics.GL.Ext.EXT.Cmyka import Graphics.GL.Ext.SGIX.Ycrcb import Graphics.GL.Compatibility32 import Data.Vector.Storable (unsafeWith, unsafeCast, Vector) import Foreign.Ptr (castPtr) import Foreign.Marshal.Array (allocaArray, peekArray) data Atlas = Atlas { unAtlas :: HM.HashMap Text Texture } buildAtlas :: MonadIO m => (Text -> IO DynamicImage) -> [Text] -> m Atlas buildAtlas cb [] = return $ Atlas HM.empty buildAtlas cb srcs = do -- TODO Merge textures into an actual atlas. let len = length srcs textures <- liftIO $ allocaArray (toEnum len) $ \ptr -> do glGenTextures (toEnum len) ptr peekArray len ptr imgs <- liftIO $ forM srcs cb sizes <- forM (zip textures imgs) $ \(texture, dyn) -> do -- NOTE: `unsafe` crashes with a divide-by-zero given a `Vector ()` let dyn' = convertDyn dyn let img = dynamicMap (unsafeCast . imageData) dyn' :: Vector Word let (format, word) = glFormat dyn' liftIO $ glBindTexture GL_TEXTURE_2D texture liftIO $ unsafeWith img $ glTexImage2D GL_TEXTURE_2D 0 GL_RGBA (toEnum $ dynamicMap imageWidth dyn') (toEnum $ dynamicMap imageHeight dyn') 0 format word . castPtr liftIO $ glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER GL_LINEAR liftIO $ glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER GL_LINEAR liftIO $ glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S GL_REPEAT liftIO $ glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T GL_REPEAT return (toEnum $ dynamicMap imageWidth dyn', toEnum $ dynamicMap imageHeight dyn') let textures' = map (uncurry Texture) $ zip textures sizes return $ Atlas $ HM.fromList $ zip srcs textures' data Texture = Texture { unTexture :: GLuint, texSize :: (Float, Float) } nilTexture = Texture 0 (0, 0) atlasLookup :: Text -> Atlas -> Texture atlasLookup key = fromMaybe nilTexture . HM.lookup key . unAtlas -- OpenGL deals in CrCb not CbCr... -- Convert pixels to some flavour of RGBA convertDyn :: DynamicImage -> DynamicImage convertDyn (ImageY8 img) = ImageRGBA8 $ promoteImage img convertDyn (ImageY16 img) = ImageRGBA16 $ promoteImage img convertDyn (ImageY32 img) = ImageRGBF $ promoteImage (pixelMap fromIntegral img :: Image PixelF) convertDyn (ImageYF img) = ImageRGBF $ promoteImage img convertDyn (ImageYA8 img) = ImageRGBA8 $ promoteImage img convertDyn (ImageYA16 img) = ImageRGBA16 $ promoteImage img convertDyn (ImageRGB8 img) = ImageRGB8 img convertDyn (ImageRGB16 img) = ImageRGB16 img convertDyn (ImageRGBF img) = ImageRGBF img convertDyn (ImageRGBA8 img) = ImageRGBA8 img convertDyn (ImageRGBA16 img) = ImageRGBA16 img convertDyn (ImageYCbCr8 img) = ImageRGB8 $ convertImage img convertDyn (ImageCMYK8 img) = ImageRGB8 $ convertImage img convertDyn (ImageCMYK16 img) = ImageRGB16 $ convertImage img glFormat (ImageY8 _) = (GL_LUMINANCE, GL_UNSIGNED_BYTE) glFormat (ImageY16 _) = (GL_LUMINANCE, GL_UNSIGNED_SHORT) glFormat (ImageY32 _) = (GL_LUMINANCE, GL_UNSIGNED_INT) glFormat (ImageYF _) = (GL_LUMINANCE, GL_FLOAT) glFormat (ImageYA8 _) = (GL_LUMINANCE_ALPHA, GL_BYTE) glFormat (ImageYA16 _) = (GL_LUMINANCE_ALPHA, GL_UNSIGNED_SHORT) glFormat (ImageRGB8 _) = (GL_RGB, GL_UNSIGNED_BYTE) glFormat (ImageRGB16 _) = (GL_RGB, GL_UNSIGNED_SHORT) glFormat (ImageRGBF _) = (GL_RGB, GL_FLOAT) glFormat (ImageRGBA8 _) = (GL_RGBA, GL_UNSIGNED_BYTE) glFormat (ImageRGBA16 _) = (GL_RGBA, GL_UNSIGNED_BYTE) glFormat (ImageYCbCr8 _) = (GL_YCRCB_444_SGIX, GL_UNSIGNED_BYTE) glFormat (ImageCMYK8 _) = (GL_CMYK_EXT, GL_UNSIGNED_BYTE) glFormat (ImageCMYK16 _) = (GL_CMYK_EXT, GL_UNSIGNED_SHORT)