JuicyPixels (empty) → 1.0
raw patch · 12 files changed
+3036/−0 lines, 12 filesdep +arraydep +basedep +bytestringsetup-changed
Dependencies added: array, base, bytestring, cereal, mtl, transformers, zlib
Files
- Codec/Picture.hs +80/−0
- Codec/Picture/Bitmap.hs +312/−0
- Codec/Picture/Jpg.hs +819/−0
- Codec/Picture/Jpg/DefaultTable.hs +186/−0
- Codec/Picture/Jpg/FastIdct.hs +248/−0
- Codec/Picture/Png.hs +462/−0
- Codec/Picture/Png/Export.hs +99/−0
- Codec/Picture/Png/Type.hs +315/−0
- Codec/Picture/Types.hs +431/−0
- JuicyPixels.cabal +52/−0
- LICENSE +30/−0
- Setup.hs +2/−0
+ Codec/Picture.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE FlexibleContexts #-} +-- | Main module exporting import/export functions into various +-- image formats. +-- +-- To use the library without thinking about it, look after 'decodeImage' and +-- 'readImage'. +-- +-- Generally, the read* functions read the images from a file and try to decode +-- it, and the decode* functions try to decode a bytestring. +-- +-- For an easy image writing use the write* functions and writeDynamic* functions. +module Codec.Picture ( + -- * Generic functions + readImage + , decodeImage + -- * Specific image format functions + -- ** Bitmap handling + , BmpEncodable + , writeBitmap + , encodeBitmap + , decodeBitmap + , encodeDynamicBitmap + , writeDynamicBitmap + + -- ** Jpeg handling + , readJpeg + , decodeJpeg + + -- ** Png handling + , PngSavable( .. ) + , readPng + , decodePng + , writePng + , encodeDynamicPng + , writeDynamicPng + + -- * Image types and pixel types + -- ** Image + , Image + , DynamicImage( .. ) + -- ** Pixels + , Pixel( .. ) + , Pixel8 + , PixelYA8( .. ) + , PixelRGB8( .. ) + , PixelRGBA8( .. ) + , PixelYCbCr8( .. ) + ) where + +import Control.Applicative( (<$>) ) +import Codec.Picture.Bitmap +import Codec.Picture.Jpg( readJpeg, decodeJpeg ) +import Codec.Picture.Png( PngSavable( .. ), readPng, decodePng, writePng + , encodeDynamicPng , writeDynamicPng ) +import Codec.Picture.Types + +import qualified Data.ByteString as B + +-- | Return the first Right thing, accumulating error +eitherLoad :: c -> [(String, c -> Either String b)] -> Either String b +eitherLoad v = inner "" + where inner errAcc [] = Left $ "Cannot load file\n" ++ errAcc + inner errAcc ((hdr, f) : rest) = case f v of + Left err -> inner (errAcc ++ hdr ++ " " ++ err ++ "\n") rest + Right rez -> Right rez + +-- | Load an image file without even thinking about it, it does everything +-- as 'decodeImage' +readImage :: FilePath -> IO (Either String DynamicImage) +readImage path = decodeImage <$> B.readFile path + +-- | If you want to decode an image in a bytestring without even thinking +-- in term of format or whatever, this is the function to use. It will try +-- to decode in each known format and if one decoding succeed will return +-- the decoded image in it's own colorspace +decodeImage :: B.ByteString -> Either String DynamicImage +decodeImage str = eitherLoad str [("Jpeg", decodeJpeg) + ,("PNG", decodePng) + ] +
+ Codec/Picture/Bitmap.hs view
@@ -0,0 +1,312 @@+{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeSynonymInstances #-} +-- | Modules used for Bitmap file (.bmp) file loading and writing +module Codec.Picture.Bitmap( -- * Functions + writeBitmap + , encodeBitmap + , decodeBitmap + , encodeDynamicBitmap + , writeDynamicBitmap + -- * Accepted formt in output + , BmpEncodable( ) + ) where +import Control.Monad( when, forM_ ) +import Data.Array.Base( unsafeAt, unsafeWrite ) +import Data.Array.Unboxed( IArray ) +import Data.Serialize( Serialize( .. ) + , putWord8, putWord16le, putWord32le + , getWord16le, getWord32le + , Get, Put, runGet, runPut + , remaining, getBytes ) +import Data.Word( Word32, Word16, Word8 ) +import Data.Array.ST( MArray, runSTUArray, newArray ) +import qualified Data.ByteString as B + +import Codec.Picture.Types + +data BmpHeader = BmpHeader + { magicIdentifier :: !Word16 + , fileSize :: !Word32 -- ^ in bytes + , reserved1 :: !Word16 + , reserved2 :: !Word16 + , dataOffset :: !Word32 + } + +bitmapMagicIdentifier :: Word16 +bitmapMagicIdentifier = 0x4D42 + +instance Serialize BmpHeader where + put hdr = do + putWord16le $ magicIdentifier hdr + putWord32le $ fileSize hdr + putWord16le $ reserved1 hdr + putWord16le $ reserved2 hdr + putWord32le $ dataOffset hdr + + get = do + ident <- getWord16le + when (ident /= bitmapMagicIdentifier) + (fail "Invalid Bitmap magic identifier") + fsize <- getWord32le + r1 <- getWord16le + r2 <- getWord16le + offset <- getWord32le + return BmpHeader + { magicIdentifier = ident + , fileSize = fsize + , reserved1 = r1 + , reserved2 = r2 + , dataOffset = offset + } + + +data BmpInfoHeader = BmpInfoHeader + { size :: !Word32 -- Header size in bytes + , width :: !Word32 + , height :: !Word32 + , planes :: !Word16 -- Number of colour planes + , bitPerPixel :: !Word16 + , bitmapCompression :: !Word32 + , byteImageSize :: !Word32 + , xResolution :: !Word32 -- ^ Pixels per meter + , yResolution :: !Word32 -- ^ Pixels per meter + , colorCount :: !Word32 + , importantColours :: !Word32 + } + +sizeofBmpHeader, sizeofBmpInfo :: Word32 +sizeofBmpHeader = 2 + 4 + 2 + 2 + 4 +sizeofBmpInfo = 3 * 4 + 2 * 2 + 6 * 4 + +instance Serialize BmpInfoHeader where + put hdr = do + putWord32le $ size hdr + putWord32le $ width hdr + putWord32le $ height hdr + putWord16le $ planes hdr + putWord16le $ bitPerPixel hdr + putWord32le $ bitmapCompression hdr + putWord32le $ byteImageSize hdr + putWord32le $ xResolution hdr + putWord32le $ yResolution hdr + putWord32le $ colorCount hdr + putWord32le $ importantColours hdr + + get = do + readSize <- getWord32le + readWidth <- getWord32le + readHeight <- getWord32le + readPlanes <- getWord16le + readBitPerPixel <- getWord16le + readBitmapCompression <- getWord32le + readByteImageSize <- getWord32le + readXResolution <- getWord32le + readYResolution <- getWord32le + readColorCount <- getWord32le + readImportantColours <- getWord32le + return BmpInfoHeader { + size = readSize, + width = readWidth, + height = readHeight, + planes = readPlanes, + bitPerPixel = readBitPerPixel, + bitmapCompression = readBitmapCompression, + byteImageSize = readByteImageSize, + xResolution = readXResolution, + yResolution = readYResolution, + colorCount = readColorCount, + importantColours = readImportantColours + } + +newtype BmpPalette = BmpPalette [(Word8, Word8, Word8, Word8)] + +putPalette :: BmpPalette -> Put +putPalette (BmpPalette p) = mapM_ (\(r, g, b, a) -> put r >> put g >> put b >> put a) p + +-- | All the instance of this class can be written as a bitmap file +-- using this library. +class BmpEncodable pixel where + bitsPerPixel :: pixel -> Int + bmpEncode :: Image pixel -> Put + defaultPalette :: pixel -> BmpPalette + defaultPalette _ = BmpPalette [] + +{-# INLINE (!!!) #-} +(!!!) :: (IArray array e) => array Int e -> Int -> e +(!!!) = unsafeAt -- (!) + +{-# INLINE stridePut #-} +stridePut :: Int -> Put +stridePut 0 = return () +stridePut 1 = putWord8 0 +stridePut n = putWord8 0 >> stridePut (n - 1) + +instance BmpEncodable Pixel8 where + defaultPalette _ = BmpPalette [(x,x,x, 255) | x <- [0 .. 255]] + bitsPerPixel _ = 8 + bmpEncode (Image {imageWidth = w, imageHeight = h, imageData = arr}) = putLine $ h - 1 + where stride = fromIntegral $ linePadding 8 w + + putLine line | line < 0 = return () + putLine line = do + let lineIdx = line * w + inner col | col >= w = return () + | otherwise = put (arr !!! (lineIdx + col)) >> inner (col + 1) + inner 0 + stridePut stride + putLine (line - 1) + +instance BmpEncodable PixelRGBA8 where + bitsPerPixel _ = 32 + bmpEncode (Image {imageWidth = w, imageHeight = h, imageData = arr}) = putLine (h - 1) + where putLine line | line < 0 = return () + putLine line = do + let initialIndex = line * w * 4 + inner col _ | col >= w = return () + inner col readIdx = do + put (arr !!! (readIdx + 2)) + put (arr !!! (readIdx + 1)) + put (arr !!! readIdx) + put (arr !!! (readIdx + 3)) + inner (col + 1) (readIdx + 4) + inner 0 initialIndex + putLine (line - 1) + +instance BmpEncodable PixelRGB8 where + bitsPerPixel _ = 24 + bmpEncode (Image {imageWidth = w, imageHeight = h, imageData = arr}) = putLine (h - 1) + where stride = fromIntegral . linePadding 24 $ w + putLine line | line < 0 = return () + putLine line = do + let initialIndex = line * w * 3 + inner col _ | col >= w = return () + inner col readIdx = do + put (arr !!! (readIdx + 2)) + put (arr !!! (readIdx + 1)) + put (arr !!! readIdx) + inner (col + 1) (readIdx + 3) + inner 0 initialIndex + stridePut stride + putLine (line - 1) + +{-# INLINE (.<-.) #-} +(.<-.) :: (MArray array e m) => array Int e -> Int -> e -> m () +(.<-.) = unsafeWrite + +decodeImageRGB8 :: BmpInfoHeader -> B.ByteString -> Image PixelRGB8 +decodeImageRGB8 (BmpInfoHeader { width = w, height = h }) str = Image wi hi stArray + where wi = fromIntegral w + hi = fromIntegral h + stArray = runSTUArray $ do + arr <- newArray (0, fromIntegral $ w * h * 3) 128 + forM_ [hi - 1, hi - 2 .. 0] (readLine arr) + return arr + + stride = linePadding 24 wi + readLine arr line = + let readIndex = (wi * 3 + stride) * line + lastIndex = wi * (hi - 1 - line + 1) * 3 + writeIndex = wi * (hi - 1 - line) * 3 + + inner _ writeIdx | writeIdx >= lastIndex = return () + inner readIdx writeIdx = do + (arr .<-. writeIdx ) (str `B.index` (readIdx + 2)) + (arr .<-. (writeIdx + 1)) (str `B.index` (readIdx + 1)) + (arr .<-. (writeIdx + 2)) (str `B.index` readIdx) + inner (readIdx + 3) (writeIdx + 3) + + in inner readIndex writeIndex + + +-- | Try to decode a bitmap image. +-- Right now this function can output the following pixel types : +-- +-- * PixelRGB8 +-- +decodeBitmap :: B.ByteString -> Either String DynamicImage +decodeBitmap str = flip runGet str $ do + _hdr <- (get :: Get BmpHeader) + bmpHeader <- (get :: Get BmpInfoHeader) + case (bitPerPixel bmpHeader, planes bmpHeader, + bitmapCompression bmpHeader) of + -- (32, 1, 0) -> {- ImageRGBA8 <$>-} fail "Meuh" + (24, 1, 0) -> do + rest <- remaining >>= getBytes + return . ImageRGB8 $ decodeImageRGB8 bmpHeader rest + _ -> fail "Can't handle BMP file" + + +-- | Write an image in a file use the bitmap format. +writeBitmap :: (BmpEncodable pixel) + => FilePath -> Image pixel -> IO () +writeBitmap filename img = B.writeFile filename $ encodeBitmap img + +linePadding :: Int -> Int -> Int +linePadding bpp imgWidth = (4 - (bytesPerLine `mod` 4)) `mod` 4 + where bytesPerLine = imgWidth * (fromIntegral bpp `div` 8) + +-- | Encode an image into a bytestring in .bmp format ready to be written +-- on disk. +encodeBitmap :: forall pixel. (BmpEncodable pixel) => Image pixel -> B.ByteString +encodeBitmap = encodeBitmapWithPalette (defaultPalette (undefined :: pixel)) + + +-- | Write a dynamic image in a .bmp image file if possible. +-- The same restriction as encodeDynamicBitmap apply. +writeDynamicBitmap :: FilePath -> DynamicImage -> IO (Either String Bool) +writeDynamicBitmap path img = case encodeDynamicBitmap img of + Left err -> return $ Left err + Right b -> B.writeFile path b >> return (Right True) + +-- | Encode a dynamic image in bmp if possible, supported pixel type are : +-- +-- - RGB8 +-- +-- - RGBA8 +-- +-- - Y8 +-- +encodeDynamicBitmap :: DynamicImage -> Either String B.ByteString +encodeDynamicBitmap (ImageRGB8 img) = Right $ encodeBitmap img +encodeDynamicBitmap (ImageRGBA8 img) = Right $ encodeBitmap img +encodeDynamicBitmap (ImageY8 img) = Right $ encodeBitmap img +encodeDynamicBitmap _ = Left "Unsupported image format for bitmap export" + + +-- | Convert an image to a bytestring ready to be serialized. +encodeBitmapWithPalette :: forall pixel. (BmpEncodable pixel) + => BmpPalette -> Image pixel -> B.ByteString +encodeBitmapWithPalette pal@(BmpPalette palette) img = + runPut $ put hdr >> put info >> putPalette pal >> bmpEncode img + where imgWidth = fromIntegral $ imageWidth img + imgHeight = fromIntegral $ imageHeight img + + paletteSize = fromIntegral $ length palette + bpp = bitsPerPixel (undefined :: pixel) + padding = linePadding bpp (imgWidth + 1) + imagePixelSize = fromIntegral $ (imgWidth + padding) * imgHeight * 4 + hdr = BmpHeader { + magicIdentifier = bitmapMagicIdentifier, + fileSize = sizeofBmpHeader + sizeofBmpInfo + 4 * paletteSize + imagePixelSize, + reserved1 = 0, + reserved2 = 0, + dataOffset = sizeofBmpHeader + sizeofBmpInfo + 4 * paletteSize + } + + info = BmpInfoHeader { + size = sizeofBmpInfo, + width = fromIntegral $ imgWidth, + height = fromIntegral $ imgHeight, + planes = 1, + bitPerPixel = fromIntegral $ bpp, + bitmapCompression = 0, -- no compression + byteImageSize = imagePixelSize, + xResolution = 0, + yResolution = 0, + colorCount = 0, + importantColours = paletteSize + } + +
+ Codec/Picture/Jpg.hs view
@@ -0,0 +1,819 @@+{-# LANGUAGE TupleSections #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE FlexibleInstances #-} +-- | Module used for JPEG file loading and writing. +module Codec.Picture.Jpg( readJpeg, decodeJpeg ) where + +import Control.Applicative( (<$>), (<*>)) +import Control.Monad( when, replicateM, forM, forM_, foldM_, unless ) +import Control.Monad.ST( ST ) +import Control.Monad.Trans( lift ) +import qualified Control.Monad.Trans.State.Strict as S + +import Data.Array.Base( unsafeAt, unsafeRead, unsafeWrite ) +import Data.List( find, foldl' ) +import Data.Bits( (.|.), (.&.), shiftL, shiftR ) +import Data.Int( Int16, Int32 ) +import Data.Word(Word8, Word16, Word32) +import Data.Serialize( Serialize(..), Get, Put + , getWord8, putWord8 + , getWord16be, putWord16be + , remaining, lookAhead, skip + , getBytes, decode ) +import Data.Maybe( fromJust ) +import Data.Array.Unboxed( IArray, Array, UArray, elems, listArray) +import Data.Array.ST( STUArray, MArray, newArray, runSTUArray ) +import qualified Data.ByteString as B + +import Codec.Picture.Types +import Codec.Picture.Jpg.DefaultTable +import Codec.Picture.Jpg.FastIdct + + +-------------------------------------------------- +---- Types +-------------------------------------------------- +data JpgFrameKind = + JpgBaselineDCTHuffman + | JpgExtendedSequentialDCTHuffman + | JpgProgressiveDCTHuffman + | JpgLosslessHuffman + | JpgDifferentialSequentialDCTHuffman + | JpgDifferentialProgressiveDCTHuffman + | JpgDifferentialLosslessHuffman + | JpgExtendedSequentialArithmetic + | JpgProgressiveDCTArithmetic + | JpgLosslessArithmetic + | JpgDifferentialSequentialDCTArithmetic + | JpgDifferentialProgressiveDCTArithmetic + | JpgDifferentialLosslessArithmetic + | JpgQuantizationTable + | JpgHuffmanTableMarker + | JpgStartOfScan + | JpgAppSegment Word8 + | JpgExtensionSegment Word8 + + | JpgRestartInterval + deriving (Eq, Show) + + +data JpgFrame = + JpgAppFrame !Word8 B.ByteString + | JpgExtension !Word8 B.ByteString + | JpgQuantTable ![JpgQuantTableSpec] + | JpgHuffmanTable ![(JpgHuffmanTableSpec, HuffmanTree)] + | JpgScanBlob !JpgScanHeader !B.ByteString + | JpgScans !JpgFrameKind !JpgFrameHeader + | JpgIntervalRestart !Word16 + deriving Show + +data JpgFrameHeader = JpgFrameHeader + { jpgFrameHeaderLength :: !Word16 + , jpgSamplePrecision :: !Word8 + , jpgHeight :: !Word16 + , jpgWidth :: !Word16 + , jpgImageComponentCount :: !Word8 + , jpgComponents :: [JpgComponent] + } + deriving Show + +data JpgComponent = JpgComponent + { componentIdentifier :: !Word8 + -- | Stored with 4 bits + , horizontalSamplingFactor :: !Word8 + -- | Stored with 4 bits + , verticalSamplingFactor :: !Word8 + , quantizationTableDest :: !Word8 + } + deriving Show + +data JpgImage = JpgImage { jpgFrame :: [JpgFrame]} + deriving Show + +data JpgScanSpecification = JpgScanSpecification + { componentSelector :: !Word8 + -- | Encoded as 4 bits + , dcEntropyCodingTable :: !Word8 + -- | Encoded as 4 bits + , acEntropyCodingTable :: !Word8 + + } + deriving Show + +data JpgScanHeader = JpgScanHeader + { scanLength :: !Word16 + , scanComponentCount :: !Word8 + , scans :: [JpgScanSpecification] + + -- | (begin, end) + , spectralSelection :: (Word8, Word8) + + -- | Encoded as 4 bits + , successiveApproxHigh :: !Word8 + + -- | Encoded as 4 bits + , successiveApproxLow :: !Word8 + } + deriving Show + +data JpgQuantTableSpec = JpgQuantTableSpec + { -- | Stored on 4 bits + quantPrecision :: !Word8 + + -- | Stored on 4 bits + , quantDestination :: !Word8 + + , quantTable :: MacroBlock Int16 + } + deriving Show + +-- | Type introduced only to avoid some typeclass overlapping +-- problem +newtype TableList a = TableList [a] + +class SizeCalculable a where + calculateSize :: a -> Int + +instance (SizeCalculable a, Serialize a) => Serialize (TableList a) where + put (TableList lst) = do + putWord16be . fromIntegral $ sum [calculateSize table | table <- lst] + mapM_ put lst + + get = TableList <$> (getWord16be >>= \s -> innerParse (fromIntegral s - 2)) + where innerParse :: Int -> Get [a] + innerParse 0 = return [] + innerParse size = do + onStart <- fromIntegral <$> remaining + table <- get + onEnd <- fromIntegral <$> remaining + (table :) <$> innerParse (size - (onStart - onEnd)) + +instance SizeCalculable JpgQuantTableSpec where + calculateSize table = + 1 + (fromIntegral (quantPrecision table) + 1) * 64 + +instance Serialize JpgQuantTableSpec where + put table = do + let precision = quantPrecision table + put4BitsOfEach precision (quantDestination table) + forM_ (elems $ quantTable table) $ \coeff -> + if precision == 0 then putWord8 $ fromIntegral coeff + else putWord16be $ fromIntegral coeff + + get = do + (precision, dest) <- get4BitOfEach + coeffs <- replicateM 64 $ if precision == 0 + then fromIntegral <$> getWord8 + else fromIntegral <$> getWord16be + return JpgQuantTableSpec + { quantPrecision = precision + , quantDestination = dest + , quantTable = listArray (0, 63) coeffs + } + +data JpgHuffmanTableSpec = JpgHuffmanTableSpec + { -- | 0 : DC, 1 : AC, stored on 4 bits + huffmanTableClass :: !DctComponent + -- | Stored on 4 bits + , huffmanTableDest :: !Word8 + + , huffSizes :: !(UArray Word32 Word8) + , huffCodes :: !(Array Word32 (UArray Int Word8)) + } + deriving Show + +buildPackedHuffmanTree :: Array Word32 (UArray Int Word8) -> HuffmanTree +buildPackedHuffmanTree = buildHuffmanTree . map elems . elems + +-- | Decode a list of huffman values, not optimized for speed, but it +-- should work. +huffmanDecode :: HuffmanTree -> BoolReader s Word8 +huffmanDecode originalTree = getNextBit >>= huffDecode originalTree + where huffDecode Empty _ = return 0 + huffDecode (Branch (Leaf v) _) False = return v + huffDecode (Branch l _ ) False = getNextBit >>= huffDecode l + huffDecode (Branch _ (Leaf v)) True = return v + huffDecode (Branch _ r ) True = getNextBit >>= huffDecode r + huffDecode (Leaf v) _ = return v + +-- | Drop all bit until the bit of indice 0, usefull to parse restart +-- marker, as they are byte aligned, but Huffman might not. +byteAlign :: BoolReader s () +byteAlign = do + (idx, _, chain) <- S.get + when (idx /= 7) (setDecodedString chain) + +-- | Bitify a list of things to decode. +setDecodedString :: B.ByteString -> BoolReader s () +setDecodedString str = case B.uncons str of + Nothing -> S.put (maxBound, 0, B.empty) + Just (0xFF, rest) -> case B.uncons rest of + Nothing -> S.put (maxBound, 0, B.empty) + Just (0x00, afterMarker) -> S.put (7, 0xFF, afterMarker) + Just (_ , afterMarker) -> setDecodedString afterMarker + Just (v, rest) -> S.put ( 7, v, rest) + +{-# INLINE getNextBit #-} +getNextBit :: BoolReader s Bool +getNextBit = do + (idx, v, chain) <- S.get + let val = (v .&. (1 `shiftL` idx)) /= 0 + if idx == 0 + then setDecodedString chain + else S.put (idx - 1, v, chain) + return val + +-------------------------------------------------- +---- Serialization instances +-------------------------------------------------- +commonMarkerFirstByte :: Word8 +commonMarkerFirstByte = 0xFF + +checkMarker :: Word8 -> Word8 -> Get () +checkMarker b1 b2 = do + rb1 <- getWord8 + rb2 <- getWord8 + when (rb1 /= b1 || rb2 /= b2) + (fail "Invalid marker used") + +eatUntilCode :: Get () +eatUntilCode = do + code <- lookAhead getWord8 + unless (code == 0xFF) + (skip 1 >> eatUntilCode) + +instance SizeCalculable JpgHuffmanTableSpec where + calculateSize table = 1 + 16 + sum [fromIntegral e | e <- elems $ huffSizes table] + +instance Serialize JpgHuffmanTableSpec where + put = error "Unimplemented" + get = do + (huffClass, huffDest) <- get4BitOfEach + sizes <- replicateM 16 getWord8 + codes <- forM sizes $ \s -> do + let si = fromIntegral s + listArray (0, si - 1) <$> replicateM (fromIntegral s) getWord8 + return JpgHuffmanTableSpec + { huffmanTableClass = + if huffClass == 0 then DcComponent else AcComponent + , huffmanTableDest = huffDest + , huffSizes = listArray (0, 15) sizes + , huffCodes = listArray (0, 15) codes + } + +instance Serialize JpgImage where + put = error "Unimplemented" + get = do + let startOfImageMarker = 0xD8 + -- endOfImageMarker = 0xD9 + checkMarker commonMarkerFirstByte startOfImageMarker + eatUntilCode + frames <- parseFrames + {-checkMarker commonMarkerFirstByte endOfImageMarker-} + return JpgImage { jpgFrame = frames } + +takeCurrentFrame :: Get B.ByteString +takeCurrentFrame = do + size <- getWord16be + getBytes (fromIntegral size - 2) + +parseFrames :: Get [JpgFrame] +parseFrames = do + kind <- get + case kind of + JpgAppSegment c -> + (\frm lst -> JpgAppFrame c frm : lst) <$> takeCurrentFrame <*> parseFrames + JpgExtensionSegment c -> + (\frm lst -> JpgExtension c frm : lst) <$> takeCurrentFrame <*> parseFrames + JpgQuantizationTable -> + (\(TableList quants) lst -> JpgQuantTable quants : lst) <$> get <*> parseFrames + JpgRestartInterval -> + (\(RestartInterval i) lst -> JpgIntervalRestart i : lst) <$> get <*> parseFrames + JpgHuffmanTableMarker -> + (\(TableList huffTables) lst -> + JpgHuffmanTable [(t, buildPackedHuffmanTree $ huffCodes t) | t <- huffTables] : lst) + <$> get <*> parseFrames + JpgStartOfScan -> + (\frm imgData -> [JpgScanBlob frm imgData]) + <$> get <*> (remaining >>= getBytes) + + _ -> (\hdr lst -> JpgScans kind hdr : lst) <$> get <*> parseFrames + +secondStartOfFrameByteOfKind :: JpgFrameKind -> Word8 +secondStartOfFrameByteOfKind JpgBaselineDCTHuffman = 0xC0 +secondStartOfFrameByteOfKind JpgExtendedSequentialDCTHuffman = 0xC1 +secondStartOfFrameByteOfKind JpgProgressiveDCTHuffman = 0xC2 +secondStartOfFrameByteOfKind JpgLosslessHuffman = 0xC3 +secondStartOfFrameByteOfKind JpgDifferentialSequentialDCTHuffman = 0xC5 +secondStartOfFrameByteOfKind JpgDifferentialProgressiveDCTHuffman = 0xC6 +secondStartOfFrameByteOfKind JpgDifferentialLosslessHuffman = 0xC7 +secondStartOfFrameByteOfKind JpgExtendedSequentialArithmetic = 0xC9 +secondStartOfFrameByteOfKind JpgProgressiveDCTArithmetic = 0xCA +secondStartOfFrameByteOfKind JpgLosslessArithmetic = 0xCB +secondStartOfFrameByteOfKind JpgHuffmanTableMarker = 0xC4 +secondStartOfFrameByteOfKind JpgDifferentialSequentialDCTArithmetic = 0xCD +secondStartOfFrameByteOfKind JpgDifferentialProgressiveDCTArithmetic = 0xCE +secondStartOfFrameByteOfKind JpgDifferentialLosslessArithmetic = 0xCF +secondStartOfFrameByteOfKind JpgQuantizationTable = 0xDB +secondStartOfFrameByteOfKind JpgStartOfScan = 0xDA +secondStartOfFrameByteOfKind JpgRestartInterval = 0xDD +secondStartOfFrameByteOfKind (JpgAppSegment a) = a +secondStartOfFrameByteOfKind (JpgExtensionSegment a) = a + +instance Serialize JpgFrameKind where + put v = putWord8 0xFF >> put (secondStartOfFrameByteOfKind v) + get = do + word <- getWord8 + word2 <- getWord8 + when (word /= 0xFF) (do leftData <- remaining + fail $ "Invalid Frame marker (" ++ show word + ++ ", remaining : " ++ show leftData ++ ")") + return $ case word2 of + 0xC0 -> JpgBaselineDCTHuffman + 0xC1 -> JpgExtendedSequentialDCTHuffman + 0xC2 -> JpgProgressiveDCTHuffman + 0xC3 -> JpgLosslessHuffman + 0xC4 -> JpgHuffmanTableMarker + 0xC5 -> JpgDifferentialSequentialDCTHuffman + 0xC6 -> JpgDifferentialProgressiveDCTHuffman + 0xC7 -> JpgDifferentialLosslessHuffman + 0xC9 -> JpgExtendedSequentialArithmetic + 0xCA -> JpgProgressiveDCTArithmetic + 0xCB -> JpgLosslessArithmetic + 0xCD -> JpgDifferentialSequentialDCTArithmetic + 0xCE -> JpgDifferentialProgressiveDCTArithmetic + 0xCF -> JpgDifferentialLosslessArithmetic + 0xDA -> JpgStartOfScan + 0xDB -> JpgQuantizationTable + 0xDD -> JpgRestartInterval + a | a >= 0xF0 -> JpgExtensionSegment a + | a >= 0xE0 -> JpgAppSegment a + | otherwise -> error ("Invalid frame marker (" ++ show a ++ ")") + +put4BitsOfEach :: Word8 -> Word8 -> Put +put4BitsOfEach a b = put $ (a `shiftL` 4) .|. b + +get4BitOfEach :: Get (Word8, Word8) +get4BitOfEach = do + val <- get + return ((val `shiftR` 4) .&. 0xF, val .&. 0xF) + +newtype RestartInterval = RestartInterval Word16 + +instance Serialize RestartInterval where + put (RestartInterval i) = putWord16be 4 >> putWord16be i + get = do + size <- getWord16be + when (size /= 4) (fail "Invalid jpeg restart interval size") + RestartInterval <$> getWord16be + +instance Serialize JpgComponent where + get = do + ident <- getWord8 + (horiz, vert) <- get4BitOfEach + quantTableIndex <- getWord8 + return JpgComponent + { componentIdentifier = ident + , horizontalSamplingFactor = horiz + , verticalSamplingFactor = vert + , quantizationTableDest = quantTableIndex + } + put v = do + put $ componentIdentifier v + put4BitsOfEach (horizontalSamplingFactor v) $ verticalSamplingFactor v + put $ quantizationTableDest v + +instance Serialize JpgFrameHeader where + get = do + beginOffset <- remaining + frmHLength <- getWord16be + samplePrec <- getWord8 + h <- getWord16be + w <- getWord16be + compCount <- getWord8 + components <- replicateM (fromIntegral compCount) get + endOffset <- remaining + when (beginOffset - endOffset < fromIntegral frmHLength) + (skip $ fromIntegral frmHLength - (beginOffset - endOffset)) + return JpgFrameHeader + { jpgFrameHeaderLength = frmHLength + , jpgSamplePrecision = samplePrec + , jpgHeight = h + , jpgWidth = w + , jpgImageComponentCount = compCount + , jpgComponents = components + } + + put v = do + putWord16be $ jpgFrameHeaderLength v + putWord8 $ jpgSamplePrecision v + putWord16be $ jpgHeight v + putWord16be $ jpgWidth v + putWord8 $ jpgImageComponentCount v + mapM_ put $ jpgComponents v + +instance Serialize JpgScanSpecification where + put v = do + put $ componentSelector v + put4BitsOfEach (dcEntropyCodingTable v) $ acEntropyCodingTable v + + get = do + compSel <- get + (dc, ac) <- get4BitOfEach + return JpgScanSpecification { + componentSelector = compSel + , dcEntropyCodingTable = dc + , acEntropyCodingTable = ac + } + +instance Serialize JpgScanHeader where + get = do + thisScanLength <- getWord16be + compCount <- getWord8 + comp <- replicateM (fromIntegral compCount) get + specBeg <- get + specEnd <- get + (approxHigh, approxLow) <- get4BitOfEach + return JpgScanHeader { + scanLength = thisScanLength, + scanComponentCount = compCount, + scans = comp, + spectralSelection = (specBeg, specEnd), + successiveApproxHigh = approxHigh, + successiveApproxLow = approxLow + } + + put v = do + put $ scanLength v + put $ scanComponentCount v + mapM_ put $ scans v + put . fst $ spectralSelection v + put . snd $ spectralSelection v + put4BitsOfEach (successiveApproxHigh v) $ successiveApproxLow v + +-- | Current bit index, current value, string +type BoolState = (Int, Word8, B.ByteString) + +type BoolReader s a = S.StateT BoolState (ST s) a + +{-# INLINE (!!!) #-} +(!!!) :: (IArray array e) => array Int e -> Int -> e +(!!!) = unsafeAt + +{-# INLINE (.!!!.) #-} +(.!!!.) :: (MArray array e m) => array Int e -> Int -> m e +(.!!!.) = unsafeRead + +{-# INLINE (.<-.) #-} +(.<-.) :: (MArray array e m) => array Int e -> Int -> e -> m () +(.<-.) = unsafeWrite + +-- | Apply a quantization matrix to a macroblock +{-# INLINE deQuantize #-} +deQuantize :: MacroBlock Int16 -> MutableMacroBlock s Int16 + -> ST s (MutableMacroBlock s Int16) +deQuantize table block = dequant 0 >> return block + where updateVal i = do + val <- block .!!!. i + let quantCoeff = table !!! i + newVal = val * quantCoeff + (block .<-. i) newVal + + dequant 63 = updateVal 63 + dequant n = updateVal n >> dequant (n + 1) + +inverseDirectCosineTransform :: MutableMacroBlock s Int16 + -> ST s (MutableMacroBlock s Int16) +inverseDirectCosineTransform mBlock = + fastIdct mBlock >>= mutableLevelShift + +zigZagOrder :: MacroBlock Word8 +zigZagOrder = makeMacroBlock $ concat + [[ 0, 1, 5, 6,14,15,27,28] + ,[ 2, 4, 7,13,16,26,29,42] + ,[ 3, 8,12,17,25,30,41,43] + ,[ 9,11,18,24,31,40,44,53] + ,[10,19,23,32,39,45,52,54] + ,[20,22,33,38,46,51,55,60] + ,[21,34,37,47,50,56,59,61] + ,[35,36,48,49,57,58,62,63] + ] + +zigZagReorder :: MutableMacroBlock s Int16 -> ST s (MutableMacroBlock s Int16) +zigZagReorder block = do + zigzaged <- newArray (0, 63) 0 + let update i = do + let idx = zigZagOrder !!! i + v <- block .!!!. fromIntegral idx + (zigzaged .<-. i) v + + reorder 63 = update 63 + reorder i = update i >> reorder (i + 1) + + reorder 0 + return zigzaged + + +-- | This is one of the most important function of the decoding, +-- it form the barebone decoding pipeline for macroblock. It's all +-- there is to know for macro block transformation +decodeMacroBlock :: MacroBlock DctCoefficients + -> MutableMacroBlock s Int16 + -> ST s (MutableMacroBlock s Int16) +decodeMacroBlock quantizationTable block = + deQuantize quantizationTable block >>= zigZagReorder + >>= inverseDirectCosineTransform + +packInt :: [Bool] -> Int32 +packInt = foldl' bitStep 0 + where bitStep acc True = (acc `shiftL` 1) + 1 + bitStep acc False = acc `shiftL` 1 + +-- | Unpack an int of the given size encoded from MSB to LSB. +unpackInt :: Int32 -> BoolReader s Int32 +unpackInt bitCount = packInt <$> replicateM (fromIntegral bitCount) getNextBit + +decodeInt :: Int32 -> BoolReader s Int32 +decodeInt ssss = do + signBit <- getNextBit + let dataRange = 1 `shiftL` fromIntegral (ssss - 1) + leftBitCount = ssss - 1 + -- First following bits store the sign of the coefficient, and counted in + -- SSSS, so the bit count for the int, is ssss - 1 + if signBit + then (\w -> dataRange + fromIntegral w) <$> unpackInt leftBitCount + else (\w -> 1 - dataRange * 2 + fromIntegral w) <$> unpackInt leftBitCount + +dcCoefficientDecode :: HuffmanTree -> BoolReader s DcCoefficient +dcCoefficientDecode dcTree = do + ssss <- huffmanDecode dcTree + if ssss == 0 + then return 0 + else fromIntegral <$> decodeInt (fromIntegral ssss) + +-- | Assume the macro block is initialized with zeroes +acCoefficientsDecode :: HuffmanTree -> MutableMacroBlock s Int16 + -> BoolReader s (MutableMacroBlock s Int16) +acCoefficientsDecode acTree mutableBlock = parseAcCoefficient 1 >> return mutableBlock + where parseAcCoefficient n | n >= 64 = return () + | otherwise = do + rrrrssss <- huffmanDecode acTree + let rrrr = fromIntegral $ (rrrrssss `shiftR` 4) .&. 0xF + ssss = rrrrssss .&. 0xF + case (rrrr, ssss) of + ( 0, 0) -> return () + (0xF, 0) -> parseAcCoefficient (n + 16) + _ -> do + decoded <- fromIntegral <$> decodeInt (fromIntegral ssss) + lift $ (mutableBlock .<-. (n + rrrr)) decoded + parseAcCoefficient (n + rrrr + 1) + +-- | Decompress a macroblock from a bitstream given the current configuration +-- from the frame. +decompressMacroBlock :: HuffmanTree -- ^ Tree used for DC coefficient + -> HuffmanTree -- ^ Tree used for Ac coefficient + -> MacroBlock Int16 -- ^ Current quantization table + -> DcCoefficient -- ^ Previous dc value + -> BoolReader s (DcCoefficient, MutableMacroBlock s Int16) +decompressMacroBlock dcTree acTree quantizationTable previousDc = do + dcDeltaCoefficient <- dcCoefficientDecode dcTree + block <- lift createEmptyMutableMacroBlock + let neoDcCoefficient = previousDc + dcDeltaCoefficient + lift $ (block .<-. 0) neoDcCoefficient + fullBlock <- acCoefficientsDecode acTree block + decodedBlock <- lift $ decodeMacroBlock quantizationTable fullBlock + return (neoDcCoefficient, decodedBlock) + +gatherQuantTables :: JpgImage -> [JpgQuantTableSpec] +gatherQuantTables img = concat [t | JpgQuantTable t <- jpgFrame img] + +gatherHuffmanTables :: JpgImage -> [(JpgHuffmanTableSpec, HuffmanTree)] +gatherHuffmanTables img = concat [lst | JpgHuffmanTable lst <- jpgFrame img] + +gatherScanInfo :: JpgImage -> (JpgFrameKind, JpgFrameHeader) +gatherScanInfo img = fromJust $ unScan <$> find scanDesc (jpgFrame img) + where scanDesc (JpgScans _ _) = True + scanDesc _ = False + + unScan (JpgScans a b) = (a,b) + unScan _ = error "If this can happen, the JPEG image is ill-formed" + +pixelClamp :: Int16 -> Word8 +pixelClamp n = fromIntegral . min 255 $ max 0 n + +-- | Given a size coefficient (how much a pixel span horizontally +-- and vertically), the position of the macroblock, return a list +-- of indices and value to be stored in an array (like the final +-- image) +unpackMacroBlock :: Int -- ^ Component count + -> Int -- ^ Component index + -> Int -- ^ Width coefficient + -> Int -- ^ Height coefficient + -> Int -- ^ x + -> Int -- ^ y + -> MutableImage s PixelYCbCr8 + -> MutableMacroBlock s Int16 + -> ST s () + -- Simple case, a macroblock value => a pixel +unpackMacroBlock compCount compIdx wCoeff hCoeff x y + (MutableImage { mutableImageWidth = imgWidth, + mutableImageHeight = imgHeight, mutableImageData = img }) + block = do + forM_ pixelIndices $ \(i, j, wDup, hDup) -> do + let xPos = (i + x * 8) * wCoeff + wDup + yPos = (j + y * 8) * hCoeff + hDup + when (0 <= xPos && xPos < imgWidth && 0 <= yPos && yPos < imgHeight) + (do compVal <- pixelClamp <$> (block .!!!. (i + j * 8)) + let mutableIdx = (xPos + yPos * imgWidth) * compCount + compIdx + (img .<-. mutableIdx) compVal) + + where pixelIndices = [(i, j, wDup, hDup) | i <- [0 .. 7], j <- [0 .. 7] + -- Repetition to spread macro block + , wDup <- [0 .. wCoeff - 1] + , hDup <- [0 .. hCoeff - 1] + ] + +-- | Type only used to make clear what kind of integer we are carrying +-- Might be transformed into newtype in the future +type DcCoefficient = Int16 + +-- | Same as for DcCoefficient, to provide nicer type signatures +type DctCoefficients = DcCoefficient + +decodeRestartInterval :: BoolReader s Int32 +decodeRestartInterval = return (-1) {- do + bits <- replicateM 8 getNextBit + if bits == replicate 8 True + then do + marker <- replicateM 8 getNextBit + return $ packInt marker + else return (-1) + -} + + +decodeImage :: Int -- ^ Component count + -> JpegDecoder s -- ^ Function to call to decode an MCU + -> MutableImage s PixelYCbCr8 -- ^ Result image to write into + -> BoolReader s () +decodeImage compCount decoder img = do + let blockIndices = [(x,y) | y <- [0 .. verticalMcuCount decoder - 1] + , x <- [0 .. horizontalMcuCount decoder - 1] ] + mcuDecode = mcuDecoder decoder + blockBeforeRestart = restartInterval decoder + + folder f = foldM_ f blockBeforeRestart blockIndices + + dcArray <- lift (newArray (0, compCount - 1) 0 :: ST s (STUArray s Int DcCoefficient)) + folder (\resetCounter (x,y) -> do + when (resetCounter == 0) + (do forM_ [0.. compCount - 1] $ + \c -> lift $ (dcArray .<-. c) 0 + byteAlign + _restartCode <- decodeRestartInterval + -- if 0xD0 <= restartCode && restartCode <= 0xD7 + return ()) + + forM_ mcuDecode $ \(comp, dataUnitDecoder) -> do + dc <- lift $ dcArray .!!!. comp + dcCoeff <- dataUnitDecoder x y img $ fromIntegral dc + lift $ (dcArray .<-. comp) dcCoeff + return () + + if resetCounter /= 0 then return $ resetCounter - 1 + -- we use blockBeforeRestart - 1 to count + -- the current MCU + else return $ blockBeforeRestart - 1) + +-- | Type of a data unit (as in the ITU 81) standard +type DataUnitDecoder s = + (Int, Int -> Int -> MutableImage s PixelYCbCr8 -> DcCoefficient -> BoolReader s DcCoefficient) + +data JpegDecoder s = JpegDecoder + { restartInterval :: Int + , horizontalMcuCount :: Int + , verticalMcuCount :: Int + , mcuDecoder :: [DataUnitDecoder s] + } + +allElementsEqual :: (Eq a) => [a] -> Bool +allElementsEqual [] = True +allElementsEqual (x:xs) = all (== x) xs + +-- | An MCU (Minimal coded unit) is an unit of data for all components +-- (Y, Cb & Cr), taking into account downsampling. +buildJpegImageDecoder :: JpgImage -> JpegDecoder s +buildJpegImageDecoder img = JpegDecoder { restartInterval = mcuBeforeRestart + , horizontalMcuCount = horizontalBlockCount + , verticalMcuCount = verticalBlockCount + , mcuDecoder = mcus } + where huffmans = gatherHuffmanTables img + huffmanForComponent dcOrAc dest = + head [t | (h,t) <- huffmans, huffmanTableClass h == dcOrAc + , huffmanTableDest h == dest] + + mcuBeforeRestart = case [i | JpgIntervalRestart i <- jpgFrame img] of + [] -> maxBound -- HUUUUUUGE value (enough to parse all MCU) + (x:_) -> fromIntegral x + + quants = gatherQuantTables img + quantForComponent dest = + head [quantTable q | q <- quants, quantDestination q == dest] + + hdr = head [h | JpgScanBlob h _ <- jpgFrame img] + + (_, scanInfo) = gatherScanInfo img + imgWidth = fromIntegral $ jpgWidth scanInfo + imgHeight = fromIntegral $ jpgHeight scanInfo + + blockSizeOfDim fullDim maxBlockSize = block + (if rest /= 0 then 1 else 0) + where (block, rest) = fullDim `divMod` maxBlockSize + + horizontalSamplings = [horiz | (horiz, _, _, _, _) <- componentsInfo] + + imgComponentCount = fromIntegral $ jpgImageComponentCount scanInfo + isImageLumanOnly = imgComponentCount == 1 + maxHorizFactor | not isImageLumanOnly && + not (allElementsEqual horizontalSamplings) = maximum horizontalSamplings + | otherwise = 1 + + verticalSamplings = [vert | (_, vert, _, _, _) <- componentsInfo] + maxVertFactor | not isImageLumanOnly && + not (allElementsEqual verticalSamplings) = maximum verticalSamplings + | otherwise = 1 + + horizontalBlockCount = + blockSizeOfDim imgWidth $ fromIntegral (maxHorizFactor * 8) + + verticalBlockCount = + blockSizeOfDim imgHeight $ fromIntegral (maxVertFactor * 8) + + fetchTablesForComponent component = (horizCount, vertCount, dcTree, acTree, qTable) + where idx = componentIdentifier component + descr = head [c | c <- scans hdr, componentSelector c == idx] + dcTree = huffmanForComponent DcComponent $ dcEntropyCodingTable descr + acTree = huffmanForComponent AcComponent $ acEntropyCodingTable descr + qTable = quantForComponent $ if idx == 1 then 0 else 1 + horizCount = if not isImageLumanOnly + then fromIntegral $ horizontalSamplingFactor component + else 1 + vertCount = if not isImageLumanOnly + then fromIntegral $ verticalSamplingFactor component + else 1 + + componentsInfo = map fetchTablesForComponent $ jpgComponents scanInfo + + mcus = [(compIdx, \x y writeImg dc -> do + (dcCoeff, block) <- decompressMacroBlock dcTree acTree qTable dc + lift $ unpacker (x * horizCount + xd) (y * vertCount + yd) writeImg block + return dcCoeff) + | (compIdx, (horizCount, vertCount, dcTree, acTree, qTable)) + <- zip [0..] componentsInfo + , let xScalingFactor = maxHorizFactor - horizCount + 1 + yScalingFactor = maxVertFactor - vertCount + 1 + , yd <- [0 .. vertCount - 1] + , xd <- [0 .. horizCount - 1] + , let unpacker = unpackMacroBlock imgComponentCount compIdx + xScalingFactor yScalingFactor + ] + +-- | Try to load a jpeg file and decompress. The colorspace is still +-- YCbCr if you want to perform computation on the luma part. You can +-- convert it to RGB using 'colorSpaceConversion' +readJpeg :: FilePath -> IO (Either String DynamicImage) +readJpeg f = decodeJpeg <$> B.readFile f + +-- | Try to decompress a jpeg file and decompress. The colorspace is still +-- YCbCr if you want to perform computation on the luma part. You can +-- convert it to RGB using 'colorSpaceConversion' +-- +-- This function can output the following pixel types : +-- +-- * PixelY8 +-- +-- * PixelYCbCr8 +-- +decodeJpeg :: B.ByteString -> Either String DynamicImage +decodeJpeg file = case decode file of + Left err -> Left err + Right img -> case compCount of + 1 -> Right . ImageY8 $ Image imgWidth imgHeight pixelData + 3 -> Right . ImageYCbCr8 $ Image imgWidth imgHeight pixelData + _ -> Left "Wrong component count" + + where (imgData:_) = [d | JpgScanBlob _kind d <- jpgFrame img] + (_, scanInfo) = gatherScanInfo img + compCount = length $ jpgComponents scanInfo + + imgWidth = fromIntegral $ jpgWidth scanInfo + imgHeight = fromIntegral $ jpgHeight scanInfo + + imageSize = (0, imgWidth * imgHeight * compCount - 1) + + pixelData = runSTUArray $ S.evalStateT (do + resultImage <- lift $ newArray imageSize 0 + let wrapped = MutableImage imgWidth imgHeight resultImage + setDecodedString imgData + decodeImage compCount (buildJpegImageDecoder img) wrapped + return resultImage) (-1, 0, B.empty) +
+ Codec/Picture/Jpg/DefaultTable.hs view
@@ -0,0 +1,186 @@+{-# LANGUAGE TupleSections #-} +{-# LANGUAGE FlexibleContexts #-} +-- | Module used by the jpeg decoder internally, shouldn't be used +-- in user code. +module Codec.Picture.Jpg.DefaultTable( DctComponent( .. ) + , HuffmanTree( .. ) + , MacroBlock + , makeMacroBlock + , buildHuffmanTree + {- + , defaultChromaQuantizationTable + , defaultLumaQuantizationTable + , defaultAcChromaHuffmanTable + , defaultAcLumaHuffmanTable + , defaultDcChromaHuffmanTable + , defaultDcLumaHuffmanTable + -} + ) where + +import Data.Array.Unboxed( IArray, UArray, listArray ) +import Data.Word( Word8 ) +import Data.List( foldl' ) + +-- | Tree storing the code used for huffman encoding. +data HuffmanTree = Branch HuffmanTree HuffmanTree -- ^ If bit is 0 take the first subtree, if 1, the right. + | Leaf Word8 -- ^ We should output the value + | Empty -- ^ no value present + deriving (Eq, Show) + +-- | Represent a compact array of 8 * 8 values. The size +-- is not guarenteed by type system, but if makeMacroBlock is +-- used, everything should be fine size-wise +type MacroBlock a = UArray Int a + +-- | Helper function to create pure macro block of the good size. +makeMacroBlock :: (IArray UArray a) => [a] -> MacroBlock a +makeMacroBlock = listArray (0, 63) + +-- | Enumeration used to search in the tables for different components. +data DctComponent = DcComponent | AcComponent + deriving (Eq, Show) + +-- | Transform parsed coefficients from the jpeg header to a +-- tree which can be used to decode data. +buildHuffmanTree :: [[Word8]] -> HuffmanTree +buildHuffmanTree table = foldl' insertHuffmanVal Empty + . concatMap (\(i, t) -> map (i + 1,) t) + $ zip ([0..] :: [Int]) table + where isTreeFullyDefined Empty = False + isTreeFullyDefined (Leaf _) = True + isTreeFullyDefined (Branch l r) = isTreeFullyDefined l && isTreeFullyDefined r + + insertHuffmanVal Empty (0, val) = Leaf val + insertHuffmanVal Empty (d, val) = Branch (insertHuffmanVal Empty (d - 1, val)) Empty + insertHuffmanVal (Branch l r) (d, val) + | isTreeFullyDefined l = Branch l (insertHuffmanVal r (d - 1, val)) + | otherwise = Branch (insertHuffmanVal l (d - 1, val)) r + insertHuffmanVal (Leaf _) _ = error "Inserting in value, shouldn't happen" + +{- +defaultLumaQuantizationTable :: MacroBlock Int16 +defaultLumaQuantizationTable = makeMacroBlock + [16, 11, 10, 16, 24, 40, 51, 61 + ,12, 12, 14, 19, 26, 58, 60, 55 + ,14, 13, 16, 24, 40, 57, 69, 56 + ,14, 17, 22, 29, 51, 87, 80, 62 + ,18, 22, 37, 56, 68, 109, 103, 77 + ,24, 35, 55, 64, 81, 104, 113, 92 + ,49, 64, 78, 87, 103, 121, 120, 101 + ,72, 92, 95, 98, 112, 100, 103, 99 + ] + +defaultChromaQuantizationTable :: MacroBlock Int16 +defaultChromaQuantizationTable = makeMacroBlock + [17, 18, 24, 47, 99, 99, 99, 99 + ,18, 21, 26, 66, 99, 99, 99, 99 + ,24, 26, 56, 99, 99, 99, 99, 99 + ,47, 66, 99, 99, 99, 99, 99, 99 + ,99, 99, 99, 99, 99, 99, 99, 99 + ,99, 99, 99, 99, 99, 99, 99, 99 + ,99, 99, 99, 99, 99, 99, 99, 99 + ,99, 99, 99, 99, 99, 99, 99, 99 + ] +-- | From the Table K.3 of ITU-81 (p153) +defaultDcLumaHuffmanTable :: HuffmanTree +defaultDcLumaHuffmanTable = buildHuffmanTree + [ [] + , [0] + , [1, 2, 3, 4, 5] + , [6] + , [7] + , [8] + , [9] + , [10] + , [11] + , [] + , [] + , [] + , [] + , [] + , [] + , [] + ] + +-- | From the Table K.4 of ITU-81 (p153) +defaultDcChromaHuffmanTable :: HuffmanTree +defaultDcChromaHuffmanTable = buildHuffmanTree + [ [] + , [0, 1, 2] + , [3] + , [4] + , [5] + , [6] + , [7] + , [8] + , [9] + , [10] + , [11] + , [] + , [] + , [] + , [] + , [] + ] + +-- | From the Table K.5 of ITU-81 (p154) +defaultAcLumaHuffmanTable :: HuffmanTree +defaultAcLumaHuffmanTable = buildHuffmanTree + [ [] + , [0x01, 0x02] + , [0x03] + , [0x00, 0x04, 0x11] + , [0x05, 0x12, 0x21] + , [0x31, 0x41] + , [0x06, 0x13, 0x51, 0x61] + , [0x07, 0x22, 0x71] + , [0x14, 0x32, 0x81, 0x91, 0xA1] + , [0x08, 0x23, 0x42, 0xB1, 0xC1] + , [0x15, 0x52, 0xD1, 0xF0] + , [0x24, 0x33, 0x62, 0x72] + , [] + , [] + , [0x82] + , [0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35 + ,0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54 + ,0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73 + ,0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A + ,0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7 + ,0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4 + ,0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA + ,0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5 + ,0xF6, 0xF7, 0xF8, 0xF9, 0xFA] + ] + +defaultAcChromaHuffmanTable :: HuffmanTree +defaultAcChromaHuffmanTable = buildHuffmanTree + [ [] + , [0x00, 0x01] + , [0x02] + , [0x03, 0x11] + , [0x04, 0x05, 0x21, 0x31] + , [0x06, 0x12, 0x41, 0x51] + , [0x07, 0x61, 0x71] + , [0x13, 0x22, 0x32, 0x81] + , [0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1] + , [0x09, 0x23, 0x33, 0x52, 0xF0] + , [0x15, 0x62, 0x72, 0xD1] + , [0x0A, 0x16, 0x24, 0x34] + , [] + , [0xE1] + , [0x25, 0xF1] + , [ 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x35 + , 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47 + , 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59 + , 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73 + , 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84 + , 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95 + , 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6 + , 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7 + , 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8 + , 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9 + , 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA + , 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA + ] + ] +-}
+ Codec/Picture/Jpg/FastIdct.hs view
@@ -0,0 +1,248 @@+{-# LANGUAGE FlexibleContexts #-} +-- | Module providing a 'fast' implementation of IDCT +-- +-- inverse two dimensional DCT, Chen-Wang algorithm +-- (cf. IEEE ASSP-32, pp. 803-816, Aug. 1984) +-- 32-bit integer arithmetic (8 bit coefficients) +-- 11 mults, 29 adds per DCT +-- sE, 18.8.91 +-- +-- coefficients extended to 12 bit for IEEE1180-1990 +-- compliance sE, 2.1.94 +-- +-- this code assumes >> to be a two's-complement arithmetic +-- right shift: (-2)>>1 == -1 , (-3)>>1 == -2 +module Codec.Picture.Jpg.FastIdct( MutableMacroBlock + , fastIdct + , mutableLevelShift + , createEmptyMutableMacroBlock + ) where + +import Data.Array.Base( IArray, UArray, unsafeAt + , unsafeRead, unsafeWrite, listArray ) +import Data.Array.ST( MArray, STUArray, newArray ) +import Control.Monad( forM_ ) +import Control.Monad.ST( ST ) +import Data.Bits( shiftL, shiftR ) +import Data.Int( Int16 ) + +iclip :: UArray Int Int16 +iclip = listArray (-512, 512) [ val i| i <- [(-512) .. 511]] + where val i | i < (-256) = -256 + | i > 255 = 255 + | otherwise = i + + + +{-# INLINE (.<<.) #-} +{-# INLINE (.>>.) #-} +(.<<.), (.>>.) :: Int -> Int -> Int +(.<<.) = shiftL +(.>>.) = shiftR + +data IDctStage = IDctStage { + x0 :: {-# UNPACK #-} !Int, + x1 :: {-# UNPACK #-} !Int, + x2 :: {-# UNPACK #-} !Int, + x3 :: {-# UNPACK #-} !Int, + x4 :: {-# UNPACK #-} !Int, + x5 :: {-# UNPACK #-} !Int, + x6 :: {-# UNPACK #-} !Int, + x7 :: {-# UNPACK #-} !Int, + x8 :: {-# UNPACK #-} !Int + } + +w1, w2, w3, w5, w6, w7 :: Int +w1 = 2841 -- 2048*sqrt(2)*cos(1*pi/16) +w2 = 2676 -- 2048*sqrt(2)*cos(2*pi/16) +w3 = 2408 -- 2048*sqrt(2)*cos(3*pi/16) +w5 = 1609 -- 2048*sqrt(2)*cos(5*pi/16) +w6 = 1108 -- 2048*sqrt(2)*cos(6*pi/16) +w7 = 565 -- 2048*sqrt(2)*cos(7*pi/16) + +{-# INLINE (!!!) #-} +(!!!) :: (IArray array e) => array Int e -> Int -> e +(!!!) a i = unsafeAt a (i + 512) + +{-# INLINE (.!!!.) #-} +(.!!!.) :: (MArray array e m) => array Int e -> Int -> m e +(.!!!.) = unsafeRead + +{-# INLINE (.<-.) #-} +(.<-.) :: (MArray array e m) => array Int e -> Int -> e -> m () +(.<-.) = unsafeWrite + +-- | Macroblock that can be transformed. +type MutableMacroBlock s a = STUArray s Int a + +{-# INLINE createEmptyMutableMacroBlock #-} +-- | Create a new macroblock with the good array size +createEmptyMutableMacroBlock :: ST s (MutableMacroBlock s Int16) +createEmptyMutableMacroBlock = newArray (0, 63) 0 + +-- row (horizontal) IDCT +-- +-- 7 pi 1 +-- dst[k] = sum c[l] * src[l] * cos( -- * ( k + - ) * l ) +-- l=0 8 2 +-- +-- where: c[0] = 128 +-- c[1..7] = 128*sqrt(2) +idctRow :: MutableMacroBlock s Int16 -> Int -> ST s () +idctRow blk idx = do + xx0 <- blk .!!!. (0 + idx) + xx1 <- blk .!!!. (4 + idx) + xx2 <- blk .!!!. (6 + idx) + xx3 <- blk .!!!. (2 + idx) + xx4 <- blk .!!!. (1 + idx) + xx5 <- blk .!!!. (7 + idx) + xx6 <- blk .!!!. (5 + idx) + xx7 <- blk .!!!. (3 + idx) + let initialState = IDctStage { x0 = (fromIntegral xx0 .<<. 11) + 128 + , x1 = fromIntegral xx1 .<<. 11 + , x2 = fromIntegral xx2 + , x3 = fromIntegral xx3 + , x4 = fromIntegral xx4 + , x5 = fromIntegral xx5 + , x6 = fromIntegral xx6 + , x7 = fromIntegral xx7 + , x8 = 0 + } + + firstStage c = c { x4 = x8' + (w1 - w7) * x4 c + , x5 = x8' - (w1 + w7) * x5 c + , x6 = x8'' - (w3 - w5) * x6 c + , x7 = x8'' - (w3 + w5) * x7 c + , x8 = x8'' + } + where x8' = w7 * (x4 c + x5 c) + x8'' = w3 * (x6 c + x7 c) + + secondStage c = c { x0 = x0 c - x1 c + , x8 = x0 c + x1 c + , x1 = x1'' + , x2 = x1' - (w2 + w6) * x2 c + , x3 = x1' + (w2 - w6) * x3 c + , x4 = x4 c - x6 c + , x6 = x5 c + x7 c + , x5 = x5 c - x7 c + } + where x1' = w6 * (x3 c + x2 c) + x1'' = x4 c + x6 c + + thirdStage c = c { x7 = x8 c + x3 c + , x8 = x8 c - x3 c + , x3 = x0 c + x2 c + , x0 = x0 c - x2 c + , x2 = (181 * (x4 c + x5 c) + 128) .>>. 8 + , x4 = (181 * (x4 c - x5 c) + 128) .>>. 8 + } + scaled c = c { x0 = (x7 c + x1 c) .>>. 8 + , x1 = (x3 c + x2 c) .>>. 8 + , x2 = (x0 c + x4 c) .>>. 8 + , x3 = (x8 c + x6 c) .>>. 8 + , x4 = (x8 c - x6 c) .>>. 8 + , x5 = (x0 c - x4 c) .>>. 8 + , x6 = (x3 c - x2 c) .>>. 8 + , x7 = (x7 c - x1 c) .>>. 8 + } + transformed = scaled . thirdStage . secondStage $ firstStage initialState + + (blk .<-. (0 + idx)) . fromIntegral $ x0 transformed + (blk .<-. (1 + idx)) . fromIntegral $ x1 transformed + (blk .<-. (2 + idx)) . fromIntegral $ x2 transformed + (blk .<-. (3 + idx)) . fromIntegral $ x3 transformed + (blk .<-. (4 + idx)) . fromIntegral $ x4 transformed + (blk .<-. (5 + idx)) . fromIntegral $ x5 transformed + (blk .<-. (6 + idx)) . fromIntegral $ x6 transformed + (blk .<-. (7 + idx)) . fromIntegral $ x7 transformed + +-- column (vertical) IDCT +-- +-- 7 pi 1 +-- dst[8*k] = sum c[l] * src[8*l] * cos( -- * ( k + - ) * l ) +-- l=0 8 2 +-- +-- where: c[0] = 1/1024 +-- c[1..7] = (1/1024)*sqrt(2) +-- +idctCol :: MutableMacroBlock s Int16 -> Int -> ST s () +idctCol blk idx = do + xx0 <- blk .!!!. (8 * 0 + idx) + xx1 <- blk .!!!. (8 * 4 + idx) + xx2 <- blk .!!!. (8 * 6 + idx) + xx3 <- blk .!!!. (8 * 2 + idx) + xx4 <- blk .!!!. (8 * 1 + idx) + xx5 <- blk .!!!. (8 * 7 + idx) + xx6 <- blk .!!!. (8 * 5 + idx) + xx7 <- blk .!!!. (8 * 3 + idx) + let initialState = IDctStage { x0 = (fromIntegral xx0 .<<. 8) + 8192 + , x1 = fromIntegral xx1 .<<. 8 + , x2 = fromIntegral xx2 + , x3 = fromIntegral xx3 + , x4 = fromIntegral xx4 + , x5 = fromIntegral xx5 + , x6 = fromIntegral xx6 + , x7 = fromIntegral xx7 + , x8 = 0 + } + firstStage c = c { x4 = (x8' + (w1 - w7) * x4 c) .>>. 3 + , x5 = (x8' - (w1 + w7) * x5 c) .>>. 3 + , x6 = (x8'' - (w3 - w5) * x6 c) .>>. 3 + , x7 = (x8'' - (w3 + w5) * x7 c) .>>. 3 + , x8 = x8'' + } + where x8' = w7 * (x4 c + x5 c) + 4 + x8'' = w3 * (x6 c + x7 c) + 4 + + secondStage c = c { x8 = x0 c + x1 c + , x0 = x0 c - x1 c + , x2 = (x1' - (w2 + w6) * x2 c) .>>. 3 + , x3 = (x1' + (w2 - w6) * x3 c) .>>. 3 + , x4 = x4 c - x6 c + , x1 = x1'' + , x6 = x5 c + x7 c + , x5 = x5 c - x7 c + } + where x1' = w6 * (x3 c + x2 c) + 4 + x1'' = x4 c + x6 c + + thirdStage c = c { x7 = x8 c + x3 c + , x8 = x8 c - x3 c + , x3 = x0 c + x2 c + , x0 = x0 c - x2 c + , x2 = (181 * (x4 c + x5 c) + 128) .>>. 8 + , x4 = (181 * (x4 c - x5 c) + 128) .>>. 8 + } + + f = thirdStage . secondStage $ firstStage initialState + (blk .<-. (idx + 8*0)) $ iclip !!! ((x7 f + x1 f) .>>. 14) + (blk .<-. (idx + 8*1)) $ iclip !!! ((x3 f + x2 f) .>>. 14) + (blk .<-. (idx + 8*2)) $ iclip !!! ((x0 f + x4 f) .>>. 14) + (blk .<-. (idx + 8*3)) $ iclip !!! ((x8 f + x6 f) .>>. 14) + (blk .<-. (idx + 8*4)) $ iclip !!! ((x8 f - x6 f) .>>. 14) + (blk .<-. (idx + 8*5)) $ iclip !!! ((x0 f - x4 f) .>>. 14) + (blk .<-. (idx + 8*6)) $ iclip !!! ((x3 f - x2 f) .>>. 14) + (blk .<-. (idx + 8*7)) $ iclip !!! ((x7 f - x1 f) .>>. 14) + + +{-# INLINE fastIdct #-} +-- | Algorithm to call to perform an IDCT, return the same +-- block that the one given as input. +fastIdct :: MutableMacroBlock s Int16 + -> ST s (MutableMacroBlock s Int16) +fastIdct block = do + forM_ [0..7] (\i -> idctRow block (8 * i)) + forM_ [0..7] (idctCol block) + return block + +{-# INLINE mutableLevelShift #-} +-- | Perform a Jpeg level shift in a mutable fashion. +mutableLevelShift :: MutableMacroBlock s Int16 + -> ST s (MutableMacroBlock s Int16) +mutableLevelShift block = do + forM_ [0..63] (\i -> do + v <- block .!!!. i + (block .<-. i) $ v + 128) + return block +
+ Codec/Picture/Png.hs view
@@ -0,0 +1,462 @@+{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE TypeSynonymInstances #-} +{-# LANGUAGE BangPatterns #-} +-- | Module used for loading & writing \'Portable Network Graphics\' (PNG) +-- files. The API has two layers, the high level, which load the image without +-- looking deeply about it and the low level, allowing access to data chunks contained +-- in the PNG image. +-- +-- For general use, please use 'decodePng' function. +-- +-- The loader has been validated against the pngsuite (http://www.libpng.org/pub/png/pngsuite.html) +module Codec.Picture.Png( -- * High level functions + PngSavable( .. ) + + , readPng + , decodePng + , writePng + , encodeDynamicPng + , writeDynamicPng + + ) where + +import Control.Monad( foldM_, forM_, when ) +import Control.Monad.ST( ST ) +import Control.Monad.Trans( lift ) +import qualified Control.Monad.Trans.State.Strict as S +import Data.Serialize( Serialize, runGet, get) + +import Data.Array.Base( unsafeAt, unsafeRead, unsafeWrite ) +-- import Data.Array.Unboxed( (!) ) +-- import Data.Array.ST( readArray, writeArray ) + +import Data.Array.Unboxed( IArray, UArray, listArray, bounds, elems ) +import Data.Array.ST( STUArray, runSTUArray, MArray, newArray, getBounds ) +import Data.Bits( (.&.), (.|.), shiftL, shiftR ) +import Data.List( find, zip4 ) +import Data.Word( Word8, Word16, Word32 ) +import qualified Codec.Compression.Zlib as Z +import qualified Data.ByteString as B +import qualified Data.ByteString.Lazy as Lb + +import Codec.Picture.Types +import Codec.Picture.Png.Type +import Codec.Picture.Png.Export + +-- | Simple structure used to hold information about Adam7 deinterlacing. +-- A structure is used to avoid pollution of the module namespace. +data Adam7MatrixInfo = Adam7MatrixInfo + { adam7StartingRow :: [Int] + , adam7StartingCol :: [Int] + , adam7RowIncrement :: [Int] + , adam7ColIncrement :: [Int] + , adam7BlockHeight :: [Int] + , adam7BlockWidth :: [Int] + } + +-- | The real info about the matrix. +adam7MatrixInfo :: Adam7MatrixInfo +adam7MatrixInfo = Adam7MatrixInfo + { adam7StartingRow = [0, 0, 4, 0, 2, 0, 1] + , adam7StartingCol = [0, 4, 0, 2, 0, 1, 0] + , adam7RowIncrement = [8, 8, 8, 4, 4, 2, 2] + , adam7ColIncrement = [8, 8, 4, 4, 2, 2, 1] + , adam7BlockHeight = [8, 8, 4, 4, 2, 2, 1] + , adam7BlockWidth = [8, 4, 4, 2, 2, 1, 1] + } + +unparsePngFilter :: Word8 -> Either String PngFilter +unparsePngFilter 0 = Right FilterNone +unparsePngFilter 1 = Right FilterSub +unparsePngFilter 2 = Right FilterUp +unparsePngFilter 3 = Right FilterAverage +unparsePngFilter 4 = Right FilterPaeth +unparsePngFilter _ = Left "Invalid scanline filter" + +type ByteReader s a = S.StateT B.ByteString (ST s) a + +{-# INLINE getNextByte #-} +getNextByte :: ByteReader s Word8 +getNextByte = do str <- S.get + case B.uncons str of + Just (v, rest) -> S.put rest >> return v + Nothing -> return 0 + +{-# INLINE (!!!) #-} +(!!!) :: (IArray array e) => array Int e -> Int -> e +(!!!) = unsafeAt + +{-# INLINE (.!!!.) #-} +(.!!!.) :: (MArray array e m) => array Int e -> Int -> m e +(.!!!.) = unsafeRead + +{-# INLINE (.<-.) #-} +(.<-.) :: (MArray array e m) => array Int e -> Int -> e -> m () +(.<-.) = unsafeWrite + +-- | Apply a filtering method on a reduced image. Apply the filter +-- on each line, using the previous line (the one above it) to perform +-- some prediction on the value. +pngFiltering :: LineUnpacker s -> Int -> (Int, Int) -- ^ Image size + -> ByteReader s () +pngFiltering _ _ (imgWidth, imgHeight) | imgWidth <= 0 || imgHeight <= 0 = return () +pngFiltering unpacker beginZeroes (imgWidth, imgHeight) = do + thisLine <- lift $ newArray (0, beginZeroes + imgWidth - 1) 0 + otherLine <- lift $ newArray (0, beginZeroes + imgWidth - 1) 0 + foldM_ (\(previousLine, currentLine) lineIndex -> do + byte <- getNextByte + let lineFilter = case unparsePngFilter byte of + Right FilterNone -> filterNone + Right FilterSub -> filterSub + Right FilterAverage -> filterAverage + Right FilterUp -> filterUp + Right FilterPaeth -> filterPaeth + _ -> filterNone + lineFilter (previousLine, currentLine) + lift $ unpacker lineIndex (stride, currentLine) + return (currentLine, previousLine) + ) (thisLine, otherLine) [0 .. imgHeight - 1] + + where stride = fromIntegral beginZeroes + lastIdx = beginZeroes + imgWidth - 1 + + -- The filter implementation are... well non-idiomatic + -- to say the least, but my benchmarks proved me one thing, + -- they are faster than mapM_, gained something like 5% with + -- a rewrite from mapM_ to this direct version + filterNone, filterSub, filterUp, filterPaeth, + filterAverage :: (PngLine s, PngLine s) -> ByteReader s () + filterNone (_previousLine, thisLine) = inner beginZeroes + where inner idx | idx > lastIdx = return () + | otherwise = do byte <- getNextByte + lift $ (thisLine .<-. idx) byte + inner (idx + 1) + + filterSub (_previousLine, thisLine) = inner beginZeroes + where inner idx | idx > lastIdx = return () + | otherwise = do byte <- getNextByte + val <- lift $ thisLine .!!!. (idx - stride) + lift . (thisLine .<-. idx) $ byte + val + inner (idx + 1) + + filterUp (previousLine, thisLine) = inner beginZeroes + where inner idx | idx > lastIdx = return () + | otherwise = do byte <- getNextByte + val <- lift $ previousLine .!!!. idx + lift . (thisLine .<-. idx) $ val + byte + inner (idx + 1) + + filterAverage (previousLine, thisLine) = inner beginZeroes + where inner idx | idx > lastIdx = return () + | otherwise = do byte <- getNextByte + valA <- lift $ thisLine .!!!. (idx - stride) + valB <- lift $ previousLine .!!!. idx + let a' = fromIntegral valA + b' = fromIntegral valB + average = fromIntegral ((a' + b') `div` (2 :: Word16)) + writeVal = byte + average + lift . (thisLine .<-. idx) $ writeVal + inner (idx + 1) + + filterPaeth (previousLine, thisLine) = inner beginZeroes + where inner idx | idx > lastIdx = return () + | otherwise = do byte <- getNextByte + valA <- lift $ thisLine .!!!. (idx - stride) + valC <- lift $ previousLine .!!!. (idx - stride) + valB <- lift $ previousLine .!!!. idx + lift . (thisLine .<-. idx) $ byte + paeth valA valB valC + inner (idx + 1) + +-- | Directly stolen from the definition in the standard (on W3C page), +-- pixel predictor. +paeth :: Word8 -> Word8 -> Word8 -> Word8 +paeth a b c + | pa <= pb && pa <= pc = a + | pb <= pc = b + | otherwise = c + where a' = fromIntegral a :: Int + b' = fromIntegral b + c' = fromIntegral c + p = a' + b' - c' + pa = abs $ p - a' + pb = abs $ p - b' + pc = abs $ p - c' + +type PngLine s = STUArray s Int Word8 +type LineUnpacker s = Int -> (Int, PngLine s) -> ST s () + +type StrideInfo = (Int, Int) + +type BeginOffset = (Int, Int) + + +-- | Unpack lines where bit depth is 8 +byteUnpacker :: Int -> MutableImage s Word8 -> StrideInfo -> BeginOffset -> LineUnpacker s +byteUnpacker sampleCount (MutableImage{ mutableImageWidth = imgWidth, mutableImageData = arr }) + (strideWidth, strideHeight) (beginLeft, beginTop) h (beginIdx, line) = do + (_, maxIdx) <- getBounds line + let realTop = beginTop + h * strideHeight + lineIndex = realTop * imgWidth + pixelToRead = min (imgWidth - 1) $ (maxIdx - beginIdx) `div` sampleCount + inner pixelIndex | pixelIndex > pixelToRead = return () + | otherwise = do + let destPixelIndex = lineIndex + pixelIndex * strideWidth + beginLeft + destSampleIndex = destPixelIndex * sampleCount + srcPixelIndex = pixelIndex * sampleCount + beginIdx + perPixel sample | sample >= sampleCount = return () + | otherwise = (do + val <- line .!!!. (srcPixelIndex + sample) + let writeIdx = destSampleIndex + sample + (arr .<-. writeIdx) val + perPixel (sample + 1)) + perPixel 0 + inner (pixelIndex + 1) + inner 0 + + +-- | Unpack lines where bit depth is 1 +bitUnpacker :: Int -> MutableImage s Word8 -> StrideInfo -> BeginOffset -> LineUnpacker s +bitUnpacker _ (MutableImage{ mutableImageWidth = imgWidth, mutableImageData = arr }) + (strideWidth, strideHeight) (beginLeft, beginTop) h (beginIdx, line) = do + (_, endLine) <- getBounds line + let realTop = beginTop + h * strideHeight + lineIndex = realTop * imgWidth + (lineWidth, subImageRest) = (imgWidth - beginLeft) `divMod` strideWidth + subPadd | subImageRest > 0 = 1 + | otherwise = 0 + (pixelToRead, lineRest) = (lineWidth + subPadd) `divMod` 8 + forM_ [0 .. pixelToRead - 1] $ \pixelIndex -> do + val <- line .!!!. (pixelIndex + beginIdx) + let writeIdx n = lineIndex + (pixelIndex * 8 + n) * strideWidth + beginLeft + forM_ [0 .. 7] $ \bit -> (arr .<-. writeIdx (7 - bit)) ((val `shiftR` bit) .&. 1) + + when (lineRest /= 0) + (do val <- line .!!!. endLine + let writeIdx n = lineIndex + (pixelToRead * 8 + n) * strideWidth + beginLeft + forM_ [0 .. lineRest - 1] $ \bit -> + (arr .<-. writeIdx bit) $ ((val `shiftR` (7 - bit)) .&. 0x1)) + + +-- | Unpack lines when bit depth is 2 +twoBitsUnpacker :: Int -> MutableImage s Word8 -> StrideInfo -> BeginOffset -> LineUnpacker s +twoBitsUnpacker _ (MutableImage{ mutableImageWidth = imgWidth, mutableImageData = arr }) + (strideWidth, strideHeight) (beginLeft, beginTop) h (beginIdx, line) = do + (_, endLine) <- getBounds line + let realTop = beginTop + h * strideHeight + lineIndex = realTop * imgWidth + (lineWidth, subImageRest) = (imgWidth - beginLeft) `divMod` strideWidth + subPadd | subImageRest > 0 = 1 + | otherwise = 0 + (pixelToRead, lineRest) = (lineWidth + subPadd) `divMod` 4 + + forM_ [0 .. pixelToRead - 1] $ \pixelIndex -> do + val <- line .!!!. (pixelIndex + beginIdx) + let writeIdx n = lineIndex + (pixelIndex * 4 + n) * strideWidth + beginLeft + (arr .<-. writeIdx 0) $ (val `shiftR` 6) .&. 0x3 + (arr .<-. writeIdx 1) $ (val `shiftR` 4) .&. 0x3 + (arr .<-. writeIdx 2) $ (val `shiftR` 2) .&. 0x3 + (arr .<-. writeIdx 3) $ val .&. 0x3 + + when (lineRest /= 0) + (do val <- line .!!!. endLine + let writeIdx n = lineIndex + (pixelToRead * 4 + n) * strideWidth + beginLeft + forM_ [0 .. lineRest - 1] $ \bit -> + (arr .<-. writeIdx bit) $ ((val `shiftR` (6 - 2 * bit)) .&. 0x3)) + +halfByteUnpacker :: Int -> MutableImage s Word8 -> StrideInfo -> BeginOffset -> LineUnpacker s +halfByteUnpacker _ (MutableImage{ mutableImageWidth = imgWidth, mutableImageData = arr }) + (strideWidth, strideHeight) (beginLeft, beginTop) h (beginIdx, line) = do + (_, endLine) <- getBounds line + let realTop = beginTop + h * strideHeight + lineIndex = realTop * imgWidth + (lineWidth, subImageRest) = (imgWidth - beginLeft) `divMod` strideWidth + subPadd | subImageRest > 0 = 1 + | otherwise = 0 + (pixelToRead, lineRest) = (lineWidth + subPadd) `divMod` 2 + forM_ [0 .. pixelToRead - 1] $ \pixelIndex -> do + val <- line .!!!. (pixelIndex + beginIdx) + let writeIdx n = lineIndex + (pixelIndex * 2 + n) * strideWidth + beginLeft + (arr .<-. writeIdx 0) $ (val `shiftR` 4) .&. 0xF + (arr .<-. writeIdx 1) $ val .&. 0xF + + when (lineRest /= 0) + (do val <- line .!!!. endLine + let writeIdx = lineIndex + (pixelToRead * 2) * strideWidth + beginLeft + (arr .<-. writeIdx) $ (val `shiftR` 4) .&. 0xF) + +shortUnpacker :: Int -> MutableImage s Word8 -> StrideInfo -> BeginOffset -> LineUnpacker s +shortUnpacker sampleCount (MutableImage{ mutableImageWidth = imgWidth, mutableImageData = arr }) + (strideWidth, strideHeight) (beginLeft, beginTop) h (beginIdx, line) = do + (_, maxIdx) <- getBounds line + let realTop = beginTop + h * strideHeight + lineIndex = realTop * imgWidth + pixelToRead = min (imgWidth - 1) $ (maxIdx - beginIdx) `div` (sampleCount * 2) + forM_ [0 .. pixelToRead] $ \pixelIndex -> do + let destPixelIndex = lineIndex + pixelIndex * strideWidth + beginLeft + destSampleIndex = destPixelIndex * sampleCount + srcPixelIndex = pixelIndex * sampleCount * 2 + beginIdx + forM_ [0 .. sampleCount - 1] $ \sample -> do + highBits <- line .!!!. (srcPixelIndex + sample * 2 + 0) + lowBits <- line .!!!. (srcPixelIndex + sample * 2 + 1) + let fullValue = fromIntegral lowBits .|. (fromIntegral highBits `shiftL` 8) :: Word32 + word8Max = 2 ^ (8 :: Word32) - 1 :: Word32 + word16Max = 2 ^ (16 :: Word32) - 1 :: Word32 + val = fullValue * word8Max `div` word16Max + writeIdx = destSampleIndex + sample + (arr .<-. writeIdx) $ fromIntegral val + +-- | Transform a scanline to a bunch of bytes. Bytes are then packed +-- into pixels at a further step. +scanlineUnpacker :: Int -> Int -> MutableImage s Word8 -> StrideInfo -> BeginOffset -> LineUnpacker s +scanlineUnpacker 1 = bitUnpacker +scanlineUnpacker 2 = twoBitsUnpacker +scanlineUnpacker 4 = halfByteUnpacker +scanlineUnpacker 8 = byteUnpacker +scanlineUnpacker 16 = shortUnpacker +scanlineUnpacker _ = error "Impossible bit depth" + +byteSizeOfBitLength :: Int -> Int -> Int -> Int +byteSizeOfBitLength pixelBitDepth sampleCount dimension = size + (if rest /= 0 then 1 else 0) + where (size, rest) = (pixelBitDepth * dimension * sampleCount) `quotRem` 8 + +scanLineInterleaving :: Int -> Int -> (Int, Int) -> (StrideInfo -> BeginOffset -> LineUnpacker s) + -> ByteReader s () +scanLineInterleaving depth sampleCount (imgWidth, imgHeight) unpacker = + pngFiltering (unpacker (1,1) (0, 0)) strideInfo (byteWidth, imgHeight) + where byteWidth = byteSizeOfBitLength depth sampleCount imgWidth + strideInfo | depth < 8 = 1 + | otherwise = sampleCount * (depth `div` 8) + +-- | Given data and image size, recreate an image with deinterlaced +-- data for PNG's adam 7 method. +adam7Unpack :: Int -> Int -> (Int, Int) -> (StrideInfo -> BeginOffset -> LineUnpacker s) + -> ByteReader s () +adam7Unpack depth sampleCount (imgWidth, imgHeight) unpacker = sequence_ + [pngFiltering (unpacker (incrW, incrH) (beginW, beginH)) strideInfo (byteWidth, passHeight) + | (beginW, incrW, beginH, incrH) <- zip4 startCols colIncrement startRows rowIncrement + , let passWidth = sizer imgWidth beginW incrW + passHeight = sizer imgHeight beginH incrH + byteWidth = byteSizeOfBitLength depth sampleCount passWidth + ] + where Adam7MatrixInfo { adam7StartingRow = startRows + , adam7RowIncrement = rowIncrement + , adam7StartingCol = startCols + , adam7ColIncrement = colIncrement } = adam7MatrixInfo + + strideInfo | depth < 8 = 1 + | otherwise = sampleCount * (depth `div` 8) + sizer dimension begin increment + | dimension <= begin = 0 + | otherwise = outDim + (if restDim /= 0 then 1 else 0) + where (outDim, restDim) = (dimension - begin) `quotRem` increment + +-- | deinterlace picture in function of the method indicated +-- in the iHDR +deinterlacer :: PngIHdr -> ByteReader s (STUArray s Int Word8) +deinterlacer (PngIHdr { width = w, height = h, colourType = imgKind + , interlaceMethod = method, bitDepth = depth }) = do + let compCount = sampleCountOfImageType imgKind + arraySize = fromIntegral $ w * h * compCount + deinterlaceFunction = case method of + PngNoInterlace -> scanLineInterleaving + PngInterlaceAdam7 -> adam7Unpack + iBitDepth = fromIntegral depth + imgArray <- lift $ newArray (0, arraySize - 1) 0 + let mutableImage = MutableImage (fromIntegral w) (fromIntegral h) imgArray + deinterlaceFunction iBitDepth + (fromIntegral compCount) + (fromIntegral w, fromIntegral h) + (scanlineUnpacker iBitDepth (fromIntegral compCount) + mutableImage) + return imgArray + +generateGreyscalePalette :: Word8 -> PngPalette +generateGreyscalePalette times = listArray (0, fromIntegral possibilities) pixels + where possibilities = 2 ^ times - 1 + pixels = [PixelRGB8 i i i | n <- [0..possibilities] + , let i = n * (255 `div` possibilities)] + +sampleCountOfImageType :: PngImageType -> Word32 +sampleCountOfImageType PngGreyscale = 1 +sampleCountOfImageType PngTrueColour = 3 +sampleCountOfImageType PngIndexedColor = 1 +sampleCountOfImageType PngGreyscaleWithAlpha = 2 +sampleCountOfImageType PngTrueColourWithAlpha = 4 + +paletteRGBA1, paletteRGBA2, paletteRGBA4 :: PngPalette +paletteRGBA1 = generateGreyscalePalette 1 +paletteRGBA2 = generateGreyscalePalette 2 +paletteRGBA4 = generateGreyscalePalette 4 + +applyPalette :: PngPalette -> UArray Int Word8 -> UArray Int Word8 +applyPalette pal img = listArray (0, (initSize + 1) * 3 - 1) pixels + where (_, initSize) = bounds img + pixels = concat [[r, g, b] | ipx <- elems img + , let PixelRGB8 r g b = pal !!! fromIntegral ipx] + +-- | Helper function trying to load a png file from a file on disk. +readPng :: FilePath -> IO (Either String DynamicImage) +readPng path = B.readFile path >>= return . decodePng + +-- | Transform a raw png image to an image, without modifying the +-- underlying pixel type. If the image is greyscale and < 8 bits, +-- a transformation to RGBA8 is performed. This should change +-- in the future. +-- The resulting image let you manage the pixel types. +-- +-- This function can output the following pixel types : +-- +-- * PixelY8 +-- +-- * PixelYA8 +-- +-- * PixelRGB8 +-- +-- * PixelRGBA8 +-- +decodePng :: B.ByteString -> Either String DynamicImage +decodePng byte = do + rawImg <- runGet get byte + let ihdr@(PngIHdr { width = w, height = h }) = header rawImg + compressedImageData = + B.concat [chunkData chunk | chunk <- chunks rawImg + , chunkType chunk == iDATSignature] + zlibHeaderSize = 1 {- compression method/flags code -} + + 1 {- Additional flags/check bits -} + + 4 {-CRC-} + + imager = Image (fromIntegral w) (fromIntegral h) + + unparse _ PngGreyscale bytes + | bitDepth ihdr == 1 = unparse (Just paletteRGBA1) PngIndexedColor bytes + | bitDepth ihdr == 2 = unparse (Just paletteRGBA2) PngIndexedColor bytes + | bitDepth ihdr == 4 = unparse (Just paletteRGBA4) PngIndexedColor bytes + | otherwise = Right . ImageY8 . imager $ runSTUArray stArray + where stArray = S.evalStateT (deinterlacer ihdr) bytes + unparse Nothing PngIndexedColor _ = Left "no valid palette found" + unparse _ PngTrueColour bytes = + Right . ImageRGB8 . imager $ runSTUArray stArray + where stArray = S.evalStateT (deinterlacer ihdr) bytes + unparse _ PngGreyscaleWithAlpha bytes = + Right . ImageYA8 . imager $ runSTUArray stArray + where stArray = S.evalStateT (deinterlacer ihdr) bytes + unparse _ PngTrueColourWithAlpha bytes = + Right . ImageRGBA8 . imager $ runSTUArray stArray + where stArray = S.evalStateT (deinterlacer ihdr) bytes + unparse (Just plte) PngIndexedColor bytes = + Right . ImageRGB8 . imager $ applyPalette plte uarray + where stArray = S.evalStateT (deinterlacer ihdr) bytes + uarray = runSTUArray stArray + + if B.length compressedImageData <= zlibHeaderSize + then Left "Invalid data size" + else let imgData = Z.decompress $ Lb.fromChunks [compressedImageData] + parseableData = B.concat $ Lb.toChunks imgData + palette = case find (\c -> pLTESignature == chunkType c) $ chunks rawImg of + Nothing -> Nothing + Just p -> case parsePalette p of + Left _ -> Nothing + Right plte -> Just plte + in unparse palette (colourType ihdr) parseableData +
+ Codec/Picture/Png/Export.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TypeSynonymInstances #-} +-- | Module implementing a basic png export, no filtering is applyed, but +-- export at least valid images. +module Codec.Picture.Png.Export( PngSavable( .. ) + , writePng + , encodeDynamicPng + , writeDynamicPng + ) where + +import Data.Serialize(encode) +import Data.Array.Unboxed((!)) +import Data.Word(Word8) +import qualified Codec.Compression.Zlib as Z +import qualified Data.ByteString as B +import qualified Data.ByteString.Lazy as Lb + +import Codec.Picture.Types +import Codec.Picture.Png.Type + +-- | Encode an image into a png if possible. +class PngSavable a where + -- | Transform an image into a png encoded bytestring, ready + -- to be writte as a file. + encodePng :: Image a -> B.ByteString + +preparePngHeader :: Image a -> PngImageType -> Word8 -> PngIHdr +preparePngHeader (Image { imageWidth = w, imageHeight = h }) imgType depth = PngIHdr + { width = fromIntegral w + , height = fromIntegral h + , bitDepth = depth + , colourType = imgType + , compressionMethod = 0 + , filterMethod = 0 + , interlaceMethod = PngNoInterlace + } + +-- | Helper function to directly write an image as a png on disk. +writePng :: (PngSavable pixel) => FilePath -> Image pixel -> IO () +writePng path img = B.writeFile path $ encodePng img + +endChunk :: PngRawChunk +endChunk = PngRawChunk { chunkLength = 0 + , chunkType = iENDSignature + , chunkCRC = pngComputeCrc [iENDSignature] + , chunkData = B.empty + } + + +prepareIDatChunk :: B.ByteString -> PngRawChunk +prepareIDatChunk imgData = PngRawChunk + { chunkLength = fromIntegral $ B.length imgData + , chunkType = iDATSignature + , chunkCRC = pngComputeCrc [iDATSignature, imgData] + , chunkData = imgData + } + +genericEncodePng :: PngImageType -> Int -> Image a -> B.ByteString +genericEncodePng imgKind compCount + image@(Image { imageWidth = w, imageHeight = h, imageData = arr }) = + encode PngRawImage { header = hdr, chunks = [prepareIDatChunk strictEncoded, endChunk]} + where hdr = preparePngHeader image imgKind 8 + compBound = compCount - 1 + encodeLine line = + 0 : [arr ! ((line * w + column) * compCount + comp) | column <- [0 .. w - 1] + , comp <- [0 .. compBound]] + imgEncodedData = Z.compress . Lb.pack + $ concat [encodeLine line | line <- [0 .. h - 1]] + strictEncoded = B.concat $ Lb.toChunks imgEncodedData + +instance PngSavable PixelRGBA8 where + encodePng = genericEncodePng PngTrueColourWithAlpha 4 + +instance PngSavable PixelRGB8 where + encodePng = genericEncodePng PngTrueColour 3 + +instance PngSavable Pixel8 where + encodePng = genericEncodePng PngGreyscale 1 + +-- | Write a dynamic image in a .png image file if possible. +-- The same restriction as encodeDynamicPng apply. +writeDynamicPng :: FilePath -> DynamicImage -> IO (Either String Bool) +writeDynamicPng path img = case encodeDynamicPng img of + Left err -> return $ Left err + Right b -> B.writeFile path b >> return (Right True) + +-- | Encode a dynamic image in bmp if possible, supported pixel type are : +-- +-- - RGB8 +-- +-- - RGBA8 +-- +-- - Y8 +-- +encodeDynamicPng :: DynamicImage -> Either String B.ByteString +encodeDynamicPng (ImageRGB8 img) = Right $ encodePng img +encodeDynamicPng (ImageRGBA8 img) = Right $ encodePng img +encodeDynamicPng (ImageY8 img) = Right $ encodePng img +encodeDynamicPng _ = Left "Unsupported image format for PNG export"
+ Codec/Picture/Png/Type.hs view
@@ -0,0 +1,315 @@+-- | Low level png module, you should import 'Codec.Picture.Png' instead. +module Codec.Picture.Png.Type( PngIHdr( .. ) + , PngFilter( .. ) + , PngInterlaceMethod( .. ) + , PngPalette + , PngImageType( .. ) + , parsePalette + , pngComputeCrc + , pLTESignature + , iDATSignature + , iENDSignature + -- * Low level types + , ChunkSignature + , PngRawImage( .. ) + , PngChunk( .. ) + , PngRawChunk( .. ) + , PngLowLevel( .. ) + ) where + +import Control.Applicative( (<$>) ) +import Control.Monad( when, replicateM ) +import Data.Bits( xor, (.&.), shiftR ) +import Data.Serialize( Serialize(..), Get, get, runGet, runPut + , putWord8, getWord8 + , putWord32be, getWord32be + , getByteString, putByteString ) +import Data.Array.Unboxed( Array, UArray, listArray, (!) ) +import Data.List( foldl' ) +import Data.Word( Word32, Word8 ) +import qualified Data.ByteString as B + +import Codec.Picture.Types + +-------------------------------------------------- +---- Types +-------------------------------------------------- + +-- | Value used to identify a png chunk, must be 4 bytes long. +type ChunkSignature = B.ByteString + +-- | Generic header used in PNG images. +data PngIHdr = PngIHdr + { width :: Word32 -- ^ Image width in number of pixel + , height :: Word32 -- ^ Image height in number of pixel + , bitDepth :: Word8 -- ^ Number of bit per sample + , colourType :: PngImageType -- ^ Kind of png image (greyscale, true color, indexed...) + , compressionMethod :: Word8 -- ^ Compression method used + , filterMethod :: Word8 -- ^ Must be 0 + , interlaceMethod :: PngInterlaceMethod -- ^ If the image is interlaced (for progressive rendering) + } + deriving Show + +-- | What kind of information is encoded in the IDAT section +-- of the PngFile +data PngImageType = + PngGreyscale + | PngTrueColour + | PngIndexedColor + | PngGreyscaleWithAlpha + | PngTrueColourWithAlpha + deriving Show + +-- | Raw parsed image which need to be decoded. +data PngRawImage = PngRawImage + { header :: PngIHdr + , chunks :: [PngRawChunk] + } + +-- | Palette with indices beginning at 0 to elemcount - 1 +type PngPalette = Array Int PixelRGB8 + +-- | Parse a palette from a png chunk. +parsePalette :: PngRawChunk -> Either String PngPalette +parsePalette plte + | chunkLength plte `mod` 3 /= 0 = Left "Invalid palette size" + | otherwise = listArray (0, pixelCount - 1) <$> runGet pixelUnpacker (chunkData plte) + where pixelUnpacker = replicateM (fromIntegral pixelCount) get + pixelCount = fromIntegral $ chunkLength plte `div` 3 + +-- | Data structure during real png loading/parsing +data PngRawChunk = PngRawChunk + { chunkLength :: Word32 + , chunkType :: ChunkSignature + , chunkCRC :: Word32 + , chunkData :: B.ByteString + } + +-- | PNG chunk representing some extra information found in the parsed file. +data PngChunk = PngChunk + { pngChunkData :: B.ByteString -- ^ The raw data inside the chunk + , pngChunkSignature :: ChunkSignature -- ^ The name of the chunk. + } + +-- | Low level access to PNG information +data PngLowLevel a = PngLowLevel + { pngImage :: Image a -- ^ The real uncompressed image + , pngChunks :: [PngChunk] -- ^ List of raw chunk where some user data might be present. + } + +-- | The pixels value should be : +-- +---+---+ +-- | c | b | +-- +---+---+ +-- | a | x | +-- +---+---+ +-- x being the current filtered pixel +data PngFilter = + -- | Filt(x) = Orig(x), Recon(x) = Filt(x) + FilterNone + -- | Filt(x) = Orig(x) - Orig(a), Recon(x) = Filt(x) + Recon(a) + | FilterSub + -- | Filt(x) = Orig(x) - Orig(b), Recon(x) = Filt(x) + Recon(b) + | FilterUp + -- | Filt(x) = Orig(x) - floor((Orig(a) + Orig(b)) / 2), + -- Recon(x) = Filt(x) + floor((Recon(a) + Recon(b)) / 2) + | FilterAverage + -- | Filt(x) = Orig(x) - PaethPredictor(Orig(a), Orig(b), Orig(c)), + -- Recon(x) = Filt(x) + PaethPredictor(Recon(a), Recon(b), Recon(c)) + | FilterPaeth + deriving (Enum, Show) + +-- | Different known interlace methods for PNG image +data PngInterlaceMethod = + -- | No interlacing, basic data ordering, line by line + -- from left to right. + PngNoInterlace + + -- | Use the Adam7 ordering, see `adam7Reordering` + | PngInterlaceAdam7 + deriving (Enum, Show) + +-------------------------------------------------- +---- Instances +-------------------------------------------------- +instance Serialize PngFilter where + put = putWord8 . toEnum . fromEnum + get = getWord8 >>= \w -> case w of + 0 -> return FilterNone + 1 -> return FilterSub + 2 -> return FilterUp + 3 -> return FilterAverage + 4 -> return FilterPaeth + _ -> fail "Invalid scanline filter" + +instance Serialize PngRawImage where + put img = do + putByteString pngSignature + put $ header img + mapM_ put $ chunks img + + get = parseRawPngImage + +instance Serialize PngRawChunk where + put chunk = do + putWord32be $ chunkLength chunk + putByteString $ chunkType chunk + when (chunkLength chunk /= 0) + (putByteString $ chunkData chunk) + putWord32be $ chunkCRC chunk + + get = do + size <- getWord32be + chunkSig <- getByteString (B.length iHDRSignature) + imgData <- if size == 0 + then return B.empty + else getByteString (fromIntegral size) + crc <- getWord32be + + let computedCrc = pngComputeCrc [chunkSig, imgData] + when (computedCrc `xor` crc /= 0) + (fail $ "Invalid CRC : " ++ show computedCrc ++ ", " + ++ show crc) + return PngRawChunk { + chunkLength = size, + chunkData = imgData, + chunkCRC = crc, + chunkType = chunkSig + } + +instance Serialize PngIHdr where + put hdr = do + putWord32be 13 + let inner = runPut $ do + putByteString iHDRSignature + putWord32be $ width hdr + putWord32be $ height hdr + putWord8 $ bitDepth hdr + put $ colourType hdr + put $ compressionMethod hdr + put $ filterMethod hdr + put $ interlaceMethod hdr + crc = pngComputeCrc [inner] + putByteString inner + putWord32be crc + + get = do + _size <- getWord32be + ihdrSig <- getByteString (B.length iHDRSignature) + when (ihdrSig /= iHDRSignature) + (fail "Invalid PNG file, wrong ihdr") + w <- getWord32be + h <- getWord32be + depth <- get + colorType <- get + compression <- get + filtermethod <- get + interlace <- get + _crc <- getWord32be + return PngIHdr { + width = w, + height = h, + bitDepth = depth, + colourType = colorType, + compressionMethod = compression, + filterMethod = filtermethod, + interlaceMethod = interlace + } + +-- | Parse method for a png chunk, without decompression. +parseChunks :: Get [PngRawChunk] +parseChunks = do + chunk <- get + + if chunkType chunk == iENDSignature + then return [chunk] + else (chunk:) <$> parseChunks + + +instance Serialize PngInterlaceMethod where + get = getWord8 >>= \w -> case w of + 0 -> return PngNoInterlace + 1 -> return PngInterlaceAdam7 + _ -> fail "Invalid interlace method" + + put PngNoInterlace = putWord8 0 + put PngInterlaceAdam7 = putWord8 1 + +-- | Implementation of the get method for the PngRawImage, +-- unpack raw data, without decompressing it. +parseRawPngImage :: Get PngRawImage +parseRawPngImage = do + sig <- getByteString (B.length pngSignature) + when (sig /= pngSignature) + (fail "Invalid PNG file, signature broken") + + ihdr <- get + + chunkList <- parseChunks + return PngRawImage { header = ihdr, chunks = chunkList } + +-------------------------------------------------- +---- functions +-------------------------------------------------- + +-- | Signature signalling that the following data will be a png image +-- in the png bit stream +pngSignature :: ChunkSignature +pngSignature = signature [137, 80, 78, 71, 13, 10, 26, 10] + +-- | Helper function to help pack signatures. +signature :: [Word8] -> ChunkSignature +signature = B.pack . map (toEnum . fromEnum) + +-- | Signature for the header chunk of png (must be the first) +iHDRSignature :: ChunkSignature +iHDRSignature = signature [73, 72, 68, 82] + +-- | Signature for a palette chunk in the pgn file. Must +-- occure before iDAT. +pLTESignature :: ChunkSignature +pLTESignature = signature [80, 76, 84, 69] + +-- | Signature for a data chuck (with image parts in it) +iDATSignature :: ChunkSignature +iDATSignature = signature [73, 68, 65, 84] + +-- | Signature for the last chunk of a png image, telling +-- the end. +iENDSignature :: ChunkSignature +iENDSignature = signature [73, 69, 78, 68] + +instance Serialize PngImageType where + put PngGreyscale = putWord8 0 + put PngTrueColour = putWord8 2 + put PngIndexedColor = putWord8 3 + put PngGreyscaleWithAlpha = putWord8 4 + put PngTrueColourWithAlpha = putWord8 6 + + get = get >>= imageTypeOfCode + +imageTypeOfCode :: Word8 -> Get PngImageType +imageTypeOfCode 0 = return PngGreyscale +imageTypeOfCode 2 = return PngTrueColour +imageTypeOfCode 3 = return PngIndexedColor +imageTypeOfCode 4 = return PngGreyscaleWithAlpha +imageTypeOfCode 6 = return PngTrueColourWithAlpha +imageTypeOfCode _ = fail "Invalid png color code" + +-- | From the Annex D of the png specification. +pngCrcTable :: UArray Word32 Word32 +pngCrcTable = listArray (0, 255) [ foldl' updateCrcConstant c [zero .. 7] | c <- [0 .. 255] ] + where zero = 0 :: Int -- To avoid defaulting to Integer + updateCrcConstant c _ | c .&. 1 /= 0 = magicConstant `xor` (c `shiftR` 1) + | otherwise = c `shiftR` 1 + magicConstant = 0xedb88320 :: Word32 + +-- | Compute the CRC of a raw buffer, as described in annex D of the PNG +-- specification. +pngComputeCrc :: [B.ByteString] -> Word32 +pngComputeCrc = (0xFFFFFFFF `xor`) . B.foldl' updateCrc 0xFFFFFFFF . B.concat + where updateCrc crc val = + let u32Val = fromIntegral val + lutVal = pngCrcTable ! ((crc `xor` u32Val) .&. 0xFF) + in lutVal `xor` (crc `shiftR` 8) +
+ Codec/Picture/Types.hs view
@@ -0,0 +1,431 @@+{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeSynonymInstances #-} +-- | Module providing the basic types for image manipulation in the library. +-- Defining the types used to store all those _Juicy Pixels_ +module Codec.Picture.Types( -- * Types + -- ** Image types + Image( .. ) + , MutableImage( .. ) + , DynamicImage( .. ) + , PixelType( .. ) + -- ** Pixel types + , Pixel8 + , PixelYA8( .. ) + , PixelRGB8( .. ) + , PixelRGBA8( .. ) + , PixelYCbCr8( .. ) + + -- * Type classes + , ColorConvertible( .. ) + , Pixel(..) + , ColorSpaceConvertible( .. ) + -- * Helper functions + , canConvertTo + ) where + +import Control.Applicative( (<$>), (<*>) ) +import Control.Monad.ST( ST ) +import Data.Word( Word8 ) +import Data.Array.Unboxed( UArray, (!) ) +import Data.Array.ST( MArray, STUArray + , writeArray, newArray, runSTUArray, readArray ) +import Data.Serialize( Serialize, put, get ) + + +-- | Image or pixel buffer, the coordinates are assumed to start +-- from the upper-left corner of the image, with the horizontal +-- position first, then the vertical one. +data Image a = Image + { -- | Width of the image in pixels + imageWidth :: {-# UNPACK #-} !Int + -- | Height of the image in pixels. + , imageHeight :: {-# UNPACK #-} !Int + + -- | The real image, to extract pixels at some position + -- you should use the helpers functions. + , imageData :: UArray Int Word8 + } + +-- | Image or pixel buffer, the coordinates are assumed to start +-- from the upper-left corner of the image, with the horizontal +-- position first, then the vertical one. The image can be transformed in place. +data MutableImage s a = MutableImage + { -- | Width of the image in pixels + mutableImageWidth :: {-# UNPACK #-} !Int + + -- | Height of the image in pixels. + , mutableImageHeight :: {-# UNPACK #-} !Int + + -- | The real image, to extract pixels at some position + -- you should use the helpers functions. + , mutableImageData :: STUArray s Int Word8 + } + +-- | Type allowing the loading of an image with different pixel +-- structures +data DynamicImage = + -- | A greyscale image. + ImageY8 (Image Pixel8) + -- | An image in greyscale with an alpha channel. + | ImageYA8 (Image PixelYA8) + -- | An image in true color. + | ImageRGB8 (Image PixelRGB8) + -- | An image in true color and an alpha channel. + | ImageRGBA8 (Image PixelRGBA8) + -- | An image in the colorspace used by Jpeg images. + | ImageYCbCr8 (Image PixelYCbCr8) + +-- | Simple alias for greyscale value in 8 bits. +type Pixel8 = Word8 + +-- | Pixel type storing Luminance (Y) and alpha information +-- on 8 bits. +-- Value are stored in the following order : +-- +-- * Luminance +-- +-- * Alpha +-- +data PixelYA8 = PixelYA8 {-# UNPACK #-} !Word8 -- Luminance + {-# UNPACK #-} !Word8 -- Alpha value + +-- | Pixel type storing classic pixel on 8 bits +-- Value are stored in the following order : +-- +-- * Red +-- +-- * Green +-- +-- * Blue +-- +data PixelRGB8 = PixelRGB8 {-# UNPACK #-} !Word8 -- Red + {-# UNPACK #-} !Word8 -- Green + {-# UNPACK #-} !Word8 -- Blue + +-- | Pixel storing data in the YCbCr colorspace, +-- value are stored in teh following order : +-- +-- * Y (luminance) +-- +-- * Cr +-- +-- * Cb +-- +data PixelYCbCr8 = PixelYCbCr8 {-# UNPACK #-} !Word8 -- Y luminance + {-# UNPACK #-} !Word8 -- Cr red difference + {-# UNPACK #-} !Word8 -- Cb blue difference + +-- | Pixel type storing a classic pixel, with an alpha component. +-- Values are stored in the following order +-- +-- * Red +-- +-- * Green +-- +-- * Blue +-- +-- * Alpha +data PixelRGBA8 = PixelRGBA8 {-# UNPACK #-} !Word8 -- Red + {-# UNPACK #-} !Word8 -- Green + {-# UNPACK #-} !Word8 -- Blue + {-# UNPACK #-} !Word8 -- Alpha + +instance Serialize PixelYA8 where + {-# INLINE put #-} + put (PixelYA8 y a) = put y >> put a + {-# INLINE get #-} + get = PixelYA8 <$> get <*> get + +instance Serialize PixelRGB8 where + {-# INLINE put #-} + put (PixelRGB8 r g b) = put r >> put g >> put b + {-# INLINE get #-} + get = PixelRGB8 <$> get <*> get <*> get + +instance Serialize PixelYCbCr8 where + {-# INLINE put #-} + put (PixelYCbCr8 y cb cr) = put y >> put cb >> put cr + {-# INLINE get #-} + get = PixelYCbCr8 <$> get <*> get <*> get + +instance Serialize PixelRGBA8 where + {-# INLINE put #-} + put (PixelRGBA8 r g b a) = put r >> put g >> put b >> put a + {-# INLINE get #-} + get = PixelRGBA8 <$> get <*> get <*> get <*> get + +-- | Describe pixel kind at runtime +data PixelType = PixelMonochromatic -- ^ For 2 bits pixels + | PixelGreyscale + | PixelGreyscaleAlpha + | PixelRedGreenBlue8 + | PixelRedGreenBlueAlpha8 + | PixelYChromaRChromaB8 + deriving Eq + +-- | Typeclass used to query a type about it's properties +-- regarding casting to other pixel types +class (Serialize a) => Pixel a where + -- | Tell if a pixel can be converted to another pixel, + -- the first value should not be used, and 'undefined' can + -- be used as a valid value. + canPromoteTo :: a -> PixelType -> Bool + + -- | Return the number of component of the pixel + componentCount :: a -> Int + + -- | Calculate the index for the begining of the pixel + pixelBaseIndex :: Image a -> Int -> Int -> Int + pixelBaseIndex (Image { imageWidth = w }) x y = + (x + y * w) * componentCount (undefined :: a) + + -- | Calculate theindex for the begining of the pixel at position x y + mutablePixelBaseIndex :: MutableImage s a -> Int -> Int -> Int + mutablePixelBaseIndex (MutableImage { mutableImageWidth = w }) x y = + (x + y * w) * componentCount (undefined :: a) + + -- | Return the constructor associated to the type, again + -- the value in the first parameter is not used, so you can use undefined + promotionType :: a -> PixelType + + -- | Extract a pixel at a given position, (x, y), the origin + -- is assumed to be at the corner top left, positive y to the + -- bottom of the image + pixelAt :: Image a -> Int -> Int -> a + + -- | Same as pixelAt but for mutable images. + readPixel :: MutableImage s a -> Int -> Int -> ST s a + + -- | Write a pixel in a mutable image at position x y + writePixel :: MutableImage s a -> Int -> Int -> a -> ST s () + +-- | Tell if you can convert between two pixel types, both arguments +-- are unused. +canConvertTo :: (Pixel a, Pixel b) => a -> b -> Bool +canConvertTo a b = canPromoteTo a $ promotionType b + +-- | Implement upcasting for pixel types +-- Minimal declaration declaration `promotePixel` +-- It is strongly recommanded to overload promoteImage to keep +-- performance acceptable +class (Pixel a, Pixel b) => ColorConvertible a b where + -- | Convert a pixel type to another pixel type. This + -- operation should never loss any data. + promotePixel :: a -> b + + -- | Change the underlying pixel type of an image by performing a full copy + -- of it. + promoteImage :: Image a -> Image b + promoteImage image@(Image { imageWidth = w, imageHeight = h }) = + Image w h pixels + where pixels = runSTUArray $ do + newArr <- newArray (0, w * h * componentCount (undefined :: b) - 1) 0 + let wrapped = MutableImage w h newArr + promotedPixel :: Int -> Int -> b + promotedPixel x y = promotePixel $ pixelAt image x y + sequence_ [writePixel wrapped x y $ promotedPixel x y + | y <- [0 .. h - 1], x <- [0 .. w - 1] ] + return newArr + +-- | This class abstract colorspace conversion. This +-- conversion can be lossy, which ColorConvertible cannot +class (Pixel a, Pixel b) => ColorSpaceConvertible a b where + convertPixel :: a -> b + + convertImage :: Image a -> Image b + convertImage image@(Image { imageWidth = w, imageHeight = h }) = + Image w h pixels + where pixels = runSTUArray $ do + newArr <- newArray (0, w * h * componentCount (undefined :: b) - 1) 0 + let wrapped = MutableImage w h newArr + promotedPixel :: Int -> Int -> b + promotedPixel x y = convertPixel $ pixelAt image x y + sequence_ [writePixel wrapped x y $ promotedPixel x y + | y <- [0 .. h - 1], x <- [0 .. w - 1] ] + return newArr + +-- | Free promotion for identic pixel types +instance (Pixel a) => ColorConvertible a a where + {-# INLINE promotePixel #-} + promotePixel = id + + {-# INLINE promoteImage #-} + promoteImage = id + +{-# INLINE (.!!!.) #-} +(.!!!.) :: (MArray array e m) => array Int e -> Int -> m e +(.!!!.) = readArray -- unsafeRead + +{-# INLINE (.<-.) #-} +(.<-.) :: (MArray array e m) => array Int e -> Int -> e -> m () +(.<-.) = writeArray -- unsafeWrite + +-------------------------------------------------- +---- Pixel8 instances +-------------------------------------------------- +instance Pixel Pixel8 where + canPromoteTo _ a = a /= PixelMonochromatic + promotionType _ = PixelGreyscale + componentCount _ = 1 + pixelAt (Image { imageWidth = w, imageData = arr }) x y = arr ! (x + y * w) + + readPixel image@(MutableImage { mutableImageData = arr }) x y = + arr .!!!. (mutablePixelBaseIndex image x y) + + writePixel image@(MutableImage { mutableImageData = arr }) x y v = + (arr .<-. (mutablePixelBaseIndex image x y)) v + +instance ColorConvertible Pixel8 PixelYA8 where + {-# INLINE promotePixel #-} + promotePixel c = PixelYA8 c 255 + +instance ColorConvertible Pixel8 PixelRGB8 where + {-# INLINE promotePixel #-} + promotePixel c = PixelRGB8 c c c + +instance ColorConvertible Pixel8 PixelRGBA8 where + {-# INLINE promotePixel #-} + promotePixel c = PixelRGBA8 c c c 255 + +-------------------------------------------------- +---- PixelYA8 instances +-------------------------------------------------- +instance Pixel PixelYA8 where + canPromoteTo _ a = a == PixelRedGreenBlueAlpha8 + promotionType _ = PixelGreyscaleAlpha + componentCount _ = 2 + pixelAt image@(Image { imageData = arr }) x y = PixelYA8 (arr ! (baseIdx + 0)) + (arr ! (baseIdx + 1)) + where baseIdx = pixelBaseIndex image x y + + readPixel image@(MutableImage { mutableImageData = arr }) x y = do + yv <- arr .!!!. baseIdx + av <- arr .!!!. (baseIdx + 1) + return $ PixelYA8 yv av + where baseIdx = mutablePixelBaseIndex image x y + + writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelYA8 yv av) = do + let baseIdx = mutablePixelBaseIndex image x y + (arr .<-. (baseIdx + 0)) yv + (arr .<-. (baseIdx + 1)) av + + +instance ColorConvertible PixelYA8 PixelRGB8 where + {-# INLINE promotePixel #-} + promotePixel (PixelYA8 y _) = PixelRGB8 y y y + +instance ColorConvertible PixelYA8 PixelRGBA8 where + {-# INLINE promotePixel #-} + promotePixel (PixelYA8 y a) = PixelRGBA8 y y y a + +-------------------------------------------------- +---- PixelRGB8 instances +-------------------------------------------------- +instance Pixel PixelRGB8 where + canPromoteTo _ PixelMonochromatic = False + canPromoteTo _ PixelGreyscale = False + canPromoteTo _ _ = True + + componentCount _ = 3 + + promotionType _ = PixelRedGreenBlue8 + + pixelAt image@(Image { imageData = arr }) x y = PixelRGB8 (arr ! (baseIdx + 0)) + (arr ! (baseIdx + 1)) + (arr ! (baseIdx + 2)) + where baseIdx = pixelBaseIndex image x y + + readPixel image@(MutableImage { mutableImageData = arr }) x y = do + rv <- arr .!!!. baseIdx + gv <- arr .!!!. (baseIdx + 1) + bv <- arr .!!!. (baseIdx + 2) + return $ PixelRGB8 rv gv bv + where baseIdx = mutablePixelBaseIndex image x y + + writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelRGB8 rv gv bv) = do + let baseIdx = mutablePixelBaseIndex image x y + (arr .<-. (baseIdx + 0)) rv + (arr .<-. (baseIdx + 1)) gv + (arr .<-. (baseIdx + 2)) bv + +instance ColorConvertible PixelRGB8 PixelRGBA8 where + {-# INLINE promotePixel #-} + promotePixel (PixelRGB8 r g b) = PixelRGBA8 r g b 255 + +-------------------------------------------------- +---- PixelRGBA8 instances +-------------------------------------------------- +instance Pixel PixelRGBA8 where + canPromoteTo _ PixelRedGreenBlueAlpha8 = True + canPromoteTo _ _ = False + + promotionType _ = PixelRedGreenBlueAlpha8 + + componentCount _ = 4 + + pixelAt image@(Image { imageData = arr }) x y = PixelRGBA8 (arr ! (baseIdx + 0)) + (arr ! (baseIdx + 1)) + (arr ! (baseIdx + 2)) + (arr ! (baseIdx + 3)) + where baseIdx = pixelBaseIndex image x y + + readPixel image@(MutableImage { mutableImageData = arr }) x y = do + rv <- arr .!!!. baseIdx + gv <- arr .!!!. (baseIdx + 1) + bv <- arr .!!!. (baseIdx + 2) + av <- arr .!!!. (baseIdx + 3) + return $ PixelRGBA8 rv gv bv av + where baseIdx = mutablePixelBaseIndex image x y + + writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelRGBA8 rv gv bv av) = do + let baseIdx = mutablePixelBaseIndex image x y + (arr .<-. (baseIdx + 0)) rv + (arr .<-. (baseIdx + 1)) gv + (arr .<-. (baseIdx + 2)) bv + (arr .<-. (baseIdx + 3)) av + +-------------------------------------------------- +---- PixelYCbCr8 instances +-------------------------------------------------- +instance Pixel PixelYCbCr8 where + canPromoteTo _ _ = False + promotionType _ = PixelYChromaRChromaB8 + componentCount _ = 3 + pixelAt image@(Image { imageData = arr }) x y = PixelYCbCr8 (arr ! (baseIdx + 0)) + (arr ! (baseIdx + 1)) + (arr ! (baseIdx + 2)) + where baseIdx = pixelBaseIndex image x y + + readPixel image@(MutableImage { mutableImageData = arr }) x y = do + yv <- arr .!!!. baseIdx + cbv <- arr .!!!. (baseIdx + 1) + crv <- arr .!!!. (baseIdx + 2) + return $ PixelYCbCr8 yv cbv crv + where baseIdx = mutablePixelBaseIndex image x y + + writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelYCbCr8 yv cbv crv) = do + let baseIdx = mutablePixelBaseIndex image x y + (arr .<-. (baseIdx + 0)) yv + (arr .<-. (baseIdx + 1)) cbv + (arr .<-. (baseIdx + 2)) crv + +instance ColorSpaceConvertible PixelYCbCr8 PixelRGB8 where + {-# INLINE convertPixel #-} + convertPixel (PixelYCbCr8 y_w8 cb_w8 cr_w8) = PixelRGB8 (clampWord8 r) (clampWord8 g) (clampWord8 b) + where y :: Float + y = fromIntegral y_w8 - 128.0 + cb = fromIntegral cb_w8 - 128.0 + cr = fromIntegral cr_w8 - 128.0 + + clampWord8 = truncate . max 0.0 . min 255.0 . (128 +) + + cred = 0.299 + cgreen = 0.587 + cblue = 0.114 + + r = cr * (2 - 2 * cred) + y + b = cb * (2 - 2 * cblue) + y + g = (y - cblue * b - cred * r) / cgreen +
+ JuicyPixels.cabal view
@@ -0,0 +1,52 @@+Name: JuicyPixels +Version: 1.0 +Synopsis: Picture loading/serialization (in png, jpeg and bitmap) +Description: + This library can load and store images in various image formats, + for now mainly in PNG/Bitmap and Jpeg (jpeg writing not + implemented yet though) +homepage: https://github.com/Twinside/Juicy.Pixels +License: BSD3 +License-file: LICENSE +Author: Vincent Berthoux +Maintainer: vincent.berthoux@gmail.com +Category: Codec, Graphics +Build-type: Simple + +-- Extra-source-files: + +-- Constraint on the version of Cabal needed to build this package. +Cabal-version: >=1.6 + +Source-Repository head + Type: git + Location: git://github.com/Twinside/Juicy.Pixels.git + +Source-Repository this + Type: git + Location: git://github.com/Twinside/Juicy.Pixels.git + Tag: v1.0 + +Library + Exposed-modules: Codec.Picture, + Codec.Picture.Bitmap, + Codec.Picture.Png, + Codec.Picture.Jpg, + Codec.Picture.Types + + Ghc-options: -O3 -Wall + Build-depends: base >= 4 && < 5, + array, + bytestring, + mtl >= 1.1, + cereal >= 0.3.3.0 && < 0.4, + zlib >= 0.5.3.1, + transformers >= 0.2.2 && < 0.3 + +-- Modules not exported by this package. + Other-modules: Codec.Picture.Jpg.DefaultTable, + Codec.Picture.Jpg.FastIdct, + Codec.Picture.Png.Export, + Codec.Picture.Png.Type + +
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2011, Vincent Berthoux + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Vincent Berthoux nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple +main = defaultMain