Как я могу преобразовать (StorableArray (Int, Int) Word8) в ленивую ByteString?

Я пытаюсь загрузить файл PNG, получить несжатые байты RGBA, а затем отправить их в пакеты gzip или zlib.

Пакет 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, но там много "небезопасных" функций.

Любая помощь здесь будет оценена по достоинству; Я довольно озадачен.


person Keith Holman    schedule 27.06.2010    source источник


Ответы (2)


Как правило, упаковка и распаковка — плохая идея для производительности. Если у вас есть 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), которая просто переупаковывает Ptr из StorableArray. Вы можете сначала скопировать его через "packCStringLen".

person Don Stewart    schedule 27.06.2010

Проблема с вашим «bytesFromPointer» заключается в том, что вы берете упакованное представление, StorableArray из pngload, и хотите преобразовать его в другое упакованное представление, 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, они выполняются по порядку. Это означает, что каждый элемент выходного списка должен быть построен до того, как список будет построен, и лень никогда не сможет вам помочь.

Даже если список был составлен лениво, как отмечает Дон Стюарт, функция «упаковать» все равно испортит вашу производительность. Проблема с пакетом заключается в том, что ему необходимо знать, сколько элементов находится в списке, чтобы выделить правильный объем памяти. Чтобы найти длину списка, программа должна пройти его до конца. Из-за необходимости вычисления длины список должен быть полностью загружен, прежде чем его можно будет упаковать в строку байтов.

Я считаю, что «mapM», наряду с «pack», является запахом кода. Иногда вы можете заменить «mapM» на «mapM_», но в этом случае лучше использовать функции создания байтовых строк, например. "packCStringLen".

person John L    schedule 27.06.2010