Search code examples
arrayshaskellbytestring

How can I convert a (StorableArray (Int, Int) Word8) into a lazy ByteString?


I am trying to load a PNG file, get the uncompressed RGBA bytes, then send them to the gzip or zlib packages.

The pngload package returns image data as a (StorableArray (Int, Int) Word8), and the compression packages take lazy ByteStrings. Therefore, I am attempting to build a (StorableArray (Int, Int) Word8 -> ByteString) function.

So far, I have tried the following:

import qualified Codec.Image.PNG as PNG
import Control.Monad (mapM)
import Data.Array.Storable (withStorableArray)
import qualified Data.ByteString.Lazy as LB (ByteString, pack, take)
import Data.Word (Word8)
import Foreign (Ptr, peekByteOff)

main = do
    -- Load PNG into "image"...
    bytes <- withStorableArray 
        (PNG.imageData image)
        (bytesFromPointer lengthOfImageData)

bytesFromPointer :: Int -> Ptr Word8 -> IO LB.ByteString
bytesFromPointer count pointer = LB.pack $ 
    mapM (peekByteOff pointer) [0..(count-1)]

This causes the stack to run out of memory, so clearly I am doing something very wrong. There are more things I could try with Ptr's and ForeignPtr's, but there are a lot of "unsafe" functions in there.

Any help here would be appreciated; I'm fairly stumped.


Solution

  • Generally, pack and unpack are a bad idea for performance. If you have a Ptr, and a length in bytes, you can generate a strict bytestring in two different ways:

    Like this:

    import qualified Codec.Image.PNG as PNG
    import Control.Monad
    import Data.Array.Storable (withStorableArray)
    
    import Codec.Compression.GZip
    
    import qualified Data.ByteString.Lazy   as L
    import qualified Data.ByteString.Unsafe as S
    
    import Data.Word
    import Foreign
    
    -- Pack a Ptr Word8 as a strict bytestring, then box it to a lazy one, very
    -- efficiently
    bytesFromPointer :: Int -> Ptr Word8 -> IO L.ByteString
    bytesFromPointer n ptr = do
        s <- S.unsafePackCStringLen (castPtr ptr, n)
        return $! L.fromChunks [s]
    
    -- Dummies, since they were not provided 
    image = undefined
    lengthOfImageData = 10^3
    
    -- Load a PNG, and compress it, writing it back to disk
    main = do
        bytes <- withStorableArray
            (PNG.imageData image)
            (bytesFromPointer lengthOfImageData)
        L.writeFile "foo" . compress $ bytes
    

    I'm using the O(1) version, that just repackages the Ptr from the StorableArray. You might wish to copy it first, via "packCStringLen".