I'm trying to make snapshots of a opengl area in a haskell program using the glut library.
I found this snippet of code using the juicypixels library.
saveImage :: Window -> IO ()
saveImage window = do
currentWindow $= Just window
Size w h <- get windowSize
let npixels = fromIntegral (w*h) :: Int
nbytes = 3*npixels
fptr <- mallocForeignPtrArray nbytes :: IO (ForeignPtr Word8)
withForeignPtr fptr $ \ptr -> do
let pdata = PixelData RGB UnsignedByte ptr :: PixelData Word8
readPixels (Position 0 0) (Size w h) pdata
let fptr' = castForeignPtr fptr :: ForeignPtr (PixelBaseComponent PixelRGB8)
let imgdata = unsafeFromForeignPtr0 fptr' npixels :: Vector (PixelBaseComponent PixelRGB8)
let image = Image (fromIntegral w) (fromIntegral h) imgdata :: Image PixelRGB8
writePng "test.png" image
This code is running well. Just a problem, it save the png image like this :
The image is flipped vertically
So, I tried to flip my image to get it in the right direction.
I found this function flipVertically and implemented it locally:
flipVertically :: Image PixelRGB8 -> Image PixelRGB8
flipVertically img@Image {..} =
generateImage gen imageWidth imageHeight
where
gen x y = pixelAt img x (imageHeight - 1 - y)
When I use it
writePng "test.png" (flipVertically image)
My program fail with the following error message
geometrix-cli_gl_snap: ./Data/Vector/Generic.hs:257 ((!)): index out of bounds (1079997,360000)
CallStack (from HasCallStack):
error, called at ./Data/Vector/Internal/Check.hs:87:5 in vector-0.12.3.1-TXkE6leK98EdYcmdk29JF:Data.Vector.Internal.Check
It seems that it's trying to access an index (1079997) out of my image (600*600=360000px).
as a juicypixels image is made with a vector, I tried to convert the vector to a list, reverse this list and reconvert it to a vector with the functions toList and fromList.
let lst = (V.toList imgdata)
let image = Image (fromIntegral w) (fromIntegral h) (V.fromList (reverse (lst))) :: Image PixelRGB8
the program is running but instead of my image in the right direction, I have this free style:
The top of the image seems to be correctly flipped, but the bottom is a non sense.
When I test the list length, I get 360000. So it contain the right amount of pixels for my image.
What did I do wrong in my function ?
How do I flip a juicypixels image correctly ?
The culprit is where you convert the pointer into a Vector
, you here make a Vector
of essentially bytes with length npixels
, whereas it should be nbytes
, since PixelBaseComponent PixelRGB8
takes the byte size of one channel, indeedĀ [Haskell-src]:
instance Pixel PixelRGB8 where
type PixelBaseComponent PixelRGB8 = Word8
-- …
It is possible that if you save an image, and it uses a pointer, that might still work, but the vector is here to essentially make sure you can not access data outside the bounds.
You thus should use:
saveImage :: Window -> IO ()
saveImage window = do
currentWindow $= Just window
Size w h <- get windowSize
let npixels = fromIntegral (w * h) :: Int
nbytes = 3 * npixels
fptr <- mallocForeignPtrArray nbytes :: IO (ForeignPtr Word8)
withForeignPtr fptr $ \ptr -> do
let pdata = PixelData RGB UnsignedByte ptr :: PixelData Word8
readPixels (Position 0 0) (Size w h) pdata
let fptr' = castForeignPtr fptr :: ForeignPtr (PixelBaseComponent PixelRGB8)
let imgdata = unsafeFromForeignPtr0 fptr' nbytes :: Vector (PixelBaseComponent PixelRGB8)
let image = Image (fromIntegral w) (fromIntegral h) imgdata :: Image PixelRGB8
writePng "test.png" (rotate180 image)