Search code examples
haskellopenglvectorglutjuicy-pixels

How do I rotate a juicy pixel image at 180°


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 :

enter image description here

The image is rotated at 180°

So, I tried to rotate 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" (rotate180 image)

My programm 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:

enter image description here

The top of the image seems to be correctly rotated, 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 rotate a juicypixels image correctly ?


Solution

  • 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)