4

I have PNG files and the Gloss library has a Bitmap constructor for Picture. I can't use loadBMP :: FilePath -> IO Picture because of the filetype, so I'm searching how to load a PNG file, convert it to BMP, and feed it to either bitmapOfBMP :: BMP -> Picture, bitmapOfForeignPtr :: Int -> Int -> ForeignPtr Word8 -> Bool -> Picture or bitmapOfByteString :: Int -> Int -> ByteString -> Bool -> Picture.


Test with JuicyPixels

import Data.ByteString as B
import System.IO as A

import Codec.Picture.Png
import Graphics.Gloss.Interface.Pure.Game


main = do
    png <- B.readFile "samus.png"
    let img = decodePng png
    case img of
        Left x -> A.putStrLn x
        Right x -> do
            let bmp = encodeDynamicPng x
            case bmp of
                Left x -> A.putStrLn x
                Right x -> do
                    let pic = bitmapOfByteString 29 52 x True
                    game pic

game pic
    =  play
        (InWindow "Test" (700, 500) (10, 10))
        white
        30
        pic
        draw
        (const id)
        (const id)

draw bmp
    = bmp

Everything succeeds but the image is not the same at all.

Chris Stryczynski
  • 30,145
  • 48
  • 175
  • 286
L01man
  • 1,521
  • 10
  • 27

3 Answers3

4

That's why I made JuicyPixel-repa. You read in the image as a Repa array and convert it, like I did in gloss-osm, to a Picture:

repaToPicture :: Bool -> Array F.F DIM3 Word8 -> (Int, Int, Picture)
repaToPicture b arr =
let fptr = F.toForeignPtr arr
bs = BI.fromForeignPtr fptr 0 len
in (col, row, bitmapOfByteString row col bs b)
 where
  len = row * col * depth
  (Z :. row :. col :. depth) = extent arr

Alternatively, you could just use JuicyPixels directly, case over the DynamicImage type and get the underlying imgData from the contained Image.

Thomas M. DuBuisson
  • 64,245
  • 7
  • 109
  • 166
  • Thanks, I tried the second solution. I didn't use the `imgData` because I don't know how to, but converted to a BMP `ByteString`, which doesn't show me my image but spreaded blue and pink pixels. I added the code to the question. – L01man Aug 31 '12 at 23:06
  • I managed to use your `repaToPicture`. I put the code in the question. What is the `Bool` parameter for? Why does it return `(Int, Int, Picture)` and not `Picture`? The generated `Picture` is "wrong", so I did: `let (w, h, pic) = repaToPicture True repa; Bitmap _ _ bmp _ = pic in Bitmap w h bmp True`. Now, the image shows, but it's rotated by 180 degrees counter-clockwise. – L01man Sep 05 '12 at 08:45
  • Sorry, I've been busy but I'll try to get to helping you some this weekend if you still need it by then. The boolean determines if the picture will be cached by your OpenGL system. As for the rotation, just use repa's [backpermute](http://www.haskell.org/haskellwiki/Numeric_Haskell:_A_Repa_Tutorial). – Thomas M. DuBuisson Sep 05 '12 at 15:59
  • Thank you for your help! The tutorial was very helpful: it explained enough for me to hack the code and have it working and gave a ready-to-use function. I ran into some trouble with the representation stuff but `computeS` did the trick. Also, I said that the image was rotated by 180 degrees, but it was in fact flipped vertically, so I just changed `y - j - 1` to `j` and it worked. – L01man Sep 05 '12 at 18:32
3

Even though I didn't come with the answer alone, but thanks to Thomas' answer, I will post it here instead of inside the question.

As a reminder, the goal is to convert a BMP file into a Gloss' Picture, so I wrote a function called bmpToPic. I put it in a module, because it uses two others functions, and needs many imports. Also, repaToPicture from Thomas' answer is slighty different here.

module PngToPic
    (pngToPic)
    where

import Data.ByteString as B
import Data.Word

import Codec.Picture.Png
import Codec.Picture.Repa
import qualified Data.ByteString.Internal as BI
import Data.Array.Repa ((:.)(..), Z, Z(..), extent, DIM3, Array)
import qualified Data.Array.Repa as R
import qualified Data.Array.Repa.Repr.ForeignPtr as F
import Graphics.Gloss.Data.Picture


pngToPic :: ByteString -> Picture
pngToPic png
    = let
        Right img -- unsafe
            = decodePng png
        repa
            = imgData (convertImage img :: Img RGBA)
    in repaToPicture True repa

repaToPicture :: Bool -> Array F.F DIM3 Word8 -> Picture
repaToPicture b arr
    = bitmapOfByteString row col bs b
    where
        bs
            = BI.fromForeignPtr fptr 0 (R.size sh)
        fptr
            = F.toForeignPtr arr'
        sh@(Z :. col :. row :. depth)
            = extent arr'
        arr'
            = flipVert arr

flipVert :: Array F.F DIM3 Word8 -> Array F.F DIM3 Word8
flipVert g
    = R.computeS $ R.backpermute e flop g
    where
        e@(Z :. x :. y :. _)
            = extent g
        flop (Z :. i         :. j         :. k)
            = Z :. x - i - 1 :. j :. k

You use it like that:

import Data.ByteString as B

import Graphics.Gloss.Interface.Pure.Game

import PngToPic

main = do
    png <- B.readFile "someImage.png"
    game $ pngToPic png

game pic
    = play
        (InWindow "Test" (700, 500) (10, 10))
        white
        30
        pic
        id
        (const id)
        (const id)

and the image will show up in the middle of the window.

L01man
  • 1,521
  • 10
  • 27
  • Thank you for your help and compliment. I think of making a more general function, fileFormatToGlossPicture, for example, because it's easy and covers all the cases, and making it a package. – L01man Sep 26 '12 at 16:21
0

For anyone stumbling upon this in 2017, this has gotten significantly easier! It took a while for me to figure out since the sample code above no longer appears to work. Here's my function for reading a PNG into a Picture:

import Codec.Picture.Repa (readImageRGBA, toByteString, reverseColorChannel)
import Graphics.Gloss

readPng :: FilePath -> Int -> Int -> IO Picture
readPng path w h = do
  (Right img) <- readImageRGBA path
  let bs = toByteString $ reverseColorChannel img
  return $ bitmapOfByteString w h (BitmapFormat TopToBottom PxRGBA) bs True
David Ackerman
  • 12,649
  • 6
  • 24
  • 19
  • Will this work with Play from Graphics.Gloss.Interface.Pure.Game? I tried here but i get "Couldn't match expected type `Picture' with actual type `Int -> Int -> IO Picture'". Maybe i'm doing something wrong... Is there a way to cast IO Picture into Picture? – chr0m1ng Nov 29 '17 at 00:26
  • @alitalvez It's an `IO` type because it needs to read from the filesystem. In haskell, you can't just turn an `IO Picture` into a `Picture` in a pure function, since it interacts with the file system. So the options I see for you are to either run this code _before_ you initialize your game (like `thePicture <- readPng "/mypath.png"`), or you could switch to `Graphics.Gloss.Interface.IO.Game` to use it in your game. I'd start with the former first! – David Ackerman Dec 03 '17 at 00:57