2010-06-27 32 views
6

我想加载一个PNG文件,得到未压缩的RGBA字节,然后将它们发送到gzip或zlib软件包。如何将一个(StorableArray(Int,Int)Word8)转换为一个懒惰的ByteString?

pngload包将图像数据作为(StorableArray(Int,Int)Word8)返回,并且压缩包会使用惰性ByteStrings。因此,我试图构建一个(StorableArray(Int,Int)Word8 - > ByteString)函数。

到目前为止,我已经试过如下:

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

这将导致堆栈耗尽内存,这么清楚我做的东西非常错误的。我可以用Ptr和ForeignPtr尝试更多的东西,但是那里有很多“不安全”的功能。

任何帮助在这里将不胜感激;我很难过。

回答

7

一般来说,打包和解压缩对于性能来说是个坏主意。如果你有一个PTR,并以字节为单位的长度,你可以用两种不同的方式产生了严格的字节串:

像这样:

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 

我正在使用O(1)版本,它只是重新包装StorableArray中的Ptr。您可能希望先通过“packCStringLen”复制它。

+0

这工作得很好。谢谢您的帮助! – 2010-06-27 18:13:48

3

“bytesFromPointer”的问题在于你从pngload中取出一个打包表示,StorableArray,并且想要将它转换为另一个打包表示,一个ByteString,通过中间列表。有时懒惰意味着中间名单不会在记忆中建立,但在这里并不是这样。

函数“mapM”是第一个罪犯。如果展开mapM (peekByteOff pointer) [0..(count-1)]

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

,因为这些行动的所有IO单子内发生,他们按顺序执行。这意味着输出列表中的每个元素都必须在构建列表之前构建,懒惰永远不会有机会帮助您。

即使列表是懒散地构建的,正如Don Stewart所指出的那样,“pack”函数仍然会破坏你的性能。 “pack”的问题在于它需要知道列表中有多少元素才能分配正确数量的内存。要查找列表的长度,程序需要遍历它到最后。由于计算长度的必要性,在列表打包成字节串之前,需要完整地加载列表。

我认为“mapM”和“pack”一起是一种代码味道。有时你可以用“mapM_”替换“mapM”,但在这种情况下,最好使用字节串创建函数,例如“packCStringLen”。