Quick note post, I saw some stackoverflow questions of how to load OpenGL textures from JuicyPixels and I found one way to do it. Seems like an easy way to shuttle binary data between libraries is to use bytestrings, though you might end up with more copies in the process. Looking at some other libraries, you can probably use vector directly but that's a bit too advanced for me.
I just noticed in pasting this code that useAsCString will add a null terminator, which shouldn't be a problem, but it's a little odd for this application.
module TestGame.TextureLoading where import Codec.Picture import qualified Data.ByteString import qualified Data.ByteString.Lazy import qualified Data.ByteString.Lazy.Builder as ByteString.Builder import qualified Data.Vector.Storable import Data.Monoid import qualified Graphics.Rendering.OpenGL as OpenGL import Data.Int data Loaded2dTexture = Loaded2dTexture { width :: Int, height::Int, textureData :: Data.ByteString.ByteString} loadTexture :: FilePath -> IO (Maybe Loaded2dTexture) loadTexture filePath = do imageEither <- readImage filePath case imageEither of Left error -> do putStrLn error return Nothing Right image -> return $ rawImage image rawImage :: DynamicImage -> (Maybe Loaded2dTexture) rawImage (ImageRGBA8 image) = Just $ rawImageRGBA8 $ flipImageVertically image rawImage _ = Nothing --OpenGL has y going up so this makes it convenient for 2d game images flipImageVertically :: Image PixelRGBA8 -> Image PixelRGBA8 flipImageVertically image = generateImage (flippedPixel) (imageWidth image) (imageHeight image) where flippedPixel x y = pixelAt image x ((imageHeight image) - 1 - y) rawImageRGBA8 :: Image PixelRGBA8 -> Loaded2dTexture rawImageRGBA8 image = Loaded2dTexture{ width = imageWidth image, height = imageHeight image, --JuicyPixels image data is packed from first component to last component, --should not need endian conversion textureData = copyPixelDataToByteString (imageData image)} copyPixelDataToByteString imageData = Data.ByteString.Lazy.toStrict $ ByteString.Builder.toLazyByteString $ Data.Vector.Storable.foldl' appendPixel (mempty :: ByteString.Builder.Builder) imageData appendPixel :: ByteString.Builder.Builder -> (PixelBaseComponent PixelRGBA8) -> ByteString.Builder.Builder appendPixel pixelBytes pixelComponent = pixelBytes `mappend` (ByteString.Builder.word8 pixelComponent) uploadTexture :: Loaded2dTexture -> IO () uploadTexture texture = do uploadTextureT texture OpenGL.Texture2D uploadTextureRectangle :: Loaded2dTexture -> IO () uploadTextureRectangle texture = do uploadTextureT texture OpenGL.TextureRectangle uploadTextureT :: (OpenGL.TwoDimensionalTextureTarget t, OpenGL.ParameterizedTextureTarget t ) => Loaded2dTexture -> t -> IO () uploadTextureT texture textureTarget = do Data.ByteString.useAsCString (textureData texture) $ \ texturePtr -> do let (texWidth, texHeight) = ((fromIntegral $ width texture), (fromIntegral $ height texture)) OpenGL.texImage2D textureTarget OpenGL.NoProxy 0 OpenGL.RGBA' (OpenGL.TextureSize2D texWidth texHeight) 0 (OpenGL.PixelData OpenGL.RGBA OpenGL.UnsignedByte (texturePtr)) OpenGL.textureFilter textureTarget OpenGL.$= ((OpenGL.Linear', Nothing), OpenGL.Linear')
Bytestrings also work for shuttling data from cairo (hoping to get diagram generated textures!) but cairo uses the host endian format. Little endian ARGB = BGRA. This one is ripped from some existing code so it's not a full example.
import qualified Graphics.UI.GLFW as GLFW import qualified Graphics.Rendering.OpenGL as OpenGL import qualified Graphics.Rendering.OpenGL.GLU as OpenGL.GLU import qualified Graphics.Rendering.FTGL as FTGL import Control.Monad import qualified Control.Concurrent import Foreign.C.Types import qualified Graphics.Rendering.Cairo as Cairo import qualified Data.ByteString import qualified Foreign.Ptr import qualified Data.Word import qualified Data.ByteString.Lazy.Builder import qualified Data.ByteString.Lazy import qualified Data.Binary.Get import Data.Monoid loadTexture = do cairoTexture <- renderCairo OpenGL.textureBinding OpenGL.Texture2D OpenGL.$= Just textureName Data.ByteString.writeFile "texture.raw" cairoTexture Data.ByteString.useAsCString (hostEndianToLittleEndian cairoTexture) $ \ texturePtr -> do OpenGL.texImage2D OpenGL.Texture2D OpenGL.NoProxy 0 OpenGL.RGBA' (OpenGL.TextureSize2D 512 512) 0 (OpenGL.PixelData OpenGL.BGRA OpenGL.UnsignedByte (texturePtr)) OpenGL.textureFilter OpenGL.Texture2D OpenGL.$= ((OpenGL.Linear', Nothing), OpenGL.Linear') hostEndianToLittleEndian :: Data.ByteString.ByteString -> Data.ByteString.ByteString hostEndianToLittleEndian sourceByteString = let initialBuilder = mempty :: Data.ByteString.Lazy.Builder.Builder convert builder = do empty <- Data.Binary.Get.isEmpty if empty then return builder else do word32 <- Data.Binary.Get.getWord32host convert (builder `mappend` (Data.ByteString.Lazy.Builder.word32LE word32) ) in Data.ByteString.Lazy.toStrict $ Data.ByteString.Lazy.Builder.toLazyByteString $ Data.Binary.Get.runGet (convert initialBuilder) (Data.ByteString.Lazy.fromStrict sourceByteString) renderCairo :: IO Data.ByteString.ByteString renderCairo = do Cairo.withImageSurface Cairo.FormatARGB32 512 512 $ \surface -> do drawSurface surface Cairo.imageSurfaceGetData surface drawSurface :: Cairo.Surface -> IO () drawSurface surface = do Cairo.renderWith surface $ do --Doing the y axis flip for OpenGL directly in cairo Cairo.scale 1.0 (-1.0) Cairo.translate 0 (-512) Cairo.rectangle 0 0 512 512 Cairo.setSourceRGBA 0.0 0.0 0.0 0.0 Cairo.fill Cairo.setSourceRGB 0.5 0.5 0.5 Cairo.rectangle 0 0 10 10 Cairo.fill Cairo.setLineWidth 10.0 Cairo.setSourceRGB 1.0 0.0 0.0 Cairo.rectangle 50 50 200 200 Cairo.stroke roundRectPath 100 100 200 50 10 Cairo.setSourceRGB 0.25 0.25 0.25 Cairo.fill Cairo.setSourceRGB 0.0 1.0 1.0 Cairo.moveTo 120 120 Cairo.setFontSize 20 Cairo.showText $ "Hello Cairo!" Cairo.surfaceFlush surface return () roundRectPath x y width height radius = do Cairo.newPath Cairo.arc (x+width-radius) (y+radius) radius (3*pi/2) (2*pi) Cairo.arc (x+width-radius) (y+height-radius) radius 0 (pi/2.0) Cairo.arc (x+radius) (y+height-radius) radius (pi/2.0) (pi) Cairo.arc (x+radius) (y+radius) radius pi (3*pi/2) Cairo.closePath