2010-06-27 4 views
6

Ich versuche eine PNG-Datei zu laden, die unkomprimierten RGBA-Bytes zu erhalten und sie dann an die gzip- oder zlib-Pakete zu senden.Wie kann ich ein (StorableArray (Int, Int) Word8) in einen faulen ByteString konvertieren?

Das pngload-Paket gibt Bilddaten als (StorableArray (Int, Int) Word8) zurück und die Komprimierungspakete nehmen faule ByteStrings. Daher versuche ich eine Funktion (StorableArray (Int, Int) Word8 -> ByteString) zu erstellen.

Bisher habe ich versucht, die folgenden:

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

Dies bewirkt, dass der Stapel aus dem Speicher zu laufen, so klar mache ich etwas sehr falsch. Es gibt mehr Dinge, die ich mit PTRs und ForeignPtrs ausprobieren könnte, aber es gibt viele "unsichere" Funktionen darin.

Jede Hilfe hier würde geschätzt werden; Ich bin ziemlich ratlos.

Antwort

7

Im Allgemeinen sind das Packen und Entpacken eine schlechte Idee für die Leistung. Wenn Sie einen PTR und eine Länge in Bytes haben, können Sie eine strenge bytestring auf zwei verschiedene Arten erzeugen:

So:

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 

Ich benutze die O (1) -Version, die das Ptr einfach aus dem StorableArray umpackt. Vielleicht möchten Sie es zuerst kopieren, über "packCStringLen".

+0

Das funktioniert sehr gut. Danke für die Hilfe! –

3

Das Problem mit Ihrem "BytesFromPointer" besteht darin, dass Sie eine gepackte Repräsentation, das StorableArray von pngload, nehmen und diese in eine andere gepackte Repräsentation, einen ByteString, konvertieren möchten. Manchmal bedeutet Faulheit, dass die Zwischenliste nicht im Speicher erstellt wird, aber das ist hier nicht der Fall.

Die Funktion "mapM" ist der erste Täter. Wenn Sie mapM (peekByteOff pointer) [0..(count-1)] erweitern Sie

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

bekommen, weil diese Aktionen alle innerhalb des IO Monade auftreten, werden diese in der Reihenfolge ausgeführt. Das bedeutet, dass jedes Element der Ausgabeliste erstellt werden muss, bevor die Liste erstellt wird, und Faulheit hat nie die Möglichkeit, Ihnen zu helfen.

Auch wenn die Liste träge konstruiert wurde, wie Don Stewart darauf hinweist, wird die "Pack" -Funktion immer noch Ihre Leistung ruinieren. Das Problem mit "pack" ist, dass es wissen muss, wie viele Elemente in der Liste sind, um die richtige Menge an Speicher zuzuordnen. Um die Länge einer Liste zu finden, muss das Programm sie bis zum Ende durchlaufen. Wegen der Notwendigkeit, die Länge zu berechnen, muss die Liste vollständig geladen werden, bevor sie in einen Byte-String gepackt werden kann.

Ich halte "mapM", zusammen mit "pack", für einen Code-Geruch. Manchmal können Sie "mapM" durch "mapM_" ersetzen, aber in diesem Fall ist es besser, die Funktionen zum Erstellen von Bytefolgen zu verwenden, z. "packCStringLen".