packages feed

aztecs-gl-0.3.0: src/Aztecs/GL/D2/Image.hs

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}

module Aztecs.GL.D2.Image (Image (..), loadImage, TextureFilter (..)) where

import Aztecs
import Aztecs.GL.D2.Image.Internal
import Aztecs.GL.Internal
import Aztecs.Transform
import qualified Codec.Picture as JP
import Control.Monad
import Control.Monad.IO.Class
import qualified Data.ByteString as BS
import qualified Data.Vector.Storable as VS
import Foreign
import Graphics.Rendering.OpenGL (($=))
import qualified Graphics.Rendering.OpenGL as GL
import Graphics.Rendering.OpenGL.GL (TextureFilter (..))
import Prelude hiding (lookup)

-- | Image component
data Image = Image
  { -- | Image source data
    imageData :: JP.Image JP.PixelRGBA8,
    -- | Image texture filtering mode
    imageFilter :: !TextureFilter
  }

instance (MonadIO m) => Component m Image where
  componentOnInsert e img = inAnyWindowContext $ do
    let w = JP.imageWidth $ imageData img
        h = JP.imageHeight $ imageData img
        pixels = JP.imageData $ imageData img
        imgData = BS.pack $ VS.toList pixels

    res <- liftIO $ GL.genObjectNames 1
    tex <- case res of
      [tex] -> return tex
      _ -> error "Failed to generate texture object"

    liftIO $ do
      GL.textureBinding GL.Texture2D $= Just tex

      -- Set texture parameters (use Nearest filtering for crisp pixel art)
      GL.textureFilter GL.Texture2D $= ((imageFilter img, Nothing), imageFilter img)
      GL.textureWrapMode GL.Texture2D GL.S $= (GL.Repeated, GL.Repeat)
      GL.textureWrapMode GL.Texture2D GL.T $= (GL.Repeated, GL.Repeat)

      -- Upload texture data
      BS.useAsCString imgData $ \ptr -> do
        GL.texImage2D
          GL.Texture2D
          GL.NoProxy
          0
          GL.RGBA8
          (GL.TextureSize2D (fromIntegral w) (fromIntegral h))
          0
          (GL.PixelData GL.RGBA GL.UnsignedByte (castPtr ptr))
      GL.textureBinding GL.Texture2D $= Nothing
    insert e . bundle $ ImageState tex (V2 (fromIntegral w) (fromIntegral h))

loadImage :: FilePath -> IO Image
loadImage path = do
  eImg <- JP.readImage path
  case eImg of
    Left err -> error $ "Failed to load image: " ++ err
    Right dynImg -> let img = JP.convertRGBA8 dynImg in return $ Image img GL.Nearest