6

我正在尝试加载 PNG 文件,获取未压缩的 RGBA 字节,然后将它们发送到 gzip 或 zlib 包。

pngload 包以 (StorableArray (Int, Int) Word8) 形式返回图像数据,压缩包采用惰性字节字符串。因此,我正在尝试构建一个 (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 尝试更多的东西,但是那里有很多“不安全”的功能。

任何帮助将不胜感激;我很困惑。

4

2 回答 2

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”复制它。

于 2010-06-27T17:03:12.577 回答
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 monad 中,所以它们是按顺序执行的。这意味着必须在构造列表之前构造输出列表的每个元素,并且懒惰永远没有机会帮助您。

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

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

于 2010-06-27T18:52:34.443 回答