2010-06-27 10 views
6

Estoy tratando de cargar un archivo PNG, obtener los bytes RGBA sin comprimir y luego enviarlos a los paquetes gzip o zlib.¿Cómo puedo convertir un (StorableArray (Int, Int) Word8) en un byteString perezoso?

El paquete pngload devuelve datos de imagen como (StorableArray (Int, Int) Word8), y los paquetes de compresión toman ByteStrings perezosos. Por lo tanto, estoy intentando construir una función (StorableArray (Int, Int) Word8 -> ByteString).

Hasta ahora, he intentado el siguiente:

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

Esto hace que la pila que se quede sin memoria, tan claramente que estoy haciendo algo muy malo. Hay más cosas que podría probar con Ptr's y ForeignPtr's, pero hay muchas funciones "inseguras" allí.

Cualquier ayuda aquí sería apreciada; Estoy bastante perplejo.

Respuesta

7

En general, empaquetar y desempaquetar son una mala idea para el rendimiento. Si usted tiene un Ptr, y una longitud en bytes, se puede generar una cadena de bytes estricta de dos maneras diferentes:

De esta manera:

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 

Estoy usando la versión O (1), que simplemente reempaca el Ptr del StorableArray. Es posible que desee copiarlo primero, a través de "packCStringLen".

+0

Esto funciona muy bien. ¡Gracias por la ayuda! –

3

El problema con su "bytesFromPointer" es que toma una representación empaquetada, la StorableArray de pngload, y desea convertirla a otra representación empaquetada, una ByteString, pasando por una lista intermedia. A veces, la pereza significa que la lista intermedia no se construirá en la memoria, pero ese no es el caso aquí.

La función "mapM" es la primera ofensa. Si expande mapM (peekByteOff pointer) [0..(count-1)] se obtiene

el0 <- peekByteOff pointer 0 
el1 <- peekByteOff pointer 1 
el2 <- peekByteOff pointer 2 
... 
eln <- peekByteOff pointer (count-1) 
return [el0,el1,el2,...eln] 

porque estas acciones ocurren todos dentro de la mónada IO, que se ejecutan en orden. Esto significa que cada elemento de la lista de salida debe construirse antes de que se construya la lista y la pereza nunca tiene la oportunidad de ayudarlo.

Incluso si la lista se construyó perezosamente, como señala Don Stewart, la función "paquete" aún arruinará su rendimiento. El problema con "paquete" es que necesita saber cuántos elementos hay en la lista para asignar la cantidad correcta de memoria. Para encontrar la longitud de una lista, el programa debe recorrerla hasta el final. Debido a la necesidad de calcular la longitud, la lista deberá estar completamente cargada antes de que pueda empaquetarse en una cadena de bytes.

Considero que "mapM", junto con "paquete", es un olor a código. A veces puede reemplazar "mapM" por "mapM_", pero en este caso es mejor usar las funciones de creación de bytes, por ejemplo. "packCStringLen".