Я пытаюсь загрузить файл PNG, получить несжатые байты RGBA, затем отправить их в gzip или zlib пакеты.
pngload пакет возвращает данные изображения как (StorableArray (Интервал, Интервал) Word8), и пакеты сжатия берут ленивый ByteStrings. Поэтому я пытаюсь создать (StorableArray (Интервал, Интервал) 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, но существует много "небезопасных" функций там.
Любая справка здесь ценилась бы; я справедливо озадачен.
Как правило, упаковка и распаковка - плохая идея для производительности. Если у вас есть 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".
Проблема с вашим "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]
, потому что все эти действия происходят в монаде ввода-вывода, они выполняются по порядку. Это означает, что каждый элемент выходного списка должен быть создан до создания списка, и лень никогда не сможет вам помочь.
Даже если список был составлен лениво, как указывает Дон Стюарт, функция «упаковки» все равно испортит вашу работу.Проблема с «pack» в том, что ему нужно знать, сколько элементов находится в списке, чтобы выделить правильный объем памяти. Чтобы найти длину списка, программе необходимо пройти его до конца. Из-за необходимости вычисления длины список должен быть полностью загружен, прежде чем его можно будет упаковать в байтовую строку.
Я считаю "mapM" вместе с "pack" запахом кода. Иногда вы можете заменить «mapM» на «mapM_», но в этом случае лучше использовать функции создания байтовой строки, например «packCStringLen».