JuicyPixels 3.1.2 → 3.1.3
raw patch · 48 files changed
+9730/−8642 lines, 48 filesdep +containers
Dependencies added: containers
Files
- Codec/Picture.hs +0/−226
- Codec/Picture/BitWriter.hs +0/−265
- Codec/Picture/Bitmap.hs +0/−350
- Codec/Picture/ConvGraph.hs +0/−9
- Codec/Picture/Gif.hs +0/−380
- Codec/Picture/Gif/LZW.hs +0/−189
- Codec/Picture/HDR.hs +0/−462
- Codec/Picture/InternalHelper.hs +0/−33
- Codec/Picture/Jpg.hs +0/−720
- Codec/Picture/Jpg/Common.hs +0/−232
- Codec/Picture/Jpg/DefaultTable.hs +0/−294
- Codec/Picture/Jpg/FastDct.hs +0/−203
- Codec/Picture/Jpg/FastIdct.hs +0/−228
- Codec/Picture/Jpg/Progressive.hs +0/−309
- Codec/Picture/Jpg/Types.hs +0/−546
- Codec/Picture/Png.hs +0/−500
- Codec/Picture/Png/Export.hs +0/−163
- Codec/Picture/Png/Type.hs +0/−329
- Codec/Picture/Saving.hs +0/−165
- Codec/Picture/Tiff.hs +0/−1285
- Codec/Picture/Types.hs +0/−1727
- Codec/Picture/VectorByteConversion.hs +0/−20
- JuicyPixels.cabal +11/−7
- changelog +6/−0
- src/Codec/Picture.hs +304/−0
- src/Codec/Picture/BitWriter.hs +299/−0
- src/Codec/Picture/Bitmap.hs +350/−0
- src/Codec/Picture/ColorQuant.hs +380/−0
- src/Codec/Picture/ConvGraph.hs +9/−0
- src/Codec/Picture/Gif.hs +685/−0
- src/Codec/Picture/Gif/LZW.hs +189/−0
- src/Codec/Picture/Gif/LZWEncoding.hs +93/−0
- src/Codec/Picture/HDR.hs +462/−0
- src/Codec/Picture/InternalHelper.hs +33/−0
- src/Codec/Picture/Jpg.hs +721/−0
- src/Codec/Picture/Jpg/Common.hs +232/−0
- src/Codec/Picture/Jpg/DefaultTable.hs +294/−0
- src/Codec/Picture/Jpg/FastDct.hs +203/−0
- src/Codec/Picture/Jpg/FastIdct.hs +228/−0
- src/Codec/Picture/Jpg/Progressive.hs +321/−0
- src/Codec/Picture/Jpg/Types.hs +546/−0
- src/Codec/Picture/Png.hs +501/−0
- src/Codec/Picture/Png/Export.hs +192/−0
- src/Codec/Picture/Png/Type.hs +329/−0
- src/Codec/Picture/Saving.hs +187/−0
- src/Codec/Picture/Tiff.hs +1285/−0
- src/Codec/Picture/Types.hs +1837/−0
- src/Codec/Picture/VectorByteConversion.hs +33/−0
− Codec/Picture.hs
@@ -1,226 +0,0 @@-{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE CPP #-} --- | Main module for image import/export 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 'saveBmpImage', 'saveJpgImage' & 'savePngImage' --- functions -module Codec.Picture ( - -- * Generic functions - readImage - , decodeImage - , pixelMap - , generateImage - , generateFoldImage - , withImage - - -- * Generic image writing - , saveBmpImage - , saveJpgImage - , savePngImage - , saveTiffImage - , saveRadianceImage - - -- * Specific image format functions - -- ** Bitmap handling - , BmpEncodable - , writeBitmap - , encodeBitmap - , readBitmap - , decodeBitmap - , encodeDynamicBitmap - , writeDynamicBitmap - - -- ** Gif handling - , readGif - , readGifImages - , decodeGif - , decodeGifImages - - -- ** Jpeg handling - , readJpeg - , decodeJpeg - , encodeJpeg - , encodeJpegAtQuality - - -- ** Png handling - , PngSavable( .. ) - , readPng - , decodePng - , writePng - , encodeDynamicPng - , writeDynamicPng - - -- ** Tiff handling - , readTiff - , TiffSaveable - , decodeTiff - , encodeTiff - , writeTiff - - -- ** HDR (Radiance/RGBE) handling - , readHDR - , decodeHDR - , encodeHDR - , writeHDR - - -- * Image types and pixel types - -- ** Image - , Image( .. ) - , DynamicImage( .. ) - -- ** Pixels - , Pixel( .. ) - -- $graph - , Pixel8 - , Pixel16 - , PixelF - - , PixelYA8( .. ) - , PixelYA16( .. ) - , PixelRGB8( .. ) - , PixelRGB16( .. ) - , PixelRGBF( .. ) - , PixelRGBA8( .. ) - , PixelRGBA16( .. ) - , PixelYCbCr8( .. ) - , PixelCMYK8( .. ) - , PixelCMYK16( .. ) - ) where - -import Control.Applicative( (<$>) ) -import Control.DeepSeq( NFData, deepseq ) -import qualified Control.Exception as Exc ( catch, IOException ) -import Codec.Picture.Bitmap( BmpEncodable, decodeBitmap - , writeBitmap, encodeBitmap - , encodeDynamicBitmap, writeDynamicBitmap ) -import Codec.Picture.Jpg( decodeJpeg, encodeJpeg, encodeJpegAtQuality ) -import Codec.Picture.Png( PngSavable( .. ), decodePng, writePng - , encodeDynamicPng , writeDynamicPng ) -import Codec.Picture.Gif( decodeGif, decodeGifImages ) -import Codec.Picture.HDR( decodeHDR - , encodeHDR - , writeHDR - ) -import Codec.Picture.Tiff( decodeTiff - , TiffSaveable - , encodeTiff - , writeTiff ) -import Codec.Picture.Saving -import Codec.Picture.Types --- import System.IO ( withFile, IOMode(ReadMode) ) -#ifdef WITH_MMAP_BYTESTRING -import System.IO.MMap ( mmapFileByteString ) -#endif - -import qualified Data.ByteString as B -import qualified Data.ByteString.Lazy as L - -#include "ConvGraph.hs" - --- | 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 - -withImageDecoder :: (NFData a) - => (B.ByteString -> Either String a) -> FilePath - -> IO (Either String a) -withImageDecoder decoder path = Exc.catch doit - (\e -> return . Left $ show (e :: Exc.IOException)) - where doit = force . decoder <$> get -#ifdef WITH_MMAP_BYTESTRING - get = mmapFileByteString path Nothing -#else - get = B.readFile path -#endif - -- force appeared in deepseq 1.3, Haskell Platform - -- provide 1.1 - force x = x `deepseq` x - --- | Load an image file without even thinking about it, it does everything --- as 'decodeImage' -readImage :: FilePath -> IO (Either String DynamicImage) -readImage = withImageDecoder decodeImage - --- | 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) - ,("Bitmap", decodeBitmap) - ,("GIF", decodeGif) - ,("HDR", decodeHDR) - ,("Tiff", decodeTiff) - ] - --- | Helper function trying to load a png file from a file on disk. -readPng :: FilePath -> IO (Either String DynamicImage) -readPng = withImageDecoder decodePng - --- | Helper function trying to load a gif file from a file on disk. -readGif :: FilePath -> IO (Either String DynamicImage) -readGif = withImageDecoder decodeGif - --- | Helper function trying to load tiff file from a file on disk. -readTiff :: FilePath -> IO (Either String DynamicImage) -readTiff = withImageDecoder decodeTiff - --- | Helper function trying to load all the images of an animated --- gif file. -readGifImages :: FilePath -> IO (Either String [Image PixelRGB8]) -readGifImages = withImageDecoder decodeGifImages - --- | 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 = withImageDecoder decodeJpeg - --- | Try to load a .bmp file. The colorspace would be RGB or RGBA -readBitmap :: FilePath -> IO (Either String DynamicImage) -readBitmap = withImageDecoder decodeBitmap - --- | Try to load a .pic file. The colorspace can only be --- RGB with floating point precision. -readHDR :: FilePath -> IO (Either String DynamicImage) -readHDR = withImageDecoder decodeHDR - --- | Save an image to a '.jpg' file, will do everything it can to save an image. -saveJpgImage :: Int -> FilePath -> DynamicImage -> IO () -saveJpgImage quality path img = L.writeFile path $ imageToJpg quality img - --- | Save an image to a '.tiff' file, will do everything it can to save an image. -saveTiffImage :: FilePath -> DynamicImage -> IO () -saveTiffImage path img = L.writeFile path $ imageToTiff img - --- | Save an image to a '.hdr' file, will do everything it can to save an image. -saveRadianceImage :: FilePath -> DynamicImage -> IO () -saveRadianceImage path = L.writeFile path . imageToRadiance - --- | Save an image to a '.png' file, will do everything it can to save an image. --- For example, a simple transcoder to png --- --- > transcodeToPng :: FilePath -> FilePath -> IO () --- > transcodeToPng pathIn pathOut = do --- > eitherImg <- readImage pathIn --- > case eitherImg of --- > Left _ -> return () --- > Right img -> savePngImage pathOut img --- -savePngImage :: String -> DynamicImage -> IO () -savePngImage path img = L.writeFile path $ imageToPng img - --- | Save an image to a '.bmp' file, will do everything it can to save an image. -saveBmpImage :: String -> DynamicImage -> IO () -saveBmpImage path img = L.writeFile path $ imageToBitmap img -
− Codec/Picture/BitWriter.hs
@@ -1,265 +0,0 @@-{-# LANGUAGE Rank2Types #-} --- | This module implement helper functions to read & write data --- at bits level. -module Codec.Picture.BitWriter( BoolReader - , emptyBoolState - , BoolState - , byteAlignJpg - , getNextBitsLSBFirst - , getNextBitsMSBFirst - , getNextBitJpg - , setDecodedString - , setDecodedStringJpg - , runBoolReader - - , BoolWriteStateRef - , newWriteStateRef - , finalizeBoolWriter - , writeBits' - - , initBoolState - , initBoolStateJpg - , execBoolReader - , runBoolReaderWith - ) where - -import Data.STRef -import Control.Applicative( (<$>), (<*>) ) -import Control.Monad( when ) -import Control.Monad.ST( ST ) -import qualified Control.Monad.Trans.State.Strict as S -import Data.Word( Word8, Word32 ) -import Data.Bits( Bits, (.&.), (.|.), unsafeShiftR, unsafeShiftL ) - -import Codec.Picture.VectorByteConversion( blitVector ) -import qualified Data.Vector.Storable.Mutable as M -import qualified Data.Vector.Storable as VS -import qualified Data.ByteString as B -import qualified Data.ByteString.Lazy as L - --------------------------------------------------- ----- Reader --------------------------------------------------- --- | Current bit index, current value, string -data BoolState = BoolState {-# UNPACK #-} !Int - {-# UNPACK #-} !Word8 - !B.ByteString - -emptyBoolState :: BoolState -emptyBoolState = BoolState (-1) 0 B.empty - --- | Type used to read bits -type BoolReader s a = S.StateT BoolState (ST s) a - -runBoolReader :: BoolReader s a -> ST s a -runBoolReader action = S.evalStateT action $ BoolState 0 0 B.empty - -runBoolReaderWith :: BoolState -> BoolReader s a -> ST s (a, BoolState) -runBoolReaderWith st action = S.runStateT action st - -execBoolReader :: BoolState -> BoolReader s a -> ST s BoolState -execBoolReader st reader = S.execStateT reader st - -initBoolState :: B.ByteString -> BoolState -initBoolState str = case B.uncons str of - Nothing -> BoolState 0 0 B.empty - Just (v, rest) -> BoolState 0 v rest - -initBoolStateJpg :: B.ByteString -> BoolState -initBoolStateJpg str = - case B.uncons str of - Nothing -> BoolState 0 0 B.empty - Just (0xFF, rest) -> case B.uncons rest of - Nothing -> BoolState maxBound 0 B.empty - Just (0x00, afterMarker) -> BoolState 7 0xFF afterMarker - Just (_ , afterMarker) -> initBoolStateJpg afterMarker - Just (v, rest) -> BoolState 7 v rest - --- | Bitify a list of things to decode. -setDecodedString :: B.ByteString -> BoolReader s () -setDecodedString str = case B.uncons str of - Nothing -> S.put $ BoolState 0 0 B.empty - Just (v, rest) -> S.put $ BoolState 0 v rest - --- | Drop all bit until the bit of indice 0, usefull to parse restart --- marker, as they are byte aligned, but Huffman might not. -byteAlignJpg :: BoolReader s () -byteAlignJpg = do - BoolState idx _ chain <- S.get - when (idx /= 7) (setDecodedStringJpg chain) - -{-# INLINE getNextBitJpg #-} -getNextBitJpg :: BoolReader s Bool -getNextBitJpg = do - BoolState idx v chain <- S.get - let val = (v .&. (1 `unsafeShiftL` idx)) /= 0 - if idx == 0 - then setDecodedStringJpg chain - else S.put $ BoolState (idx - 1) v chain - return val - -{-# INLINE getNextBitMSB #-} -getNextBitMSB :: BoolReader s Bool -getNextBitMSB = do - BoolState idx v chain <- S.get - let val = (v .&. (1 `unsafeShiftL` (7 - idx))) /= 0 - if idx == 7 - then setDecodedString chain - else S.put $ BoolState (idx + 1) v chain - return val - -{-# INLINE getNextBitsMSBFirst #-} -getNextBitsMSBFirst :: Int -> BoolReader s Word32 -getNextBitsMSBFirst count = aux 0 count - where aux acc 0 = return acc - aux acc n = do - bit <- getNextBitMSB - let nextVal | bit = (acc `unsafeShiftL` 1) .|. 1 - | otherwise = acc `unsafeShiftL` 1 - aux nextVal (n - 1) - -{-# INLINE getNextBitsLSBFirst #-} -getNextBitsLSBFirst :: Int -> BoolReader s Word32 -getNextBitsLSBFirst count = aux 0 count - where aux acc 0 = return acc - aux acc n = do - bit <- getNextBit - let nextVal | bit = acc .|. (1 `unsafeShiftL` (count - n)) - | otherwise = acc - aux nextVal (n - 1) - -{-# INLINE getNextBit #-} -getNextBit :: BoolReader s Bool -getNextBit = do - BoolState idx v chain <- S.get - let val = (v .&. (1 `unsafeShiftL` idx)) /= 0 - if idx == 7 - then setDecodedString chain - else S.put $ BoolState (idx + 1) v chain - return val - --- | Bitify a list of things to decode. Handle Jpeg escape --- code (0xFF 0x00), thus should be only used in JPEG decoding. -setDecodedStringJpg :: B.ByteString -> BoolReader s () -setDecodedStringJpg str = case B.uncons str of - Nothing -> S.put $ BoolState maxBound 0 B.empty - Just (0xFF, rest) -> case B.uncons rest of - Nothing -> S.put $ BoolState maxBound 0 B.empty - Just (0x00, afterMarker) -> -- trace "00" $ - S.put $ BoolState 7 0xFF afterMarker - Just (_ , afterMarker) -> setDecodedStringJpg afterMarker - Just (v, rest) -> - S.put $ BoolState 7 v rest - --------------------------------------------------- ----- Writer --------------------------------------------------- -defaultBufferSize :: Int -defaultBufferSize = 256 * 1024 - -data BoolWriteStateRef s = BoolWriteStateRef - { bwsCurrBuffer :: STRef s (M.MVector s Word8) - , bwsBufferList :: STRef s [B.ByteString] - , bwsWrittenWords :: STRef s Int - , bwsBitAcc :: STRef s Word8 - , bwsBitReaded :: STRef s Int - } - -newWriteStateRef :: ST s (BoolWriteStateRef s) -newWriteStateRef = do - origMv <- M.new defaultBufferSize - BoolWriteStateRef <$> newSTRef origMv - <*> newSTRef [] - <*> newSTRef 0 - <*> newSTRef 0 - <*> newSTRef 0 - -finalizeBoolWriter :: BoolWriteStateRef s -> ST s L.ByteString -finalizeBoolWriter st = do - forceBufferFlushing' st - L.fromChunks <$> readSTRef (bwsBufferList st) - -forceBufferFlushing' :: BoolWriteStateRef s -> ST s () -forceBufferFlushing' (BoolWriteStateRef { bwsCurrBuffer = vecRef - , bwsWrittenWords = countRef - , bwsBufferList = lstRef - }) = do - vec <- readSTRef vecRef - count <- readSTRef countRef - lst <- readSTRef lstRef - - nmv <- M.new defaultBufferSize - str <- byteStringFromVector vec count - - writeSTRef vecRef nmv - writeSTRef lstRef $ lst ++ [str] - writeSTRef countRef 0 - -flushCurrentBuffer' :: BoolWriteStateRef s -> ST s () -flushCurrentBuffer' st = do - count <- readSTRef $ bwsWrittenWords st - when (count >= defaultBufferSize) - (forceBufferFlushing' st) - -byteStringFromVector :: M.MVector s Word8 -> Int -> ST s B.ByteString -byteStringFromVector vec size = do - frozen <- VS.unsafeFreeze vec - return $ blitVector frozen 0 size - -setBitCount' :: BoolWriteStateRef s -> Word8 -> Int -> ST s () -{-# INLINE setBitCount' #-} -setBitCount' st acc count = do - writeSTRef (bwsBitAcc st) acc - writeSTRef (bwsBitReaded st) count - -resetBitCount' :: BoolWriteStateRef s -> ST s () -{-# INLINE resetBitCount' #-} -resetBitCount' st = setBitCount' st 0 0 - -pushByte' :: BoolWriteStateRef s -> Word8 -> ST s () -{-# INLINE pushByte' #-} -pushByte' st v = do - flushCurrentBuffer' st - idx <- readSTRef (bwsWrittenWords st) - vec <- readSTRef (bwsCurrBuffer st) - M.write vec idx v - writeSTRef (bwsWrittenWords st) $ idx + 1 - --- | Append some data bits to a Put monad. -writeBits' :: BoolWriteStateRef s - -> Word32 -- ^ The real data to be stored. Actual data should be in the LSB - -> Int -- ^ Number of bit to write from 1 to 32 - -> ST s () -{-# INLINE writeBits' #-} -writeBits' st d c = do - currWord <- readSTRef $ bwsBitAcc st - currCount <- readSTRef $ bwsBitReaded st - serialize d c currWord currCount - where dumpByte 0xFF = pushByte' st 0xFF >> pushByte' st 0x00 - dumpByte i = pushByte' st i - - serialize bitData bitCount currentWord count - | bitCount + count == 8 = do - resetBitCount' st - dumpByte (fromIntegral $ (currentWord `unsafeShiftL` bitCount) .|. - fromIntegral cleanData) - - | bitCount + count < 8 = - let newVal = currentWord `unsafeShiftL` bitCount - in setBitCount' st (newVal .|. fromIntegral cleanData) $ count + bitCount - - | otherwise = - let leftBitCount = 8 - count :: Int - highPart = cleanData `unsafeShiftR` (bitCount - leftBitCount) :: Word32 - prevPart = fromIntegral currentWord `unsafeShiftL` leftBitCount :: Word32 - - nextMask = (1 `unsafeShiftL` (bitCount - leftBitCount)) - 1 :: Word32 - newData = cleanData .&. nextMask :: Word32 - newCount = bitCount - leftBitCount :: Int - - toWrite = fromIntegral $ prevPart .|. highPart :: Word8 - in dumpByte toWrite >> serialize newData newCount 0 0 - - where cleanMask = (1 `unsafeShiftL` bitCount) - 1 :: Word32 - cleanData = bitData .&. cleanMask :: Word32 -
− Codec/Picture/Bitmap.hs
@@ -1,350 +0,0 @@-{-# 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 Control.Monad.ST ( ST, runST ) -import qualified Data.Vector.Storable as V -import qualified Data.Vector.Storable.Mutable as M -import Data.Binary( Binary( .. ) ) -import Data.Binary.Put( Put - , runPut - , putWord16le - , putWord32le - , putByteString - ) - -import Data.Binary.Get( Get - , getWord16le - , getWord32le - , bytesRead - , skip - ) - -import Data.Word( Word32, Word16, Word8 ) -import qualified Data.ByteString as B -import qualified Data.ByteString.Lazy as L - -import Codec.Picture.InternalHelper -import Codec.Picture.Types -import Codec.Picture.VectorByteConversion - -data BmpHeader = BmpHeader - { magicIdentifier :: !Word16 - , fileSize :: !Word32 -- ^ in bytes - , reserved1 :: !Word16 - , reserved2 :: !Word16 - , dataOffset :: !Word32 - } - -bitmapMagicIdentifier :: Word16 -bitmapMagicIdentifier = 0x4D42 - -instance Binary 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 Binary 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 [] - -stridePut :: M.STVector s Word8 -> Int -> Int -> ST s () -{-# INLINE stridePut #-} -stridePut vec = inner - where inner _ 0 = return () - inner ix n = do - (vec `M.unsafeWrite` ix) 0 - inner (ix + 1) (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}) = - forM_ [h - 1, h - 2 .. 0] $ \l -> putVector $ runST $ encodeLine l - where stride = fromIntegral $ linePadding 8 w - putVector vec = putByteString $ blitVector vec 0 lineWidth - lineWidth = w + stride - - encodeLine line = do - buff <- M.new lineWidth - - let lineIdx = line * w - inner col | col >= w = return () - inner col = do - let v = (arr `V.unsafeIndex` (lineIdx + col)) - (buff `M.unsafeWrite` col) v - inner (col + 1) - - inner 0 - - stridePut buff w stride - V.unsafeFreeze buff - -instance BmpEncodable PixelRGBA8 where - bitsPerPixel _ = 32 - bmpEncode (Image {imageWidth = w, imageHeight = h, imageData = arr}) = - forM_ [h - 1, h - 2 .. 0] $ \l -> putVector $ runST $ putLine l - where putVector vec = putByteString . blitVector vec 0 $ w * 4 - putLine line = do - buff <- M.new $ 4 * w - let initialIndex = line * w * 4 - inner col _ _ | col >= w = return () - inner col writeIdx readIdx = do - let r = arr `V.unsafeIndex` readIdx - g = arr `V.unsafeIndex` (readIdx + 1) - b = arr `V.unsafeIndex` (readIdx + 2) - a = arr `V.unsafeIndex` (readIdx + 3) - - (buff `M.unsafeWrite` writeIdx) b - (buff `M.unsafeWrite` (writeIdx + 1)) g - (buff `M.unsafeWrite` (writeIdx + 2)) r - (buff `M.unsafeWrite` (writeIdx + 3)) a - - inner (col + 1) (writeIdx + 4) (readIdx + 4) - - inner 0 0 initialIndex - V.unsafeFreeze buff - -instance BmpEncodable PixelRGB8 where - bitsPerPixel _ = 24 - bmpEncode (Image {imageWidth = w, imageHeight = h, imageData = arr}) = - forM_ [h - 1, h - 2 .. 0] $ \l -> putVector $ runST $ putLine l - where stride = fromIntegral . linePadding 24 $ w - putVector vec = putByteString $ blitVector vec 0 (w * 3 + stride) - putLine line = do - buff <- M.new $ w * 3 + stride - let initialIndex = line * w * 3 - inner col _ _ | col >= w = return () - inner col writeIdx readIdx = do - let r = (arr `V.unsafeIndex` readIdx) - g = (arr `V.unsafeIndex` (readIdx + 1)) - b = (arr `V.unsafeIndex` (readIdx + 2)) - - (buff `M.unsafeWrite` writeIdx) b - (buff `M.unsafeWrite` (writeIdx + 1)) g - (buff `M.unsafeWrite` (writeIdx + 2)) r - - inner (col + 1) (writeIdx + 3) (readIdx + 3) - - inner 0 0 initialIndex - V.unsafeFreeze buff - -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 = runST $ do - arr <- M.new (fromIntegral $ w * h * 3) - forM_ [hi - 1, hi - 2 .. 0] (readLine arr) - V.unsafeFreeze 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 `M.unsafeWrite` writeIdx ) (str `B.index` (readIdx + 2)) - (arr `M.unsafeWrite` (writeIdx + 1)) (str `B.index` (readIdx + 1)) - (arr `M.unsafeWrite` (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 runGetStrict 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 - readed <- bytesRead - - when (readed > fromIntegral (dataOffset hdr)) - (fail "Invalid bmp image, data in header") - - skip . fromIntegral $ dataOffset hdr - fromIntegral readed - rest <- getRemainingBytes - return . ImageRGB8 $ decodeImageRGB8 bmpHeader rest - a -> fail $ "Can't handle BMP file " ++ show a - - --- | Write an image in a file use the bitmap format. -writeBitmap :: (BmpEncodable pixel) - => FilePath -> Image pixel -> IO () -writeBitmap filename img = L.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 -> L.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 -> L.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 L.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 -> L.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/ConvGraph.hs
@@ -1,9 +0,0 @@--- $graph --- --- The following graph describe the differents way to convert between pixel types, --- --- * Nodes describe pixel type --- --- * Arrows describe functions --- --- <<data:image/png;base64,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>>
− Codec/Picture/Gif.hs
@@ -1,380 +0,0 @@--- | Module implementing GIF decoding. -module Codec.Picture.Gif ( decodeGif - , decodeGifImages - ) where - -import Control.Applicative( pure, (<$>), (<*>) ) -import Control.Monad( replicateM ) -import Control.Monad.ST( runST ) -import Control.Monad.Trans.Class( lift ) - -import Data.Bits( (.&.), unsafeShiftR, testBit ) -import Data.Word( Word8, Word16 ) - -import qualified Data.ByteString as B -import qualified Data.Vector.Storable as V -import qualified Data.Vector.Storable.Mutable as M - -import Data.Binary( Binary(..) ) -import Data.Binary.Get( Get - , getWord8 - , getWord16le - , getByteString - ) - -import Codec.Picture.InternalHelper -import Codec.Picture.Types -import Codec.Picture.Gif.LZW -import Codec.Picture.BitWriter - -{- - <GIF Data Stream> ::= Header <Logical Screen> <Data>* Trailer - - <Logical Screen> ::= Logical Screen Descriptor [Global Color Table] - - <Data> ::= <Graphic Block> | - <Special-Purpose Block> - - <Graphic Block> ::= [Graphic Control Extension] <Graphic-Rendering Block> - - <Graphic-Rendering Block> ::= <Table-Based Image> | - Plain Text Extension - - <Table-Based Image> ::= Image Descriptor [Local Color Table] Image Data - - <Special-Purpose Block> ::= Application Extension | - Comment Extension - -} - --------------------------------------------------- ----- GifVersion --------------------------------------------------- -data GifVersion = GIF87a | GIF89a - -gif87aSignature, gif89aSignature :: B.ByteString -gif87aSignature = B.pack $ map (fromIntegral . fromEnum) "GIF87a" -gif89aSignature = B.pack $ map (fromIntegral . fromEnum) "GIF89a" - -instance Binary GifVersion where - put GIF87a = put gif87aSignature - put GIF89a = put gif89aSignature - - get = do - sig <- getByteString (B.length gif87aSignature) - case (sig == gif87aSignature, sig == gif89aSignature) of - (True, _) -> pure GIF87a - (_ , True) -> pure GIF89a - _ -> fail $ "Invalid Gif signature : " ++ (toEnum . fromEnum <$> B.unpack sig) - - --------------------------------------------------- ----- LogicalScreenDescriptor --------------------------------------------------- --- | Section 18 of spec-gif89a -data LogicalScreenDescriptor = LogicalScreenDescriptor - { -- | Stored on 16 bits - screenWidth :: !Word16 - -- | Stored on 16 bits - , screenHeight :: !Word16 - -- | Stored on 8 bits - , backgroundIndex :: !Word8 - - -- | Stored on 1 bit - , hasGlobalMap :: !Bool - -- | Stored on 3 bits - , colorResolution :: !Word8 - -- | Stored on 1 bit - , isColorTableSorted :: !Bool - -- | Stored on 3 bits - , colorTableSize :: !Word8 - } - -instance Binary LogicalScreenDescriptor where - put _ = undefined - get = do - w <- getWord16le - h <- getWord16le - packedField <- getWord8 - backgroundColorIndex <- getWord8 - _aspectRatio <- getWord8 - return LogicalScreenDescriptor - { screenWidth = w - , screenHeight = h - , hasGlobalMap = packedField `testBit` 7 - , colorResolution = (packedField `unsafeShiftR` 5) .&. 0x7 + 1 - , isColorTableSorted = packedField `testBit` 3 - , colorTableSize = (packedField .&. 0x7) + 1 - , backgroundIndex = backgroundColorIndex - } - - --------------------------------------------------- ----- ImageDescriptor --------------------------------------------------- --- | Section 20 of spec-gif89a -data ImageDescriptor = ImageDescriptor - { gDescPixelsFromLeft :: !Word16 - , gDescPixelsFromTop :: !Word16 - , gDescImageWidth :: !Word16 - , gDescImageHeight :: !Word16 - , gDescHasLocalMap :: !Bool - , gDescIsInterlaced :: !Bool - , gDescIsImgDescriptorSorted :: !Bool - , gDescLocalColorTableSize :: !Word8 - } - -imageSeparator, extensionIntroducer, gifTrailer :: Word8 -imageSeparator = 0x2C -extensionIntroducer = 0x21 -gifTrailer = 0x3B - -graphicControlLabel :: Word8 -graphicControlLabel = 0xF9 - ---commentLabel, graphicControlLabel, applicationLabel --- -{-commentLabel = 0xFE-} -{-plainTextLabel = 0x01-} -{-applicationLabel = 0xFF-} - -parseDataBlocks :: Get B.ByteString -parseDataBlocks = B.concat <$> (getWord8 >>= aux) - where aux 0 = pure [] - aux size = (:) <$> getByteString (fromIntegral size) <*> (getWord8 >>= aux) - -data GraphicControlExtension = GraphicControlExtension - { gceDisposalMethod :: !Word8 -- ^ Stored on 3 bits - , gceUserInputFlag :: !Bool - , gceTransparentFlag :: !Bool - , gceDelay :: !Word16 - , gceTransparentColorIndex :: !Word8 - } - -instance Binary GraphicControlExtension where - put _ = undefined - get = do - -- due to missing lookahead - {-_extensionLabel <- getWord8-} - _size <- getWord8 - packedFields <- getWord8 - delay <- getWord16le - idx <- getWord8 - _blockTerminator <- getWord8 - return GraphicControlExtension - { gceDisposalMethod = (packedFields `unsafeShiftR` 2) .&. 0x07 - , gceUserInputFlag = packedFields `testBit` 1 - , gceTransparentFlag = packedFields `testBit` 0 - , gceDelay = delay - , gceTransparentColorIndex = idx - } - -data GifImage = GifImage - { imgDescriptor :: !ImageDescriptor - , imgLocalPalette :: !(Maybe Palette) - , imgLzwRootSize :: !Word8 - , imgData :: B.ByteString - } - -instance Binary GifImage where - put _ = undefined - get = do - desc <- get - let hasLocalColorTable = gDescHasLocalMap desc - palette <- if hasLocalColorTable - then Just <$> getPalette (gDescLocalColorTableSize desc) - else pure Nothing - - GifImage desc palette <$> getWord8 <*> parseDataBlocks - -data Block = BlockImage GifImage - | BlockGraphicControl GraphicControlExtension - -parseGifBlocks :: Get [Block] -parseGifBlocks = getWord8 >>= blockParse - where blockParse v - | v == gifTrailer = pure [] - | v == imageSeparator = (:) <$> (BlockImage <$> get) <*> parseGifBlocks - | v == extensionIntroducer = do - extensionCode <- getWord8 - if extensionCode /= graphicControlLabel - then parseDataBlocks >> parseGifBlocks - else (:) <$> (BlockGraphicControl <$> get) <*> parseGifBlocks - - blockParse v = do - fail ("Unrecognized gif block " ++ show v) - -instance Binary ImageDescriptor where - put _ = undefined - get = do - -- due to missing lookahead - {-_imageSeparator <- getWord8-} - imgLeftPos <- getWord16le - imgTopPos <- getWord16le - imgWidth <- getWord16le - imgHeight <- getWord16le - packedFields <- getWord8 - let tableSize = packedFields .&. 0x7 - return ImageDescriptor - { gDescPixelsFromLeft = imgLeftPos - , gDescPixelsFromTop = imgTopPos - , gDescImageWidth = imgWidth - , gDescImageHeight = imgHeight - , gDescHasLocalMap = packedFields `testBit` 7 - , gDescIsInterlaced = packedFields `testBit` 6 - , gDescIsImgDescriptorSorted = packedFields `testBit` 5 - , gDescLocalColorTableSize = if tableSize > 0 then tableSize + 1 else 0 - } - - --------------------------------------------------- ----- Palette --------------------------------------------------- -type Palette = Image PixelRGB8 - -getPalette :: Word8 -> Get Palette -getPalette bitDepth = replicateM (size * 3) get >>= return . Image size 1 . V.fromList - where size = 2 ^ (fromIntegral bitDepth :: Int) - --------------------------------------------------- ----- GifImage --------------------------------------------------- -data GifHeader = GifHeader - { gifVersion :: GifVersion - , gifScreenDescriptor :: LogicalScreenDescriptor - , gifGlobalMap :: !Palette - } - -instance Binary GifHeader where - put _ = undefined - get = do - version <- get - screenDesc <- get - palette <- getPalette $ colorTableSize screenDesc - return GifHeader - { gifVersion = version - , gifScreenDescriptor = screenDesc - , gifGlobalMap = palette - } - -data GifFile = GifFile - { gifHeader :: !GifHeader - , gifImages :: [(Maybe GraphicControlExtension, GifImage)] - } - -associateDescr :: [Block] -> [(Maybe GraphicControlExtension, GifImage)] -associateDescr [] = [] -associateDescr [BlockGraphicControl _] = [] -associateDescr (BlockGraphicControl _ : rest@(BlockGraphicControl _ : _)) = associateDescr rest -associateDescr (BlockImage img:xs) = (Nothing, img) : associateDescr xs -associateDescr (BlockGraphicControl ctrl : BlockImage img : xs) = - (Just ctrl, img) : associateDescr xs - -instance Binary GifFile where - put _ = undefined - get = do - hdr <- get - blocks <- parseGifBlocks - return GifFile { gifHeader = hdr - , gifImages = associateDescr blocks } - -substituteColors :: Palette -> Image Pixel8 -> Image PixelRGB8 -substituteColors palette = pixelMap swaper - where swaper n = pixelAt palette (fromIntegral n) 0 - -decodeImage :: GifImage -> Image Pixel8 -decodeImage img = runST $ runBoolReader $ do - outputVector <- lift . M.new $ width * height - decodeLzw (imgData img) 12 lzwRoot outputVector - frozenData <- lift $ V.unsafeFreeze outputVector - return . deinterlaceGif $ Image - { imageWidth = width - , imageHeight = height - , imageData = frozenData - } - where lzwRoot = fromIntegral $ imgLzwRootSize img - width = fromIntegral $ gDescImageWidth descriptor - height = fromIntegral $ gDescImageHeight descriptor - isInterlaced = gDescIsInterlaced descriptor - descriptor = imgDescriptor img - - deinterlaceGif | not isInterlaced = id - | otherwise = deinterlaceGifImage - -deinterlaceGifImage :: Image Pixel8 -> Image Pixel8 -deinterlaceGifImage img@(Image { imageWidth = w, imageHeight = h }) = generateImage generator w h - where lineIndices = gifInterlacingIndices h - generator x y = pixelAt img x y' - where y' = lineIndices V.! y - -gifInterlacingIndices :: Int -> V.Vector Int -gifInterlacingIndices height = V.accum (\_ v -> v) (V.replicate height 0) indices - where indices = flip zip [0..] $ - concat [ [0, 8 .. height - 1] - , [4, 4 + 8 .. height - 1] - , [2, 2 + 4 .. height - 1] - , [1, 1 + 2 .. height - 1] - ] - -paletteOf :: Palette -> GifImage -> Palette -paletteOf global GifImage { imgLocalPalette = Nothing } = global -paletteOf _ GifImage { imgLocalPalette = Just p } = p - -decodeAllGifImages :: GifFile -> [Image PixelRGB8] -decodeAllGifImages GifFile { gifImages = [] } = [] -decodeAllGifImages GifFile { gifHeader = GifHeader { gifGlobalMap = palette - , gifScreenDescriptor = wholeDescriptor - } - , gifImages = (_, firstImage) : rest } = map paletteApplyer $ - scanl generator (paletteOf palette firstImage, decodeImage firstImage) rest - where globalWidth = fromIntegral $ screenWidth wholeDescriptor - globalHeight = fromIntegral $ screenHeight wholeDescriptor - - {-background = backgroundIndex wholeDescriptor-} - - paletteApplyer (pal, img) = substituteColors pal img - - generator (_, img1) (controlExt, img2@(GifImage { imgDescriptor = descriptor })) = - (paletteOf palette img2, generateImage pixeler globalWidth globalHeight) - where localWidth = fromIntegral $ gDescImageWidth descriptor - localHeight = fromIntegral $ gDescImageHeight descriptor - - left = fromIntegral $ gDescPixelsFromLeft descriptor - top = fromIntegral $ gDescPixelsFromTop descriptor - - isPixelInLocalImage x y = - x >= left && x < left + localWidth && y >= top && y < top + localHeight - - decoded = decodeImage img2 - - transparent :: Int - transparent = case controlExt of - Nothing -> 300 - Just ext -> if gceTransparentFlag ext - then fromIntegral $ gceTransparentColorIndex ext - else 300 - - pixeler x y - | isPixelInLocalImage x y && fromIntegral val /= transparent = val - where val = pixelAt decoded (x - left) (y - top) - pixeler x y = pixelAt img1 x y - -decodeFirstGifImage :: GifFile -> Either String (Image PixelRGB8) -decodeFirstGifImage - GifFile { gifHeader = GifHeader { gifGlobalMap = palette} - , gifImages = ((_, gif):_) } = Right . substituteColors palette $ decodeImage gif -decodeFirstGifImage _ = Left "No image in gif file" - --- | Transform a raw gif image to an image, witout --- modifying the pixels. --- This function can output the following pixel types : --- --- * PixelRGB8 --- -decodeGif :: B.ByteString -> Either String DynamicImage -decodeGif img = ImageRGB8 <$> (decode img >>= decodeFirstGifImage) - --- | Transform a raw gif to a list of images, representing --- all the images of an animation. -decodeGifImages :: B.ByteString -> Either String [Image PixelRGB8] -decodeGifImages img = decodeAllGifImages <$> decode img -
− Codec/Picture/Gif/LZW.hs
@@ -1,189 +0,0 @@-module Codec.Picture.Gif.LZW( decodeLzw, decodeLzwTiff ) where - -import Data.Word( Word8 ) -import Control.Applicative( (<$>) ) -import Control.Monad( when ) - -import Data.Bits( (.&.) ) - -import Control.Monad.ST( ST ) -import Control.Monad.Trans.Class( MonadTrans, lift ) - -import Foreign.Storable ( Storable ) - -import qualified Data.ByteString as B -import qualified Data.Vector.Storable.Mutable as M - -import Codec.Picture.BitWriter - -{-# INLINE (.!!!.) #-} -(.!!!.) :: (Storable a) => M.STVector s a -> Int -> ST s a -(.!!!.) = M.unsafeRead - {-M.read-} - -{-# INLINE (..!!!..) #-} -(..!!!..) :: (MonadTrans t, Storable a) - => M.STVector s a -> Int -> t (ST s) a -(..!!!..) v idx = lift $ v .!!!. idx - -{-# INLINE (.<-.) #-} -(.<-.) :: (Storable a) => M.STVector s a -> Int -> a -> ST s () -(.<-.) = M.unsafeWrite - {-M.write-} - -{-# INLINE (..<-..) #-} -(..<-..) :: (MonadTrans t, Storable a) - => M.STVector s a -> Int -> a -> t (ST s) () -(..<-..) v idx = lift . (v .<-. idx) - - -duplicateData :: (Show a, MonadTrans t, Storable a) - => M.STVector s a -> M.STVector s a - -> Int -> Int -> Int -> t (ST s) () -duplicateData src dest sourceIndex size destIndex = lift $ aux sourceIndex destIndex - where endIndex = sourceIndex + size - aux i _ | i == endIndex = return () - aux i j = do - src .!!!. i >>= (dest .<-. j) - aux (i + 1) (j + 1) - -rangeSetter :: (Storable a, Num a) - => Int -> M.STVector s a - -> ST s (M.STVector s a) -rangeSetter count vec = aux 0 - where aux n | n == count = return vec - aux n = (vec .<-. n) (fromIntegral n) >> aux (n + 1) - -decodeLzw :: B.ByteString -> Int -> Int -> M.STVector s Word8 - -> BoolReader s () -decodeLzw str maxBitKey initialKey outVec = do - setDecodedString str - lzw GifVariant maxBitKey initialKey 0 outVec - -isOldTiffLZW :: B.ByteString -> Bool -isOldTiffLZW str = firstByte == 0 && secondByte == 1 - where firstByte = str `B.index` 0 - secondByte = (str `B.index` 1) .&. 1 - -decodeLzwTiff :: B.ByteString -> M.STVector s Word8 -> Int - -> BoolReader s() -decodeLzwTiff str outVec initialWriteIdx = do - setDecodedString str - let variant | isOldTiffLZW str = OldTiffVariant - | otherwise = TiffVariant - lzw variant 12 9 initialWriteIdx outVec - -data TiffVariant = - GifVariant - | TiffVariant - | OldTiffVariant - deriving Eq - --- | Gif image constraint from spec-gif89a, code size max : 12 bits. -lzw :: TiffVariant -> Int -> Int -> Int -> M.STVector s Word8 - -> BoolReader s () -lzw variant nMaxBitKeySize initialKeySize initialWriteIdx outVec = do - -- Allocate buffer of maximum size. - lzwData <- lift (M.replicate maxDataSize 0) >>= resetArray - lzwOffsetTable <- lift (M.replicate tableEntryCount 0) >>= resetArray - lzwSizeTable <- lift $ M.replicate tableEntryCount 0 - lift $ lzwSizeTable `M.set` 1 - - let firstVal code = do - dataOffset <- lzwOffsetTable ..!!!.. code - lzwData ..!!!.. dataOffset - - writeString at code = do - dataOffset <- lzwOffsetTable ..!!!.. code - dataSize <- lzwSizeTable ..!!!.. code - - when (at + dataSize <= maxWrite) $ - duplicateData lzwData outVec dataOffset dataSize at - - return dataSize - - addString pos at code val = do - dataOffset <- lzwOffsetTable ..!!!.. code - dataSize <- lzwSizeTable ..!!!.. code - - when (pos < tableEntryCount) $ do - (lzwOffsetTable ..<-.. pos) at - (lzwSizeTable ..<-.. pos) $ dataSize + 1 - - when (at + dataSize + 1 <= maxDataSize) $ do - duplicateData lzwData lzwData dataOffset dataSize at - (lzwData ..<-.. (at + dataSize)) val - - return $ dataSize + 1 - - maxWrite = M.length outVec - loop outWriteIdx writeIdx dicWriteIdx codeSize oldCode code - | outWriteIdx >= maxWrite = return () - | code == endOfInfo = return () - | code == clearCode = do - toOutput <- getNextCode startCodeSize - if toOutput == endOfInfo then - return () - else do - dataSize <- writeString outWriteIdx toOutput - getNextCode startCodeSize >>= - loop (outWriteIdx + dataSize) - firstFreeIndex firstFreeIndex startCodeSize toOutput - - | otherwise = do - (written, dicAdd) <- - if code >= writeIdx then do - c <- firstVal oldCode - wroteSize <- writeString outWriteIdx oldCode - (outVec ..<-.. (outWriteIdx + wroteSize)) c - addedSize <- addString writeIdx dicWriteIdx oldCode c - return (wroteSize + 1, addedSize) - else do - wroteSize <- writeString outWriteIdx code - c <- firstVal code - addedSize <- addString writeIdx dicWriteIdx oldCode c - return (wroteSize, addedSize) - - let new_code_size = updateCodeSize codeSize $ writeIdx + 1 - getNextCode new_code_size >>= - loop (outWriteIdx + written) - (writeIdx + 1) - (dicWriteIdx + dicAdd) - new_code_size - code - - getNextCode startCodeSize >>= - loop initialWriteIdx firstFreeIndex firstFreeIndex startCodeSize 0 - - where tableEntryCount = 2 ^ min 12 nMaxBitKeySize - maxDataSize = tableEntryCount `div` 2 * (1 + tableEntryCount) + 1 - - isNewTiff = variant == TiffVariant - (switchOffset, isTiffVariant) = case variant of - GifVariant -> (0, False) - TiffVariant -> (1, True) - OldTiffVariant -> (0, True) - - initialElementCount = 2 ^ initialKeySize :: Int - clearCode | isTiffVariant = 256 - | otherwise = initialElementCount - - endOfInfo | isTiffVariant = 257 - | otherwise = clearCode + 1 - - startCodeSize - | isTiffVariant = initialKeySize - | otherwise = initialKeySize + 1 - - firstFreeIndex = endOfInfo + 1 - - resetArray a = lift $ rangeSetter initialElementCount a - - updateCodeSize codeSize writeIdx - | writeIdx == 2 ^ codeSize - switchOffset = min 12 $ codeSize + 1 - | otherwise = codeSize - - getNextCode s - | isNewTiff = fromIntegral <$> getNextBitsMSBFirst s - | otherwise = fromIntegral <$> getNextBitsLSBFirst s -
− Codec/Picture/HDR.hs
@@ -1,462 +0,0 @@--- | Module dedicated of Radiance file decompression (.hdr or .pic) file. --- Radiance file format is used for High dynamic range imaging. -module Codec.Picture.HDR( decodeHDR, encodeHDR, writeHDR ) where - -import Data.Bits( Bits, (.&.), (.|.), unsafeShiftL, unsafeShiftR ) -import Data.Char( ord, chr, isDigit ) -import Data.Word( Word8 ) -import Data.Monoid( (<>) ) -import Control.Applicative( pure, (<$>), (<*>) ) -import Control.Monad( when, foldM, foldM_, forM, forM_ ) -import Control.Monad.Trans.Class( lift ) -import Control.Monad.Trans.Error( ErrorT, throwError, runErrorT ) -import qualified Data.ByteString as B -import qualified Data.ByteString.Lazy as L -import qualified Data.ByteString.Char8 as BC - -import Data.List( partition ) -import Data.Binary( Binary( .. ), encode ) -import Data.Binary.Get( Get, getByteString, getWord8 ) -import Data.Binary.Put( putByteString, putLazyByteString ) - -import Control.Monad.ST( ST, runST ) -import Foreign.Storable ( Storable ) -import Control.Monad.Primitive ( PrimState, PrimMonad ) -import qualified Data.Vector.Storable as V -import qualified Data.Vector.Storable.Mutable as M - -import Codec.Picture.InternalHelper -import Codec.Picture.Types -import Codec.Picture.VectorByteConversion - -{-# INLINE (.<<.) #-} -(.<<.), (.>>.) :: (Bits a) => a -> Int -> a -(.<<.) = unsafeShiftL -(.>>.) = unsafeShiftR - -{-# INLINE (.<-.) #-} -(.<-.) :: (PrimMonad m, Storable a) - => M.STVector (PrimState m) a -> Int -> a -> m () -(.<-.) = M.write - {-M.unsafeWrite-} - -type HDRReader s a = ErrorT String (ST s) a - -data RGBE = RGBE !Word8 !Word8 !Word8 !Word8 - -instance Binary RGBE where - put (RGBE r g b e) = put r >> put g >> put b >> put e - get = RGBE <$> get <*> get <*> get <*> get - -checkLineLength :: RGBE -> Int -checkLineLength (RGBE _ _ a b) = - (fromIntegral a .<<. 8) .|. fromIntegral b - -isNewRunLengthMarker :: RGBE -> Bool -isNewRunLengthMarker (RGBE 2 2 _ _) = True -isNewRunLengthMarker _ = False - -data RadianceFormat = - FormatRGBE - | FormatXYZE - -radiance32bitRleRGBEFormat, radiance32bitRleXYZEFromat :: B.ByteString -radiance32bitRleRGBEFormat = BC.pack "32-bit_rle_rgbe" -radiance32bitRleXYZEFromat = BC.pack "32-bit_rle_xyze" - -instance Binary RadianceFormat where - put FormatRGBE = putByteString radiance32bitRleRGBEFormat - put FormatXYZE = putByteString radiance32bitRleXYZEFromat - - get = getByteString (B.length radiance32bitRleRGBEFormat) >>= format - where format sig - | sig == radiance32bitRleRGBEFormat = pure FormatRGBE - | sig == radiance32bitRleXYZEFromat = pure FormatXYZE - | otherwise = fail "Unrecognized Radiance format" - -toRGBE :: PixelRGBF -> RGBE -toRGBE (PixelRGBF r g b) - | d <= 1e-32 = RGBE 0 0 0 0 - | otherwise = RGBE (fix r) (fix g) (fix b) (fromIntegral $ e + 128) - where d = maximum [r, g, b] - e = exponent d - coeff = significand d * 255.9999 / d - fix v = truncate $ v * coeff - - -dropUntil :: Word8 -> Get () -dropUntil c = getWord8 >>= inner - where inner val | val == c = pure () - inner _ = getWord8 >>= inner - -getUntil :: (Word8 -> Bool) -> B.ByteString -> Get B.ByteString -getUntil f initialAcc = getWord8 >>= inner initialAcc - where inner acc c | f c = pure acc - inner acc c = getWord8 >>= inner (B.snoc acc c) - -data RadianceHeader = RadianceHeader - { radianceInfos :: [(B.ByteString, B.ByteString)] - , radianceFormat :: RadianceFormat - , radianceHeight :: !Int - , radianceWidth :: !Int - , radianceData :: L.ByteString - } - -radianceFileSignature :: B.ByteString -radianceFileSignature = BC.pack "#?RADIANCE\n" - -unpackColor :: L.ByteString -> Int -> RGBE -unpackColor str idx = RGBE (at 0) (at 1) (at 2) (at 3) - where at n = L.index str . fromIntegral $ idx + n - -storeColor :: M.STVector s Word8 -> Int -> RGBE -> ST s () -storeColor vec idx (RGBE r g b e) = do - (vec .<-. (idx + 0)) r - (vec .<-. (idx + 1)) g - (vec .<-. (idx + 2)) b - (vec .<-. (idx + 3)) e - -parsePair :: Char -> Get (B.ByteString, B.ByteString) -parsePair firstChar = do - let eol c = c == fromIntegral (ord '\n') - line <- getUntil eol B.empty - case BC.split '=' line of - [] -> pure (BC.singleton firstChar, B.empty) - [val] -> pure (BC.singleton firstChar, val) - [key, val] -> pure (BC.singleton firstChar <> key, val) - (key : vals) -> pure (BC.singleton firstChar <> key, B.concat vals) - -decodeInfos :: Get [(B.ByteString, B.ByteString)] -decodeInfos = do - char <- getChar8 - case char of - -- comment - '#' -> dropUntil (fromIntegral $ ord '\n') >> decodeInfos - -- end of header, no more information - '\n' -> pure [] - -- Classical parsing - c -> (:) <$> parsePair c <*> decodeInfos - - --- | Decode an HDR (radiance) image, the resulting pixel --- type can be : --- --- * PixelRGBF --- -decodeHDR :: B.ByteString -> Either String DynamicImage -decodeHDR str = runST $ runErrorT $ do - case runGet decodeHeader $ L.fromChunks [str] of - Left err -> throwError err - Right rez -> - ImageRGBF <$> (decodeRadiancePicture rez >>= lift . unsafeFreezeImage) - -getChar8 :: Get Char -getChar8 = chr . fromIntegral <$> getWord8 - -isSign :: Char -> Bool -isSign c = c == '+' || c == '-' - -isAxisLetter :: Char -> Bool -isAxisLetter c = c == 'X' || c == 'Y' - -decodeNum :: Get Int -decodeNum = do - sign <- getChar8 - letter <- getChar8 - space <- getChar8 - - when (not $ isSign sign && isAxisLetter letter && space == ' ') - (fail "Invalid radiance size declaration") - - let numDec acc c | isDigit c = - getChar8 >>= numDec (acc * 10 + ord c - (ord '0')) - numDec acc _ - | sign == '-' = pure $ negate acc - | otherwise = pure acc - - getChar8 >>= numDec 0 - -copyPrevColor :: M.STVector s Word8 -> Int -> ST s () -copyPrevColor scanLine idx = do - r <- scanLine `M.unsafeRead` (idx - 4) - g <- scanLine `M.unsafeRead` (idx - 3) - b <- scanLine `M.unsafeRead` (idx - 2) - e <- scanLine `M.unsafeRead` (idx - 1) - - (scanLine `M.unsafeWrite` (idx + 0)) r - (scanLine `M.unsafeWrite` (idx + 1)) g - (scanLine `M.unsafeWrite` (idx + 2)) b - (scanLine `M.unsafeWrite` (idx + 3)) e - -oldStyleRLE :: L.ByteString -> Int -> M.STVector s Word8 - -> HDRReader s Int -oldStyleRLE inputData initialIdx scanLine = inner initialIdx 0 0 - where maxOutput = M.length scanLine - maxInput = fromIntegral $ L.length inputData - - inner readIdx writeIdx _ - | readIdx >= maxInput || writeIdx >= maxOutput = pure readIdx - inner readIdx writeIdx shift = do - let color@(RGBE r g b e) = unpackColor inputData readIdx - isRun = r == 1 && g == 1 && b == 1 - - if not isRun - then do - lift $ storeColor scanLine writeIdx color - inner (readIdx + 4) (writeIdx + 4) 0 - - else do - let count = fromIntegral e .<<. shift - lift $ forM_ [0 .. count] $ \i -> copyPrevColor scanLine (writeIdx + 4 * i) - inner (readIdx + 4) (writeIdx + 4 * count) (shift + 8) - -newStyleRLE :: L.ByteString -> Int -> M.STVector s Word8 - -> HDRReader s Int -newStyleRLE inputData initialIdx scanline = foldM inner initialIdx [0 .. 3] - where dataAt idx - | fromIntegral idx >= maxInput = throwError $ "Read index out of bound (" ++ show idx ++ ")" - | otherwise = pure $ L.index inputData (fromIntegral idx) - - maxOutput = M.length scanline - maxInput = fromIntegral $ L.length inputData - stride = 4 - - - strideSet count destIndex _ | endIndex > maxOutput + stride = - throwError $ "Out of bound HDR scanline " ++ show endIndex ++ " (max " ++ show maxOutput ++ ")" - where endIndex = destIndex + count * stride - strideSet count destIndex val = aux destIndex count - where aux i 0 = pure i - aux i c = do - lift $ (scanline .<-. i) val - aux (i + stride) (c - 1) - - - strideCopy _ count destIndex - | writeEndBound > maxOutput + stride = throwError "Out of bound HDR scanline" - where writeEndBound = destIndex + count * stride - strideCopy sourceIndex count destIndex = aux sourceIndex destIndex count - where aux _ j 0 = pure j - aux i j c = do - val <- dataAt i - lift $ (scanline .<-. j) val - aux (i + 1) (j + stride) (c - 1) - - inner readIdx writeIdx - | readIdx >= maxInput || writeIdx >= maxOutput = pure readIdx - inner readIdx writeIdx = do - code <- dataAt readIdx - if code > 128 - then do - let repeatCount = fromIntegral code .&. 0x7F - newVal <- dataAt $ readIdx + 1 - endIndex <- strideSet repeatCount writeIdx newVal - inner (readIdx + 2) endIndex - - else do - let iCode = fromIntegral code - endIndex <- strideCopy (readIdx + 1) iCode writeIdx - inner (readIdx + iCode + 1) endIndex - -instance Binary RadianceHeader where - get = decodeHeader - put hdr = do - putByteString radianceFileSignature - putByteString $ BC.pack "FORMAT=" - put $ radianceFormat hdr - let sizeString = - BC.pack $ "\n\n-Y " ++ show (radianceHeight hdr) - ++ " +X " ++ show (radianceWidth hdr) ++ "\n" - putByteString sizeString - putLazyByteString $ radianceData hdr - - -decodeHeader :: Get RadianceHeader -decodeHeader = do - sig <- getByteString $ B.length radianceFileSignature - when (sig /= radianceFileSignature) - (fail "Invalid radiance file signature") - - infos <- decodeInfos - let formatKey = BC.pack "FORMAT" - case partition (\(k,_) -> k /= formatKey) infos of - (_, []) -> fail "No radiance format specified" - (info, [(_, formatString)]) -> - case runGet get $ L.fromChunks [formatString] of - Left err -> fail err - Right format -> do - (n1, n2, b) <- (,,) <$> decodeNum - <*> decodeNum - <*> getRemainingBytes - return . RadianceHeader info format n1 n2 $ L.fromChunks [b] - - _ -> fail "Multiple radiance format specified" - -toFloat :: RGBE -> PixelRGBF -toFloat (RGBE r g b e) = PixelRGBF rf gf bf - where f = encodeFloat 1 $ fromIntegral e - (128 + 8) - rf = (fromIntegral r + 0.0) * f - gf = (fromIntegral g + 0.0) * f - bf = (fromIntegral b + 0.0) * f - -encodeScanlineColor :: M.STVector s Word8 - -> M.STVector s Word8 - -> Int - -> ST s Int -encodeScanlineColor vec outVec outIdx = do - val <- vec `M.unsafeRead` 0 - runLength 1 0 val 1 outIdx - where maxIndex = M.length vec - - pushRun len val at = do - (outVec `M.unsafeWrite` at) $ fromIntegral $ len .|. 0x80 - (outVec `M.unsafeWrite` (at + 1)) val - return $ at + 2 - - pushData start len at = do - (outVec `M.unsafeWrite` at) $ fromIntegral len - let first = start - len - end = start - 1 - offset = at - first + 1 - forM_ [first .. end] $ \i -> do - v <- vec `M.unsafeRead` i - (outVec `M.unsafeWrite` (offset + i)) v - - return $ at + len + 1 - - -- End of scanline, empty the thing - runLength run cpy prev idx at | idx >= maxIndex = - case (run, cpy) of - (0, 0) -> pure at - (0, n) -> pushData idx n at - (n, 0) -> pushRun n prev at - (_, _) -> error "HDR - Run length algorithm is wrong" - - -- full runlength, we must write the packet - runLength r@127 _ prev idx at = do - val <- vec `M.unsafeRead` idx - pushRun r prev at >>= - runLength 1 0 val (idx + 1) - - -- full copy, we must write the packet - runLength _ c@127 _ idx at = do - val <- vec `M.unsafeRead` idx - pushData idx c at >>= - runLength 1 0 val (idx + 1) - - runLength n 0 prev idx at = do - val <- vec `M.unsafeRead` idx - case val == prev of - True -> runLength (n + 1) 0 prev (idx + 1) at - False | n < 4 -> runLength 0 (n + 1) val (idx + 1) at - False -> do - pushRun n prev at >>= - runLength 1 0 val (idx + 1) - - runLength 0 n prev idx at = do - val <- vec `M.unsafeRead` idx - if val /= prev - then runLength 0 (n + 1) val (idx + 1) at - else do - pushData (idx - 1) (n - 1) at >>= - runLength (2 :: Int) 0 val (idx + 1) - - runLength _ _ _ _ _ = - error "HDR RLE inconsistent state" - --- | Write an High dynamic range image into a radiance --- image file on disk. -writeHDR :: FilePath -> Image PixelRGBF -> IO () -writeHDR filename img = L.writeFile filename $ encodeHDR img - --- | Encode an High dynamic range image into a radiance image --- file format. -encodeHDR :: Image PixelRGBF -> L.ByteString -encodeHDR pic = encode $ runST $ do - let w = imageWidth pic - h = imageHeight pic - - scanLineR <- M.new w :: ST s (M.STVector s Word8) - scanLineG <- M.new w - scanLineB <- M.new w - scanLineE <- M.new w - - encoded <- - forM [0 .. h - 1] $ \line -> do - buff <- M.new $ w * 4 + w `div` 127 + 2 - let columner col | col >= w = return () - columner col = do - let RGBE r g b e = toRGBE $ pixelAt pic col line - (scanLineR `M.unsafeWrite` col) r - (scanLineG `M.unsafeWrite` col) g - (scanLineB `M.unsafeWrite` col) b - (scanLineE `M.unsafeWrite` col) e - - columner (col + 1) - - columner 0 - - (buff `M.unsafeWrite` 0) 2 - (buff `M.unsafeWrite` 1) 2 - (buff `M.unsafeWrite` 2) $ fromIntegral ((w .>>. 8) .&. 0xFF) - (buff `M.unsafeWrite` 3) $ fromIntegral (w .&. 0xFF) - - i1 <- encodeScanlineColor scanLineR buff 4 - i2 <- encodeScanlineColor scanLineG buff i1 - i3 <- encodeScanlineColor scanLineB buff i2 - endIndex <- encodeScanlineColor scanLineE buff i3 - - (\v -> blitVector v 0 endIndex) <$> V.unsafeFreeze buff - - pure $ RadianceHeader - { radianceInfos = [] - , radianceFormat = FormatRGBE - , radianceHeight = h - , radianceWidth = w - , radianceData = L.fromChunks encoded - } - - -decodeRadiancePicture :: RadianceHeader -> HDRReader s (MutableImage s (PixelRGBF)) -decodeRadiancePicture hdr = do - let width = abs $ radianceWidth hdr - height = abs $ radianceHeight hdr - packedData = radianceData hdr - - scanLine <- lift $ M.new $ width * 4 - resultBuffer <- lift $ M.new $ width * height * 3 - - let scanLineImage = MutableImage - { mutableImageWidth = width - , mutableImageHeight = 1 - , mutableImageData = scanLine - } - - finalImage = MutableImage - { mutableImageWidth = width - , mutableImageHeight = height - , mutableImageData = resultBuffer - } - - let scanLineExtractor readIdx line = do - let color = unpackColor packedData readIdx - inner | isNewRunLengthMarker color = do - let calcSize = checkLineLength color - when (calcSize /= width) - (throwError "Invalid sanline size") - pure $ \idx -> newStyleRLE packedData (idx + 4) - | otherwise = pure $ oldStyleRLE packedData - f <- inner - newRead <- f readIdx scanLine - forM_ [0 .. width - 1] $ \i -> do - -- mokay, it's a hack, but I don't want to define a - -- pixel instance of RGBE... - PixelRGBA8 r g b e <- lift $ readPixel scanLineImage i 0 - lift $ writePixel finalImage i line . toFloat $ RGBE r g b e - - return newRead - - foldM_ scanLineExtractor 0 [0 .. height - 1] - - return finalImage -
− Codec/Picture/InternalHelper.hs
@@ -1,33 +0,0 @@-module Codec.Picture.InternalHelper ( runGet - , runGetStrict - , decode - , getRemainingBytes - , getRemainingLazyBytes ) where - -import qualified Data.ByteString as B -import qualified Data.ByteString.Lazy as L -import Data.Binary( Binary( get ) ) -import Data.Binary.Get( Get, runGetOrFail, getRemainingLazyByteString ) - -decode :: (Binary a) => B.ByteString -> Either String a -decode = runGetStrict get - -runGet :: Get a -> L.ByteString -> Either String a -runGet act = unpack . runGetOrFail act - where unpack (Left (_, _, str)) = Left str - unpack (Right (_, _, element)) = Right element - -runGetStrict :: Get a -> B.ByteString -> Either String a -runGetStrict act buffer = runGet act $ L.fromChunks [buffer] - -getRemainingBytes :: Get B.ByteString -getRemainingBytes = do - rest <- getRemainingLazyByteString - return $ case L.toChunks rest of - [] -> B.empty - [a] -> a - lst -> B.concat lst - -getRemainingLazyBytes :: Get L.ByteString -getRemainingLazyBytes = getRemainingLazyByteString -
− Codec/Picture/Jpg.hs
@@ -1,720 +0,0 @@-{-# LANGUAGE TupleSections #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE BangPatterns #-} -{-# OPTIONS_GHC -fspec-constr-count=5 #-} --- | Module used for JPEG file loading and writing. -module Codec.Picture.Jpg( decodeJpeg - , encodeJpegAtQuality - , encodeJpeg - - , jpgMachineStep - , JpgDecoderState( JpgDecoderState ) - ) where - -import Control.Arrow( (>>>) ) -import Control.Applicative( pure, (<$>) ) -import Control.Monad( when, forM_ ) -import Control.Monad.ST( ST, runST ) -import Control.Monad.Trans( lift ) -import Control.Monad.Trans.RWS.Strict( RWS, modify, tell, gets, execRWS ) - -import Data.Bits( (.|.), unsafeShiftL ) -import Data.Int( Int16, Int32 ) -import Data.Word(Word8, Word32) -import Data.Binary( Binary(..), encode ) -import Data.STRef( newSTRef, writeSTRef, readSTRef ) - -import Data.Vector( (//) ) -import Data.Vector.Unboxed( (!) ) -import qualified Data.Vector as V -import qualified Data.Vector.Unboxed as VU -import qualified Data.Vector.Storable as VS -import qualified Data.Vector.Storable.Mutable as M -import qualified Data.ByteString as B -import qualified Data.ByteString.Lazy as L - -import Codec.Picture.InternalHelper -import Codec.Picture.BitWriter -import Codec.Picture.Types -import Codec.Picture.Jpg.Types -import Codec.Picture.Jpg.Common -import Codec.Picture.Jpg.Progressive -import Codec.Picture.Jpg.DefaultTable -import Codec.Picture.Jpg.FastDct - -quantize :: MacroBlock Int16 -> MutableMacroBlock s Int32 - -> ST s (MutableMacroBlock s Int32) -quantize table block = update 0 - where update 64 = return block - update idx = do - val <- block `M.unsafeRead` idx - let q = fromIntegral (table `VS.unsafeIndex` idx) - finalValue = (val + (q `div` 2)) `quot` q -- rounded integer division - (block `M.unsafeWrite` idx) finalValue - update $ idx + 1 - - -powerOf :: Int32 -> Word32 -powerOf 0 = 0 -powerOf n = limit 1 0 - where val = abs n - limit range i | val < range = i - limit range i = limit (2 * range) (i + 1) - -encodeInt :: BoolWriteStateRef s -> Word32 -> Int32 -> ST s () -{-# INLINE encodeInt #-} -encodeInt st ssss n | n > 0 = writeBits' st (fromIntegral n) (fromIntegral ssss) -encodeInt st ssss n = writeBits' st (fromIntegral $ n - 1) (fromIntegral ssss) - --- | Assume the macro block is initialized with zeroes -acCoefficientsDecode :: HuffmanPackedTree -> MutableMacroBlock s Int16 - -> BoolReader s (MutableMacroBlock s Int16) -acCoefficientsDecode acTree mutableBlock = parseAcCoefficient 1 >> return mutableBlock - where parseAcCoefficient n | n >= 64 = return () - | otherwise = do - rrrrssss <- decodeRrrrSsss acTree - case rrrrssss of - ( 0, 0) -> return () - (0xF, 0) -> parseAcCoefficient (n + 16) - (rrrr, ssss) -> do - decoded <- fromIntegral <$> decodeInt ssss - lift $ (mutableBlock `M.unsafeWrite` (n + rrrr)) decoded - parseAcCoefficient (n + rrrr + 1) - --- | Decompress a macroblock from a bitstream given the current configuration --- from the frame. -decompressMacroBlock :: HuffmanPackedTree -- ^ Tree used for DC coefficient - -> HuffmanPackedTree -- ^ Tree used for Ac coefficient - -> MacroBlock Int16 -- ^ Current quantization table - -> MutableMacroBlock s Int16 -- ^ A zigzag table, to avoid allocation - -> DcCoefficient -- ^ Previous dc value - -> BoolReader s (DcCoefficient, MutableMacroBlock s Int16) -decompressMacroBlock dcTree acTree quantizationTable zigzagBlock previousDc = do - dcDeltaCoefficient <- dcCoefficientDecode dcTree - block <- lift createEmptyMutableMacroBlock - let neoDcCoefficient = previousDc + dcDeltaCoefficient - lift $ (block `M.unsafeWrite` 0) neoDcCoefficient - fullBlock <- acCoefficientsDecode acTree block - decodedBlock <- lift $ decodeMacroBlock quantizationTable zigzagBlock fullBlock - return (neoDcCoefficient, decodedBlock) - -pixelClamp :: Int16 -> Word8 -pixelClamp n = fromIntegral . min 255 $ max 0 n - -unpack444Y :: Int -- ^ component index - -> Int -- ^ x - -> Int -- ^ y - -> MutableImage s PixelYCbCr8 - -> MutableMacroBlock s Int16 - -> ST s () -unpack444Y _ x y (MutableImage { mutableImageWidth = imgWidth, mutableImageData = img }) - block = blockVert baseIdx 0 zero - where zero = 0 :: Int - baseIdx = x * dctBlockSize + y * dctBlockSize * imgWidth - - blockVert _ _ j | j >= dctBlockSize = return () - blockVert writeIdx readingIdx j = blockHoriz writeIdx readingIdx zero - where blockHoriz _ readIdx i | i >= dctBlockSize = blockVert (writeIdx + imgWidth) readIdx $ j + 1 - blockHoriz idx readIdx i = do - val <- pixelClamp <$> (block `M.unsafeRead` readIdx) - (img `M.unsafeWrite` idx) val - blockHoriz (idx + 1) (readIdx + 1) $ i + 1 - -unpack444Ycbcr :: Int -- ^ Component index - -> Int -- ^ x - -> Int -- ^ y - -> MutableImage s PixelYCbCr8 - -> MutableMacroBlock s Int16 - -> ST s () -unpack444Ycbcr compIdx x y - (MutableImage { mutableImageWidth = imgWidth, mutableImageData = img }) - block = blockVert baseIdx 0 zero - where zero = 0 :: Int - baseIdx = (x * dctBlockSize + y * dctBlockSize * imgWidth) * 3 + compIdx - - blockVert _ _ j | j >= dctBlockSize = return () - blockVert idx readIdx j = do - val0 <- pixelClamp <$> (block `M.unsafeRead` readIdx) - val1 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 1)) - val2 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 2)) - val3 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 3)) - val4 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 4)) - val5 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 5)) - val6 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 6)) - val7 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 7)) - - (img `M.unsafeWrite` idx) val0 - (img `M.unsafeWrite` (idx + (3 * 1))) val1 - (img `M.unsafeWrite` (idx + (3 * 2))) val2 - (img `M.unsafeWrite` (idx + (3 * 3))) val3 - (img `M.unsafeWrite` (idx + (3 * 4))) val4 - (img `M.unsafeWrite` (idx + (3 * 5))) val5 - (img `M.unsafeWrite` (idx + (3 * 6))) val6 - (img `M.unsafeWrite` (idx + (3 * 7))) val7 - - blockVert (idx + 3 * imgWidth) (readIdx + dctBlockSize) $ j + 1 - - - {-where blockHoriz _ readIdx i | i >= 8 = blockVert (writeIdx + imgWidth * 3) readIdx $ j + 1-} - {-blockHoriz idx readIdx i = do-} - {-val <- pixelClamp <$> (block `M.unsafeRead` readIdx) -} - {-(img `M.unsafeWrite` idx) val-} - {-blockHoriz (idx + 3) (readIdx + 1) $ i + 1-} - -unpack421Ycbcr :: Int -- ^ Component index - -> Int -- ^ x - -> Int -- ^ y - -> MutableImage s PixelYCbCr8 - -> MutableMacroBlock s Int16 - -> ST s () -unpack421Ycbcr compIdx x y - (MutableImage { mutableImageWidth = imgWidth, - mutableImageHeight = _, mutableImageData = img }) - block = blockVert baseIdx 0 zero - where zero = 0 :: Int - baseIdx = (x * dctBlockSize + y * dctBlockSize * imgWidth) * 3 + compIdx - lineOffset = imgWidth * 3 - - blockVert _ _ j | j >= dctBlockSize = return () - blockVert idx readIdx j = do - v0 <- pixelClamp <$> (block `M.unsafeRead` readIdx) - v1 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 1)) - v2 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 2)) - v3 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 3)) - v4 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 4)) - v5 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 5)) - v6 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 6)) - v7 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 7)) - - (img `M.unsafeWrite` idx) v0 - (img `M.unsafeWrite` (idx + 3)) v0 - - (img `M.unsafeWrite` (idx + 6 * 1)) v1 - (img `M.unsafeWrite` (idx + 6 * 1 + 3)) v1 - - (img `M.unsafeWrite` (idx + 6 * 2)) v2 - (img `M.unsafeWrite` (idx + 6 * 2 + 3)) v2 - - (img `M.unsafeWrite` (idx + 6 * 3)) v3 - (img `M.unsafeWrite` (idx + 6 * 3 + 3)) v3 - - (img `M.unsafeWrite` (idx + 6 * 4)) v4 - (img `M.unsafeWrite` (idx + 6 * 4 + 3)) v4 - - (img `M.unsafeWrite` (idx + 6 * 5)) v5 - (img `M.unsafeWrite` (idx + 6 * 5 + 3)) v5 - - (img `M.unsafeWrite` (idx + 6 * 6)) v6 - (img `M.unsafeWrite` (idx + 6 * 6 + 3)) v6 - - (img `M.unsafeWrite` (idx + 6 * 7)) v7 - (img `M.unsafeWrite` (idx + 6 * 7 + 3)) v7 - - blockVert (idx + lineOffset) (readIdx + dctBlockSize) $ j + 1 - -type Unpacker s = Int -- ^ component index - -> Int -- ^ x - -> Int -- ^ y - -> MutableImage s PixelYCbCr8 - -> MutableMacroBlock s Int16 - -> ST s () - -type JpgScripter s a = - RWS () [([(JpgUnpackerParameter, Unpacker s)], L.ByteString)] JpgDecoderState a - -data JpgDecoderState = JpgDecoderState - { dcDecoderTables :: !(V.Vector HuffmanPackedTree) - , acDecoderTables :: !(V.Vector HuffmanPackedTree) - , quantizationMatrices :: !(V.Vector (MacroBlock Int16)) - , currentRestartInterv :: !Int - , currentFrame :: Maybe JpgFrameHeader - , isProgressive :: !Bool - , maximumHorizontalResolution :: !Int - , maximumVerticalResolution :: !Int - , seenBlobs :: !Int - } - -emptyDecoderState :: JpgDecoderState -emptyDecoderState = JpgDecoderState - { dcDecoderTables = V.fromList $ map snd - [ prepareHuffmanTable DcComponent 0 defaultDcLumaHuffmanTable - , prepareHuffmanTable DcComponent 1 defaultDcChromaHuffmanTable - ] - - , acDecoderTables = V.fromList $ map snd - [ prepareHuffmanTable AcComponent 0 defaultAcLumaHuffmanTable - , prepareHuffmanTable AcComponent 1 defaultAcChromaHuffmanTable - ] - - , quantizationMatrices = V.replicate 4 (VS.replicate (8 * 8) 1) - , currentRestartInterv = -1 - , currentFrame = Nothing - , isProgressive = False - , maximumHorizontalResolution = 0 - , maximumVerticalResolution = 0 - , seenBlobs = 0 - } - --- | This pseudo interpreter interpret the Jpg frame for the huffman, --- quant table and restart interval parameters. -jpgMachineStep :: JpgFrame -> JpgScripter s () -jpgMachineStep (JpgAppFrame _ _) = pure () -jpgMachineStep (JpgExtension _ _) = pure () -jpgMachineStep (JpgScanBlob hdr raw_data) = do - let scanCount = length $ scans hdr - params <- concat <$> mapM (scanSpecifier scanCount) (scans hdr) - modify $ \st -> st { seenBlobs = seenBlobs st + 1 } - tell [(params, raw_data) ] - where (selectionLow, selectionHigh) = spectralSelection hdr - approxHigh = fromIntegral $ successiveApproxHigh hdr - approxLow = fromIntegral $ successiveApproxLow hdr - - scanSpecifier scanCount scanSpec = do - let dcIndex = fromIntegral $ dcEntropyCodingTable scanSpec - acIndex = fromIntegral $ acEntropyCodingTable scanSpec - comp = fromIntegral (componentSelector scanSpec) - 1 - dcTree <- gets $ (V.! dcIndex) . dcDecoderTables - acTree <- gets $ (V.! acIndex) . acDecoderTables - isProgressiveImage <- gets isProgressive - maxiW <- gets maximumHorizontalResolution - maxiH <- gets maximumVerticalResolution - restart <- gets currentRestartInterv - frameInfo <- gets currentFrame - blobId <- gets seenBlobs - case frameInfo of - Nothing -> fail "Jpg decoding error - no previous frame" - Just v -> do - let compDesc = jpgComponents v !! comp - compCount = length $ jpgComponents v - xSampling = fromIntegral $ horizontalSamplingFactor compDesc - ySampling = fromIntegral $ verticalSamplingFactor compDesc - componentSubSampling = - (maxiW - xSampling + 1, maxiH - ySampling + 1) - (xCount, yCount) - | scanCount > 1 || isProgressiveImage = (xSampling, ySampling) - | otherwise = (1, 1) - - pure [ (JpgUnpackerParameter - { dcHuffmanTree = dcTree - , acHuffmanTree = acTree - , componentIndex = comp - , restartInterval = fromIntegral restart - , componentWidth = xSampling - , componentHeight = ySampling - , subSampling = componentSubSampling - , successiveApprox = (approxLow, approxHigh) - , readerIndex = blobId - , indiceVector = - if selectionLow == 0 then 0 else 1 - , coefficientRange = - ( fromIntegral selectionLow - , fromIntegral selectionHigh ) - , blockIndex = y * ySampling + x - , blockMcuX = x - , blockMcuY = y - }, unpackerDecision compCount componentSubSampling) - | y <- [0 .. yCount - 1] - , x <- [0 .. xCount - 1] ] - -jpgMachineStep (JpgScans kind hdr) = modify $ \s -> - s { currentFrame = Just hdr - , isProgressive = case kind of - JpgProgressiveDCTHuffman -> True - _ -> False - , maximumHorizontalResolution = - fromIntegral $ maximum horizontalResolutions - , maximumVerticalResolution = - fromIntegral $ maximum verticalResolutions - } - where components = jpgComponents hdr - horizontalResolutions = map horizontalSamplingFactor components - verticalResolutions = map verticalSamplingFactor components -jpgMachineStep (JpgIntervalRestart restart) = - modify $ \s -> s { currentRestartInterv = fromIntegral restart } -jpgMachineStep (JpgHuffmanTable tables) = mapM_ placeHuffmanTrees tables - where placeHuffmanTrees (spec, tree) = case huffmanTableClass spec of - DcComponent -> modify $ \s -> - let neu = dcDecoderTables s // [(idx, tree)] in - s { dcDecoderTables = neu `seq` neu } - where idx = fromIntegral $ huffmanTableDest spec - - AcComponent -> modify $ \s -> - s { acDecoderTables = acDecoderTables s // [(idx, tree)] } - where idx = fromIntegral $ huffmanTableDest spec - -jpgMachineStep (JpgQuantTable tables) = mapM_ placeQuantizationTables tables - where placeQuantizationTables table = do - let idx = fromIntegral $ quantDestination table - tableData = quantTable table - modify $ \s -> - s { quantizationMatrices = quantizationMatrices s // [(idx, tableData)] } - -unpackerDecision :: Int -> (Int, Int) -> Unpacker s -unpackerDecision 1 (1, 1) = unpack444Y -unpackerDecision _ (1, 1) = unpack444Ycbcr -unpackerDecision _ (2, 1) = unpack421Ycbcr -unpackerDecision compCount (xScalingFactor, yScalingFactor) = - unpackMacroBlock compCount xScalingFactor yScalingFactor - -decodeImage :: JpgFrameHeader - -> V.Vector (MacroBlock Int16) - -> [([(JpgUnpackerParameter, Unpacker s)], L.ByteString)] - -> MutableImage s PixelYCbCr8 -- ^ Result image to write into - -> ST s () -decodeImage frame quants lst outImage = do - let compCount = length $ jpgComponents frame - zigZagArray <- createEmptyMutableMacroBlock - dcArray <- M.replicate compCount 0 :: ST s (M.STVector s DcCoefficient) - resetCounter <- newSTRef restartIntervalValue - - forM_ lst $ \(params, str) -> do - let componentsInfo = V.fromList params - compReader = initBoolStateJpg . B.concat $ L.toChunks str - maxiW = maximum [fst $ subSampling c | (c,_) <- params] - maxiH = maximum [snd $ subSampling c | (c,_) <- params] - mcuBlockWidth = maxiW * dctBlockSize - mcuBlockHeight = maxiH * dctBlockSize - imageMcuWidth = (imgWidth + mcuBlockWidth - 1) `div` mcuBlockWidth - imageMcuHeight = (imgHeight + mcuBlockHeight - 1) `div` mcuBlockHeight - - execBoolReader compReader $ rasterMap imageMcuWidth imageMcuHeight $ \x y -> do - resetLeft <- lift $ readSTRef resetCounter - if resetLeft == 0 then do - lift $ M.set dcArray 0 - byteAlignJpg - _restartCode <- decodeRestartInterval - lift $ resetCounter `writeSTRef` (restartIntervalValue - 1) - else - lift $ resetCounter `writeSTRef` (resetLeft - 1) - - V.forM_ componentsInfo $ \(comp, unpack) -> do - let compIdx = componentIndex comp - dcTree = dcHuffmanTree comp - acTree = acHuffmanTree comp - qTable = quants V.! (min 1 compIdx) - xd = blockMcuX comp - yd = blockMcuY comp - (subX, subY) = subSampling comp - dc <- lift $ dcArray `M.unsafeRead` compIdx - (dcCoeff, block) <- - decompressMacroBlock dcTree acTree qTable zigZagArray $ fromIntegral dc - lift $ (dcArray `M.unsafeWrite` compIdx) dcCoeff - let verticalLimited = y == imageMcuHeight - 1 - if (x == imageMcuWidth - 1) || verticalLimited then - lift $ unpackMacroBlock imgComponentCount - subX subY compIdx - (x * maxiW + xd) (y * maxiH + yd) outImage block - else - lift $ unpack compIdx (x * maxiW + xd) (y * maxiH + yd) outImage block - - where imgComponentCount = length $ jpgComponents frame - - imgWidth = fromIntegral $ jpgWidth frame - imgHeight = fromIntegral $ jpgHeight frame - restartIntervalValue = case lst of - ((p,_):_,_): _ -> restartInterval p - _ -> -1 - -gatherImageKind :: [JpgFrame] -> Maybe JpgImageKind -gatherImageKind lst = case [k | JpgScans k _ <- lst, isDctSpecifier k] of - [JpgBaselineDCTHuffman] -> Just BaseLineDCT - [JpgProgressiveDCTHuffman] -> Just ProgressiveDCT - _ -> Nothing - where isDctSpecifier JpgProgressiveDCTHuffman = True - isDctSpecifier JpgBaselineDCTHuffman = True - isDctSpecifier _ = False - -gatherScanInfo :: JpgImage -> (JpgFrameKind, JpgFrameHeader) -gatherScanInfo img = head [(a, b) | JpgScans a b <- jpgFrame img] - --- | 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 'convertImage' from the 'ColorSpaceConvertible' --- typeclass. --- --- This function can output the following pixel types : --- --- * PixelY8 --- --- * PixelYCbCr8 --- -decodeJpeg :: B.ByteString -> Either String DynamicImage -decodeJpeg file = case runGetStrict get file of - Left err -> Left err - Right img -> case (compCount, imgKind) of - (_, Nothing) -> Left "Unknown Jpg kind" - (3, Just ProgressiveDCT) -> Right . ImageYCbCr8 $ decodeProgressive - (1, Just BaseLineDCT) -> Right . ImageY8 $ Image imgWidth imgHeight pixelData - (3, Just BaseLineDCT) -> Right . ImageYCbCr8 $ Image imgWidth imgHeight pixelData - _ -> Left "Wrong component count" - - where compCount = length $ jpgComponents scanInfo - (_,scanInfo) = gatherScanInfo img - - imgKind = gatherImageKind $ jpgFrame img - imgWidth = fromIntegral $ jpgWidth scanInfo - imgHeight = fromIntegral $ jpgHeight scanInfo - - imageSize = imgWidth * imgHeight * compCount - (st, wrotten) = execRWS (mapM_ jpgMachineStep (jpgFrame img)) () emptyDecoderState - Just fHdr = currentFrame st - - decodeProgressive = runST $ - progressiveUnpack - (maximumHorizontalResolution st, maximumVerticalResolution st) - fHdr - (quantizationMatrices st) - wrotten >>= unsafeFreezeImage - - pixelData = runST $ do - resultImage <- M.new imageSize - let wrapped = MutableImage imgWidth imgHeight resultImage - decodeImage - fHdr - (quantizationMatrices st) - wrotten - wrapped - VS.unsafeFreeze resultImage - -extractBlock :: Image PixelYCbCr8 -- ^ Source image - -> MutableMacroBlock s Int16 -- ^ Mutable block where to put extracted block - -> Int -- ^ Plane - -> Int -- ^ X sampling factor - -> Int -- ^ Y sampling factor - -> Int -- ^ Sample per pixel - -> Int -- ^ Block x - -> Int -- ^ Block y - -> ST s (MutableMacroBlock s Int16) -extractBlock (Image { imageWidth = w, imageHeight = h, imageData = src }) - block 1 1 sampCount plane bx by | (bx * dctBlockSize) + 7 < w && (by * 8) + 7 < h = do - let baseReadIdx = (by * dctBlockSize * w) + bx * dctBlockSize - sequence_ [(block `M.unsafeWrite` (y * dctBlockSize + x)) val - | y <- [0 .. dctBlockSize - 1] - , let blockReadIdx = baseReadIdx + y * w - , x <- [0 .. dctBlockSize - 1] - , let val = fromIntegral $ src `VS.unsafeIndex` ((blockReadIdx + x) * sampCount + plane) - ] - return block -extractBlock (Image { imageWidth = w, imageHeight = h, imageData = src }) - block sampWidth sampHeight sampCount plane bx by = do - let accessPixel x y | x < w && y < h = let idx = (y * w + x) * sampCount + plane in src `VS.unsafeIndex` idx - | x >= w = accessPixel (w - 1) y - | otherwise = accessPixel x (h - 1) - - pixelPerCoeff = fromIntegral $ sampWidth * sampHeight - - blockVal x y = sum [fromIntegral $ accessPixel (xBase + dx) (yBase + dy) - | dy <- [0 .. sampHeight - 1] - , dx <- [0 .. sampWidth - 1] ] `div` pixelPerCoeff - where xBase = blockXBegin + x * sampWidth - yBase = blockYBegin + y * sampHeight - - blockXBegin = bx * dctBlockSize * sampWidth - blockYBegin = by * dctBlockSize * sampHeight - - sequence_ [(block `M.unsafeWrite` (y * dctBlockSize + x)) $ blockVal x y | y <- [0 .. 7], x <- [0 .. 7] ] - return block - -serializeMacroBlock :: BoolWriteStateRef s - -> HuffmanWriterCode -> HuffmanWriterCode - -> MutableMacroBlock s Int32 - -> ST s () -serializeMacroBlock !st !dcCode !acCode !blk = - (blk `M.unsafeRead` 0) >>= (fromIntegral >>> encodeDc) >> writeAcs (0, 1) >> return () - where writeAcs acc@(_, 63) = - (blk `M.unsafeRead` 63) >>= (fromIntegral >>> encodeAcCoefs acc) >> return () - writeAcs acc@(_, i ) = - (blk `M.unsafeRead` i) >>= (fromIntegral >>> encodeAcCoefs acc) >>= writeAcs - - encodeDc n = writeBits' st (fromIntegral code) (fromIntegral bitCount) - >> when (ssss /= 0) (encodeInt st ssss n) - where ssss = powerOf $ fromIntegral n - (bitCount, code) = dcCode `V.unsafeIndex` fromIntegral ssss - - encodeAc 0 0 = writeBits' st (fromIntegral code) $ fromIntegral bitCount - where (bitCount, code) = acCode `V.unsafeIndex` 0 - - encodeAc zeroCount n | zeroCount >= 16 = - writeBits' st (fromIntegral code) (fromIntegral bitCount) >> encodeAc (zeroCount - 16) n - where (bitCount, code) = acCode `V.unsafeIndex` 0xF0 - encodeAc zeroCount n = - writeBits' st (fromIntegral code) (fromIntegral bitCount) >> encodeInt st ssss n - where rrrr = zeroCount `unsafeShiftL` 4 - ssss = powerOf $ fromIntegral n - rrrrssss = rrrr .|. ssss - (bitCount, code) = acCode `V.unsafeIndex` fromIntegral rrrrssss - - encodeAcCoefs ( _, 63) 0 = encodeAc 0 0 >> return (0, 64) - encodeAcCoefs (zeroRunLength, i) 0 = return (zeroRunLength + 1, i + 1) - encodeAcCoefs (zeroRunLength, i) n = - encodeAc zeroRunLength n >> return (0, i + 1) - -encodeMacroBlock :: QuantificationTable - -> MutableMacroBlock s Int32 - -> MutableMacroBlock s Int32 - -> Int16 - -> MutableMacroBlock s Int16 - -> ST s (Int32, MutableMacroBlock s Int32) -encodeMacroBlock quantTableOfComponent workData finalData prev_dc block = do - -- the inverse level shift is performed internally by the fastDCT routine - blk <- fastDctLibJpeg workData block - >>= zigZagReorderForward finalData - >>= quantize quantTableOfComponent - dc <- blk `M.unsafeRead` 0 - (blk `M.unsafeWrite` 0) $ dc - fromIntegral prev_dc - return (dc, blk) - -divUpward :: (Integral a) => a -> a -> a -divUpward n dividor = val + (if rest /= 0 then 1 else 0) - where (val, rest) = n `divMod` dividor - -prepareHuffmanTable :: DctComponent -> Word8 -> HuffmanTable - -> (JpgHuffmanTableSpec, HuffmanPackedTree) -prepareHuffmanTable classVal dest tableDef = - (JpgHuffmanTableSpec { huffmanTableClass = classVal - , huffmanTableDest = dest - , huffSizes = sizes - , huffCodes = V.fromListN 16 - [VU.fromListN (fromIntegral $ sizes ! i) lst - | (i, lst) <- zip [0..] tableDef ] - }, VS.singleton 0) - where sizes = VU.fromListN 16 $ map (fromIntegral . length) tableDef - --- | Encode an image in jpeg at a reasonnable quality level. --- If you want better quality or reduced file size, you should --- use `encodeJpegAtQuality` -encodeJpeg :: Image PixelYCbCr8 -> L.ByteString -encodeJpeg = encodeJpegAtQuality 50 - -defaultHuffmanTables :: [(JpgHuffmanTableSpec, HuffmanPackedTree)] -defaultHuffmanTables = - [ prepareHuffmanTable DcComponent 0 defaultDcLumaHuffmanTable - , prepareHuffmanTable AcComponent 0 defaultAcLumaHuffmanTable - , prepareHuffmanTable DcComponent 1 defaultDcChromaHuffmanTable - , prepareHuffmanTable AcComponent 1 defaultAcChromaHuffmanTable - ] - --- | Function to call to encode an image to jpeg. --- The quality factor should be between 0 and 100 (100 being --- the best quality). -encodeJpegAtQuality :: Word8 -- ^ Quality factor - -> Image PixelYCbCr8 -- ^ Image to encode - -> L.ByteString -- ^ Encoded JPEG -encodeJpegAtQuality quality img@(Image { imageWidth = w, imageHeight = h }) = encode finalImage - where finalImage = JpgImage [ JpgQuantTable quantTables - , JpgScans JpgBaselineDCTHuffman hdr - , JpgHuffmanTable defaultHuffmanTables - , JpgScanBlob scanHeader encodedImage - ] - - outputComponentCount = 3 - - scanHeader = scanHeader'{ scanLength = fromIntegral $ calculateSize scanHeader' } - scanHeader' = JpgScanHeader - { scanLength = 0 - , scanComponentCount = outputComponentCount - , scans = [ JpgScanSpecification { componentSelector = 1 - , dcEntropyCodingTable = 0 - , acEntropyCodingTable = 0 - } - , JpgScanSpecification { componentSelector = 2 - , dcEntropyCodingTable = 1 - , acEntropyCodingTable = 1 - } - , JpgScanSpecification { componentSelector = 3 - , dcEntropyCodingTable = 1 - , acEntropyCodingTable = 1 - } - ] - - , spectralSelection = (0, 63) - , successiveApproxHigh = 0 - , successiveApproxLow = 0 - } - - hdr = hdr' { jpgFrameHeaderLength = fromIntegral $ calculateSize hdr' } - hdr' = JpgFrameHeader { jpgFrameHeaderLength = 0 - , jpgSamplePrecision = 8 - , jpgHeight = fromIntegral h - , jpgWidth = fromIntegral w - , jpgImageComponentCount = outputComponentCount - , jpgComponents = [ - JpgComponent { componentIdentifier = 1 - , horizontalSamplingFactor = 2 - , verticalSamplingFactor = 2 - , quantizationTableDest = 0 - } - , JpgComponent { componentIdentifier = 2 - , horizontalSamplingFactor = 1 - , verticalSamplingFactor = 1 - , quantizationTableDest = 1 - } - , JpgComponent { componentIdentifier = 3 - , horizontalSamplingFactor = 1 - , verticalSamplingFactor = 1 - , quantizationTableDest = 1 - } - ] - } - - lumaQuant = scaleQuantisationMatrix (fromIntegral quality) - defaultLumaQuantizationTable - chromaQuant = scaleQuantisationMatrix (fromIntegral quality) - defaultChromaQuantizationTable - - zigzagedLumaQuant = zigZagReorderForwardv lumaQuant - zigzagedChromaQuant = zigZagReorderForwardv chromaQuant - quantTables = [ JpgQuantTableSpec { quantPrecision = 0, quantDestination = 0 - , quantTable = zigzagedLumaQuant } - , JpgQuantTableSpec { quantPrecision = 0, quantDestination = 1 - , quantTable = zigzagedChromaQuant } - ] - - encodedImage = runST $ do - let horizontalMetaBlockCount = - w `divUpward` (dctBlockSize * maxSampling) - verticalMetaBlockCount = - h `divUpward` (dctBlockSize * maxSampling) - maxSampling = 2 - lumaSamplingSize = ( maxSampling, maxSampling, zigzagedLumaQuant - , makeInverseTable defaultDcLumaHuffmanTree - , makeInverseTable defaultAcLumaHuffmanTree) - chromaSamplingSize = ( maxSampling - 1, maxSampling - 1, zigzagedChromaQuant - , makeInverseTable defaultDcChromaHuffmanTree - , makeInverseTable defaultAcChromaHuffmanTree) - componentDef = [lumaSamplingSize, chromaSamplingSize, chromaSamplingSize] - - imageComponentCount = length componentDef - - dc_table <- M.replicate 3 0 - block <- createEmptyMutableMacroBlock - workData <- createEmptyMutableMacroBlock - zigzaged <- createEmptyMutableMacroBlock - writeState <- newWriteStateRef - - -- It's ugly, I know, be avoid allocation - let blockDecoder mx my = component $ zip [0..] componentDef - where component [] = return () - component ((comp, (sizeX, sizeY, table, dc, ac)) : comp_rest) = - rasterMap sizeX sizeY decoder >> component comp_rest - where xSamplingFactor = maxSampling - sizeX + 1 - ySamplingFactor = maxSampling - sizeY + 1 - extractor = extractBlock img block xSamplingFactor ySamplingFactor imageComponentCount - - decoder subX subY = do - let blockY = my * sizeY + subY - blockX = mx * sizeX + subX - prev_dc <- dc_table `M.unsafeRead` comp - (dc_coeff, neo_block) <- (extractor comp blockX blockY >>= - encodeMacroBlock table workData zigzaged prev_dc) - (dc_table `M.unsafeWrite` comp) $ fromIntegral dc_coeff - serializeMacroBlock writeState dc ac neo_block - - rasterMap - horizontalMetaBlockCount verticalMetaBlockCount - blockDecoder - - finalizeBoolWriter writeState -
− Codec/Picture/Jpg/Common.hs
@@ -1,232 +0,0 @@-{-# LANGUAGE BangPatterns #-} -module Codec.Picture.Jpg.Common - ( DctCoefficients - , JpgUnpackerParameter( .. ) - , decodeInt - , dcCoefficientDecode - , deQuantize - , decodeRrrrSsss - , zigZagReorderForward - , zigZagReorderForwardv - , zigZagReorder - , inverseDirectCosineTransform - , unpackInt - , unpackMacroBlock - , rasterMap - , decodeMacroBlock - , decodeRestartInterval - ) where - -import Control.Applicative( (<$>), pure ) -import Control.Monad( replicateM, when ) -import Control.Monad.ST( ST, runST ) -import Data.Bits( unsafeShiftL, unsafeShiftR, (.&.) ) -import Data.Int( Int16, Int32 ) -import Data.List( foldl' ) -import Data.Maybe( fromMaybe ) -import Data.Word( Word8 ) -import qualified Data.Vector.Storable as VS -import qualified Data.Vector.Storable.Mutable as M -import Foreign.Storable ( Storable ) - -import Codec.Picture.Types -import Codec.Picture.BitWriter -import Codec.Picture.Jpg.Types -import Codec.Picture.Jpg.FastIdct -import Codec.Picture.Jpg.DefaultTable - --- | Same as for DcCoefficient, to provide nicer type signatures -type DctCoefficients = DcCoefficient - -data JpgUnpackerParameter = JpgUnpackerParameter - { dcHuffmanTree :: !HuffmanPackedTree - , acHuffmanTree :: !HuffmanPackedTree - , componentIndex :: {-# UNPACK #-} !Int - , restartInterval :: {-# UNPACK #-} !Int - , componentWidth :: {-# UNPACK #-} !Int - , componentHeight :: {-# UNPACK #-} !Int - , subSampling :: !(Int, Int) - , coefficientRange :: !(Int, Int) - , successiveApprox :: !(Int, Int) - , readerIndex :: {-# UNPACK #-} !Int - , indiceVector :: {-# UNPACK #-} !Int - , blockIndex :: {-# UNPACK #-} !Int - , blockMcuX :: {-# UNPACK #-} !Int - , blockMcuY :: {-# UNPACK #-} !Int - } - deriving Show - -decodeRestartInterval :: BoolReader s Int32 -decodeRestartInterval = return (-1) {- do - bits <- replicateM 8 getNextBitJpg - if bits == replicate 8 True - then do - marker <- replicateM 8 getNextBitJpg - return $ packInt marker - else return (-1) - -} - -{-# INLINE decodeInt #-} -decodeInt :: Int -> BoolReader s Int32 -decodeInt ssss = do - signBit <- getNextBitJpg - let dataRange = 1 `unsafeShiftL` 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 - -decodeRrrrSsss :: HuffmanPackedTree -> BoolReader s (Int, Int) -decodeRrrrSsss tree = do - rrrrssss <- huffmanPackedDecode tree - let rrrr = (rrrrssss `unsafeShiftR` 4) .&. 0xF - ssss = rrrrssss .&. 0xF - pure (fromIntegral rrrr, fromIntegral ssss) - -dcCoefficientDecode :: HuffmanPackedTree -> BoolReader s DcCoefficient -dcCoefficientDecode dcTree = do - ssss <- huffmanPackedDecode dcTree - if ssss == 0 - then return 0 - else fromIntegral <$> decodeInt (fromIntegral ssss) - --- | Apply a quantization matrix to a macroblock -{-# INLINE deQuantize #-} -deQuantize :: MacroBlock Int16 -> MutableMacroBlock s Int16 - -> ST s (MutableMacroBlock s Int16) -deQuantize table block = update 0 - where update 64 = return block - update i = do - val <- block `M.unsafeRead` i - let finalValue = val * (table `VS.unsafeIndex` i) - (block `M.unsafeWrite` i) finalValue - update $ i + 1 - -inverseDirectCosineTransform :: MutableMacroBlock s Int16 - -> ST s (MutableMacroBlock s Int16) -inverseDirectCosineTransform mBlock = - fastIdct mBlock >>= mutableLevelShift - -zigZagOrder :: MacroBlock Int -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] - ] - -zigZagReorderForwardv :: (Storable a, Num a) => VS.Vector a -> VS.Vector a -zigZagReorderForwardv vec = runST $ do - v <- M.new 64 - mv <- VS.thaw vec - zigZagReorderForward v mv >>= VS.freeze - -zigZagOrderForward :: MacroBlock Int -zigZagOrderForward = VS.generate 64 inv - where inv i = fromMaybe 0 $ VS.findIndex (i ==) zigZagOrder - -zigZagReorderForward :: (Storable a, Num a) - => MutableMacroBlock s a - -> MutableMacroBlock s a - -> ST s (MutableMacroBlock s a) -{-# SPECIALIZE INLINE zigZagReorderForward :: MutableMacroBlock s Int32 - -> MutableMacroBlock s Int32 - -> ST s (MutableMacroBlock s Int32) #-} -{-# SPECIALIZE INLINE zigZagReorderForward :: MutableMacroBlock s Int16 - -> MutableMacroBlock s Int16 - -> ST s (MutableMacroBlock s Int16) #-} -{-# SPECIALIZE INLINE zigZagReorderForward :: MutableMacroBlock s Word8 - -> MutableMacroBlock s Word8 - -> ST s (MutableMacroBlock s Word8) #-} -zigZagReorderForward zigzaged block = ordering zigZagOrderForward >> return zigzaged - where ordering !table = reorder (0 :: Int) - where reorder !i | i >= 64 = return () - reorder i = do - let idx = table `VS.unsafeIndex` i - v <- block `M.unsafeRead` idx - (zigzaged `M.unsafeWrite` i) v - reorder (i + 1) - -zigZagReorder :: MutableMacroBlock s Int16 -> MutableMacroBlock s Int16 - -> ST s (MutableMacroBlock s Int16) -zigZagReorder zigzaged block = do - let update i = do - let idx = zigZagOrder `VS.unsafeIndex` i - v <- block `M.unsafeRead` idx - (zigzaged `M.unsafeWrite` i) v - - reorder 63 = update 63 - reorder i = update i >> reorder (i + 1) - - reorder (0 :: Int) - return zigzaged - --- | Unpack an int of the given size encoded from MSB to LSB. -unpackInt :: Int -> BoolReader s Int32 -unpackInt bitCount = packInt <$> replicateM bitCount getNextBitJpg - - -{-# INLINE rasterMap #-} -rasterMap :: (Monad m) - => Int -> Int -> (Int -> Int -> m ()) - -> m () -rasterMap width height f = liner 0 - where liner y | y >= height = return () - liner y = columner 0 - where columner x | x >= width = liner (y + 1) - columner x = f x y >> columner (x + 1) - -packInt :: [Bool] -> Int32 -packInt = foldl' bitStep 0 - where bitStep acc True = (acc `unsafeShiftL` 1) + 1 - bitStep acc False = acc `unsafeShiftL` 1 - -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 -- ^ Width coefficient - -> Int -- ^ Height coefficient - -> Int -- ^ x - -> Int -- ^ y - -> Int -- ^ Component index - -> MutableImage s PixelYCbCr8 - -> MutableMacroBlock s Int16 - -> ST s () -unpackMacroBlock compCount wCoeff hCoeff compIdx x y - (MutableImage { mutableImageWidth = imgWidth, - mutableImageHeight = imgHeight, mutableImageData = img }) - block = rasterMap dctBlockSize dctBlockSize unpacker - where unpacker i j = do - let yBase = y * dctBlockSize + j * hCoeff - compVal <- pixelClamp <$> (block `M.unsafeRead` (i + j * dctBlockSize)) - rasterMap wCoeff hCoeff $ \wDup hDup -> do - let xBase = x * dctBlockSize + i * wCoeff - xPos = xBase + wDup - yPos = yBase + hDup - - when (xPos < imgWidth && yPos < imgHeight) - (do let mutableIdx = (xPos + yPos * imgWidth) * compCount + compIdx - (img `M.unsafeWrite` mutableIdx) compVal) - --- | 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 - -> MutableMacroBlock s Int16 - -> ST s (MutableMacroBlock s Int16) -decodeMacroBlock quantizationTable zigZagBlock block = - deQuantize quantizationTable block >>= zigZagReorder zigZagBlock - >>= inverseDirectCosineTransform -
− Codec/Picture/Jpg/DefaultTable.hs
@@ -1,294 +0,0 @@-{-# 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( .. ) - , HuffmanTable - , HuffmanPackedTree - , MacroBlock - , QuantificationTable - , HuffmanWriterCode - , scaleQuantisationMatrix - , makeMacroBlock - , makeInverseTable - , buildHuffmanTree - , packHuffmanTree - , huffmanPackedDecode - - , defaultChromaQuantizationTable - - , defaultLumaQuantizationTable - - , defaultAcChromaHuffmanTree - , defaultAcChromaHuffmanTable - - , defaultAcLumaHuffmanTree - , defaultAcLumaHuffmanTable - - , defaultDcChromaHuffmanTree - , defaultDcChromaHuffmanTable - - , defaultDcLumaHuffmanTree - , defaultDcLumaHuffmanTable - ) where - -import Data.Int( Int16 ) -import Foreign.Storable ( Storable ) -import Control.Monad.ST( runST ) -import qualified Data.Vector.Storable as SV -import qualified Data.Vector as V -import Data.Bits( unsafeShiftL, (.|.), (.&.) ) -import Data.Word( Word8, Word16 ) -import Data.List( foldl' ) -import qualified Data.Vector.Storable.Mutable as M - -import Codec.Picture.BitWriter - --- | 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) - -type HuffmanPackedTree = SV.Vector Word16 - -type HuffmanWriterCode = V.Vector (Word8, Word16) - -packHuffmanTree :: HuffmanTree -> HuffmanPackedTree -packHuffmanTree tree = runST $ do - table <- M.replicate 512 0x8000 - let aux (Empty) idx = return $ idx + 1 - aux (Leaf v) idx = do - (table `M.unsafeWrite` idx) $ (fromIntegral v .|. 0x4000) - return $ idx + 1 - - aux (Branch i1@(Leaf _) i2@(Leaf _)) idx = - aux i1 idx >>= aux i2 - - aux (Branch i1@(Leaf _) i2) idx = do - _ <- aux i1 idx - ix2 <- aux i2 $ idx + 2 - (table `M.unsafeWrite` (idx + 1)) $ fromIntegral $ idx + 2 - return ix2 - - aux (Branch i1 i2@(Leaf _)) idx = do - ix1 <- aux i1 (idx + 2) - _ <- aux i2 (idx + 1) - (table `M.unsafeWrite` idx) . fromIntegral $ idx + 2 - return ix1 - - aux (Branch i1 i2) idx = do - ix1 <- aux i1 (idx + 2) - ix2 <- aux i2 ix1 - (table `M.unsafeWrite` idx) (fromIntegral $ idx + 2) - (table `M.unsafeWrite` (idx + 1)) (fromIntegral ix1) - return ix2 - _ <- aux tree 0 - SV.unsafeFreeze table - -makeInverseTable :: HuffmanTree -> HuffmanWriterCode -makeInverseTable t = V.replicate 255 (0,0) V.// inner 0 0 t - where inner _ _ Empty = [] - inner depth code (Leaf v) = [(fromIntegral v, (depth, code))] - inner depth code (Branch l r) = - inner (depth + 1) shifted l ++ inner (depth + 1) (shifted .|. 1) r - where shifted = code `unsafeShiftL` 1 - --- | 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 = SV.Vector a - -type QuantificationTable = MacroBlock Int16 - --- | Helper function to create pure macro block of the good size. -makeMacroBlock :: (Storable a) => [a] -> MacroBlock a -makeMacroBlock = SV.fromListN 64 - --- | 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" - -scaleQuantisationMatrix :: Int -> QuantificationTable -> QuantificationTable -scaleQuantisationMatrix quality - | quality < 0 = scaleQuantisationMatrix 0 - -- shouldn't show much difference than with 1, - -- but hey, at least we're complete - | quality == 0 = SV.map (scale (10000 :: Int)) - | quality < 50 = let qq = 5000 `div` quality - in SV.map (scale qq) - | otherwise = SV.map (scale q) - where q = 200 - quality * 2 - scale coeff i = fromIntegral . min 255 - . max 1 - $ fromIntegral i * coeff `div` 100 - -huffmanPackedDecode :: HuffmanPackedTree -> BoolReader s Word8 -huffmanPackedDecode table = getNextBitJpg >>= aux 0 - where aux idx b - | (v .&. 0x8000) /= 0 = return 0 - | (v .&. 0x4000) /= 0 = return . fromIntegral $ v .&. 0xFF - | otherwise = getNextBitJpg >>= aux v - where tableIndex | b = idx + 1 - | otherwise = idx - v = table `SV.unsafeIndex` fromIntegral tableIndex - -defaultLumaQuantizationTable :: QuantificationTable -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 :: QuantificationTable -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 - ] - -defaultDcLumaHuffmanTree :: HuffmanTree -defaultDcLumaHuffmanTree = buildHuffmanTree defaultDcLumaHuffmanTable - --- | From the Table K.3 of ITU-81 (p153) -defaultDcLumaHuffmanTable :: HuffmanTable -defaultDcLumaHuffmanTable = - [ [] - , [0] - , [1, 2, 3, 4, 5] - , [6] - , [7] - , [8] - , [9] - , [10] - , [11] - , [] - , [] - , [] - , [] - , [] - , [] - , [] - ] - -defaultDcChromaHuffmanTree :: HuffmanTree -defaultDcChromaHuffmanTree = buildHuffmanTree defaultDcChromaHuffmanTable - --- | From the Table K.4 of ITU-81 (p153) -defaultDcChromaHuffmanTable :: HuffmanTable -defaultDcChromaHuffmanTable = - [ [] - , [0, 1, 2] - , [3] - , [4] - , [5] - , [6] - , [7] - , [8] - , [9] - , [10] - , [11] - , [] - , [] - , [] - , [] - , [] - ] - -defaultAcLumaHuffmanTree :: HuffmanTree -defaultAcLumaHuffmanTree = buildHuffmanTree defaultAcLumaHuffmanTable - --- | From the Table K.5 of ITU-81 (p154) -defaultAcLumaHuffmanTable :: HuffmanTable -defaultAcLumaHuffmanTable = - [ [] - , [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] - ] - -type HuffmanTable = [[Word8]] - -defaultAcChromaHuffmanTree :: HuffmanTree -defaultAcChromaHuffmanTree = buildHuffmanTree defaultAcChromaHuffmanTable - -defaultAcChromaHuffmanTable :: HuffmanTable -defaultAcChromaHuffmanTable = - [ [] - , [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/FastDct.hs
@@ -1,203 +0,0 @@-module Codec.Picture.Jpg.FastDct( referenceDct, fastDctLibJpeg ) where - -import Control.Applicative( (<$>) ) -import Data.Int( Int16, Int32 ) -import Data.Bits( Bits, unsafeShiftR, unsafeShiftL ) -import Control.Monad.ST( ST ) - -import qualified Data.Vector.Storable.Mutable as M - -import Codec.Picture.Jpg.Types -import Control.Monad( forM, forM_ ) - --- | Reference implementation of the DCT, directly implementing the formula --- of ITU-81. It's slow as hell, perform to many operations, but is accurate --- and a good reference point. -referenceDct :: MutableMacroBlock s Int32 - -> MutableMacroBlock s Int16 - -> ST s (MutableMacroBlock s Int32) -referenceDct workData block = forM_ [(u, v) | u <- [0 :: Int .. dctBlockSize - 1], v <- [0..dctBlockSize - 1]] (\(u,v) -> do - val <- at (u,v) - (workData `M.unsafeWrite` (v * dctBlockSize + u)) . truncate $ (1 / 4) * c u * c v * val) - >> return workData - where -- at :: (Int, Int) -> ST s Float - at (u,v) = sum <$> (forM [(x,y) | x <- [0..dctBlockSize - 1], y <- [0..dctBlockSize - 1 :: Int]] $ \(x,y) -> do - sample <- fromIntegral <$> (block `M.unsafeRead` (y * dctBlockSize + x)) - return $ sample * cos ((2 * fromIntegral x + 1) * fromIntegral u * (pi :: Float)/ 16) - * cos ((2 * fromIntegral y + 1) * fromIntegral v * pi / 16)) - c 0 = 1 / sqrt 2 - c _ = 1 - -pASS1_BITS, cONST_BITS :: Int -cONST_BITS = 13 -pASS1_BITS = 2 - - -fIX_0_298631336, fIX_0_390180644, fIX_0_541196100, - fIX_0_765366865, fIX_0_899976223, fIX_1_175875602, - fIX_1_501321110, fIX_1_847759065, fIX_1_961570560, - fIX_2_053119869, fIX_2_562915447, fIX_3_072711026 :: Int32 -fIX_0_298631336 =(2446) -- FIX(0.298631336) */ -fIX_0_390180644 =(3196) -- FIX(0.390180644) */ -fIX_0_541196100 =(4433) -- FIX(0.541196100) */ -fIX_0_765366865 =(6270) -- FIX(0.765366865) */ -fIX_0_899976223 =(7373) -- FIX(0.899976223) */ -fIX_1_175875602 =(9633) -- FIX(1.175875602) */ -fIX_1_501321110 =(12299) -- FIX(1.501321110) */ -fIX_1_847759065 =(15137) -- FIX(1.847759065) */ -fIX_1_961570560 =(16069) -- FIX(1.961570560) */ -fIX_2_053119869 =(16819) -- FIX(2.053119869) */ -fIX_2_562915447 =(20995) -- FIX(2.562915447) */ -fIX_3_072711026 =(25172) -- FIX(3.072711026) */ - -cENTERJSAMPLE :: Int32 -cENTERJSAMPLE = 128 - --- | Fast DCT extracted from libjpeg -fastDctLibJpeg :: MutableMacroBlock s Int32 - -> MutableMacroBlock s Int16 - -> ST s (MutableMacroBlock s Int32) -fastDctLibJpeg workData sample_block = do - firstPass workData 0 - secondPass workData 7 - {-_ <- mutate (\_ a -> a `quot` 8) workData-} - return workData - where -- Pass 1: process rows. - -- Note results are scaled up by sqrt(8) compared to a true DCT; - -- furthermore, we scale the results by 2**PASS1_BITS. - firstPass _ i | i == dctBlockSize = return () - firstPass dataBlock i = do - let baseIdx = i * dctBlockSize - readAt idx = fromIntegral <$> sample_block `M.unsafeRead` (baseIdx + idx) - mult = (*) - writeAt idx n = (dataBlock `M.unsafeWrite` (baseIdx + idx)) n - writeAtPos idx n = (dataBlock `M.unsafeWrite` (baseIdx + idx)) - (n `unsafeShiftR` (cONST_BITS - pASS1_BITS)) - - blk0 <- readAt 0 - blk1 <- readAt 1 - blk2 <- readAt 2 - blk3 <- readAt 3 - blk4 <- readAt 4 - blk5 <- readAt 5 - blk6 <- readAt 6 - blk7 <- readAt 7 - - let tmp0 = blk0 + blk7 - tmp1 = blk1 + blk6 - tmp2 = blk2 + blk5 - tmp3 = blk3 + blk4 - - tmp10 = tmp0 + tmp3 - tmp12 = tmp0 - tmp3 - tmp11 = tmp1 + tmp2 - tmp13 = tmp1 - tmp2 - - tmp0' = blk0 - blk7 - tmp1' = blk1 - blk6 - tmp2' = blk2 - blk5 - tmp3' = blk3 - blk4 - - -- Stage 4 and output - writeAt 0 $ (tmp10 + tmp11 - dctBlockSize * cENTERJSAMPLE) `unsafeShiftL` pASS1_BITS - writeAt 4 $ (tmp10 - tmp11) `unsafeShiftL` pASS1_BITS - - let z1 = mult (tmp12 + tmp13) fIX_0_541196100 - + (1 `unsafeShiftL` (cONST_BITS - pASS1_BITS - 1)) - - writeAtPos 2 $ z1 + mult tmp12 fIX_0_765366865 - writeAtPos 6 $ z1 - mult tmp13 fIX_1_847759065 - - let tmp10' = tmp0' + tmp3' - tmp11' = tmp1' + tmp2' - tmp12' = tmp0' + tmp2' - tmp13' = tmp1' + tmp3' - z1' = mult (tmp12' + tmp13') fIX_1_175875602 -- c3 */ - -- Add fudge factor here for final descale. */ - + (1 `unsafeShiftL` (cONST_BITS - pASS1_BITS-1)) - tmp0'' = mult tmp0' fIX_1_501321110 - tmp1'' = mult tmp1' fIX_3_072711026 - tmp2'' = mult tmp2' fIX_2_053119869 - tmp3'' = mult tmp3' fIX_0_298631336 - - tmp10'' = mult tmp10' (- fIX_0_899976223) - tmp11'' = mult tmp11' (- fIX_2_562915447) - tmp12'' = mult tmp12' (- fIX_0_390180644) + z1' - tmp13'' = mult tmp13' (- fIX_1_961570560) + z1' - - writeAtPos 1 $ tmp0'' + tmp10'' + tmp12'' - writeAtPos 3 $ tmp1'' + tmp11'' + tmp13'' - writeAtPos 5 $ tmp2'' + tmp11'' + tmp12'' - writeAtPos 7 $ tmp3'' + tmp10'' + tmp13'' - - firstPass dataBlock $ i + 1 - - -- Pass 2: process columns. - -- We remove the PASS1_BITS scaling, but leave the results scaled up - -- by an overall factor of 8. - secondPass :: M.STVector s Int32 -> Int -> ST s () - secondPass _ (-1) = return () - secondPass block i = do - let readAt idx = block `M.unsafeRead` ((7 - i) + idx * dctBlockSize) - mult = (*) - writeAt idx n = (block `M.unsafeWrite` (dctBlockSize * idx + (7 - i))) n - writeAtPos idx n = (block `M.unsafeWrite` (dctBlockSize * idx + (7 - i))) $ n `unsafeShiftR` (cONST_BITS + pASS1_BITS + 3) - blk0 <- readAt 0 - blk1 <- readAt 1 - blk2 <- readAt 2 - blk3 <- readAt 3 - blk4 <- readAt 4 - blk5 <- readAt 5 - blk6 <- readAt 6 - blk7 <- readAt 7 - - let tmp0 = blk0 + blk7 - tmp1 = blk1 + blk6 - tmp2 = blk2 + blk5 - tmp3 = blk3 + blk4 - - -- Add fudge factor here for final descale. */ - tmp10 = tmp0 + tmp3 + (1 `unsafeShiftL` (pASS1_BITS-1)) - tmp12 = tmp0 - tmp3 - tmp11 = tmp1 + tmp2 - tmp13 = tmp1 - tmp2 - - tmp0' = blk0 - blk7 - tmp1' = blk1 - blk6 - tmp2' = blk2 - blk5 - tmp3' = blk3 - blk4 - - writeAt 0 $ (tmp10 + tmp11) `unsafeShiftR` (pASS1_BITS + 3) - writeAt 4 $ (tmp10 - tmp11) `unsafeShiftR` (pASS1_BITS + 3) - - let z1 = mult (tmp12 + tmp13) fIX_0_541196100 - + (1 `unsafeShiftL` (cONST_BITS + pASS1_BITS - 1)) - - writeAtPos 2 $ z1 + mult tmp12 fIX_0_765366865 - writeAtPos 6 $ z1 - mult tmp13 fIX_1_847759065 - - let tmp10' = tmp0' + tmp3' - tmp11' = tmp1' + tmp2' - tmp12' = tmp0' + tmp2' - tmp13' = tmp1' + tmp3' - - z1' = mult (tmp12' + tmp13') fIX_1_175875602 - -- Add fudge factor here for final descale. */ - + 1 `unsafeShiftL` (cONST_BITS+pASS1_BITS-1); - - tmp0'' = mult tmp0' fIX_1_501321110 - tmp1'' = mult tmp1' fIX_3_072711026 - tmp2'' = mult tmp2' fIX_2_053119869 - tmp3'' = mult tmp3' fIX_0_298631336 - tmp10'' = mult tmp10' (- fIX_0_899976223) - tmp11'' = mult tmp11' (- fIX_2_562915447) - tmp12'' = mult tmp12' (- fIX_0_390180644) - + z1' - tmp13'' = mult tmp13' (- fIX_1_961570560) - + z1' - writeAtPos 1 $ tmp0'' + tmp10'' + tmp12'' - writeAtPos 3 $ tmp1'' + tmp11'' + tmp13'' - writeAtPos 5 $ tmp2'' + tmp11'' + tmp12'' - writeAtPos 7 $ tmp3'' + tmp10'' + tmp13'' - - secondPass block (i - 1)
− Codec/Picture/Jpg/FastIdct.hs
@@ -1,228 +0,0 @@-{-# 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 qualified Data.Vector.Storable as V -import Control.Monad.ST( ST ) -import Data.Bits( unsafeShiftL, unsafeShiftR ) -import Data.Int( Int16 ) -import qualified Data.Vector.Storable.Mutable as M - -import Codec.Picture.Jpg.Types - -iclip :: V.Vector Int16 -iclip = V.fromListN 1024 [ val i| i <- [(-512) .. 511] ] - where val i | i < (-256) = -256 - | i > 255 = 255 - | otherwise = i - -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) - --- 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 `M.unsafeRead` (0 + idx) - xx1 <- blk `M.unsafeRead` (4 + idx) - xx2 <- blk `M.unsafeRead` (6 + idx) - xx3 <- blk `M.unsafeRead` (2 + idx) - xx4 <- blk `M.unsafeRead` (1 + idx) - xx5 <- blk `M.unsafeRead` (7 + idx) - xx6 <- blk `M.unsafeRead` (5 + idx) - xx7 <- blk `M.unsafeRead` (3 + idx) - let initialState = IDctStage { x0 = (fromIntegral xx0 `unsafeShiftL` 11) + 128 - , x1 = fromIntegral xx1 `unsafeShiftL` 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) `unsafeShiftR` 8 - , x4 = (181 * (x4 c - x5 c) + 128) `unsafeShiftR` 8 - } - scaled c = c { x0 = (x7 c + x1 c) `unsafeShiftR` 8 - , x1 = (x3 c + x2 c) `unsafeShiftR` 8 - , x2 = (x0 c + x4 c) `unsafeShiftR` 8 - , x3 = (x8 c + x6 c) `unsafeShiftR` 8 - , x4 = (x8 c - x6 c) `unsafeShiftR` 8 - , x5 = (x0 c - x4 c) `unsafeShiftR` 8 - , x6 = (x3 c - x2 c) `unsafeShiftR` 8 - , x7 = (x7 c - x1 c) `unsafeShiftR` 8 - } - transformed = scaled . thirdStage . secondStage $ firstStage initialState - - (blk `M.unsafeWrite` (0 + idx)) . fromIntegral $ x0 transformed - (blk `M.unsafeWrite` (1 + idx)) . fromIntegral $ x1 transformed - (blk `M.unsafeWrite` (2 + idx)) . fromIntegral $ x2 transformed - (blk `M.unsafeWrite` (3 + idx)) . fromIntegral $ x3 transformed - (blk `M.unsafeWrite` (4 + idx)) . fromIntegral $ x4 transformed - (blk `M.unsafeWrite` (5 + idx)) . fromIntegral $ x5 transformed - (blk `M.unsafeWrite` (6 + idx)) . fromIntegral $ x6 transformed - (blk `M.unsafeWrite` (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 `M.unsafeRead` ( 0 + idx) - xx1 <- blk `M.unsafeRead` (8 * 4 + idx) - xx2 <- blk `M.unsafeRead` (8 * 6 + idx) - xx3 <- blk `M.unsafeRead` (8 * 2 + idx) - xx4 <- blk `M.unsafeRead` (8 + idx) - xx5 <- blk `M.unsafeRead` (8 * 7 + idx) - xx6 <- blk `M.unsafeRead` (8 * 5 + idx) - xx7 <- blk `M.unsafeRead` (8 * 3 + idx) - let initialState = IDctStage { x0 = (fromIntegral xx0 `unsafeShiftL` 8) + 8192 - , x1 = fromIntegral xx1 `unsafeShiftL` 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) `unsafeShiftR` 3 - , x5 = (x8' - (w1 + w7) * x5 c) `unsafeShiftR` 3 - , x6 = (x8'' - (w3 - w5) * x6 c) `unsafeShiftR` 3 - , x7 = (x8'' - (w3 + w5) * x7 c) `unsafeShiftR` 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) `unsafeShiftR` 3 - , x3 = (x1' + (w2 - w6) * x3 c) `unsafeShiftR` 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) `unsafeShiftR` 8 - , x4 = (181 * (x4 c - x5 c) + 128) `unsafeShiftR` 8 - } - - clip i | i < 511 = if i > -512 then iclip `V.unsafeIndex` (i + 512) - else iclip `V.unsafeIndex` 0 - - | otherwise = iclip `V.unsafeIndex` 1023 - - f = thirdStage . secondStage $ firstStage initialState - (blk `M.unsafeWrite` (idx + 8*0)) . clip $ (x7 f + x1 f) `unsafeShiftR` 14 - (blk `M.unsafeWrite` (idx + 8 )) . clip $ (x3 f + x2 f) `unsafeShiftR` 14 - (blk `M.unsafeWrite` (idx + 8*2)) . clip $ (x0 f + x4 f) `unsafeShiftR` 14 - (blk `M.unsafeWrite` (idx + 8*3)) . clip $ (x8 f + x6 f) `unsafeShiftR` 14 - (blk `M.unsafeWrite` (idx + 8*4)) . clip $ (x8 f - x6 f) `unsafeShiftR` 14 - (blk `M.unsafeWrite` (idx + 8*5)) . clip $ (x0 f - x4 f) `unsafeShiftR` 14 - (blk `M.unsafeWrite` (idx + 8*6)) . clip $ (x3 f - x2 f) `unsafeShiftR` 14 - (blk `M.unsafeWrite` (idx + 8*7)) . clip $ (x7 f - x1 f) `unsafeShiftR` 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 = rows 0 - where rows 8 = cols 0 - rows i = idctRow block (8 * i) >> rows (i + 1) - - cols 8 = return block - cols i = idctCol block i >> cols (i + 1) - -{-# INLINE mutableLevelShift #-} --- | Perform a Jpeg level shift in a mutable fashion. -mutableLevelShift :: MutableMacroBlock s Int16 - -> ST s (MutableMacroBlock s Int16) -mutableLevelShift block = update 0 - where update 64 = return block - update idx = do - val <- block `M.unsafeRead` idx - (block `M.unsafeWrite` idx) $ val + 128 - update $ idx + 1 -
− Codec/Picture/Jpg/Progressive.hs
@@ -1,309 +0,0 @@-{-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeSynonymInstances #-} -{-# LANGUAGE FlexibleInstances #-} -module Codec.Picture.Jpg.Progressive - ( JpgUnpackerParameter( .. ) - , progressiveUnpack - ) where - -import Control.Applicative( pure, (<$>), (<*>) ) -import Control.Monad( when, forM_ ) -import Control.Monad.ST( ST ) -import Control.Monad.Trans( lift ) -import Data.Bits( (.&.), (.|.), unsafeShiftL ) -import Data.Int( Int16, Int32 ) -import qualified Data.ByteString as B -import qualified Data.ByteString.Lazy as L -import qualified Data.Vector as V -import qualified Data.Vector.Storable as VS -import Data.Vector( (!) ) -import qualified Data.Vector.Mutable as M -import qualified Data.Vector.Storable.Mutable as MS - -import Codec.Picture.Types -import Codec.Picture.BitWriter -import Codec.Picture.Jpg.Common -import Codec.Picture.Jpg.Types -import Codec.Picture.Jpg.DefaultTable - -mcuIndexer :: JpgComponent -> DctComponent -> Int -> VS.Vector Int -mcuIndexer param kind mcuWidth = VS.fromListN (mcuWidth * th) indexes - where compW = fromIntegral $ horizontalSamplingFactor param - compH = fromIntegral $ verticalSamplingFactor param - th = compW * compH - - index AcComponent mcu y x = mcu * th + x + y * compW - index DcComponent mcu y x = (mcu + y * mcuWidth) * compW + x - indexes = - index kind <$> [0 .. mcuWidth - 1] <*> [0 .. compH - 1] <*> [0 .. compW - 1] - -createMcuLineIndices :: JpgComponent -> Int -> V.Vector (VS.Vector Int) -createMcuLineIndices comp mcuWidth = - V.fromList [ mcuIndexer comp part mcuWidth | part <- [DcComponent, AcComponent] ] - -decodeFirstDC :: JpgUnpackerParameter - -> MS.STVector s Int16 - -> MutableMacroBlock s Int16 - -> Int32 - -> BoolReader s Int32 -decodeFirstDC params dcCoeffs block eobrun = unpack >> pure eobrun - where unpack = do - (dcDeltaCoefficient) <- dcCoefficientDecode $ dcHuffmanTree params - previousDc <- lift $ dcCoeffs `MS.unsafeRead` componentIndex params - let neoDcCoefficient = previousDc + dcDeltaCoefficient - approxLow = fst $ successiveApprox params - scaledDc = neoDcCoefficient `unsafeShiftL` approxLow - lift $ (block `MS.unsafeWrite` 0) scaledDc - lift $ (dcCoeffs `MS.unsafeWrite` componentIndex params) neoDcCoefficient - -decodeRefineDc :: JpgUnpackerParameter - -> a - -> MutableMacroBlock s Int16 - -> Int32 - -> BoolReader s Int32 -decodeRefineDc params _ block eobrun = unpack >> pure eobrun - where approxLow = fst $ successiveApprox params - plusOne = 1 `unsafeShiftL` approxLow - unpack = do - bit <- getNextBitJpg - when bit . lift $ do - v <- block `MS.unsafeRead` 0 - (block `MS.unsafeWrite` 0) $ v .|. plusOne - -decodeFirstAc :: JpgUnpackerParameter - -> a - -> MutableMacroBlock s Int16 - -> Int32 - -> BoolReader s Int32 -decodeFirstAc _params _ _block eobrun | eobrun > 0 = pure $ eobrun - 1 -decodeFirstAc params _ block _ = unpack startIndex - where (startIndex, maxIndex) = coefficientRange params - (low, _) = successiveApprox params - unpack n | n > maxIndex = pure 0 - unpack n = do - rrrrssss <- decodeRrrrSsss $ acHuffmanTree params - case rrrrssss of - (0xF, 0) -> unpack $ n + 16 - ( 0, 0) -> return 0 - ( r, 0) -> eobrun <$> unpackInt r - where eobrun lowBits = (1 `unsafeShiftL` r) - 1 + lowBits - ( r, s) -> do - let n' = n + r - val <- (`unsafeShiftL` low) <$> decodeInt s - lift . (block `MS.unsafeWrite` n') $ fromIntegral val - unpack $ n' + 1 - -decodeRefineAc :: JpgUnpackerParameter - -> a - -> MutableMacroBlock s Int16 - -> Int32 - -> BoolReader s Int32 -decodeRefineAc params _ block eobrun - | eobrun == 0 = unpack startIndex - | otherwise = performEobRun startIndex >> return (eobrun - 1) - where (startIndex, maxIndex) = coefficientRange params - (low, _) = successiveApprox params - plusOne = 1 `unsafeShiftL` low - minusOne = (-1) `unsafeShiftL` low - - getBitVal = do - v <- getNextBitJpg - pure $ if v then plusOne else minusOne - - {-performEobRun :: Int -> BoolReader s ()-} - performEobRun idx | idx > maxIndex = pure () - performEobRun idx = do - coeff <- lift $ block `MS.unsafeRead` idx - when (coeff /= 0) $ do - bit <- getNextBitJpg - case (bit, (coeff .&. plusOne) == 0) of - (False, _) -> performEobRun $ idx + 1 - (True, False) -> performEobRun $ idx + 1 - (True, True) -> do - let newVal | coeff >= 0 = coeff + plusOne - | otherwise = coeff + minusOne - lift $ (block `MS.unsafeWrite` idx) newVal - performEobRun $ idx + 1 - - unpack idx | idx > maxIndex = pure 0 - unpack idx = do - rrrrssss <- decodeRrrrSsss $ acHuffmanTree params - case rrrrssss of - (0xF, 0) -> error "Dunno" - ( r, 0) -> do - lowBits <- unpackInt r - let newEobRun = (1 `unsafeShiftL` r) + lowBits - 1 - performEobRun idx - pure newEobRun - - ( r, _) -> do - idx' <- updateCoeffs r idx - val <- getBitVal - when (idx <= maxIndex) $ - (lift $ (block `MS.unsafeWrite` idx') val) - unpack $ idx' + 1 - - updateCoeffs r idx - | r < 0 = pure idx - | idx > maxIndex = pure idx - updateCoeffs r idx = do - coeff <- lift $ block `MS.unsafeRead` idx - if coeff /= 0 then do - bit <- getNextBitJpg - when (bit && coeff .&. plusOne == 0) $ - if coeff >= 0 then - lift . (block `MS.unsafeWrite` idx) $ coeff + plusOne - else - lift . (block `MS.unsafeWrite` idx) $ coeff + minusOne - updateCoeffs r $ idx + 1 - else - updateCoeffs (r - 1) $ idx + 1 - -type Unpacker s = - JpgUnpackerParameter -> MS.STVector s Int16 -> MutableMacroBlock s Int16 -> Int32 - -> BoolReader s Int32 - - -prepareUnpacker :: [([(JpgUnpackerParameter, a)], L.ByteString)] - -> ST s ( V.Vector (V.Vector (JpgUnpackerParameter, Unpacker s)) - , M.STVector s BoolState) -prepareUnpacker lst = do - let boolStates = V.fromList $ map snd infos - vec <- V.unsafeThaw boolStates - return (V.fromList $ map fst infos, vec) - where infos = map prepare lst - prepare ([], _) = error "progressiveUnpack, no component" - prepare (whole@((param, _) : _) , byteString) = - (V.fromList $ map (\(p,_) -> (p, unpacker)) whole, boolReader) - where unpacker = selection (successiveApprox param) (coefficientRange param) - boolReader = initBoolStateJpg . B.concat $ L.toChunks byteString - - selection (_, 0) (0, _) = decodeFirstDC - selection (_, 0) _ = decodeFirstAc - selection _ (0, _) = decodeRefineDc - selection _ _ = decodeRefineAc - -data ComponentData s = ComponentData - { componentIndices :: V.Vector (VS.Vector Int) - , componentBlocks :: V.Vector (MutableMacroBlock s Int16) - , componentId :: !Int - , componentBlockCount :: !Int - } - --- | Iteration from to n in monadic context, without data --- keeping. -lineMap :: (Monad m) => Int -> (Int -> m ()) -> m () -{-# INLINE lineMap #-} -lineMap count f = go 0 - where go n | n >= count = return () - go n = f n >> go (n + 1) - -progressiveUnpack :: (Int, Int) - -> JpgFrameHeader - -> V.Vector (MacroBlock Int16) - -> [([(JpgUnpackerParameter, a)], L.ByteString)] - -> ST s (MutableImage s PixelYCbCr8) -progressiveUnpack (maxiW, maxiH) frame quants lst = do - (unpackers, readers) <- prepareUnpacker lst - allBlocks <- mapM allocateWorkingBlocks . zip [0..] $ jpgComponents frame - :: ST s [ComponentData s] - let scanCount = length lst - restartIntervalValue = case lst of - ((p,_):_,_): _ -> restartInterval p - _ -> -1 - dcCoeffs <- MS.replicate imgComponentCount 0 - eobRuns <- MS.replicate (length lst) 0 - workBlock <- createEmptyMutableMacroBlock - writeIndices <- MS.replicate imgComponentCount (0 :: Int) - restartIntervals <- MS.replicate scanCount restartIntervalValue - let elementCount = imgWidth * imgHeight * fromIntegral imgComponentCount - img <- MutableImage imgWidth imgHeight <$> MS.replicate elementCount 128 - - let processRestartInterval = - forM_ [0 .. scanCount - 1] $ \ix -> do - v <- restartIntervals `MS.read` ix - if v == 0 then do - -- reset DC prediction - when (ix == 0) (MS.set dcCoeffs 0) - reader <- readers `M.read` ix - (_, updated) <- runBoolReaderWith reader $ - byteAlignJpg >> decodeRestartInterval - (readers `M.write` ix) updated - (eobRuns `MS.unsafeWrite` ix) 0 - (restartIntervals `MS.unsafeWrite` ix) $ restartIntervalValue - 1 - else - (restartIntervals `MS.unsafeWrite` ix) $ v - 1 - - - lineMap imageMcuHeight $ \mmY -> do - -- Reset all blocks to 0 - forM_ allBlocks $ V.mapM_ (flip MS.set 0) . componentBlocks - MS.set writeIndices 0 - - lineMap imageMcuWidth $ \_mmx -> do - processRestartInterval - V.forM_ unpackers $ V.mapM_ $ \(unpackParam, unpacker) -> do - boolState <- readers `M.read` readerIndex unpackParam - eobrun <- eobRuns `MS.read` readerIndex unpackParam - let componentNumber = componentIndex unpackParam - writeIndex <- writeIndices `MS.read` componentNumber - let componentData = allBlocks !! componentNumber - indexVector = - componentIndices componentData ! indiceVector unpackParam - realIndex = indexVector VS.! (writeIndex + blockIndex unpackParam) - writeBlock = - componentBlocks componentData ! realIndex - (eobrun', state) <- - runBoolReaderWith boolState $ - unpacker unpackParam dcCoeffs writeBlock eobrun - - (readers `M.write` readerIndex unpackParam) state - (eobRuns `MS.write` readerIndex unpackParam) eobrun' - - -- Update the write indices - forM_ allBlocks $ \comp -> do - writeIndex <- writeIndices `MS.read` componentId comp - let newIndex = writeIndex + componentBlockCount comp - (writeIndices `MS.write` componentId comp) $ newIndex - - forM_ allBlocks $ \compData -> do - let compBlocks = componentBlocks compData - cId = componentId compData - table = quants ! min 1 cId - comp = jpgComponents frame !! cId - compW = fromIntegral $ horizontalSamplingFactor comp - compH = fromIntegral $ verticalSamplingFactor comp - cw8 = maxiW - fromIntegral (horizontalSamplingFactor comp) + 1 - ch8 = maxiH - fromIntegral (verticalSamplingFactor comp) + 1 - - rasterMap (imageMcuWidth * compW) compH $ \rx y -> do - let ry = mmY * maxiH + y - block = compBlocks ! (y * imageMcuWidth * compW + rx) - transformed <- decodeMacroBlock table workBlock block - unpackMacroBlock imgComponentCount - cw8 ch8 cId (rx * cw8) ry - img transformed - - return img - - where imgComponentCount = length $ jpgComponents frame - mcuBlockWidth = maxiW * dctBlockSize - mcuBlockHeight = maxiH * dctBlockSize - - imgWidth = fromIntegral $ jpgWidth frame - imgHeight = fromIntegral $ jpgHeight frame - imageMcuWidth = (imgWidth + mcuBlockWidth - 1) `div` mcuBlockWidth - imageMcuHeight = (imgHeight + mcuBlockHeight - 1) `div` mcuBlockHeight - - allocateWorkingBlocks (ix, comp) = do - let blockCount = hSample * vSample * imageMcuWidth - blocks <- V.replicateM blockCount createEmptyMutableMacroBlock - return $ ComponentData - { componentBlocks = blocks - , componentIndices = createMcuLineIndices comp imageMcuWidth - , componentBlockCount = hSample * vSample - , componentId = ix - } - where hSample = fromIntegral $ horizontalSamplingFactor comp - vSample = fromIntegral $ verticalSamplingFactor comp -
− Codec/Picture/Jpg/Types.hs
@@ -1,546 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-} -module Codec.Picture.Jpg.Types( MutableMacroBlock - , createEmptyMutableMacroBlock - , printMacroBlock - , printPureMacroBlock - , DcCoefficient - , JpgImage( .. ) - , JpgComponent( .. ) - , JpgFrameHeader( .. ) - , JpgFrame( .. ) - , JpgFrameKind( .. ) - , JpgScanHeader( .. ) - , JpgQuantTableSpec( .. ) - , JpgHuffmanTableSpec( .. ) - , JpgImageKind( .. ) - , JpgScanSpecification( .. ) - , calculateSize - , dctBlockSize - ) where - -import Control.Applicative( (<$>), (<*>)) -import Control.Monad( when, replicateM, forM, forM_, unless ) -import Control.Monad.ST( ST ) -import Data.Bits( (.|.), (.&.), unsafeShiftL, unsafeShiftR ) -import Foreign.Storable ( Storable ) -import Data.Vector.Unboxed( (!) ) -import qualified Data.Vector as V -import qualified Data.Vector.Unboxed as VU -import qualified Data.Vector.Storable as VS -import qualified Data.Vector.Storable.Mutable as M -import qualified Data.ByteString as B -import qualified Data.ByteString.Lazy as L - -import Data.Int( Int16 ) -import Data.Word(Word8, Word16 ) -import Data.Binary( Binary(..) ) - -import Data.Binary.Get( Get - , getWord8 - , getWord16be - , getByteString - , skip - , bytesRead - ) - -import Data.Binary.Put( Put - , putWord8 - , putWord16be - , putLazyByteString - ) - -import Codec.Picture.InternalHelper -import Codec.Picture.Jpg.DefaultTable - -{-import Debug.Trace-} -import Text.Printf - --- | Type only used to make clear what kind of integer we are carrying --- Might be transformed into newtype in the future -type DcCoefficient = Int16 - --- | Macroblock that can be transformed. -type MutableMacroBlock s a = M.STVector s a - -data JpgFrameKind = - JpgBaselineDCTHuffman - | JpgExtendedSequentialDCTHuffman - | JpgProgressiveDCTHuffman - | JpgLosslessHuffman - | JpgDifferentialSequentialDCTHuffman - | JpgDifferentialProgressiveDCTHuffman - | JpgDifferentialLosslessHuffman - | JpgExtendedSequentialArithmetic - | JpgProgressiveDCTArithmetic - | JpgLosslessArithmetic - | JpgDifferentialSequentialDCTArithmetic - | JpgDifferentialProgressiveDCTArithmetic - | JpgDifferentialLosslessArithmetic - | JpgQuantizationTable - | JpgHuffmanTableMarker - | JpgStartOfScan - | JpgEndOfImage - | JpgAppSegment Word8 - | JpgExtensionSegment Word8 - - | JpgRestartInterval - | JpgRestartIntervalEnd Word8 - deriving (Eq, Show) - -data JpgFrame = - JpgAppFrame !Word8 B.ByteString - | JpgExtension !Word8 B.ByteString - | JpgQuantTable ![JpgQuantTableSpec] - | JpgHuffmanTable ![(JpgHuffmanTableSpec, HuffmanPackedTree)] - | JpgScanBlob !JpgScanHeader !L.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 - - -instance SizeCalculable JpgFrameHeader where - calculateSize hdr = 2 + 1 + 2 + 2 + 1 - + sum [calculateSize c | c <- jpgComponents hdr] - -data JpgComponent = JpgComponent - { componentIdentifier :: !Word8 - -- | Stored with 4 bits - , horizontalSamplingFactor :: !Word8 - -- | Stored with 4 bits - , verticalSamplingFactor :: !Word8 - , quantizationTableDest :: !Word8 - } - deriving Show - -instance SizeCalculable JpgComponent where - calculateSize _ = 3 - -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 - -instance SizeCalculable JpgScanSpecification where - calculateSize _ = 2 - -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 - -instance SizeCalculable JpgScanHeader where - calculateSize hdr = 2 + 1 - + sum [calculateSize c | c <- scans hdr] - + 2 - + 1 - -data JpgQuantTableSpec = JpgQuantTableSpec - { -- | Stored on 4 bits - quantPrecision :: !Word8 - - -- | Stored on 4 bits - , quantDestination :: !Word8 - - , quantTable :: MacroBlock Int16 - } - deriving Show - -class SizeCalculable a where - calculateSize :: a -> Int - --- | Type introduced only to avoid some typeclass overlapping --- problem -newtype TableList a = TableList [a] - -instance (SizeCalculable a, Binary a) => Binary (TableList a) where - put (TableList lst) = do - putWord16be . fromIntegral $ sum [calculateSize table | table <- lst] + 2 - mapM_ put lst - - get = TableList <$> (getWord16be >>= \s -> innerParse (fromIntegral s - 2)) - where innerParse :: Int -> Get [a] - innerParse 0 = return [] - innerParse size = do - onStart <- fromIntegral <$> bytesRead - table <- get - onEnd <- fromIntegral <$> bytesRead - (table :) <$> innerParse (size - (onEnd - onStart)) - -instance SizeCalculable JpgQuantTableSpec where - calculateSize table = - 1 + (fromIntegral (quantPrecision table) + 1) * 64 - -instance Binary JpgQuantTableSpec where - put table = do - let precision = quantPrecision table - put4BitsOfEach precision (quantDestination table) - forM_ (VS.toList $ 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 = VS.fromListN 64 coeffs - } - -data JpgHuffmanTableSpec = JpgHuffmanTableSpec - { -- | 0 : DC, 1 : AC, stored on 4 bits - huffmanTableClass :: !DctComponent - -- | Stored on 4 bits - , huffmanTableDest :: !Word8 - - , huffSizes :: !(VU.Vector Word8) - , huffCodes :: !(V.Vector (VU.Vector Word8)) - } - deriving Show - -instance SizeCalculable JpgHuffmanTableSpec where - calculateSize table = 1 + 16 + sum [fromIntegral e | e <- VU.toList $ huffSizes table] - -instance Binary JpgHuffmanTableSpec where - put table = do - let classVal = if huffmanTableClass table == DcComponent - then 0 else 1 - put4BitsOfEach classVal $ huffmanTableDest table - mapM_ put . VU.toList $ huffSizes table - forM_ [0 .. 15] $ \i -> - when (huffSizes table ! i /= 0) - (let elements = VU.toList $ huffCodes table V.! i - in mapM_ put elements) - - get = do - (huffClass, huffDest) <- get4BitOfEach - sizes <- replicateM 16 getWord8 - codes <- forM sizes $ \s -> - VU.replicateM (fromIntegral s) getWord8 - return JpgHuffmanTableSpec - { huffmanTableClass = - if huffClass == 0 then DcComponent else AcComponent - , huffmanTableDest = huffDest - , huffSizes = VU.fromListN 16 sizes - , huffCodes = V.fromListN 16 codes - } - -instance Binary JpgImage where - put (JpgImage { jpgFrame = frames }) = - putWord8 0xFF >> putWord8 0xD8 >> mapM_ putFrame frames - >> putWord8 0xFF >> putWord8 0xD9 - - get = do - let startOfImageMarker = 0xD8 - -- endOfImageMarker = 0xD9 - checkMarker commonMarkerFirstByte startOfImageMarker - eatUntilCode - frames <- parseFrames - {-checkMarker commonMarkerFirstByte endOfImageMarker-} - return JpgImage { jpgFrame = frames } - -eatUntilCode :: Get () -eatUntilCode = do - code <- getWord8 - unless (code == 0xFF) eatUntilCode - -takeCurrentFrame :: Get B.ByteString -takeCurrentFrame = do - size <- getWord16be - getByteString (fromIntegral size - 2) - -putFrame :: JpgFrame -> Put -putFrame (JpgAppFrame appCode str) = - put (JpgAppSegment appCode) >> putWord16be (fromIntegral $ B.length str) >> put str -putFrame (JpgExtension appCode str) = - put (JpgExtensionSegment appCode) >> putWord16be (fromIntegral $ B.length str) >> put str -putFrame (JpgQuantTable tables) = - put JpgQuantizationTable >> put (TableList tables) -putFrame (JpgHuffmanTable tables) = - put JpgHuffmanTableMarker >> put (TableList $ map fst tables) -putFrame (JpgIntervalRestart size) = - put JpgRestartInterval >> put (RestartInterval size) -putFrame (JpgScanBlob hdr blob) = - put JpgStartOfScan >> put hdr >> putLazyByteString blob -putFrame (JpgScans kind hdr) = - put kind >> put hdr - --------------------------------------------------- ----- 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") - -extractScanContent :: L.ByteString -> (L.ByteString, L.ByteString) -extractScanContent str = aux 0 - where maxi = fromIntegral $ L.length str - 1 - - aux n | n >= maxi = (str, L.empty) - | v == 0xFF && vNext /= 0 && not isReset = L.splitAt n str - | otherwise = aux (n + 1) - where v = str `L.index` n - vNext = str `L.index` (n + 1) - isReset = 0xD0 <= vNext && vNext <= 0xD7 - -parseFrames :: Get [JpgFrame] -parseFrames = do - kind <- get - let parseNextFrame = do - word <- getWord8 - when (word /= 0xFF) $ do - readedData <- bytesRead - fail $ "Invalid Frame marker (" ++ show word - ++ ", bytes read : " ++ show readedData ++ ")" - parseFrames - - case kind of - JpgEndOfImage -> return [] - JpgAppSegment c -> - (\frm lst -> JpgAppFrame c frm : lst) <$> takeCurrentFrame <*> parseNextFrame - JpgExtensionSegment c -> - (\frm lst -> JpgExtension c frm : lst) <$> takeCurrentFrame <*> parseNextFrame - JpgQuantizationTable -> - (\(TableList quants) lst -> JpgQuantTable quants : lst) <$> get <*> parseNextFrame - JpgRestartInterval -> - (\(RestartInterval i) lst -> JpgIntervalRestart i : lst) <$> get <*> parseNextFrame - JpgHuffmanTableMarker -> - (\(TableList huffTables) lst -> - JpgHuffmanTable [(t, packHuffmanTree . buildPackedHuffmanTree $ huffCodes t) | t <- huffTables] : lst) - <$> get <*> parseNextFrame - JpgStartOfScan -> - (\frm imgData -> - let (d, other) = extractScanContent imgData - in - case runGet parseFrames (L.drop 1 other) of - Left _ -> [JpgScanBlob frm d] - Right lst -> JpgScanBlob frm d : lst - ) <$> get <*> getRemainingLazyBytes - - _ -> (\hdr lst -> JpgScans kind hdr : lst) <$> get <*> parseNextFrame - -buildPackedHuffmanTree :: V.Vector (VU.Vector Word8) -> HuffmanTree -buildPackedHuffmanTree = buildHuffmanTree . map VU.toList . V.toList - -secondStartOfFrameByteOfKind :: JpgFrameKind -> Word8 -secondStartOfFrameByteOfKind = aux - where - aux JpgBaselineDCTHuffman = 0xC0 - aux JpgExtendedSequentialDCTHuffman = 0xC1 - aux JpgProgressiveDCTHuffman = 0xC2 - aux JpgLosslessHuffman = 0xC3 - aux JpgDifferentialSequentialDCTHuffman = 0xC5 - aux JpgDifferentialProgressiveDCTHuffman = 0xC6 - aux JpgDifferentialLosslessHuffman = 0xC7 - aux JpgExtendedSequentialArithmetic = 0xC9 - aux JpgProgressiveDCTArithmetic = 0xCA - aux JpgLosslessArithmetic = 0xCB - aux JpgHuffmanTableMarker = 0xC4 - aux JpgDifferentialSequentialDCTArithmetic = 0xCD - aux JpgDifferentialProgressiveDCTArithmetic = 0xCE - aux JpgDifferentialLosslessArithmetic = 0xCF - aux JpgEndOfImage = 0xD9 - aux JpgQuantizationTable = 0xDB - aux JpgStartOfScan = 0xDA - aux JpgRestartInterval = 0xDD - aux (JpgRestartIntervalEnd v) = v - aux (JpgAppSegment a) = a - aux (JpgExtensionSegment a) = a - -data JpgImageKind = BaseLineDCT | ProgressiveDCT - -instance Binary JpgFrameKind where - put v = putWord8 0xFF >> put (secondStartOfFrameByteOfKind v) - get = do - -- no lookahead :( - {-word <- getWord8-} - word2 <- getWord8 - 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 - 0xD9 -> JpgEndOfImage - 0xDA -> JpgStartOfScan - 0xDB -> JpgQuantizationTable - 0xDD -> JpgRestartInterval - a | a >= 0xF0 -> JpgExtensionSegment a - | a >= 0xE0 -> JpgAppSegment a - | a >= 0xD0 && a <= 0xD7 -> JpgRestartIntervalEnd a - | otherwise -> error ("Invalid frame marker (" ++ show a ++ ")") - -put4BitsOfEach :: Word8 -> Word8 -> Put -put4BitsOfEach a b = put $ (a `unsafeShiftL` 4) .|. b - -get4BitOfEach :: Get (Word8, Word8) -get4BitOfEach = do - val <- get - return ((val `unsafeShiftR` 4) .&. 0xF, val .&. 0xF) - -newtype RestartInterval = RestartInterval Word16 - -instance Binary 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 Binary 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 Binary JpgFrameHeader where - get = do - beginOffset <- fromIntegral <$> bytesRead - frmHLength <- getWord16be - samplePrec <- getWord8 - h <- getWord16be - w <- getWord16be - compCount <- getWord8 - components <- replicateM (fromIntegral compCount) get - endOffset <- fromIntegral <$> bytesRead - when (beginOffset - endOffset < fromIntegral frmHLength) - (skip $ fromIntegral frmHLength - (endOffset - beginOffset)) - 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 Binary 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 Binary 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 - putWord16be $ scanLength v - putWord8 $ scanComponentCount v - mapM_ put $ scans v - putWord8 . fst $ spectralSelection v - putWord8 . snd $ spectralSelection v - put4BitsOfEach (successiveApproxHigh v) $ successiveApproxLow v - -{-# INLINE createEmptyMutableMacroBlock #-} --- | Create a new macroblock with the good array size -createEmptyMutableMacroBlock :: (Storable a, Num a) => ST s (MutableMacroBlock s a) -createEmptyMutableMacroBlock = M.replicate 64 0 - -printMacroBlock :: (Storable a, PrintfArg a) - => MutableMacroBlock s a -> ST s String -printMacroBlock block = pLn 0 - where pLn 64 = return "===============================\n" - pLn i = do - v <- block `M.unsafeRead` i - vn <- pLn (i+1) - return $ printf (if i `mod` 8 == 0 then "\n%5d " else "%5d ") v ++ vn - -printPureMacroBlock :: (Storable a, PrintfArg a) => MacroBlock a -> String -printPureMacroBlock block = pLn 0 - where pLn 64 = "===============================\n" - pLn i = str ++ pLn (i + 1) - where str | i `mod` 8 == 0 = printf "\n%5d " v - | otherwise = printf "%5d" v - v = block VS.! i - - -{-# INLINE dctBlockSize #-} -dctBlockSize :: Num a => a -dctBlockSize = 8
− Codec/Picture/Png.hs
@@ -1,500 +0,0 @@-{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE TypeSynonymInstances #-} -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE ViewPatterns #-} --- | 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( .. ) - - , decodePng - , writePng - , encodeDynamicPng - , writeDynamicPng - - ) where - -import Control.Applicative( (<$>) ) -import Control.Monad( forM_, foldM_, when ) -import Control.Monad.ST( ST, runST ) -import Data.Binary( Binary( get) ) - -import qualified Data.Vector.Storable as V -import qualified Data.Vector.Storable.Mutable as M -import Data.Bits( (.&.), (.|.), unsafeShiftL, unsafeShiftR ) -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.Unsafe as BU -import qualified Data.ByteString.Lazy as Lb -import Foreign.Storable ( Storable ) - -import Codec.Picture.Types -import Codec.Picture.Png.Type -import Codec.Picture.Png.Export -import Codec.Picture.InternalHelper - --- | 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 -{-# INLINE unparsePngFilter #-} -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" - -getBounds :: (Monad m, Storable a) => M.STVector s a -> m (Int, Int) -{-# INLINE getBounds #-} -getBounds v = return (0, M.length v - 1) - --- | 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 - -> B.ByteString -> Int - -> ST s Int -pngFiltering _ _ (imgWidth, imgHeight) _str initialIdx - | imgWidth <= 0 || imgHeight <= 0 = return initialIdx -pngFiltering unpacker beginZeroes (imgWidth, imgHeight) str initialIdx = do - thisLine <- M.replicate (beginZeroes + imgWidth) 0 - otherLine <- M.replicate (beginZeroes + imgWidth) 0 - let folder _ _ lineIndex !idx | lineIndex >= imgHeight = return idx - folder previousLine currentLine lineIndex !idx = do - let byte = str `BU.unsafeIndex` idx - let lineFilter = case unparsePngFilter byte of - Right FilterNone -> filterNone - Right FilterSub -> filterSub - Right FilterAverage -> filterAverage - Right FilterUp -> filterUp - Right FilterPaeth -> filterPaeth - _ -> filterNone - idx' <- lineFilter previousLine currentLine $ idx + 1 - unpacker lineIndex (stride, currentLine) - folder currentLine previousLine (lineIndex + 1) idx' - - folder thisLine otherLine (0 :: Int) initialIdx - - 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 -> Int -> ST s Int - filterNone !_previousLine !thisLine = inner beginZeroes - where inner idx !readIdx - | idx > lastIdx = return readIdx - | otherwise = do let byte = str `BU.unsafeIndex` readIdx - (thisLine `M.unsafeWrite` idx) byte - inner (idx + 1) $ readIdx + 1 - - filterSub !_previousLine !thisLine = inner beginZeroes - where inner idx !readIdx - | idx > lastIdx = return readIdx - | otherwise = do let byte = str `BU.unsafeIndex` readIdx - val <- thisLine `M.unsafeRead` (idx - stride) - (thisLine `M.unsafeWrite` idx) $ byte + val - inner (idx + 1) $ readIdx + 1 - - filterUp !previousLine !thisLine = inner beginZeroes - where inner idx !readIdx - | idx > lastIdx = return readIdx - | otherwise = do let byte = str `BU.unsafeIndex` readIdx - val <- previousLine `M.unsafeRead` idx - (thisLine `M.unsafeWrite` idx) $ val + byte - inner (idx + 1) $ readIdx + 1 - - filterAverage !previousLine !thisLine = inner beginZeroes - where inner idx !readIdx - | idx > lastIdx = return readIdx - | otherwise = do let byte = str `BU.unsafeIndex` readIdx - valA <- thisLine `M.unsafeRead` (idx - stride) - valB <- previousLine `M.unsafeRead` idx - let a' = fromIntegral valA - b' = fromIntegral valB - average = fromIntegral ((a' + b') `div` (2 :: Word16)) - writeVal = byte + average - (thisLine `M.unsafeWrite` idx) $ writeVal - inner (idx + 1) $ readIdx + 1 - - filterPaeth !previousLine !thisLine = inner beginZeroes - where inner idx !readIdx - | idx > lastIdx = return readIdx - | otherwise = do let byte = str `BU.unsafeIndex` readIdx - valA <- thisLine `M.unsafeRead` (idx - stride) - valC <- previousLine `M.unsafeRead` (idx - stride) - valB <- previousLine `M.unsafeRead` idx - (thisLine `M.unsafeWrite` idx) $ byte + paeth valA valB valC - inner (idx + 1) $ readIdx + 1 - - 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' - --- | Directly stolen from the definition in the standard (on W3C page), --- pixel predictor. - -type PngLine s = M.STVector s 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 `M.unsafeRead` (srcPixelIndex + sample) - let writeIdx = destSampleIndex + sample - (arr `M.unsafeWrite` 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 `M.unsafeRead` (pixelIndex + beginIdx) - let writeIdx n = lineIndex + (pixelIndex * 8 + n) * strideWidth + beginLeft - forM_ [0 .. 7] $ \bit -> (arr `M.unsafeWrite` writeIdx (7 - bit)) ((val `unsafeShiftR` bit) .&. 1) - - when (lineRest /= 0) - (do val <- line `M.unsafeRead` endLine - let writeIdx n = lineIndex + (pixelToRead * 8 + n) * strideWidth + beginLeft - forM_ [0 .. lineRest - 1] $ \bit -> - (arr `M.unsafeWrite` writeIdx bit) ((val `unsafeShiftR` (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 `M.unsafeRead` (pixelIndex + beginIdx) - let writeIdx n = lineIndex + (pixelIndex * 4 + n) * strideWidth + beginLeft - (arr `M.unsafeWrite` writeIdx 0) $ (val `unsafeShiftR` 6) .&. 0x3 - (arr `M.unsafeWrite` writeIdx 1) $ (val `unsafeShiftR` 4) .&. 0x3 - (arr `M.unsafeWrite` writeIdx 2) $ (val `unsafeShiftR` 2) .&. 0x3 - (arr `M.unsafeWrite` writeIdx 3) $ val .&. 0x3 - - when (lineRest /= 0) - (do val <- line `M.unsafeRead` endLine - let writeIdx n = lineIndex + (pixelToRead * 4 + n) * strideWidth + beginLeft - forM_ [0 .. lineRest - 1] $ \bit -> - (arr `M.unsafeWrite` writeIdx bit) ((val `unsafeShiftR` (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 `M.unsafeRead` (pixelIndex + beginIdx) - let writeIdx n = lineIndex + (pixelIndex * 2 + n) * strideWidth + beginLeft - (arr `M.unsafeWrite` writeIdx 0) $ (val `unsafeShiftR` 4) .&. 0xF - (arr `M.unsafeWrite` writeIdx 1) $ val .&. 0xF - - when (lineRest /= 0) - (do val <- line `M.unsafeRead` endLine - let writeIdx = lineIndex + (pixelToRead * 2) * strideWidth + beginLeft - (arr `M.unsafeWrite` writeIdx) $ (val `unsafeShiftR` 4) .&. 0xF) - -shortUnpacker :: Int -> MutableImage s Word16 -> 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 `M.unsafeRead` (srcPixelIndex + sample * 2 + 0) - lowBits <- line `M.unsafeRead` (srcPixelIndex + sample * 2 + 1) - let fullValue = fromIntegral lowBits .|. (fromIntegral highBits `unsafeShiftL` 8) - writeIdx = destSampleIndex + sample - (arr `M.unsafeWrite` writeIdx) $ fullValue - --- | Transform a scanline to a bunch of bytes. Bytes are then packed --- into pixels at a further step. -scanlineUnpacker8 :: Int -> Int -> MutableImage s Word8 -> StrideInfo -> BeginOffset - -> LineUnpacker s -scanlineUnpacker8 1 = bitUnpacker -scanlineUnpacker8 2 = twoBitsUnpacker -scanlineUnpacker8 4 = halfByteUnpacker -scanlineUnpacker8 8 = byteUnpacker -scanlineUnpacker8 _ = 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) - -> B.ByteString - -> ST s () -scanLineInterleaving depth sampleCount (imgWidth, imgHeight) unpacker str = - pngFiltering (unpacker (1,1) (0, 0)) strideInfo (byteWidth, imgHeight) str 0 >> return () - 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) - -> B.ByteString -> ST s () -adam7Unpack depth sampleCount (imgWidth, imgHeight) unpacker str = - foldM_ (\i f -> f i) 0 subImages >> return () - where Adam7MatrixInfo { adam7StartingRow = startRows - , adam7RowIncrement = rowIncrement - , adam7StartingCol = startCols - , adam7ColIncrement = colIncrement } = adam7MatrixInfo - - subImages = - [pngFiltering (unpacker (incrW, incrH) (beginW, beginH)) strideInfo (byteWidth, passHeight) str - | (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 - ] - 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 -> B.ByteString -> ST s (Either (V.Vector Word8) (V.Vector Word16)) -deinterlacer (PngIHdr { width = w, height = h, colourType = imgKind - , interlaceMethod = method, bitDepth = depth }) str = do - let compCount = sampleCountOfImageType imgKind - arraySize = fromIntegral $ w * h * compCount - deinterlaceFunction = case method of - PngNoInterlace -> scanLineInterleaving - PngInterlaceAdam7 -> adam7Unpack - iBitDepth = fromIntegral depth - if iBitDepth <= 8 - then do - imgArray <- M.new arraySize - let mutableImage = MutableImage (fromIntegral w) (fromIntegral h) imgArray - deinterlaceFunction iBitDepth - (fromIntegral compCount) - (fromIntegral w, fromIntegral h) - (scanlineUnpacker8 iBitDepth (fromIntegral compCount) - mutableImage) - str - Left <$> V.unsafeFreeze imgArray - - else do - imgArray <- M.new arraySize - let mutableImage = MutableImage (fromIntegral w) (fromIntegral h) imgArray - deinterlaceFunction iBitDepth - (fromIntegral compCount) - (fromIntegral w, fromIntegral h) - (shortUnpacker (fromIntegral compCount) mutableImage) - str - Right <$> V.unsafeFreeze imgArray - -generateGreyscalePalette :: Word8 -> PngPalette -generateGreyscalePalette times = Image possibilities 0 vec - where possibilities = 2 ^ times - 1 - vec = V.fromListN ((fromIntegral possibilities + 1) * 3) $ concat pixels - pixels = [[i, i, i] | n <- [0 .. possibilities] - , let i = fromIntegral $ 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 - -{-# INLINE bounds #-} -bounds :: Storable a => V.Vector a -> (Int, Int) -bounds v = (0, V.length v - 1) - -applyPalette :: PngPalette -> V.Vector Word8 -> V.Vector Word8 -applyPalette pal img = V.fromListN ((initSize + 1) * 3) pixels - where (_, initSize) = bounds img - pixels = concat [[r, g, b] | ipx <- V.toList img - , let PixelRGB8 r g b = pixelAt pal (fromIntegral ipx) 0] - -applyPaletteWithTransparency :: PngPalette -> Word8 -> V.Vector Word8 -> V.Vector Word8 -applyPaletteWithTransparency pal transp img = V.fromListN ((initSize + 1) * 4) pixels - where (_, initSize) = bounds img - pixels = concat [ if ipx == transp then [0, 0, 0, 0] else [r, g, b, 255] - | ipx <- V.toList img - , let PixelRGB8 r g b = pixelAt pal (fromIntegral ipx) 0] - --- | 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 --- --- * PixelY16 --- --- * PixelYA8 --- --- * PixelYA16 --- --- * PixelRGB8 --- --- * PixelRGB16 --- --- * PixelRGBA8 --- --- * PixelRGBA16 --- -decodePng :: B.ByteString -> Either String DynamicImage -decodePng byte = do - rawImg <- runGetStrict get byte - let ihdr@(PngIHdr { width = w, height = h }) = header rawImg - compressedImageData = - Lb.concat [chunkData chunk | chunk <- chunks rawImg - , chunkType chunk == iDATSignature] - zlibHeaderSize = 1 {- compression method/flags code -} - + 1 {- Additional flags/check bits -} - + 4 {-CRC-} - - toImage const1 _const2 (Left a) = - Right . const1 $ Image (fromIntegral w) (fromIntegral h) a - toImage _const1 const2 (Right a) = - Right . const2 $ Image (fromIntegral w) (fromIntegral h) a - - palette8 palette [(Lb.uncons -> Just (c,_))] (Left img) = - Right . ImageRGBA8 - . Image (fromIntegral w) (fromIntegral h) - $ applyPaletteWithTransparency palette c img - - palette8 palette _ (Left img) = - Right . ImageRGB8 - . Image (fromIntegral w) (fromIntegral h) - $ applyPalette palette img - - palette8 _ _ (Right _) = Left "Invalid bit depth for paleted image" - - transparencyColor = - [ chunkData chunk | chunk <- chunks rawImg - , chunkType chunk == tRNSSignature ] - - unparse _ t PngGreyscale bytes - | bitDepth ihdr == 1 = unparse (Just paletteRGBA1) t PngIndexedColor bytes - | bitDepth ihdr == 2 = unparse (Just paletteRGBA2) t PngIndexedColor bytes - | bitDepth ihdr == 4 = unparse (Just paletteRGBA4) t PngIndexedColor bytes - | otherwise = toImage ImageY8 ImageY16 $ runST stArray - where stArray = deinterlacer ihdr bytes - - unparse Nothing _ PngIndexedColor _ = Left "no valid palette found" - unparse _ _ PngTrueColour bytes = - toImage ImageRGB8 ImageRGB16 $ runST stArray - where stArray = deinterlacer ihdr bytes - unparse _ _ PngGreyscaleWithAlpha bytes = - toImage ImageYA8 ImageYA16 $ runST stArray - where stArray = deinterlacer ihdr bytes - unparse _ _ PngTrueColourWithAlpha bytes = - toImage ImageRGBA8 ImageRGBA16 $ runST stArray - where stArray = deinterlacer ihdr bytes - unparse (Just plte) transparency PngIndexedColor bytes = - palette8 plte transparency $ runST stArray - where stArray = deinterlacer ihdr bytes - - if Lb.length compressedImageData <= zlibHeaderSize - then Left "Invalid data size" - else let imgData = Z.decompress 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 transparencyColor (colourType ihdr) parseableData -
− Codec/Picture/Png/Export.hs
@@ -1,163 +0,0 @@-{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE TypeSynonymInstances #-} -{-# LANGUAGE TypeFamilies #-} --- | 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 Control.Monad( forM_ ) -import Control.Monad.ST( ST, runST ) -import Data.Bits( unsafeShiftR, (.&.) ) -import Data.Binary( encode ) -import Data.Word(Word8, Word16) -import qualified Codec.Compression.Zlib as Z -import qualified Data.ByteString as B -import qualified Data.ByteString.Lazy as Lb - -import qualified Data.Vector.Storable as VS -import qualified Data.Vector.Storable.Mutable as M - -import Codec.Picture.Types -import Codec.Picture.Png.Type -import Codec.Picture.VectorByteConversion( blitVector ) - --- | Encode an image into a png if possible. -class PngSavable a where - -- | Transform an image into a png encoded bytestring, ready - -- to be written as a file. - encodePng :: Image a -> Lb.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 = Lb.writeFile path $ encodePng img - -endChunk :: PngRawChunk -endChunk = PngRawChunk { chunkLength = 0 - , chunkType = iENDSignature - , chunkCRC = pngComputeCrc [iENDSignature] - , chunkData = Lb.empty - } - - -prepareIDatChunk :: Lb.ByteString -> PngRawChunk -prepareIDatChunk imgData = PngRawChunk - { chunkLength = fromIntegral $ Lb.length imgData - , chunkType = iDATSignature - , chunkCRC = pngComputeCrc [iDATSignature, imgData] - , chunkData = imgData - } - -genericEncode16BitsPng :: (PixelBaseComponent a ~ Word16) - => PngImageType -> Int -> Image a -> Lb.ByteString -genericEncode16BitsPng imgKind compCount - image@(Image { imageWidth = w, imageHeight = h, imageData = arr }) = - encode PngRawImage { header = hdr, chunks = [prepareIDatChunk imgEncodedData, endChunk]} - where hdr = preparePngHeader image imgKind 16 - zero = B.singleton 0 - - lineSize = compCount * w - toByteString vec = blitVector vec 0 (lineSize * 2) - encodeLine line = toByteString $ runST $ do - finalVec <- M.new $ lineSize * 2 :: ST s (M.STVector s Word8) - let baseIndex = line * lineSize - forM_ [0 .. lineSize - 1] $ \ix -> do - let v = arr `VS.unsafeIndex` (baseIndex + ix) - high = fromIntegral $ (v `unsafeShiftR` 8) .&. 0xFF - low = fromIntegral $ v .&. 0xFF - - (finalVec `M.unsafeWrite` (ix * 2 + 0)) high - (finalVec `M.unsafeWrite` (ix * 2 + 1)) low - - VS.unsafeFreeze finalVec - - imgEncodedData = Z.compress . Lb.fromChunks - $ concat [[zero, encodeLine line] | line <- [0 .. h - 1]] - - -genericEncodePng :: (PixelBaseComponent a ~ Word8) - => PngImageType -> Int -> Image a -> Lb.ByteString -genericEncodePng imgKind compCount - image@(Image { imageWidth = w, imageHeight = h, imageData = arr }) = - encode PngRawImage { header = hdr, chunks = [prepareIDatChunk imgEncodedData, endChunk]} - where hdr = preparePngHeader image imgKind 8 - zero = B.singleton 0 - - lineSize = compCount * w - encodeLine line = blitVector arr (line * lineSize) lineSize - imgEncodedData = Z.compress . Lb.fromChunks - $ concat [[zero, encodeLine line] | line <- [0 .. h - 1]] - -instance PngSavable PixelRGBA8 where - encodePng = genericEncodePng PngTrueColourWithAlpha 4 - -instance PngSavable PixelRGB8 where - encodePng = genericEncodePng PngTrueColour 3 - -instance PngSavable Pixel8 where - encodePng = genericEncodePng PngGreyscale 1 - -instance PngSavable PixelYA8 where - encodePng = genericEncodePng PngGreyscaleWithAlpha 2 - -instance PngSavable PixelYA16 where - encodePng = genericEncode16BitsPng PngGreyscaleWithAlpha 2 - -instance PngSavable Pixel16 where - encodePng = genericEncode16BitsPng PngGreyscale 1 - -instance PngSavable PixelRGB16 where - encodePng = genericEncode16BitsPng PngTrueColour 3 - -instance PngSavable PixelRGBA16 where - encodePng = genericEncode16BitsPng PngTrueColourWithAlpha 4 - --- | 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 -> Lb.writeFile path b >> return (Right True) - --- | Encode a dynamic image in bmp if possible, supported pixel type are : --- --- - Y8 --- --- - Y16 --- --- - YA8 --- --- - YA16 --- --- - RGB8 --- --- - RGB16 --- --- - RGBA8 --- --- - RGBA16 --- -encodeDynamicPng :: DynamicImage -> Either String Lb.ByteString -encodeDynamicPng (ImageRGB8 img) = Right $ encodePng img -encodeDynamicPng (ImageRGBA8 img) = Right $ encodePng img -encodeDynamicPng (ImageY8 img) = Right $ encodePng img -encodeDynamicPng (ImageY16 img) = Right $ encodePng img -encodeDynamicPng (ImageYA8 img) = Right $ encodePng img -encodeDynamicPng (ImageYA16 img) = Right $ encodePng img -encodeDynamicPng (ImageRGB16 img) = Right $ encodePng img -encodeDynamicPng (ImageRGBA16 img) = Right $ encodePng img -encodeDynamicPng _ = Left "Unsupported image format for PNG export"
− Codec/Picture/Png/Type.hs
@@ -1,329 +0,0 @@--- | 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 - , tRNSSignature - -- * Low level types - , ChunkSignature - , PngRawImage( .. ) - , PngChunk( .. ) - , PngRawChunk( .. ) - , PngLowLevel( .. ) - ) where - -import Control.Applicative( (<$>) ) -import Control.Monad( when, replicateM ) -import Data.Bits( xor, (.&.), unsafeShiftR ) -import Data.Binary( Binary(..), Get, get ) -import Data.Binary.Get( getWord8 - , getWord32be - , getLazyByteString - ) -import Data.Binary.Put( runPut - , putWord8 - , putWord32be - , putLazyByteString - ) -import Data.Vector.Unboxed( Vector, fromListN, (!) ) -import qualified Data.Vector.Storable as V -import Data.List( foldl' ) -import Data.Word( Word32, Word8 ) -import qualified Data.ByteString.Lazy as L -import qualified Data.ByteString.Lazy.Char8 as LS - -import Codec.Picture.Types -import Codec.Picture.InternalHelper - --------------------------------------------------- ----- Types --------------------------------------------------- - --- | Value used to identify a png chunk, must be 4 bytes long. -type ChunkSignature = L.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 = Image 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 = Image pixelCount 1 . V.fromListN (3 * pixelCount) <$> pixels - where pixelUnpacker = replicateM (fromIntegral pixelCount * 3) get - pixelCount = fromIntegral $ chunkLength plte `div` 3 - pixels = runGet pixelUnpacker (chunkData plte) - --- | Data structure during real png loading/parsing -data PngRawChunk = PngRawChunk - { chunkLength :: Word32 - , chunkType :: ChunkSignature - , chunkCRC :: Word32 - , chunkData :: L.ByteString - } - --- | PNG chunk representing some extra information found in the parsed file. -data PngChunk = PngChunk - { pngChunkData :: L.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 Binary 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 Binary PngRawImage where - put img = do - putLazyByteString pngSignature - put $ header img - mapM_ put $ chunks img - - get = parseRawPngImage - -instance Binary PngRawChunk where - put chunk = do - putWord32be $ chunkLength chunk - putLazyByteString $ chunkType chunk - when (chunkLength chunk /= 0) - (putLazyByteString $ chunkData chunk) - putWord32be $ chunkCRC chunk - - get = do - size <- getWord32be - chunkSig <- getLazyByteString (fromIntegral $ L.length iHDRSignature) - imgData <- if size == 0 - then return L.empty - else getLazyByteString (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 Binary PngIHdr where - put hdr = do - putWord32be 13 - let inner = runPut $ do - putLazyByteString 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] - putLazyByteString inner - putWord32be crc - - get = do - _size <- getWord32be - ihdrSig <- getLazyByteString (L.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 Binary 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 <- getLazyByteString (L.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 = L.pack [137, 80, 78, 71, 13, 10, 26, 10] - --- | Helper function to help pack signatures. -signature :: String -> ChunkSignature -signature = LS.pack - --- | Signature for the header chunk of png (must be the first) -iHDRSignature :: ChunkSignature -iHDRSignature = signature "IHDR" - --- | Signature for a palette chunk in the pgn file. Must --- occure before iDAT. -pLTESignature :: ChunkSignature -pLTESignature = signature "PLTE" - --- | Signature for a data chuck (with image parts in it) -iDATSignature :: ChunkSignature -iDATSignature = signature "IDAT" - --- | Signature for the last chunk of a png image, telling --- the end. -iENDSignature :: ChunkSignature -iENDSignature = signature "IEND" - -tRNSSignature :: ChunkSignature -tRNSSignature = signature "tRNS" - -instance Binary 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 :: Vector Word32 -pngCrcTable = fromListN 256 [ 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 `unsafeShiftR` 1) - | otherwise = c `unsafeShiftR` 1 - magicConstant = 0xedb88320 :: Word32 - --- | Compute the CRC of a raw buffer, as described in annex D of the PNG --- specification. -pngComputeCrc :: [L.ByteString] -> Word32 -pngComputeCrc = (0xFFFFFFFF `xor`) . L.foldl' updateCrc 0xFFFFFFFF . L.concat - where updateCrc crc val = - let u32Val = fromIntegral val - lutVal = pngCrcTable ! (fromIntegral ((crc `xor` u32Val) .&. 0xFF)) - in lutVal `xor` (crc `unsafeShiftR` 8) -
− Codec/Picture/Saving.hs
@@ -1,165 +0,0 @@-{-# LANGUAGE TypeFamilies #-} --- | Helper functions to save dynamic images to other file format --- with automatic color space/sample format conversion done automatically. -module Codec.Picture.Saving( imageToJpg - , imageToPng - , imageToBitmap - , imageToTiff - , imageToRadiance - ) where - -import Data.Bits( unsafeShiftR ) -import Data.Word( Word8, Word16 ) -import qualified Data.ByteString.Lazy as L -import Codec.Picture.Bitmap -import Codec.Picture.Jpg -import Codec.Picture.Png -import Codec.Picture.HDR -import Codec.Picture.Types -import Codec.Picture.Tiff - -import qualified Data.Vector.Storable as V - -componentToLDR :: Float -> Word8 -componentToLDR = truncate . (255 *) . min 1.0 . max 0.0 - -toStandardDef :: Image PixelRGBF -> Image PixelRGB8 -toStandardDef img = pixelMap pixelConverter img - where pixelConverter (PixelRGBF rf gf bf) = PixelRGB8 r g b - where r = componentToLDR rf - g = componentToLDR gf - b = componentToLDR bf - -greyScaleToStandardDef :: Image PixelF -> Image Pixel8 -greyScaleToStandardDef = pixelMap componentToLDR - -from16to8 :: ( PixelBaseComponent source ~ Word16 - , PixelBaseComponent dest ~ Word8 ) - => Image source -> Image dest -from16to8 Image { imageWidth = w, imageHeight = h - , imageData = arr } = Image w h transformed - where transformed = V.map toWord8 arr - toWord8 v = fromIntegral (v `unsafeShiftR` 8) - -from16toFloat :: ( PixelBaseComponent source ~ Word16 - , PixelBaseComponent dest ~ Float ) - => Image source -> Image dest -from16toFloat Image { imageWidth = w, imageHeight = h - , imageData = arr } = Image w h transformed - where transformed = V.map toWord8 arr - toWord8 v = fromIntegral v / 65536.0 - --- | This function will try to do anything to encode an image --- as RADIANCE, make all color conversion and such. Equivalent --- of 'decodeImage' for radiance encoding -imageToRadiance :: DynamicImage -> L.ByteString -imageToRadiance (ImageCMYK8 img) = - imageToRadiance . ImageRGB8 $ convertImage img -imageToRadiance (ImageCMYK16 img) = - imageToRadiance . ImageRGB16 $ convertImage img -imageToRadiance (ImageYCbCr8 img) = - imageToRadiance . ImageRGB8 $ convertImage img -imageToRadiance (ImageRGB8 img) = - imageToRadiance . ImageRGBF $ promoteImage img -imageToRadiance (ImageRGBF img) = encodeHDR img -imageToRadiance (ImageRGBA8 img) = - imageToRadiance . ImageRGBF . promoteImage $ dropAlphaLayer img -imageToRadiance (ImageY8 img) = - imageToRadiance . ImageRGB8 $ promoteImage img -imageToRadiance (ImageYF img) = - imageToRadiance . ImageRGBF $ promoteImage img -imageToRadiance (ImageYA8 img) = - imageToRadiance . ImageRGB8 . promoteImage $ dropAlphaLayer img -imageToRadiance (ImageY16 img) = - imageToRadiance . ImageRGBF $ pixelMap toRgbf img - where toRgbf v = PixelRGBF val val val - where val = fromIntegral v / 65536.0 - -imageToRadiance (ImageYA16 img) = - imageToRadiance . ImageRGBF $ pixelMap toRgbf img - where toRgbf (PixelYA16 v _) = PixelRGBF val val val - where val = fromIntegral v / 65536.0 -imageToRadiance (ImageRGB16 img) = - imageToRadiance . ImageRGBF $ from16toFloat img -imageToRadiance (ImageRGBA16 img) = - imageToRadiance . ImageRGBF $ pixelMap toRgbf img - where toRgbf (PixelRGBA16 r g b _) = PixelRGBF (f r) (f g) (f b) - where f v = fromIntegral v / 65536.0 - --- | This function will try to do anything to encode an image --- as JPEG, make all color conversion and such. Equivalent --- of 'decodeImage' for jpeg encoding -imageToJpg :: Int -> DynamicImage -> L.ByteString -imageToJpg quality dynImage = - let encodeAtQuality = encodeJpegAtQuality (fromIntegral quality) - in case dynImage of - ImageYCbCr8 img -> encodeAtQuality img - ImageCMYK8 img -> imageToJpg quality . ImageRGB8 $ convertImage img - ImageCMYK16 img -> imageToJpg quality . ImageRGB16 $ convertImage img - ImageRGB8 img -> encodeAtQuality (convertImage img) - ImageRGBF img -> imageToJpg quality . ImageRGB8 $ toStandardDef img - ImageRGBA8 img -> encodeAtQuality (convertImage $ dropAlphaLayer img) - ImageYF img -> imageToJpg quality . ImageY8 $ greyScaleToStandardDef img - ImageY8 img -> encodeAtQuality . convertImage - $ (promoteImage img :: Image PixelRGB8) - ImageYA8 img -> encodeAtQuality $ - convertImage (promoteImage $ dropAlphaLayer img :: Image PixelRGB8) - ImageY16 img -> imageToJpg quality . ImageY8 $ from16to8 img - ImageYA16 img -> imageToJpg quality . ImageYA8 $ from16to8 img - ImageRGB16 img -> imageToJpg quality . ImageRGB8 $ from16to8 img - ImageRGBA16 img -> imageToJpg quality . ImageRGBA8 $ from16to8 img - --- | This function will try to do anything to encode an image --- as PNG, make all color conversion and such. Equivalent --- of 'decodeImage' for PNG encoding -imageToPng :: DynamicImage -> L.ByteString -imageToPng (ImageYCbCr8 img) = encodePng (convertImage img :: Image PixelRGB8) -imageToPng (ImageCMYK8 img) = encodePng (convertImage img :: Image PixelRGB8) -imageToPng (ImageCMYK16 img) = encodePng (convertImage img :: Image PixelRGB16) -imageToPng (ImageRGB8 img) = encodePng img -imageToPng (ImageRGBF img) = encodePng $ toStandardDef img -imageToPng (ImageRGBA8 img) = encodePng img -imageToPng (ImageY8 img) = encodePng img -imageToPng (ImageYF img) = encodePng $ greyScaleToStandardDef img -imageToPng (ImageYA8 img) = encodePng img -imageToPng (ImageY16 img) = encodePng img -imageToPng (ImageYA16 img) = encodePng img -imageToPng (ImageRGB16 img) = encodePng img -imageToPng (ImageRGBA16 img) = encodePng img - --- | This function will try to do anything to encode an image --- as a Tiff, make all color conversion and such. Equivalent --- of 'decodeImage' for Tiff encoding -imageToTiff :: DynamicImage -> L.ByteString -imageToTiff (ImageYCbCr8 img) = encodeTiff img -imageToTiff (ImageCMYK8 img) = encodeTiff img -imageToTiff (ImageCMYK16 img) = encodeTiff img -imageToTiff (ImageRGB8 img) = encodeTiff img -imageToTiff (ImageRGBF img) = encodeTiff $ toStandardDef img -imageToTiff (ImageRGBA8 img) = encodeTiff img -imageToTiff (ImageY8 img) = encodeTiff img -imageToTiff (ImageYF img) = encodeTiff $ greyScaleToStandardDef img -imageToTiff (ImageYA8 img) = encodeTiff $ dropAlphaLayer img -imageToTiff (ImageY16 img) = encodeTiff img -imageToTiff (ImageYA16 img) = encodeTiff $ dropAlphaLayer img -imageToTiff (ImageRGB16 img) = encodeTiff img -imageToTiff (ImageRGBA16 img) = encodeTiff img - --- | This function will try to do anything to encode an image --- as bitmap, make all color conversion and such. Equivalent --- of 'decodeImage' for Bitmap encoding -imageToBitmap :: DynamicImage -> L.ByteString -imageToBitmap (ImageYCbCr8 img) = encodeBitmap (convertImage img :: Image PixelRGB8) -imageToBitmap (ImageCMYK8 img) = encodeBitmap (convertImage img :: Image PixelRGB8) -imageToBitmap (ImageCMYK16 img) = imageToBitmap . ImageRGB16 $ convertImage img -imageToBitmap (ImageRGBF img) = encodeBitmap $ toStandardDef img -imageToBitmap (ImageRGB8 img) = encodeBitmap img -imageToBitmap (ImageRGBA8 img) = encodeBitmap img -imageToBitmap (ImageY8 img) = encodeBitmap img -imageToBitmap (ImageYF img) = encodeBitmap $ greyScaleToStandardDef img -imageToBitmap (ImageYA8 img) = encodeBitmap (promoteImage img :: Image PixelRGBA8) -imageToBitmap (ImageY16 img) = imageToBitmap . ImageY8 $ from16to8 img -imageToBitmap (ImageYA16 img) = imageToBitmap . ImageYA8 $ from16to8 img -imageToBitmap (ImageRGB16 img) = imageToBitmap . ImageRGB8 $ from16to8 img -imageToBitmap (ImageRGBA16 img) = imageToBitmap . ImageRGBA8 $ from16to8 img -
− Codec/Picture/Tiff.hs
@@ -1,1285 +0,0 @@-{-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE ScopedTypeVariables #-} --- | Module implementing TIFF decoding. --- --- Supported compression schemes : --- --- * Uncompressed --- --- * PackBits --- --- * LZW --- --- Supported bit depth : --- --- * 2 bits --- --- * 4 bits --- --- * 8 bits --- --- * 16 bits --- -module Codec.Picture.Tiff( decodeTiff, TiffSaveable, encodeTiff, writeTiff ) where - -import Control.Applicative( (<$>), (<*>), pure ) -import Control.Monad( when, replicateM, foldM_ ) -import Control.Monad.ST( ST, runST ) -import Control.Monad.Writer.Strict( execWriter, tell, Writer ) -import Data.Int( Int8 ) -import Data.Word( Word8, Word16 ) -import Data.Bits( (.&.), (.|.), unsafeShiftL, unsafeShiftR ) -import Data.Binary( Binary( .. ) ) -import Data.Binary.Get( Get - , getWord16le, getWord16be - , getWord32le, getWord32be - , bytesRead - , skip - , getByteString - ) -import Data.Binary.Put( Put, runPut - , putWord16le, putWord16be - , putWord32le, putWord32be - , putByteString - ) - -import Data.List( sortBy, mapAccumL ) -import qualified Data.Vector as V -import qualified Data.Vector.Storable as VS -import qualified Data.Vector.Storable.Mutable as M -import Data.Word( Word32 ) -import qualified Data.ByteString as B -import qualified Data.ByteString.Lazy as Lb -import qualified Data.ByteString.Unsafe as BU - -import Foreign.Storable( sizeOf ) - -import Codec.Picture.InternalHelper -import Codec.Picture.BitWriter -import Codec.Picture.Types -import Codec.Picture.Gif.LZW -import Codec.Picture.VectorByteConversion( toByteString ) - -data Endianness = EndianLittle - | EndianBig - deriving (Eq, Show) - -instance Binary Endianness where - put EndianLittle = putWord16le 0x4949 - put EndianBig = putWord16le 0x4D4D - - get = do - tag <- getWord16le - case tag of - 0x4949 -> return EndianLittle - 0x4D4D -> return EndianBig - _ -> fail "Invalid endian tag value" - --- | Because having a polymorphic get with endianness is to nice --- to pass on, introducing this helper type class, which is just --- a superset of Binary, but formalising a parameter passing --- into it. -class BinaryParam a b where - getP :: a -> Get b - putP :: a -> b -> Put - -data TiffHeader = TiffHeader - { hdrEndianness :: !Endianness - , hdrOffset :: {-# UNPACK #-} !Word32 - } - deriving (Eq, Show) - -instance BinaryParam Endianness Word16 where - putP EndianLittle = putWord16le - putP EndianBig = putWord16be - - getP EndianLittle = getWord16le - getP EndianBig = getWord16be - -instance BinaryParam Endianness Word32 where - putP EndianLittle = putWord32le - putP EndianBig = putWord32be - - getP EndianLittle = getWord32le - getP EndianBig = getWord32be - -instance Binary TiffHeader where - put hdr = do - let endian = hdrEndianness hdr - put endian - putP endian (42 :: Word16) - putP endian $ hdrOffset hdr - - get = do - endian <- get - magic <- getP endian - let magicValue = 42 :: Word16 - when (magic /= magicValue) - (fail "Invalid TIFF magic number") - TiffHeader endian <$> getP endian - -data TiffPlanarConfiguration = - PlanarConfigContig -- = 1 - | PlanarConfigSeparate -- = 2 - -planarConfgOfConstant :: Word32 -> Get TiffPlanarConfiguration -planarConfgOfConstant 0 = pure PlanarConfigContig -planarConfgOfConstant 1 = pure PlanarConfigContig -planarConfgOfConstant 2 = pure PlanarConfigSeparate -planarConfgOfConstant v = fail $ "Unknown planar constant (" ++ show v ++ ")" - -constantToPlaneConfiguration :: TiffPlanarConfiguration -> Word16 -constantToPlaneConfiguration PlanarConfigContig = 1 -constantToPlaneConfiguration PlanarConfigSeparate = 2 - -data TiffCompression = - CompressionNone -- 1 - | CompressionModifiedRLE -- 2 - | CompressionLZW -- 5 - | CompressionJPEG -- 6 - | CompressionPackBit -- 32273 - -data IfdType = TypeByte - | TypeAscii - | TypeShort - | TypeLong - | TypeRational - | TypeSByte - | TypeUndefined - | TypeSignedShort - | TypeSignedLong - | TypeSignedRational - | TypeFloat - | TypeDouble - -instance BinaryParam Endianness IfdType where - getP endianness = getP endianness >>= conv - where - conv :: Word16 -> Get IfdType - conv 1 = return TypeByte - conv 2 = return TypeAscii - conv 3 = return TypeShort - conv 4 = return TypeLong - conv 5 = return TypeRational - conv 6 = return TypeSByte - conv 7 = return TypeUndefined - conv 8 = return TypeSignedShort - conv 9 = return TypeSignedLong - conv 10 = return TypeSignedRational - conv 11 = return TypeFloat - conv 12 = return TypeDouble - conv _ = fail "Invalid TIF directory type" - - putP endianness = putP endianness . conv - where - conv :: IfdType -> Word16 - conv TypeByte = 1 - conv TypeAscii = 2 - conv TypeShort = 3 - conv TypeLong = 4 - conv TypeRational = 5 - conv TypeSByte = 6 - conv TypeUndefined = 7 - conv TypeSignedShort = 8 - conv TypeSignedLong = 9 - conv TypeSignedRational = 10 - conv TypeFloat = 11 - conv TypeDouble = 12 - -data TiffTag = TagPhotometricInterpretation - | TagCompression -- ^ Short type - | TagImageWidth -- ^ Short or long type - | TagImageLength -- ^ Short or long type - | TagXResolution -- ^ Rational type - | TagYResolution -- ^ Rational type - | TagResolutionUnit -- ^ Short type - | TagRowPerStrip -- ^ Short or long type - | TagStripByteCounts -- ^ Short or long - | TagStripOffsets -- ^ Short or long - | TagBitsPerSample -- ^ Short - | TagColorMap -- ^ Short - | TagTileWidth - | TagTileLength - | TagTileOffset - | TagTileByteCount - | TagSamplesPerPixel -- ^ Short - | TagArtist - | TagDocumentName - | TagSoftware - | TagPlanarConfiguration -- ^ Short - | TagOrientation - | TagSampleFormat -- ^ Short - | TagInkSet - | TagSubfileType - | TagFillOrder - | TagYCbCrCoeff - | TagYCbCrSubsampling - | TagYCbCrPositioning - | TagReferenceBlackWhite - | TagXPosition - | TagYPosition - | TagExtraSample - | TagImageDescription - - | TagJpegProc - | TagJPEGInterchangeFormat - | TagJPEGInterchangeFormatLength - | TagJPEGRestartInterval - | TagJPEGLosslessPredictors - | TagJPEGPointTransforms - | TagJPEGQTables - | TagJPEGDCTables - | TagJPEGACTables - - | TagUnknown Word16 - deriving (Eq, Show) - -tagOfWord16 :: Word16 -> TiffTag -tagOfWord16 = aux - where aux 255 = TagSubfileType - aux 256 = TagImageWidth - aux 257 = TagImageLength - aux 258 = TagBitsPerSample - aux 259 = TagCompression - aux 262 = TagPhotometricInterpretation - aux 266 = TagFillOrder - aux 269 = TagDocumentName - aux 270 = TagImageDescription - aux 273 = TagStripOffsets - aux 274 = TagOrientation - aux 277 = TagSamplesPerPixel - aux 278 = TagRowPerStrip - aux 279 = TagStripByteCounts - aux 282 = TagXResolution - aux 283 = TagYResolution - aux 284 = TagPlanarConfiguration - aux 286 = TagXPosition - aux 287 = TagYPosition - aux 296 = TagResolutionUnit - aux 305 = TagSoftware - aux 315 = TagArtist - aux 320 = TagColorMap - aux 322 = TagTileWidth - aux 323 = TagTileLength - aux 324 = TagTileOffset - aux 325 = TagTileByteCount - aux 332 = TagInkSet - aux 338 = TagExtraSample - aux 339 = TagSampleFormat - aux 529 = TagYCbCrCoeff - aux 512 = TagJpegProc - aux 513 = TagJPEGInterchangeFormat - aux 514 = TagJPEGInterchangeFormatLength - aux 515 = TagJPEGRestartInterval - aux 517 = TagJPEGLosslessPredictors - aux 518 = TagJPEGPointTransforms - aux 519 = TagJPEGQTables - aux 520 = TagJPEGDCTables - aux 521 = TagJPEGACTables - aux 530 = TagYCbCrSubsampling - aux 531 = TagYCbCrPositioning - aux 532 = TagReferenceBlackWhite - aux v = TagUnknown v - -word16OfTag :: TiffTag -> Word16 -word16OfTag = aux - where aux TagSubfileType = 255 - aux TagImageWidth = 256 - aux TagImageLength = 257 - aux TagBitsPerSample = 258 - aux TagCompression = 259 - aux TagPhotometricInterpretation = 262 - aux TagFillOrder = 266 - aux TagDocumentName = 269 - aux TagImageDescription = 270 - aux TagStripOffsets = 273 - aux TagOrientation = 274 - aux TagSamplesPerPixel = 277 - aux TagRowPerStrip = 278 - aux TagStripByteCounts = 279 - aux TagXResolution = 282 - aux TagYResolution = 283 - aux TagPlanarConfiguration = 284 - aux TagXPosition = 286 - aux TagYPosition = 287 - aux TagResolutionUnit = 296 - aux TagSoftware = 305 - aux TagArtist = 315 - aux TagColorMap = 320 - aux TagTileWidth = 322 - aux TagTileLength = 323 - aux TagTileOffset = 324 - aux TagTileByteCount = 325 - aux TagInkSet = 332 - aux TagExtraSample = 338 - aux TagSampleFormat = 339 - aux TagYCbCrCoeff = 529 - aux TagJpegProc = 512 - aux TagJPEGInterchangeFormat = 513 - aux TagJPEGInterchangeFormatLength = 514 - aux TagJPEGRestartInterval = 515 - aux TagJPEGLosslessPredictors = 517 - aux TagJPEGPointTransforms = 518 - aux TagJPEGQTables = 519 - aux TagJPEGDCTables = 520 - aux TagJPEGACTables = 521 - aux TagYCbCrSubsampling = 530 - aux TagYCbCrPositioning = 531 - aux TagReferenceBlackWhite = 532 - aux (TagUnknown v) = v - -instance BinaryParam Endianness TiffTag where - getP endianness = tagOfWord16 <$> getP endianness - putP endianness = putP endianness . word16OfTag - -data ExtendedDirectoryData = - ExtendedDataNone - | ExtendedDataAscii !B.ByteString - | ExtendedDataShort !(V.Vector Word16) - | ExtendedDataLong !(V.Vector Word32) - deriving (Eq, Show) - -instance BinaryParam (Endianness, ImageFileDirectory) ExtendedDirectoryData where - putP (endianness, _) = dump - where - dump ExtendedDataNone = pure () - dump (ExtendedDataAscii bstr) = putByteString bstr - dump (ExtendedDataShort shorts) = V.mapM_ (putP endianness) shorts - dump (ExtendedDataLong longs) = V.mapM_ (putP endianness) longs - - getP (endianness, ifd) = fetcher ifd - where - align ImageFileDirectory { ifdOffset = offset } = do - readed <- bytesRead - skip . fromIntegral $ fromIntegral offset - readed - - getE :: (BinaryParam Endianness a) => Get a - getE = getP endianness - - getVec count = V.replicateM (fromIntegral count) - - fetcher ImageFileDirectory { ifdType = TypeAscii, ifdCount = count } | count > 1 = - align ifd >> (ExtendedDataAscii <$> getByteString (fromIntegral count)) - fetcher ImageFileDirectory { ifdType = TypeShort, ifdCount = 2, ifdOffset = ofs } = - pure . ExtendedDataShort $ V.fromListN 2 valList - where high = fromIntegral $ ofs `unsafeShiftR` 16 - low = fromIntegral $ ofs .&. 0xFFFF - valList = case endianness of - EndianLittle -> [low, high] - EndianBig -> [high, low] - fetcher ImageFileDirectory { ifdType = TypeShort, ifdCount = count } | count > 2 = - align ifd >> (ExtendedDataShort <$> getVec count getE) - fetcher ImageFileDirectory { ifdType = TypeLong, ifdCount = count } | count > 1 = - align ifd >> (ExtendedDataLong <$> getVec count getE) - fetcher _ = pure ExtendedDataNone - -data TiffSampleFormat = - TiffSampleUint - | TiffSampleInt - | TiffSampleDouble - | TiffSampleUnknown - deriving Eq - -unpackSampleFormat :: Word32 -> Get TiffSampleFormat -unpackSampleFormat = aux - where - aux 1 = pure TiffSampleUint - aux 2 = pure TiffSampleInt - aux 3 = pure TiffSampleDouble - aux 4 = pure TiffSampleUnknown - aux v = fail $ "Undefined data format (" ++ show v ++ ")" - -data ImageFileDirectory = ImageFileDirectory - { ifdIdentifier :: !TiffTag - , ifdType :: !IfdType - , ifdCount :: !Word32 - , ifdOffset :: !Word32 - , ifdExtended :: !ExtendedDirectoryData - } - -unLong :: String -> ExtendedDirectoryData -> Get (V.Vector Word32) -unLong _ (ExtendedDataShort v) = pure $ V.map fromIntegral v -unLong _ (ExtendedDataLong v) = pure v -unLong errMessage _ = fail errMessage - -cleanImageFileDirectory :: Endianness -> ImageFileDirectory -> ImageFileDirectory -cleanImageFileDirectory EndianBig ifd@(ImageFileDirectory { ifdCount = 1 }) = aux $ ifdType ifd - where aux TypeShort = ifd { ifdOffset = ifdOffset ifd `unsafeShiftR` 16 } - aux _ = ifd -cleanImageFileDirectory _ ifd = ifd - -instance BinaryParam Endianness ImageFileDirectory where - getP endianness = - ImageFileDirectory <$> getE <*> getE <*> getE <*> getE - <*> pure ExtendedDataNone - where getE :: (BinaryParam Endianness a) => Get a - getE = getP endianness - - putP endianness ifd =do - let putE :: (BinaryParam Endianness a) => a -> Put - putE = putP endianness - putE $ ifdIdentifier ifd - putE $ ifdType ifd - putE $ ifdCount ifd - putE $ ifdOffset ifd - -instance BinaryParam Endianness [ImageFileDirectory] where - getP endianness = do - count <- getP endianness :: Get Word16 - rez <- replicateM (fromIntegral count) $ getP endianness - _ <- getP endianness :: Get Word32 - pure rez - - putP endianness lst = do - let count = fromIntegral $ length lst :: Word16 - putP endianness count - mapM_ (putP endianness) lst - putP endianness (0 :: Word32) - -fetchExtended :: Endianness -> [ImageFileDirectory] -> Get [ImageFileDirectory] -fetchExtended endian = mapM $ \ifd -> do - v <- getP (endian, ifd) - pure $ ifd { ifdExtended = v } - -findIFD :: String -> TiffTag -> [ImageFileDirectory] - -> Get ImageFileDirectory -findIFD errorMessage tag lst = - case [v | v <- lst, ifdIdentifier v == tag] of - [] -> fail errorMessage - (x:_) -> pure x - -findPalette :: [ImageFileDirectory] -> Get (Maybe (Image PixelRGB16)) -findPalette ifds = - case [v | v <- ifds, ifdIdentifier v == TagColorMap] of - (ImageFileDirectory { ifdExtended = ExtendedDataShort vec }:_) -> - pure . Just . Image pixelCount 1 $ VS.generate (V.length vec) axx - where pixelCount = V.length vec `div` 3 - axx v = vec `V.unsafeIndex` (idx + color * pixelCount) - where (idx, color) = v `divMod` 3 - - _ -> pure Nothing - -findIFDData :: String -> TiffTag -> [ImageFileDirectory] -> Get Word32 -findIFDData msg tag lst = ifdOffset <$> findIFD msg tag lst - -findIFDDefaultData :: Word32 -> TiffTag -> [ImageFileDirectory] -> Get Word32 -findIFDDefaultData d tag lst = - case [v | v <- lst, ifdIdentifier v == tag] of - [] -> pure d - (x:_) -> pure $ ifdOffset x - -findIFDExt :: String -> TiffTag -> [ImageFileDirectory] -> Get ExtendedDirectoryData -findIFDExt msg tag lst = do - val <- findIFD msg tag lst - case val of - ImageFileDirectory - { ifdCount = 1, ifdOffset = ofs, ifdType = TypeShort } -> - pure . ExtendedDataShort . V.singleton $ fromIntegral ofs - ImageFileDirectory - { ifdCount = 1, ifdOffset = ofs, ifdType = TypeLong } -> - pure . ExtendedDataLong . V.singleton $ fromIntegral ofs - ImageFileDirectory { ifdExtended = v } -> pure v - - -findIFDExtDefaultData :: [Word32] -> TiffTag -> [ImageFileDirectory] - -> Get [Word32] -findIFDExtDefaultData d tag lst = - case [v | v <- lst, ifdIdentifier v == tag] of - [] -> pure d - (ImageFileDirectory { ifdExtended = ExtendedDataNone }:_) -> return d - (x:_) -> V.toList <$> unLong errorMessage (ifdExtended x) - where errorMessage = - "Can't parse tag " ++ show tag ++ " " ++ show (ifdExtended x) - --- It's temporary, remove once tiff decoding is better --- handled. -{- -instance Show (Image PixelRGB16) where - show _ = "Image PixelRGB16" --} - -data TiffInfo = TiffInfo - { tiffHeader :: TiffHeader - , tiffWidth :: Word32 - , tiffHeight :: Word32 - , tiffColorspace :: TiffColorspace - , tiffSampleCount :: Word32 - , tiffRowPerStrip :: Word32 - , tiffPlaneConfiguration :: TiffPlanarConfiguration - , tiffSampleFormat :: [TiffSampleFormat] - , tiffBitsPerSample :: V.Vector Word32 - , tiffCompression :: TiffCompression - , tiffStripSize :: V.Vector Word32 - , tiffOffsets :: V.Vector Word32 - , tiffPalette :: Maybe (Image PixelRGB16) - , tiffYCbCrSubsampling :: V.Vector Word32 - } - -data TiffColorspace = - TiffMonochromeWhite0 -- ^ 0 - | TiffMonochrome -- ^ 1 - | TiffRGB -- ^ 2 - | TiffPaleted -- ^ 3 - | TiffTransparencyMask -- ^ 4 - | TiffCMYK -- ^ 5 - | TiffYCbCr -- ^ 6 - | TiffCIELab -- ^ 8 - -packPhotometricInterpretation :: TiffColorspace -> Word16 -packPhotometricInterpretation = aux - where - aux TiffMonochromeWhite0 = 0 - aux TiffMonochrome = 1 - aux TiffRGB = 2 - aux TiffPaleted = 3 - aux TiffTransparencyMask = 4 - aux TiffCMYK = 5 - aux TiffYCbCr = 6 - aux TiffCIELab = 8 - -unpackPhotometricInterpretation :: Word32 -> Get TiffColorspace -unpackPhotometricInterpretation = aux - where aux 0 = pure TiffMonochromeWhite0 - aux 1 = pure TiffMonochrome - aux 2 = pure TiffRGB - aux 3 = pure TiffPaleted - aux 4 = pure TiffTransparencyMask - aux 5 = pure TiffCMYK - aux 6 = pure TiffYCbCr - aux 8 = pure TiffCIELab - aux v = fail $ "Unrecognized color space " ++ show v - -unPackCompression :: Word32 -> Get TiffCompression -unPackCompression = aux - where - aux 0 = pure CompressionNone - aux 1 = pure CompressionNone - aux 2 = pure CompressionModifiedRLE - aux 5 = pure CompressionLZW - aux 6 = pure CompressionJPEG - aux 32773 = pure CompressionPackBit - aux v = fail $ "Unknown compression scheme " ++ show v - -packCompression :: TiffCompression -> Word16 -packCompression = aux - where - aux CompressionNone = 1 - aux CompressionModifiedRLE = 2 - aux CompressionLZW = 5 - aux CompressionJPEG = 6 - aux CompressionPackBit = 32773 - -copyByteString :: B.ByteString -> M.STVector s Word8 -> Int -> Int -> (Word32, Word32) - -> ST s Int -copyByteString str vec stride startWrite (from, count) = inner startWrite fromi - where fromi = fromIntegral from - maxi = fromi + fromIntegral count - - inner writeIdx i | i >= maxi = pure writeIdx - inner writeIdx i = do - let v = str `BU.unsafeIndex` i - (vec `M.unsafeWrite` writeIdx) v - inner (writeIdx + stride) $ i + 1 - -unpackPackBit :: B.ByteString -> M.STVector s Word8 -> Int -> Int - -> (Word32, Word32) - -> ST s Int -unpackPackBit str outVec stride writeIndex (offset, size) = loop fromi writeIndex - where fromi = fromIntegral offset - maxi = fromi + fromIntegral size - - replicateByte writeIdx _ 0 = pure writeIdx - replicateByte writeIdx v count = do - (outVec `M.unsafeWrite` writeIdx) v - replicateByte (writeIdx + stride) v $ count - 1 - - loop i writeIdx | i >= maxi = pure writeIdx - loop i writeIdx = choice - {-where v = fromIntegral (str `BU.unsafeIndex` i) :: Int8-} - where v = fromIntegral (str `B.index` i) :: Int8 - - choice - -- data - | 0 <= v = do - copyByteString str outVec stride writeIdx - (fromIntegral $ i + 1, fromIntegral v + 1) - >>= loop (i + 2 + fromIntegral v) - -- run - | -127 <= v = do - {-let nextByte = str `BU.unsafeIndex` (i + 1)-} - let nextByte = str `B.index` (i + 1) - count = negate (fromIntegral v) + 1 :: Int - replicateByte writeIdx nextByte count - >>= loop (i + 2) - - -- noop - | otherwise = loop writeIdx $ i + 1 - -uncompressAt :: TiffCompression - -> B.ByteString -> M.STVector s Word8 -> Int -> Int -> (Word32, Word32) - -> ST s Int -uncompressAt CompressionNone = copyByteString -uncompressAt CompressionPackBit = unpackPackBit -uncompressAt CompressionLZW = \str outVec _stride writeIndex (offset, size) -> do - let toDecode = B.take (fromIntegral size) $ B.drop (fromIntegral offset) str - runBoolReader $ decodeLzwTiff toDecode outVec writeIndex - return 0 -uncompressAt _ = error "Unhandled compression" - -class Unpackable a where - type StorageType a :: * - - outAlloc :: a -> Int -> ST s (M.STVector s (StorageType a)) - - -- | Final image and size, return offset and vector - allocTempBuffer :: a -> M.STVector s (StorageType a) -> Int - -> ST s (M.STVector s Word8) - - offsetStride :: a -> Int -> Int -> (Int, Int) - - mergeBackTempBuffer :: a -- ^ Type witness, just for the type checker. - -> Endianness - -> M.STVector s Word8 -- ^ Temporary buffer handling decompression. - -> Int -- ^ Line size in pixels - -> Int -- ^ Write index, in bytes - -> Word32 -- ^ size, in bytes - -> Int -- ^ Stride - -> M.STVector s (StorageType a) -- ^ Final buffer - -> ST s () - --- | The Word8 instance is just a passthrough, to avoid --- copying memory twice -instance Unpackable Word8 where - type StorageType Word8 = Word8 - - offsetStride _ i stride = (i, stride) - allocTempBuffer _ buff _ = pure buff - mergeBackTempBuffer _ _ _ _ _ _ _ _ = pure () - outAlloc _ count = M.replicate count 0 -- M.new - -instance Unpackable Word16 where - type StorageType Word16 = Word16 - - offsetStride _ _ _ = (0, 1) - outAlloc _ = M.new - allocTempBuffer _ _ s = M.new $ s * 2 - mergeBackTempBuffer _ EndianLittle tempVec _ index size stride outVec = - looperLe index 0 - where looperLe _ readIndex | readIndex >= fromIntegral size = pure () - looperLe writeIndex readIndex = do - v1 <- tempVec `M.read` readIndex - v2 <- tempVec `M.read` (readIndex + 1) - let finalValue = - (fromIntegral v2 `unsafeShiftL` 8) .|. fromIntegral v1 - (outVec `M.write` writeIndex) finalValue - - looperLe (writeIndex + stride) (readIndex + 2) - mergeBackTempBuffer _ EndianBig tempVec _ index size stride outVec = - looperBe index 0 - where looperBe _ readIndex | readIndex >= fromIntegral size = pure () - looperBe writeIndex readIndex = do - v1 <- tempVec `M.read` readIndex - v2 <- tempVec `M.read` (readIndex + 1) - let finalValue = - (fromIntegral v1 `unsafeShiftL` 8) .|. fromIntegral v2 - (outVec `M.write` writeIndex) finalValue - - looperBe (writeIndex + stride) (readIndex + 2) - -instance Unpackable Word32 where - type StorageType Word32 = Word32 - - offsetStride _ _ _ = (0, 1) - outAlloc _ = M.new - allocTempBuffer _ _ s = M.new $ s * 4 - mergeBackTempBuffer _ EndianLittle tempVec _ index size stride outVec = - looperLe index 0 - where looperLe _ readIndex | readIndex >= fromIntegral size = pure () - looperLe writeIndex readIndex = do - v1 <- tempVec `M.read` readIndex - v2 <- tempVec `M.read` (readIndex + 1) - v3 <- tempVec `M.read` (readIndex + 2) - v4 <- tempVec `M.read` (readIndex + 3) - let finalValue = - (fromIntegral v4 `unsafeShiftL` 24) .|. - (fromIntegral v3 `unsafeShiftL` 16) .|. - (fromIntegral v2 `unsafeShiftL` 8) .|. - fromIntegral v1 - (outVec `M.write` writeIndex) finalValue - - looperLe (writeIndex + stride) (readIndex + 4) - mergeBackTempBuffer _ EndianBig tempVec _ index size stride outVec = - looperBe index 0 - where looperBe _ readIndex | readIndex >= fromIntegral size = pure () - looperBe writeIndex readIndex = do - v1 <- tempVec `M.read` readIndex - v2 <- tempVec `M.read` (readIndex + 1) - v3 <- tempVec `M.read` (readIndex + 2) - v4 <- tempVec `M.read` (readIndex + 3) - let finalValue = - (fromIntegral v1 `unsafeShiftL` 24) .|. - (fromIntegral v2 `unsafeShiftL` 16) .|. - (fromIntegral v3 `unsafeShiftL` 8) .|. - fromIntegral v4 - (outVec `M.write` writeIndex) finalValue - - looperBe (writeIndex + stride) (readIndex + 4) - -data Pack4 = Pack4 - -instance Unpackable Pack4 where - type StorageType Pack4 = Word8 - allocTempBuffer _ _ = M.new - offsetStride _ _ _ = (0, 1) - outAlloc _ = M.new - mergeBackTempBuffer _ _ tempVec lineSize index size stride outVec = - inner 0 index pxCount - where pxCount = lineSize `div` stride - - maxWrite = M.length outVec - inner readIdx writeIdx _ - | readIdx >= fromIntegral size || writeIdx >= maxWrite = pure () - inner readIdx writeIdx line - | line <= 0 = inner readIdx (writeIdx + line * stride) pxCount - inner readIdx writeIdx line = do - v <- tempVec `M.read` readIdx - let high = (v `unsafeShiftR` 4) .&. 0xF - low = v .&. 0xF - (outVec `M.write` writeIdx) high - when (writeIdx + stride < maxWrite) $ - (outVec `M.write` (writeIdx + stride)) low - - inner (readIdx + 1) (writeIdx + 2 * stride) (line - 2) - -data Pack2 = Pack2 - -instance Unpackable Pack2 where - type StorageType Pack2 = Word8 - allocTempBuffer _ _ = M.new - offsetStride _ _ _ = (0, 1) - outAlloc _ = M.new - mergeBackTempBuffer _ _ tempVec lineSize index size stride outVec = - inner 0 index pxCount - where pxCount = lineSize `div` stride - - maxWrite = M.length outVec - inner readIdx writeIdx _ - | readIdx >= fromIntegral size || writeIdx >= maxWrite = pure () - inner readIdx writeIdx line - | line <= 0 = inner readIdx (writeIdx + line * stride) pxCount - inner readIdx writeIdx line = do - v <- tempVec `M.read` readIdx - let v0 = (v `unsafeShiftR` 6) .&. 0x3 - v1 = (v `unsafeShiftR` 4) .&. 0x3 - v2 = (v `unsafeShiftR` 2) .&. 0x3 - v3 = v .&. 0x3 - - (outVec `M.write` writeIdx) v0 - when (writeIdx + 1 * stride < maxWrite) $ - (outVec `M.write` (writeIdx + stride)) v1 - - when (writeIdx + 2 * stride < maxWrite) $ - (outVec `M.write` (writeIdx + stride * 2)) v2 - - when (writeIdx + 3 * stride < maxWrite) $ - (outVec `M.write` (writeIdx + stride * 3)) v3 - - inner (readIdx + 1) (writeIdx + 4 * stride) (line - 4) - -data Pack12 = Pack12 - -instance Unpackable Pack12 where - type StorageType Pack12 = Word16 - allocTempBuffer _ _ = M.new - offsetStride _ _ _ = (0, 1) - outAlloc _ = M.new - mergeBackTempBuffer _ _ tempVec lineSize index size stride outVec = - inner 0 index pxCount - where pxCount = lineSize `div` stride - - maxWrite = M.length outVec - inner readIdx writeIdx _ - | readIdx >= fromIntegral size || writeIdx >= maxWrite = pure () - inner readIdx writeIdx line - | line <= 0 = inner readIdx (writeIdx + line * stride) pxCount - inner readIdx writeIdx line = do - v0 <- tempVec `M.read` readIdx - v1 <- if readIdx + 1 < fromIntegral size - then tempVec `M.read` (readIdx + 1) - else pure 0 - v2 <- if readIdx + 2 < fromIntegral size - then tempVec `M.read` (readIdx + 2) - else pure 0 - - let high0 = fromIntegral v0 `unsafeShiftL` 4 - low0 = (fromIntegral v1 `unsafeShiftR` 4) .&. 0xF - - p0 = high0 .|. low0 - - high1 = (fromIntegral v1 .&. 0xF) `unsafeShiftL` 8 - low1 = fromIntegral v2 - p1 = high1 .|. low1 - - (outVec `M.write` writeIdx) p0 - when (writeIdx + 1 * stride < maxWrite) $ - (outVec `M.write` (writeIdx + stride)) p1 - - inner (readIdx + 3) (writeIdx + 2 * stride) (line - 2) - -data YCbCrSubsampling = YCbCrSubsampling - { ycbcrWidth :: !Int - , ycbcrHeight :: !Int - , ycbcrImageWidth :: !Int - , ycbcrStripHeight :: !Int - } - -instance Unpackable YCbCrSubsampling where - type StorageType YCbCrSubsampling = Word8 - - offsetStride _ _ _ = (0, 1) - outAlloc _ = M.new - allocTempBuffer _ _ = M.new - mergeBackTempBuffer subSampling _ tempVec _ index size _ outVec = - foldM_ unpacker 0 [(bx, by) | by <- [0, h .. lineCount - 1] - , bx <- [0, w .. imgWidth - 1]] - where w = ycbcrWidth subSampling - h = ycbcrHeight subSampling - imgWidth = ycbcrImageWidth subSampling - lineCount = ycbcrStripHeight subSampling - - lumaCount = w * h - blockSize = lumaCount + 2 - - maxOut = M.length outVec - - unpacker readIdx _ | readIdx >= fromIntegral size * 3 = pure readIdx - unpacker readIdx (bx, by) = do - cb <- tempVec `M.read` (readIdx + lumaCount) - cr <- tempVec `M.read` (readIdx + lumaCount + 1) - - let pixelIndices = - [index + ((by + y) * imgWidth + bx + x) * 3 | y <- [0 .. h - 1], x <- [0 .. w - 1]] - - writer readIndex writeIdx | writeIdx + 3 > maxOut = pure readIndex - writer readIndex writeIdx = do - y <- tempVec `M.read` readIndex - (outVec `M.write` writeIdx) y - (outVec `M.write` (writeIdx + 1)) cb - (outVec `M.write` (writeIdx + 2)) cr - return $ readIndex + 1 - - foldM_ writer readIdx pixelIndices - - return $ readIdx + blockSize - -gatherStrips :: ( Unpackable comp - , Pixel pixel - , StorageType comp ~ PixelBaseComponent pixel - ) - => comp -> B.ByteString -> TiffInfo -> Image pixel -gatherStrips comp str nfo = runST $ do - let width = fromIntegral $ tiffWidth nfo - height = fromIntegral $ tiffHeight nfo - sampleCount = if tiffSampleCount nfo /= 0 - then fromIntegral $ tiffSampleCount nfo - else V.length $ tiffBitsPerSample nfo - - rowPerStrip = fromIntegral $ tiffRowPerStrip nfo - endianness = hdrEndianness $ tiffHeader nfo - - stripCount = V.length $ tiffOffsets nfo - compression = tiffCompression nfo - - outVec <- outAlloc comp $ width * height * sampleCount - tempVec <- allocTempBuffer comp outVec - (rowPerStrip * width * sampleCount) - - let mutableImage = MutableImage - { mutableImageWidth = fromIntegral width - , mutableImageHeight = fromIntegral height - , mutableImageData = outVec - } - - case tiffPlaneConfiguration nfo of - PlanarConfigContig -> V.mapM_ unpacker sizes - where unpacker (idx, offset, size) = do - let (writeIdx, tempStride) = offsetStride comp idx 1 - _ <- uncompressAt compression str tempVec tempStride - writeIdx (offset, size) - mergeBackTempBuffer comp endianness tempVec (width * sampleCount) - idx size 1 outVec - - startWriteOffset = - V.generate stripCount(width * rowPerStrip * sampleCount *) - - sizes = V.zip3 startWriteOffset (tiffOffsets nfo) (tiffStripSize nfo) - - PlanarConfigSeparate -> V.mapM_ unpacker sizes - where unpacker (idx, offset, size) = do - let (writeIdx, tempStride) = offsetStride comp idx stride - _ <- uncompressAt compression str tempVec tempStride - writeIdx (offset, size) - mergeBackTempBuffer comp endianness tempVec (width * sampleCount) - idx size stride outVec - - stride = V.length $ tiffOffsets nfo - idxVector = V.enumFromN 0 stride - sizes = V.zip3 idxVector (tiffOffsets nfo) (tiffStripSize nfo) - - unsafeFreezeImage mutableImage - -ifdSingleLong :: TiffTag -> Word32 -> Writer [ImageFileDirectory] () -ifdSingleLong tag = ifdMultiLong tag . V.singleton - -ifdSingleShort :: Endianness -> TiffTag -> Word16 - -> Writer [ImageFileDirectory] () -ifdSingleShort endian tag = ifdMultiShort endian tag . V.singleton . fromIntegral - -ifdMultiLong :: TiffTag -> V.Vector Word32 -> Writer [ImageFileDirectory] () -ifdMultiLong tag v = tell . pure $ ImageFileDirectory - { ifdIdentifier = tag - , ifdType = TypeLong - , ifdCount = fromIntegral $ V.length v - , ifdOffset = offset - , ifdExtended = extended - } - where (offset, extended) - | V.length v > 1 = (0, ExtendedDataLong v) - | otherwise = (V.head v, ExtendedDataNone) - -ifdMultiShort :: Endianness -> TiffTag -> V.Vector Word32 - -> Writer [ImageFileDirectory] () -ifdMultiShort endian tag v = tell . pure $ ImageFileDirectory - { ifdIdentifier = tag - , ifdType = TypeShort - , ifdCount = size - , ifdOffset = offset - , ifdExtended = extended - } - where size = fromIntegral $ V.length v - (offset, extended) - | size > 2 = (0, ExtendedDataShort $ V.map fromIntegral v) - | size == 2 = - let v1 = fromIntegral $ V.head v - v2 = fromIntegral $ v `V.unsafeIndex` 1 - in - case endian of - EndianLittle -> (v2 `unsafeShiftL` 16 .|. v1, ExtendedDataNone) - EndianBig -> (v1 `unsafeShiftL` 16 .|. v2, ExtendedDataNone) - - | otherwise = case endian of - EndianLittle -> (V.head v, ExtendedDataNone) - EndianBig -> (V.head v `unsafeShiftL` 16, ExtendedDataNone) - --- | All the IFD must be written in order according to the tag --- value of the IFD. To avoid getting to much restriction in the --- serialization code, just sort it. -orderIfdByTag :: [ImageFileDirectory] -> [ImageFileDirectory] -orderIfdByTag = sortBy comparer - where comparer a b = compare t1 t2 - where t1 = word16OfTag $ ifdIdentifier a - t2 = word16OfTag $ ifdIdentifier b - --- | Given an official offset and a list of IFD, update the offset information --- of the IFD with extended data. -setupIfdOffsets :: Word32 -> [ImageFileDirectory] -> [ImageFileDirectory] -setupIfdOffsets initialOffset lst = snd $ mapAccumL updater startExtended lst - where ifdElementCount = fromIntegral $ length lst - ifdSize = 12 - ifdCountSize = 2 - nextOffsetSize = 4 - startExtended = initialOffset - + ifdElementCount * ifdSize - + ifdCountSize + nextOffsetSize - - updater ix ifd@(ImageFileDirectory { ifdExtended = ExtendedDataAscii b }) = - (ix + fromIntegral (B.length b), ifd { ifdOffset = ix } ) - updater ix ifd@(ImageFileDirectory { ifdExtended = ExtendedDataLong v }) - | V.length v > 1 = ( ix + fromIntegral (V.length v * 4) - , ifd { ifdOffset = ix } ) - updater ix ifd@(ImageFileDirectory { ifdExtended = ExtendedDataShort v }) - | V.length v > 2 = ( ix + fromIntegral (V.length v * 2) - , ifd { ifdOffset = ix }) - updater ix ifd = (ix, ifd) - -instance BinaryParam B.ByteString TiffInfo where - putP rawData nfo = do - put $ tiffHeader nfo - - let ifdStartOffset = hdrOffset $ tiffHeader nfo - endianness = hdrEndianness $ tiffHeader nfo - - ifdShort = ifdSingleShort endianness - ifdShorts = ifdMultiShort endianness - list = setupIfdOffsets ifdStartOffset . orderIfdByTag . execWriter $ do - ifdSingleLong TagImageWidth $ tiffWidth nfo - ifdSingleLong TagImageLength $ tiffHeight nfo - ifdShorts TagBitsPerSample $ tiffBitsPerSample nfo - ifdSingleLong TagSamplesPerPixel $ tiffSampleCount nfo - ifdSingleLong TagRowPerStrip $ tiffRowPerStrip nfo - ifdShort TagPhotometricInterpretation - . packPhotometricInterpretation - $ tiffColorspace nfo - ifdShort TagPlanarConfiguration - . constantToPlaneConfiguration $ tiffPlaneConfiguration nfo - ifdShort TagCompression . packCompression - $ tiffCompression nfo - ifdMultiLong TagStripOffsets $ tiffOffsets nfo - - ifdMultiLong TagStripByteCounts $ tiffStripSize nfo - - let subSampling = tiffYCbCrSubsampling nfo - when (not $ V.null subSampling) $ - ifdShorts TagYCbCrSubsampling subSampling - - putByteString rawData - putP endianness list - mapM_ (\ifd -> putP (endianness, ifd) $ ifdExtended ifd) list - - getP _ = do - hdr <- get - readed <- bytesRead - skip . fromIntegral $ fromIntegral (hdrOffset hdr) - readed - let endian = hdrEndianness hdr - - ifd <- fmap (cleanImageFileDirectory endian) <$> getP endian - cleaned <- fetchExtended endian ifd - - let dataFind str tag = findIFDData str tag cleaned - dataDefault def tag = findIFDDefaultData def tag cleaned - extFind str tag = findIFDExt str tag cleaned - extDefault def tag = findIFDExtDefaultData def tag cleaned - - TiffInfo hdr - <$> dataFind "Can't find width" TagImageWidth - <*> dataFind "Can't find height" TagImageLength - <*> (dataFind "Can't find color space" TagPhotometricInterpretation - >>= unpackPhotometricInterpretation) - <*> dataFind "Can't find sample per pixel" TagSamplesPerPixel - <*> dataFind "Can't find row per strip" TagRowPerStrip - <*> (dataDefault 1 TagPlanarConfiguration - >>= planarConfgOfConstant) - <*> (extDefault [1] TagSampleFormat - >>= mapM unpackSampleFormat) - <*> (extFind "Can't find bit per sample" TagBitsPerSample - >>= unLong "Can't find bit depth") - <*> (dataFind "Can't find Compression" TagCompression - >>= unPackCompression) - <*> (extFind "Can't find byte counts" TagStripByteCounts - >>= unLong "Can't find bit per sample") - <*> (extFind "Strip offsets missing" TagStripOffsets - >>= unLong "Can't find strip offsets") - <*> findPalette cleaned - <*> (V.fromList <$> extDefault [2, 2] TagYCbCrSubsampling) - -unpack :: B.ByteString -> TiffInfo -> Either String DynamicImage --- | while mandatory some images don't put correct --- rowperstrip. So replacing 0 with actual image height. -unpack file nfo@TiffInfo { tiffRowPerStrip = 0 } = - unpack file $ nfo { tiffRowPerStrip = tiffHeight nfo } -unpack file nfo@TiffInfo { tiffColorspace = TiffPaleted - , tiffBitsPerSample = lst - , tiffSampleFormat = format - , tiffPalette = Just p - } - | lst == V.singleton 8 && format == [TiffSampleUint] = - let applyPalette = pixelMap (\v -> pixelAt p (fromIntegral v) 0) - gathered :: Image Pixel8 - gathered = gatherStrips (0 :: Word8) file nfo - in - pure . ImageRGB16 $ applyPalette gathered - - | lst == V.singleton 4 && format == [TiffSampleUint] = - let applyPalette = pixelMap (\v -> pixelAt p (fromIntegral v) 0) - gathered :: Image Pixel8 - gathered = gatherStrips Pack4 file nfo - in - pure . ImageRGB16 $ applyPalette gathered - - | lst == V.singleton 2 && format == [TiffSampleUint] = - let applyPalette = pixelMap (\v -> pixelAt p (fromIntegral v) 0) - gathered :: Image Pixel8 - gathered = gatherStrips Pack2 file nfo - in - pure . ImageRGB16 $ applyPalette gathered - -unpack file nfo@TiffInfo { tiffColorspace = TiffCMYK - , tiffBitsPerSample = lst - , tiffSampleFormat = format } - | lst == V.fromList [8, 8, 8, 8] && all (TiffSampleUint ==) format = - pure . ImageCMYK8 $ gatherStrips (0 :: Word8) file nfo - - | lst == V.fromList [16, 16, 16, 16] && all (TiffSampleUint ==) format = - pure . ImageCMYK16 $ gatherStrips (0 :: Word16) file nfo - -unpack file nfo@TiffInfo { tiffColorspace = TiffMonochromeWhite0 } - | otherwise = do - img <- unpack file (nfo { tiffColorspace = TiffMonochrome }) - case img of - ImageY8 i -> pure . ImageY8 $ pixelMap (maxBound -) i - ImageY16 i -> pure . ImageY16 $ pixelMap (maxBound -) i - _ -> pure img - -unpack file nfo@TiffInfo { tiffColorspace = TiffMonochrome - , tiffBitsPerSample = lst - , tiffSampleFormat = format } - | lst == V.singleton 2 && all (TiffSampleUint ==) format = - pure . ImageY8 . pixelMap (colorMap (64 *)) $ gatherStrips Pack2 file nfo - | lst == V.singleton 4 && all (TiffSampleUint ==) format = - pure . ImageY8 . pixelMap (colorMap (16 *)) $ gatherStrips Pack4 file nfo - | lst == V.singleton 8 && all (TiffSampleUint ==) format = - pure . ImageY8 $ gatherStrips (0 :: Word8) file nfo - | lst == V.singleton 12 && all (TiffSampleUint ==) format = - pure . ImageY16 . pixelMap (16 *) $ gatherStrips Pack12 file nfo - | lst == V.singleton 16 && all (TiffSampleUint ==) format = - pure . ImageY16 $ gatherStrips (0 :: Word16) file nfo - | lst == V.singleton 32 && all (TiffSampleUint ==) format = - pure . ImageY16 $ pixelMap (toWord16) img - where toWord16 v = fromIntegral $ v `unsafeShiftR` 16 - img = gatherStrips (0 :: Word32) file nfo :: Image Pixel32 - -unpack file nfo@TiffInfo { tiffColorspace = TiffYCbCr - , tiffBitsPerSample = lst - , tiffPlaneConfiguration = PlanarConfigContig - , tiffSampleFormat = format } - | lst == V.fromList [8, 8, 8] && all (TiffSampleUint ==) format = - pure . ImageYCbCr8 $ gatherStrips cbcrConf file nfo - where defaulting 0 = 2 - defaulting n = n - - w = defaulting $ tiffYCbCrSubsampling nfo V.! 0 - h = defaulting $ tiffYCbCrSubsampling nfo V.! 1 - cbcrConf = YCbCrSubsampling - { ycbcrWidth = fromIntegral w - , ycbcrHeight = fromIntegral h - , ycbcrImageWidth = fromIntegral $ tiffWidth nfo - , ycbcrStripHeight = fromIntegral $ tiffRowPerStrip nfo - } - -unpack file nfo@TiffInfo { tiffColorspace = TiffRGB - , tiffBitsPerSample = lst - , tiffSampleFormat = format } - | lst == V.fromList [2, 2, 2] && all (TiffSampleUint ==) format = - pure . ImageRGB8 . pixelMap (colorMap (64 *)) $ gatherStrips Pack2 file nfo - | lst == V.fromList [4, 4, 4] && all (TiffSampleUint ==) format = - pure . ImageRGB8 . pixelMap (colorMap (16 *)) $ gatherStrips Pack4 file nfo - | lst == V.fromList [8, 8, 8] && all (TiffSampleUint ==) format = - pure . ImageRGB8 $ gatherStrips (0 :: Word8) file nfo - | lst == V.fromList [8, 8, 8, 8] && all (TiffSampleUint ==) format = - pure . ImageRGBA8 $ gatherStrips (0 :: Word8) file nfo - | lst == V.fromList [16, 16, 16] && all (TiffSampleUint ==) format = - pure . ImageRGB16 $ gatherStrips (0 :: Word16) file nfo - | lst == V.fromList [16, 16, 16, 16] && all (TiffSampleUint ==) format = - pure . ImageRGBA16 $ gatherStrips (0 :: Word16) file nfo -unpack file nfo@TiffInfo { tiffColorspace = TiffMonochrome - , tiffBitsPerSample = lst - , tiffSampleFormat = format } - -- some files are a little bit borked... - | lst == V.fromList [8, 8, 8] && all (TiffSampleUint ==) format = - pure . ImageRGB8 $ gatherStrips (0 :: Word8) file nfo - -unpack _ _ = fail "Failure to unpack TIFF file" - --- | Transform a raw tiff image to an image, without modifying --- the underlying pixel type. --- --- This function can output the following pixel types : --- --- * PixelY8 --- --- * PixelY16 --- --- * PixelRGB8 --- --- * PixelRGB16 --- --- * PixelCMYK8 --- --- * PixelCMYK16 --- -decodeTiff :: B.ByteString -> Either String DynamicImage -decodeTiff file = runGetStrict (getP file) file >>= unpack file - --- | Class defining which pixel types can be serialized in a --- Tiff file. -class (Pixel px) => TiffSaveable px where - colorSpaceOfPixel :: px -> TiffColorspace - - subSamplingInfo :: px -> V.Vector Word32 - subSamplingInfo _ = V.empty - -instance TiffSaveable Pixel8 where - colorSpaceOfPixel _ = TiffMonochrome - -instance TiffSaveable Pixel16 where - colorSpaceOfPixel _ = TiffMonochrome - -instance TiffSaveable PixelCMYK8 where - colorSpaceOfPixel _ = TiffCMYK - -instance TiffSaveable PixelCMYK16 where - colorSpaceOfPixel _ = TiffCMYK - -instance TiffSaveable PixelRGB8 where - colorSpaceOfPixel _ = TiffRGB - -instance TiffSaveable PixelRGB16 where - colorSpaceOfPixel _ = TiffRGB - -instance TiffSaveable PixelRGBA8 where - colorSpaceOfPixel _ = TiffRGB - -instance TiffSaveable PixelRGBA16 where - colorSpaceOfPixel _ = TiffRGB - -instance TiffSaveable PixelYCbCr8 where - colorSpaceOfPixel _ = TiffYCbCr - subSamplingInfo _ = V.fromListN 2 [1, 1] - --- | Transform an image into a Tiff encoded bytestring, reade to be --- written as a file. -encodeTiff :: forall px. (TiffSaveable px) => Image px -> Lb.ByteString -encodeTiff img = runPut $ putP rawPixelData hdr - where intSampleCount = componentCount (undefined :: px) - sampleCount = fromIntegral intSampleCount - - sampleType = (undefined :: PixelBaseComponent px) - pixelData = imageData img - - rawPixelData = toByteString pixelData - width = fromIntegral $ imageWidth img - height = fromIntegral $ imageHeight img - intSampleSize = sizeOf sampleType - sampleSize = fromIntegral intSampleSize - bitPerSample = sampleSize * 8 - imageSize = width * height * sampleCount * sampleSize - headerSize = 8 - - hdr = TiffInfo - { tiffHeader = TiffHeader - { hdrEndianness = EndianLittle - , hdrOffset = headerSize + imageSize - } - , tiffWidth = width - , tiffHeight = height - , tiffColorspace = colorSpaceOfPixel (undefined :: px) - , tiffSampleCount = fromIntegral $ sampleCount - , tiffRowPerStrip = fromIntegral $ imageHeight img - , tiffPlaneConfiguration = PlanarConfigContig - , tiffSampleFormat = [TiffSampleUint] - , tiffBitsPerSample = V.replicate intSampleCount bitPerSample - , tiffCompression = CompressionNone - , tiffStripSize = V.singleton imageSize - , tiffOffsets = V.singleton headerSize - , tiffPalette = Nothing - , tiffYCbCrSubsampling = subSamplingInfo (undefined :: px) - } - --- | Helper function to directly write an image as a tiff on disk. -writeTiff :: (TiffSaveable pixel) => FilePath -> Image pixel -> IO () -writeTiff path img = Lb.writeFile path $ encodeTiff img -
− Codec/Picture/Types.hs
@@ -1,1727 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeSynonymInstances #-} -{-# LANGUAGE FunctionalDependencies #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE Rank2Types #-} -{-# LANGUAGE CPP #-} --- | 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( .. ) - - -- ** Image functions - , freezeImage - , unsafeFreezeImage - - -- ** Pixel types - , Pixel8 - , Pixel16 - , Pixel32 - , PixelF - , PixelYA8( .. ) - , PixelYA16( .. ) - , PixelRGB8( .. ) - , PixelRGB16( .. ) - , PixelRGBF( .. ) - , PixelRGBA8( .. ) - , PixelRGBA16( .. ) - , PixelCMYK8( .. ) - , PixelCMYK16( .. ) - , PixelYCbCr8( .. ) - - -- * Type classes - , ColorConvertible( .. ) - , Pixel(..) - -- $graph - , ColorSpaceConvertible( .. ) - , LumaPlaneExtractable( .. ) - , TransparentPixel( .. ) - - -- * Helper functions - , pixelMap - , pixelFold - , dynamicMap - , dynamicPixelMap - , dropAlphaLayer - , withImage - , generateImage - , generateFoldImage - , gammaCorrection - , toneMapping - - -- * Color plane extraction - , ColorPlane ( ) - - , PlaneRed( .. ) - , PlaneGreen( .. ) - , PlaneBlue( .. ) - , PlaneAlpha( .. ) - , PlaneLuma( .. ) - , PlaneCr( .. ) - , PlaneCb( .. ) - , PlaneCyan( .. ) - , PlaneMagenta( .. ) - , PlaneYellow( .. ) - , PlaneBlack( .. ) - - , extractComponent - , unsafeExtractComponent - ) where - -import Control.Monad( forM_, foldM, liftM, ap ) -import Control.DeepSeq( NFData( .. ) ) -import Control.Monad.ST( runST ) -import Control.Monad.Primitive ( PrimMonad, PrimState ) -import Foreign.Storable ( Storable ) -import Data.Bits( unsafeShiftL, unsafeShiftR ) -import Data.Word( Word8, Word16, Word32 ) -import Data.List( foldl' ) -import Data.Vector.Storable ( (!) ) -import qualified Data.Vector.Storable as V -import qualified Data.Vector.Storable.Mutable as M - -#include "ConvGraph.hs" - --- | 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 :: V.Vector (PixelBaseComponent a) - } - -{-# INLINE (!!!) #-} -(!!!) :: (Storable e) => V.Vector e -> Int -> e -(!!!) = V.unsafeIndex - --- | Class used to describle plane present in the pixel --- type. If a pixel has a plane description associated, --- you can use the plane name to extract planes independently. -class ColorPlane pixel planeToken where - -- | Retrieve the index of the component in the - -- given pixel type. - toComponentIndex :: pixel -> planeToken -> Int - --- | Define the plane for the red color component -data PlaneRed = PlaneRed - --- | Define the plane for the green color component -data PlaneGreen = PlaneGreen - --- | Define the plane for the blue color component -data PlaneBlue = PlaneBlue - --- | Define the plane for the alpha (transparency) component -data PlaneAlpha = PlaneAlpha - --- | Define the plane for the luma component -data PlaneLuma = PlaneLuma - --- | Define the plane for the Cr component -data PlaneCr = PlaneCr - --- | Define the plane for the Cb component -data PlaneCb = PlaneCb - --- | Define plane for the cyan component of the --- CMYK color space. -data PlaneCyan = PlaneCyan - --- | Define plane for the magenta component of the --- CMYK color space. -data PlaneMagenta = PlaneMagenta - --- | Define plane for the yellow component of the --- CMYK color space. -data PlaneYellow = PlaneYellow - --- | Define plane for the black component of --- the CMYK color space. -data PlaneBlack = PlaneBlack - --- | Extract a color plane from an image given a present plane in the image --- examples : --- --- @ --- extractRedPlane :: Image PixelRGB8-> Image Pixel8 --- extractRedPlane = extractComponent PlaneRed --- @ --- -extractComponent :: forall px plane. ( Pixel px - , Pixel (PixelBaseComponent px) - , PixelBaseComponent (PixelBaseComponent px) - ~ PixelBaseComponent px - , ColorPlane px plane ) - => plane -> Image px -> Image (PixelBaseComponent px) -extractComponent plane = unsafeExtractComponent idx - where idx = toComponentIndex (undefined :: px) plane - --- | Extract an image plane of an image, returning an image which --- can be represented by a gray scale image. --- If you ask a component out of bound, the `error` function will --- be called -unsafeExtractComponent :: forall a - . ( Pixel a - , Pixel (PixelBaseComponent a) - , PixelBaseComponent (PixelBaseComponent a) - ~ PixelBaseComponent a) - => Int -- ^ The component index, beginning at 0 ending at (componentCount - 1) - -> Image a -- ^ Source image - -> Image (PixelBaseComponent a) -unsafeExtractComponent comp img@(Image { imageWidth = w, imageHeight = h }) - | comp >= padd = error $ "extractComponent : invalid component index (" - ++ show comp ++ ", max:" ++ show padd ++ ")" - | otherwise = Image { imageWidth = w, imageHeight = h, imageData = plane } - where plane = stride img 1 padd comp - padd = componentCount (undefined :: a) - --- | For any image with an alpha component (transparency), --- drop it, returning a pure opaque image. -dropAlphaLayer :: (TransparentPixel a b) => Image a -> Image b -dropAlphaLayer = pixelMap dropTransparency - --- | Class modeling transparent pixel, should provide a method --- to combine transparent pixels -class (Pixel a, Pixel b) => TransparentPixel a b | a -> b where - -- | Just return the opaque pixel value - dropTransparency :: a -> b - -instance TransparentPixel PixelRGBA8 PixelRGB8 where - {-# INLINE dropTransparency #-} - dropTransparency (PixelRGBA8 r g b _) = PixelRGB8 r g b - -stride :: (Storable (PixelBaseComponent a)) - => Image a -> Int -> Int -> Int -> V.Vector (PixelBaseComponent a) -stride Image { imageWidth = w, imageHeight = h, imageData = array } - run padd firstComponent = runST $ do - let cell_count = w * h * run - outArray <- M.new cell_count - - let strideWrite write_idx _ | write_idx == cell_count = return () - strideWrite write_idx read_idx = do - forM_ [0 .. run - 1] $ \i -> - (outArray .<-. (write_idx + i)) $ array !!! (read_idx + i) - strideWrite (write_idx + run) (read_idx + padd) - - strideWrite 0 firstComponent - V.unsafeFreeze outArray - -instance NFData (Image a) where - rnf (Image width height dat) = width `seq` - height `seq` - dat `seq` - () - --- | 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 :: M.STVector s (PixelBaseComponent a) - } - --- | `O(n)` Yield an immutable copy of an image by making a copy of it -freezeImage :: (Storable (PixelBaseComponent a), PrimMonad m) - => MutableImage (PrimState m) a -> m (Image a) -freezeImage (MutableImage w h d) = Image w h `liftM` V.freeze d - --- | `O(1)` Unsafe convert a mutable image to an immutable one without copying. --- The mutable image may not be used after this operation. -unsafeFreezeImage :: (Storable (PixelBaseComponent a), PrimMonad m) - => MutableImage (PrimState m) a -> m (Image a) -unsafeFreezeImage (MutableImage w h d) = Image w h `liftM` V.unsafeFreeze d - -instance NFData (MutableImage s a) where - rnf (MutableImage width height dat) = width `seq` - height `seq` - dat `seq` - () - --- | Type allowing the loading of an image with different pixel --- structures -data DynamicImage = - -- | A greyscale image. - ImageY8 (Image Pixel8) - -- | A greyscale image with 16bit components - | ImageY16 (Image Pixel16) - -- | A greyscale HDR image - | ImageYF (Image PixelF) - -- | An image in greyscale with an alpha channel. - | ImageYA8 (Image PixelYA8) - -- | An image in greyscale with alpha channel on 16 bits. - | ImageYA16 (Image PixelYA16) - -- | An image in true color. - | ImageRGB8 (Image PixelRGB8) - -- | An image in true color with 16bit depth. - | ImageRGB16 (Image PixelRGB16) - -- | An image with HDR pixels - | ImageRGBF (Image PixelRGBF) - -- | An image in true color and an alpha channel. - | ImageRGBA8 (Image PixelRGBA8) - -- | A true color image with alpha on 16 bits. - | ImageRGBA16 (Image PixelRGBA16) - -- | An image in the colorspace used by Jpeg images. - | ImageYCbCr8 (Image PixelYCbCr8) - -- | An image in the colorspace CMYK - | ImageCMYK8 (Image PixelCMYK8) - -- | An image in the colorspace CMYK and 16 bots precision - | ImageCMYK16 (Image PixelCMYK16) - --- | Helper function to help extract information from dynamic --- image. To get the width of an dynamic image, you can use --- the following snippet : --- --- > dynWidth :: DynamicImage -> Int --- > dynWidth img = dynamicMap imageWidth img --- -dynamicMap :: (forall pixel . (Pixel pixel) => Image pixel -> a) - -> DynamicImage -> a -dynamicMap f (ImageY8 i) = f i -dynamicMap f (ImageY16 i) = f i -dynamicMap f (ImageYF i) = f i -dynamicMap f (ImageYA8 i) = f i -dynamicMap f (ImageYA16 i) = f i -dynamicMap f (ImageRGB8 i) = f i -dynamicMap f (ImageRGB16 i) = f i -dynamicMap f (ImageRGBF i) = f i -dynamicMap f (ImageRGBA8 i) = f i -dynamicMap f (ImageRGBA16 i) = f i -dynamicMap f (ImageYCbCr8 i) = f i -dynamicMap f (ImageCMYK8 i) = f i -dynamicMap f (ImageCMYK16 i) = f i - --- | Equivalent of the `pixelMap` function for the dynamic images. --- You can perform pixel colorspace independant operations with this --- function. --- --- For instance, if you wan't to extract a square crop of any image, --- without caring about colorspace, you can use the following snippet. --- --- > dynSquare :: DynamicImage -> DynamicImage --- > dynSquare = dynMap squareImage --- > --- > squareImage :: Pixel a => Image a -> Image a --- > squareImage img = generateImage (\x y -> pixelAt img x y) edge edge --- > where edge = min (imageWidth img) (imageHeight img) --- -dynamicPixelMap :: (forall pixel . (Pixel pixel) => Image pixel -> Image pixel) - -> DynamicImage -> DynamicImage -dynamicPixelMap f = aux - where - aux (ImageY8 i) = ImageY8 (f i) - aux (ImageY16 i) = ImageY16 (f i) - aux (ImageYF i) = ImageYF (f i) - aux (ImageYA8 i) = ImageYA8 (f i) - aux (ImageYA16 i) = ImageYA16 (f i) - aux (ImageRGB8 i) = ImageRGB8 (f i) - aux (ImageRGB16 i) = ImageRGB16 (f i) - aux (ImageRGBF i) = ImageRGBF (f i) - aux (ImageRGBA8 i) = ImageRGBA8 (f i) - aux (ImageRGBA16 i) = ImageRGBA16 (f i) - aux (ImageYCbCr8 i) = ImageYCbCr8 (f i) - aux (ImageCMYK8 i) = ImageCMYK8 (f i) - aux (ImageCMYK16 i) = ImageCMYK16 (f i) - -instance NFData DynamicImage where - rnf (ImageY8 img) = rnf img - rnf (ImageY16 img) = rnf img - rnf (ImageYF img) = rnf img - rnf (ImageYA8 img) = rnf img - rnf (ImageYA16 img) = rnf img - rnf (ImageRGB8 img) = rnf img - rnf (ImageRGB16 img) = rnf img - rnf (ImageRGBF img) = rnf img - rnf (ImageRGBA8 img) = rnf img - rnf (ImageRGBA16 img) = rnf img - rnf (ImageYCbCr8 img) = rnf img - rnf (ImageCMYK8 img) = rnf img - rnf (ImageCMYK16 img) = rnf img - --- | Simple alias for greyscale value in 8 bits. -type Pixel8 = Word8 - --- | Simple alias for greyscale value in 16 bits. -type Pixel16 = Word16 - --- | Simple alias for greyscale value in 16 bits. -type Pixel32 = Word32 - --- | Floating greyscale value, the 0 to 255 8 bit range maps --- to 0 to 1 in this floating version -type PixelF = Float - --- | Pixel type storing Luminance (Y) and alpha information --- on 8 bits. --- Value are stored in the following order : --- --- * Luminance --- --- * Alpha --- -data PixelYA8 = PixelYA8 {-# UNPACK #-} !Pixel8 -- Luminance - {-# UNPACK #-} !Pixel8 -- Alpha value - deriving (Eq, Show) - --- | Pixel type storing Luminance (Y) and alpha information --- on 16 bits. --- Value are stored in the following order : --- --- * Luminance --- --- * Alpha --- -data PixelYA16 = PixelYA16 {-# UNPACK #-} !Pixel16 -- Luminance - {-# UNPACK #-} !Pixel16 -- Alpha value - deriving (Eq, Show) - --- | Pixel type storing classic pixel on 8 bits --- Value are stored in the following order : --- --- * Red --- --- * Green --- --- * Blue --- -data PixelRGB8 = PixelRGB8 {-# UNPACK #-} !Pixel8 -- Red - {-# UNPACK #-} !Pixel8 -- Green - {-# UNPACK #-} !Pixel8 -- Blue - deriving (Eq, Show) - --- | Pixel type storing pixels on 16 bits --- Value are stored in the following order : --- --- * Red --- --- * Green --- --- * Blue --- -data PixelRGB16 = PixelRGB16 {-# UNPACK #-} !Pixel16 -- Red - {-# UNPACK #-} !Pixel16 -- Green - {-# UNPACK #-} !Pixel16 -- Blue - deriving (Eq, Show) - --- | Pixel type storing HDR pixel on 32 bits float --- Value are stored in the following order : --- --- * Red --- --- * Green --- --- * Blue --- -data PixelRGBF = PixelRGBF {-# UNPACK #-} !PixelF -- Red - {-# UNPACK #-} !PixelF -- Green - {-# UNPACK #-} !PixelF -- Blue - deriving (Eq, Show) - --- | Pixel storing data in the YCbCr colorspace, --- value are stored in the following order : --- --- * Y (luminance) --- --- * Cr --- --- * Cb --- -data PixelYCbCr8 = PixelYCbCr8 {-# UNPACK #-} !Pixel8 -- Y luminance - {-# UNPACK #-} !Pixel8 -- Cr red difference - {-# UNPACK #-} !Pixel8 -- Cb blue difference - deriving (Eq, Show) - --- | Pixel storing data in the CMYK colorspace. value --- are stored in the following order : --- --- * Cyan --- --- * Magenta --- --- * Yellow --- --- * Black --- -data PixelCMYK8 = PixelCMYK8 {-# UNPACK #-} !Pixel8 -- Cyan - {-# UNPACK #-} !Pixel8 -- Magenta - {-# UNPACK #-} !Pixel8 -- Yellow - {-# UNPACK #-} !Pixel8 -- Black - deriving (Eq, Show) - --- | Pixel storing data in the CMYK colorspace. value --- are stored in the following order : --- --- * Cyan --- --- * Magenta --- --- * Yellow --- --- * Black --- -data PixelCMYK16 = PixelCMYK16 {-# UNPACK #-} !Pixel16 -- Cyan - {-# UNPACK #-} !Pixel16 -- Magenta - {-# UNPACK #-} !Pixel16 -- Yellow - {-# UNPACK #-} !Pixel16 -- Black - deriving (Eq, Show) - - --- | 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 #-} !Pixel8 -- Red - {-# UNPACK #-} !Pixel8 -- Green - {-# UNPACK #-} !Pixel8 -- Blue - {-# UNPACK #-} !Pixel8 -- Alpha - deriving (Eq, Show) - --- | Pixel type storing a RGB information with an alpha --- channel on 16 bits. --- Values are stored in the following order --- --- * Red --- --- * Green --- --- * Blue --- --- * Alpha --- -data PixelRGBA16 = PixelRGBA16 {-# UNPACK #-} !Pixel16 -- Red - {-# UNPACK #-} !Pixel16 -- Green - {-# UNPACK #-} !Pixel16 -- Blue - {-# UNPACK #-} !Pixel16 -- Alpha - deriving (Eq, Show) - --- | Definition of pixels used in images. Each pixel has a color space, and a representative --- component (Word8 or Float). -class ( Storable (PixelBaseComponent a) - , Num (PixelBaseComponent a), Eq a ) => Pixel a where - -- | Type of the pixel component, "classical" images - -- would have Word8 type as their PixelBaseComponent, - -- HDR image would have Float for instance - type PixelBaseComponent a :: * - - -- | Call the function for every component of the pixels. - -- For example for RGB pixels mixWith is declared like this : - -- - -- > mixWith f (PixelRGB8 ra ga ba) (PixelRGB8 rb gb bb) = - -- > PixelRGB8 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) - -- - mixWith :: (Int -> PixelBaseComponent a -> PixelBaseComponent a -> PixelBaseComponent a) - -> a -> a -> a - - -- | Return the number of component of the pixel - componentCount :: a -> Int - - -- | Apply a function to all color component of a pixel. - colorMap :: (PixelBaseComponent a -> PixelBaseComponent a) -> a -> a - - -- | 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) - - -- | 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 :: PrimMonad m => MutableImage (PrimState m) a -> Int -> Int -> m a - - -- | Write a pixel in a mutable image at position x y - writePixel :: PrimMonad m => MutableImage (PrimState m) a -> Int -> Int -> a -> m () - - -- | Unsafe version of pixelAt, read a pixel at the given - -- index without bound checking (if possible). - -- The index is expressed in number (PixelBaseComponent a) - unsafePixelAt :: V.Vector (PixelBaseComponent a) -> Int -> a - - -- | Unsafe version of readPixel, read a pixel at the given - -- position without bound checking (if possible). The index - -- is expressed in number (PixelBaseComponent a) - unsafeReadPixel :: PrimMonad m => M.STVector (PrimState m) (PixelBaseComponent a) -> Int -> m a - - -- | Unsafe version of writePixel, write a pixel at the - -- given position without bound checking. This can be _really_ unsafe. - -- The index is expressed in number (PixelBaseComponent a) - unsafeWritePixel :: PrimMonad m => M.STVector (PrimState m) (PixelBaseComponent a) -> Int -> a -> m () - - --- | 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 = pixelMap promotePixel - --- | This class abstract colorspace conversion. This --- conversion can be lossy, which ColorConvertible cannot -class (Pixel a, Pixel b) => ColorSpaceConvertible a b where - -- | Pass a pixel from a colorspace (say RGB) to the second one - -- (say YCbCr) - convertPixel :: a -> b - - -- | Helper function to convert a whole image by taking a - -- copy it. - convertImage :: Image a -> Image b - convertImage = pixelMap convertPixel - --- | Create an image given a function to generate pixels. --- The function will receive value from 0 to width-1 for the x parameter --- and 0 to height-1 for the y parameter. The coordinate 0,0 is the upper --- left corner of the image, and (width-1, height-1) the lower right corner. --- --- for example, to create a small gradient image : --- --- > imageCreator :: String -> Image PixelRGB8 --- > imageCreator path = writePng path $ generateImage pixelRenderer 250 300 --- > where pixelRenderer x y = PixelRGB8 x y 128 --- -generateImage :: forall a. (Pixel a) - => (Int -> Int -> a) -- ^ Generating function, with `x` and `y` params. - -> Int -- ^ Width in pixels - -> Int -- ^ Height in pixels - -> Image a -generateImage f w h = Image { imageWidth = w, imageHeight = h, imageData = generated } - where compCount = componentCount (undefined :: a) - generated = runST $ do - arr <- M.new (w * h * compCount) - let lineGenerator _ y | y >= h = return () - lineGenerator lineIdx y = column lineIdx 0 - where column idx x | x >= w = lineGenerator idx $ y + 1 - column idx x = do - unsafeWritePixel arr idx $ f x y - column (idx + compCount) $ x + 1 - - lineGenerator 0 0 - V.unsafeFreeze arr - --- | Create an image using a monadic initializer function. --- The function will receive value from 0 to width-1 for the x parameter --- and 0 to height-1 for the y parameter. The coordinate 0,0 is the upper --- left corner of the image, and (width-1, height-1) the lower right corner. --- --- The function is called for each pixel in the line from left to right (0 to width - 1) --- and for each line (0 to height - 1). -withImage :: forall m pixel. (Pixel pixel, PrimMonad m) - => Int -- ^ Image width - -> Int -- ^ Image height - -> (Int -> Int -> m pixel) -- ^ Generating functions - -> m (Image pixel) -withImage width height pixelGenerator = do - let pixelComponentCount = componentCount (undefined :: pixel) - arr <- M.new (width * height * pixelComponentCount) - let mutImage = MutableImage - { mutableImageWidth = width - , mutableImageHeight = height - , mutableImageData = arr - } - - let pixelPositions = [(x, y) | y <- [0 .. height-1], x <- [0..width-1]] - sequence_ [pixelGenerator x y >>= unsafeWritePixel arr idx - | ((x,y), idx) <- zip pixelPositions [0, pixelComponentCount ..]] - unsafeFreezeImage mutImage - --- | Create an image given a function to generate pixels. --- The function will receive value from 0 to width-1 for the x parameter --- and 0 to height-1 for the y parameter. The coordinate 0,0 is the upper --- left corner of the image, and (width-1, height-1) the lower right corner. --- --- the acc parameter is a user defined one. --- --- The function is called for each pixel in the line from left to right (0 to width - 1) --- and for each line (0 to height - 1). -generateFoldImage :: forall a acc. (Pixel a) - => (acc -> Int -> Int -> (acc, a)) -- ^ Function taking the state, x and y - -> acc -- ^ Initial state - -> Int -- ^ Width in pixels - -> Int -- ^ Height in pixels - -> (acc, Image a) -generateFoldImage f intialAcc w h = - (finalState, Image { imageWidth = w, imageHeight = h, imageData = generated }) - where compCount = componentCount (undefined :: a) - (finalState, generated) = runST $ do - arr <- M.new (w * h * compCount) - let mutImage = MutableImage { - mutableImageWidth = w, - mutableImageHeight = h, - mutableImageData = arr } - foldResult <- foldM (\acc (x,y) -> do - let (acc', px) = f acc x y - writePixel mutImage x y px - return acc') intialAcc [(x,y) | y <- [0 .. h-1], x <- [0 .. w-1]] - - frozen <- V.unsafeFreeze arr - return (foldResult, frozen) - --- | Fold over the pixel of an image with a raster scan order : --- from top to bottom, left to right -{-# INLINE pixelFold #-} -pixelFold :: (Pixel pixel) - => (acc -> Int -> Int -> pixel -> acc) -> acc -> Image pixel -> acc -pixelFold f initialAccumulator img@(Image { imageWidth = w, imageHeight = h }) = - lineFold - where pixelFolder y acc x = f acc x y $ pixelAt img x y - columnFold lineAcc y = foldl' (pixelFolder y) lineAcc [0 .. w - 1] - lineFold = foldl' columnFold initialAccumulator [0 .. h - 1] - --- | `map` equivalent for an image, working at the pixel level. --- Little example : a brightness function for an rgb image --- --- > brightnessRGB8 :: Int -> Image PixelRGB8 -> Image PixelRGB8 --- > brightnessRGB8 add = pixelMap brightFunction --- > where up v = fromIntegral (fromIntegral v + add) --- > brightFunction (PixelRGB8 r g b) = --- > PixelRGB8 (up r) (up g) (up b) --- -pixelMap :: forall a b. (Pixel a, Pixel b) - => (a -> b) -> Image a -> Image b -{-# RULES "pixelMap fusion" forall g f. pixelMap g . pixelMap f = pixelMap (g . f) #-} -{-# SPECIALIZE INLINE pixelMap :: (PixelYCbCr8 -> PixelRGB8) -> Image PixelYCbCr8 -> Image PixelRGB8 #-} -{-# SPECIALIZE INLINE pixelMap :: (PixelRGB8 -> PixelYCbCr8) -> Image PixelRGB8 -> Image PixelYCbCr8 #-} -{-# SPECIALIZE INLINE pixelMap :: (PixelRGB8 -> PixelRGB8) -> Image PixelRGB8 -> Image PixelRGB8 #-} -{-# SPECIALIZE INLINE pixelMap :: (PixelRGB8 -> PixelRGBA8) -> Image PixelRGB8 -> Image PixelRGBA8 #-} -{-# SPECIALIZE INLINE pixelMap :: (PixelRGBA8 -> PixelRGBA8) -> Image PixelRGBA8 -> Image PixelRGBA8 #-} -{-# SPECIALIZE INLINE pixelMap :: (Pixel8 -> PixelRGB8) -> Image Pixel8 -> Image PixelRGB8 #-} -pixelMap f Image { imageWidth = w, imageHeight = h, imageData = vec } = - Image w h pixels - where sourceComponentCount = componentCount (undefined :: a) - destComponentCount = componentCount (undefined :: b) - - pixels = runST $ do - newArr <- M.new (w * h * destComponentCount) - let lineMapper _ _ y | y >= h = return () - lineMapper readIdxLine writeIdxLine y = colMapper readIdxLine writeIdxLine 0 - where colMapper readIdx writeIdx x - | x >= w = lineMapper readIdx writeIdx $ y + 1 - | otherwise = do - unsafeWritePixel newArr writeIdx . f $ unsafePixelAt vec readIdx - colMapper (readIdx + sourceComponentCount) - (writeIdx + destComponentCount) - (x + 1) - lineMapper 0 0 0 - - -- unsafeFreeze avoids making a second copy and it will be - -- safe because newArray can't be referenced as a mutable array - -- outside of this where block - V.unsafeFreeze newArr - --- | Helper class to help extract a luma plane out --- of an image or a pixel -class (Pixel a, Pixel (PixelBaseComponent a)) => LumaPlaneExtractable a where - -- | Compute the luminance part of a pixel - computeLuma :: a -> (PixelBaseComponent a) - - -- | Extract a luma plane out of an image. This - -- method is in the typeclass to help performant - -- implementation. - -- - -- > jpegToGrayScale :: FilePath -> FilePath -> IO () - -- > jpegToGrayScale source dest - extractLumaPlane :: Image a -> Image (PixelBaseComponent a) - extractLumaPlane = pixelMap computeLuma - -instance LumaPlaneExtractable Pixel8 where - {-# INLINE computeLuma #-} - computeLuma = id - extractLumaPlane = id - -instance LumaPlaneExtractable Pixel16 where - {-# INLINE computeLuma #-} - computeLuma = id - extractLumaPlane = id - -instance LumaPlaneExtractable Pixel32 where - {-# INLINE computeLuma #-} - computeLuma = id - extractLumaPlane = id - -instance LumaPlaneExtractable PixelF where - {-# INLINE computeLuma #-} - computeLuma = id - extractLumaPlane = id - -instance LumaPlaneExtractable PixelRGBF where - {-# INLINE computeLuma #-} - computeLuma (PixelRGBF r g b) = - 0.3 * r + 0.59 * g + 0.11 * b - -instance LumaPlaneExtractable PixelRGBA8 where - {-# INLINE computeLuma #-} - computeLuma (PixelRGBA8 r g b _) = floor $ 0.3 * toRational r + - 0.59 * toRational g + - 0.11 * toRational b - -instance LumaPlaneExtractable PixelYCbCr8 where - {-# INLINE computeLuma #-} - computeLuma (PixelYCbCr8 y _ _) = y - extractLumaPlane = extractComponent PlaneLuma - --- | Free promotion for identic pixel types -instance (Pixel a) => ColorConvertible a a where - {-# INLINE promotePixel #-} - promotePixel = id - - {-# INLINE promoteImage #-} - promoteImage = id - -{-# INLINE (.!!!.) #-} -(.!!!.) :: (PrimMonad m, Storable a) => M.STVector (PrimState m) a -> Int -> m a -(.!!!.) = M.read -- unsafeRead - -{-# INLINE (.<-.) #-} -(.<-.) :: (PrimMonad m, Storable a) => M.STVector (PrimState m) a -> Int -> a -> m () -(.<-.) = M.write -- unsafeWrite - --------------------------------------------------- ----- Pixel8 instances --------------------------------------------------- -instance Pixel Pixel8 where - type PixelBaseComponent Pixel8 = Word8 - - {-# INLINE mixWith #-} - mixWith f = f 0 - - {-# INLINE colorMap #-} - colorMap f = f - - componentCount _ = 1 - pixelAt (Image { imageWidth = w, imageData = arr }) x y = arr ! (x + y * w) - - readPixel image@(MutableImage { mutableImageData = arr }) x y = - arr `M.read` mutablePixelBaseIndex image x y - - writePixel image@(MutableImage { mutableImageData = arr }) x y = - arr `M.write` mutablePixelBaseIndex image x y - - unsafePixelAt = V.unsafeIndex - unsafeReadPixel = M.unsafeRead - unsafeWritePixel = M.unsafeWrite - -instance ColorConvertible Pixel8 PixelYA8 where - {-# INLINE promotePixel #-} - promotePixel c = PixelYA8 c 255 - -instance ColorConvertible Pixel8 PixelF where - {-# INLINE promotePixel #-} - promotePixel c = fromIntegral c / 255.0 - -instance ColorConvertible Pixel8 Pixel16 where - {-# INLINE promotePixel #-} - promotePixel c = fromIntegral c `unsafeShiftL` 8 - -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 - --------------------------------------------------- ----- Pixel16 instances --------------------------------------------------- -instance Pixel Pixel16 where - type PixelBaseComponent Pixel16 = Word16 - - {-# INLINE mixWith #-} - mixWith f = f 0 - - {-# INLINE colorMap #-} - colorMap f = f - - componentCount _ = 1 - pixelAt (Image { imageWidth = w, imageData = arr }) x y = arr ! (x + y * w) - - readPixel image@(MutableImage { mutableImageData = arr }) x y = - arr `M.read` mutablePixelBaseIndex image x y - - writePixel image@(MutableImage { mutableImageData = arr }) x y = - arr `M.write` mutablePixelBaseIndex image x y - - unsafePixelAt = V.unsafeIndex - unsafeReadPixel = M.unsafeRead - unsafeWritePixel = M.unsafeWrite - -instance ColorConvertible Pixel16 PixelYA16 where - {-# INLINE promotePixel #-} - promotePixel c = PixelYA16 c maxBound - -instance ColorConvertible Pixel16 PixelRGB16 where - {-# INLINE promotePixel #-} - promotePixel c = PixelRGB16 c c c - -instance ColorConvertible Pixel16 PixelRGBA16 where - {-# INLINE promotePixel #-} - promotePixel c = PixelRGBA16 c c c maxBound - --------------------------------------------------- ----- Pixel32 instances --------------------------------------------------- -instance Pixel Pixel32 where - type PixelBaseComponent Pixel32 = Word32 - - {-# INLINE mixWith #-} - mixWith f = f 0 - - {-# INLINE colorMap #-} - colorMap f = f - - componentCount _ = 1 - pixelAt (Image { imageWidth = w, imageData = arr }) x y = arr ! (x + y * w) - - readPixel image@(MutableImage { mutableImageData = arr }) x y = - arr `M.read` mutablePixelBaseIndex image x y - - writePixel image@(MutableImage { mutableImageData = arr }) x y = - arr `M.write` mutablePixelBaseIndex image x y - - unsafePixelAt = V.unsafeIndex - unsafeReadPixel = M.unsafeRead - unsafeWritePixel = M.unsafeWrite - --------------------------------------------------- ----- PixelF instances --------------------------------------------------- -instance Pixel PixelF where - type PixelBaseComponent PixelF = Float - - {-# INLINE mixWith #-} - mixWith f = f 0 - - {-# INLINE colorMap #-} - colorMap f = f - componentCount _ = 1 - pixelAt (Image { imageWidth = w, imageData = arr }) x y = arr ! (x + y * w) - - readPixel image@(MutableImage { mutableImageData = arr }) x y = - arr `M.read` mutablePixelBaseIndex image x y - - writePixel image@(MutableImage { mutableImageData = arr }) x y = - arr `M.write` mutablePixelBaseIndex image x y - - unsafePixelAt = V.unsafeIndex - unsafeReadPixel = M.unsafeRead - unsafeWritePixel = M.unsafeWrite - -instance ColorConvertible PixelF PixelRGBF where - {-# INLINE promotePixel #-} - promotePixel c = PixelRGBF c c c-- (c / 0.3) (c / 0.59) (c / 0.11) - --------------------------------------------------- ----- PixelYA8 instances --------------------------------------------------- -instance Pixel PixelYA8 where - type PixelBaseComponent PixelYA8 = Word8 - - {-# INLINE mixWith #-} - mixWith f (PixelYA8 ya aa) (PixelYA8 yb ab) = - PixelYA8 (f 0 ya yb) (f 1 aa ab) - - - {-# INLINE colorMap #-} - colorMap f (PixelYA8 y a) = PixelYA8 (f y) (f a) - 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 - - unsafePixelAt v idx = - PixelYA8 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) - unsafeReadPixel vec idx = - PixelYA8 `liftM` M.unsafeRead vec idx `ap` M.unsafeRead vec (idx + 1) - unsafeWritePixel v idx (PixelYA8 y a) = - M.unsafeWrite v idx y >> M.unsafeWrite v (idx + 1) a - -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 - -instance ColorPlane PixelYA8 PlaneLuma where - toComponentIndex _ _ = 0 - -instance ColorPlane PixelYA8 PlaneAlpha where - toComponentIndex _ _ = 1 - -instance TransparentPixel PixelYA8 Pixel8 where - {-# INLINE dropTransparency #-} - dropTransparency (PixelYA8 y _) = y - -instance LumaPlaneExtractable PixelYA8 where - {-# INLINE computeLuma #-} - computeLuma (PixelYA8 y _) = y - extractLumaPlane = extractComponent PlaneLuma - --------------------------------------------------- ----- PixelYA16 instances --------------------------------------------------- -instance Pixel PixelYA16 where - type PixelBaseComponent PixelYA16 = Word16 - - {-# INLINE mixWith #-} - mixWith f (PixelYA16 ya aa) (PixelYA16 yb ab) = - PixelYA16 (f 0 ya yb) (f 1 aa ab) - - {-# INLINE colorMap #-} - colorMap f (PixelYA16 y a) = PixelYA16 (f y) (f a) - componentCount _ = 2 - pixelAt image@(Image { imageData = arr }) x y = PixelYA16 (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 $ PixelYA16 yv av - where baseIdx = mutablePixelBaseIndex image x y - - writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelYA16 yv av) = do - let baseIdx = mutablePixelBaseIndex image x y - (arr .<-. (baseIdx + 0)) yv - (arr .<-. (baseIdx + 1)) av - - unsafePixelAt v idx = - PixelYA16 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) - unsafeReadPixel vec idx = - PixelYA16 `liftM` M.unsafeRead vec idx `ap` M.unsafeRead vec (idx + 1) - unsafeWritePixel v idx (PixelYA16 y a) = - M.unsafeWrite v idx y >> M.unsafeWrite v (idx + 1) a - -instance ColorConvertible PixelYA16 PixelRGBA16 where - {-# INLINE promotePixel #-} - promotePixel (PixelYA16 y a) = PixelRGBA16 y y y a - -instance ColorPlane PixelYA16 PlaneLuma where - toComponentIndex _ _ = 0 - -instance ColorPlane PixelYA16 PlaneAlpha where - toComponentIndex _ _ = 1 - -instance TransparentPixel PixelYA16 Pixel16 where - {-# INLINE dropTransparency #-} - dropTransparency (PixelYA16 y _) = y --------------------------------------------------- ----- PixelRGBF instances --------------------------------------------------- -instance Pixel PixelRGBF where - type PixelBaseComponent PixelRGBF = PixelF - - {-# INLINE mixWith #-} - mixWith f (PixelRGBF ra ga ba) (PixelRGBF rb gb bb) = - PixelRGBF (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) - - {-# INLINE colorMap #-} - colorMap f (PixelRGBF r g b) = PixelRGBF (f r) (f g) (f b) - - componentCount _ = 3 - - pixelAt image@(Image { imageData = arr }) x y = PixelRGBF (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 $ PixelRGBF rv gv bv - where baseIdx = mutablePixelBaseIndex image x y - - writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelRGBF rv gv bv) = do - let baseIdx = mutablePixelBaseIndex image x y - (arr .<-. (baseIdx + 0)) rv - (arr .<-. (baseIdx + 1)) gv - (arr .<-. (baseIdx + 2)) bv - - unsafePixelAt v idx = - PixelRGBF (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) - unsafeReadPixel vec idx = - PixelRGBF `liftM` M.unsafeRead vec idx - `ap` M.unsafeRead vec (idx + 1) - `ap` M.unsafeRead vec (idx + 2) - unsafeWritePixel v idx (PixelRGBF r g b) = - M.unsafeWrite v idx r >> M.unsafeWrite v (idx + 1) g - >> M.unsafeWrite v (idx + 2) b - -instance ColorPlane PixelRGBF PlaneRed where - toComponentIndex _ _ = 0 - -instance ColorPlane PixelRGBF PlaneGreen where - toComponentIndex _ _ = 1 - -instance ColorPlane PixelRGBF PlaneBlue where - toComponentIndex _ _ = 2 - --------------------------------------------------- ----- PixelRGB16 instances --------------------------------------------------- -instance Pixel PixelRGB16 where - type PixelBaseComponent PixelRGB16 = Pixel16 - - {-# INLINE mixWith #-} - mixWith f (PixelRGB16 ra ga ba) (PixelRGB16 rb gb bb) = - PixelRGB16 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) - - {-# INLINE colorMap #-} - colorMap f (PixelRGB16 r g b) = PixelRGB16 (f r) (f g) (f b) - - componentCount _ = 3 - - pixelAt image@(Image { imageData = arr }) x y = PixelRGB16 (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 $ PixelRGB16 rv gv bv - where baseIdx = mutablePixelBaseIndex image x y - - writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelRGB16 rv gv bv) = do - let baseIdx = mutablePixelBaseIndex image x y - (arr .<-. (baseIdx + 0)) rv - (arr .<-. (baseIdx + 1)) gv - (arr .<-. (baseIdx + 2)) bv - - unsafePixelAt v idx = - PixelRGB16 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) - unsafeReadPixel vec idx = - PixelRGB16 `liftM` M.unsafeRead vec idx - `ap` M.unsafeRead vec (idx + 1) - `ap` M.unsafeRead vec (idx + 2) - unsafeWritePixel v idx (PixelRGB16 r g b) = - M.unsafeWrite v idx r >> M.unsafeWrite v (idx + 1) g - >> M.unsafeWrite v (idx + 2) b - -instance ColorPlane PixelRGB16 PlaneRed where - toComponentIndex _ _ = 0 - -instance ColorPlane PixelRGB16 PlaneGreen where - toComponentIndex _ _ = 1 - -instance ColorPlane PixelRGB16 PlaneBlue where - toComponentIndex _ _ = 2 - -instance ColorSpaceConvertible PixelRGB16 PixelCMYK16 where - {-# INLINE convertPixel #-} - convertPixel (PixelRGB16 r g b) = integralRGBToCMYK PixelCMYK16 (r, g, b) - -instance ColorConvertible PixelRGB16 PixelRGBA16 where - {-# INLINE promotePixel #-} - promotePixel (PixelRGB16 r g b) = PixelRGBA16 r g b maxBound - -instance LumaPlaneExtractable PixelRGB16 where - {-# INLINE computeLuma #-} - computeLuma (PixelRGB16 r g b) = floor $ 0.3 * toRational r + - 0.59 * toRational g + - 0.11 * toRational b --------------------------------------------------- ----- PixelRGB8 instances --------------------------------------------------- -instance Pixel PixelRGB8 where - type PixelBaseComponent PixelRGB8 = Word8 - - {-# INLINE mixWith #-} - mixWith f (PixelRGB8 ra ga ba) (PixelRGB8 rb gb bb) = - PixelRGB8 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) - - {-# INLINE colorMap #-} - colorMap f (PixelRGB8 r g b) = PixelRGB8 (f r) (f g) (f b) - - componentCount _ = 3 - - 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 - - unsafePixelAt v idx = - PixelRGB8 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) - unsafeReadPixel vec idx = - PixelRGB8 `liftM` M.unsafeRead vec idx - `ap` M.unsafeRead vec (idx + 1) - `ap` M.unsafeRead vec (idx + 2) - unsafeWritePixel v idx (PixelRGB8 r g b) = - M.unsafeWrite v idx r >> M.unsafeWrite v (idx + 1) g - >> M.unsafeWrite v (idx + 2) b - -instance ColorConvertible PixelRGB8 PixelRGBA8 where - {-# INLINE promotePixel #-} - promotePixel (PixelRGB8 r g b) = PixelRGBA8 r g b maxBound - -instance ColorConvertible PixelRGB8 PixelRGBF where - {-# INLINE promotePixel #-} - promotePixel (PixelRGB8 r g b) = PixelRGBF (toF r) (toF g) (toF b) - where toF v = fromIntegral v / 255.0 - -instance ColorPlane PixelRGB8 PlaneRed where - toComponentIndex _ _ = 0 - -instance ColorPlane PixelRGB8 PlaneGreen where - toComponentIndex _ _ = 1 - -instance ColorPlane PixelRGB8 PlaneBlue where - toComponentIndex _ _ = 2 - -instance LumaPlaneExtractable PixelRGB8 where - {-# INLINE computeLuma #-} - computeLuma (PixelRGB8 r g b) = floor $ 0.3 * toRational r + - 0.59 * toRational g + - 0.11 * toRational b - --------------------------------------------------- ----- PixelRGBA8 instances --------------------------------------------------- -instance Pixel PixelRGBA8 where - type PixelBaseComponent PixelRGBA8 = Word8 - - {-# INLINE mixWith #-} - mixWith f (PixelRGBA8 ra ga ba aa) (PixelRGBA8 rb gb bb ab) = - PixelRGBA8 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) (f 3 aa ab) - - {-# INLINE colorMap #-} - colorMap f (PixelRGBA8 r g b a) = PixelRGBA8 (f r) (f g) (f b) (f a) - - 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 - - unsafePixelAt v idx = - PixelRGBA8 (V.unsafeIndex v idx) - (V.unsafeIndex v $ idx + 1) - (V.unsafeIndex v $ idx + 2) - (V.unsafeIndex v $ idx + 3) - unsafeReadPixel vec idx = - PixelRGBA8 `liftM` M.unsafeRead vec idx - `ap` M.unsafeRead vec (idx + 1) - `ap` M.unsafeRead vec (idx + 2) - `ap` M.unsafeRead vec (idx + 3) - unsafeWritePixel v idx (PixelRGBA8 r g b a) = - M.unsafeWrite v idx r >> M.unsafeWrite v (idx + 1) g - >> M.unsafeWrite v (idx + 2) b - >> M.unsafeWrite v (idx + 3) a - -instance ColorPlane PixelRGBA8 PlaneRed where - toComponentIndex _ _ = 0 - -instance ColorPlane PixelRGBA8 PlaneGreen where - toComponentIndex _ _ = 1 - -instance ColorPlane PixelRGBA8 PlaneBlue where - toComponentIndex _ _ = 2 - -instance ColorPlane PixelRGBA8 PlaneAlpha where - toComponentIndex _ _ = 3 - --------------------------------------------------- ----- PixelRGBA16 instances --------------------------------------------------- -instance Pixel PixelRGBA16 where - type PixelBaseComponent PixelRGBA16 = Pixel16 - - {-# INLINE mixWith #-} - mixWith f (PixelRGBA16 ra ga ba aa) (PixelRGBA16 rb gb bb ab) = - PixelRGBA16 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) (f 3 aa ab) - - {-# INLINE colorMap #-} - colorMap f (PixelRGBA16 r g b a) = PixelRGBA16 (f r) (f g) (f b) (f a) - - componentCount _ = 4 - - pixelAt image@(Image { imageData = arr }) x y = - PixelRGBA16 (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 $ PixelRGBA16 rv gv bv av - where baseIdx = mutablePixelBaseIndex image x y - - writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelRGBA16 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 - - unsafePixelAt v idx = - PixelRGBA16 (V.unsafeIndex v idx) - (V.unsafeIndex v $ idx + 1) - (V.unsafeIndex v $ idx + 2) - (V.unsafeIndex v $ idx + 3) - unsafeReadPixel vec idx = - PixelRGBA16 `liftM` M.unsafeRead vec idx - `ap` M.unsafeRead vec (idx + 1) - `ap` M.unsafeRead vec (idx + 2) - `ap` M.unsafeRead vec (idx + 3) - unsafeWritePixel v idx (PixelRGBA16 r g b a) = - M.unsafeWrite v idx r >> M.unsafeWrite v (idx + 1) g - >> M.unsafeWrite v (idx + 2) b - >> M.unsafeWrite v (idx + 3) a - - -instance TransparentPixel PixelRGBA16 PixelRGB16 where - {-# INLINE dropTransparency #-} - dropTransparency (PixelRGBA16 r g b _) = PixelRGB16 r g b - -instance ColorPlane PixelRGBA16 PlaneRed where - toComponentIndex _ _ = 0 - -instance ColorPlane PixelRGBA16 PlaneGreen where - toComponentIndex _ _ = 1 - -instance ColorPlane PixelRGBA16 PlaneBlue where - toComponentIndex _ _ = 2 - -instance ColorPlane PixelRGBA16 PlaneAlpha where - toComponentIndex _ _ = 3 - --------------------------------------------------- ----- PixelYCbCr8 instances --------------------------------------------------- -instance Pixel PixelYCbCr8 where - type PixelBaseComponent PixelYCbCr8 = Word8 - - {-# INLINE mixWith #-} - mixWith f (PixelYCbCr8 ya cba cra) (PixelYCbCr8 yb cbb crb) = - PixelYCbCr8 (f 0 ya yb) (f 1 cba cbb) (f 2 cra crb) - - {-# INLINE colorMap #-} - colorMap f (PixelYCbCr8 y cb cr) = PixelYCbCr8 (f y) (f cb) (f cr) - 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 - - unsafePixelAt v idx = - PixelYCbCr8 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) - unsafeReadPixel vec idx = - PixelYCbCr8 `liftM` M.unsafeRead vec idx - `ap` M.unsafeRead vec (idx + 1) - `ap` M.unsafeRead vec (idx + 2) - unsafeWritePixel v idx (PixelYCbCr8 y cb cr) = - M.unsafeWrite v idx y >> M.unsafeWrite v (idx + 1) cb - >> M.unsafeWrite v (idx + 2) cr - -instance (Pixel a) => ColorSpaceConvertible a a where - convertPixel = id - convertImage = id - -scaleBits, oneHalf :: Int -scaleBits = 16 -oneHalf = 1 `unsafeShiftL` (scaleBits - 1) - -fix :: Float -> Int -fix x = floor $ x * fromIntegral ((1 :: Int) `unsafeShiftL` scaleBits) + 0.5 - - -rYTab, gYTab, bYTab, rCbTab, gCbTab, bCbTab, gCrTab, bCrTab :: V.Vector Int -rYTab = V.fromListN 256 [fix 0.29900 * i | i <- [0..255] ] -gYTab = V.fromListN 256 [fix 0.58700 * i | i <- [0..255] ] -bYTab = V.fromListN 256 [fix 0.11400 * i + oneHalf | i <- [0..255] ] -rCbTab = V.fromListN 256 [(- fix 0.16874) * i | i <- [0..255] ] -gCbTab = V.fromListN 256 [(- fix 0.33126) * i | i <- [0..255] ] -bCbTab = V.fromListN 256 [fix 0.5 * i + (128 `unsafeShiftL` scaleBits) + oneHalf - 1| i <- [0..255] ] -gCrTab = V.fromListN 256 [(- fix 0.41869) * i | i <- [0..255] ] -bCrTab = V.fromListN 256 [(- fix 0.08131) * i | i <- [0..255] ] - - -instance ColorSpaceConvertible PixelRGB8 PixelYCbCr8 where - {-# INLINE convertPixel #-} - convertPixel (PixelRGB8 r g b) = PixelYCbCr8 (fromIntegral y) (fromIntegral cb) (fromIntegral cr) - where ri = fromIntegral r - gi = fromIntegral g - bi = fromIntegral b - - y = (rYTab `V.unsafeIndex` ri + gYTab `V.unsafeIndex` gi + bYTab `V.unsafeIndex` bi) `unsafeShiftR` scaleBits - cb = (rCbTab `V.unsafeIndex` ri + gCbTab `V.unsafeIndex` gi + bCbTab `V.unsafeIndex` bi) `unsafeShiftR` scaleBits - cr = (bCbTab `V.unsafeIndex` ri + gCrTab `V.unsafeIndex` gi + bCrTab `V.unsafeIndex` bi) `unsafeShiftR` scaleBits - - convertImage Image { imageWidth = w, imageHeight = h, imageData = d } = Image w h newData - where maxi = w * h - - rY = fix 0.29900 - gY = fix 0.58700 - bY = fix 0.11400 - rCb = (- fix 0.16874) - gCb = (- fix 0.33126) - bCb = fix 0.5 - gCr = (- fix 0.41869) - bCr = (- fix 0.08131) - - newData = runST $ do - block <- M.new $ maxi * 3 - let traductor _ idx | idx >= maxi = return block - traductor readIdx idx = do - let ri = fromIntegral $ d `V.unsafeIndex` readIdx - gi = fromIntegral $ d `V.unsafeIndex` (readIdx + 1) - bi = fromIntegral $ d `V.unsafeIndex` (readIdx + 2) - - y = (rY * ri + gY * gi + bY * bi + oneHalf) `unsafeShiftR` scaleBits - cb = (rCb * ri + gCb * gi + bCb * bi + (128 `unsafeShiftL` scaleBits) + oneHalf - 1) `unsafeShiftR` scaleBits - cr = (bCb * ri + (128 `unsafeShiftL` scaleBits) + oneHalf - 1+ gCr * gi + bCr * bi) `unsafeShiftR` scaleBits - - (block `M.unsafeWrite` (readIdx + 0)) $ fromIntegral y - (block `M.unsafeWrite` (readIdx + 1)) $ fromIntegral cb - (block `M.unsafeWrite` (readIdx + 2)) $ fromIntegral cr - traductor (readIdx + 3) (idx + 1) - - traductor 0 0 >>= V.freeze - -crRTab, cbBTab, crGTab, cbGTab :: V.Vector Int -crRTab = V.fromListN 256 [(fix 1.40200 * x + oneHalf) `unsafeShiftR` scaleBits | x <- [-128 .. 127]] -cbBTab = V.fromListN 256 [(fix 1.77200 * x + oneHalf) `unsafeShiftR` scaleBits | x <- [-128 .. 127]] -crGTab = V.fromListN 256 [negate (fix 0.71414) * x | x <- [-128 .. 127]] -cbGTab = V.fromListN 256 [negate (fix 0.34414) * x + oneHalf | x <- [-128 .. 127]] - -instance ColorSpaceConvertible PixelYCbCr8 PixelRGB8 where - {-# INLINE convertPixel #-} - convertPixel (PixelYCbCr8 y cb cr) = PixelRGB8 (clampWord8 r) (clampWord8 g) (clampWord8 b) - where clampWord8 = fromIntegral . max 0 . min 255 - yi = fromIntegral y - cbi = fromIntegral cb - cri = fromIntegral cr - - r = yi + crRTab `V.unsafeIndex` cri - g = yi + (cbGTab `V.unsafeIndex` cbi + crGTab `V.unsafeIndex` cri) `unsafeShiftR` scaleBits - b = yi + cbBTab `V.unsafeIndex` cbi - - convertImage Image { imageWidth = w, imageHeight = h, imageData = d } = Image w h newData - where maxi = w * h - clampWord8 v | v < 0 = 0 - | v > 255 = 255 - | otherwise = fromIntegral v - - newData = runST $ do - block <- M.new $ maxi * 3 - let traductor _ idx | idx >= maxi = return block - traductor readIdx idx = do - let yi = fromIntegral $ d `V.unsafeIndex` readIdx - cbi = fromIntegral $ d `V.unsafeIndex` (readIdx + 1) - cri = fromIntegral $ d `V.unsafeIndex` (readIdx + 2) - - r = yi + crRTab `V.unsafeIndex` cri - g = yi + (cbGTab `V.unsafeIndex` cbi + crGTab `V.unsafeIndex` cri) `unsafeShiftR` scaleBits - b = yi + cbBTab `V.unsafeIndex` cbi - - (block `M.unsafeWrite` (readIdx + 0)) $ clampWord8 r - (block `M.unsafeWrite` (readIdx + 1)) $ clampWord8 g - (block `M.unsafeWrite` (readIdx + 2)) $ clampWord8 b - traductor (readIdx + 3) (idx + 1) - - traductor 0 0 >>= V.freeze - -instance ColorPlane PixelYCbCr8 PlaneLuma where - toComponentIndex _ _ = 0 - -instance ColorPlane PixelYCbCr8 PlaneCb where - toComponentIndex _ _ = 1 - -instance ColorPlane PixelYCbCr8 PlaneCr where - toComponentIndex _ _ = 2 - --------------------------------------------------- ----- PixelCMYK8 instances --------------------------------------------------- -instance Pixel PixelCMYK8 where - type PixelBaseComponent PixelCMYK8 = Word8 - - {-# INLINE mixWith #-} - mixWith f (PixelCMYK8 ca ma ya ka) (PixelCMYK8 cb mb yb kb) = - PixelCMYK8 (f 0 ca cb) (f 1 ma mb) (f 2 ya yb) (f 3 ka kb) - - {-# INLINE colorMap #-} - colorMap f (PixelCMYK8 c m y k) = PixelCMYK8 (f c) (f m) (f y) (f k) - - componentCount _ = 4 - - pixelAt image@(Image { imageData = arr }) x y = PixelCMYK8 (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 $ PixelCMYK8 rv gv bv av - where baseIdx = mutablePixelBaseIndex image x y - - writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelCMYK8 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 - - unsafePixelAt v idx = - PixelCMYK8 (V.unsafeIndex v idx) - (V.unsafeIndex v $ idx + 1) - (V.unsafeIndex v $ idx + 2) - (V.unsafeIndex v $ idx + 3) - unsafeReadPixel vec idx = - PixelCMYK8 `liftM` M.unsafeRead vec idx - `ap` M.unsafeRead vec (idx + 1) - `ap` M.unsafeRead vec (idx + 2) - `ap` M.unsafeRead vec (idx + 3) - unsafeWritePixel v idx (PixelCMYK8 r g b a) = - M.unsafeWrite v idx r >> M.unsafeWrite v (idx + 1) g - >> M.unsafeWrite v (idx + 2) b - >> M.unsafeWrite v (idx + 3) a - -instance ColorSpaceConvertible PixelCMYK8 PixelRGB8 where - convertPixel (PixelCMYK8 c m y k) = - PixelRGB8 (clampWord8 r) (clampWord8 g) (clampWord8 b) - where - clampWord8 = fromIntegral . (`unsafeShiftR` 8) - ik :: Int - ik = 255 - fromIntegral k - - r = (255 - fromIntegral c) * ik - g = (255 - fromIntegral m) * ik - b = (255 - fromIntegral y) * ik - - -{-# SPECIALIZE integralRGBToCMYK :: (Word8 -> Word8 -> Word8 -> Word8 -> b) - -> (Word8, Word8, Word8) -> b #-} -{-# SPECIALIZE integralRGBToCMYK :: (Word16 -> Word16 -> Word16 -> Word16 -> b) - -> (Word16, Word16, Word16) -> b #-} -integralRGBToCMYK :: (Bounded a, Integral a) - => (a -> a -> a -> a -> b) -- ^ Pixel building function - -> (a, a, a) -- ^ RGB sample - -> b -- ^ Resulting sample -integralRGBToCMYK build (r, g, b) = - build (clamp c) (clamp m) (clamp y) (fromIntegral kInt) - where maxi = maxBound - - ir = fromIntegral $ maxi - r :: Int - ig = fromIntegral $ maxi - g - ib = fromIntegral $ maxi - b - - kInt = minimum [ir, ig, ib] - ik = fromIntegral maxi - kInt - - c = (ir - kInt) `div` ik - m = (ig - kInt) `div` ik - y = (ib - kInt) `div` ik - - clamp = fromIntegral . (max 0) - -instance ColorSpaceConvertible PixelRGB8 PixelCMYK8 where - convertPixel (PixelRGB8 r g b) = integralRGBToCMYK PixelCMYK8 (r, g, b) - -instance ColorPlane PixelCMYK8 PlaneCyan where - toComponentIndex _ _ = 0 - -instance ColorPlane PixelCMYK8 PlaneMagenta where - toComponentIndex _ _ = 1 - -instance ColorPlane PixelCMYK8 PlaneYellow where - toComponentIndex _ _ = 2 - -instance ColorPlane PixelCMYK8 PlaneBlack where - toComponentIndex _ _ = 3 - --------------------------------------------------- ----- PixelCMYK16 instances --------------------------------------------------- -instance Pixel PixelCMYK16 where - type PixelBaseComponent PixelCMYK16 = Word16 - - {-# INLINE mixWith #-} - mixWith f (PixelCMYK16 ca ma ya ka) (PixelCMYK16 cb mb yb kb) = - PixelCMYK16 (f 0 ca cb) (f 1 ma mb) (f 2 ya yb) (f 3 ka kb) - - {-# INLINE colorMap #-} - colorMap f (PixelCMYK16 c m y k) = PixelCMYK16 (f c) (f m) (f y) (f k) - - componentCount _ = 4 - - pixelAt image@(Image { imageData = arr }) x y = PixelCMYK16 (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 $ PixelCMYK16 rv gv bv av - where baseIdx = mutablePixelBaseIndex image x y - - writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelCMYK16 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 - - unsafePixelAt v idx = - PixelCMYK16 (V.unsafeIndex v idx) - (V.unsafeIndex v $ idx + 1) - (V.unsafeIndex v $ idx + 2) - (V.unsafeIndex v $ idx + 3) - unsafeReadPixel vec idx = - PixelCMYK16 `liftM` M.unsafeRead vec idx - `ap` M.unsafeRead vec (idx + 1) - `ap` M.unsafeRead vec (idx + 2) - `ap` M.unsafeRead vec (idx + 3) - unsafeWritePixel v idx (PixelCMYK16 r g b a) = - M.unsafeWrite v idx r >> M.unsafeWrite v (idx + 1) g - >> M.unsafeWrite v (idx + 2) b - >> M.unsafeWrite v (idx + 3) a - -instance ColorSpaceConvertible PixelCMYK16 PixelRGB16 where - convertPixel (PixelCMYK16 c m y k) = - PixelRGB16 (clampWord16 r) (clampWord16 g) (clampWord16 b) - where - clampWord16 = fromIntegral . (`unsafeShiftR` 16) - ik :: Int - ik = 65535 - fromIntegral k - - r = (65535 - fromIntegral c) * ik - g = (65535 - fromIntegral m) * ik - b = (65535 - fromIntegral y) * ik - -instance ColorPlane PixelCMYK16 PlaneCyan where - toComponentIndex _ _ = 0 - -instance ColorPlane PixelCMYK16 PlaneMagenta where - toComponentIndex _ _ = 1 - -instance ColorPlane PixelCMYK16 PlaneYellow where - toComponentIndex _ _ = 2 - -instance ColorPlane PixelCMYK16 PlaneBlack where - toComponentIndex _ _ = 3 - --- | Perform a gamma correction for an image with HDR pixels. -gammaCorrection :: PixelF -- ^ Gamma value, should be between 0.5 and 3.0 - -> Image PixelRGBF -- ^ Image to treat. - -> Image PixelRGBF -gammaCorrection gammaVal = pixelMap gammaCorrector - where gammaExponent = 1.0 / gammaVal - fixVal v = v ** gammaExponent - gammaCorrector (PixelRGBF r g b) = - PixelRGBF (fixVal r) (fixVal g) (fixVal b) - --- | Perform a tone mapping operation on an High dynamic range image. -toneMapping :: PixelF -- ^ Exposure parameter - -> Image PixelRGBF -- ^ Image to treat. - -> Image PixelRGBF -toneMapping exposure img = Image (imageWidth img) (imageHeight img) scaledData - where coeff = exposure * (exposure / maxBrightness + 1.0) / (exposure + 1.0); - maxBrightness = pixelFold (\luma _ _ px -> max luma $ computeLuma px) 0 img - scaledData = V.map (* coeff) $ imageData img -
− Codec/Picture/VectorByteConversion.hs
@@ -1,20 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-} -module Codec.Picture.VectorByteConversion( blitVector, toByteString ) where - -import Data.Word( Word8 ) -import Data.Vector.Storable( Vector, unsafeToForeignPtr ) -import Foreign.ForeignPtr.Safe( castForeignPtr ) -import Foreign.Storable( Storable, sizeOf ) - -import qualified Data.ByteString as B -import qualified Data.ByteString.Internal as S - -blitVector :: Vector Word8 -> Int -> Int -> B.ByteString -blitVector vec atIndex blitSize = S.PS ptr (offset + atIndex) blitSize - where (ptr, offset, _length) = unsafeToForeignPtr vec - -toByteString :: forall a. (Storable a) => Vector a -> B.ByteString -toByteString vec = S.PS (castForeignPtr ptr) offset (len * size) - where (ptr, offset, len) = unsafeToForeignPtr vec - size = sizeOf (undefined :: a) -
JuicyPixels.cabal view
@@ -1,10 +1,10 @@ Name: JuicyPixels -Version: 3.1.2 +Version: 3.1.3 Synopsis: Picture loading/serialization (in png, jpeg, bitmap, gif, tiff and radiance) Description: <<data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADABAMAAACg8nE0AAAAElBMVEUAAABJqDSTWEL/qyb///8AAABH/1GTAAAAAXRSTlMAQObYZgAAAN5JREFUeF7s1sEJgFAQxFBbsAV72v5bEVYWPwT/XDxmCsi7zvHXavYREBDI3XP2GgICqBBYuwIC+/rVayPUAyAg0HvIXBcQoDFDGnUBgWQQ2Bx3AYFaRoBpAQHWb3bt2ARgGAiCYFFuwf3X5HA/McgGJWI2FdykCv4aBYzmKwDwvl6NVmUAAK2vlwEALK7fo88GANB6HQsAAAAAAAAA7P94AQCzswEAAAAAAAAAAAAAAAAAAICzh4UAO4zWAYBfRutHA4Bn5C69JhowAMGoBaMWDG0wCkbBKBgFo2AUAACPmegUST/IJAAAAABJRU5ErkJggg==>> . - This library can load and store images in PNG,Bitmap, Jpeg, Radiance, Tiff and read Gif images. + This library can load and store images in PNG,Bitmap, Jpeg, Radiance, Tiff and Gif images. homepage: https://github.com/Twinside/Juicy.Pixels License: BSD3 @@ -26,13 +26,14 @@ Source-Repository this Type: git Location: git://github.com/Twinside/Juicy.Pixels.git - Tag: v3.1.2 + Tag: v3.1.3 Flag Mmap Description: Enable the file loading via mmap (memory map) Default: False Library + hs-source-dirs: src Default-Language: Haskell2010 Exposed-modules: Codec.Picture, Codec.Picture.Bitmap, @@ -42,7 +43,8 @@ Codec.Picture.HDR, Codec.Picture.Tiff, Codec.Picture.Saving, - Codec.Picture.Types + Codec.Picture.Types, + Codec.Picture.ColorQuant Ghc-options: -O3 -Wall Build-depends: base >= 4 && < 5, @@ -53,7 +55,8 @@ transformers >= 0.2.2 && < 0.4, vector >= 0.9 && < 0.11, primitive >= 0.5 && < 0.6, - deepseq >= 1.1 && < 1.4 + deepseq >= 1.1 && < 1.4, + containers >= 0.5 && < 0.6 if flag(Mmap) Build-depends: mmap @@ -67,12 +70,13 @@ Codec.Picture.Jpg.Common, Codec.Picture.Jpg.Progressive, Codec.Picture.Gif.LZW, + Codec.Picture.Gif.LZWEncoding, Codec.Picture.Png.Export, Codec.Picture.Png.Type, Codec.Picture.BitWriter, Codec.Picture.InternalHelper, Codec.Picture.VectorByteConversion - Install-Includes: Codec/Picture/ConvGraph.hs - Include-Dirs: Codec/Picture + Install-Includes: src/Codec/Picture/ConvGraph.hs + Include-Dirs: src/Codec/Picture
changelog view
@@ -1,5 +1,11 @@ -*-change-log-*- +v3.1.3 + * Adding palette creation (color Quantization) by Jeffrey Rosenbluth. + * Adding support for Gif writing + * Adding support for Gif animation writing + * Fixing some progressive Jpeg decoding problems (#39) + v3.1.2 December 2013 * Adding support for progressive jpeg. * Adding support for plane separated MCU jpeg.
+ src/Codec/Picture.hs view
@@ -0,0 +1,304 @@+{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE CPP #-} +-- | Main module for image import/export 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 'saveBmpImage', 'saveJpgImage' & 'savePngImage' +-- functions +module Codec.Picture ( + -- * Generic functions + readImage + , decodeImage + , pixelMap + , generateImage + , generateFoldImage + , withImage + + -- * Generic image writing + , saveBmpImage + , saveJpgImage + , saveGifImage + , savePngImage + , saveTiffImage + , saveRadianceImage + + -- * Specific image format functions + -- ** Bitmap handling + , BmpEncodable + , writeBitmap + , encodeBitmap + , readBitmap + , decodeBitmap + , encodeDynamicBitmap + , writeDynamicBitmap + + -- ** Gif handling + , readGif + , readGifImages + , decodeGif + , decodeGifImages + + , encodeGifImage + , writeGifImage + , encodeGifImageWithPalette + , writeGifImageWithPalette + , encodeColorReducedGifImage + , writeColorReducedGifImage + , encodeGifImages + , writeGifImages + + -- *** Gif animation + , GifDelay + , GifLooping( .. ) + , encodeGifAnimation + , writeGifAnimation + + -- ** Jpeg handling + , readJpeg + , decodeJpeg + , encodeJpeg + , encodeJpegAtQuality + + -- ** Png handling + , PngSavable( .. ) + , readPng + , decodePng + , writePng + , encodePalettedPng + , encodeDynamicPng + , writeDynamicPng + + -- ** Tiff handling + , readTiff + , TiffSaveable + , decodeTiff + , encodeTiff + , writeTiff + + -- ** HDR (Radiance/RGBE) handling + , readHDR + , decodeHDR + , encodeHDR + , writeHDR + + -- ** Color Quantization + , PaletteCreationMethod(..) + , PaletteOptions(..) + , palettize + + -- * Image types and pixel types + -- ** Image + , Image( .. ) + , DynamicImage( .. ) + , Palette + -- ** Pixels + , Pixel( .. ) + -- $graph + , Pixel8 + , Pixel16 + , PixelF + + , PixelYA8( .. ) + , PixelYA16( .. ) + , PixelRGB8( .. ) + , PixelRGB16( .. ) + , PixelRGBF( .. ) + , PixelRGBA8( .. ) + , PixelRGBA16( .. ) + , PixelYCbCr8( .. ) + , PixelCMYK8( .. ) + , PixelCMYK16( .. ) + + -- * Foreign unsafe import + , imageFromUnsafePtr + ) where + +import Control.Applicative( (<$>) ) +import Control.DeepSeq( NFData, deepseq ) +import qualified Control.Exception as Exc ( catch, IOException ) +import Codec.Picture.Bitmap( BmpEncodable, decodeBitmap + , writeBitmap, encodeBitmap + , encodeDynamicBitmap, writeDynamicBitmap ) +import Codec.Picture.Jpg( decodeJpeg, encodeJpeg, encodeJpegAtQuality ) +import Codec.Picture.Png( PngSavable( .. ), decodePng, writePng + , encodeDynamicPng + , encodePalettedPng + , writeDynamicPng + ) + +import Codec.Picture.Gif( GifDelay + , GifLooping( .. ) + , decodeGif + , decodeGifImages + , encodeGifImage + , encodeGifImageWithPalette + , encodeGifImages + + , writeGifImage + , writeGifImageWithPalette + , writeGifImages + ) + +import Codec.Picture.HDR( decodeHDR + , encodeHDR + , writeHDR + ) +import Codec.Picture.Tiff( decodeTiff + , TiffSaveable + , encodeTiff + , writeTiff ) +import Codec.Picture.Saving +import Codec.Picture.Types +import Codec.Picture.ColorQuant +import Codec.Picture.VectorByteConversion( imageFromUnsafePtr ) +-- import System.IO ( withFile, IOMode(ReadMode) ) +#ifdef WITH_MMAP_BYTESTRING +import System.IO.MMap ( mmapFileByteString ) +#endif + +import qualified Data.ByteString as B +import qualified Data.ByteString.Lazy as L + +#include "ConvGraph.hs" + +-- | 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 + +-- | Encode a full color image to a gif by applying a color quantization +-- algorithm on it. +encodeColorReducedGifImage :: Image PixelRGB8 -> Either String L.ByteString +encodeColorReducedGifImage img = encodeGifImageWithPalette indexed pal + where (indexed, pal) = palettize defaultPaletteOptions img + +-- | Write a full color image to a gif by applying a color quantization +-- algorithm on it. +writeColorReducedGifImage :: FilePath -> Image PixelRGB8 -> Either String (IO ()) +writeColorReducedGifImage path img = + L.writeFile path <$> encodeColorReducedGifImage img + + +-- | Helper function to create a gif animation. +-- All the images of the animation are separated +-- by the same delay. +encodeGifAnimation :: GifDelay -> GifLooping + -> [Image PixelRGB8] -> Either String (L.ByteString) +encodeGifAnimation delay looping lst = + encodeGifImages looping + [(pal, delay, img) + | (img, pal) <- palettize defaultPaletteOptions <$> lst] + +-- | Helper function to write a gif animtion on disk. +-- See encodeGifAnimation +writeGifAnimation :: FilePath -> GifDelay -> GifLooping + -> [Image PixelRGB8] -> Either String (IO ()) +writeGifAnimation path delay looping img = + L.writeFile path <$> encodeGifAnimation delay looping img + +withImageDecoder :: (NFData a) + => (B.ByteString -> Either String a) -> FilePath + -> IO (Either String a) +withImageDecoder decoder path = Exc.catch doit + (\e -> return . Left $ show (e :: Exc.IOException)) + where doit = force . decoder <$> get +#ifdef WITH_MMAP_BYTESTRING + get = mmapFileByteString path Nothing +#else + get = B.readFile path +#endif + -- force appeared in deepseq 1.3, Haskell Platform + -- provide 1.1 + force x = x `deepseq` x + +-- | Load an image file without even thinking about it, it does everything +-- as 'decodeImage' +readImage :: FilePath -> IO (Either String DynamicImage) +readImage = withImageDecoder decodeImage + +-- | 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) + ,("Bitmap", decodeBitmap) + ,("GIF", decodeGif) + ,("HDR", decodeHDR) + ,("Tiff", decodeTiff) + ] + +-- | Helper function trying to load a png file from a file on disk. +readPng :: FilePath -> IO (Either String DynamicImage) +readPng = withImageDecoder decodePng + +-- | Helper function trying to load a gif file from a file on disk. +readGif :: FilePath -> IO (Either String DynamicImage) +readGif = withImageDecoder decodeGif + +-- | Helper function trying to load tiff file from a file on disk. +readTiff :: FilePath -> IO (Either String DynamicImage) +readTiff = withImageDecoder decodeTiff + +-- | Helper function trying to load all the images of an animated +-- gif file. +readGifImages :: FilePath -> IO (Either String [Image PixelRGB8]) +readGifImages = withImageDecoder decodeGifImages + +-- | 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 = withImageDecoder decodeJpeg + +-- | Try to load a .bmp file. The colorspace would be RGB or RGBA +readBitmap :: FilePath -> IO (Either String DynamicImage) +readBitmap = withImageDecoder decodeBitmap + +-- | Try to load a .pic file. The colorspace can only be +-- RGB with floating point precision. +readHDR :: FilePath -> IO (Either String DynamicImage) +readHDR = withImageDecoder decodeHDR + +-- | Save an image to a '.jpg' file, will do everything it can to save an image. +saveJpgImage :: Int -> FilePath -> DynamicImage -> IO () +saveJpgImage quality path img = L.writeFile path $ imageToJpg quality img + +-- | Save an image to a '.gif' file, will do everything it can to save it. +saveGifImage :: FilePath -> DynamicImage -> Either String (IO ()) +saveGifImage path img = L.writeFile path <$> imageToGif img + +-- | Save an image to a '.tiff' file, will do everything it can to save an image. +saveTiffImage :: FilePath -> DynamicImage -> IO () +saveTiffImage path img = L.writeFile path $ imageToTiff img + +-- | Save an image to a '.hdr' file, will do everything it can to save an image. +saveRadianceImage :: FilePath -> DynamicImage -> IO () +saveRadianceImage path = L.writeFile path . imageToRadiance + +-- | Save an image to a '.png' file, will do everything it can to save an image. +-- For example, a simple transcoder to png +-- +-- > transcodeToPng :: FilePath -> FilePath -> IO () +-- > transcodeToPng pathIn pathOut = do +-- > eitherImg <- readImage pathIn +-- > case eitherImg of +-- > Left _ -> return () +-- > Right img -> savePngImage pathOut img +-- +savePngImage :: FilePath -> DynamicImage -> IO () +savePngImage path img = L.writeFile path $ imageToPng img + +-- | Save an image to a '.bmp' file, will do everything it can to save an image. +saveBmpImage :: FilePath -> DynamicImage -> IO () +saveBmpImage path img = L.writeFile path $ imageToBitmap img +
+ src/Codec/Picture/BitWriter.hs view
@@ -0,0 +1,299 @@+{-# LANGUAGE Rank2Types #-} +-- | This module implement helper functions to read & write data +-- at bits level. +module Codec.Picture.BitWriter( BoolReader + , emptyBoolState + , BoolState + , byteAlignJpg + , getNextBitsLSBFirst + , getNextBitsMSBFirst + , getNextBitJpg + , setDecodedString + , setDecodedStringJpg + , runBoolReader + + , BoolWriteStateRef + , newWriteStateRef + , finalizeBoolWriter + , writeBits' + , writeBitsGif + + , initBoolState + , initBoolStateJpg + , execBoolReader + , runBoolReaderWith + ) where + +import Data.STRef +import Control.Applicative( (<$>), (<*>) ) +import Control.Monad( when ) +import Control.Monad.ST( ST ) +import qualified Control.Monad.Trans.State.Strict as S +import Data.Word( Word8, Word32 ) +import Data.Bits( Bits, (.&.), (.|.), unsafeShiftR, unsafeShiftL ) + +import Codec.Picture.VectorByteConversion( blitVector ) +import qualified Data.Vector.Storable.Mutable as M +import qualified Data.Vector.Storable as VS +import qualified Data.ByteString as B +import qualified Data.ByteString.Lazy as L + +-------------------------------------------------- +---- Reader +-------------------------------------------------- +-- | Current bit index, current value, string +data BoolState = BoolState {-# UNPACK #-} !Int + {-# UNPACK #-} !Word8 + !B.ByteString + +emptyBoolState :: BoolState +emptyBoolState = BoolState (-1) 0 B.empty + +-- | Type used to read bits +type BoolReader s a = S.StateT BoolState (ST s) a + +runBoolReader :: BoolReader s a -> ST s a +runBoolReader action = S.evalStateT action $ BoolState 0 0 B.empty + +runBoolReaderWith :: BoolState -> BoolReader s a -> ST s (a, BoolState) +runBoolReaderWith st action = S.runStateT action st + +execBoolReader :: BoolState -> BoolReader s a -> ST s BoolState +execBoolReader st reader = S.execStateT reader st + +initBoolState :: B.ByteString -> BoolState +initBoolState str = case B.uncons str of + Nothing -> BoolState 0 0 B.empty + Just (v, rest) -> BoolState 0 v rest + +initBoolStateJpg :: B.ByteString -> BoolState +initBoolStateJpg str = + case B.uncons str of + Nothing -> BoolState 0 0 B.empty + Just (0xFF, rest) -> case B.uncons rest of + Nothing -> BoolState maxBound 0 B.empty + Just (0x00, afterMarker) -> BoolState 7 0xFF afterMarker + Just (_ , afterMarker) -> initBoolStateJpg afterMarker + Just (v, rest) -> BoolState 7 v rest + +-- | Bitify a list of things to decode. +setDecodedString :: B.ByteString -> BoolReader s () +setDecodedString str = case B.uncons str of + Nothing -> S.put $ BoolState 0 0 B.empty + Just (v, rest) -> S.put $ BoolState 0 v rest + +-- | Drop all bit until the bit of indice 0, usefull to parse restart +-- marker, as they are byte aligned, but Huffman might not. +byteAlignJpg :: BoolReader s () +byteAlignJpg = do + BoolState idx _ chain <- S.get + when (idx /= 7) (setDecodedStringJpg chain) + +{-# INLINE getNextBitJpg #-} +getNextBitJpg :: BoolReader s Bool +getNextBitJpg = do + BoolState idx v chain <- S.get + let val = (v .&. (1 `unsafeShiftL` idx)) /= 0 + if idx == 0 + then setDecodedStringJpg chain + else S.put $ BoolState (idx - 1) v chain + return val + +{-# INLINE getNextBitMSB #-} +getNextBitMSB :: BoolReader s Bool +getNextBitMSB = do + BoolState idx v chain <- S.get + let val = (v .&. (1 `unsafeShiftL` (7 - idx))) /= 0 + if idx == 7 + then setDecodedString chain + else S.put $ BoolState (idx + 1) v chain + return val + +{-# INLINE getNextBitsMSBFirst #-} +getNextBitsMSBFirst :: Int -> BoolReader s Word32 +getNextBitsMSBFirst count = aux 0 count + where aux acc 0 = return acc + aux acc n = do + bit <- getNextBitMSB + let nextVal | bit = (acc `unsafeShiftL` 1) .|. 1 + | otherwise = acc `unsafeShiftL` 1 + aux nextVal (n - 1) + +{-# INLINE getNextBitsLSBFirst #-} +getNextBitsLSBFirst :: Int -> BoolReader s Word32 +getNextBitsLSBFirst count = aux 0 count + where aux acc 0 = return acc + aux acc n = do + bit <- getNextBit + let nextVal | bit = acc .|. (1 `unsafeShiftL` (count - n)) + | otherwise = acc + aux nextVal (n - 1) + +{-# INLINE getNextBit #-} +getNextBit :: BoolReader s Bool +getNextBit = do + BoolState idx v chain <- S.get + let val = (v .&. (1 `unsafeShiftL` idx)) /= 0 + if idx == 7 + then setDecodedString chain + else S.put $ BoolState (idx + 1) v chain + return val + +-- | Bitify a list of things to decode. Handle Jpeg escape +-- code (0xFF 0x00), thus should be only used in JPEG decoding. +setDecodedStringJpg :: B.ByteString -> BoolReader s () +setDecodedStringJpg str = case B.uncons str of + Nothing -> S.put $ BoolState maxBound 0 B.empty + Just (0xFF, rest) -> case B.uncons rest of + Nothing -> S.put $ BoolState maxBound 0 B.empty + Just (0x00, afterMarker) -> -- trace "00" $ + S.put $ BoolState 7 0xFF afterMarker + Just (_ , afterMarker) -> setDecodedStringJpg afterMarker + Just (v, rest) -> + S.put $ BoolState 7 v rest + +-------------------------------------------------- +---- Writer +-------------------------------------------------- +defaultBufferSize :: Int +defaultBufferSize = 256 * 1024 + +data BoolWriteStateRef s = BoolWriteStateRef + { bwsCurrBuffer :: STRef s (M.MVector s Word8) + , bwsBufferList :: STRef s [B.ByteString] + , bwsWrittenWords :: STRef s Int + , bwsBitAcc :: STRef s Word8 + , bwsBitReaded :: STRef s Int + } + +newWriteStateRef :: ST s (BoolWriteStateRef s) +newWriteStateRef = do + origMv <- M.new defaultBufferSize + BoolWriteStateRef <$> newSTRef origMv + <*> newSTRef [] + <*> newSTRef 0 + <*> newSTRef 0 + <*> newSTRef 0 + +finalizeBoolWriter :: BoolWriteStateRef s -> ST s L.ByteString +finalizeBoolWriter st = do + forceBufferFlushing' st + L.fromChunks <$> readSTRef (bwsBufferList st) + +forceBufferFlushing' :: BoolWriteStateRef s -> ST s () +forceBufferFlushing' (BoolWriteStateRef { bwsCurrBuffer = vecRef + , bwsWrittenWords = countRef + , bwsBufferList = lstRef + }) = do + vec <- readSTRef vecRef + count <- readSTRef countRef + lst <- readSTRef lstRef + + nmv <- M.new defaultBufferSize + str <- byteStringFromVector vec count + + writeSTRef vecRef nmv + writeSTRef lstRef $ lst ++ [str] + writeSTRef countRef 0 + +flushCurrentBuffer' :: BoolWriteStateRef s -> ST s () +flushCurrentBuffer' st = do + count <- readSTRef $ bwsWrittenWords st + when (count >= defaultBufferSize) + (forceBufferFlushing' st) + +byteStringFromVector :: M.MVector s Word8 -> Int -> ST s B.ByteString +byteStringFromVector vec size = do + frozen <- VS.unsafeFreeze vec + return $ blitVector frozen 0 size + +setBitCount' :: BoolWriteStateRef s -> Word8 -> Int -> ST s () +{-# INLINE setBitCount' #-} +setBitCount' st acc count = do + writeSTRef (bwsBitAcc st) acc + writeSTRef (bwsBitReaded st) count + +resetBitCount' :: BoolWriteStateRef s -> ST s () +{-# INLINE resetBitCount' #-} +resetBitCount' st = setBitCount' st 0 0 + +pushByte' :: BoolWriteStateRef s -> Word8 -> ST s () +{-# INLINE pushByte' #-} +pushByte' st v = do + flushCurrentBuffer' st + idx <- readSTRef (bwsWrittenWords st) + vec <- readSTRef (bwsCurrBuffer st) + M.write vec idx v + writeSTRef (bwsWrittenWords st) $ idx + 1 + +-- | Append some data bits to a Put monad. +writeBits' :: BoolWriteStateRef s + -> Word32 -- ^ The real data to be stored. Actual data should be in the LSB + -> Int -- ^ Number of bit to write from 1 to 32 + -> ST s () +{-# INLINE writeBits' #-} +writeBits' st d c = do + currWord <- readSTRef $ bwsBitAcc st + currCount <- readSTRef $ bwsBitReaded st + serialize d c currWord currCount + where dumpByte 0xFF = pushByte' st 0xFF >> pushByte' st 0x00 + dumpByte i = pushByte' st i + + serialize bitData bitCount currentWord count + | bitCount + count == 8 = do + resetBitCount' st + dumpByte (fromIntegral $ (currentWord `unsafeShiftL` bitCount) .|. + fromIntegral cleanData) + + | bitCount + count < 8 = + let newVal = currentWord `unsafeShiftL` bitCount + in setBitCount' st (newVal .|. fromIntegral cleanData) $ count + bitCount + + | otherwise = + let leftBitCount = 8 - count :: Int + highPart = cleanData `unsafeShiftR` (bitCount - leftBitCount) :: Word32 + prevPart = fromIntegral currentWord `unsafeShiftL` leftBitCount :: Word32 + + nextMask = (1 `unsafeShiftL` (bitCount - leftBitCount)) - 1 :: Word32 + newData = cleanData .&. nextMask :: Word32 + newCount = bitCount - leftBitCount :: Int + + toWrite = fromIntegral $ prevPart .|. highPart :: Word8 + in dumpByte toWrite >> serialize newData newCount 0 0 + + where cleanMask = (1 `unsafeShiftL` bitCount) - 1 :: Word32 + cleanData = bitData .&. cleanMask :: Word32 + +-- | Append some data bits to a Put monad. +writeBitsGif :: BoolWriteStateRef s + -> Word32 -- ^ The real data to be stored. Actual data should be in the LSB + -> Int -- ^ Number of bit to write from 1 to 32 + -> ST s () +{-# INLINE writeBitsGif #-} +writeBitsGif st d c = do + currWord <- readSTRef $ bwsBitAcc st + currCount <- readSTRef $ bwsBitReaded st + serialize d c currWord currCount + where dumpByte i = pushByte' st i + + serialize bitData bitCount currentWord count + | bitCount + count == 8 = do + resetBitCount' st + dumpByte (fromIntegral $ currentWord .|. + (fromIntegral cleanData `unsafeShiftL` count)) + + | bitCount + count < 8 = + let newVal = fromIntegral cleanData `unsafeShiftL` count + in setBitCount' st (newVal .|. currentWord) $ count + bitCount + + | otherwise = + let leftBitCount = 8 - count :: Int + newData = cleanData `unsafeShiftR` leftBitCount :: Word32 + newCount = bitCount - leftBitCount :: Int + toWrite = fromIntegral $ fromIntegral currentWord + .|. (cleanData `unsafeShiftL` count) :: Word8 + in dumpByte toWrite >> serialize newData newCount 0 0 + + where cleanMask = (1 `unsafeShiftL` bitCount) - 1 :: Word32 + cleanData = bitData .&. cleanMask :: Word32 +
+ src/Codec/Picture/Bitmap.hs view
@@ -0,0 +1,350 @@+{-# 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 Control.Monad.ST ( ST, runST ) +import qualified Data.Vector.Storable as V +import qualified Data.Vector.Storable.Mutable as M +import Data.Binary( Binary( .. ) ) +import Data.Binary.Put( Put + , runPut + , putWord16le + , putWord32le + , putByteString + ) + +import Data.Binary.Get( Get + , getWord16le + , getWord32le + , bytesRead + , skip + ) + +import Data.Word( Word32, Word16, Word8 ) +import qualified Data.ByteString as B +import qualified Data.ByteString.Lazy as L + +import Codec.Picture.InternalHelper +import Codec.Picture.Types +import Codec.Picture.VectorByteConversion + +data BmpHeader = BmpHeader + { magicIdentifier :: !Word16 + , fileSize :: !Word32 -- ^ in bytes + , reserved1 :: !Word16 + , reserved2 :: !Word16 + , dataOffset :: !Word32 + } + +bitmapMagicIdentifier :: Word16 +bitmapMagicIdentifier = 0x4D42 + +instance Binary 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 Binary 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 [] + +stridePut :: M.STVector s Word8 -> Int -> Int -> ST s () +{-# INLINE stridePut #-} +stridePut vec = inner + where inner _ 0 = return () + inner ix n = do + (vec `M.unsafeWrite` ix) 0 + inner (ix + 1) (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}) = + forM_ [h - 1, h - 2 .. 0] $ \l -> putVector $ runST $ encodeLine l + where stride = fromIntegral $ linePadding 8 w + putVector vec = putByteString $ blitVector vec 0 lineWidth + lineWidth = w + stride + + encodeLine line = do + buff <- M.new lineWidth + + let lineIdx = line * w + inner col | col >= w = return () + inner col = do + let v = (arr `V.unsafeIndex` (lineIdx + col)) + (buff `M.unsafeWrite` col) v + inner (col + 1) + + inner 0 + + stridePut buff w stride + V.unsafeFreeze buff + +instance BmpEncodable PixelRGBA8 where + bitsPerPixel _ = 32 + bmpEncode (Image {imageWidth = w, imageHeight = h, imageData = arr}) = + forM_ [h - 1, h - 2 .. 0] $ \l -> putVector $ runST $ putLine l + where putVector vec = putByteString . blitVector vec 0 $ w * 4 + putLine line = do + buff <- M.new $ 4 * w + let initialIndex = line * w * 4 + inner col _ _ | col >= w = return () + inner col writeIdx readIdx = do + let r = arr `V.unsafeIndex` readIdx + g = arr `V.unsafeIndex` (readIdx + 1) + b = arr `V.unsafeIndex` (readIdx + 2) + a = arr `V.unsafeIndex` (readIdx + 3) + + (buff `M.unsafeWrite` writeIdx) b + (buff `M.unsafeWrite` (writeIdx + 1)) g + (buff `M.unsafeWrite` (writeIdx + 2)) r + (buff `M.unsafeWrite` (writeIdx + 3)) a + + inner (col + 1) (writeIdx + 4) (readIdx + 4) + + inner 0 0 initialIndex + V.unsafeFreeze buff + +instance BmpEncodable PixelRGB8 where + bitsPerPixel _ = 24 + bmpEncode (Image {imageWidth = w, imageHeight = h, imageData = arr}) = + forM_ [h - 1, h - 2 .. 0] $ \l -> putVector $ runST $ putLine l + where stride = fromIntegral . linePadding 24 $ w + putVector vec = putByteString $ blitVector vec 0 (w * 3 + stride) + putLine line = do + buff <- M.new $ w * 3 + stride + let initialIndex = line * w * 3 + inner col _ _ | col >= w = return () + inner col writeIdx readIdx = do + let r = (arr `V.unsafeIndex` readIdx) + g = (arr `V.unsafeIndex` (readIdx + 1)) + b = (arr `V.unsafeIndex` (readIdx + 2)) + + (buff `M.unsafeWrite` writeIdx) b + (buff `M.unsafeWrite` (writeIdx + 1)) g + (buff `M.unsafeWrite` (writeIdx + 2)) r + + inner (col + 1) (writeIdx + 3) (readIdx + 3) + + inner 0 0 initialIndex + V.unsafeFreeze buff + +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 = runST $ do + arr <- M.new (fromIntegral $ w * h * 3) + forM_ [hi - 1, hi - 2 .. 0] (readLine arr) + V.unsafeFreeze 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 `M.unsafeWrite` writeIdx ) (str `B.index` (readIdx + 2)) + (arr `M.unsafeWrite` (writeIdx + 1)) (str `B.index` (readIdx + 1)) + (arr `M.unsafeWrite` (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 runGetStrict 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 + readed <- bytesRead + + when (readed > fromIntegral (dataOffset hdr)) + (fail "Invalid bmp image, data in header") + + skip . fromIntegral $ dataOffset hdr - fromIntegral readed + rest <- getRemainingBytes + return . ImageRGB8 $ decodeImageRGB8 bmpHeader rest + a -> fail $ "Can't handle BMP file " ++ show a + + +-- | Write an image in a file use the bitmap format. +writeBitmap :: (BmpEncodable pixel) + => FilePath -> Image pixel -> IO () +writeBitmap filename img = L.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 -> L.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 -> L.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 L.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 -> L.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 + } + +
+ src/Codec/Picture/ColorQuant.hs view
@@ -0,0 +1,380 @@+{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE BangPatterns #-} +-- | This module provide some color quantisation algorithm +-- in order to help in the creation of paletted images. +-- The most important function is `palettize` which will +-- make everything to create a nice color indexed image +-- with its palette. +module Codec.Picture.ColorQuant + ( palettize + , defaultPaletteOptions + , PaletteCreationMethod(..) + , PaletteOptions( .. ) + ) where + +import Control.Applicative (Applicative (..), (<$>)) +import Data.Bits (unsafeShiftL, unsafeShiftR, (.&.), (.|.)) +import Data.List (elemIndex) +import Data.Maybe (fromMaybe) +import Data.Set (Set) +import qualified Data.Set as Set +import Data.Word (Word32) + +import Data.Vector (Vector, (!)) +import qualified Data.Vector as V +import qualified Data.Vector.Unboxed as VU +import qualified Data.Vector.Storable as VS + +import Codec.Picture.Types + +------------------------------------------------------------------------------- +---- Palette Creation and Dithering +------------------------------------------------------------------------------- + +-- | Define which palette creation method is used. +data PaletteCreationMethod = + -- | MedianMeanCut method, provide the best results (visualy) + -- at the cost of increased calculations. + MedianMeanCut + -- | Very fast algorithm (one pass), doesn't provide good + -- looking results. + | Uniform + +-- | To specify how the palette will be created. +data PaletteOptions = PaletteOptions + { -- | Algorithm used to find the palette + paletteCreationMethod :: PaletteCreationMethod + + -- | Do we want to apply the dithering to the + -- image. Enabling it often reduce compression + -- ratio but enhance the perceived quality + -- of the final image. + , enableImageDithering :: Bool + + -- | Maximum number of color we want in the + -- palette + , paletteColorCount :: Int + } + +-- | Default palette option, which aim at the best quality +-- and maximum possible colors (256) +defaultPaletteOptions :: PaletteOptions +defaultPaletteOptions = PaletteOptions + { paletteCreationMethod = MedianMeanCut + , enableImageDithering = True + , paletteColorCount = 256 + } + +-- | Reduces an image to a color palette according to `PaletteOpts` and +-- returns the /indices image/ along with its `Palette`. +palettize :: PaletteOptions -> Image PixelRGB8 -> (Image Pixel8, Palette) +palettize opts@PaletteOptions { paletteCreationMethod = method } = + case method of + MedianMeanCut -> medianMeanCutQuantization opts + Uniform -> uniformQuantization opts + +-- | Modified median cut algorithm with optional ordered dithering. Returns an +-- image of `Pixel8` that acts as a matrix of indices into the `Palette`. +medianMeanCutQuantization :: PaletteOptions -> Image PixelRGB8 + -> (Image Pixel8, Palette) +medianMeanCutQuantization opts img + | isBelow = + (pixelMap okPaletteIndex img, vecToPalette okPaletteVec) + | enableImageDithering opts = (pixelMap paletteIndex dImg, palette) + | otherwise = (pixelMap paletteIndex img, palette) + where + maxColorCount = paletteColorCount opts + (okPalette, isBelow) = isColorCountBelow maxColorCount img + okPaletteVec = V.fromList $ Set.toList okPalette + okPaletteIndex p = nearestColorIdx p okPaletteVec + + palette = vecToPalette paletteVec + paletteIndex p = nearestColorIdx p paletteVec + paletteVec = mkPaletteVec cs + cs = Set.toList . clusters maxColorCount $ img + dImg = pixelMapXY dither img + +-- | A naive one pass Color Quantiation algorithm - Uniform Quantization. +-- Simply take the most significant bits. The maxCols parameter is rounded +-- down to the nearest power of 2, and the bits are divided among the three +-- color channels with priority order green, red, blue. Returns an +-- image of `Pixel8` that acts as a matrix of indices into the `Palette`. +uniformQuantization :: PaletteOptions -> Image PixelRGB8 -> (Image Pixel8, Palette) +uniformQuantization opts img + -- -| colorCount img <= maxCols = colorQuantExact img + | enableImageDithering opts = + (pixelMap paletteIndex (pixelMapXY dither img), palette) + | otherwise = (pixelMap paletteIndex img, palette) + where + maxCols = paletteColorCount opts + palette = listToPalette paletteList + paletteList = [PixelRGB8 r g b | r <- [0,dr..255] + , g <- [0,dg..255] + , b <- [0,db..255]] + (bg, br, bb) = bitDiv3 maxCols + (dr, dg, db) = (2^(8-br), 2^(8-bg), 2^(8-bb)) + paletteIndex (PixelRGB8 r g b) = fromIntegral $ fromMaybe 0 (elemIndex + (PixelRGB8 (r .&. (256 - dr)) (g .&. (256 - dg)) (b .&. (256 - db))) + paletteList) + +isColorCountBelow :: Int -> Image PixelRGB8 -> (Set.Set PixelRGB8, Bool) +isColorCountBelow maxColorCount img = go 0 0 Set.empty + where rawData = imageData img + maxIndex = VS.length rawData + + go !count !idx !allColors + | count > maxColorCount = (Set.empty, False) + | idx >= maxIndex - 2 = (allColors, True) + | otherwise = go (count + 1) (idx + 3) + $ Set.insert px allColors + where px = unsafePixelAt rawData idx + +vecToPalette :: Vector PixelRGB8 -> Palette +vecToPalette ps = generateImage (\x _ -> ps ! x) (V.length ps) 1 + +listToPalette :: [PixelRGB8] -> Palette +listToPalette ps = generateImage (\x _ -> ps !! x) (length ps) 1 + +bitDiv3 :: Int -> (Int, Int, Int) +bitDiv3 n = case r of + 0 -> (q, q, q) + 1 -> (q+1, q, q) + _ -> (q+1, q+1, q) + where + r = m `mod` 3 + q = m `div` 3 + m = floor . logBase (2 :: Double) $ fromIntegral n + +------------------------------------------------------------------------------- +---- Dithering +------------------------------------------------------------------------------- + +-- Add a dither mask to an image for ordered dithering. +-- Uses a small, spatially stable dithering algorithm based on magic numbers +-- and arithmetic inspired by the /a dither/ algorithm of Øyvind Kolås, +-- pippin@gimp.org, 2013. See, http://pippin.gimp.org/a_dither/. +dither :: Int -> Int -> PixelRGB8 -> PixelRGB8 +dither x y (PixelRGB8 r g b) = PixelRGB8 (fromIntegral r') + (fromIntegral g') + (fromIntegral b') + where + -- Should view 16 as a parameter that can be optimized for best looking + -- results + r' = min 255 (fromIntegral r + (x' + y') .&. 16) + g' = min 255 (fromIntegral g + (x' + y' + 7973) .&. 16) + b' = min 255 (fromIntegral b + (x' + y' + 15946) .&. 16) + x' = 119 * x + y' = 28084 * y + +------------------------------------------------------------------------------- +---- Small modification of foldl package by Gabriel Gonzalez +------------------------------------------------------------------------------- + +-- Modification to Control.foldl by Gabriel Gonzalez copyright 2013, BSD3. +-- http://hackage.haskell.org/package/foldl-1.0.1/docs/Control-Foldl.html + +{-| Efficient representation of a left fold that preserves the fold's step + function, initial accumulator, and extraction function + + This allows the 'Applicative' instance to assemble derived folds that + traverse the container only once +-} +data Fold a b = forall x . Fold (x -> a -> x) x (x -> b) + +{-| Apply a strict left 'Fold' to a 'Foldable' container + + Much slower than 'fold' on lists because 'Foldable' operations currently do + not trigger @build/foldr@ fusion +-} +fold :: Fold PackedRGB b -> VU.Vector PackedRGB -> b +fold (Fold step begin done) = done . VU.foldl' step begin +{-# INLINE fold #-} + +{- +F.foldr :: (a -> b -> b) -> b -> t a -> b + +fold :: (Foldable f) => Fold a b -> f a -> b +fold (Fold step begin done) as = F.foldr step' done as begin + where step' x k z = k $! step z x +-} + +data Pair a b = Pair !a !b + +instance Functor (Fold a) where + fmap f (Fold step begin done) = Fold step begin (f . done) + {-# INLINABLE fmap #-} + +instance Applicative (Fold a) where + pure b = Fold (\() _ -> ()) () (\() -> b) + {-# INLINABLE pure #-} + (Fold stepL beginL doneL) <*> (Fold stepR beginR doneR) = + let step (Pair xL xR) a = Pair (stepL xL a) (stepR xR a) + begin = Pair beginL beginR + done (Pair xL xR) = (doneL xL) (doneR xR) + in Fold step begin done + {-# INLINABLE (<*>) #-} + +{- | Like 'length', except with a more general 'Num' return value -} +intLength :: Fold a Int +intLength = Fold (\n _ -> n + 1) 0 id + +------------------------------------------------------------------------------- +---- Modified Median Cut Algorithm +------------------------------------------------------------------------------- + +-- Based on the OCaml implementation: +-- http://rosettacode.org/wiki/Color_quantization +-- which is in turn based on: www.leptonica.com/papers/mediancut.pdf. +-- We use the product of volume and population to determine the next cluster +-- to split and determine the placement of each color by compating it to the +-- mean of the parent cluster. So median cut is a bit of a misnomer, since one +-- of the modifiations is to use the mean. + +mkPaletteVec :: [Cluster] -> Vector PixelRGB8 +mkPaletteVec = V.fromList . map (toRGB8 . meanColor) + +type PackedRGB = Word32 + +data Cluster = Cluster + { value :: {-# UNPACK #-} !Float + , meanColor :: !PixelRGBF + , dims :: !PixelRGBF + , colors :: VU.Vector PackedRGB + } + +instance Eq Cluster where + a == b = + (value a, meanColor a, dims a) == (value b, meanColor b, dims b) + +instance Ord Cluster where + compare a b = + compare (value a, meanColor a, dims a) (value b, meanColor b, dims b) + +data Axis = RAxis | GAxis | BAxis + +inf :: Float +inf = read "Infinity" + +fromRGB8 :: PixelRGB8 -> PixelRGBF +fromRGB8 (PixelRGB8 r g b) = + PixelRGBF (fromIntegral r) (fromIntegral g) (fromIntegral b) + +toRGB8 :: PixelRGBF -> PixelRGB8 +toRGB8 (PixelRGBF r g b) = + PixelRGB8 (round r) (round g) (round b) + +meanRGB :: Fold PixelRGBF PixelRGBF +meanRGB = mean <$> intLength <*> pixelSum + where + pixelSum = Fold (mixWith $ const (+)) (PixelRGBF 0 0 0) id + mean n = colorMap (/ nf) + where nf = fromIntegral n + +minimal :: Fold PixelRGBF PixelRGBF +minimal = Fold mini (PixelRGBF inf inf inf) id + where mini = mixWith $ const min + +maximal :: Fold PixelRGBF PixelRGBF +maximal = Fold maxi (PixelRGBF (-inf) (-inf) (-inf)) id + where maxi = mixWith $ const max + +extrems :: Fold PixelRGBF (PixelRGBF, PixelRGBF) +extrems = (,) <$> minimal <*> maximal + +volAndDims :: Fold PixelRGBF (Float, PixelRGBF) +volAndDims = deltify <$> extrems + where deltify (mini, maxi) = (dr * dg * db, delta) + where delta@(PixelRGBF dr dg db) = + mixWith (const (-)) maxi mini + +unpackFold :: Fold PixelRGBF a -> Fold PackedRGB a +unpackFold (Fold step start done) = Fold (\acc -> step acc . transform) start done + where transform = fromRGB8 . rgbIntUnpack + +mkCluster :: VU.Vector PackedRGB -> Cluster +mkCluster ps = Cluster + { value = v * fromIntegral l + , meanColor = m + , dims = ds + , colors = ps + } + where + worker = (,,) <$> volAndDims <*> meanRGB <*> intLength + ((v, ds), m, l) = fold (unpackFold worker) ps + +maxAxis :: PixelRGBF -> Axis +maxAxis (PixelRGBF r g b) = + case (r `compare` g, r `compare` b, g `compare` b) of + (GT, GT, _) -> RAxis + (LT, GT, _) -> GAxis + (GT, LT, _) -> BAxis + (LT, LT, GT) -> GAxis + (_, _, _) -> BAxis + +-- Split a cluster about its largest axis using the mean to divide up the +-- pixels. +subdivide :: Cluster -> (Cluster, Cluster) +subdivide cluster = (mkCluster px1, mkCluster px2) + where + (PixelRGBF mr mg mb) = meanColor cluster + (px1, px2) = VU.partition (cond . rgbIntUnpack) $ colors cluster + cond = case maxAxis $ dims cluster of + RAxis -> (\(PixelRGB8 r _ _) -> fromIntegral r < mr) + GAxis -> (\(PixelRGB8 _ g _) -> fromIntegral g < mg) + BAxis -> (\(PixelRGB8 _ _ b) -> fromIntegral b < mb) + +rgbIntPack :: PixelRGB8 -> PackedRGB +rgbIntPack (PixelRGB8 r g b) = + wr `unsafeShiftL` (2 * 8) .|. wg `unsafeShiftL` 8 .|. wb + where wr = fromIntegral r + wg = fromIntegral g + wb = fromIntegral b + +rgbIntUnpack :: PackedRGB -> PixelRGB8 +rgbIntUnpack v = PixelRGB8 r g b + where + r = fromIntegral $ v `unsafeShiftR` (2 * 8) + g = fromIntegral $ v `unsafeShiftR` 8 + b = fromIntegral $ v + +initCluster :: Image PixelRGB8 -> Cluster +initCluster img = mkCluster $ VU.generate ((w * h) `div` subSampling) packer + where samplingFactor = 3 + subSampling = samplingFactor * samplingFactor + compCount = componentCount (undefined :: PixelRGB8) + w = imageWidth img + h = imageHeight img + rawData = imageData img + packer ix = + rgbIntPack . unsafePixelAt rawData $ ix * subSampling * compCount + +-- Take the cluster with the largest value = (volume * population) and remove it +-- from the priority queue. Then subdivide it about its largest axis and put the +-- two new clusters on the queue. +split :: Set Cluster -> Set Cluster +split cs = Set.insert c1 . Set.insert c2 $ cs' + where + (c, cs') = Set.deleteFindMax cs + (c1, c2) = subdivide c + +-- Keep splitting the initial cluster until there are 256 clusters, then return +-- a priority queue containing all 256. +clusters :: Int -> Image PixelRGB8 -> Set Cluster +clusters maxCols img = clusters' (maxCols - 1) + where + clusters' :: Int -> Set Cluster + clusters' 0 = Set.singleton c + clusters' n = split (clusters' (n-1)) + c = initCluster img + +-- Euclidean distance squared, between two pixels. +dist2Px :: PixelRGB8 -> PixelRGB8 -> Int +dist2Px (PixelRGB8 r1 g1 b1) (PixelRGB8 r2 g2 b2) = dr*dr + dg*dg + db*db + where + (dr, dg, db) = + ( fromIntegral r1 - fromIntegral r2 + , fromIntegral g1 - fromIntegral g2 + , fromIntegral b1 - fromIntegral b2 ) + +nearestColorIdx :: PixelRGB8 -> Vector PixelRGB8 -> Pixel8 +nearestColorIdx p ps = fromIntegral $ V.minIndex (V.map (\px -> dist2Px px p) ps)
+ src/Codec/Picture/ConvGraph.hs view
@@ -0,0 +1,9 @@+-- $graph +-- +-- The following graph describe the differents way to convert between pixel types, +-- +-- * Nodes describe pixel type +-- +-- * Arrows describe functions +-- +-- <<data:image/png;base64,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>>
+ src/Codec/Picture/Gif.hs view
@@ -0,0 +1,685 @@+-- | Module implementing GIF decoding. +module Codec.Picture.Gif ( -- * Reading + decodeGif + , decodeGifImages + + -- * Writing + , GifDelay + , GifLooping( .. ) + , encodeGifImage + , encodeGifImageWithPalette + , encodeGifImages + + , writeGifImage + , writeGifImageWithPalette + , writeGifImages + , greyPalette + ) where + +import Control.Applicative( pure, (<$>), (<*>) ) +import Control.Monad( replicateM, replicateM_ ) +import Control.Monad.ST( runST ) +import Control.Monad.Trans.Class( lift ) + +import Data.Bits( (.&.), (.|.) + , unsafeShiftR + , unsafeShiftL + , testBit, setBit ) +import Data.Word( Word8, Word16 ) + +import qualified Data.ByteString as B +import qualified Data.ByteString.Char8 as BC +import qualified Data.ByteString.Lazy as L +import qualified Data.Vector.Storable as V +import qualified Data.Vector.Storable.Mutable as M + +import Data.Binary( Binary(..), encode ) +import Data.Binary.Get( Get + , getWord8 + , getWord16le + , getByteString + ) + +import Data.Binary.Put( Put + , putWord8 + , putWord16le + , putByteString + ) + +import Codec.Picture.InternalHelper +import Codec.Picture.Types +import Codec.Picture.Gif.LZW +import Codec.Picture.Gif.LZWEncoding +import Codec.Picture.BitWriter + +-- | Delay to wait before showing the next Gif image. +-- The delay is expressed in 100th of seconds. +type GifDelay = Int + +-- | Help to control the behaviour of GIF animation looping. +data GifLooping = + -- | The animation will stop once the end is reached + LoopingNever + -- | The animation will restart once the end is reached + | LoopingForever + -- | The animation will repeat n times before stoping + | LoopingRepeat Word16 + +{- + <GIF Data Stream> ::= Header <Logical Screen> <Data>* Trailer + + <Logical Screen> ::= Logical Screen Descriptor [Global Color Table] + + <Data> ::= <Graphic Block> | + <Special-Purpose Block> + + <Graphic Block> ::= [Graphic Control Extension] <Graphic-Rendering Block> + + <Graphic-Rendering Block> ::= <Table-Based Image> | + Plain Text Extension + + <Table-Based Image> ::= Image Descriptor [Local Color Table] Image Data + + <Special-Purpose Block> ::= Application Extension | + Comment Extension + -} + +-------------------------------------------------- +---- GifVersion +-------------------------------------------------- +data GifVersion = GIF87a | GIF89a + +gif87aSignature, gif89aSignature :: B.ByteString +gif87aSignature = B.pack $ map (fromIntegral . fromEnum) "GIF87a" +gif89aSignature = B.pack $ map (fromIntegral . fromEnum) "GIF89a" + +instance Binary GifVersion where + put GIF87a = putByteString gif87aSignature + put GIF89a = putByteString gif89aSignature + + get = do + sig <- getByteString (B.length gif87aSignature) + case (sig == gif87aSignature, sig == gif89aSignature) of + (True, _) -> pure GIF87a + (_ , True) -> pure GIF89a + _ -> fail $ "Invalid Gif signature : " ++ (toEnum . fromEnum <$> B.unpack sig) + + +-------------------------------------------------- +---- LogicalScreenDescriptor +-------------------------------------------------- +-- | Section 18 of spec-gif89a +data LogicalScreenDescriptor = LogicalScreenDescriptor + { -- | Stored on 16 bits + screenWidth :: !Word16 + -- | Stored on 16 bits + , screenHeight :: !Word16 + -- | Stored on 8 bits + , backgroundIndex :: !Word8 + + -- | Stored on 1 bit + , hasGlobalMap :: !Bool + -- | Stored on 3 bits + , colorResolution :: !Word8 + -- | Stored on 1 bit + , isColorTableSorted :: !Bool + -- | Stored on 3 bits + , colorTableSize :: !Word8 + } + +instance Binary LogicalScreenDescriptor where + put v = do + putWord16le $ screenWidth v + putWord16le $ screenHeight v + let globalMapField + | hasGlobalMap v = 0x80 + | otherwise = 0 + + colorTableSortedField + | isColorTableSorted v = 0x08 + | otherwise = 0 + + tableSizeField = (colorTableSize v - 1) .&. 7 + + colorResolutionField = + ((colorResolution v - 1) .&. 7) `unsafeShiftL` 5 + + packedField = globalMapField + .|. colorTableSortedField + .|. tableSizeField + .|. colorResolutionField + + putWord8 packedField + putWord8 0 -- aspect ratio + putWord8 $ backgroundIndex v + + get = do + w <- getWord16le + h <- getWord16le + packedField <- getWord8 + backgroundColorIndex <- getWord8 + _aspectRatio <- getWord8 + return LogicalScreenDescriptor + { screenWidth = w + , screenHeight = h + , hasGlobalMap = packedField `testBit` 7 + , colorResolution = (packedField `unsafeShiftR` 5) .&. 0x7 + 1 + , isColorTableSorted = packedField `testBit` 3 + , colorTableSize = (packedField .&. 0x7) + 1 + , backgroundIndex = backgroundColorIndex + } + + +-------------------------------------------------- +---- ImageDescriptor +-------------------------------------------------- +-- | Section 20 of spec-gif89a +data ImageDescriptor = ImageDescriptor + { gDescPixelsFromLeft :: !Word16 + , gDescPixelsFromTop :: !Word16 + , gDescImageWidth :: !Word16 + , gDescImageHeight :: !Word16 + , gDescHasLocalMap :: !Bool + , gDescIsInterlaced :: !Bool + , gDescIsImgDescriptorSorted :: !Bool + , gDescLocalColorTableSize :: !Word8 + } + +imageSeparator, extensionIntroducer, gifTrailer, applicationLabel :: Word8 +imageSeparator = 0x2C +extensionIntroducer = 0x21 +gifTrailer = 0x3B +applicationLabel = 0xFF + +graphicControlLabel :: Word8 +graphicControlLabel = 0xF9 + +--commentLabel, graphicControlLabel, +-- +{-commentLabel = 0xFE-} +{-plainTextLabel = 0x01-} + +parseDataBlocks :: Get B.ByteString +parseDataBlocks = B.concat <$> (getWord8 >>= aux) + where aux 0 = pure [] + aux size = (:) <$> getByteString (fromIntegral size) <*> (getWord8 >>= aux) + +putDataBlocks :: B.ByteString -> Put +putDataBlocks wholeString = putSlices wholeString >> putWord8 0 + where putSlices str | B.length str == 0 = pure () + | B.length str > 0xFF = + let (before, after) = B.splitAt 0xFF str in + putWord8 0xFF >> putByteString before >> putSlices after + putSlices str = + putWord8 (fromIntegral $ B.length str) >> putByteString str + + +data GraphicControlExtension = GraphicControlExtension + { gceDisposalMethod :: !Word8 -- ^ Stored on 3 bits + , gceUserInputFlag :: !Bool + , gceTransparentFlag :: !Bool + , gceDelay :: !Word16 + , gceTransparentColorIndex :: !Word8 + } + +instance Binary GraphicControlExtension where + put v = do + putWord8 extensionIntroducer + putWord8 graphicControlLabel + putWord8 0x4 -- size + let disposalField = + (gceDisposalMethod v .&. 0x7) `unsafeShiftL` 2 + + userInputField + | gceUserInputFlag v = 0 `setBit` 1 + | otherwise = 0 + + transparentField + | gceTransparentFlag v = 0 `setBit` 0 + | otherwise = 0 + + packedFields = disposalField + .|. userInputField + .|. transparentField + + putWord8 packedFields + putWord16le $ gceDelay v + putWord8 $ gceTransparentColorIndex v + putWord8 0 -- blockTerminator + + get = do + -- due to missing lookahead + {-_extensionLabel <- getWord8-} + _size <- getWord8 + packedFields <- getWord8 + delay <- getWord16le + idx <- getWord8 + _blockTerminator <- getWord8 + return GraphicControlExtension + { gceDisposalMethod = (packedFields `unsafeShiftR` 2) .&. 0x07 + , gceUserInputFlag = packedFields `testBit` 1 + , gceTransparentFlag = packedFields `testBit` 0 + , gceDelay = delay + , gceTransparentColorIndex = idx + } + +data GifImage = GifImage + { imgDescriptor :: !ImageDescriptor + , imgLocalPalette :: !(Maybe Palette) + , imgLzwRootSize :: !Word8 + , imgData :: B.ByteString + } + +instance Binary GifImage where + put img = do + let descriptor = imgDescriptor img + put descriptor + case ( imgLocalPalette img + , gDescHasLocalMap $ imgDescriptor img) of + (Nothing, _) -> return () + (Just _, False) -> return () + (Just p, True) -> + putPalette (fromIntegral $ gDescLocalColorTableSize descriptor) p + putWord8 $ imgLzwRootSize img + putDataBlocks $ imgData img + + get = do + desc <- get + let hasLocalColorTable = gDescHasLocalMap desc + palette <- if hasLocalColorTable + then Just <$> getPalette (gDescLocalColorTableSize desc) + else pure Nothing + + GifImage desc palette <$> getWord8 <*> parseDataBlocks + +data Block = BlockImage GifImage + | BlockGraphicControl GraphicControlExtension + +parseGifBlocks :: Get [Block] +parseGifBlocks = getWord8 >>= blockParse + where blockParse v + | v == gifTrailer = pure [] + | v == imageSeparator = (:) <$> (BlockImage <$> get) <*> parseGifBlocks + | v == extensionIntroducer = do + extensionCode <- getWord8 + if extensionCode /= graphicControlLabel + then parseDataBlocks >> parseGifBlocks + else (:) <$> (BlockGraphicControl <$> get) <*> parseGifBlocks + + blockParse v = do + fail ("Unrecognized gif block " ++ show v) + +instance Binary ImageDescriptor where + put v = do + putWord8 imageSeparator + putWord16le $ gDescPixelsFromLeft v + putWord16le $ gDescPixelsFromTop v + putWord16le $ gDescImageWidth v + putWord16le $ gDescImageHeight v + let localMapField + | gDescHasLocalMap v = 0 `setBit` 7 + | otherwise = 0 + + isInterlacedField + | gDescIsInterlaced v = 0 `setBit` 6 + | otherwise = 0 + + isImageDescriptorSorted + | gDescIsImgDescriptorSorted v = 0 `setBit` 5 + | otherwise = 0 + + localSize = gDescLocalColorTableSize v + tableSizeField + | localSize > 0 = (localSize - 1) .&. 0x7 + | otherwise = 0 + + packedFields = localMapField + .|. isInterlacedField + .|. isImageDescriptorSorted + .|. tableSizeField + putWord8 packedFields + + get = do + -- due to missing lookahead + {-_imageSeparator <- getWord8-} + imgLeftPos <- getWord16le + imgTopPos <- getWord16le + imgWidth <- getWord16le + imgHeight <- getWord16le + packedFields <- getWord8 + let tableSize = packedFields .&. 0x7 + return ImageDescriptor + { gDescPixelsFromLeft = imgLeftPos + , gDescPixelsFromTop = imgTopPos + , gDescImageWidth = imgWidth + , gDescImageHeight = imgHeight + , gDescHasLocalMap = packedFields `testBit` 7 + , gDescIsInterlaced = packedFields `testBit` 6 + , gDescIsImgDescriptorSorted = packedFields `testBit` 5 + , gDescLocalColorTableSize = if tableSize > 0 then tableSize + 1 else 0 + } + + +-------------------------------------------------- +---- Palette +-------------------------------------------------- +getPalette :: Word8 -> Get Palette +getPalette bitDepth = replicateM (size * 3) get >>= return . Image size 1 . V.fromList + where size = 2 ^ (fromIntegral bitDepth :: Int) + +putPalette :: Int -> Palette -> Put +putPalette size pal = do + V.mapM_ putWord8 (imageData pal) + replicateM_ missingColorComponent (putWord8 0) + where elemCount = 2 ^ size + missingColorComponent = (elemCount - imageWidth pal) * 3 + +-------------------------------------------------- +---- GifImage +-------------------------------------------------- +data GifHeader = GifHeader + { gifVersion :: GifVersion + , gifScreenDescriptor :: LogicalScreenDescriptor + , gifGlobalMap :: !Palette + } + +instance Binary GifHeader where + put v = do + put $ gifVersion v + let descr = gifScreenDescriptor v + put $ descr + putPalette (fromIntegral $ colorTableSize descr) $ gifGlobalMap v + + get = do + version <- get + screenDesc <- get + palette <- getPalette $ colorTableSize screenDesc + return GifHeader + { gifVersion = version + , gifScreenDescriptor = screenDesc + , gifGlobalMap = palette + } + +data GifFile = GifFile + { gifHeader :: !GifHeader + , gifImages :: [(Maybe GraphicControlExtension, GifImage)] + , gifLoopingBehaviour :: GifLooping + } + +putLooping :: GifLooping -> Put +putLooping LoopingNever = return () +putLooping LoopingForever = putLooping $ LoopingRepeat 0 +putLooping (LoopingRepeat count) = do + putWord8 extensionIntroducer + putWord8 applicationLabel + putWord8 11 -- the size + putByteString $ BC.pack "NETSCAPE2.0" + putWord8 3 -- size of sub block + putWord8 1 + putWord16le count + putWord8 0 + +associateDescr :: [Block] -> [(Maybe GraphicControlExtension, GifImage)] +associateDescr [] = [] +associateDescr [BlockGraphicControl _] = [] +associateDescr (BlockGraphicControl _ : rest@(BlockGraphicControl _ : _)) = + associateDescr rest +associateDescr (BlockImage img:xs) = (Nothing, img) : associateDescr xs +associateDescr (BlockGraphicControl ctrl : BlockImage img : xs) = + (Just ctrl, img) : associateDescr xs + +instance Binary GifFile where + put v = do + put $ gifHeader v + let putter (Nothing, i) = put i + putter (Just a, i) = put a >> put i + putLooping $ gifLoopingBehaviour v + mapM_ putter $ gifImages v + put gifTrailer + + get = do + hdr <- get + blocks <- parseGifBlocks + return GifFile { gifHeader = hdr + , gifImages = associateDescr blocks + , gifLoopingBehaviour = LoopingNever + } + +substituteColors :: Palette -> Image Pixel8 -> Image PixelRGB8 +substituteColors palette = pixelMap swaper + where swaper n = pixelAt palette (fromIntegral n) 0 + +decodeImage :: GifImage -> Image Pixel8 +decodeImage img = runST $ runBoolReader $ do + outputVector <- lift . M.new $ width * height + decodeLzw (imgData img) 12 lzwRoot outputVector + frozenData <- lift $ V.unsafeFreeze outputVector + return . deinterlaceGif $ Image + { imageWidth = width + , imageHeight = height + , imageData = frozenData + } + where lzwRoot = fromIntegral $ imgLzwRootSize img + width = fromIntegral $ gDescImageWidth descriptor + height = fromIntegral $ gDescImageHeight descriptor + isInterlaced = gDescIsInterlaced descriptor + descriptor = imgDescriptor img + + deinterlaceGif | not isInterlaced = id + | otherwise = deinterlaceGifImage + +deinterlaceGifImage :: Image Pixel8 -> Image Pixel8 +deinterlaceGifImage img@(Image { imageWidth = w, imageHeight = h }) = generateImage generator w h + where lineIndices = gifInterlacingIndices h + generator x y = pixelAt img x y' + where y' = lineIndices V.! y + +gifInterlacingIndices :: Int -> V.Vector Int +gifInterlacingIndices height = V.accum (\_ v -> v) (V.replicate height 0) indices + where indices = flip zip [0..] $ + concat [ [0, 8 .. height - 1] + , [4, 4 + 8 .. height - 1] + , [2, 2 + 4 .. height - 1] + , [1, 1 + 2 .. height - 1] + ] + +paletteOf :: Palette -> GifImage -> Palette +paletteOf global GifImage { imgLocalPalette = Nothing } = global +paletteOf _ GifImage { imgLocalPalette = Just p } = p + +decodeAllGifImages :: GifFile -> [Image PixelRGB8] +decodeAllGifImages GifFile { gifImages = [] } = [] +decodeAllGifImages GifFile { gifHeader = GifHeader { gifGlobalMap = palette + , gifScreenDescriptor = wholeDescriptor + } + , gifImages = (_, firstImage) : rest } = map paletteApplyer $ + scanl generator (paletteOf palette firstImage, decodeImage firstImage) rest + where globalWidth = fromIntegral $ screenWidth wholeDescriptor + globalHeight = fromIntegral $ screenHeight wholeDescriptor + + {-background = backgroundIndex wholeDescriptor-} + + paletteApplyer (pal, img) = substituteColors pal img + + generator (_, img1) (controlExt, img2@(GifImage { imgDescriptor = descriptor })) = + (paletteOf palette img2, generateImage pixeler globalWidth globalHeight) + where localWidth = fromIntegral $ gDescImageWidth descriptor + localHeight = fromIntegral $ gDescImageHeight descriptor + + left = fromIntegral $ gDescPixelsFromLeft descriptor + top = fromIntegral $ gDescPixelsFromTop descriptor + + isPixelInLocalImage x y = + x >= left && x < left + localWidth && y >= top && y < top + localHeight + + decoded = decodeImage img2 + + transparent :: Int + transparent = case controlExt of + Nothing -> 300 + Just ext -> if gceTransparentFlag ext + then fromIntegral $ gceTransparentColorIndex ext + else 300 + + pixeler x y + | isPixelInLocalImage x y && fromIntegral val /= transparent = val + where val = pixelAt decoded (x - left) (y - top) + pixeler x y = pixelAt img1 x y + +decodeFirstGifImage :: GifFile -> Either String (Image PixelRGB8) +decodeFirstGifImage + GifFile { gifHeader = GifHeader { gifGlobalMap = palette} + , gifImages = ((_, gif):_) } = Right . substituteColors palette $ decodeImage gif +decodeFirstGifImage _ = Left "No image in gif file" + +-- | Transform a raw gif image to an image, witout +-- modifying the pixels. +-- This function can output the following pixel types : +-- +-- * PixelRGB8 +-- +decodeGif :: B.ByteString -> Either String DynamicImage +decodeGif img = ImageRGB8 <$> (decode img >>= decodeFirstGifImage) + +-- | Transform a raw gif to a list of images, representing +-- all the images of an animation. +decodeGifImages :: B.ByteString -> Either String [Image PixelRGB8] +decodeGifImages img = decodeAllGifImages <$> decode img + +-- | Default palette to produce greyscale images. +greyPalette :: Palette +greyPalette = generateImage toGrey 256 1 + where toGrey x _ = PixelRGB8 ix ix ix + where ix = fromIntegral x + +checkGifImageSizes :: [(a, b, Image px)] -> Bool +checkGifImageSizes [] = False +checkGifImageSizes ((_, _, img) : rest) = all checkDimension rest + where width = imageWidth img + height = imageHeight img + + checkDimension (_,_,Image { imageWidth = w, imageHeight = h }) = + w == width && h == height + +checkPaletteValidity :: [(Palette, a, b)] -> Bool +checkPaletteValidity [] = False +checkPaletteValidity lst = + and [h == 1 && w > 0 && w <= 256 | (p, _, _) <- lst + , let w = imageWidth p + h = imageHeight p ] + +areIndexAbsentFromPalette :: (Palette, a, Image Pixel8) -> Bool +areIndexAbsentFromPalette (palette, _, img) = V.any isTooBig $ imageData img + where paletteElemCount = imageWidth palette + isTooBig v = fromIntegral v >= paletteElemCount + +computeMinimumLzwKeySize :: Palette -> Int +computeMinimumLzwKeySize Image { imageWidth = itemCount } = go 2 + where go k | 2 ^ k >= itemCount = k + | otherwise = go $ k + 1 + +-- | Encode a gif animation to a bytestring. +-- +-- * Every image must have the same size +-- +-- * Every palette must have between one and 256 colors. +-- +encodeGifImages :: GifLooping -> [(Palette, GifDelay, Image Pixel8)] + -> Either String L.ByteString +encodeGifImages _ [] = Left "No image in list" +encodeGifImages _ imageList + | not $ checkGifImageSizes imageList = Left "Gif images have different size" + | not $ checkPaletteValidity imageList = + Left $ "Invalid palette size " ++ concat [show (imageWidth pal) ++ " "| (pal, _, _) <- imageList ] + | any areIndexAbsentFromPalette imageList = Left "Image contains indexes absent from the palette" +encodeGifImages looping imageList@((firstPalette, _,firstImage):_) = Right $ encode allFile + where + allFile = GifFile + { gifHeader = GifHeader + { gifVersion = GIF89a + , gifScreenDescriptor = logicalScreen + , gifGlobalMap = firstPalette + } + , gifImages = toSerialize + , gifLoopingBehaviour = looping + } + + logicalScreen = LogicalScreenDescriptor + { screenWidth = fromIntegral $ imageWidth firstImage + , screenHeight = fromIntegral $ imageHeight firstImage + , backgroundIndex = 0 + , hasGlobalMap = True + , colorResolution = 8 + , isColorTableSorted = False + , colorTableSize = 8 + } + + paletteEqual p = imageData firstPalette == imageData p + + controlExtension 0 = Nothing + controlExtension delay = Just GraphicControlExtension + { gceDisposalMethod = 0 + , gceUserInputFlag = False + , gceTransparentFlag = False + , gceDelay = fromIntegral delay + , gceTransparentColorIndex = 0 + } + + toSerialize = [(controlExtension delay, GifImage + { imgDescriptor = imageDescriptor lzwKeySize (paletteEqual palette) img + , imgLocalPalette = Just palette + , imgLzwRootSize = fromIntegral $ lzwKeySize + , imgData = B.concat . L.toChunks . lzwEncode lzwKeySize $ imageData img + }) | (palette, delay, img) <- imageList + , let lzwKeySize = computeMinimumLzwKeySize palette + ] + + imageDescriptor paletteSize palEqual img = ImageDescriptor + { gDescPixelsFromLeft = 0 + , gDescPixelsFromTop = 0 + , gDescImageWidth = fromIntegral $ imageWidth img + , gDescImageHeight = fromIntegral $ imageHeight img + , gDescHasLocalMap = paletteSize > 0 && not palEqual + , gDescIsInterlaced = False + , gDescIsImgDescriptorSorted = False + , gDescLocalColorTableSize = if palEqual then 0 else fromIntegral paletteSize + } + +-- | Encode a greyscale image to a bytestring. +encodeGifImage :: Image Pixel8 -> L.ByteString +encodeGifImage img = case encodeGifImages LoopingNever [(greyPalette, 0, img)] of + Left err -> error $ "Impossible:" ++ err + Right v -> v + +-- | Encode an image with a given palette. +-- Can return errors if the palette is ill-formed. +-- +-- * A palette must have between 1 and 256 colors +-- +encodeGifImageWithPalette :: Image Pixel8 -> Palette -> Either String L.ByteString +encodeGifImageWithPalette img palette = + encodeGifImages LoopingNever [(palette, 0, img)] + +-- | Write a greyscale in a gif file on the disk. +writeGifImage :: FilePath -> Image Pixel8 -> IO () +writeGifImage file = L.writeFile file . encodeGifImage + +-- | Write a list of images as a gif animation in a file. +-- +-- * Every image must have the same size +-- +-- * Every palette must have between one and 256 colors. +-- +writeGifImages :: FilePath -> GifLooping -> [(Palette, GifDelay, Image Pixel8)] + -> Either String (IO ()) +writeGifImages file looping lst = L.writeFile file <$> encodeGifImages looping lst + +-- | Write a gif image with a palette to a file. +-- +-- * A palette must have between 1 and 256 colors +-- +writeGifImageWithPalette :: FilePath -> Image Pixel8 -> Palette + -> Either String (IO ()) +writeGifImageWithPalette file img palette = + L.writeFile file <$> encodeGifImageWithPalette img palette +
+ src/Codec/Picture/Gif/LZW.hs view
@@ -0,0 +1,189 @@+module Codec.Picture.Gif.LZW( decodeLzw, decodeLzwTiff ) where + +import Data.Word( Word8 ) +import Control.Applicative( (<$>) ) +import Control.Monad( when ) + +import Data.Bits( (.&.) ) + +import Control.Monad.ST( ST ) +import Control.Monad.Trans.Class( MonadTrans, lift ) + +import Foreign.Storable ( Storable ) + +import qualified Data.ByteString as B +import qualified Data.Vector.Storable.Mutable as M + +import Codec.Picture.BitWriter + +{-# INLINE (.!!!.) #-} +(.!!!.) :: (Storable a) => M.STVector s a -> Int -> ST s a +(.!!!.) = M.unsafeRead + {-M.read-} + +{-# INLINE (..!!!..) #-} +(..!!!..) :: (MonadTrans t, Storable a) + => M.STVector s a -> Int -> t (ST s) a +(..!!!..) v idx = lift $ v .!!!. idx + +{-# INLINE (.<-.) #-} +(.<-.) :: (Storable a) => M.STVector s a -> Int -> a -> ST s () +(.<-.) = M.unsafeWrite + {-M.write-} + +{-# INLINE (..<-..) #-} +(..<-..) :: (MonadTrans t, Storable a) + => M.STVector s a -> Int -> a -> t (ST s) () +(..<-..) v idx = lift . (v .<-. idx) + + +duplicateData :: (Show a, MonadTrans t, Storable a) + => M.STVector s a -> M.STVector s a + -> Int -> Int -> Int -> t (ST s) () +duplicateData src dest sourceIndex size destIndex = lift $ aux sourceIndex destIndex + where endIndex = sourceIndex + size + aux i _ | i == endIndex = return () + aux i j = do + src .!!!. i >>= (dest .<-. j) + aux (i + 1) (j + 1) + +rangeSetter :: (Storable a, Num a) + => Int -> M.STVector s a + -> ST s (M.STVector s a) +rangeSetter count vec = aux 0 + where aux n | n == count = return vec + aux n = (vec .<-. n) (fromIntegral n) >> aux (n + 1) + +decodeLzw :: B.ByteString -> Int -> Int -> M.STVector s Word8 + -> BoolReader s () +decodeLzw str maxBitKey initialKey outVec = do + setDecodedString str + lzw GifVariant maxBitKey initialKey 0 outVec + +isOldTiffLZW :: B.ByteString -> Bool +isOldTiffLZW str = firstByte == 0 && secondByte == 1 + where firstByte = str `B.index` 0 + secondByte = (str `B.index` 1) .&. 1 + +decodeLzwTiff :: B.ByteString -> M.STVector s Word8 -> Int + -> BoolReader s() +decodeLzwTiff str outVec initialWriteIdx = do + setDecodedString str + let variant | isOldTiffLZW str = OldTiffVariant + | otherwise = TiffVariant + lzw variant 12 9 initialWriteIdx outVec + +data TiffVariant = + GifVariant + | TiffVariant + | OldTiffVariant + deriving Eq + +-- | Gif image constraint from spec-gif89a, code size max : 12 bits. +lzw :: TiffVariant -> Int -> Int -> Int -> M.STVector s Word8 + -> BoolReader s () +lzw variant nMaxBitKeySize initialKeySize initialWriteIdx outVec = do + -- Allocate buffer of maximum size. + lzwData <- lift (M.replicate maxDataSize 0) >>= resetArray + lzwOffsetTable <- lift (M.replicate tableEntryCount 0) >>= resetArray + lzwSizeTable <- lift $ M.replicate tableEntryCount 0 + lift $ lzwSizeTable `M.set` 1 + + let firstVal code = do + dataOffset <- lzwOffsetTable ..!!!.. code + lzwData ..!!!.. dataOffset + + writeString at code = do + dataOffset <- lzwOffsetTable ..!!!.. code + dataSize <- lzwSizeTable ..!!!.. code + + when (at + dataSize <= maxWrite) $ + duplicateData lzwData outVec dataOffset dataSize at + + return dataSize + + addString pos at code val = do + dataOffset <- lzwOffsetTable ..!!!.. code + dataSize <- lzwSizeTable ..!!!.. code + + when (pos < tableEntryCount) $ do + (lzwOffsetTable ..<-.. pos) at + (lzwSizeTable ..<-.. pos) $ dataSize + 1 + + when (at + dataSize + 1 <= maxDataSize) $ do + duplicateData lzwData lzwData dataOffset dataSize at + (lzwData ..<-.. (at + dataSize)) val + + return $ dataSize + 1 + + maxWrite = M.length outVec + loop outWriteIdx writeIdx dicWriteIdx codeSize oldCode code + | outWriteIdx >= maxWrite = return () + | code == endOfInfo = return () + | code == clearCode = do + toOutput <- getNextCode startCodeSize + if toOutput == endOfInfo then + return () + else do + dataSize <- writeString outWriteIdx toOutput + getNextCode startCodeSize >>= + loop (outWriteIdx + dataSize) + firstFreeIndex firstFreeIndex startCodeSize toOutput + + | otherwise = do + (written, dicAdd) <- + if code >= writeIdx then do + c <- firstVal oldCode + wroteSize <- writeString outWriteIdx oldCode + (outVec ..<-.. (outWriteIdx + wroteSize)) c + addedSize <- addString writeIdx dicWriteIdx oldCode c + return (wroteSize + 1, addedSize) + else do + wroteSize <- writeString outWriteIdx code + c <- firstVal code + addedSize <- addString writeIdx dicWriteIdx oldCode c + return (wroteSize, addedSize) + + let new_code_size = updateCodeSize codeSize $ writeIdx + 1 + getNextCode new_code_size >>= + loop (outWriteIdx + written) + (writeIdx + 1) + (dicWriteIdx + dicAdd) + new_code_size + code + + getNextCode startCodeSize >>= + loop initialWriteIdx firstFreeIndex firstFreeIndex startCodeSize 0 + + where tableEntryCount = 2 ^ min 12 nMaxBitKeySize + maxDataSize = tableEntryCount `div` 2 * (1 + tableEntryCount) + 1 + + isNewTiff = variant == TiffVariant + (switchOffset, isTiffVariant) = case variant of + GifVariant -> (0, False) + TiffVariant -> (1, True) + OldTiffVariant -> (0, True) + + initialElementCount = 2 ^ initialKeySize :: Int + clearCode | isTiffVariant = 256 + | otherwise = initialElementCount + + endOfInfo | isTiffVariant = 257 + | otherwise = clearCode + 1 + + startCodeSize + | isTiffVariant = initialKeySize + | otherwise = initialKeySize + 1 + + firstFreeIndex = endOfInfo + 1 + + resetArray a = lift $ rangeSetter initialElementCount a + + updateCodeSize codeSize writeIdx + | writeIdx == 2 ^ codeSize - switchOffset = min 12 $ codeSize + 1 + | otherwise = codeSize + + getNextCode s + | isNewTiff = fromIntegral <$> getNextBitsMSBFirst s + | otherwise = fromIntegral <$> getNextBitsLSBFirst s +
+ src/Codec/Picture/Gif/LZWEncoding.hs view
@@ -0,0 +1,93 @@+{-# LANGUAGE BangPatterns #-} +module Codec.Picture.Gif.LZWEncoding( lzwEncode ) where + +import Control.Applicative( (<$>) ) +import Control.Monad.ST( runST ) +import qualified Data.ByteString.Lazy as L +import Data.Maybe( fromMaybe ) +import Data.Monoid( mempty ) +import Data.Word( Word8 ) +import qualified Data.IntMap.Strict as I +import qualified Data.Vector.Storable as V + +import Codec.Picture.BitWriter + +type Trie = I.IntMap TrieNode + +data TrieNode = TrieNode + { trieIndex :: {-# UNPACK #-} !Int + , trieSub :: !Trie + } + +emptyNode :: TrieNode +emptyNode = TrieNode + { trieIndex = -1 + , trieSub = mempty + } + +initialTrie :: Trie +initialTrie = I.fromList + [(i, emptyNode { trieIndex = i }) | i <- [0 .. 255]] + +lookupUpdate :: V.Vector Word8 -> Int -> Int -> Trie -> (Int, Int, Trie) +lookupUpdate vector freeIndex firstIndex trie = + matchUpdate $ go trie 0 firstIndex + where + matchUpdate (lzwOutputIndex, nextReadIndex, sub) = + (lzwOutputIndex, nextReadIndex, fromMaybe trie sub) + + maxi = V.length vector + go !currentTrie !prevIndex !index + | index >= maxi = (prevIndex, index, Nothing) + | otherwise = case I.lookup val currentTrie of + Just (TrieNode ix subTable) -> + let (lzwOutputIndex, nextReadIndex, newTable) = + go subTable ix $ index + 1 + tableUpdater t = + I.insert val (TrieNode ix t) currentTrie + in + (lzwOutputIndex, nextReadIndex, tableUpdater <$> newTable) + + Nothing | index == maxi -> (prevIndex, index, Nothing) + | otherwise -> (prevIndex, index, Just $ I.insert val newNode currentTrie) + + where val = fromIntegral $ vector `V.unsafeIndex` index + newNode = emptyNode { trieIndex = freeIndex } + +lzwEncode :: Int -> V.Vector Word8 -> L.ByteString +lzwEncode initialKeySize vec = runST $ do + bitWriter <- newWriteStateRef + + let updateCodeSize 12 writeIdx _ + | writeIdx == 2 ^ (12 :: Int) - 1 = do + writeBitsGif bitWriter (fromIntegral clearCode) 12 + return (startCodeSize, firstFreeIndex, initialTrie) + + updateCodeSize codeSize writeIdx trie + | writeIdx == 2 ^ codeSize = + return (codeSize + 1, writeIdx + 1, trie) + | otherwise = return (codeSize, writeIdx + 1, trie) + + go readIndex (codeSize, _, _) | readIndex >= maxi = + writeBitsGif bitWriter (fromIntegral endOfInfo) codeSize + go !readIndex (!codeSize, !writeIndex, !trie) = do + let (indexToWrite, endIndex, trie') = + lookuper writeIndex readIndex trie + writeBitsGif bitWriter (fromIntegral indexToWrite) codeSize + updateCodeSize codeSize writeIndex trie' + >>= go endIndex + + writeBitsGif bitWriter (fromIntegral clearCode) startCodeSize + go 0 (startCodeSize, firstFreeIndex, initialTrie) + + finalizeBoolWriter bitWriter + where + maxi = V.length vec + + startCodeSize = initialKeySize + 1 + + clearCode = 2 ^ initialKeySize :: Int + endOfInfo = clearCode + 1 + firstFreeIndex = endOfInfo + 1 + + lookuper = lookupUpdate vec
+ src/Codec/Picture/HDR.hs view
@@ -0,0 +1,462 @@+-- | Module dedicated of Radiance file decompression (.hdr or .pic) file. +-- Radiance file format is used for High dynamic range imaging. +module Codec.Picture.HDR( decodeHDR, encodeHDR, writeHDR ) where + +import Data.Bits( Bits, (.&.), (.|.), unsafeShiftL, unsafeShiftR ) +import Data.Char( ord, chr, isDigit ) +import Data.Word( Word8 ) +import Data.Monoid( (<>) ) +import Control.Applicative( pure, (<$>), (<*>) ) +import Control.Monad( when, foldM, foldM_, forM, forM_ ) +import Control.Monad.Trans.Class( lift ) +import Control.Monad.Trans.Error( ErrorT, throwError, runErrorT ) +import qualified Data.ByteString as B +import qualified Data.ByteString.Lazy as L +import qualified Data.ByteString.Char8 as BC + +import Data.List( partition ) +import Data.Binary( Binary( .. ), encode ) +import Data.Binary.Get( Get, getByteString, getWord8 ) +import Data.Binary.Put( putByteString, putLazyByteString ) + +import Control.Monad.ST( ST, runST ) +import Foreign.Storable ( Storable ) +import Control.Monad.Primitive ( PrimState, PrimMonad ) +import qualified Data.Vector.Storable as V +import qualified Data.Vector.Storable.Mutable as M + +import Codec.Picture.InternalHelper +import Codec.Picture.Types +import Codec.Picture.VectorByteConversion + +{-# INLINE (.<<.) #-} +(.<<.), (.>>.) :: (Bits a) => a -> Int -> a +(.<<.) = unsafeShiftL +(.>>.) = unsafeShiftR + +{-# INLINE (.<-.) #-} +(.<-.) :: (PrimMonad m, Storable a) + => M.STVector (PrimState m) a -> Int -> a -> m () +(.<-.) = M.write + {-M.unsafeWrite-} + +type HDRReader s a = ErrorT String (ST s) a + +data RGBE = RGBE !Word8 !Word8 !Word8 !Word8 + +instance Binary RGBE where + put (RGBE r g b e) = put r >> put g >> put b >> put e + get = RGBE <$> get <*> get <*> get <*> get + +checkLineLength :: RGBE -> Int +checkLineLength (RGBE _ _ a b) = + (fromIntegral a .<<. 8) .|. fromIntegral b + +isNewRunLengthMarker :: RGBE -> Bool +isNewRunLengthMarker (RGBE 2 2 _ _) = True +isNewRunLengthMarker _ = False + +data RadianceFormat = + FormatRGBE + | FormatXYZE + +radiance32bitRleRGBEFormat, radiance32bitRleXYZEFromat :: B.ByteString +radiance32bitRleRGBEFormat = BC.pack "32-bit_rle_rgbe" +radiance32bitRleXYZEFromat = BC.pack "32-bit_rle_xyze" + +instance Binary RadianceFormat where + put FormatRGBE = putByteString radiance32bitRleRGBEFormat + put FormatXYZE = putByteString radiance32bitRleXYZEFromat + + get = getByteString (B.length radiance32bitRleRGBEFormat) >>= format + where format sig + | sig == radiance32bitRleRGBEFormat = pure FormatRGBE + | sig == radiance32bitRleXYZEFromat = pure FormatXYZE + | otherwise = fail "Unrecognized Radiance format" + +toRGBE :: PixelRGBF -> RGBE +toRGBE (PixelRGBF r g b) + | d <= 1e-32 = RGBE 0 0 0 0 + | otherwise = RGBE (fix r) (fix g) (fix b) (fromIntegral $ e + 128) + where d = maximum [r, g, b] + e = exponent d + coeff = significand d * 255.9999 / d + fix v = truncate $ v * coeff + + +dropUntil :: Word8 -> Get () +dropUntil c = getWord8 >>= inner + where inner val | val == c = pure () + inner _ = getWord8 >>= inner + +getUntil :: (Word8 -> Bool) -> B.ByteString -> Get B.ByteString +getUntil f initialAcc = getWord8 >>= inner initialAcc + where inner acc c | f c = pure acc + inner acc c = getWord8 >>= inner (B.snoc acc c) + +data RadianceHeader = RadianceHeader + { radianceInfos :: [(B.ByteString, B.ByteString)] + , radianceFormat :: RadianceFormat + , radianceHeight :: !Int + , radianceWidth :: !Int + , radianceData :: L.ByteString + } + +radianceFileSignature :: B.ByteString +radianceFileSignature = BC.pack "#?RADIANCE\n" + +unpackColor :: L.ByteString -> Int -> RGBE +unpackColor str idx = RGBE (at 0) (at 1) (at 2) (at 3) + where at n = L.index str . fromIntegral $ idx + n + +storeColor :: M.STVector s Word8 -> Int -> RGBE -> ST s () +storeColor vec idx (RGBE r g b e) = do + (vec .<-. (idx + 0)) r + (vec .<-. (idx + 1)) g + (vec .<-. (idx + 2)) b + (vec .<-. (idx + 3)) e + +parsePair :: Char -> Get (B.ByteString, B.ByteString) +parsePair firstChar = do + let eol c = c == fromIntegral (ord '\n') + line <- getUntil eol B.empty + case BC.split '=' line of + [] -> pure (BC.singleton firstChar, B.empty) + [val] -> pure (BC.singleton firstChar, val) + [key, val] -> pure (BC.singleton firstChar <> key, val) + (key : vals) -> pure (BC.singleton firstChar <> key, B.concat vals) + +decodeInfos :: Get [(B.ByteString, B.ByteString)] +decodeInfos = do + char <- getChar8 + case char of + -- comment + '#' -> dropUntil (fromIntegral $ ord '\n') >> decodeInfos + -- end of header, no more information + '\n' -> pure [] + -- Classical parsing + c -> (:) <$> parsePair c <*> decodeInfos + + +-- | Decode an HDR (radiance) image, the resulting pixel +-- type can be : +-- +-- * PixelRGBF +-- +decodeHDR :: B.ByteString -> Either String DynamicImage +decodeHDR str = runST $ runErrorT $ do + case runGet decodeHeader $ L.fromChunks [str] of + Left err -> throwError err + Right rez -> + ImageRGBF <$> (decodeRadiancePicture rez >>= lift . unsafeFreezeImage) + +getChar8 :: Get Char +getChar8 = chr . fromIntegral <$> getWord8 + +isSign :: Char -> Bool +isSign c = c == '+' || c == '-' + +isAxisLetter :: Char -> Bool +isAxisLetter c = c == 'X' || c == 'Y' + +decodeNum :: Get Int +decodeNum = do + sign <- getChar8 + letter <- getChar8 + space <- getChar8 + + when (not $ isSign sign && isAxisLetter letter && space == ' ') + (fail "Invalid radiance size declaration") + + let numDec acc c | isDigit c = + getChar8 >>= numDec (acc * 10 + ord c - (ord '0')) + numDec acc _ + | sign == '-' = pure $ negate acc + | otherwise = pure acc + + getChar8 >>= numDec 0 + +copyPrevColor :: M.STVector s Word8 -> Int -> ST s () +copyPrevColor scanLine idx = do + r <- scanLine `M.unsafeRead` (idx - 4) + g <- scanLine `M.unsafeRead` (idx - 3) + b <- scanLine `M.unsafeRead` (idx - 2) + e <- scanLine `M.unsafeRead` (idx - 1) + + (scanLine `M.unsafeWrite` (idx + 0)) r + (scanLine `M.unsafeWrite` (idx + 1)) g + (scanLine `M.unsafeWrite` (idx + 2)) b + (scanLine `M.unsafeWrite` (idx + 3)) e + +oldStyleRLE :: L.ByteString -> Int -> M.STVector s Word8 + -> HDRReader s Int +oldStyleRLE inputData initialIdx scanLine = inner initialIdx 0 0 + where maxOutput = M.length scanLine + maxInput = fromIntegral $ L.length inputData + + inner readIdx writeIdx _ + | readIdx >= maxInput || writeIdx >= maxOutput = pure readIdx + inner readIdx writeIdx shift = do + let color@(RGBE r g b e) = unpackColor inputData readIdx + isRun = r == 1 && g == 1 && b == 1 + + if not isRun + then do + lift $ storeColor scanLine writeIdx color + inner (readIdx + 4) (writeIdx + 4) 0 + + else do + let count = fromIntegral e .<<. shift + lift $ forM_ [0 .. count] $ \i -> copyPrevColor scanLine (writeIdx + 4 * i) + inner (readIdx + 4) (writeIdx + 4 * count) (shift + 8) + +newStyleRLE :: L.ByteString -> Int -> M.STVector s Word8 + -> HDRReader s Int +newStyleRLE inputData initialIdx scanline = foldM inner initialIdx [0 .. 3] + where dataAt idx + | fromIntegral idx >= maxInput = throwError $ "Read index out of bound (" ++ show idx ++ ")" + | otherwise = pure $ L.index inputData (fromIntegral idx) + + maxOutput = M.length scanline + maxInput = fromIntegral $ L.length inputData + stride = 4 + + + strideSet count destIndex _ | endIndex > maxOutput + stride = + throwError $ "Out of bound HDR scanline " ++ show endIndex ++ " (max " ++ show maxOutput ++ ")" + where endIndex = destIndex + count * stride + strideSet count destIndex val = aux destIndex count + where aux i 0 = pure i + aux i c = do + lift $ (scanline .<-. i) val + aux (i + stride) (c - 1) + + + strideCopy _ count destIndex + | writeEndBound > maxOutput + stride = throwError "Out of bound HDR scanline" + where writeEndBound = destIndex + count * stride + strideCopy sourceIndex count destIndex = aux sourceIndex destIndex count + where aux _ j 0 = pure j + aux i j c = do + val <- dataAt i + lift $ (scanline .<-. j) val + aux (i + 1) (j + stride) (c - 1) + + inner readIdx writeIdx + | readIdx >= maxInput || writeIdx >= maxOutput = pure readIdx + inner readIdx writeIdx = do + code <- dataAt readIdx + if code > 128 + then do + let repeatCount = fromIntegral code .&. 0x7F + newVal <- dataAt $ readIdx + 1 + endIndex <- strideSet repeatCount writeIdx newVal + inner (readIdx + 2) endIndex + + else do + let iCode = fromIntegral code + endIndex <- strideCopy (readIdx + 1) iCode writeIdx + inner (readIdx + iCode + 1) endIndex + +instance Binary RadianceHeader where + get = decodeHeader + put hdr = do + putByteString radianceFileSignature + putByteString $ BC.pack "FORMAT=" + put $ radianceFormat hdr + let sizeString = + BC.pack $ "\n\n-Y " ++ show (radianceHeight hdr) + ++ " +X " ++ show (radianceWidth hdr) ++ "\n" + putByteString sizeString + putLazyByteString $ radianceData hdr + + +decodeHeader :: Get RadianceHeader +decodeHeader = do + sig <- getByteString $ B.length radianceFileSignature + when (sig /= radianceFileSignature) + (fail "Invalid radiance file signature") + + infos <- decodeInfos + let formatKey = BC.pack "FORMAT" + case partition (\(k,_) -> k /= formatKey) infos of + (_, []) -> fail "No radiance format specified" + (info, [(_, formatString)]) -> + case runGet get $ L.fromChunks [formatString] of + Left err -> fail err + Right format -> do + (n1, n2, b) <- (,,) <$> decodeNum + <*> decodeNum + <*> getRemainingBytes + return . RadianceHeader info format n1 n2 $ L.fromChunks [b] + + _ -> fail "Multiple radiance format specified" + +toFloat :: RGBE -> PixelRGBF +toFloat (RGBE r g b e) = PixelRGBF rf gf bf + where f = encodeFloat 1 $ fromIntegral e - (128 + 8) + rf = (fromIntegral r + 0.0) * f + gf = (fromIntegral g + 0.0) * f + bf = (fromIntegral b + 0.0) * f + +encodeScanlineColor :: M.STVector s Word8 + -> M.STVector s Word8 + -> Int + -> ST s Int +encodeScanlineColor vec outVec outIdx = do + val <- vec `M.unsafeRead` 0 + runLength 1 0 val 1 outIdx + where maxIndex = M.length vec + + pushRun len val at = do + (outVec `M.unsafeWrite` at) $ fromIntegral $ len .|. 0x80 + (outVec `M.unsafeWrite` (at + 1)) val + return $ at + 2 + + pushData start len at = do + (outVec `M.unsafeWrite` at) $ fromIntegral len + let first = start - len + end = start - 1 + offset = at - first + 1 + forM_ [first .. end] $ \i -> do + v <- vec `M.unsafeRead` i + (outVec `M.unsafeWrite` (offset + i)) v + + return $ at + len + 1 + + -- End of scanline, empty the thing + runLength run cpy prev idx at | idx >= maxIndex = + case (run, cpy) of + (0, 0) -> pure at + (0, n) -> pushData idx n at + (n, 0) -> pushRun n prev at + (_, _) -> error "HDR - Run length algorithm is wrong" + + -- full runlength, we must write the packet + runLength r@127 _ prev idx at = do + val <- vec `M.unsafeRead` idx + pushRun r prev at >>= + runLength 1 0 val (idx + 1) + + -- full copy, we must write the packet + runLength _ c@127 _ idx at = do + val <- vec `M.unsafeRead` idx + pushData idx c at >>= + runLength 1 0 val (idx + 1) + + runLength n 0 prev idx at = do + val <- vec `M.unsafeRead` idx + case val == prev of + True -> runLength (n + 1) 0 prev (idx + 1) at + False | n < 4 -> runLength 0 (n + 1) val (idx + 1) at + False -> do + pushRun n prev at >>= + runLength 1 0 val (idx + 1) + + runLength 0 n prev idx at = do + val <- vec `M.unsafeRead` idx + if val /= prev + then runLength 0 (n + 1) val (idx + 1) at + else do + pushData (idx - 1) (n - 1) at >>= + runLength (2 :: Int) 0 val (idx + 1) + + runLength _ _ _ _ _ = + error "HDR RLE inconsistent state" + +-- | Write an High dynamic range image into a radiance +-- image file on disk. +writeHDR :: FilePath -> Image PixelRGBF -> IO () +writeHDR filename img = L.writeFile filename $ encodeHDR img + +-- | Encode an High dynamic range image into a radiance image +-- file format. +encodeHDR :: Image PixelRGBF -> L.ByteString +encodeHDR pic = encode $ runST $ do + let w = imageWidth pic + h = imageHeight pic + + scanLineR <- M.new w :: ST s (M.STVector s Word8) + scanLineG <- M.new w + scanLineB <- M.new w + scanLineE <- M.new w + + encoded <- + forM [0 .. h - 1] $ \line -> do + buff <- M.new $ w * 4 + w `div` 127 + 2 + let columner col | col >= w = return () + columner col = do + let RGBE r g b e = toRGBE $ pixelAt pic col line + (scanLineR `M.unsafeWrite` col) r + (scanLineG `M.unsafeWrite` col) g + (scanLineB `M.unsafeWrite` col) b + (scanLineE `M.unsafeWrite` col) e + + columner (col + 1) + + columner 0 + + (buff `M.unsafeWrite` 0) 2 + (buff `M.unsafeWrite` 1) 2 + (buff `M.unsafeWrite` 2) $ fromIntegral ((w .>>. 8) .&. 0xFF) + (buff `M.unsafeWrite` 3) $ fromIntegral (w .&. 0xFF) + + i1 <- encodeScanlineColor scanLineR buff 4 + i2 <- encodeScanlineColor scanLineG buff i1 + i3 <- encodeScanlineColor scanLineB buff i2 + endIndex <- encodeScanlineColor scanLineE buff i3 + + (\v -> blitVector v 0 endIndex) <$> V.unsafeFreeze buff + + pure $ RadianceHeader + { radianceInfos = [] + , radianceFormat = FormatRGBE + , radianceHeight = h + , radianceWidth = w + , radianceData = L.fromChunks encoded + } + + +decodeRadiancePicture :: RadianceHeader -> HDRReader s (MutableImage s (PixelRGBF)) +decodeRadiancePicture hdr = do + let width = abs $ radianceWidth hdr + height = abs $ radianceHeight hdr + packedData = radianceData hdr + + scanLine <- lift $ M.new $ width * 4 + resultBuffer <- lift $ M.new $ width * height * 3 + + let scanLineImage = MutableImage + { mutableImageWidth = width + , mutableImageHeight = 1 + , mutableImageData = scanLine + } + + finalImage = MutableImage + { mutableImageWidth = width + , mutableImageHeight = height + , mutableImageData = resultBuffer + } + + let scanLineExtractor readIdx line = do + let color = unpackColor packedData readIdx + inner | isNewRunLengthMarker color = do + let calcSize = checkLineLength color + when (calcSize /= width) + (throwError "Invalid sanline size") + pure $ \idx -> newStyleRLE packedData (idx + 4) + | otherwise = pure $ oldStyleRLE packedData + f <- inner + newRead <- f readIdx scanLine + forM_ [0 .. width - 1] $ \i -> do + -- mokay, it's a hack, but I don't want to define a + -- pixel instance of RGBE... + PixelRGBA8 r g b e <- lift $ readPixel scanLineImage i 0 + lift $ writePixel finalImage i line . toFloat $ RGBE r g b e + + return newRead + + foldM_ scanLineExtractor 0 [0 .. height - 1] + + return finalImage +
+ src/Codec/Picture/InternalHelper.hs view
@@ -0,0 +1,33 @@+module Codec.Picture.InternalHelper ( runGet + , runGetStrict + , decode + , getRemainingBytes + , getRemainingLazyBytes ) where + +import qualified Data.ByteString as B +import qualified Data.ByteString.Lazy as L +import Data.Binary( Binary( get ) ) +import Data.Binary.Get( Get, runGetOrFail, getRemainingLazyByteString ) + +decode :: (Binary a) => B.ByteString -> Either String a +decode = runGetStrict get + +runGet :: Get a -> L.ByteString -> Either String a +runGet act = unpack . runGetOrFail act + where unpack (Left (_, _, str)) = Left str + unpack (Right (_, _, element)) = Right element + +runGetStrict :: Get a -> B.ByteString -> Either String a +runGetStrict act buffer = runGet act $ L.fromChunks [buffer] + +getRemainingBytes :: Get B.ByteString +getRemainingBytes = do + rest <- getRemainingLazyByteString + return $ case L.toChunks rest of + [] -> B.empty + [a] -> a + lst -> B.concat lst + +getRemainingLazyBytes :: Get L.ByteString +getRemainingLazyBytes = getRemainingLazyByteString +
+ src/Codec/Picture/Jpg.hs view
@@ -0,0 +1,721 @@+{-# LANGUAGE TupleSections #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE BangPatterns #-} +{-# OPTIONS_GHC -fspec-constr-count=5 #-} +-- | Module used for JPEG file loading and writing. +module Codec.Picture.Jpg( decodeJpeg + , encodeJpegAtQuality + , encodeJpeg + + , jpgMachineStep + ) where + +import Control.Arrow( (>>>) ) +import Control.Applicative( pure, (<$>) ) +import Control.Monad( when, forM_ ) +import Control.Monad.ST( ST, runST ) +import Control.Monad.Trans( lift ) +import Control.Monad.Trans.RWS.Strict( RWS, modify, tell, gets, execRWS ) + +import Data.Bits( (.|.), unsafeShiftL ) +import Data.Int( Int16, Int32 ) +import Data.Word(Word8, Word32) +import Data.Binary( Binary(..), encode ) +import Data.STRef( newSTRef, writeSTRef, readSTRef ) + +import Data.Vector( (//) ) +import Data.Vector.Unboxed( (!) ) +import qualified Data.Vector as V +import qualified Data.Vector.Unboxed as VU +import qualified Data.Vector.Storable as VS +import qualified Data.Vector.Storable.Mutable as M +import qualified Data.ByteString as B +import qualified Data.ByteString.Lazy as L + +import Codec.Picture.InternalHelper +import Codec.Picture.BitWriter +import Codec.Picture.Types +import Codec.Picture.Jpg.Types +import Codec.Picture.Jpg.Common +import Codec.Picture.Jpg.Progressive +import Codec.Picture.Jpg.DefaultTable +import Codec.Picture.Jpg.FastDct + +quantize :: MacroBlock Int16 -> MutableMacroBlock s Int32 + -> ST s (MutableMacroBlock s Int32) +quantize table block = update 0 + where update 64 = return block + update idx = do + val <- block `M.unsafeRead` idx + let q = fromIntegral (table `VS.unsafeIndex` idx) + finalValue = (val + (q `div` 2)) `quot` q -- rounded integer division + (block `M.unsafeWrite` idx) finalValue + update $ idx + 1 + + +powerOf :: Int32 -> Word32 +powerOf 0 = 0 +powerOf n = limit 1 0 + where val = abs n + limit range i | val < range = i + limit range i = limit (2 * range) (i + 1) + +encodeInt :: BoolWriteStateRef s -> Word32 -> Int32 -> ST s () +{-# INLINE encodeInt #-} +encodeInt st ssss n | n > 0 = writeBits' st (fromIntegral n) (fromIntegral ssss) +encodeInt st ssss n = writeBits' st (fromIntegral $ n - 1) (fromIntegral ssss) + +-- | Assume the macro block is initialized with zeroes +acCoefficientsDecode :: HuffmanPackedTree -> MutableMacroBlock s Int16 + -> BoolReader s (MutableMacroBlock s Int16) +acCoefficientsDecode acTree mutableBlock = parseAcCoefficient 1 >> return mutableBlock + where parseAcCoefficient n | n >= 64 = return () + | otherwise = do + rrrrssss <- decodeRrrrSsss acTree + case rrrrssss of + ( 0, 0) -> return () + (0xF, 0) -> parseAcCoefficient (n + 16) + (rrrr, ssss) -> do + decoded <- fromIntegral <$> decodeInt ssss + lift $ (mutableBlock `M.unsafeWrite` (n + rrrr)) decoded + parseAcCoefficient (n + rrrr + 1) + +-- | Decompress a macroblock from a bitstream given the current configuration +-- from the frame. +decompressMacroBlock :: HuffmanPackedTree -- ^ Tree used for DC coefficient + -> HuffmanPackedTree -- ^ Tree used for Ac coefficient + -> MacroBlock Int16 -- ^ Current quantization table + -> MutableMacroBlock s Int16 -- ^ A zigzag table, to avoid allocation + -> DcCoefficient -- ^ Previous dc value + -> BoolReader s (DcCoefficient, MutableMacroBlock s Int16) +decompressMacroBlock dcTree acTree quantizationTable zigzagBlock previousDc = do + dcDeltaCoefficient <- dcCoefficientDecode dcTree + block <- lift createEmptyMutableMacroBlock + let neoDcCoefficient = previousDc + dcDeltaCoefficient + lift $ (block `M.unsafeWrite` 0) neoDcCoefficient + fullBlock <- acCoefficientsDecode acTree block + decodedBlock <- lift $ decodeMacroBlock quantizationTable zigzagBlock fullBlock + return (neoDcCoefficient, decodedBlock) + +pixelClamp :: Int16 -> Word8 +pixelClamp n = fromIntegral . min 255 $ max 0 n + +unpack444Y :: Int -- ^ component index + -> Int -- ^ x + -> Int -- ^ y + -> MutableImage s PixelYCbCr8 + -> MutableMacroBlock s Int16 + -> ST s () +unpack444Y _ x y (MutableImage { mutableImageWidth = imgWidth, mutableImageData = img }) + block = blockVert baseIdx 0 zero + where zero = 0 :: Int + baseIdx = x * dctBlockSize + y * dctBlockSize * imgWidth + + blockVert _ _ j | j >= dctBlockSize = return () + blockVert writeIdx readingIdx j = blockHoriz writeIdx readingIdx zero + where blockHoriz _ readIdx i | i >= dctBlockSize = blockVert (writeIdx + imgWidth) readIdx $ j + 1 + blockHoriz idx readIdx i = do + val <- pixelClamp <$> (block `M.unsafeRead` readIdx) + (img `M.unsafeWrite` idx) val + blockHoriz (idx + 1) (readIdx + 1) $ i + 1 + +unpack444Ycbcr :: Int -- ^ Component index + -> Int -- ^ x + -> Int -- ^ y + -> MutableImage s PixelYCbCr8 + -> MutableMacroBlock s Int16 + -> ST s () +unpack444Ycbcr compIdx x y + (MutableImage { mutableImageWidth = imgWidth, mutableImageData = img }) + block = blockVert baseIdx 0 zero + where zero = 0 :: Int + baseIdx = (x * dctBlockSize + y * dctBlockSize * imgWidth) * 3 + compIdx + + blockVert _ _ j | j >= dctBlockSize = return () + blockVert idx readIdx j = do + val0 <- pixelClamp <$> (block `M.unsafeRead` readIdx) + val1 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 1)) + val2 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 2)) + val3 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 3)) + val4 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 4)) + val5 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 5)) + val6 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 6)) + val7 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 7)) + + (img `M.unsafeWrite` idx) val0 + (img `M.unsafeWrite` (idx + (3 * 1))) val1 + (img `M.unsafeWrite` (idx + (3 * 2))) val2 + (img `M.unsafeWrite` (idx + (3 * 3))) val3 + (img `M.unsafeWrite` (idx + (3 * 4))) val4 + (img `M.unsafeWrite` (idx + (3 * 5))) val5 + (img `M.unsafeWrite` (idx + (3 * 6))) val6 + (img `M.unsafeWrite` (idx + (3 * 7))) val7 + + blockVert (idx + 3 * imgWidth) (readIdx + dctBlockSize) $ j + 1 + + + {-where blockHoriz _ readIdx i | i >= 8 = blockVert (writeIdx + imgWidth * 3) readIdx $ j + 1-} + {-blockHoriz idx readIdx i = do-} + {-val <- pixelClamp <$> (block `M.unsafeRead` readIdx) -} + {-(img `M.unsafeWrite` idx) val-} + {-blockHoriz (idx + 3) (readIdx + 1) $ i + 1-} + +unpack421Ycbcr :: Int -- ^ Component index + -> Int -- ^ x + -> Int -- ^ y + -> MutableImage s PixelYCbCr8 + -> MutableMacroBlock s Int16 + -> ST s () +unpack421Ycbcr compIdx x y + (MutableImage { mutableImageWidth = imgWidth, + mutableImageHeight = _, mutableImageData = img }) + block = blockVert baseIdx 0 zero + where zero = 0 :: Int + baseIdx = (x * dctBlockSize + y * dctBlockSize * imgWidth) * 3 + compIdx + lineOffset = imgWidth * 3 + + blockVert _ _ j | j >= dctBlockSize = return () + blockVert idx readIdx j = do + v0 <- pixelClamp <$> (block `M.unsafeRead` readIdx) + v1 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 1)) + v2 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 2)) + v3 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 3)) + v4 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 4)) + v5 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 5)) + v6 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 6)) + v7 <- pixelClamp <$> (block `M.unsafeRead` (readIdx + 7)) + + (img `M.unsafeWrite` idx) v0 + (img `M.unsafeWrite` (idx + 3)) v0 + + (img `M.unsafeWrite` (idx + 6 * 1)) v1 + (img `M.unsafeWrite` (idx + 6 * 1 + 3)) v1 + + (img `M.unsafeWrite` (idx + 6 * 2)) v2 + (img `M.unsafeWrite` (idx + 6 * 2 + 3)) v2 + + (img `M.unsafeWrite` (idx + 6 * 3)) v3 + (img `M.unsafeWrite` (idx + 6 * 3 + 3)) v3 + + (img `M.unsafeWrite` (idx + 6 * 4)) v4 + (img `M.unsafeWrite` (idx + 6 * 4 + 3)) v4 + + (img `M.unsafeWrite` (idx + 6 * 5)) v5 + (img `M.unsafeWrite` (idx + 6 * 5 + 3)) v5 + + (img `M.unsafeWrite` (idx + 6 * 6)) v6 + (img `M.unsafeWrite` (idx + 6 * 6 + 3)) v6 + + (img `M.unsafeWrite` (idx + 6 * 7)) v7 + (img `M.unsafeWrite` (idx + 6 * 7 + 3)) v7 + + blockVert (idx + lineOffset) (readIdx + dctBlockSize) $ j + 1 + +type Unpacker s = Int -- ^ component index + -> Int -- ^ x + -> Int -- ^ y + -> MutableImage s PixelYCbCr8 + -> MutableMacroBlock s Int16 + -> ST s () + +type JpgScripter s a = + RWS () [([(JpgUnpackerParameter, Unpacker s)], L.ByteString)] JpgDecoderState a + +data JpgDecoderState = JpgDecoderState + { dcDecoderTables :: !(V.Vector HuffmanPackedTree) + , acDecoderTables :: !(V.Vector HuffmanPackedTree) + , quantizationMatrices :: !(V.Vector (MacroBlock Int16)) + , currentRestartInterv :: !Int + , currentFrame :: Maybe JpgFrameHeader + , isProgressive :: !Bool + , maximumHorizontalResolution :: !Int + , maximumVerticalResolution :: !Int + , seenBlobs :: !Int + } + +emptyDecoderState :: JpgDecoderState +emptyDecoderState = JpgDecoderState + { dcDecoderTables = V.fromList $ map snd + [ prepareHuffmanTable DcComponent 0 defaultDcLumaHuffmanTable + , prepareHuffmanTable DcComponent 1 defaultDcChromaHuffmanTable + ] + + , acDecoderTables = V.fromList $ map snd + [ prepareHuffmanTable AcComponent 0 defaultAcLumaHuffmanTable + , prepareHuffmanTable AcComponent 1 defaultAcChromaHuffmanTable + ] + + , quantizationMatrices = V.replicate 4 (VS.replicate (8 * 8) 1) + , currentRestartInterv = -1 + , currentFrame = Nothing + , isProgressive = False + , maximumHorizontalResolution = 0 + , maximumVerticalResolution = 0 + , seenBlobs = 0 + } + +-- | This pseudo interpreter interpret the Jpg frame for the huffman, +-- quant table and restart interval parameters. +jpgMachineStep :: JpgFrame -> JpgScripter s () +jpgMachineStep (JpgAppFrame _ _) = pure () +jpgMachineStep (JpgExtension _ _) = pure () +jpgMachineStep (JpgScanBlob hdr raw_data) = do + let scanCount = length $ scans hdr + params <- concat <$> mapM (scanSpecifier scanCount) (scans hdr) + modify $ \st -> st { seenBlobs = seenBlobs st + 1 } + tell [(params, raw_data) ] + where (selectionLow, selectionHigh) = spectralSelection hdr + approxHigh = fromIntegral $ successiveApproxHigh hdr + approxLow = fromIntegral $ successiveApproxLow hdr + + scanSpecifier scanCount scanSpec = do + let dcIndex = fromIntegral $ dcEntropyCodingTable scanSpec + acIndex = fromIntegral $ acEntropyCodingTable scanSpec + comp = fromIntegral (componentSelector scanSpec) - 1 + dcTree <- gets $ (V.! dcIndex) . dcDecoderTables + acTree <- gets $ (V.! acIndex) . acDecoderTables + isProgressiveImage <- gets isProgressive + maxiW <- gets maximumHorizontalResolution + maxiH <- gets maximumVerticalResolution + restart <- gets currentRestartInterv + frameInfo <- gets currentFrame + blobId <- gets seenBlobs + case frameInfo of + Nothing -> fail "Jpg decoding error - no previous frame" + Just v -> do + let compDesc = jpgComponents v !! comp + compCount = length $ jpgComponents v + xSampling = fromIntegral $ horizontalSamplingFactor compDesc + ySampling = fromIntegral $ verticalSamplingFactor compDesc + componentSubSampling = + (maxiW - xSampling + 1, maxiH - ySampling + 1) + (xCount, yCount) + | scanCount > 1 || isProgressiveImage = (xSampling, ySampling) + | otherwise = (1, 1) + + pure [ (JpgUnpackerParameter + { dcHuffmanTree = dcTree + , acHuffmanTree = acTree + , componentIndex = comp + , restartInterval = fromIntegral restart + , componentWidth = xSampling + , componentHeight = ySampling + , subSampling = componentSubSampling + , successiveApprox = (approxLow, approxHigh) + , readerIndex = blobId + , indiceVector = + if scanCount == 1 then 0 else 1 + , coefficientRange = + ( fromIntegral selectionLow + , fromIntegral selectionHigh ) + , blockIndex = y * ySampling + x + , blockMcuX = x + , blockMcuY = y + }, unpackerDecision compCount componentSubSampling) + | y <- [0 .. yCount - 1] + , x <- [0 .. xCount - 1] ] + +jpgMachineStep (JpgScans kind hdr) = modify $ \s -> + s { currentFrame = Just hdr + , isProgressive = case kind of + JpgProgressiveDCTHuffman -> True + _ -> False + , maximumHorizontalResolution = + fromIntegral $ maximum horizontalResolutions + , maximumVerticalResolution = + fromIntegral $ maximum verticalResolutions + } + where components = jpgComponents hdr + horizontalResolutions = map horizontalSamplingFactor components + verticalResolutions = map verticalSamplingFactor components +jpgMachineStep (JpgIntervalRestart restart) = + modify $ \s -> s { currentRestartInterv = fromIntegral restart } +jpgMachineStep (JpgHuffmanTable tables) = mapM_ placeHuffmanTrees tables + where placeHuffmanTrees (spec, tree) = case huffmanTableClass spec of + DcComponent -> modify $ \s -> + let neu = dcDecoderTables s // [(idx, tree)] in + s { dcDecoderTables = neu `seq` neu } + where idx = fromIntegral $ huffmanTableDest spec + + AcComponent -> modify $ \s -> + s { acDecoderTables = acDecoderTables s // [(idx, tree)] } + where idx = fromIntegral $ huffmanTableDest spec + +jpgMachineStep (JpgQuantTable tables) = mapM_ placeQuantizationTables tables + where placeQuantizationTables table = do + let idx = fromIntegral $ quantDestination table + tableData = quantTable table + modify $ \s -> + s { quantizationMatrices = quantizationMatrices s // [(idx, tableData)] } + +unpackerDecision :: Int -> (Int, Int) -> Unpacker s +unpackerDecision 1 (1, 1) = unpack444Y +unpackerDecision _ (1, 1) = unpack444Ycbcr +unpackerDecision _ (2, 1) = unpack421Ycbcr +unpackerDecision compCount (xScalingFactor, yScalingFactor) = + unpackMacroBlock compCount xScalingFactor yScalingFactor + +decodeImage :: JpgFrameHeader + -> V.Vector (MacroBlock Int16) + -> [([(JpgUnpackerParameter, Unpacker s)], L.ByteString)] + -> MutableImage s PixelYCbCr8 -- ^ Result image to write into + -> ST s () +decodeImage frame quants lst outImage = do + let compCount = length $ jpgComponents frame + zigZagArray <- createEmptyMutableMacroBlock + dcArray <- M.replicate compCount 0 :: ST s (M.STVector s DcCoefficient) + resetCounter <- newSTRef restartIntervalValue + + forM_ lst $ \(params, str) -> do + let componentsInfo = V.fromList params + compReader = initBoolStateJpg . B.concat $ L.toChunks str + maxiW = maximum [fst $ subSampling c | (c,_) <- params] + maxiH = maximum [snd $ subSampling c | (c,_) <- params] + + imageBlockWidth = (imgWidth + 7) `div` 8 + imageBlockHeight = (imgHeight + 7) `div` 8 + + imageMcuWidth = (imageBlockWidth + (maxiW - 1)) `div` maxiW + imageMcuHeight = (imageBlockHeight + (maxiH - 1)) `div` maxiH + + execBoolReader compReader $ rasterMap imageMcuWidth imageMcuHeight $ \x y -> do + resetLeft <- lift $ readSTRef resetCounter + if resetLeft == 0 then do + lift $ M.set dcArray 0 + byteAlignJpg + _restartCode <- decodeRestartInterval + lift $ resetCounter `writeSTRef` (restartIntervalValue - 1) + else + lift $ resetCounter `writeSTRef` (resetLeft - 1) + + V.forM_ componentsInfo $ \(comp, unpack) -> do + let compIdx = componentIndex comp + dcTree = dcHuffmanTree comp + acTree = acHuffmanTree comp + qTable = quants V.! (min 1 compIdx) + xd = blockMcuX comp + yd = blockMcuY comp + (subX, subY) = subSampling comp + dc <- lift $ dcArray `M.unsafeRead` compIdx + (dcCoeff, block) <- + decompressMacroBlock dcTree acTree qTable zigZagArray $ fromIntegral dc + lift $ (dcArray `M.unsafeWrite` compIdx) dcCoeff + let verticalLimited = y == imageMcuHeight - 1 + if (x == imageMcuWidth - 1) || verticalLimited then + lift $ unpackMacroBlock imgComponentCount + subX subY compIdx + (x * maxiW + xd) (y * maxiH + yd) outImage block + else + lift $ unpack compIdx (x * maxiW + xd) (y * maxiH + yd) outImage block + + where imgComponentCount = length $ jpgComponents frame + + imgWidth = fromIntegral $ jpgWidth frame + imgHeight = fromIntegral $ jpgHeight frame + restartIntervalValue = case lst of + ((p,_):_,_): _ -> restartInterval p + _ -> -1 + +gatherImageKind :: [JpgFrame] -> Maybe JpgImageKind +gatherImageKind lst = case [k | JpgScans k _ <- lst, isDctSpecifier k] of + [JpgBaselineDCTHuffman] -> Just BaseLineDCT + [JpgProgressiveDCTHuffman] -> Just ProgressiveDCT + _ -> Nothing + where isDctSpecifier JpgProgressiveDCTHuffman = True + isDctSpecifier JpgBaselineDCTHuffman = True + isDctSpecifier _ = False + +gatherScanInfo :: JpgImage -> (JpgFrameKind, JpgFrameHeader) +gatherScanInfo img = head [(a, b) | JpgScans a b <- jpgFrame img] + +-- | 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 'convertImage' from the 'ColorSpaceConvertible' +-- typeclass. +-- +-- This function can output the following pixel types : +-- +-- * PixelY8 +-- +-- * PixelYCbCr8 +-- +decodeJpeg :: B.ByteString -> Either String DynamicImage +decodeJpeg file = case runGetStrict get file of + Left err -> Left err + Right img -> case (compCount, imgKind) of + (_, Nothing) -> Left "Unknown Jpg kind" + (3, Just ProgressiveDCT) -> Right . ImageYCbCr8 $ decodeProgressive + (1, Just BaseLineDCT) -> Right . ImageY8 $ Image imgWidth imgHeight pixelData + (3, Just BaseLineDCT) -> Right . ImageYCbCr8 $ Image imgWidth imgHeight pixelData + _ -> Left "Wrong component count" + + where compCount = length $ jpgComponents scanInfo + (_,scanInfo) = gatherScanInfo img + + imgKind = gatherImageKind $ jpgFrame img + imgWidth = fromIntegral $ jpgWidth scanInfo + imgHeight = fromIntegral $ jpgHeight scanInfo + + imageSize = imgWidth * imgHeight * compCount + (st, wrotten) = execRWS (mapM_ jpgMachineStep (jpgFrame img)) () emptyDecoderState + Just fHdr = currentFrame st + + decodeProgressive = runST $ + progressiveUnpack + (maximumHorizontalResolution st, maximumVerticalResolution st) + fHdr + (quantizationMatrices st) + wrotten >>= unsafeFreezeImage + + pixelData = runST $ do + resultImage <- M.new imageSize + let wrapped = MutableImage imgWidth imgHeight resultImage + decodeImage + fHdr + (quantizationMatrices st) + wrotten + wrapped + VS.unsafeFreeze resultImage + +extractBlock :: Image PixelYCbCr8 -- ^ Source image + -> MutableMacroBlock s Int16 -- ^ Mutable block where to put extracted block + -> Int -- ^ Plane + -> Int -- ^ X sampling factor + -> Int -- ^ Y sampling factor + -> Int -- ^ Sample per pixel + -> Int -- ^ Block x + -> Int -- ^ Block y + -> ST s (MutableMacroBlock s Int16) +extractBlock (Image { imageWidth = w, imageHeight = h, imageData = src }) + block 1 1 sampCount plane bx by | (bx * dctBlockSize) + 7 < w && (by * 8) + 7 < h = do + let baseReadIdx = (by * dctBlockSize * w) + bx * dctBlockSize + sequence_ [(block `M.unsafeWrite` (y * dctBlockSize + x)) val + | y <- [0 .. dctBlockSize - 1] + , let blockReadIdx = baseReadIdx + y * w + , x <- [0 .. dctBlockSize - 1] + , let val = fromIntegral $ src `VS.unsafeIndex` ((blockReadIdx + x) * sampCount + plane) + ] + return block +extractBlock (Image { imageWidth = w, imageHeight = h, imageData = src }) + block sampWidth sampHeight sampCount plane bx by = do + let accessPixel x y | x < w && y < h = let idx = (y * w + x) * sampCount + plane in src `VS.unsafeIndex` idx + | x >= w = accessPixel (w - 1) y + | otherwise = accessPixel x (h - 1) + + pixelPerCoeff = fromIntegral $ sampWidth * sampHeight + + blockVal x y = sum [fromIntegral $ accessPixel (xBase + dx) (yBase + dy) + | dy <- [0 .. sampHeight - 1] + , dx <- [0 .. sampWidth - 1] ] `div` pixelPerCoeff + where xBase = blockXBegin + x * sampWidth + yBase = blockYBegin + y * sampHeight + + blockXBegin = bx * dctBlockSize * sampWidth + blockYBegin = by * dctBlockSize * sampHeight + + sequence_ [(block `M.unsafeWrite` (y * dctBlockSize + x)) $ blockVal x y | y <- [0 .. 7], x <- [0 .. 7] ] + return block + +serializeMacroBlock :: BoolWriteStateRef s + -> HuffmanWriterCode -> HuffmanWriterCode + -> MutableMacroBlock s Int32 + -> ST s () +serializeMacroBlock !st !dcCode !acCode !blk = + (blk `M.unsafeRead` 0) >>= (fromIntegral >>> encodeDc) >> writeAcs (0, 1) >> return () + where writeAcs acc@(_, 63) = + (blk `M.unsafeRead` 63) >>= (fromIntegral >>> encodeAcCoefs acc) >> return () + writeAcs acc@(_, i ) = + (blk `M.unsafeRead` i) >>= (fromIntegral >>> encodeAcCoefs acc) >>= writeAcs + + encodeDc n = writeBits' st (fromIntegral code) (fromIntegral bitCount) + >> when (ssss /= 0) (encodeInt st ssss n) + where ssss = powerOf $ fromIntegral n + (bitCount, code) = dcCode `V.unsafeIndex` fromIntegral ssss + + encodeAc 0 0 = writeBits' st (fromIntegral code) $ fromIntegral bitCount + where (bitCount, code) = acCode `V.unsafeIndex` 0 + + encodeAc zeroCount n | zeroCount >= 16 = + writeBits' st (fromIntegral code) (fromIntegral bitCount) >> encodeAc (zeroCount - 16) n + where (bitCount, code) = acCode `V.unsafeIndex` 0xF0 + encodeAc zeroCount n = + writeBits' st (fromIntegral code) (fromIntegral bitCount) >> encodeInt st ssss n + where rrrr = zeroCount `unsafeShiftL` 4 + ssss = powerOf $ fromIntegral n + rrrrssss = rrrr .|. ssss + (bitCount, code) = acCode `V.unsafeIndex` fromIntegral rrrrssss + + encodeAcCoefs ( _, 63) 0 = encodeAc 0 0 >> return (0, 64) + encodeAcCoefs (zeroRunLength, i) 0 = return (zeroRunLength + 1, i + 1) + encodeAcCoefs (zeroRunLength, i) n = + encodeAc zeroRunLength n >> return (0, i + 1) + +encodeMacroBlock :: QuantificationTable + -> MutableMacroBlock s Int32 + -> MutableMacroBlock s Int32 + -> Int16 + -> MutableMacroBlock s Int16 + -> ST s (Int32, MutableMacroBlock s Int32) +encodeMacroBlock quantTableOfComponent workData finalData prev_dc block = do + -- the inverse level shift is performed internally by the fastDCT routine + blk <- fastDctLibJpeg workData block + >>= zigZagReorderForward finalData + >>= quantize quantTableOfComponent + dc <- blk `M.unsafeRead` 0 + (blk `M.unsafeWrite` 0) $ dc - fromIntegral prev_dc + return (dc, blk) + +divUpward :: (Integral a) => a -> a -> a +divUpward n dividor = val + (if rest /= 0 then 1 else 0) + where (val, rest) = n `divMod` dividor + +prepareHuffmanTable :: DctComponent -> Word8 -> HuffmanTable + -> (JpgHuffmanTableSpec, HuffmanPackedTree) +prepareHuffmanTable classVal dest tableDef = + (JpgHuffmanTableSpec { huffmanTableClass = classVal + , huffmanTableDest = dest + , huffSizes = sizes + , huffCodes = V.fromListN 16 + [VU.fromListN (fromIntegral $ sizes ! i) lst + | (i, lst) <- zip [0..] tableDef ] + }, VS.singleton 0) + where sizes = VU.fromListN 16 $ map (fromIntegral . length) tableDef + +-- | Encode an image in jpeg at a reasonnable quality level. +-- If you want better quality or reduced file size, you should +-- use `encodeJpegAtQuality` +encodeJpeg :: Image PixelYCbCr8 -> L.ByteString +encodeJpeg = encodeJpegAtQuality 50 + +defaultHuffmanTables :: [(JpgHuffmanTableSpec, HuffmanPackedTree)] +defaultHuffmanTables = + [ prepareHuffmanTable DcComponent 0 defaultDcLumaHuffmanTable + , prepareHuffmanTable AcComponent 0 defaultAcLumaHuffmanTable + , prepareHuffmanTable DcComponent 1 defaultDcChromaHuffmanTable + , prepareHuffmanTable AcComponent 1 defaultAcChromaHuffmanTable + ] + +-- | Function to call to encode an image to jpeg. +-- The quality factor should be between 0 and 100 (100 being +-- the best quality). +encodeJpegAtQuality :: Word8 -- ^ Quality factor + -> Image PixelYCbCr8 -- ^ Image to encode + -> L.ByteString -- ^ Encoded JPEG +encodeJpegAtQuality quality img@(Image { imageWidth = w, imageHeight = h }) = encode finalImage + where finalImage = JpgImage [ JpgQuantTable quantTables + , JpgScans JpgBaselineDCTHuffman hdr + , JpgHuffmanTable defaultHuffmanTables + , JpgScanBlob scanHeader encodedImage + ] + + outputComponentCount = 3 + + scanHeader = scanHeader'{ scanLength = fromIntegral $ calculateSize scanHeader' } + scanHeader' = JpgScanHeader + { scanLength = 0 + , scanComponentCount = outputComponentCount + , scans = [ JpgScanSpecification { componentSelector = 1 + , dcEntropyCodingTable = 0 + , acEntropyCodingTable = 0 + } + , JpgScanSpecification { componentSelector = 2 + , dcEntropyCodingTable = 1 + , acEntropyCodingTable = 1 + } + , JpgScanSpecification { componentSelector = 3 + , dcEntropyCodingTable = 1 + , acEntropyCodingTable = 1 + } + ] + + , spectralSelection = (0, 63) + , successiveApproxHigh = 0 + , successiveApproxLow = 0 + } + + hdr = hdr' { jpgFrameHeaderLength = fromIntegral $ calculateSize hdr' } + hdr' = JpgFrameHeader { jpgFrameHeaderLength = 0 + , jpgSamplePrecision = 8 + , jpgHeight = fromIntegral h + , jpgWidth = fromIntegral w + , jpgImageComponentCount = outputComponentCount + , jpgComponents = [ + JpgComponent { componentIdentifier = 1 + , horizontalSamplingFactor = 2 + , verticalSamplingFactor = 2 + , quantizationTableDest = 0 + } + , JpgComponent { componentIdentifier = 2 + , horizontalSamplingFactor = 1 + , verticalSamplingFactor = 1 + , quantizationTableDest = 1 + } + , JpgComponent { componentIdentifier = 3 + , horizontalSamplingFactor = 1 + , verticalSamplingFactor = 1 + , quantizationTableDest = 1 + } + ] + } + + lumaQuant = scaleQuantisationMatrix (fromIntegral quality) + defaultLumaQuantizationTable + chromaQuant = scaleQuantisationMatrix (fromIntegral quality) + defaultChromaQuantizationTable + + zigzagedLumaQuant = zigZagReorderForwardv lumaQuant + zigzagedChromaQuant = zigZagReorderForwardv chromaQuant + quantTables = [ JpgQuantTableSpec { quantPrecision = 0, quantDestination = 0 + , quantTable = zigzagedLumaQuant } + , JpgQuantTableSpec { quantPrecision = 0, quantDestination = 1 + , quantTable = zigzagedChromaQuant } + ] + + encodedImage = runST $ do + let horizontalMetaBlockCount = + w `divUpward` (dctBlockSize * maxSampling) + verticalMetaBlockCount = + h `divUpward` (dctBlockSize * maxSampling) + maxSampling = 2 + lumaSamplingSize = ( maxSampling, maxSampling, zigzagedLumaQuant + , makeInverseTable defaultDcLumaHuffmanTree + , makeInverseTable defaultAcLumaHuffmanTree) + chromaSamplingSize = ( maxSampling - 1, maxSampling - 1, zigzagedChromaQuant + , makeInverseTable defaultDcChromaHuffmanTree + , makeInverseTable defaultAcChromaHuffmanTree) + componentDef = [lumaSamplingSize, chromaSamplingSize, chromaSamplingSize] + + imageComponentCount = length componentDef + + dc_table <- M.replicate 3 0 + block <- createEmptyMutableMacroBlock + workData <- createEmptyMutableMacroBlock + zigzaged <- createEmptyMutableMacroBlock + writeState <- newWriteStateRef + + -- It's ugly, I know, be avoid allocation + let blockDecoder mx my = component $ zip [0..] componentDef + where component [] = return () + component ((comp, (sizeX, sizeY, table, dc, ac)) : comp_rest) = + rasterMap sizeX sizeY decoder >> component comp_rest + where xSamplingFactor = maxSampling - sizeX + 1 + ySamplingFactor = maxSampling - sizeY + 1 + extractor = extractBlock img block xSamplingFactor ySamplingFactor imageComponentCount + + decoder subX subY = do + let blockY = my * sizeY + subY + blockX = mx * sizeX + subX + prev_dc <- dc_table `M.unsafeRead` comp + (dc_coeff, neo_block) <- (extractor comp blockX blockY >>= + encodeMacroBlock table workData zigzaged prev_dc) + (dc_table `M.unsafeWrite` comp) $ fromIntegral dc_coeff + serializeMacroBlock writeState dc ac neo_block + + rasterMap + horizontalMetaBlockCount verticalMetaBlockCount + blockDecoder + + finalizeBoolWriter writeState +
+ src/Codec/Picture/Jpg/Common.hs view
@@ -0,0 +1,232 @@+{-# LANGUAGE BangPatterns #-} +module Codec.Picture.Jpg.Common + ( DctCoefficients + , JpgUnpackerParameter( .. ) + , decodeInt + , dcCoefficientDecode + , deQuantize + , decodeRrrrSsss + , zigZagReorderForward + , zigZagReorderForwardv + , zigZagReorder + , inverseDirectCosineTransform + , unpackInt + , unpackMacroBlock + , rasterMap + , decodeMacroBlock + , decodeRestartInterval + ) where + +import Control.Applicative( (<$>), pure ) +import Control.Monad( replicateM, when ) +import Control.Monad.ST( ST, runST ) +import Data.Bits( unsafeShiftL, unsafeShiftR, (.&.) ) +import Data.Int( Int16, Int32 ) +import Data.List( foldl' ) +import Data.Maybe( fromMaybe ) +import Data.Word( Word8 ) +import qualified Data.Vector.Storable as VS +import qualified Data.Vector.Storable.Mutable as M +import Foreign.Storable ( Storable ) + +import Codec.Picture.Types +import Codec.Picture.BitWriter +import Codec.Picture.Jpg.Types +import Codec.Picture.Jpg.FastIdct +import Codec.Picture.Jpg.DefaultTable + +-- | Same as for DcCoefficient, to provide nicer type signatures +type DctCoefficients = DcCoefficient + +data JpgUnpackerParameter = JpgUnpackerParameter + { dcHuffmanTree :: !HuffmanPackedTree + , acHuffmanTree :: !HuffmanPackedTree + , componentIndex :: {-# UNPACK #-} !Int + , restartInterval :: {-# UNPACK #-} !Int + , componentWidth :: {-# UNPACK #-} !Int + , componentHeight :: {-# UNPACK #-} !Int + , subSampling :: !(Int, Int) + , coefficientRange :: !(Int, Int) + , successiveApprox :: !(Int, Int) + , readerIndex :: {-# UNPACK #-} !Int + , indiceVector :: {-# UNPACK #-} !Int + , blockIndex :: {-# UNPACK #-} !Int + , blockMcuX :: {-# UNPACK #-} !Int + , blockMcuY :: {-# UNPACK #-} !Int + } + deriving Show + +decodeRestartInterval :: BoolReader s Int32 +decodeRestartInterval = return (-1) {- do + bits <- replicateM 8 getNextBitJpg + if bits == replicate 8 True + then do + marker <- replicateM 8 getNextBitJpg + return $ packInt marker + else return (-1) + -} + +{-# INLINE decodeInt #-} +decodeInt :: Int -> BoolReader s Int32 +decodeInt ssss = do + signBit <- getNextBitJpg + let dataRange = 1 `unsafeShiftL` 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 + +decodeRrrrSsss :: HuffmanPackedTree -> BoolReader s (Int, Int) +decodeRrrrSsss tree = do + rrrrssss <- huffmanPackedDecode tree + let rrrr = (rrrrssss `unsafeShiftR` 4) .&. 0xF + ssss = rrrrssss .&. 0xF + pure (fromIntegral rrrr, fromIntegral ssss) + +dcCoefficientDecode :: HuffmanPackedTree -> BoolReader s DcCoefficient +dcCoefficientDecode dcTree = do + ssss <- huffmanPackedDecode dcTree + if ssss == 0 + then return 0 + else fromIntegral <$> decodeInt (fromIntegral ssss) + +-- | Apply a quantization matrix to a macroblock +{-# INLINE deQuantize #-} +deQuantize :: MacroBlock Int16 -> MutableMacroBlock s Int16 + -> ST s (MutableMacroBlock s Int16) +deQuantize table block = update 0 + where update 64 = return block + update i = do + val <- block `M.unsafeRead` i + let finalValue = val * (table `VS.unsafeIndex` i) + (block `M.unsafeWrite` i) finalValue + update $ i + 1 + +inverseDirectCosineTransform :: MutableMacroBlock s Int16 + -> ST s (MutableMacroBlock s Int16) +inverseDirectCosineTransform mBlock = + fastIdct mBlock >>= mutableLevelShift + +zigZagOrder :: MacroBlock Int +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] + ] + +zigZagReorderForwardv :: (Storable a, Num a) => VS.Vector a -> VS.Vector a +zigZagReorderForwardv vec = runST $ do + v <- M.new 64 + mv <- VS.thaw vec + zigZagReorderForward v mv >>= VS.freeze + +zigZagOrderForward :: MacroBlock Int +zigZagOrderForward = VS.generate 64 inv + where inv i = fromMaybe 0 $ VS.findIndex (i ==) zigZagOrder + +zigZagReorderForward :: (Storable a, Num a) + => MutableMacroBlock s a + -> MutableMacroBlock s a + -> ST s (MutableMacroBlock s a) +{-# SPECIALIZE INLINE zigZagReorderForward :: MutableMacroBlock s Int32 + -> MutableMacroBlock s Int32 + -> ST s (MutableMacroBlock s Int32) #-} +{-# SPECIALIZE INLINE zigZagReorderForward :: MutableMacroBlock s Int16 + -> MutableMacroBlock s Int16 + -> ST s (MutableMacroBlock s Int16) #-} +{-# SPECIALIZE INLINE zigZagReorderForward :: MutableMacroBlock s Word8 + -> MutableMacroBlock s Word8 + -> ST s (MutableMacroBlock s Word8) #-} +zigZagReorderForward zigzaged block = ordering zigZagOrderForward >> return zigzaged + where ordering !table = reorder (0 :: Int) + where reorder !i | i >= 64 = return () + reorder i = do + let idx = table `VS.unsafeIndex` i + v <- block `M.unsafeRead` idx + (zigzaged `M.unsafeWrite` i) v + reorder (i + 1) + +zigZagReorder :: MutableMacroBlock s Int16 -> MutableMacroBlock s Int16 + -> ST s (MutableMacroBlock s Int16) +zigZagReorder zigzaged block = do + let update i = do + let idx = zigZagOrder `VS.unsafeIndex` i + v <- block `M.unsafeRead` idx + (zigzaged `M.unsafeWrite` i) v + + reorder 63 = update 63 + reorder i = update i >> reorder (i + 1) + + reorder (0 :: Int) + return zigzaged + +-- | Unpack an int of the given size encoded from MSB to LSB. +unpackInt :: Int -> BoolReader s Int32 +unpackInt bitCount = packInt <$> replicateM bitCount getNextBitJpg + + +{-# INLINE rasterMap #-} +rasterMap :: (Monad m) + => Int -> Int -> (Int -> Int -> m ()) + -> m () +rasterMap width height f = liner 0 + where liner y | y >= height = return () + liner y = columner 0 + where columner x | x >= width = liner (y + 1) + columner x = f x y >> columner (x + 1) + +packInt :: [Bool] -> Int32 +packInt = foldl' bitStep 0 + where bitStep acc True = (acc `unsafeShiftL` 1) + 1 + bitStep acc False = acc `unsafeShiftL` 1 + +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 -- ^ Width coefficient + -> Int -- ^ Height coefficient + -> Int -- ^ x + -> Int -- ^ y + -> Int -- ^ Component index + -> MutableImage s PixelYCbCr8 + -> MutableMacroBlock s Int16 + -> ST s () +unpackMacroBlock compCount wCoeff hCoeff compIdx x y + (MutableImage { mutableImageWidth = imgWidth, + mutableImageHeight = imgHeight, mutableImageData = img }) + block = rasterMap dctBlockSize dctBlockSize unpacker + where unpacker i j = do + let yBase = y * dctBlockSize + j * hCoeff + compVal <- pixelClamp <$> (block `M.unsafeRead` (i + j * dctBlockSize)) + rasterMap wCoeff hCoeff $ \wDup hDup -> do + let xBase = x * dctBlockSize + i * wCoeff + xPos = xBase + wDup + yPos = yBase + hDup + + when (xPos < imgWidth && yPos < imgHeight) + (do let mutableIdx = (xPos + yPos * imgWidth) * compCount + compIdx + (img `M.unsafeWrite` mutableIdx) compVal) + +-- | 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 + -> MutableMacroBlock s Int16 + -> ST s (MutableMacroBlock s Int16) +decodeMacroBlock quantizationTable zigZagBlock block = + deQuantize quantizationTable block >>= zigZagReorder zigZagBlock + >>= inverseDirectCosineTransform +
+ src/Codec/Picture/Jpg/DefaultTable.hs view
@@ -0,0 +1,294 @@+{-# 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( .. ) + , HuffmanTable + , HuffmanPackedTree + , MacroBlock + , QuantificationTable + , HuffmanWriterCode + , scaleQuantisationMatrix + , makeMacroBlock + , makeInverseTable + , buildHuffmanTree + , packHuffmanTree + , huffmanPackedDecode + + , defaultChromaQuantizationTable + + , defaultLumaQuantizationTable + + , defaultAcChromaHuffmanTree + , defaultAcChromaHuffmanTable + + , defaultAcLumaHuffmanTree + , defaultAcLumaHuffmanTable + + , defaultDcChromaHuffmanTree + , defaultDcChromaHuffmanTable + + , defaultDcLumaHuffmanTree + , defaultDcLumaHuffmanTable + ) where + +import Data.Int( Int16 ) +import Foreign.Storable ( Storable ) +import Control.Monad.ST( runST ) +import qualified Data.Vector.Storable as SV +import qualified Data.Vector as V +import Data.Bits( unsafeShiftL, (.|.), (.&.) ) +import Data.Word( Word8, Word16 ) +import Data.List( foldl' ) +import qualified Data.Vector.Storable.Mutable as M + +import Codec.Picture.BitWriter + +-- | 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) + +type HuffmanPackedTree = SV.Vector Word16 + +type HuffmanWriterCode = V.Vector (Word8, Word16) + +packHuffmanTree :: HuffmanTree -> HuffmanPackedTree +packHuffmanTree tree = runST $ do + table <- M.replicate 512 0x8000 + let aux (Empty) idx = return $ idx + 1 + aux (Leaf v) idx = do + (table `M.unsafeWrite` idx) $ (fromIntegral v .|. 0x4000) + return $ idx + 1 + + aux (Branch i1@(Leaf _) i2@(Leaf _)) idx = + aux i1 idx >>= aux i2 + + aux (Branch i1@(Leaf _) i2) idx = do + _ <- aux i1 idx + ix2 <- aux i2 $ idx + 2 + (table `M.unsafeWrite` (idx + 1)) $ fromIntegral $ idx + 2 + return ix2 + + aux (Branch i1 i2@(Leaf _)) idx = do + ix1 <- aux i1 (idx + 2) + _ <- aux i2 (idx + 1) + (table `M.unsafeWrite` idx) . fromIntegral $ idx + 2 + return ix1 + + aux (Branch i1 i2) idx = do + ix1 <- aux i1 (idx + 2) + ix2 <- aux i2 ix1 + (table `M.unsafeWrite` idx) (fromIntegral $ idx + 2) + (table `M.unsafeWrite` (idx + 1)) (fromIntegral ix1) + return ix2 + _ <- aux tree 0 + SV.unsafeFreeze table + +makeInverseTable :: HuffmanTree -> HuffmanWriterCode +makeInverseTable t = V.replicate 255 (0,0) V.// inner 0 0 t + where inner _ _ Empty = [] + inner depth code (Leaf v) = [(fromIntegral v, (depth, code))] + inner depth code (Branch l r) = + inner (depth + 1) shifted l ++ inner (depth + 1) (shifted .|. 1) r + where shifted = code `unsafeShiftL` 1 + +-- | 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 = SV.Vector a + +type QuantificationTable = MacroBlock Int16 + +-- | Helper function to create pure macro block of the good size. +makeMacroBlock :: (Storable a) => [a] -> MacroBlock a +makeMacroBlock = SV.fromListN 64 + +-- | 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" + +scaleQuantisationMatrix :: Int -> QuantificationTable -> QuantificationTable +scaleQuantisationMatrix quality + | quality < 0 = scaleQuantisationMatrix 0 + -- shouldn't show much difference than with 1, + -- but hey, at least we're complete + | quality == 0 = SV.map (scale (10000 :: Int)) + | quality < 50 = let qq = 5000 `div` quality + in SV.map (scale qq) + | otherwise = SV.map (scale q) + where q = 200 - quality * 2 + scale coeff i = fromIntegral . min 255 + . max 1 + $ fromIntegral i * coeff `div` 100 + +huffmanPackedDecode :: HuffmanPackedTree -> BoolReader s Word8 +huffmanPackedDecode table = getNextBitJpg >>= aux 0 + where aux idx b + | (v .&. 0x8000) /= 0 = return 0 + | (v .&. 0x4000) /= 0 = return . fromIntegral $ v .&. 0xFF + | otherwise = getNextBitJpg >>= aux v + where tableIndex | b = idx + 1 + | otherwise = idx + v = table `SV.unsafeIndex` fromIntegral tableIndex + +defaultLumaQuantizationTable :: QuantificationTable +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 :: QuantificationTable +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 + ] + +defaultDcLumaHuffmanTree :: HuffmanTree +defaultDcLumaHuffmanTree = buildHuffmanTree defaultDcLumaHuffmanTable + +-- | From the Table K.3 of ITU-81 (p153) +defaultDcLumaHuffmanTable :: HuffmanTable +defaultDcLumaHuffmanTable = + [ [] + , [0] + , [1, 2, 3, 4, 5] + , [6] + , [7] + , [8] + , [9] + , [10] + , [11] + , [] + , [] + , [] + , [] + , [] + , [] + , [] + ] + +defaultDcChromaHuffmanTree :: HuffmanTree +defaultDcChromaHuffmanTree = buildHuffmanTree defaultDcChromaHuffmanTable + +-- | From the Table K.4 of ITU-81 (p153) +defaultDcChromaHuffmanTable :: HuffmanTable +defaultDcChromaHuffmanTable = + [ [] + , [0, 1, 2] + , [3] + , [4] + , [5] + , [6] + , [7] + , [8] + , [9] + , [10] + , [11] + , [] + , [] + , [] + , [] + , [] + ] + +defaultAcLumaHuffmanTree :: HuffmanTree +defaultAcLumaHuffmanTree = buildHuffmanTree defaultAcLumaHuffmanTable + +-- | From the Table K.5 of ITU-81 (p154) +defaultAcLumaHuffmanTable :: HuffmanTable +defaultAcLumaHuffmanTable = + [ [] + , [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] + ] + +type HuffmanTable = [[Word8]] + +defaultAcChromaHuffmanTree :: HuffmanTree +defaultAcChromaHuffmanTree = buildHuffmanTree defaultAcChromaHuffmanTable + +defaultAcChromaHuffmanTable :: HuffmanTable +defaultAcChromaHuffmanTable = + [ [] + , [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 + ] + ] +
+ src/Codec/Picture/Jpg/FastDct.hs view
@@ -0,0 +1,203 @@+module Codec.Picture.Jpg.FastDct( referenceDct, fastDctLibJpeg ) where + +import Control.Applicative( (<$>) ) +import Data.Int( Int16, Int32 ) +import Data.Bits( Bits, unsafeShiftR, unsafeShiftL ) +import Control.Monad.ST( ST ) + +import qualified Data.Vector.Storable.Mutable as M + +import Codec.Picture.Jpg.Types +import Control.Monad( forM, forM_ ) + +-- | Reference implementation of the DCT, directly implementing the formula +-- of ITU-81. It's slow as hell, perform to many operations, but is accurate +-- and a good reference point. +referenceDct :: MutableMacroBlock s Int32 + -> MutableMacroBlock s Int16 + -> ST s (MutableMacroBlock s Int32) +referenceDct workData block = forM_ [(u, v) | u <- [0 :: Int .. dctBlockSize - 1], v <- [0..dctBlockSize - 1]] (\(u,v) -> do + val <- at (u,v) + (workData `M.unsafeWrite` (v * dctBlockSize + u)) . truncate $ (1 / 4) * c u * c v * val) + >> return workData + where -- at :: (Int, Int) -> ST s Float + at (u,v) = sum <$> (forM [(x,y) | x <- [0..dctBlockSize - 1], y <- [0..dctBlockSize - 1 :: Int]] $ \(x,y) -> do + sample <- fromIntegral <$> (block `M.unsafeRead` (y * dctBlockSize + x)) + return $ sample * cos ((2 * fromIntegral x + 1) * fromIntegral u * (pi :: Float)/ 16) + * cos ((2 * fromIntegral y + 1) * fromIntegral v * pi / 16)) + c 0 = 1 / sqrt 2 + c _ = 1 + +pASS1_BITS, cONST_BITS :: Int +cONST_BITS = 13 +pASS1_BITS = 2 + + +fIX_0_298631336, fIX_0_390180644, fIX_0_541196100, + fIX_0_765366865, fIX_0_899976223, fIX_1_175875602, + fIX_1_501321110, fIX_1_847759065, fIX_1_961570560, + fIX_2_053119869, fIX_2_562915447, fIX_3_072711026 :: Int32 +fIX_0_298631336 =(2446) -- FIX(0.298631336) */ +fIX_0_390180644 =(3196) -- FIX(0.390180644) */ +fIX_0_541196100 =(4433) -- FIX(0.541196100) */ +fIX_0_765366865 =(6270) -- FIX(0.765366865) */ +fIX_0_899976223 =(7373) -- FIX(0.899976223) */ +fIX_1_175875602 =(9633) -- FIX(1.175875602) */ +fIX_1_501321110 =(12299) -- FIX(1.501321110) */ +fIX_1_847759065 =(15137) -- FIX(1.847759065) */ +fIX_1_961570560 =(16069) -- FIX(1.961570560) */ +fIX_2_053119869 =(16819) -- FIX(2.053119869) */ +fIX_2_562915447 =(20995) -- FIX(2.562915447) */ +fIX_3_072711026 =(25172) -- FIX(3.072711026) */ + +cENTERJSAMPLE :: Int32 +cENTERJSAMPLE = 128 + +-- | Fast DCT extracted from libjpeg +fastDctLibJpeg :: MutableMacroBlock s Int32 + -> MutableMacroBlock s Int16 + -> ST s (MutableMacroBlock s Int32) +fastDctLibJpeg workData sample_block = do + firstPass workData 0 + secondPass workData 7 + {-_ <- mutate (\_ a -> a `quot` 8) workData-} + return workData + where -- Pass 1: process rows. + -- Note results are scaled up by sqrt(8) compared to a true DCT; + -- furthermore, we scale the results by 2**PASS1_BITS. + firstPass _ i | i == dctBlockSize = return () + firstPass dataBlock i = do + let baseIdx = i * dctBlockSize + readAt idx = fromIntegral <$> sample_block `M.unsafeRead` (baseIdx + idx) + mult = (*) + writeAt idx n = (dataBlock `M.unsafeWrite` (baseIdx + idx)) n + writeAtPos idx n = (dataBlock `M.unsafeWrite` (baseIdx + idx)) + (n `unsafeShiftR` (cONST_BITS - pASS1_BITS)) + + blk0 <- readAt 0 + blk1 <- readAt 1 + blk2 <- readAt 2 + blk3 <- readAt 3 + blk4 <- readAt 4 + blk5 <- readAt 5 + blk6 <- readAt 6 + blk7 <- readAt 7 + + let tmp0 = blk0 + blk7 + tmp1 = blk1 + blk6 + tmp2 = blk2 + blk5 + tmp3 = blk3 + blk4 + + tmp10 = tmp0 + tmp3 + tmp12 = tmp0 - tmp3 + tmp11 = tmp1 + tmp2 + tmp13 = tmp1 - tmp2 + + tmp0' = blk0 - blk7 + tmp1' = blk1 - blk6 + tmp2' = blk2 - blk5 + tmp3' = blk3 - blk4 + + -- Stage 4 and output + writeAt 0 $ (tmp10 + tmp11 - dctBlockSize * cENTERJSAMPLE) `unsafeShiftL` pASS1_BITS + writeAt 4 $ (tmp10 - tmp11) `unsafeShiftL` pASS1_BITS + + let z1 = mult (tmp12 + tmp13) fIX_0_541196100 + + (1 `unsafeShiftL` (cONST_BITS - pASS1_BITS - 1)) + + writeAtPos 2 $ z1 + mult tmp12 fIX_0_765366865 + writeAtPos 6 $ z1 - mult tmp13 fIX_1_847759065 + + let tmp10' = tmp0' + tmp3' + tmp11' = tmp1' + tmp2' + tmp12' = tmp0' + tmp2' + tmp13' = tmp1' + tmp3' + z1' = mult (tmp12' + tmp13') fIX_1_175875602 -- c3 */ + -- Add fudge factor here for final descale. */ + + (1 `unsafeShiftL` (cONST_BITS - pASS1_BITS-1)) + tmp0'' = mult tmp0' fIX_1_501321110 + tmp1'' = mult tmp1' fIX_3_072711026 + tmp2'' = mult tmp2' fIX_2_053119869 + tmp3'' = mult tmp3' fIX_0_298631336 + + tmp10'' = mult tmp10' (- fIX_0_899976223) + tmp11'' = mult tmp11' (- fIX_2_562915447) + tmp12'' = mult tmp12' (- fIX_0_390180644) + z1' + tmp13'' = mult tmp13' (- fIX_1_961570560) + z1' + + writeAtPos 1 $ tmp0'' + tmp10'' + tmp12'' + writeAtPos 3 $ tmp1'' + tmp11'' + tmp13'' + writeAtPos 5 $ tmp2'' + tmp11'' + tmp12'' + writeAtPos 7 $ tmp3'' + tmp10'' + tmp13'' + + firstPass dataBlock $ i + 1 + + -- Pass 2: process columns. + -- We remove the PASS1_BITS scaling, but leave the results scaled up + -- by an overall factor of 8. + secondPass :: M.STVector s Int32 -> Int -> ST s () + secondPass _ (-1) = return () + secondPass block i = do + let readAt idx = block `M.unsafeRead` ((7 - i) + idx * dctBlockSize) + mult = (*) + writeAt idx n = (block `M.unsafeWrite` (dctBlockSize * idx + (7 - i))) n + writeAtPos idx n = (block `M.unsafeWrite` (dctBlockSize * idx + (7 - i))) $ n `unsafeShiftR` (cONST_BITS + pASS1_BITS + 3) + blk0 <- readAt 0 + blk1 <- readAt 1 + blk2 <- readAt 2 + blk3 <- readAt 3 + blk4 <- readAt 4 + blk5 <- readAt 5 + blk6 <- readAt 6 + blk7 <- readAt 7 + + let tmp0 = blk0 + blk7 + tmp1 = blk1 + blk6 + tmp2 = blk2 + blk5 + tmp3 = blk3 + blk4 + + -- Add fudge factor here for final descale. */ + tmp10 = tmp0 + tmp3 + (1 `unsafeShiftL` (pASS1_BITS-1)) + tmp12 = tmp0 - tmp3 + tmp11 = tmp1 + tmp2 + tmp13 = tmp1 - tmp2 + + tmp0' = blk0 - blk7 + tmp1' = blk1 - blk6 + tmp2' = blk2 - blk5 + tmp3' = blk3 - blk4 + + writeAt 0 $ (tmp10 + tmp11) `unsafeShiftR` (pASS1_BITS + 3) + writeAt 4 $ (tmp10 - tmp11) `unsafeShiftR` (pASS1_BITS + 3) + + let z1 = mult (tmp12 + tmp13) fIX_0_541196100 + + (1 `unsafeShiftL` (cONST_BITS + pASS1_BITS - 1)) + + writeAtPos 2 $ z1 + mult tmp12 fIX_0_765366865 + writeAtPos 6 $ z1 - mult tmp13 fIX_1_847759065 + + let tmp10' = tmp0' + tmp3' + tmp11' = tmp1' + tmp2' + tmp12' = tmp0' + tmp2' + tmp13' = tmp1' + tmp3' + + z1' = mult (tmp12' + tmp13') fIX_1_175875602 + -- Add fudge factor here for final descale. */ + + 1 `unsafeShiftL` (cONST_BITS+pASS1_BITS-1); + + tmp0'' = mult tmp0' fIX_1_501321110 + tmp1'' = mult tmp1' fIX_3_072711026 + tmp2'' = mult tmp2' fIX_2_053119869 + tmp3'' = mult tmp3' fIX_0_298631336 + tmp10'' = mult tmp10' (- fIX_0_899976223) + tmp11'' = mult tmp11' (- fIX_2_562915447) + tmp12'' = mult tmp12' (- fIX_0_390180644) + + z1' + tmp13'' = mult tmp13' (- fIX_1_961570560) + + z1' + writeAtPos 1 $ tmp0'' + tmp10'' + tmp12'' + writeAtPos 3 $ tmp1'' + tmp11'' + tmp13'' + writeAtPos 5 $ tmp2'' + tmp11'' + tmp12'' + writeAtPos 7 $ tmp3'' + tmp10'' + tmp13'' + + secondPass block (i - 1)
+ src/Codec/Picture/Jpg/FastIdct.hs view
@@ -0,0 +1,228 @@+{-# 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 qualified Data.Vector.Storable as V +import Control.Monad.ST( ST ) +import Data.Bits( unsafeShiftL, unsafeShiftR ) +import Data.Int( Int16 ) +import qualified Data.Vector.Storable.Mutable as M + +import Codec.Picture.Jpg.Types + +iclip :: V.Vector Int16 +iclip = V.fromListN 1024 [ val i| i <- [(-512) .. 511] ] + where val i | i < (-256) = -256 + | i > 255 = 255 + | otherwise = i + +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) + +-- 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 `M.unsafeRead` (0 + idx) + xx1 <- blk `M.unsafeRead` (4 + idx) + xx2 <- blk `M.unsafeRead` (6 + idx) + xx3 <- blk `M.unsafeRead` (2 + idx) + xx4 <- blk `M.unsafeRead` (1 + idx) + xx5 <- blk `M.unsafeRead` (7 + idx) + xx6 <- blk `M.unsafeRead` (5 + idx) + xx7 <- blk `M.unsafeRead` (3 + idx) + let initialState = IDctStage { x0 = (fromIntegral xx0 `unsafeShiftL` 11) + 128 + , x1 = fromIntegral xx1 `unsafeShiftL` 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) `unsafeShiftR` 8 + , x4 = (181 * (x4 c - x5 c) + 128) `unsafeShiftR` 8 + } + scaled c = c { x0 = (x7 c + x1 c) `unsafeShiftR` 8 + , x1 = (x3 c + x2 c) `unsafeShiftR` 8 + , x2 = (x0 c + x4 c) `unsafeShiftR` 8 + , x3 = (x8 c + x6 c) `unsafeShiftR` 8 + , x4 = (x8 c - x6 c) `unsafeShiftR` 8 + , x5 = (x0 c - x4 c) `unsafeShiftR` 8 + , x6 = (x3 c - x2 c) `unsafeShiftR` 8 + , x7 = (x7 c - x1 c) `unsafeShiftR` 8 + } + transformed = scaled . thirdStage . secondStage $ firstStage initialState + + (blk `M.unsafeWrite` (0 + idx)) . fromIntegral $ x0 transformed + (blk `M.unsafeWrite` (1 + idx)) . fromIntegral $ x1 transformed + (blk `M.unsafeWrite` (2 + idx)) . fromIntegral $ x2 transformed + (blk `M.unsafeWrite` (3 + idx)) . fromIntegral $ x3 transformed + (blk `M.unsafeWrite` (4 + idx)) . fromIntegral $ x4 transformed + (blk `M.unsafeWrite` (5 + idx)) . fromIntegral $ x5 transformed + (blk `M.unsafeWrite` (6 + idx)) . fromIntegral $ x6 transformed + (blk `M.unsafeWrite` (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 `M.unsafeRead` ( 0 + idx) + xx1 <- blk `M.unsafeRead` (8 * 4 + idx) + xx2 <- blk `M.unsafeRead` (8 * 6 + idx) + xx3 <- blk `M.unsafeRead` (8 * 2 + idx) + xx4 <- blk `M.unsafeRead` (8 + idx) + xx5 <- blk `M.unsafeRead` (8 * 7 + idx) + xx6 <- blk `M.unsafeRead` (8 * 5 + idx) + xx7 <- blk `M.unsafeRead` (8 * 3 + idx) + let initialState = IDctStage { x0 = (fromIntegral xx0 `unsafeShiftL` 8) + 8192 + , x1 = fromIntegral xx1 `unsafeShiftL` 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) `unsafeShiftR` 3 + , x5 = (x8' - (w1 + w7) * x5 c) `unsafeShiftR` 3 + , x6 = (x8'' - (w3 - w5) * x6 c) `unsafeShiftR` 3 + , x7 = (x8'' - (w3 + w5) * x7 c) `unsafeShiftR` 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) `unsafeShiftR` 3 + , x3 = (x1' + (w2 - w6) * x3 c) `unsafeShiftR` 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) `unsafeShiftR` 8 + , x4 = (181 * (x4 c - x5 c) + 128) `unsafeShiftR` 8 + } + + clip i | i < 511 = if i > -512 then iclip `V.unsafeIndex` (i + 512) + else iclip `V.unsafeIndex` 0 + + | otherwise = iclip `V.unsafeIndex` 1023 + + f = thirdStage . secondStage $ firstStage initialState + (blk `M.unsafeWrite` (idx + 8*0)) . clip $ (x7 f + x1 f) `unsafeShiftR` 14 + (blk `M.unsafeWrite` (idx + 8 )) . clip $ (x3 f + x2 f) `unsafeShiftR` 14 + (blk `M.unsafeWrite` (idx + 8*2)) . clip $ (x0 f + x4 f) `unsafeShiftR` 14 + (blk `M.unsafeWrite` (idx + 8*3)) . clip $ (x8 f + x6 f) `unsafeShiftR` 14 + (blk `M.unsafeWrite` (idx + 8*4)) . clip $ (x8 f - x6 f) `unsafeShiftR` 14 + (blk `M.unsafeWrite` (idx + 8*5)) . clip $ (x0 f - x4 f) `unsafeShiftR` 14 + (blk `M.unsafeWrite` (idx + 8*6)) . clip $ (x3 f - x2 f) `unsafeShiftR` 14 + (blk `M.unsafeWrite` (idx + 8*7)) . clip $ (x7 f - x1 f) `unsafeShiftR` 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 = rows 0 + where rows 8 = cols 0 + rows i = idctRow block (8 * i) >> rows (i + 1) + + cols 8 = return block + cols i = idctCol block i >> cols (i + 1) + +{-# INLINE mutableLevelShift #-} +-- | Perform a Jpeg level shift in a mutable fashion. +mutableLevelShift :: MutableMacroBlock s Int16 + -> ST s (MutableMacroBlock s Int16) +mutableLevelShift block = update 0 + where update 64 = return block + update idx = do + val <- block `M.unsafeRead` idx + (block `M.unsafeWrite` idx) $ val + 128 + update $ idx + 1 +
+ src/Codec/Picture/Jpg/Progressive.hs view
@@ -0,0 +1,321 @@+{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeSynonymInstances #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ScopedTypeVariables #-} +module Codec.Picture.Jpg.Progressive + ( JpgUnpackerParameter( .. ) + , progressiveUnpack + ) where + +import Control.Applicative( pure, (<$>) ) +import Control.Monad( when, forM_ ) +import Control.Monad.ST( ST ) +import Control.Monad.Trans( lift ) +import Data.Bits( (.&.), (.|.), unsafeShiftL ) +import Data.Int( Int16, Int32 ) +import qualified Data.ByteString as B +import qualified Data.ByteString.Lazy as L +import qualified Data.Vector as V +import qualified Data.Vector.Storable as VS +import Data.Vector( (!) ) +import qualified Data.Vector.Mutable as M +import qualified Data.Vector.Storable.Mutable as MS + +import Codec.Picture.Types +import Codec.Picture.BitWriter +import Codec.Picture.Jpg.Common +import Codec.Picture.Jpg.Types +import Codec.Picture.Jpg.DefaultTable + +createMcuLineIndices :: JpgComponent -> Int -> Int -> V.Vector (VS.Vector Int) +createMcuLineIndices param imgWidth mcuWidth = + V.fromList $ VS.fromList <$> [indexSolo, indexMulti] + where compW = fromIntegral $ horizontalSamplingFactor param + compH = fromIntegral $ verticalSamplingFactor param + imageBlockSize = (imgWidth + 7) `div` 8 + + indexSolo = + [y * mcuWidth * compW + x + | y <- [0 .. compH - 1], x <- [0 .. imageBlockSize - 1]] + + indexMulti = + [(mcu + y * mcuWidth) * compW + x + | mcu <- [0 .. mcuWidth - 1] + , y <- [0 .. compH - 1] + , x <- [0 .. compW - 1] ] + +decodeFirstDC :: JpgUnpackerParameter + -> MS.STVector s Int16 + -> MutableMacroBlock s Int16 + -> Int32 + -> BoolReader s Int32 +decodeFirstDC params dcCoeffs block eobrun = unpack >> pure eobrun + where unpack = do + (dcDeltaCoefficient) <- dcCoefficientDecode $ dcHuffmanTree params + previousDc <- lift $ dcCoeffs `MS.unsafeRead` componentIndex params + let neoDcCoefficient = previousDc + dcDeltaCoefficient + approxLow = fst $ successiveApprox params + scaledDc = neoDcCoefficient `unsafeShiftL` approxLow + lift $ (block `MS.unsafeWrite` 0) scaledDc + lift $ (dcCoeffs `MS.unsafeWrite` componentIndex params) neoDcCoefficient + +decodeRefineDc :: JpgUnpackerParameter + -> a + -> MutableMacroBlock s Int16 + -> Int32 + -> BoolReader s Int32 +decodeRefineDc params _ block eobrun = unpack >> pure eobrun + where approxLow = fst $ successiveApprox params + plusOne = 1 `unsafeShiftL` approxLow + unpack = do + bit <- getNextBitJpg + when bit . lift $ do + v <- block `MS.unsafeRead` 0 + (block `MS.unsafeWrite` 0) $ v .|. plusOne + +decodeFirstAc :: JpgUnpackerParameter + -> a + -> MutableMacroBlock s Int16 + -> Int32 + -> BoolReader s Int32 +decodeFirstAc _params _ _block eobrun | eobrun > 0 = pure $ eobrun - 1 +decodeFirstAc params _ block _ = unpack startIndex + where (startIndex, maxIndex) = coefficientRange params + (low, _) = successiveApprox params + unpack n | n > maxIndex = pure 0 + unpack n = do + rrrrssss <- decodeRrrrSsss $ acHuffmanTree params + case rrrrssss of + (0xF, 0) -> unpack $ n + 16 + ( 0, 0) -> return 0 + ( r, 0) -> eobrun <$> unpackInt r + where eobrun lowBits = (1 `unsafeShiftL` r) - 1 + lowBits + ( r, s) -> do + let n' = n + r + val <- (`unsafeShiftL` low) <$> decodeInt s + lift . (block `MS.unsafeWrite` n') $ fromIntegral val + unpack $ n' + 1 + +decodeRefineAc :: forall a s. JpgUnpackerParameter + -> a + -> MutableMacroBlock s Int16 + -> Int32 + -> BoolReader s Int32 +decodeRefineAc params _ block eobrun + | eobrun == 0 = unpack startIndex + | otherwise = performEobRun startIndex >> return (eobrun - 1) + where (startIndex, maxIndex) = coefficientRange params + (low, _) = successiveApprox params + plusOne = 1 `unsafeShiftL` low + minusOne = (-1) `unsafeShiftL` low + + getBitVal = do + v <- getNextBitJpg + pure $ if v then plusOne else minusOne + + {-performEobRun :: Int -> BoolReader s ()-} + performEobRun idx | idx > maxIndex = pure () + performEobRun idx = do + coeff <- lift $ block `MS.unsafeRead` idx + when (coeff /= 0) $ do + bit <- getNextBitJpg + case (bit, (coeff .&. plusOne) == 0) of + (False, _) -> performEobRun $ idx + 1 + (True, False) -> performEobRun $ idx + 1 + (True, True) -> do + let newVal | coeff >= 0 = coeff + plusOne + | otherwise = coeff + minusOne + lift $ (block `MS.unsafeWrite` idx) newVal + performEobRun $ idx + 1 + + unpack idx | idx > maxIndex = pure 0 + unpack idx = do + rrrrssss <- decodeRrrrSsss $ acHuffmanTree params + case rrrrssss of + (0xF, 0) -> do + idx' <- updateCoeffs 0x10 idx + unpack $ idx' + + ( r, 0) -> do + lowBits <- unpackInt r + let newEobRun = (1 `unsafeShiftL` r) + lowBits - 1 + performEobRun idx + pure newEobRun + + ( r, _) -> do + idx' <- updateCoeffs (fromIntegral r) idx + val <- getBitVal + when (idx <= maxIndex) $ + (lift $ (block `MS.unsafeWrite` idx') val) + unpack $ idx' + 1 + + updateCoeffs :: Int -> Int -> BoolReader s Int + updateCoeffs r idx + | r < 0 = pure idx + | idx > maxIndex = pure idx + updateCoeffs r idx = do + coeff <- lift $ block `MS.unsafeRead` idx + if coeff /= 0 then do + bit <- getNextBitJpg + when (bit && coeff .&. plusOne == 0) $ + if coeff >= 0 then + lift . (block `MS.unsafeWrite` idx) $ coeff + plusOne + else + lift . (block `MS.unsafeWrite` idx) $ coeff + minusOne + updateCoeffs r $ idx + 1 + else + updateCoeffs (r - 1) $ idx + 1 + +type Unpacker s = + JpgUnpackerParameter -> MS.STVector s Int16 -> MutableMacroBlock s Int16 -> Int32 + -> BoolReader s Int32 + + +prepareUnpacker :: [([(JpgUnpackerParameter, a)], L.ByteString)] + -> ST s ( V.Vector (V.Vector (JpgUnpackerParameter, Unpacker s)) + , M.STVector s BoolState) +prepareUnpacker lst = do + let boolStates = V.fromList $ map snd infos + vec <- V.unsafeThaw boolStates + return (V.fromList $ map fst infos, vec) + where infos = map prepare lst + prepare ([], _) = error "progressiveUnpack, no component" + prepare (whole@((param, _) : _) , byteString) = + (V.fromList $ map (\(p,_) -> (p, unpacker)) whole, boolReader) + where unpacker = selection (successiveApprox param) (coefficientRange param) + boolReader = initBoolStateJpg . B.concat $ L.toChunks byteString + + selection (_, 0) (0, _) = decodeFirstDC + selection (_, 0) _ = decodeFirstAc + selection _ (0, _) = decodeRefineDc + selection _ _ = decodeRefineAc + +data ComponentData s = ComponentData + { componentIndices :: V.Vector (VS.Vector Int) + , componentBlocks :: V.Vector (MutableMacroBlock s Int16) + , componentId :: !Int + , componentBlockCount :: !Int + } + +-- | Iteration from to n in monadic context, without data +-- keeping. +lineMap :: (Monad m) => Int -> (Int -> m ()) -> m () +{-# INLINE lineMap #-} +lineMap count f = go 0 + where go n | n >= count = return () + go n = f n >> go (n + 1) + +progressiveUnpack :: (Int, Int) + -> JpgFrameHeader + -> V.Vector (MacroBlock Int16) + -> [([(JpgUnpackerParameter, a)], L.ByteString)] + -> ST s (MutableImage s PixelYCbCr8) +progressiveUnpack (maxiW, maxiH) frame quants lst = do + (unpackers, readers) <- prepareUnpacker lst + allBlocks <- mapM allocateWorkingBlocks . zip [0..] $ jpgComponents frame + :: ST s [ComponentData s] + let scanCount = length lst + restartIntervalValue = case lst of + ((p,_):_,_): _ -> restartInterval p + _ -> -1 + dcCoeffs <- MS.replicate imgComponentCount 0 + eobRuns <- MS.replicate (length lst) 0 + workBlock <- createEmptyMutableMacroBlock + writeIndices <- MS.replicate imgComponentCount (0 :: Int) + restartIntervals <- MS.replicate scanCount restartIntervalValue + let elementCount = imgWidth * imgHeight * fromIntegral imgComponentCount + img <- MutableImage imgWidth imgHeight <$> MS.replicate elementCount 128 + + let processRestartInterval = + forM_ [0 .. scanCount - 1] $ \ix -> do + v <- restartIntervals `MS.read` ix + if v == 0 then do + -- reset DC prediction + when (ix == 0) (MS.set dcCoeffs 0) + reader <- readers `M.read` ix + (_, updated) <- runBoolReaderWith reader $ + byteAlignJpg >> decodeRestartInterval + (readers `M.write` ix) updated + (eobRuns `MS.unsafeWrite` ix) 0 + (restartIntervals `MS.unsafeWrite` ix) $ restartIntervalValue - 1 + else + (restartIntervals `MS.unsafeWrite` ix) $ v - 1 + + + lineMap imageMcuHeight $ \mmY -> do + -- Reset all blocks to 0 + forM_ allBlocks $ V.mapM_ (flip MS.set 0) . componentBlocks + MS.set writeIndices 0 + + lineMap imageMcuWidth $ \_mmx -> do + processRestartInterval + V.forM_ unpackers $ V.mapM_ $ \(unpackParam, unpacker) -> do + boolState <- readers `M.read` readerIndex unpackParam + eobrun <- eobRuns `MS.read` readerIndex unpackParam + let componentNumber = componentIndex unpackParam + writeIndex <- writeIndices `MS.read` componentNumber + let componentData = allBlocks !! componentNumber + indexVector = + componentIndices componentData ! indiceVector unpackParam + maxIndexLength = VS.length indexVector + if writeIndex + blockIndex unpackParam >= maxIndexLength then + return () + else do + let realIndex = indexVector VS.! (writeIndex + blockIndex unpackParam) + writeBlock = componentBlocks componentData ! realIndex + (eobrun', state) <- + runBoolReaderWith boolState $ + unpacker unpackParam dcCoeffs writeBlock eobrun + + (readers `M.write` readerIndex unpackParam) state + (eobRuns `MS.write` readerIndex unpackParam) eobrun' + + -- Update the write indices + forM_ allBlocks $ \comp -> do + writeIndex <- writeIndices `MS.read` componentId comp + let newIndex = writeIndex + componentBlockCount comp + (writeIndices `MS.write` componentId comp) $ newIndex + + forM_ allBlocks $ \compData -> do + let compBlocks = componentBlocks compData + cId = componentId compData + table = quants ! min 1 cId + comp = jpgComponents frame !! cId + compW = fromIntegral $ horizontalSamplingFactor comp + compH = fromIntegral $ verticalSamplingFactor comp + cw8 = maxiW - fromIntegral (horizontalSamplingFactor comp) + 1 + ch8 = maxiH - fromIntegral (verticalSamplingFactor comp) + 1 + + rasterMap (imageMcuWidth * compW) compH $ \rx y -> do + let ry = mmY * maxiH + y + block = compBlocks ! (y * imageMcuWidth * compW + rx) + transformed <- decodeMacroBlock table workBlock block + unpackMacroBlock imgComponentCount + cw8 ch8 cId (rx * cw8) ry + img transformed + + return img + + where imgComponentCount = length $ jpgComponents frame + + imgWidth = fromIntegral $ jpgWidth frame + imgHeight = fromIntegral $ jpgHeight frame + + imageBlockWidth = (imgWidth + 7) `div` 8 + imageBlockHeight = (imgHeight + 7) `div` 8 + + imageMcuWidth = (imageBlockWidth + (maxiW - 1)) `div` maxiW + imageMcuHeight = (imageBlockHeight + (maxiH - 1)) `div` maxiH + + allocateWorkingBlocks (ix, comp) = do + let blockCount = hSample * vSample * imageMcuWidth + blocks <- V.replicateM blockCount createEmptyMutableMacroBlock + return $ ComponentData + { componentBlocks = blocks + , componentIndices = createMcuLineIndices comp imgWidth imageMcuWidth + , componentBlockCount = hSample * vSample + , componentId = ix + } + where hSample = fromIntegral $ horizontalSamplingFactor comp + vSample = fromIntegral $ verticalSamplingFactor comp +
+ src/Codec/Picture/Jpg/Types.hs view
@@ -0,0 +1,546 @@+{-# LANGUAGE ScopedTypeVariables #-} +module Codec.Picture.Jpg.Types( MutableMacroBlock + , createEmptyMutableMacroBlock + , printMacroBlock + , printPureMacroBlock + , DcCoefficient + , JpgImage( .. ) + , JpgComponent( .. ) + , JpgFrameHeader( .. ) + , JpgFrame( .. ) + , JpgFrameKind( .. ) + , JpgScanHeader( .. ) + , JpgQuantTableSpec( .. ) + , JpgHuffmanTableSpec( .. ) + , JpgImageKind( .. ) + , JpgScanSpecification( .. ) + , calculateSize + , dctBlockSize + ) where + +import Control.Applicative( (<$>), (<*>)) +import Control.Monad( when, replicateM, forM, forM_, unless ) +import Control.Monad.ST( ST ) +import Data.Bits( (.|.), (.&.), unsafeShiftL, unsafeShiftR ) +import Foreign.Storable ( Storable ) +import Data.Vector.Unboxed( (!) ) +import qualified Data.Vector as V +import qualified Data.Vector.Unboxed as VU +import qualified Data.Vector.Storable as VS +import qualified Data.Vector.Storable.Mutable as M +import qualified Data.ByteString as B +import qualified Data.ByteString.Lazy as L + +import Data.Int( Int16 ) +import Data.Word(Word8, Word16 ) +import Data.Binary( Binary(..) ) + +import Data.Binary.Get( Get + , getWord8 + , getWord16be + , getByteString + , skip + , bytesRead + ) + +import Data.Binary.Put( Put + , putWord8 + , putWord16be + , putLazyByteString + ) + +import Codec.Picture.InternalHelper +import Codec.Picture.Jpg.DefaultTable + +{-import Debug.Trace-} +import Text.Printf + +-- | Type only used to make clear what kind of integer we are carrying +-- Might be transformed into newtype in the future +type DcCoefficient = Int16 + +-- | Macroblock that can be transformed. +type MutableMacroBlock s a = M.STVector s a + +data JpgFrameKind = + JpgBaselineDCTHuffman + | JpgExtendedSequentialDCTHuffman + | JpgProgressiveDCTHuffman + | JpgLosslessHuffman + | JpgDifferentialSequentialDCTHuffman + | JpgDifferentialProgressiveDCTHuffman + | JpgDifferentialLosslessHuffman + | JpgExtendedSequentialArithmetic + | JpgProgressiveDCTArithmetic + | JpgLosslessArithmetic + | JpgDifferentialSequentialDCTArithmetic + | JpgDifferentialProgressiveDCTArithmetic + | JpgDifferentialLosslessArithmetic + | JpgQuantizationTable + | JpgHuffmanTableMarker + | JpgStartOfScan + | JpgEndOfImage + | JpgAppSegment Word8 + | JpgExtensionSegment Word8 + + | JpgRestartInterval + | JpgRestartIntervalEnd Word8 + deriving (Eq, Show) + +data JpgFrame = + JpgAppFrame !Word8 B.ByteString + | JpgExtension !Word8 B.ByteString + | JpgQuantTable ![JpgQuantTableSpec] + | JpgHuffmanTable ![(JpgHuffmanTableSpec, HuffmanPackedTree)] + | JpgScanBlob !JpgScanHeader !L.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 + + +instance SizeCalculable JpgFrameHeader where + calculateSize hdr = 2 + 1 + 2 + 2 + 1 + + sum [calculateSize c | c <- jpgComponents hdr] + +data JpgComponent = JpgComponent + { componentIdentifier :: !Word8 + -- | Stored with 4 bits + , horizontalSamplingFactor :: !Word8 + -- | Stored with 4 bits + , verticalSamplingFactor :: !Word8 + , quantizationTableDest :: !Word8 + } + deriving Show + +instance SizeCalculable JpgComponent where + calculateSize _ = 3 + +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 + +instance SizeCalculable JpgScanSpecification where + calculateSize _ = 2 + +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 + +instance SizeCalculable JpgScanHeader where + calculateSize hdr = 2 + 1 + + sum [calculateSize c | c <- scans hdr] + + 2 + + 1 + +data JpgQuantTableSpec = JpgQuantTableSpec + { -- | Stored on 4 bits + quantPrecision :: !Word8 + + -- | Stored on 4 bits + , quantDestination :: !Word8 + + , quantTable :: MacroBlock Int16 + } + deriving Show + +class SizeCalculable a where + calculateSize :: a -> Int + +-- | Type introduced only to avoid some typeclass overlapping +-- problem +newtype TableList a = TableList [a] + +instance (SizeCalculable a, Binary a) => Binary (TableList a) where + put (TableList lst) = do + putWord16be . fromIntegral $ sum [calculateSize table | table <- lst] + 2 + mapM_ put lst + + get = TableList <$> (getWord16be >>= \s -> innerParse (fromIntegral s - 2)) + where innerParse :: Int -> Get [a] + innerParse 0 = return [] + innerParse size = do + onStart <- fromIntegral <$> bytesRead + table <- get + onEnd <- fromIntegral <$> bytesRead + (table :) <$> innerParse (size - (onEnd - onStart)) + +instance SizeCalculable JpgQuantTableSpec where + calculateSize table = + 1 + (fromIntegral (quantPrecision table) + 1) * 64 + +instance Binary JpgQuantTableSpec where + put table = do + let precision = quantPrecision table + put4BitsOfEach precision (quantDestination table) + forM_ (VS.toList $ 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 = VS.fromListN 64 coeffs + } + +data JpgHuffmanTableSpec = JpgHuffmanTableSpec + { -- | 0 : DC, 1 : AC, stored on 4 bits + huffmanTableClass :: !DctComponent + -- | Stored on 4 bits + , huffmanTableDest :: !Word8 + + , huffSizes :: !(VU.Vector Word8) + , huffCodes :: !(V.Vector (VU.Vector Word8)) + } + deriving Show + +instance SizeCalculable JpgHuffmanTableSpec where + calculateSize table = 1 + 16 + sum [fromIntegral e | e <- VU.toList $ huffSizes table] + +instance Binary JpgHuffmanTableSpec where + put table = do + let classVal = if huffmanTableClass table == DcComponent + then 0 else 1 + put4BitsOfEach classVal $ huffmanTableDest table + mapM_ put . VU.toList $ huffSizes table + forM_ [0 .. 15] $ \i -> + when (huffSizes table ! i /= 0) + (let elements = VU.toList $ huffCodes table V.! i + in mapM_ put elements) + + get = do + (huffClass, huffDest) <- get4BitOfEach + sizes <- replicateM 16 getWord8 + codes <- forM sizes $ \s -> + VU.replicateM (fromIntegral s) getWord8 + return JpgHuffmanTableSpec + { huffmanTableClass = + if huffClass == 0 then DcComponent else AcComponent + , huffmanTableDest = huffDest + , huffSizes = VU.fromListN 16 sizes + , huffCodes = V.fromListN 16 codes + } + +instance Binary JpgImage where + put (JpgImage { jpgFrame = frames }) = + putWord8 0xFF >> putWord8 0xD8 >> mapM_ putFrame frames + >> putWord8 0xFF >> putWord8 0xD9 + + get = do + let startOfImageMarker = 0xD8 + -- endOfImageMarker = 0xD9 + checkMarker commonMarkerFirstByte startOfImageMarker + eatUntilCode + frames <- parseFrames + {-checkMarker commonMarkerFirstByte endOfImageMarker-} + return JpgImage { jpgFrame = frames } + +eatUntilCode :: Get () +eatUntilCode = do + code <- getWord8 + unless (code == 0xFF) eatUntilCode + +takeCurrentFrame :: Get B.ByteString +takeCurrentFrame = do + size <- getWord16be + getByteString (fromIntegral size - 2) + +putFrame :: JpgFrame -> Put +putFrame (JpgAppFrame appCode str) = + put (JpgAppSegment appCode) >> putWord16be (fromIntegral $ B.length str) >> put str +putFrame (JpgExtension appCode str) = + put (JpgExtensionSegment appCode) >> putWord16be (fromIntegral $ B.length str) >> put str +putFrame (JpgQuantTable tables) = + put JpgQuantizationTable >> put (TableList tables) +putFrame (JpgHuffmanTable tables) = + put JpgHuffmanTableMarker >> put (TableList $ map fst tables) +putFrame (JpgIntervalRestart size) = + put JpgRestartInterval >> put (RestartInterval size) +putFrame (JpgScanBlob hdr blob) = + put JpgStartOfScan >> put hdr >> putLazyByteString blob +putFrame (JpgScans kind hdr) = + put kind >> put hdr + +-------------------------------------------------- +---- 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") + +extractScanContent :: L.ByteString -> (L.ByteString, L.ByteString) +extractScanContent str = aux 0 + where maxi = fromIntegral $ L.length str - 1 + + aux n | n >= maxi = (str, L.empty) + | v == 0xFF && vNext /= 0 && not isReset = L.splitAt n str + | otherwise = aux (n + 1) + where v = str `L.index` n + vNext = str `L.index` (n + 1) + isReset = 0xD0 <= vNext && vNext <= 0xD7 + +parseFrames :: Get [JpgFrame] +parseFrames = do + kind <- get + let parseNextFrame = do + word <- getWord8 + when (word /= 0xFF) $ do + readedData <- bytesRead + fail $ "Invalid Frame marker (" ++ show word + ++ ", bytes read : " ++ show readedData ++ ")" + parseFrames + + case kind of + JpgEndOfImage -> return [] + JpgAppSegment c -> + (\frm lst -> JpgAppFrame c frm : lst) <$> takeCurrentFrame <*> parseNextFrame + JpgExtensionSegment c -> + (\frm lst -> JpgExtension c frm : lst) <$> takeCurrentFrame <*> parseNextFrame + JpgQuantizationTable -> + (\(TableList quants) lst -> JpgQuantTable quants : lst) <$> get <*> parseNextFrame + JpgRestartInterval -> + (\(RestartInterval i) lst -> JpgIntervalRestart i : lst) <$> get <*> parseNextFrame + JpgHuffmanTableMarker -> + (\(TableList huffTables) lst -> + JpgHuffmanTable [(t, packHuffmanTree . buildPackedHuffmanTree $ huffCodes t) | t <- huffTables] : lst) + <$> get <*> parseNextFrame + JpgStartOfScan -> + (\frm imgData -> + let (d, other) = extractScanContent imgData + in + case runGet parseFrames (L.drop 1 other) of + Left _ -> [JpgScanBlob frm d] + Right lst -> JpgScanBlob frm d : lst + ) <$> get <*> getRemainingLazyBytes + + _ -> (\hdr lst -> JpgScans kind hdr : lst) <$> get <*> parseNextFrame + +buildPackedHuffmanTree :: V.Vector (VU.Vector Word8) -> HuffmanTree +buildPackedHuffmanTree = buildHuffmanTree . map VU.toList . V.toList + +secondStartOfFrameByteOfKind :: JpgFrameKind -> Word8 +secondStartOfFrameByteOfKind = aux + where + aux JpgBaselineDCTHuffman = 0xC0 + aux JpgExtendedSequentialDCTHuffman = 0xC1 + aux JpgProgressiveDCTHuffman = 0xC2 + aux JpgLosslessHuffman = 0xC3 + aux JpgDifferentialSequentialDCTHuffman = 0xC5 + aux JpgDifferentialProgressiveDCTHuffman = 0xC6 + aux JpgDifferentialLosslessHuffman = 0xC7 + aux JpgExtendedSequentialArithmetic = 0xC9 + aux JpgProgressiveDCTArithmetic = 0xCA + aux JpgLosslessArithmetic = 0xCB + aux JpgHuffmanTableMarker = 0xC4 + aux JpgDifferentialSequentialDCTArithmetic = 0xCD + aux JpgDifferentialProgressiveDCTArithmetic = 0xCE + aux JpgDifferentialLosslessArithmetic = 0xCF + aux JpgEndOfImage = 0xD9 + aux JpgQuantizationTable = 0xDB + aux JpgStartOfScan = 0xDA + aux JpgRestartInterval = 0xDD + aux (JpgRestartIntervalEnd v) = v + aux (JpgAppSegment a) = a + aux (JpgExtensionSegment a) = a + +data JpgImageKind = BaseLineDCT | ProgressiveDCT + +instance Binary JpgFrameKind where + put v = putWord8 0xFF >> put (secondStartOfFrameByteOfKind v) + get = do + -- no lookahead :( + {-word <- getWord8-} + word2 <- getWord8 + 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 + 0xD9 -> JpgEndOfImage + 0xDA -> JpgStartOfScan + 0xDB -> JpgQuantizationTable + 0xDD -> JpgRestartInterval + a | a >= 0xF0 -> JpgExtensionSegment a + | a >= 0xE0 -> JpgAppSegment a + | a >= 0xD0 && a <= 0xD7 -> JpgRestartIntervalEnd a + | otherwise -> error ("Invalid frame marker (" ++ show a ++ ")") + +put4BitsOfEach :: Word8 -> Word8 -> Put +put4BitsOfEach a b = put $ (a `unsafeShiftL` 4) .|. b + +get4BitOfEach :: Get (Word8, Word8) +get4BitOfEach = do + val <- get + return ((val `unsafeShiftR` 4) .&. 0xF, val .&. 0xF) + +newtype RestartInterval = RestartInterval Word16 + +instance Binary 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 Binary 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 Binary JpgFrameHeader where + get = do + beginOffset <- fromIntegral <$> bytesRead + frmHLength <- getWord16be + samplePrec <- getWord8 + h <- getWord16be + w <- getWord16be + compCount <- getWord8 + components <- replicateM (fromIntegral compCount) get + endOffset <- fromIntegral <$> bytesRead + when (beginOffset - endOffset < fromIntegral frmHLength) + (skip $ fromIntegral frmHLength - (endOffset - beginOffset)) + 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 Binary 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 Binary 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 + putWord16be $ scanLength v + putWord8 $ scanComponentCount v + mapM_ put $ scans v + putWord8 . fst $ spectralSelection v + putWord8 . snd $ spectralSelection v + put4BitsOfEach (successiveApproxHigh v) $ successiveApproxLow v + +{-# INLINE createEmptyMutableMacroBlock #-} +-- | Create a new macroblock with the good array size +createEmptyMutableMacroBlock :: (Storable a, Num a) => ST s (MutableMacroBlock s a) +createEmptyMutableMacroBlock = M.replicate 64 0 + +printMacroBlock :: (Storable a, PrintfArg a) + => MutableMacroBlock s a -> ST s String +printMacroBlock block = pLn 0 + where pLn 64 = return "===============================\n" + pLn i = do + v <- block `M.unsafeRead` i + vn <- pLn (i+1) + return $ printf (if i `mod` 8 == 0 then "\n%5d " else "%5d ") v ++ vn + +printPureMacroBlock :: (Storable a, PrintfArg a) => MacroBlock a -> String +printPureMacroBlock block = pLn 0 + where pLn 64 = "===============================\n" + pLn i = str ++ pLn (i + 1) + where str | i `mod` 8 == 0 = printf "\n%5d " v + | otherwise = printf "%5d" v + v = block VS.! i + + +{-# INLINE dctBlockSize #-} +dctBlockSize :: Num a => a +dctBlockSize = 8
+ src/Codec/Picture/Png.hs view
@@ -0,0 +1,501 @@+{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TypeSynonymInstances #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE ViewPatterns #-} +-- | 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( .. ) + + , decodePng + , writePng + , encodePalettedPng + , encodeDynamicPng + , writeDynamicPng + + ) where + +import Control.Applicative( (<$>) ) +import Control.Monad( forM_, foldM_, when ) +import Control.Monad.ST( ST, runST ) +import Data.Binary( Binary( get) ) + +import qualified Data.Vector.Storable as V +import qualified Data.Vector.Storable.Mutable as M +import Data.Bits( (.&.), (.|.), unsafeShiftL, unsafeShiftR ) +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.Unsafe as BU +import qualified Data.ByteString.Lazy as Lb +import Foreign.Storable ( Storable ) + +import Codec.Picture.Types +import Codec.Picture.Png.Type +import Codec.Picture.Png.Export +import Codec.Picture.InternalHelper + +-- | 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 +{-# INLINE unparsePngFilter #-} +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" + +getBounds :: (Monad m, Storable a) => M.STVector s a -> m (Int, Int) +{-# INLINE getBounds #-} +getBounds v = return (0, M.length v - 1) + +-- | 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 + -> B.ByteString -> Int + -> ST s Int +pngFiltering _ _ (imgWidth, imgHeight) _str initialIdx + | imgWidth <= 0 || imgHeight <= 0 = return initialIdx +pngFiltering unpacker beginZeroes (imgWidth, imgHeight) str initialIdx = do + thisLine <- M.replicate (beginZeroes + imgWidth) 0 + otherLine <- M.replicate (beginZeroes + imgWidth) 0 + let folder _ _ lineIndex !idx | lineIndex >= imgHeight = return idx + folder previousLine currentLine lineIndex !idx = do + let byte = str `BU.unsafeIndex` idx + let lineFilter = case unparsePngFilter byte of + Right FilterNone -> filterNone + Right FilterSub -> filterSub + Right FilterAverage -> filterAverage + Right FilterUp -> filterUp + Right FilterPaeth -> filterPaeth + _ -> filterNone + idx' <- lineFilter previousLine currentLine $ idx + 1 + unpacker lineIndex (stride, currentLine) + folder currentLine previousLine (lineIndex + 1) idx' + + folder thisLine otherLine (0 :: Int) initialIdx + + 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 -> Int -> ST s Int + filterNone !_previousLine !thisLine = inner beginZeroes + where inner idx !readIdx + | idx > lastIdx = return readIdx + | otherwise = do let byte = str `BU.unsafeIndex` readIdx + (thisLine `M.unsafeWrite` idx) byte + inner (idx + 1) $ readIdx + 1 + + filterSub !_previousLine !thisLine = inner beginZeroes + where inner idx !readIdx + | idx > lastIdx = return readIdx + | otherwise = do let byte = str `BU.unsafeIndex` readIdx + val <- thisLine `M.unsafeRead` (idx - stride) + (thisLine `M.unsafeWrite` idx) $ byte + val + inner (idx + 1) $ readIdx + 1 + + filterUp !previousLine !thisLine = inner beginZeroes + where inner idx !readIdx + | idx > lastIdx = return readIdx + | otherwise = do let byte = str `BU.unsafeIndex` readIdx + val <- previousLine `M.unsafeRead` idx + (thisLine `M.unsafeWrite` idx) $ val + byte + inner (idx + 1) $ readIdx + 1 + + filterAverage !previousLine !thisLine = inner beginZeroes + where inner idx !readIdx + | idx > lastIdx = return readIdx + | otherwise = do let byte = str `BU.unsafeIndex` readIdx + valA <- thisLine `M.unsafeRead` (idx - stride) + valB <- previousLine `M.unsafeRead` idx + let a' = fromIntegral valA + b' = fromIntegral valB + average = fromIntegral ((a' + b') `div` (2 :: Word16)) + writeVal = byte + average + (thisLine `M.unsafeWrite` idx) $ writeVal + inner (idx + 1) $ readIdx + 1 + + filterPaeth !previousLine !thisLine = inner beginZeroes + where inner idx !readIdx + | idx > lastIdx = return readIdx + | otherwise = do let byte = str `BU.unsafeIndex` readIdx + valA <- thisLine `M.unsafeRead` (idx - stride) + valC <- previousLine `M.unsafeRead` (idx - stride) + valB <- previousLine `M.unsafeRead` idx + (thisLine `M.unsafeWrite` idx) $ byte + paeth valA valB valC + inner (idx + 1) $ readIdx + 1 + + 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' + +-- | Directly stolen from the definition in the standard (on W3C page), +-- pixel predictor. + +type PngLine s = M.STVector s 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 `M.unsafeRead` (srcPixelIndex + sample) + let writeIdx = destSampleIndex + sample + (arr `M.unsafeWrite` 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 `M.unsafeRead` (pixelIndex + beginIdx) + let writeIdx n = lineIndex + (pixelIndex * 8 + n) * strideWidth + beginLeft + forM_ [0 .. 7] $ \bit -> (arr `M.unsafeWrite` writeIdx (7 - bit)) ((val `unsafeShiftR` bit) .&. 1) + + when (lineRest /= 0) + (do val <- line `M.unsafeRead` endLine + let writeIdx n = lineIndex + (pixelToRead * 8 + n) * strideWidth + beginLeft + forM_ [0 .. lineRest - 1] $ \bit -> + (arr `M.unsafeWrite` writeIdx bit) ((val `unsafeShiftR` (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 `M.unsafeRead` (pixelIndex + beginIdx) + let writeIdx n = lineIndex + (pixelIndex * 4 + n) * strideWidth + beginLeft + (arr `M.unsafeWrite` writeIdx 0) $ (val `unsafeShiftR` 6) .&. 0x3 + (arr `M.unsafeWrite` writeIdx 1) $ (val `unsafeShiftR` 4) .&. 0x3 + (arr `M.unsafeWrite` writeIdx 2) $ (val `unsafeShiftR` 2) .&. 0x3 + (arr `M.unsafeWrite` writeIdx 3) $ val .&. 0x3 + + when (lineRest /= 0) + (do val <- line `M.unsafeRead` endLine + let writeIdx n = lineIndex + (pixelToRead * 4 + n) * strideWidth + beginLeft + forM_ [0 .. lineRest - 1] $ \bit -> + (arr `M.unsafeWrite` writeIdx bit) ((val `unsafeShiftR` (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 `M.unsafeRead` (pixelIndex + beginIdx) + let writeIdx n = lineIndex + (pixelIndex * 2 + n) * strideWidth + beginLeft + (arr `M.unsafeWrite` writeIdx 0) $ (val `unsafeShiftR` 4) .&. 0xF + (arr `M.unsafeWrite` writeIdx 1) $ val .&. 0xF + + when (lineRest /= 0) + (do val <- line `M.unsafeRead` endLine + let writeIdx = lineIndex + (pixelToRead * 2) * strideWidth + beginLeft + (arr `M.unsafeWrite` writeIdx) $ (val `unsafeShiftR` 4) .&. 0xF) + +shortUnpacker :: Int -> MutableImage s Word16 -> 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 `M.unsafeRead` (srcPixelIndex + sample * 2 + 0) + lowBits <- line `M.unsafeRead` (srcPixelIndex + sample * 2 + 1) + let fullValue = fromIntegral lowBits .|. (fromIntegral highBits `unsafeShiftL` 8) + writeIdx = destSampleIndex + sample + (arr `M.unsafeWrite` writeIdx) $ fullValue + +-- | Transform a scanline to a bunch of bytes. Bytes are then packed +-- into pixels at a further step. +scanlineUnpacker8 :: Int -> Int -> MutableImage s Word8 -> StrideInfo -> BeginOffset + -> LineUnpacker s +scanlineUnpacker8 1 = bitUnpacker +scanlineUnpacker8 2 = twoBitsUnpacker +scanlineUnpacker8 4 = halfByteUnpacker +scanlineUnpacker8 8 = byteUnpacker +scanlineUnpacker8 _ = 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) + -> B.ByteString + -> ST s () +scanLineInterleaving depth sampleCount (imgWidth, imgHeight) unpacker str = + pngFiltering (unpacker (1,1) (0, 0)) strideInfo (byteWidth, imgHeight) str 0 >> return () + 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) + -> B.ByteString -> ST s () +adam7Unpack depth sampleCount (imgWidth, imgHeight) unpacker str = + foldM_ (\i f -> f i) 0 subImages >> return () + where Adam7MatrixInfo { adam7StartingRow = startRows + , adam7RowIncrement = rowIncrement + , adam7StartingCol = startCols + , adam7ColIncrement = colIncrement } = adam7MatrixInfo + + subImages = + [pngFiltering (unpacker (incrW, incrH) (beginW, beginH)) strideInfo (byteWidth, passHeight) str + | (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 + ] + 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 -> B.ByteString -> ST s (Either (V.Vector Word8) (V.Vector Word16)) +deinterlacer (PngIHdr { width = w, height = h, colourType = imgKind + , interlaceMethod = method, bitDepth = depth }) str = do + let compCount = sampleCountOfImageType imgKind + arraySize = fromIntegral $ w * h * compCount + deinterlaceFunction = case method of + PngNoInterlace -> scanLineInterleaving + PngInterlaceAdam7 -> adam7Unpack + iBitDepth = fromIntegral depth + if iBitDepth <= 8 + then do + imgArray <- M.new arraySize + let mutableImage = MutableImage (fromIntegral w) (fromIntegral h) imgArray + deinterlaceFunction iBitDepth + (fromIntegral compCount) + (fromIntegral w, fromIntegral h) + (scanlineUnpacker8 iBitDepth (fromIntegral compCount) + mutableImage) + str + Left <$> V.unsafeFreeze imgArray + + else do + imgArray <- M.new arraySize + let mutableImage = MutableImage (fromIntegral w) (fromIntegral h) imgArray + deinterlaceFunction iBitDepth + (fromIntegral compCount) + (fromIntegral w, fromIntegral h) + (shortUnpacker (fromIntegral compCount) mutableImage) + str + Right <$> V.unsafeFreeze imgArray + +generateGreyscalePalette :: Word8 -> PngPalette +generateGreyscalePalette times = Image possibilities 0 vec + where possibilities = 2 ^ times - 1 + vec = V.fromListN ((fromIntegral possibilities + 1) * 3) $ concat pixels + pixels = [[i, i, i] | n <- [0 .. possibilities] + , let i = fromIntegral $ 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 + +{-# INLINE bounds #-} +bounds :: Storable a => V.Vector a -> (Int, Int) +bounds v = (0, V.length v - 1) + +applyPalette :: PngPalette -> V.Vector Word8 -> V.Vector Word8 +applyPalette pal img = V.fromListN ((initSize + 1) * 3) pixels + where (_, initSize) = bounds img + pixels = concat [[r, g, b] | ipx <- V.toList img + , let PixelRGB8 r g b = pixelAt pal (fromIntegral ipx) 0] + +applyPaletteWithTransparency :: PngPalette -> Word8 -> V.Vector Word8 -> V.Vector Word8 +applyPaletteWithTransparency pal transp img = V.fromListN ((initSize + 1) * 4) pixels + where (_, initSize) = bounds img + pixels = concat [ if ipx == transp then [0, 0, 0, 0] else [r, g, b, 255] + | ipx <- V.toList img + , let PixelRGB8 r g b = pixelAt pal (fromIntegral ipx) 0] + +-- | 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 +-- +-- * PixelY16 +-- +-- * PixelYA8 +-- +-- * PixelYA16 +-- +-- * PixelRGB8 +-- +-- * PixelRGB16 +-- +-- * PixelRGBA8 +-- +-- * PixelRGBA16 +-- +decodePng :: B.ByteString -> Either String DynamicImage +decodePng byte = do + rawImg <- runGetStrict get byte + let ihdr@(PngIHdr { width = w, height = h }) = header rawImg + compressedImageData = + Lb.concat [chunkData chunk | chunk <- chunks rawImg + , chunkType chunk == iDATSignature] + zlibHeaderSize = 1 {- compression method/flags code -} + + 1 {- Additional flags/check bits -} + + 4 {-CRC-} + + toImage const1 _const2 (Left a) = + Right . const1 $ Image (fromIntegral w) (fromIntegral h) a + toImage _const1 const2 (Right a) = + Right . const2 $ Image (fromIntegral w) (fromIntegral h) a + + palette8 palette [(Lb.uncons -> Just (c,_))] (Left img) = + Right . ImageRGBA8 + . Image (fromIntegral w) (fromIntegral h) + $ applyPaletteWithTransparency palette c img + + palette8 palette _ (Left img) = + Right . ImageRGB8 + . Image (fromIntegral w) (fromIntegral h) + $ applyPalette palette img + + palette8 _ _ (Right _) = Left "Invalid bit depth for paleted image" + + transparencyColor = + [ chunkData chunk | chunk <- chunks rawImg + , chunkType chunk == tRNSSignature ] + + unparse _ t PngGreyscale bytes + | bitDepth ihdr == 1 = unparse (Just paletteRGBA1) t PngIndexedColor bytes + | bitDepth ihdr == 2 = unparse (Just paletteRGBA2) t PngIndexedColor bytes + | bitDepth ihdr == 4 = unparse (Just paletteRGBA4) t PngIndexedColor bytes + | otherwise = toImage ImageY8 ImageY16 $ runST stArray + where stArray = deinterlacer ihdr bytes + + unparse Nothing _ PngIndexedColor _ = Left "no valid palette found" + unparse _ _ PngTrueColour bytes = + toImage ImageRGB8 ImageRGB16 $ runST stArray + where stArray = deinterlacer ihdr bytes + unparse _ _ PngGreyscaleWithAlpha bytes = + toImage ImageYA8 ImageYA16 $ runST stArray + where stArray = deinterlacer ihdr bytes + unparse _ _ PngTrueColourWithAlpha bytes = + toImage ImageRGBA8 ImageRGBA16 $ runST stArray + where stArray = deinterlacer ihdr bytes + unparse (Just plte) transparency PngIndexedColor bytes = + palette8 plte transparency $ runST stArray + where stArray = deinterlacer ihdr bytes + + if Lb.length compressedImageData <= zlibHeaderSize + then Left "Invalid data size" + else let imgData = Z.decompress 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 transparencyColor (colourType ihdr) parseableData +
+ src/Codec/Picture/Png/Export.hs view
@@ -0,0 +1,192 @@+{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TypeSynonymInstances #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE ScopedTypeVariables #-} +-- | 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 + , encodePalettedPng + ) where + +import Control.Monad( forM_ ) +import Control.Monad.ST( ST, runST ) +import Data.Bits( unsafeShiftR, (.&.) ) +import Data.Binary( encode ) +import Data.Word(Word8, Word16) +import qualified Codec.Compression.Zlib as Z +import qualified Data.ByteString as B +import qualified Data.ByteString.Lazy as Lb + +import qualified Data.Vector.Storable as VS +import qualified Data.Vector.Storable.Mutable as M + +import Codec.Picture.Types +import Codec.Picture.Png.Type +import Codec.Picture.VectorByteConversion( blitVector, toByteString ) + +-- | Encode an image into a png if possible. +class PngSavable a where + -- | Transform an image into a png encoded bytestring, ready + -- to be written as a file. + encodePng :: Image a -> Lb.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 = Lb.writeFile path $ encodePng img + +endChunk :: PngRawChunk +endChunk = PngRawChunk { chunkLength = 0 + , chunkType = iENDSignature + , chunkCRC = pngComputeCrc [iENDSignature] + , chunkData = Lb.empty + } + + +prepareIDatChunk :: Lb.ByteString -> PngRawChunk +prepareIDatChunk imgData = PngRawChunk + { chunkLength = fromIntegral $ Lb.length imgData + , chunkType = iDATSignature + , chunkCRC = pngComputeCrc [iDATSignature, imgData] + , chunkData = imgData + } + +genericEncode16BitsPng :: forall px. (Pixel px, PixelBaseComponent px ~ Word16) + => PngImageType -> Image px -> Lb.ByteString +genericEncode16BitsPng imgKind + image@(Image { imageWidth = w, imageHeight = h, imageData = arr }) = + encode PngRawImage { header = hdr, chunks = [prepareIDatChunk imgEncodedData, endChunk]} + where hdr = preparePngHeader image imgKind 16 + zero = B.singleton 0 + compCount = componentCount (undefined :: px) + + lineSize = compCount * w + blitToByteString vec = blitVector vec 0 (lineSize * 2) + encodeLine line = blitToByteString $ runST $ do + finalVec <- M.new $ lineSize * 2 :: ST s (M.STVector s Word8) + let baseIndex = line * lineSize + forM_ [0 .. lineSize - 1] $ \ix -> do + let v = arr `VS.unsafeIndex` (baseIndex + ix) + high = fromIntegral $ (v `unsafeShiftR` 8) .&. 0xFF + low = fromIntegral $ v .&. 0xFF + + (finalVec `M.unsafeWrite` (ix * 2 + 0)) high + (finalVec `M.unsafeWrite` (ix * 2 + 1)) low + + VS.unsafeFreeze finalVec + + imgEncodedData = Z.compress . Lb.fromChunks + $ concat [[zero, encodeLine line] | line <- [0 .. h - 1]] + +preparePalette :: Palette -> PngRawChunk +preparePalette pal = PngRawChunk + { chunkLength = fromIntegral $ imageWidth pal * 3 + , chunkType = pLTESignature + , chunkCRC = pngComputeCrc [pLTESignature, binaryData] + , chunkData = binaryData + } + where binaryData = Lb.fromChunks [toByteString $ imageData pal] + +genericEncodePng :: forall px. (Pixel px, PixelBaseComponent px ~ Word8) + => Maybe Palette -> PngImageType -> Image px + -> Lb.ByteString +genericEncodePng palette imgKind + image@(Image { imageWidth = w, imageHeight = h, imageData = arr }) = + encode PngRawImage { header = hdr + , chunks = prependPalette palette [prepareIDatChunk imgEncodedData, endChunk]} + where hdr = preparePngHeader image imgKind 8 + zero = B.singleton 0 + compCount = componentCount (undefined :: px) + + prependPalette Nothing l = l + prependPalette (Just p) l = preparePalette p : l + + lineSize = compCount * w + encodeLine line = blitVector arr (line * lineSize) lineSize + imgEncodedData = Z.compress . Lb.fromChunks + $ concat [[zero, encodeLine line] | line <- [0 .. h - 1]] + +instance PngSavable PixelRGBA8 where + encodePng = genericEncodePng Nothing PngTrueColourWithAlpha + +instance PngSavable PixelRGB8 where + encodePng = genericEncodePng Nothing PngTrueColour + +instance PngSavable Pixel8 where + encodePng = genericEncodePng Nothing PngGreyscale + +instance PngSavable PixelYA8 where + encodePng = genericEncodePng Nothing PngGreyscaleWithAlpha + +instance PngSavable PixelYA16 where + encodePng = genericEncode16BitsPng PngGreyscaleWithAlpha + +instance PngSavable Pixel16 where + encodePng = genericEncode16BitsPng PngGreyscale + +instance PngSavable PixelRGB16 where + encodePng = genericEncode16BitsPng PngTrueColour + +instance PngSavable PixelRGBA16 where + encodePng = genericEncode16BitsPng PngTrueColourWithAlpha + +-- | 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 -> Lb.writeFile path b >> return (Right True) + +-- | Encode a paletted image as a color indexed 8-bit PNG. +-- the palette must have between 1 and 256 values in it. +encodePalettedPng :: Palette -> Image Pixel8 -> Either String Lb.ByteString +encodePalettedPng pal img + | w <= 0 || w > 256 || h /= 1 = Left "Invalid palette" + | VS.any isTooBig $ imageData img = + Left "Image contains indexes absent from the palette" + | otherwise = Right $ genericEncodePng (Just pal) PngIndexedColor img + where w = imageWidth pal + h = imageHeight pal + isTooBig v = fromIntegral v >= w + +-- | Encode a dynamic image in bmp if possible, supported pixel type are : +-- +-- - Y8 +-- +-- - Y16 +-- +-- - YA8 +-- +-- - YA16 +-- +-- - RGB8 +-- +-- - RGB16 +-- +-- - RGBA8 +-- +-- - RGBA16 +-- +encodeDynamicPng :: DynamicImage -> Either String Lb.ByteString +encodeDynamicPng (ImageRGB8 img) = Right $ encodePng img +encodeDynamicPng (ImageRGBA8 img) = Right $ encodePng img +encodeDynamicPng (ImageY8 img) = Right $ encodePng img +encodeDynamicPng (ImageY16 img) = Right $ encodePng img +encodeDynamicPng (ImageYA8 img) = Right $ encodePng img +encodeDynamicPng (ImageYA16 img) = Right $ encodePng img +encodeDynamicPng (ImageRGB16 img) = Right $ encodePng img +encodeDynamicPng (ImageRGBA16 img) = Right $ encodePng img +encodeDynamicPng _ = Left "Unsupported image format for PNG export"
+ src/Codec/Picture/Png/Type.hs view
@@ -0,0 +1,329 @@+-- | 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 + , tRNSSignature + -- * Low level types + , ChunkSignature + , PngRawImage( .. ) + , PngChunk( .. ) + , PngRawChunk( .. ) + , PngLowLevel( .. ) + ) where + +import Control.Applicative( (<$>) ) +import Control.Monad( when, replicateM ) +import Data.Bits( xor, (.&.), unsafeShiftR ) +import Data.Binary( Binary(..), Get, get ) +import Data.Binary.Get( getWord8 + , getWord32be + , getLazyByteString + ) +import Data.Binary.Put( runPut + , putWord8 + , putWord32be + , putLazyByteString + ) +import Data.Vector.Unboxed( Vector, fromListN, (!) ) +import qualified Data.Vector.Storable as V +import Data.List( foldl' ) +import Data.Word( Word32, Word8 ) +import qualified Data.ByteString.Lazy as L +import qualified Data.ByteString.Lazy.Char8 as LS + +import Codec.Picture.Types +import Codec.Picture.InternalHelper + +-------------------------------------------------- +---- Types +-------------------------------------------------- + +-- | Value used to identify a png chunk, must be 4 bytes long. +type ChunkSignature = L.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 = Image 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 = Image pixelCount 1 . V.fromListN (3 * pixelCount) <$> pixels + where pixelUnpacker = replicateM (fromIntegral pixelCount * 3) get + pixelCount = fromIntegral $ chunkLength plte `div` 3 + pixels = runGet pixelUnpacker (chunkData plte) + +-- | Data structure during real png loading/parsing +data PngRawChunk = PngRawChunk + { chunkLength :: Word32 + , chunkType :: ChunkSignature + , chunkCRC :: Word32 + , chunkData :: L.ByteString + } + +-- | PNG chunk representing some extra information found in the parsed file. +data PngChunk = PngChunk + { pngChunkData :: L.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 Binary 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 Binary PngRawImage where + put img = do + putLazyByteString pngSignature + put $ header img + mapM_ put $ chunks img + + get = parseRawPngImage + +instance Binary PngRawChunk where + put chunk = do + putWord32be $ chunkLength chunk + putLazyByteString $ chunkType chunk + when (chunkLength chunk /= 0) + (putLazyByteString $ chunkData chunk) + putWord32be $ chunkCRC chunk + + get = do + size <- getWord32be + chunkSig <- getLazyByteString (fromIntegral $ L.length iHDRSignature) + imgData <- if size == 0 + then return L.empty + else getLazyByteString (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 Binary PngIHdr where + put hdr = do + putWord32be 13 + let inner = runPut $ do + putLazyByteString 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] + putLazyByteString inner + putWord32be crc + + get = do + _size <- getWord32be + ihdrSig <- getLazyByteString (L.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 Binary 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 <- getLazyByteString (L.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 = L.pack [137, 80, 78, 71, 13, 10, 26, 10] + +-- | Helper function to help pack signatures. +signature :: String -> ChunkSignature +signature = LS.pack + +-- | Signature for the header chunk of png (must be the first) +iHDRSignature :: ChunkSignature +iHDRSignature = signature "IHDR" + +-- | Signature for a palette chunk in the pgn file. Must +-- occure before iDAT. +pLTESignature :: ChunkSignature +pLTESignature = signature "PLTE" + +-- | Signature for a data chuck (with image parts in it) +iDATSignature :: ChunkSignature +iDATSignature = signature "IDAT" + +-- | Signature for the last chunk of a png image, telling +-- the end. +iENDSignature :: ChunkSignature +iENDSignature = signature "IEND" + +tRNSSignature :: ChunkSignature +tRNSSignature = signature "tRNS" + +instance Binary 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 :: Vector Word32 +pngCrcTable = fromListN 256 [ 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 `unsafeShiftR` 1) + | otherwise = c `unsafeShiftR` 1 + magicConstant = 0xedb88320 :: Word32 + +-- | Compute the CRC of a raw buffer, as described in annex D of the PNG +-- specification. +pngComputeCrc :: [L.ByteString] -> Word32 +pngComputeCrc = (0xFFFFFFFF `xor`) . L.foldl' updateCrc 0xFFFFFFFF . L.concat + where updateCrc crc val = + let u32Val = fromIntegral val + lutVal = pngCrcTable ! (fromIntegral ((crc `xor` u32Val) .&. 0xFF)) + in lutVal `xor` (crc `unsafeShiftR` 8) +
+ src/Codec/Picture/Saving.hs view
@@ -0,0 +1,187 @@+{-# LANGUAGE TypeFamilies #-} +-- | Helper functions to save dynamic images to other file format +-- with automatic color space/sample format conversion done automatically. +module Codec.Picture.Saving( imageToJpg + , imageToPng + , imageToGif + , imageToBitmap + , imageToTiff + , imageToRadiance + ) where + +import Data.Bits( unsafeShiftR ) +import Data.Word( Word8, Word16 ) +import qualified Data.ByteString.Lazy as L +import Codec.Picture.Bitmap +import Codec.Picture.Jpg +import Codec.Picture.Png +import Codec.Picture.Gif +import Codec.Picture.ColorQuant +import Codec.Picture.HDR +import Codec.Picture.Types +import Codec.Picture.Tiff + +import qualified Data.Vector.Storable as V + +componentToLDR :: Float -> Word8 +componentToLDR = truncate . (255 *) . min 1.0 . max 0.0 + +toStandardDef :: Image PixelRGBF -> Image PixelRGB8 +toStandardDef img = pixelMap pixelConverter img + where pixelConverter (PixelRGBF rf gf bf) = PixelRGB8 r g b + where r = componentToLDR rf + g = componentToLDR gf + b = componentToLDR bf + +greyScaleToStandardDef :: Image PixelF -> Image Pixel8 +greyScaleToStandardDef = pixelMap componentToLDR + +from16to8 :: ( PixelBaseComponent source ~ Word16 + , PixelBaseComponent dest ~ Word8 ) + => Image source -> Image dest +from16to8 Image { imageWidth = w, imageHeight = h + , imageData = arr } = Image w h transformed + where transformed = V.map toWord8 arr + toWord8 v = fromIntegral (v `unsafeShiftR` 8) + +from16toFloat :: ( PixelBaseComponent source ~ Word16 + , PixelBaseComponent dest ~ Float ) + => Image source -> Image dest +from16toFloat Image { imageWidth = w, imageHeight = h + , imageData = arr } = Image w h transformed + where transformed = V.map toWord8 arr + toWord8 v = fromIntegral v / 65536.0 + +-- | This function will try to do anything to encode an image +-- as RADIANCE, make all color conversion and such. Equivalent +-- of 'decodeImage' for radiance encoding +imageToRadiance :: DynamicImage -> L.ByteString +imageToRadiance (ImageCMYK8 img) = + imageToRadiance . ImageRGB8 $ convertImage img +imageToRadiance (ImageCMYK16 img) = + imageToRadiance . ImageRGB16 $ convertImage img +imageToRadiance (ImageYCbCr8 img) = + imageToRadiance . ImageRGB8 $ convertImage img +imageToRadiance (ImageRGB8 img) = + imageToRadiance . ImageRGBF $ promoteImage img +imageToRadiance (ImageRGBF img) = encodeHDR img +imageToRadiance (ImageRGBA8 img) = + imageToRadiance . ImageRGBF . promoteImage $ dropAlphaLayer img +imageToRadiance (ImageY8 img) = + imageToRadiance . ImageRGB8 $ promoteImage img +imageToRadiance (ImageYF img) = + imageToRadiance . ImageRGBF $ promoteImage img +imageToRadiance (ImageYA8 img) = + imageToRadiance . ImageRGB8 . promoteImage $ dropAlphaLayer img +imageToRadiance (ImageY16 img) = + imageToRadiance . ImageRGBF $ pixelMap toRgbf img + where toRgbf v = PixelRGBF val val val + where val = fromIntegral v / 65536.0 + +imageToRadiance (ImageYA16 img) = + imageToRadiance . ImageRGBF $ pixelMap toRgbf img + where toRgbf (PixelYA16 v _) = PixelRGBF val val val + where val = fromIntegral v / 65536.0 +imageToRadiance (ImageRGB16 img) = + imageToRadiance . ImageRGBF $ from16toFloat img +imageToRadiance (ImageRGBA16 img) = + imageToRadiance . ImageRGBF $ pixelMap toRgbf img + where toRgbf (PixelRGBA16 r g b _) = PixelRGBF (f r) (f g) (f b) + where f v = fromIntegral v / 65536.0 + +-- | This function will try to do anything to encode an image +-- as JPEG, make all color conversion and such. Equivalent +-- of 'decodeImage' for jpeg encoding +imageToJpg :: Int -> DynamicImage -> L.ByteString +imageToJpg quality dynImage = + let encodeAtQuality = encodeJpegAtQuality (fromIntegral quality) + in case dynImage of + ImageYCbCr8 img -> encodeAtQuality img + ImageCMYK8 img -> imageToJpg quality . ImageRGB8 $ convertImage img + ImageCMYK16 img -> imageToJpg quality . ImageRGB16 $ convertImage img + ImageRGB8 img -> encodeAtQuality (convertImage img) + ImageRGBF img -> imageToJpg quality . ImageRGB8 $ toStandardDef img + ImageRGBA8 img -> encodeAtQuality (convertImage $ dropAlphaLayer img) + ImageYF img -> imageToJpg quality . ImageY8 $ greyScaleToStandardDef img + ImageY8 img -> encodeAtQuality . convertImage + $ (promoteImage img :: Image PixelRGB8) + ImageYA8 img -> encodeAtQuality $ + convertImage (promoteImage $ dropAlphaLayer img :: Image PixelRGB8) + ImageY16 img -> imageToJpg quality . ImageY8 $ from16to8 img + ImageYA16 img -> imageToJpg quality . ImageYA8 $ from16to8 img + ImageRGB16 img -> imageToJpg quality . ImageRGB8 $ from16to8 img + ImageRGBA16 img -> imageToJpg quality . ImageRGBA8 $ from16to8 img + +-- | This function will try to do anything to encode an image +-- as PNG, make all color conversion and such. Equivalent +-- of 'decodeImage' for PNG encoding +imageToPng :: DynamicImage -> L.ByteString +imageToPng (ImageYCbCr8 img) = encodePng (convertImage img :: Image PixelRGB8) +imageToPng (ImageCMYK8 img) = encodePng (convertImage img :: Image PixelRGB8) +imageToPng (ImageCMYK16 img) = encodePng (convertImage img :: Image PixelRGB16) +imageToPng (ImageRGB8 img) = encodePng img +imageToPng (ImageRGBF img) = encodePng $ toStandardDef img +imageToPng (ImageRGBA8 img) = encodePng img +imageToPng (ImageY8 img) = encodePng img +imageToPng (ImageYF img) = encodePng $ greyScaleToStandardDef img +imageToPng (ImageYA8 img) = encodePng img +imageToPng (ImageY16 img) = encodePng img +imageToPng (ImageYA16 img) = encodePng img +imageToPng (ImageRGB16 img) = encodePng img +imageToPng (ImageRGBA16 img) = encodePng img + +-- | This function will try to do anything to encode an image +-- as a Tiff, make all color conversion and such. Equivalent +-- of 'decodeImage' for Tiff encoding +imageToTiff :: DynamicImage -> L.ByteString +imageToTiff (ImageYCbCr8 img) = encodeTiff img +imageToTiff (ImageCMYK8 img) = encodeTiff img +imageToTiff (ImageCMYK16 img) = encodeTiff img +imageToTiff (ImageRGB8 img) = encodeTiff img +imageToTiff (ImageRGBF img) = encodeTiff $ toStandardDef img +imageToTiff (ImageRGBA8 img) = encodeTiff img +imageToTiff (ImageY8 img) = encodeTiff img +imageToTiff (ImageYF img) = encodeTiff $ greyScaleToStandardDef img +imageToTiff (ImageYA8 img) = encodeTiff $ dropAlphaLayer img +imageToTiff (ImageY16 img) = encodeTiff img +imageToTiff (ImageYA16 img) = encodeTiff $ dropAlphaLayer img +imageToTiff (ImageRGB16 img) = encodeTiff img +imageToTiff (ImageRGBA16 img) = encodeTiff img + +-- | This function will try to do anything to encode an image +-- as bitmap, make all color conversion and such. Equivalent +-- of 'decodeImage' for Bitmap encoding +imageToBitmap :: DynamicImage -> L.ByteString +imageToBitmap (ImageYCbCr8 img) = encodeBitmap (convertImage img :: Image PixelRGB8) +imageToBitmap (ImageCMYK8 img) = encodeBitmap (convertImage img :: Image PixelRGB8) +imageToBitmap (ImageCMYK16 img) = imageToBitmap . ImageRGB16 $ convertImage img +imageToBitmap (ImageRGBF img) = encodeBitmap $ toStandardDef img +imageToBitmap (ImageRGB8 img) = encodeBitmap img +imageToBitmap (ImageRGBA8 img) = encodeBitmap img +imageToBitmap (ImageY8 img) = encodeBitmap img +imageToBitmap (ImageYF img) = encodeBitmap $ greyScaleToStandardDef img +imageToBitmap (ImageYA8 img) = encodeBitmap (promoteImage img :: Image PixelRGBA8) +imageToBitmap (ImageY16 img) = imageToBitmap . ImageY8 $ from16to8 img +imageToBitmap (ImageYA16 img) = imageToBitmap . ImageYA8 $ from16to8 img +imageToBitmap (ImageRGB16 img) = imageToBitmap . ImageRGB8 $ from16to8 img +imageToBitmap (ImageRGBA16 img) = imageToBitmap . ImageRGBA8 $ from16to8 img + + +-- | This function will try to do anything to encode an image +-- as a gif, make all color conversion and quantization. Equivalent +-- of 'decodeImage' for gif encoding +imageToGif :: DynamicImage -> Either String L.ByteString +imageToGif (ImageYCbCr8 img) = imageToGif . ImageRGB8 $ convertImage img +imageToGif (ImageCMYK8 img) = imageToGif . ImageRGB8 $ convertImage img +imageToGif (ImageCMYK16 img) = imageToGif . ImageRGB16 $ convertImage img +imageToGif (ImageRGBF img) = imageToGif . ImageRGB8 $ toStandardDef img +imageToGif (ImageRGB8 img) = encodeGifImageWithPalette indexed pal + where (indexed, pal) = palettize defaultPaletteOptions img +imageToGif (ImageRGBA8 img) = imageToGif . ImageRGB8 $ dropAlphaLayer img +imageToGif (ImageY8 img) = Right $ encodeGifImage img +imageToGif (ImageYF img) = imageToGif . ImageY8 $ greyScaleToStandardDef img +imageToGif (ImageYA8 img) = imageToGif . ImageY8 $ dropAlphaLayer img +imageToGif (ImageY16 img) = imageToGif . ImageY8 $ from16to8 img +imageToGif (ImageYA16 img) = imageToGif . ImageYA8 $ from16to8 img +imageToGif (ImageRGB16 img) = imageToGif . ImageRGB8 $ from16to8 img +imageToGif (ImageRGBA16 img) = imageToGif . ImageRGBA8 $ from16to8 img
+ src/Codec/Picture/Tiff.hs view
@@ -0,0 +1,1285 @@+{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE ScopedTypeVariables #-} +-- | Module implementing TIFF decoding. +-- +-- Supported compression schemes : +-- +-- * Uncompressed +-- +-- * PackBits +-- +-- * LZW +-- +-- Supported bit depth : +-- +-- * 2 bits +-- +-- * 4 bits +-- +-- * 8 bits +-- +-- * 16 bits +-- +module Codec.Picture.Tiff( decodeTiff, TiffSaveable, encodeTiff, writeTiff ) where + +import Control.Applicative( (<$>), (<*>), pure ) +import Control.Monad( when, replicateM, foldM_ ) +import Control.Monad.ST( ST, runST ) +import Control.Monad.Writer.Strict( execWriter, tell, Writer ) +import Data.Int( Int8 ) +import Data.Word( Word8, Word16 ) +import Data.Bits( (.&.), (.|.), unsafeShiftL, unsafeShiftR ) +import Data.Binary( Binary( .. ) ) +import Data.Binary.Get( Get + , getWord16le, getWord16be + , getWord32le, getWord32be + , bytesRead + , skip + , getByteString + ) +import Data.Binary.Put( Put, runPut + , putWord16le, putWord16be + , putWord32le, putWord32be + , putByteString + ) + +import Data.List( sortBy, mapAccumL ) +import qualified Data.Vector as V +import qualified Data.Vector.Storable as VS +import qualified Data.Vector.Storable.Mutable as M +import Data.Word( Word32 ) +import qualified Data.ByteString as B +import qualified Data.ByteString.Lazy as Lb +import qualified Data.ByteString.Unsafe as BU + +import Foreign.Storable( sizeOf ) + +import Codec.Picture.InternalHelper +import Codec.Picture.BitWriter +import Codec.Picture.Types +import Codec.Picture.Gif.LZW +import Codec.Picture.VectorByteConversion( toByteString ) + +data Endianness = EndianLittle + | EndianBig + deriving (Eq, Show) + +instance Binary Endianness where + put EndianLittle = putWord16le 0x4949 + put EndianBig = putWord16le 0x4D4D + + get = do + tag <- getWord16le + case tag of + 0x4949 -> return EndianLittle + 0x4D4D -> return EndianBig + _ -> fail "Invalid endian tag value" + +-- | Because having a polymorphic get with endianness is to nice +-- to pass on, introducing this helper type class, which is just +-- a superset of Binary, but formalising a parameter passing +-- into it. +class BinaryParam a b where + getP :: a -> Get b + putP :: a -> b -> Put + +data TiffHeader = TiffHeader + { hdrEndianness :: !Endianness + , hdrOffset :: {-# UNPACK #-} !Word32 + } + deriving (Eq, Show) + +instance BinaryParam Endianness Word16 where + putP EndianLittle = putWord16le + putP EndianBig = putWord16be + + getP EndianLittle = getWord16le + getP EndianBig = getWord16be + +instance BinaryParam Endianness Word32 where + putP EndianLittle = putWord32le + putP EndianBig = putWord32be + + getP EndianLittle = getWord32le + getP EndianBig = getWord32be + +instance Binary TiffHeader where + put hdr = do + let endian = hdrEndianness hdr + put endian + putP endian (42 :: Word16) + putP endian $ hdrOffset hdr + + get = do + endian <- get + magic <- getP endian + let magicValue = 42 :: Word16 + when (magic /= magicValue) + (fail "Invalid TIFF magic number") + TiffHeader endian <$> getP endian + +data TiffPlanarConfiguration = + PlanarConfigContig -- = 1 + | PlanarConfigSeparate -- = 2 + +planarConfgOfConstant :: Word32 -> Get TiffPlanarConfiguration +planarConfgOfConstant 0 = pure PlanarConfigContig +planarConfgOfConstant 1 = pure PlanarConfigContig +planarConfgOfConstant 2 = pure PlanarConfigSeparate +planarConfgOfConstant v = fail $ "Unknown planar constant (" ++ show v ++ ")" + +constantToPlaneConfiguration :: TiffPlanarConfiguration -> Word16 +constantToPlaneConfiguration PlanarConfigContig = 1 +constantToPlaneConfiguration PlanarConfigSeparate = 2 + +data TiffCompression = + CompressionNone -- 1 + | CompressionModifiedRLE -- 2 + | CompressionLZW -- 5 + | CompressionJPEG -- 6 + | CompressionPackBit -- 32273 + +data IfdType = TypeByte + | TypeAscii + | TypeShort + | TypeLong + | TypeRational + | TypeSByte + | TypeUndefined + | TypeSignedShort + | TypeSignedLong + | TypeSignedRational + | TypeFloat + | TypeDouble + +instance BinaryParam Endianness IfdType where + getP endianness = getP endianness >>= conv + where + conv :: Word16 -> Get IfdType + conv 1 = return TypeByte + conv 2 = return TypeAscii + conv 3 = return TypeShort + conv 4 = return TypeLong + conv 5 = return TypeRational + conv 6 = return TypeSByte + conv 7 = return TypeUndefined + conv 8 = return TypeSignedShort + conv 9 = return TypeSignedLong + conv 10 = return TypeSignedRational + conv 11 = return TypeFloat + conv 12 = return TypeDouble + conv _ = fail "Invalid TIF directory type" + + putP endianness = putP endianness . conv + where + conv :: IfdType -> Word16 + conv TypeByte = 1 + conv TypeAscii = 2 + conv TypeShort = 3 + conv TypeLong = 4 + conv TypeRational = 5 + conv TypeSByte = 6 + conv TypeUndefined = 7 + conv TypeSignedShort = 8 + conv TypeSignedLong = 9 + conv TypeSignedRational = 10 + conv TypeFloat = 11 + conv TypeDouble = 12 + +data TiffTag = TagPhotometricInterpretation + | TagCompression -- ^ Short type + | TagImageWidth -- ^ Short or long type + | TagImageLength -- ^ Short or long type + | TagXResolution -- ^ Rational type + | TagYResolution -- ^ Rational type + | TagResolutionUnit -- ^ Short type + | TagRowPerStrip -- ^ Short or long type + | TagStripByteCounts -- ^ Short or long + | TagStripOffsets -- ^ Short or long + | TagBitsPerSample -- ^ Short + | TagColorMap -- ^ Short + | TagTileWidth + | TagTileLength + | TagTileOffset + | TagTileByteCount + | TagSamplesPerPixel -- ^ Short + | TagArtist + | TagDocumentName + | TagSoftware + | TagPlanarConfiguration -- ^ Short + | TagOrientation + | TagSampleFormat -- ^ Short + | TagInkSet + | TagSubfileType + | TagFillOrder + | TagYCbCrCoeff + | TagYCbCrSubsampling + | TagYCbCrPositioning + | TagReferenceBlackWhite + | TagXPosition + | TagYPosition + | TagExtraSample + | TagImageDescription + + | TagJpegProc + | TagJPEGInterchangeFormat + | TagJPEGInterchangeFormatLength + | TagJPEGRestartInterval + | TagJPEGLosslessPredictors + | TagJPEGPointTransforms + | TagJPEGQTables + | TagJPEGDCTables + | TagJPEGACTables + + | TagUnknown Word16 + deriving (Eq, Show) + +tagOfWord16 :: Word16 -> TiffTag +tagOfWord16 = aux + where aux 255 = TagSubfileType + aux 256 = TagImageWidth + aux 257 = TagImageLength + aux 258 = TagBitsPerSample + aux 259 = TagCompression + aux 262 = TagPhotometricInterpretation + aux 266 = TagFillOrder + aux 269 = TagDocumentName + aux 270 = TagImageDescription + aux 273 = TagStripOffsets + aux 274 = TagOrientation + aux 277 = TagSamplesPerPixel + aux 278 = TagRowPerStrip + aux 279 = TagStripByteCounts + aux 282 = TagXResolution + aux 283 = TagYResolution + aux 284 = TagPlanarConfiguration + aux 286 = TagXPosition + aux 287 = TagYPosition + aux 296 = TagResolutionUnit + aux 305 = TagSoftware + aux 315 = TagArtist + aux 320 = TagColorMap + aux 322 = TagTileWidth + aux 323 = TagTileLength + aux 324 = TagTileOffset + aux 325 = TagTileByteCount + aux 332 = TagInkSet + aux 338 = TagExtraSample + aux 339 = TagSampleFormat + aux 529 = TagYCbCrCoeff + aux 512 = TagJpegProc + aux 513 = TagJPEGInterchangeFormat + aux 514 = TagJPEGInterchangeFormatLength + aux 515 = TagJPEGRestartInterval + aux 517 = TagJPEGLosslessPredictors + aux 518 = TagJPEGPointTransforms + aux 519 = TagJPEGQTables + aux 520 = TagJPEGDCTables + aux 521 = TagJPEGACTables + aux 530 = TagYCbCrSubsampling + aux 531 = TagYCbCrPositioning + aux 532 = TagReferenceBlackWhite + aux v = TagUnknown v + +word16OfTag :: TiffTag -> Word16 +word16OfTag = aux + where aux TagSubfileType = 255 + aux TagImageWidth = 256 + aux TagImageLength = 257 + aux TagBitsPerSample = 258 + aux TagCompression = 259 + aux TagPhotometricInterpretation = 262 + aux TagFillOrder = 266 + aux TagDocumentName = 269 + aux TagImageDescription = 270 + aux TagStripOffsets = 273 + aux TagOrientation = 274 + aux TagSamplesPerPixel = 277 + aux TagRowPerStrip = 278 + aux TagStripByteCounts = 279 + aux TagXResolution = 282 + aux TagYResolution = 283 + aux TagPlanarConfiguration = 284 + aux TagXPosition = 286 + aux TagYPosition = 287 + aux TagResolutionUnit = 296 + aux TagSoftware = 305 + aux TagArtist = 315 + aux TagColorMap = 320 + aux TagTileWidth = 322 + aux TagTileLength = 323 + aux TagTileOffset = 324 + aux TagTileByteCount = 325 + aux TagInkSet = 332 + aux TagExtraSample = 338 + aux TagSampleFormat = 339 + aux TagYCbCrCoeff = 529 + aux TagJpegProc = 512 + aux TagJPEGInterchangeFormat = 513 + aux TagJPEGInterchangeFormatLength = 514 + aux TagJPEGRestartInterval = 515 + aux TagJPEGLosslessPredictors = 517 + aux TagJPEGPointTransforms = 518 + aux TagJPEGQTables = 519 + aux TagJPEGDCTables = 520 + aux TagJPEGACTables = 521 + aux TagYCbCrSubsampling = 530 + aux TagYCbCrPositioning = 531 + aux TagReferenceBlackWhite = 532 + aux (TagUnknown v) = v + +instance BinaryParam Endianness TiffTag where + getP endianness = tagOfWord16 <$> getP endianness + putP endianness = putP endianness . word16OfTag + +data ExtendedDirectoryData = + ExtendedDataNone + | ExtendedDataAscii !B.ByteString + | ExtendedDataShort !(V.Vector Word16) + | ExtendedDataLong !(V.Vector Word32) + deriving (Eq, Show) + +instance BinaryParam (Endianness, ImageFileDirectory) ExtendedDirectoryData where + putP (endianness, _) = dump + where + dump ExtendedDataNone = pure () + dump (ExtendedDataAscii bstr) = putByteString bstr + dump (ExtendedDataShort shorts) = V.mapM_ (putP endianness) shorts + dump (ExtendedDataLong longs) = V.mapM_ (putP endianness) longs + + getP (endianness, ifd) = fetcher ifd + where + align ImageFileDirectory { ifdOffset = offset } = do + readed <- bytesRead + skip . fromIntegral $ fromIntegral offset - readed + + getE :: (BinaryParam Endianness a) => Get a + getE = getP endianness + + getVec count = V.replicateM (fromIntegral count) + + fetcher ImageFileDirectory { ifdType = TypeAscii, ifdCount = count } | count > 1 = + align ifd >> (ExtendedDataAscii <$> getByteString (fromIntegral count)) + fetcher ImageFileDirectory { ifdType = TypeShort, ifdCount = 2, ifdOffset = ofs } = + pure . ExtendedDataShort $ V.fromListN 2 valList + where high = fromIntegral $ ofs `unsafeShiftR` 16 + low = fromIntegral $ ofs .&. 0xFFFF + valList = case endianness of + EndianLittle -> [low, high] + EndianBig -> [high, low] + fetcher ImageFileDirectory { ifdType = TypeShort, ifdCount = count } | count > 2 = + align ifd >> (ExtendedDataShort <$> getVec count getE) + fetcher ImageFileDirectory { ifdType = TypeLong, ifdCount = count } | count > 1 = + align ifd >> (ExtendedDataLong <$> getVec count getE) + fetcher _ = pure ExtendedDataNone + +data TiffSampleFormat = + TiffSampleUint + | TiffSampleInt + | TiffSampleDouble + | TiffSampleUnknown + deriving Eq + +unpackSampleFormat :: Word32 -> Get TiffSampleFormat +unpackSampleFormat = aux + where + aux 1 = pure TiffSampleUint + aux 2 = pure TiffSampleInt + aux 3 = pure TiffSampleDouble + aux 4 = pure TiffSampleUnknown + aux v = fail $ "Undefined data format (" ++ show v ++ ")" + +data ImageFileDirectory = ImageFileDirectory + { ifdIdentifier :: !TiffTag + , ifdType :: !IfdType + , ifdCount :: !Word32 + , ifdOffset :: !Word32 + , ifdExtended :: !ExtendedDirectoryData + } + +unLong :: String -> ExtendedDirectoryData -> Get (V.Vector Word32) +unLong _ (ExtendedDataShort v) = pure $ V.map fromIntegral v +unLong _ (ExtendedDataLong v) = pure v +unLong errMessage _ = fail errMessage + +cleanImageFileDirectory :: Endianness -> ImageFileDirectory -> ImageFileDirectory +cleanImageFileDirectory EndianBig ifd@(ImageFileDirectory { ifdCount = 1 }) = aux $ ifdType ifd + where aux TypeShort = ifd { ifdOffset = ifdOffset ifd `unsafeShiftR` 16 } + aux _ = ifd +cleanImageFileDirectory _ ifd = ifd + +instance BinaryParam Endianness ImageFileDirectory where + getP endianness = + ImageFileDirectory <$> getE <*> getE <*> getE <*> getE + <*> pure ExtendedDataNone + where getE :: (BinaryParam Endianness a) => Get a + getE = getP endianness + + putP endianness ifd =do + let putE :: (BinaryParam Endianness a) => a -> Put + putE = putP endianness + putE $ ifdIdentifier ifd + putE $ ifdType ifd + putE $ ifdCount ifd + putE $ ifdOffset ifd + +instance BinaryParam Endianness [ImageFileDirectory] where + getP endianness = do + count <- getP endianness :: Get Word16 + rez <- replicateM (fromIntegral count) $ getP endianness + _ <- getP endianness :: Get Word32 + pure rez + + putP endianness lst = do + let count = fromIntegral $ length lst :: Word16 + putP endianness count + mapM_ (putP endianness) lst + putP endianness (0 :: Word32) + +fetchExtended :: Endianness -> [ImageFileDirectory] -> Get [ImageFileDirectory] +fetchExtended endian = mapM $ \ifd -> do + v <- getP (endian, ifd) + pure $ ifd { ifdExtended = v } + +findIFD :: String -> TiffTag -> [ImageFileDirectory] + -> Get ImageFileDirectory +findIFD errorMessage tag lst = + case [v | v <- lst, ifdIdentifier v == tag] of + [] -> fail errorMessage + (x:_) -> pure x + +findPalette :: [ImageFileDirectory] -> Get (Maybe (Image PixelRGB16)) +findPalette ifds = + case [v | v <- ifds, ifdIdentifier v == TagColorMap] of + (ImageFileDirectory { ifdExtended = ExtendedDataShort vec }:_) -> + pure . Just . Image pixelCount 1 $ VS.generate (V.length vec) axx + where pixelCount = V.length vec `div` 3 + axx v = vec `V.unsafeIndex` (idx + color * pixelCount) + where (idx, color) = v `divMod` 3 + + _ -> pure Nothing + +findIFDData :: String -> TiffTag -> [ImageFileDirectory] -> Get Word32 +findIFDData msg tag lst = ifdOffset <$> findIFD msg tag lst + +findIFDDefaultData :: Word32 -> TiffTag -> [ImageFileDirectory] -> Get Word32 +findIFDDefaultData d tag lst = + case [v | v <- lst, ifdIdentifier v == tag] of + [] -> pure d + (x:_) -> pure $ ifdOffset x + +findIFDExt :: String -> TiffTag -> [ImageFileDirectory] -> Get ExtendedDirectoryData +findIFDExt msg tag lst = do + val <- findIFD msg tag lst + case val of + ImageFileDirectory + { ifdCount = 1, ifdOffset = ofs, ifdType = TypeShort } -> + pure . ExtendedDataShort . V.singleton $ fromIntegral ofs + ImageFileDirectory + { ifdCount = 1, ifdOffset = ofs, ifdType = TypeLong } -> + pure . ExtendedDataLong . V.singleton $ fromIntegral ofs + ImageFileDirectory { ifdExtended = v } -> pure v + + +findIFDExtDefaultData :: [Word32] -> TiffTag -> [ImageFileDirectory] + -> Get [Word32] +findIFDExtDefaultData d tag lst = + case [v | v <- lst, ifdIdentifier v == tag] of + [] -> pure d + (ImageFileDirectory { ifdExtended = ExtendedDataNone }:_) -> return d + (x:_) -> V.toList <$> unLong errorMessage (ifdExtended x) + where errorMessage = + "Can't parse tag " ++ show tag ++ " " ++ show (ifdExtended x) + +-- It's temporary, remove once tiff decoding is better +-- handled. +{- +instance Show (Image PixelRGB16) where + show _ = "Image PixelRGB16" +-} + +data TiffInfo = TiffInfo + { tiffHeader :: TiffHeader + , tiffWidth :: Word32 + , tiffHeight :: Word32 + , tiffColorspace :: TiffColorspace + , tiffSampleCount :: Word32 + , tiffRowPerStrip :: Word32 + , tiffPlaneConfiguration :: TiffPlanarConfiguration + , tiffSampleFormat :: [TiffSampleFormat] + , tiffBitsPerSample :: V.Vector Word32 + , tiffCompression :: TiffCompression + , tiffStripSize :: V.Vector Word32 + , tiffOffsets :: V.Vector Word32 + , tiffPalette :: Maybe (Image PixelRGB16) + , tiffYCbCrSubsampling :: V.Vector Word32 + } + +data TiffColorspace = + TiffMonochromeWhite0 -- ^ 0 + | TiffMonochrome -- ^ 1 + | TiffRGB -- ^ 2 + | TiffPaleted -- ^ 3 + | TiffTransparencyMask -- ^ 4 + | TiffCMYK -- ^ 5 + | TiffYCbCr -- ^ 6 + | TiffCIELab -- ^ 8 + +packPhotometricInterpretation :: TiffColorspace -> Word16 +packPhotometricInterpretation = aux + where + aux TiffMonochromeWhite0 = 0 + aux TiffMonochrome = 1 + aux TiffRGB = 2 + aux TiffPaleted = 3 + aux TiffTransparencyMask = 4 + aux TiffCMYK = 5 + aux TiffYCbCr = 6 + aux TiffCIELab = 8 + +unpackPhotometricInterpretation :: Word32 -> Get TiffColorspace +unpackPhotometricInterpretation = aux + where aux 0 = pure TiffMonochromeWhite0 + aux 1 = pure TiffMonochrome + aux 2 = pure TiffRGB + aux 3 = pure TiffPaleted + aux 4 = pure TiffTransparencyMask + aux 5 = pure TiffCMYK + aux 6 = pure TiffYCbCr + aux 8 = pure TiffCIELab + aux v = fail $ "Unrecognized color space " ++ show v + +unPackCompression :: Word32 -> Get TiffCompression +unPackCompression = aux + where + aux 0 = pure CompressionNone + aux 1 = pure CompressionNone + aux 2 = pure CompressionModifiedRLE + aux 5 = pure CompressionLZW + aux 6 = pure CompressionJPEG + aux 32773 = pure CompressionPackBit + aux v = fail $ "Unknown compression scheme " ++ show v + +packCompression :: TiffCompression -> Word16 +packCompression = aux + where + aux CompressionNone = 1 + aux CompressionModifiedRLE = 2 + aux CompressionLZW = 5 + aux CompressionJPEG = 6 + aux CompressionPackBit = 32773 + +copyByteString :: B.ByteString -> M.STVector s Word8 -> Int -> Int -> (Word32, Word32) + -> ST s Int +copyByteString str vec stride startWrite (from, count) = inner startWrite fromi + where fromi = fromIntegral from + maxi = fromi + fromIntegral count + + inner writeIdx i | i >= maxi = pure writeIdx + inner writeIdx i = do + let v = str `BU.unsafeIndex` i + (vec `M.unsafeWrite` writeIdx) v + inner (writeIdx + stride) $ i + 1 + +unpackPackBit :: B.ByteString -> M.STVector s Word8 -> Int -> Int + -> (Word32, Word32) + -> ST s Int +unpackPackBit str outVec stride writeIndex (offset, size) = loop fromi writeIndex + where fromi = fromIntegral offset + maxi = fromi + fromIntegral size + + replicateByte writeIdx _ 0 = pure writeIdx + replicateByte writeIdx v count = do + (outVec `M.unsafeWrite` writeIdx) v + replicateByte (writeIdx + stride) v $ count - 1 + + loop i writeIdx | i >= maxi = pure writeIdx + loop i writeIdx = choice + {-where v = fromIntegral (str `BU.unsafeIndex` i) :: Int8-} + where v = fromIntegral (str `B.index` i) :: Int8 + + choice + -- data + | 0 <= v = do + copyByteString str outVec stride writeIdx + (fromIntegral $ i + 1, fromIntegral v + 1) + >>= loop (i + 2 + fromIntegral v) + -- run + | -127 <= v = do + {-let nextByte = str `BU.unsafeIndex` (i + 1)-} + let nextByte = str `B.index` (i + 1) + count = negate (fromIntegral v) + 1 :: Int + replicateByte writeIdx nextByte count + >>= loop (i + 2) + + -- noop + | otherwise = loop writeIdx $ i + 1 + +uncompressAt :: TiffCompression + -> B.ByteString -> M.STVector s Word8 -> Int -> Int -> (Word32, Word32) + -> ST s Int +uncompressAt CompressionNone = copyByteString +uncompressAt CompressionPackBit = unpackPackBit +uncompressAt CompressionLZW = \str outVec _stride writeIndex (offset, size) -> do + let toDecode = B.take (fromIntegral size) $ B.drop (fromIntegral offset) str + runBoolReader $ decodeLzwTiff toDecode outVec writeIndex + return 0 +uncompressAt _ = error "Unhandled compression" + +class Unpackable a where + type StorageType a :: * + + outAlloc :: a -> Int -> ST s (M.STVector s (StorageType a)) + + -- | Final image and size, return offset and vector + allocTempBuffer :: a -> M.STVector s (StorageType a) -> Int + -> ST s (M.STVector s Word8) + + offsetStride :: a -> Int -> Int -> (Int, Int) + + mergeBackTempBuffer :: a -- ^ Type witness, just for the type checker. + -> Endianness + -> M.STVector s Word8 -- ^ Temporary buffer handling decompression. + -> Int -- ^ Line size in pixels + -> Int -- ^ Write index, in bytes + -> Word32 -- ^ size, in bytes + -> Int -- ^ Stride + -> M.STVector s (StorageType a) -- ^ Final buffer + -> ST s () + +-- | The Word8 instance is just a passthrough, to avoid +-- copying memory twice +instance Unpackable Word8 where + type StorageType Word8 = Word8 + + offsetStride _ i stride = (i, stride) + allocTempBuffer _ buff _ = pure buff + mergeBackTempBuffer _ _ _ _ _ _ _ _ = pure () + outAlloc _ count = M.replicate count 0 -- M.new + +instance Unpackable Word16 where + type StorageType Word16 = Word16 + + offsetStride _ _ _ = (0, 1) + outAlloc _ = M.new + allocTempBuffer _ _ s = M.new $ s * 2 + mergeBackTempBuffer _ EndianLittle tempVec _ index size stride outVec = + looperLe index 0 + where looperLe _ readIndex | readIndex >= fromIntegral size = pure () + looperLe writeIndex readIndex = do + v1 <- tempVec `M.read` readIndex + v2 <- tempVec `M.read` (readIndex + 1) + let finalValue = + (fromIntegral v2 `unsafeShiftL` 8) .|. fromIntegral v1 + (outVec `M.write` writeIndex) finalValue + + looperLe (writeIndex + stride) (readIndex + 2) + mergeBackTempBuffer _ EndianBig tempVec _ index size stride outVec = + looperBe index 0 + where looperBe _ readIndex | readIndex >= fromIntegral size = pure () + looperBe writeIndex readIndex = do + v1 <- tempVec `M.read` readIndex + v2 <- tempVec `M.read` (readIndex + 1) + let finalValue = + (fromIntegral v1 `unsafeShiftL` 8) .|. fromIntegral v2 + (outVec `M.write` writeIndex) finalValue + + looperBe (writeIndex + stride) (readIndex + 2) + +instance Unpackable Word32 where + type StorageType Word32 = Word32 + + offsetStride _ _ _ = (0, 1) + outAlloc _ = M.new + allocTempBuffer _ _ s = M.new $ s * 4 + mergeBackTempBuffer _ EndianLittle tempVec _ index size stride outVec = + looperLe index 0 + where looperLe _ readIndex | readIndex >= fromIntegral size = pure () + looperLe writeIndex readIndex = do + v1 <- tempVec `M.read` readIndex + v2 <- tempVec `M.read` (readIndex + 1) + v3 <- tempVec `M.read` (readIndex + 2) + v4 <- tempVec `M.read` (readIndex + 3) + let finalValue = + (fromIntegral v4 `unsafeShiftL` 24) .|. + (fromIntegral v3 `unsafeShiftL` 16) .|. + (fromIntegral v2 `unsafeShiftL` 8) .|. + fromIntegral v1 + (outVec `M.write` writeIndex) finalValue + + looperLe (writeIndex + stride) (readIndex + 4) + mergeBackTempBuffer _ EndianBig tempVec _ index size stride outVec = + looperBe index 0 + where looperBe _ readIndex | readIndex >= fromIntegral size = pure () + looperBe writeIndex readIndex = do + v1 <- tempVec `M.read` readIndex + v2 <- tempVec `M.read` (readIndex + 1) + v3 <- tempVec `M.read` (readIndex + 2) + v4 <- tempVec `M.read` (readIndex + 3) + let finalValue = + (fromIntegral v1 `unsafeShiftL` 24) .|. + (fromIntegral v2 `unsafeShiftL` 16) .|. + (fromIntegral v3 `unsafeShiftL` 8) .|. + fromIntegral v4 + (outVec `M.write` writeIndex) finalValue + + looperBe (writeIndex + stride) (readIndex + 4) + +data Pack4 = Pack4 + +instance Unpackable Pack4 where + type StorageType Pack4 = Word8 + allocTempBuffer _ _ = M.new + offsetStride _ _ _ = (0, 1) + outAlloc _ = M.new + mergeBackTempBuffer _ _ tempVec lineSize index size stride outVec = + inner 0 index pxCount + where pxCount = lineSize `div` stride + + maxWrite = M.length outVec + inner readIdx writeIdx _ + | readIdx >= fromIntegral size || writeIdx >= maxWrite = pure () + inner readIdx writeIdx line + | line <= 0 = inner readIdx (writeIdx + line * stride) pxCount + inner readIdx writeIdx line = do + v <- tempVec `M.read` readIdx + let high = (v `unsafeShiftR` 4) .&. 0xF + low = v .&. 0xF + (outVec `M.write` writeIdx) high + when (writeIdx + stride < maxWrite) $ + (outVec `M.write` (writeIdx + stride)) low + + inner (readIdx + 1) (writeIdx + 2 * stride) (line - 2) + +data Pack2 = Pack2 + +instance Unpackable Pack2 where + type StorageType Pack2 = Word8 + allocTempBuffer _ _ = M.new + offsetStride _ _ _ = (0, 1) + outAlloc _ = M.new + mergeBackTempBuffer _ _ tempVec lineSize index size stride outVec = + inner 0 index pxCount + where pxCount = lineSize `div` stride + + maxWrite = M.length outVec + inner readIdx writeIdx _ + | readIdx >= fromIntegral size || writeIdx >= maxWrite = pure () + inner readIdx writeIdx line + | line <= 0 = inner readIdx (writeIdx + line * stride) pxCount + inner readIdx writeIdx line = do + v <- tempVec `M.read` readIdx + let v0 = (v `unsafeShiftR` 6) .&. 0x3 + v1 = (v `unsafeShiftR` 4) .&. 0x3 + v2 = (v `unsafeShiftR` 2) .&. 0x3 + v3 = v .&. 0x3 + + (outVec `M.write` writeIdx) v0 + when (writeIdx + 1 * stride < maxWrite) $ + (outVec `M.write` (writeIdx + stride)) v1 + + when (writeIdx + 2 * stride < maxWrite) $ + (outVec `M.write` (writeIdx + stride * 2)) v2 + + when (writeIdx + 3 * stride < maxWrite) $ + (outVec `M.write` (writeIdx + stride * 3)) v3 + + inner (readIdx + 1) (writeIdx + 4 * stride) (line - 4) + +data Pack12 = Pack12 + +instance Unpackable Pack12 where + type StorageType Pack12 = Word16 + allocTempBuffer _ _ = M.new + offsetStride _ _ _ = (0, 1) + outAlloc _ = M.new + mergeBackTempBuffer _ _ tempVec lineSize index size stride outVec = + inner 0 index pxCount + where pxCount = lineSize `div` stride + + maxWrite = M.length outVec + inner readIdx writeIdx _ + | readIdx >= fromIntegral size || writeIdx >= maxWrite = pure () + inner readIdx writeIdx line + | line <= 0 = inner readIdx (writeIdx + line * stride) pxCount + inner readIdx writeIdx line = do + v0 <- tempVec `M.read` readIdx + v1 <- if readIdx + 1 < fromIntegral size + then tempVec `M.read` (readIdx + 1) + else pure 0 + v2 <- if readIdx + 2 < fromIntegral size + then tempVec `M.read` (readIdx + 2) + else pure 0 + + let high0 = fromIntegral v0 `unsafeShiftL` 4 + low0 = (fromIntegral v1 `unsafeShiftR` 4) .&. 0xF + + p0 = high0 .|. low0 + + high1 = (fromIntegral v1 .&. 0xF) `unsafeShiftL` 8 + low1 = fromIntegral v2 + p1 = high1 .|. low1 + + (outVec `M.write` writeIdx) p0 + when (writeIdx + 1 * stride < maxWrite) $ + (outVec `M.write` (writeIdx + stride)) p1 + + inner (readIdx + 3) (writeIdx + 2 * stride) (line - 2) + +data YCbCrSubsampling = YCbCrSubsampling + { ycbcrWidth :: !Int + , ycbcrHeight :: !Int + , ycbcrImageWidth :: !Int + , ycbcrStripHeight :: !Int + } + +instance Unpackable YCbCrSubsampling where + type StorageType YCbCrSubsampling = Word8 + + offsetStride _ _ _ = (0, 1) + outAlloc _ = M.new + allocTempBuffer _ _ = M.new + mergeBackTempBuffer subSampling _ tempVec _ index size _ outVec = + foldM_ unpacker 0 [(bx, by) | by <- [0, h .. lineCount - 1] + , bx <- [0, w .. imgWidth - 1]] + where w = ycbcrWidth subSampling + h = ycbcrHeight subSampling + imgWidth = ycbcrImageWidth subSampling + lineCount = ycbcrStripHeight subSampling + + lumaCount = w * h + blockSize = lumaCount + 2 + + maxOut = M.length outVec + + unpacker readIdx _ | readIdx >= fromIntegral size * 3 = pure readIdx + unpacker readIdx (bx, by) = do + cb <- tempVec `M.read` (readIdx + lumaCount) + cr <- tempVec `M.read` (readIdx + lumaCount + 1) + + let pixelIndices = + [index + ((by + y) * imgWidth + bx + x) * 3 | y <- [0 .. h - 1], x <- [0 .. w - 1]] + + writer readIndex writeIdx | writeIdx + 3 > maxOut = pure readIndex + writer readIndex writeIdx = do + y <- tempVec `M.read` readIndex + (outVec `M.write` writeIdx) y + (outVec `M.write` (writeIdx + 1)) cb + (outVec `M.write` (writeIdx + 2)) cr + return $ readIndex + 1 + + foldM_ writer readIdx pixelIndices + + return $ readIdx + blockSize + +gatherStrips :: ( Unpackable comp + , Pixel pixel + , StorageType comp ~ PixelBaseComponent pixel + ) + => comp -> B.ByteString -> TiffInfo -> Image pixel +gatherStrips comp str nfo = runST $ do + let width = fromIntegral $ tiffWidth nfo + height = fromIntegral $ tiffHeight nfo + sampleCount = if tiffSampleCount nfo /= 0 + then fromIntegral $ tiffSampleCount nfo + else V.length $ tiffBitsPerSample nfo + + rowPerStrip = fromIntegral $ tiffRowPerStrip nfo + endianness = hdrEndianness $ tiffHeader nfo + + stripCount = V.length $ tiffOffsets nfo + compression = tiffCompression nfo + + outVec <- outAlloc comp $ width * height * sampleCount + tempVec <- allocTempBuffer comp outVec + (rowPerStrip * width * sampleCount) + + let mutableImage = MutableImage + { mutableImageWidth = fromIntegral width + , mutableImageHeight = fromIntegral height + , mutableImageData = outVec + } + + case tiffPlaneConfiguration nfo of + PlanarConfigContig -> V.mapM_ unpacker sizes + where unpacker (idx, offset, size) = do + let (writeIdx, tempStride) = offsetStride comp idx 1 + _ <- uncompressAt compression str tempVec tempStride + writeIdx (offset, size) + mergeBackTempBuffer comp endianness tempVec (width * sampleCount) + idx size 1 outVec + + startWriteOffset = + V.generate stripCount(width * rowPerStrip * sampleCount *) + + sizes = V.zip3 startWriteOffset (tiffOffsets nfo) (tiffStripSize nfo) + + PlanarConfigSeparate -> V.mapM_ unpacker sizes + where unpacker (idx, offset, size) = do + let (writeIdx, tempStride) = offsetStride comp idx stride + _ <- uncompressAt compression str tempVec tempStride + writeIdx (offset, size) + mergeBackTempBuffer comp endianness tempVec (width * sampleCount) + idx size stride outVec + + stride = V.length $ tiffOffsets nfo + idxVector = V.enumFromN 0 stride + sizes = V.zip3 idxVector (tiffOffsets nfo) (tiffStripSize nfo) + + unsafeFreezeImage mutableImage + +ifdSingleLong :: TiffTag -> Word32 -> Writer [ImageFileDirectory] () +ifdSingleLong tag = ifdMultiLong tag . V.singleton + +ifdSingleShort :: Endianness -> TiffTag -> Word16 + -> Writer [ImageFileDirectory] () +ifdSingleShort endian tag = ifdMultiShort endian tag . V.singleton . fromIntegral + +ifdMultiLong :: TiffTag -> V.Vector Word32 -> Writer [ImageFileDirectory] () +ifdMultiLong tag v = tell . pure $ ImageFileDirectory + { ifdIdentifier = tag + , ifdType = TypeLong + , ifdCount = fromIntegral $ V.length v + , ifdOffset = offset + , ifdExtended = extended + } + where (offset, extended) + | V.length v > 1 = (0, ExtendedDataLong v) + | otherwise = (V.head v, ExtendedDataNone) + +ifdMultiShort :: Endianness -> TiffTag -> V.Vector Word32 + -> Writer [ImageFileDirectory] () +ifdMultiShort endian tag v = tell . pure $ ImageFileDirectory + { ifdIdentifier = tag + , ifdType = TypeShort + , ifdCount = size + , ifdOffset = offset + , ifdExtended = extended + } + where size = fromIntegral $ V.length v + (offset, extended) + | size > 2 = (0, ExtendedDataShort $ V.map fromIntegral v) + | size == 2 = + let v1 = fromIntegral $ V.head v + v2 = fromIntegral $ v `V.unsafeIndex` 1 + in + case endian of + EndianLittle -> (v2 `unsafeShiftL` 16 .|. v1, ExtendedDataNone) + EndianBig -> (v1 `unsafeShiftL` 16 .|. v2, ExtendedDataNone) + + | otherwise = case endian of + EndianLittle -> (V.head v, ExtendedDataNone) + EndianBig -> (V.head v `unsafeShiftL` 16, ExtendedDataNone) + +-- | All the IFD must be written in order according to the tag +-- value of the IFD. To avoid getting to much restriction in the +-- serialization code, just sort it. +orderIfdByTag :: [ImageFileDirectory] -> [ImageFileDirectory] +orderIfdByTag = sortBy comparer + where comparer a b = compare t1 t2 + where t1 = word16OfTag $ ifdIdentifier a + t2 = word16OfTag $ ifdIdentifier b + +-- | Given an official offset and a list of IFD, update the offset information +-- of the IFD with extended data. +setupIfdOffsets :: Word32 -> [ImageFileDirectory] -> [ImageFileDirectory] +setupIfdOffsets initialOffset lst = snd $ mapAccumL updater startExtended lst + where ifdElementCount = fromIntegral $ length lst + ifdSize = 12 + ifdCountSize = 2 + nextOffsetSize = 4 + startExtended = initialOffset + + ifdElementCount * ifdSize + + ifdCountSize + nextOffsetSize + + updater ix ifd@(ImageFileDirectory { ifdExtended = ExtendedDataAscii b }) = + (ix + fromIntegral (B.length b), ifd { ifdOffset = ix } ) + updater ix ifd@(ImageFileDirectory { ifdExtended = ExtendedDataLong v }) + | V.length v > 1 = ( ix + fromIntegral (V.length v * 4) + , ifd { ifdOffset = ix } ) + updater ix ifd@(ImageFileDirectory { ifdExtended = ExtendedDataShort v }) + | V.length v > 2 = ( ix + fromIntegral (V.length v * 2) + , ifd { ifdOffset = ix }) + updater ix ifd = (ix, ifd) + +instance BinaryParam B.ByteString TiffInfo where + putP rawData nfo = do + put $ tiffHeader nfo + + let ifdStartOffset = hdrOffset $ tiffHeader nfo + endianness = hdrEndianness $ tiffHeader nfo + + ifdShort = ifdSingleShort endianness + ifdShorts = ifdMultiShort endianness + list = setupIfdOffsets ifdStartOffset . orderIfdByTag . execWriter $ do + ifdSingleLong TagImageWidth $ tiffWidth nfo + ifdSingleLong TagImageLength $ tiffHeight nfo + ifdShorts TagBitsPerSample $ tiffBitsPerSample nfo + ifdSingleLong TagSamplesPerPixel $ tiffSampleCount nfo + ifdSingleLong TagRowPerStrip $ tiffRowPerStrip nfo + ifdShort TagPhotometricInterpretation + . packPhotometricInterpretation + $ tiffColorspace nfo + ifdShort TagPlanarConfiguration + . constantToPlaneConfiguration $ tiffPlaneConfiguration nfo + ifdShort TagCompression . packCompression + $ tiffCompression nfo + ifdMultiLong TagStripOffsets $ tiffOffsets nfo + + ifdMultiLong TagStripByteCounts $ tiffStripSize nfo + + let subSampling = tiffYCbCrSubsampling nfo + when (not $ V.null subSampling) $ + ifdShorts TagYCbCrSubsampling subSampling + + putByteString rawData + putP endianness list + mapM_ (\ifd -> putP (endianness, ifd) $ ifdExtended ifd) list + + getP _ = do + hdr <- get + readed <- bytesRead + skip . fromIntegral $ fromIntegral (hdrOffset hdr) - readed + let endian = hdrEndianness hdr + + ifd <- fmap (cleanImageFileDirectory endian) <$> getP endian + cleaned <- fetchExtended endian ifd + + let dataFind str tag = findIFDData str tag cleaned + dataDefault def tag = findIFDDefaultData def tag cleaned + extFind str tag = findIFDExt str tag cleaned + extDefault def tag = findIFDExtDefaultData def tag cleaned + + TiffInfo hdr + <$> dataFind "Can't find width" TagImageWidth + <*> dataFind "Can't find height" TagImageLength + <*> (dataFind "Can't find color space" TagPhotometricInterpretation + >>= unpackPhotometricInterpretation) + <*> dataFind "Can't find sample per pixel" TagSamplesPerPixel + <*> dataFind "Can't find row per strip" TagRowPerStrip + <*> (dataDefault 1 TagPlanarConfiguration + >>= planarConfgOfConstant) + <*> (extDefault [1] TagSampleFormat + >>= mapM unpackSampleFormat) + <*> (extFind "Can't find bit per sample" TagBitsPerSample + >>= unLong "Can't find bit depth") + <*> (dataFind "Can't find Compression" TagCompression + >>= unPackCompression) + <*> (extFind "Can't find byte counts" TagStripByteCounts + >>= unLong "Can't find bit per sample") + <*> (extFind "Strip offsets missing" TagStripOffsets + >>= unLong "Can't find strip offsets") + <*> findPalette cleaned + <*> (V.fromList <$> extDefault [2, 2] TagYCbCrSubsampling) + +unpack :: B.ByteString -> TiffInfo -> Either String DynamicImage +-- | while mandatory some images don't put correct +-- rowperstrip. So replacing 0 with actual image height. +unpack file nfo@TiffInfo { tiffRowPerStrip = 0 } = + unpack file $ nfo { tiffRowPerStrip = tiffHeight nfo } +unpack file nfo@TiffInfo { tiffColorspace = TiffPaleted + , tiffBitsPerSample = lst + , tiffSampleFormat = format + , tiffPalette = Just p + } + | lst == V.singleton 8 && format == [TiffSampleUint] = + let applyPalette = pixelMap (\v -> pixelAt p (fromIntegral v) 0) + gathered :: Image Pixel8 + gathered = gatherStrips (0 :: Word8) file nfo + in + pure . ImageRGB16 $ applyPalette gathered + + | lst == V.singleton 4 && format == [TiffSampleUint] = + let applyPalette = pixelMap (\v -> pixelAt p (fromIntegral v) 0) + gathered :: Image Pixel8 + gathered = gatherStrips Pack4 file nfo + in + pure . ImageRGB16 $ applyPalette gathered + + | lst == V.singleton 2 && format == [TiffSampleUint] = + let applyPalette = pixelMap (\v -> pixelAt p (fromIntegral v) 0) + gathered :: Image Pixel8 + gathered = gatherStrips Pack2 file nfo + in + pure . ImageRGB16 $ applyPalette gathered + +unpack file nfo@TiffInfo { tiffColorspace = TiffCMYK + , tiffBitsPerSample = lst + , tiffSampleFormat = format } + | lst == V.fromList [8, 8, 8, 8] && all (TiffSampleUint ==) format = + pure . ImageCMYK8 $ gatherStrips (0 :: Word8) file nfo + + | lst == V.fromList [16, 16, 16, 16] && all (TiffSampleUint ==) format = + pure . ImageCMYK16 $ gatherStrips (0 :: Word16) file nfo + +unpack file nfo@TiffInfo { tiffColorspace = TiffMonochromeWhite0 } + | otherwise = do + img <- unpack file (nfo { tiffColorspace = TiffMonochrome }) + case img of + ImageY8 i -> pure . ImageY8 $ pixelMap (maxBound -) i + ImageY16 i -> pure . ImageY16 $ pixelMap (maxBound -) i + _ -> pure img + +unpack file nfo@TiffInfo { tiffColorspace = TiffMonochrome + , tiffBitsPerSample = lst + , tiffSampleFormat = format } + | lst == V.singleton 2 && all (TiffSampleUint ==) format = + pure . ImageY8 . pixelMap (colorMap (64 *)) $ gatherStrips Pack2 file nfo + | lst == V.singleton 4 && all (TiffSampleUint ==) format = + pure . ImageY8 . pixelMap (colorMap (16 *)) $ gatherStrips Pack4 file nfo + | lst == V.singleton 8 && all (TiffSampleUint ==) format = + pure . ImageY8 $ gatherStrips (0 :: Word8) file nfo + | lst == V.singleton 12 && all (TiffSampleUint ==) format = + pure . ImageY16 . pixelMap (16 *) $ gatherStrips Pack12 file nfo + | lst == V.singleton 16 && all (TiffSampleUint ==) format = + pure . ImageY16 $ gatherStrips (0 :: Word16) file nfo + | lst == V.singleton 32 && all (TiffSampleUint ==) format = + pure . ImageY16 $ pixelMap (toWord16) img + where toWord16 v = fromIntegral $ v `unsafeShiftR` 16 + img = gatherStrips (0 :: Word32) file nfo :: Image Pixel32 + +unpack file nfo@TiffInfo { tiffColorspace = TiffYCbCr + , tiffBitsPerSample = lst + , tiffPlaneConfiguration = PlanarConfigContig + , tiffSampleFormat = format } + | lst == V.fromList [8, 8, 8] && all (TiffSampleUint ==) format = + pure . ImageYCbCr8 $ gatherStrips cbcrConf file nfo + where defaulting 0 = 2 + defaulting n = n + + w = defaulting $ tiffYCbCrSubsampling nfo V.! 0 + h = defaulting $ tiffYCbCrSubsampling nfo V.! 1 + cbcrConf = YCbCrSubsampling + { ycbcrWidth = fromIntegral w + , ycbcrHeight = fromIntegral h + , ycbcrImageWidth = fromIntegral $ tiffWidth nfo + , ycbcrStripHeight = fromIntegral $ tiffRowPerStrip nfo + } + +unpack file nfo@TiffInfo { tiffColorspace = TiffRGB + , tiffBitsPerSample = lst + , tiffSampleFormat = format } + | lst == V.fromList [2, 2, 2] && all (TiffSampleUint ==) format = + pure . ImageRGB8 . pixelMap (colorMap (64 *)) $ gatherStrips Pack2 file nfo + | lst == V.fromList [4, 4, 4] && all (TiffSampleUint ==) format = + pure . ImageRGB8 . pixelMap (colorMap (16 *)) $ gatherStrips Pack4 file nfo + | lst == V.fromList [8, 8, 8] && all (TiffSampleUint ==) format = + pure . ImageRGB8 $ gatherStrips (0 :: Word8) file nfo + | lst == V.fromList [8, 8, 8, 8] && all (TiffSampleUint ==) format = + pure . ImageRGBA8 $ gatherStrips (0 :: Word8) file nfo + | lst == V.fromList [16, 16, 16] && all (TiffSampleUint ==) format = + pure . ImageRGB16 $ gatherStrips (0 :: Word16) file nfo + | lst == V.fromList [16, 16, 16, 16] && all (TiffSampleUint ==) format = + pure . ImageRGBA16 $ gatherStrips (0 :: Word16) file nfo +unpack file nfo@TiffInfo { tiffColorspace = TiffMonochrome + , tiffBitsPerSample = lst + , tiffSampleFormat = format } + -- some files are a little bit borked... + | lst == V.fromList [8, 8, 8] && all (TiffSampleUint ==) format = + pure . ImageRGB8 $ gatherStrips (0 :: Word8) file nfo + +unpack _ _ = fail "Failure to unpack TIFF file" + +-- | Transform a raw tiff image to an image, without modifying +-- the underlying pixel type. +-- +-- This function can output the following pixel types : +-- +-- * PixelY8 +-- +-- * PixelY16 +-- +-- * PixelRGB8 +-- +-- * PixelRGB16 +-- +-- * PixelCMYK8 +-- +-- * PixelCMYK16 +-- +decodeTiff :: B.ByteString -> Either String DynamicImage +decodeTiff file = runGetStrict (getP file) file >>= unpack file + +-- | Class defining which pixel types can be serialized in a +-- Tiff file. +class (Pixel px) => TiffSaveable px where + colorSpaceOfPixel :: px -> TiffColorspace + + subSamplingInfo :: px -> V.Vector Word32 + subSamplingInfo _ = V.empty + +instance TiffSaveable Pixel8 where + colorSpaceOfPixel _ = TiffMonochrome + +instance TiffSaveable Pixel16 where + colorSpaceOfPixel _ = TiffMonochrome + +instance TiffSaveable PixelCMYK8 where + colorSpaceOfPixel _ = TiffCMYK + +instance TiffSaveable PixelCMYK16 where + colorSpaceOfPixel _ = TiffCMYK + +instance TiffSaveable PixelRGB8 where + colorSpaceOfPixel _ = TiffRGB + +instance TiffSaveable PixelRGB16 where + colorSpaceOfPixel _ = TiffRGB + +instance TiffSaveable PixelRGBA8 where + colorSpaceOfPixel _ = TiffRGB + +instance TiffSaveable PixelRGBA16 where + colorSpaceOfPixel _ = TiffRGB + +instance TiffSaveable PixelYCbCr8 where + colorSpaceOfPixel _ = TiffYCbCr + subSamplingInfo _ = V.fromListN 2 [1, 1] + +-- | Transform an image into a Tiff encoded bytestring, reade to be +-- written as a file. +encodeTiff :: forall px. (TiffSaveable px) => Image px -> Lb.ByteString +encodeTiff img = runPut $ putP rawPixelData hdr + where intSampleCount = componentCount (undefined :: px) + sampleCount = fromIntegral intSampleCount + + sampleType = (undefined :: PixelBaseComponent px) + pixelData = imageData img + + rawPixelData = toByteString pixelData + width = fromIntegral $ imageWidth img + height = fromIntegral $ imageHeight img + intSampleSize = sizeOf sampleType + sampleSize = fromIntegral intSampleSize + bitPerSample = sampleSize * 8 + imageSize = width * height * sampleCount * sampleSize + headerSize = 8 + + hdr = TiffInfo + { tiffHeader = TiffHeader + { hdrEndianness = EndianLittle + , hdrOffset = headerSize + imageSize + } + , tiffWidth = width + , tiffHeight = height + , tiffColorspace = colorSpaceOfPixel (undefined :: px) + , tiffSampleCount = fromIntegral $ sampleCount + , tiffRowPerStrip = fromIntegral $ imageHeight img + , tiffPlaneConfiguration = PlanarConfigContig + , tiffSampleFormat = [TiffSampleUint] + , tiffBitsPerSample = V.replicate intSampleCount bitPerSample + , tiffCompression = CompressionNone + , tiffStripSize = V.singleton imageSize + , tiffOffsets = V.singleton headerSize + , tiffPalette = Nothing + , tiffYCbCrSubsampling = subSamplingInfo (undefined :: px) + } + +-- | Helper function to directly write an image as a tiff on disk. +writeTiff :: (TiffSaveable pixel) => FilePath -> Image pixel -> IO () +writeTiff path img = Lb.writeFile path $ encodeTiff img +
+ src/Codec/Picture/Types.hs view
@@ -0,0 +1,1837 @@+{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeSynonymInstances #-} +{-# LANGUAGE FunctionalDependencies #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE Rank2Types #-} +{-# LANGUAGE CPP #-} +-- | 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( .. ) + , Palette + + -- ** Image functions + , createMutableImage + , freezeImage + , unsafeFreezeImage + , thawImage + , unsafeThawImage + + -- ** Pixel types + , Pixel8 + , Pixel16 + , Pixel32 + , PixelF + , PixelYA8( .. ) + , PixelYA16( .. ) + , PixelRGB8( .. ) + , PixelRGB16( .. ) + , PixelRGBF( .. ) + , PixelRGBA8( .. ) + , PixelRGBA16( .. ) + , PixelCMYK8( .. ) + , PixelCMYK16( .. ) + , PixelYCbCr8( .. ) + + -- * Type classes + , ColorConvertible( .. ) + , Pixel(..) + -- $graph + , ColorSpaceConvertible( .. ) + , LumaPlaneExtractable( .. ) + , TransparentPixel( .. ) + + -- * Helper functions + , pixelMap + , pixelMapXY + , pixelFold + , dynamicMap + , dynamicPixelMap + , dropAlphaLayer + , withImage + , zipPixelComponent3 + , generateImage + , generateFoldImage + , gammaCorrection + , toneMapping + + -- * Color plane extraction + , ColorPlane ( ) + + , PlaneRed( .. ) + , PlaneGreen( .. ) + , PlaneBlue( .. ) + , PlaneAlpha( .. ) + , PlaneLuma( .. ) + , PlaneCr( .. ) + , PlaneCb( .. ) + , PlaneCyan( .. ) + , PlaneMagenta( .. ) + , PlaneYellow( .. ) + , PlaneBlack( .. ) + + , extractComponent + , unsafeExtractComponent + ) where + +import Control.Monad( forM_, foldM, liftM, ap ) +import Control.DeepSeq( NFData( .. ) ) +import Control.Monad.ST( runST ) +import Control.Monad.Primitive ( PrimMonad, PrimState ) +import Foreign.Storable ( Storable ) +import Data.Bits( unsafeShiftL, unsafeShiftR ) +import Data.Word( Word8, Word16, Word32 ) +import Data.List( foldl' ) +import Data.Vector.Storable ( (!) ) +import qualified Data.Vector.Storable as V +import qualified Data.Vector.Storable.Mutable as M + +#include "ConvGraph.hs" + +-- | 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 :: V.Vector (PixelBaseComponent a) + } + +-- | Type for the palette used in Gif & PNG files. +type Palette = Image PixelRGB8 + +{-# INLINE (!!!) #-} +(!!!) :: (Storable e) => V.Vector e -> Int -> e +(!!!) = V.unsafeIndex + +-- | Class used to describle plane present in the pixel +-- type. If a pixel has a plane description associated, +-- you can use the plane name to extract planes independently. +class ColorPlane pixel planeToken where + -- | Retrieve the index of the component in the + -- given pixel type. + toComponentIndex :: pixel -> planeToken -> Int + +-- | Define the plane for the red color component +data PlaneRed = PlaneRed + +-- | Define the plane for the green color component +data PlaneGreen = PlaneGreen + +-- | Define the plane for the blue color component +data PlaneBlue = PlaneBlue + +-- | Define the plane for the alpha (transparency) component +data PlaneAlpha = PlaneAlpha + +-- | Define the plane for the luma component +data PlaneLuma = PlaneLuma + +-- | Define the plane for the Cr component +data PlaneCr = PlaneCr + +-- | Define the plane for the Cb component +data PlaneCb = PlaneCb + +-- | Define plane for the cyan component of the +-- CMYK color space. +data PlaneCyan = PlaneCyan + +-- | Define plane for the magenta component of the +-- CMYK color space. +data PlaneMagenta = PlaneMagenta + +-- | Define plane for the yellow component of the +-- CMYK color space. +data PlaneYellow = PlaneYellow + +-- | Define plane for the black component of +-- the CMYK color space. +data PlaneBlack = PlaneBlack + +-- | Extract a color plane from an image given a present plane in the image +-- examples : +-- +-- @ +-- extractRedPlane :: Image PixelRGB8-> Image Pixel8 +-- extractRedPlane = extractComponent PlaneRed +-- @ +-- +extractComponent :: forall px plane. ( Pixel px + , Pixel (PixelBaseComponent px) + , PixelBaseComponent (PixelBaseComponent px) + ~ PixelBaseComponent px + , ColorPlane px plane ) + => plane -> Image px -> Image (PixelBaseComponent px) +extractComponent plane = unsafeExtractComponent idx + where idx = toComponentIndex (undefined :: px) plane + +-- | Extract an image plane of an image, returning an image which +-- can be represented by a gray scale image. +-- If you ask a component out of bound, the `error` function will +-- be called +unsafeExtractComponent :: forall a + . ( Pixel a + , Pixel (PixelBaseComponent a) + , PixelBaseComponent (PixelBaseComponent a) + ~ PixelBaseComponent a) + => Int -- ^ The component index, beginning at 0 ending at (componentCount - 1) + -> Image a -- ^ Source image + -> Image (PixelBaseComponent a) +unsafeExtractComponent comp img@(Image { imageWidth = w, imageHeight = h }) + | comp >= padd = error $ "extractComponent : invalid component index (" + ++ show comp ++ ", max:" ++ show padd ++ ")" + | otherwise = Image { imageWidth = w, imageHeight = h, imageData = plane } + where plane = stride img 1 padd comp + padd = componentCount (undefined :: a) + +-- | For any image with an alpha component (transparency), +-- drop it, returning a pure opaque image. +dropAlphaLayer :: (TransparentPixel a b) => Image a -> Image b +dropAlphaLayer = pixelMap dropTransparency + +-- | Class modeling transparent pixel, should provide a method +-- to combine transparent pixels +class (Pixel a, Pixel b) => TransparentPixel a b | a -> b where + -- | Just return the opaque pixel value + dropTransparency :: a -> b + + -- | access the transparency (alpha layer) of a given + -- transparent pixel type + getTransparency :: a -> PixelBaseComponent a + +instance TransparentPixel PixelRGBA8 PixelRGB8 where + {-# INLINE dropTransparency #-} + dropTransparency (PixelRGBA8 r g b _) = PixelRGB8 r g b + {-# INLINE getTransparency #-} + getTransparency (PixelRGBA8 _ _ _ a) = a + +stride :: (Storable (PixelBaseComponent a)) + => Image a -> Int -> Int -> Int -> V.Vector (PixelBaseComponent a) +stride Image { imageWidth = w, imageHeight = h, imageData = array } + run padd firstComponent = runST $ do + let cell_count = w * h * run + outArray <- M.new cell_count + + let strideWrite write_idx _ | write_idx == cell_count = return () + strideWrite write_idx read_idx = do + forM_ [0 .. run - 1] $ \i -> + (outArray .<-. (write_idx + i)) $ array !!! (read_idx + i) + strideWrite (write_idx + run) (read_idx + padd) + + strideWrite 0 firstComponent + V.unsafeFreeze outArray + +instance NFData (Image a) where + rnf (Image width height dat) = width `seq` + height `seq` + dat `seq` + () + +-- | 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 :: M.STVector s (PixelBaseComponent a) + } + +-- | `O(n)` Yield an immutable copy of an image by making a copy of it +freezeImage :: (Storable (PixelBaseComponent px), PrimMonad m) + => MutableImage (PrimState m) px -> m (Image px) +freezeImage (MutableImage w h d) = Image w h `liftM` V.freeze d + +-- | `O(n)` Yield a mutable copy of an image by making a copy of it. +thawImage :: (Storable (PixelBaseComponent px), PrimMonad m) + => Image px -> m (MutableImage (PrimState m) px) +thawImage (Image w h d) = MutableImage w h `liftM` V.thaw d + +-- | `O(1)` Unsafe convert an imutable image to an mutable one without copying. +-- The source image shouldn't be used after this operation. +unsafeThawImage :: (Storable (PixelBaseComponent px), PrimMonad m) + => Image px -> m (MutableImage (PrimState m) px) +unsafeThawImage (Image w h d) = MutableImage w h `liftM` V.unsafeThaw d + +-- | `O(1)` Unsafe convert a mutable image to an immutable one without copying. +-- The mutable image may not be used after this operation. +unsafeFreezeImage :: (Storable (PixelBaseComponent a), PrimMonad m) + => MutableImage (PrimState m) a -> m (Image a) +unsafeFreezeImage (MutableImage w h d) = Image w h `liftM` V.unsafeFreeze d + +-- | Create a mutable image, filled with the given background color. +createMutableImage :: (Pixel px, PrimMonad m) + => Int -- ^ Width + -> Int -- ^ Height + -> px -- ^ Background color + -> m (MutableImage (PrimState m) px) +createMutableImage width height background = + unsafeThawImage $ generateImage (\_ _ -> background) width height + +instance NFData (MutableImage s a) where + rnf (MutableImage width height dat) = width `seq` + height `seq` + dat `seq` + () + +-- | Type allowing the loading of an image with different pixel +-- structures +data DynamicImage = + -- | A greyscale image. + ImageY8 (Image Pixel8) + -- | A greyscale image with 16bit components + | ImageY16 (Image Pixel16) + -- | A greyscale HDR image + | ImageYF (Image PixelF) + -- | An image in greyscale with an alpha channel. + | ImageYA8 (Image PixelYA8) + -- | An image in greyscale with alpha channel on 16 bits. + | ImageYA16 (Image PixelYA16) + -- | An image in true color. + | ImageRGB8 (Image PixelRGB8) + -- | An image in true color with 16bit depth. + | ImageRGB16 (Image PixelRGB16) + -- | An image with HDR pixels + | ImageRGBF (Image PixelRGBF) + -- | An image in true color and an alpha channel. + | ImageRGBA8 (Image PixelRGBA8) + -- | A true color image with alpha on 16 bits. + | ImageRGBA16 (Image PixelRGBA16) + -- | An image in the colorspace used by Jpeg images. + | ImageYCbCr8 (Image PixelYCbCr8) + -- | An image in the colorspace CMYK + | ImageCMYK8 (Image PixelCMYK8) + -- | An image in the colorspace CMYK and 16 bots precision + | ImageCMYK16 (Image PixelCMYK16) + +-- | Helper function to help extract information from dynamic +-- image. To get the width of an dynamic image, you can use +-- the following snippet : +-- +-- > dynWidth :: DynamicImage -> Int +-- > dynWidth img = dynamicMap imageWidth img +-- +dynamicMap :: (forall pixel . (Pixel pixel) => Image pixel -> a) + -> DynamicImage -> a +dynamicMap f (ImageY8 i) = f i +dynamicMap f (ImageY16 i) = f i +dynamicMap f (ImageYF i) = f i +dynamicMap f (ImageYA8 i) = f i +dynamicMap f (ImageYA16 i) = f i +dynamicMap f (ImageRGB8 i) = f i +dynamicMap f (ImageRGB16 i) = f i +dynamicMap f (ImageRGBF i) = f i +dynamicMap f (ImageRGBA8 i) = f i +dynamicMap f (ImageRGBA16 i) = f i +dynamicMap f (ImageYCbCr8 i) = f i +dynamicMap f (ImageCMYK8 i) = f i +dynamicMap f (ImageCMYK16 i) = f i + +-- | Equivalent of the `pixelMap` function for the dynamic images. +-- You can perform pixel colorspace independant operations with this +-- function. +-- +-- For instance, if you wan't to extract a square crop of any image, +-- without caring about colorspace, you can use the following snippet. +-- +-- > dynSquare :: DynamicImage -> DynamicImage +-- > dynSquare = dynMap squareImage +-- > +-- > squareImage :: Pixel a => Image a -> Image a +-- > squareImage img = generateImage (\x y -> pixelAt img x y) edge edge +-- > where edge = min (imageWidth img) (imageHeight img) +-- +dynamicPixelMap :: (forall pixel . (Pixel pixel) => Image pixel -> Image pixel) + -> DynamicImage -> DynamicImage +dynamicPixelMap f = aux + where + aux (ImageY8 i) = ImageY8 (f i) + aux (ImageY16 i) = ImageY16 (f i) + aux (ImageYF i) = ImageYF (f i) + aux (ImageYA8 i) = ImageYA8 (f i) + aux (ImageYA16 i) = ImageYA16 (f i) + aux (ImageRGB8 i) = ImageRGB8 (f i) + aux (ImageRGB16 i) = ImageRGB16 (f i) + aux (ImageRGBF i) = ImageRGBF (f i) + aux (ImageRGBA8 i) = ImageRGBA8 (f i) + aux (ImageRGBA16 i) = ImageRGBA16 (f i) + aux (ImageYCbCr8 i) = ImageYCbCr8 (f i) + aux (ImageCMYK8 i) = ImageCMYK8 (f i) + aux (ImageCMYK16 i) = ImageCMYK16 (f i) + +instance NFData DynamicImage where + rnf (ImageY8 img) = rnf img + rnf (ImageY16 img) = rnf img + rnf (ImageYF img) = rnf img + rnf (ImageYA8 img) = rnf img + rnf (ImageYA16 img) = rnf img + rnf (ImageRGB8 img) = rnf img + rnf (ImageRGB16 img) = rnf img + rnf (ImageRGBF img) = rnf img + rnf (ImageRGBA8 img) = rnf img + rnf (ImageRGBA16 img) = rnf img + rnf (ImageYCbCr8 img) = rnf img + rnf (ImageCMYK8 img) = rnf img + rnf (ImageCMYK16 img) = rnf img + +-- | Simple alias for greyscale value in 8 bits. +type Pixel8 = Word8 + +-- | Simple alias for greyscale value in 16 bits. +type Pixel16 = Word16 + +-- | Simple alias for greyscale value in 16 bits. +type Pixel32 = Word32 + +-- | Floating greyscale value, the 0 to 255 8 bit range maps +-- to 0 to 1 in this floating version +type PixelF = Float + +-- | Pixel type storing Luminance (Y) and alpha information +-- on 8 bits. +-- Value are stored in the following order : +-- +-- * Luminance +-- +-- * Alpha +-- +data PixelYA8 = PixelYA8 {-# UNPACK #-} !Pixel8 -- Luminance + {-# UNPACK #-} !Pixel8 -- Alpha value + deriving (Eq, Ord, Show) + +-- | Pixel type storing Luminance (Y) and alpha information +-- on 16 bits. +-- Value are stored in the following order : +-- +-- * Luminance +-- +-- * Alpha +-- +data PixelYA16 = PixelYA16 {-# UNPACK #-} !Pixel16 -- Luminance + {-# UNPACK #-} !Pixel16 -- Alpha value + deriving (Eq, Ord, Show) + +-- | Pixel type storing classic pixel on 8 bits +-- Value are stored in the following order : +-- +-- * Red +-- +-- * Green +-- +-- * Blue +-- +data PixelRGB8 = PixelRGB8 {-# UNPACK #-} !Pixel8 -- Red + {-# UNPACK #-} !Pixel8 -- Green + {-# UNPACK #-} !Pixel8 -- Blue + deriving (Eq, Ord, Show) + +-- | Pixel type storing pixels on 16 bits +-- Value are stored in the following order : +-- +-- * Red +-- +-- * Green +-- +-- * Blue +-- +data PixelRGB16 = PixelRGB16 {-# UNPACK #-} !Pixel16 -- Red + {-# UNPACK #-} !Pixel16 -- Green + {-# UNPACK #-} !Pixel16 -- Blue + deriving (Eq, Ord, Show) + +-- | Pixel type storing HDR pixel on 32 bits float +-- Value are stored in the following order : +-- +-- * Red +-- +-- * Green +-- +-- * Blue +-- +data PixelRGBF = PixelRGBF {-# UNPACK #-} !PixelF -- Red + {-# UNPACK #-} !PixelF -- Green + {-# UNPACK #-} !PixelF -- Blue + deriving (Eq, Ord, Show) + +-- | Pixel storing data in the YCbCr colorspace, +-- value are stored in the following order : +-- +-- * Y (luminance) +-- +-- * Cr +-- +-- * Cb +-- +data PixelYCbCr8 = PixelYCbCr8 {-# UNPACK #-} !Pixel8 -- Y luminance + {-# UNPACK #-} !Pixel8 -- Cr red difference + {-# UNPACK #-} !Pixel8 -- Cb blue difference + deriving (Eq, Ord, Show) + +-- | Pixel storing data in the CMYK colorspace. value +-- are stored in the following order : +-- +-- * Cyan +-- +-- * Magenta +-- +-- * Yellow +-- +-- * Black +-- +data PixelCMYK8 = PixelCMYK8 {-# UNPACK #-} !Pixel8 -- Cyan + {-# UNPACK #-} !Pixel8 -- Magenta + {-# UNPACK #-} !Pixel8 -- Yellow + {-# UNPACK #-} !Pixel8 -- Black + deriving (Eq, Ord, Show) + +-- | Pixel storing data in the CMYK colorspace. value +-- are stored in the following order : +-- +-- * Cyan +-- +-- * Magenta +-- +-- * Yellow +-- +-- * Black +-- +data PixelCMYK16 = PixelCMYK16 {-# UNPACK #-} !Pixel16 -- Cyan + {-# UNPACK #-} !Pixel16 -- Magenta + {-# UNPACK #-} !Pixel16 -- Yellow + {-# UNPACK #-} !Pixel16 -- Black + deriving (Eq, Ord, Show) + + +-- | 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 #-} !Pixel8 -- Red + {-# UNPACK #-} !Pixel8 -- Green + {-# UNPACK #-} !Pixel8 -- Blue + {-# UNPACK #-} !Pixel8 -- Alpha + deriving (Eq, Ord, Show) + +-- | Pixel type storing a RGB information with an alpha +-- channel on 16 bits. +-- Values are stored in the following order +-- +-- * Red +-- +-- * Green +-- +-- * Blue +-- +-- * Alpha +-- +data PixelRGBA16 = PixelRGBA16 {-# UNPACK #-} !Pixel16 -- Red + {-# UNPACK #-} !Pixel16 -- Green + {-# UNPACK #-} !Pixel16 -- Blue + {-# UNPACK #-} !Pixel16 -- Alpha + deriving (Eq, Ord, Show) + +-- | Definition of pixels used in images. Each pixel has a color space, and a representative +-- component (Word8 or Float). +class ( Storable (PixelBaseComponent a) + , Num (PixelBaseComponent a), Eq a ) => Pixel a where + -- | Type of the pixel component, "classical" images + -- would have Word8 type as their PixelBaseComponent, + -- HDR image would have Float for instance + type PixelBaseComponent a :: * + + -- | Call the function for every component of the pixels. + -- For example for RGB pixels mixWith is declared like this : + -- + -- > mixWith f (PixelRGB8 ra ga ba) (PixelRGB8 rb gb bb) = + -- > PixelRGB8 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) + -- + mixWith :: (Int -> PixelBaseComponent a -> PixelBaseComponent a -> PixelBaseComponent a) + -> a -> a -> a + + -- | Return the number of component of the pixel + componentCount :: a -> Int + + -- | Apply a function to all color component of a pixel. + colorMap :: (PixelBaseComponent a -> PixelBaseComponent a) -> a -> a + + -- | 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) + + -- | 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 :: PrimMonad m => MutableImage (PrimState m) a -> Int -> Int -> m a + + -- | Write a pixel in a mutable image at position x y + writePixel :: PrimMonad m => MutableImage (PrimState m) a -> Int -> Int -> a -> m () + + -- | Unsafe version of pixelAt, read a pixel at the given + -- index without bound checking (if possible). + -- The index is expressed in number (PixelBaseComponent a) + unsafePixelAt :: V.Vector (PixelBaseComponent a) -> Int -> a + + -- | Unsafe version of readPixel, read a pixel at the given + -- position without bound checking (if possible). The index + -- is expressed in number (PixelBaseComponent a) + unsafeReadPixel :: PrimMonad m => M.STVector (PrimState m) (PixelBaseComponent a) -> Int -> m a + + -- | Unsafe version of writePixel, write a pixel at the + -- given position without bound checking. This can be _really_ unsafe. + -- The index is expressed in number (PixelBaseComponent a) + unsafeWritePixel :: PrimMonad m => M.STVector (PrimState m) (PixelBaseComponent a) -> Int -> a -> m () + + +-- | 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 = pixelMap promotePixel + +-- | This class abstract colorspace conversion. This +-- conversion can be lossy, which ColorConvertible cannot +class (Pixel a, Pixel b) => ColorSpaceConvertible a b where + -- | Pass a pixel from a colorspace (say RGB) to the second one + -- (say YCbCr) + convertPixel :: a -> b + + -- | Helper function to convert a whole image by taking a + -- copy it. + convertImage :: Image a -> Image b + convertImage = pixelMap convertPixel + +-- | Create an image given a function to generate pixels. +-- The function will receive value from 0 to width-1 for the x parameter +-- and 0 to height-1 for the y parameter. The coordinate 0,0 is the upper +-- left corner of the image, and (width-1, height-1) the lower right corner. +-- +-- for example, to create a small gradient image : +-- +-- > imageCreator :: String -> Image PixelRGB8 +-- > imageCreator path = writePng path $ generateImage pixelRenderer 250 300 +-- > where pixelRenderer x y = PixelRGB8 x y 128 +-- +generateImage :: forall a. (Pixel a) + => (Int -> Int -> a) -- ^ Generating function, with `x` and `y` params. + -> Int -- ^ Width in pixels + -> Int -- ^ Height in pixels + -> Image a +generateImage f w h = Image { imageWidth = w, imageHeight = h, imageData = generated } + where compCount = componentCount (undefined :: a) + generated = runST $ do + arr <- M.new (w * h * compCount) + let lineGenerator _ y | y >= h = return () + lineGenerator lineIdx y = column lineIdx 0 + where column idx x | x >= w = lineGenerator idx $ y + 1 + column idx x = do + unsafeWritePixel arr idx $ f x y + column (idx + compCount) $ x + 1 + + lineGenerator 0 0 + V.unsafeFreeze arr + +-- | Create an image using a monadic initializer function. +-- The function will receive value from 0 to width-1 for the x parameter +-- and 0 to height-1 for the y parameter. The coordinate 0,0 is the upper +-- left corner of the image, and (width-1, height-1) the lower right corner. +-- +-- The function is called for each pixel in the line from left to right (0 to width - 1) +-- and for each line (0 to height - 1). +withImage :: forall m pixel. (Pixel pixel, PrimMonad m) + => Int -- ^ Image width + -> Int -- ^ Image height + -> (Int -> Int -> m pixel) -- ^ Generating functions + -> m (Image pixel) +withImage width height pixelGenerator = do + let pixelComponentCount = componentCount (undefined :: pixel) + arr <- M.new (width * height * pixelComponentCount) + let mutImage = MutableImage + { mutableImageWidth = width + , mutableImageHeight = height + , mutableImageData = arr + } + + let pixelPositions = [(x, y) | y <- [0 .. height-1], x <- [0..width-1]] + sequence_ [pixelGenerator x y >>= unsafeWritePixel arr idx + | ((x,y), idx) <- zip pixelPositions [0, pixelComponentCount ..]] + unsafeFreezeImage mutImage + +-- | Create an image given a function to generate pixels. +-- The function will receive value from 0 to width-1 for the x parameter +-- and 0 to height-1 for the y parameter. The coordinate 0,0 is the upper +-- left corner of the image, and (width-1, height-1) the lower right corner. +-- +-- the acc parameter is a user defined one. +-- +-- The function is called for each pixel in the line from left to right (0 to width - 1) +-- and for each line (0 to height - 1). +generateFoldImage :: forall a acc. (Pixel a) + => (acc -> Int -> Int -> (acc, a)) -- ^ Function taking the state, x and y + -> acc -- ^ Initial state + -> Int -- ^ Width in pixels + -> Int -- ^ Height in pixels + -> (acc, Image a) +generateFoldImage f intialAcc w h = + (finalState, Image { imageWidth = w, imageHeight = h, imageData = generated }) + where compCount = componentCount (undefined :: a) + (finalState, generated) = runST $ do + arr <- M.new (w * h * compCount) + let mutImage = MutableImage { + mutableImageWidth = w, + mutableImageHeight = h, + mutableImageData = arr } + foldResult <- foldM (\acc (x,y) -> do + let (acc', px) = f acc x y + writePixel mutImage x y px + return acc') intialAcc [(x,y) | y <- [0 .. h-1], x <- [0 .. w-1]] + + frozen <- V.unsafeFreeze arr + return (foldResult, frozen) + +-- | Fold over the pixel of an image with a raster scan order : +-- from top to bottom, left to right +{-# INLINE pixelFold #-} +pixelFold :: (Pixel pixel) + => (acc -> Int -> Int -> pixel -> acc) -> acc -> Image pixel -> acc +pixelFold f initialAccumulator img@(Image { imageWidth = w, imageHeight = h }) = + lineFold + where pixelFolder y acc x = f acc x y $ pixelAt img x y + columnFold lineAcc y = foldl' (pixelFolder y) lineAcc [0 .. w - 1] + lineFold = foldl' columnFold initialAccumulator [0 .. h - 1] + +-- | `map` equivalent for an image, working at the pixel level. +-- Little example : a brightness function for an rgb image +-- +-- > brightnessRGB8 :: Int -> Image PixelRGB8 -> Image PixelRGB8 +-- > brightnessRGB8 add = pixelMap brightFunction +-- > where up v = fromIntegral (fromIntegral v + add) +-- > brightFunction (PixelRGB8 r g b) = +-- > PixelRGB8 (up r) (up g) (up b) +-- +pixelMap :: forall a b. (Pixel a, Pixel b) + => (a -> b) -> Image a -> Image b +{-# RULES "pixelMap fusion" forall g f. pixelMap g . pixelMap f = pixelMap (g . f) #-} +{-# SPECIALIZE INLINE pixelMap :: (PixelYCbCr8 -> PixelRGB8) -> Image PixelYCbCr8 -> Image PixelRGB8 #-} +{-# SPECIALIZE INLINE pixelMap :: (PixelRGB8 -> PixelYCbCr8) -> Image PixelRGB8 -> Image PixelYCbCr8 #-} +{-# SPECIALIZE INLINE pixelMap :: (PixelRGB8 -> PixelRGB8) -> Image PixelRGB8 -> Image PixelRGB8 #-} +{-# SPECIALIZE INLINE pixelMap :: (PixelRGB8 -> PixelRGBA8) -> Image PixelRGB8 -> Image PixelRGBA8 #-} +{-# SPECIALIZE INLINE pixelMap :: (PixelRGBA8 -> PixelRGBA8) -> Image PixelRGBA8 -> Image PixelRGBA8 #-} +{-# SPECIALIZE INLINE pixelMap :: (Pixel8 -> PixelRGB8) -> Image Pixel8 -> Image PixelRGB8 #-} +pixelMap f Image { imageWidth = w, imageHeight = h, imageData = vec } = + Image w h pixels + where sourceComponentCount = componentCount (undefined :: a) + destComponentCount = componentCount (undefined :: b) + + pixels = runST $ do + newArr <- M.new (w * h * destComponentCount) + let lineMapper _ _ y | y >= h = return () + lineMapper readIdxLine writeIdxLine y = colMapper readIdxLine writeIdxLine 0 + where colMapper readIdx writeIdx x + | x >= w = lineMapper readIdx writeIdx $ y + 1 + | otherwise = do + unsafeWritePixel newArr writeIdx . f $ unsafePixelAt vec readIdx + colMapper (readIdx + sourceComponentCount) + (writeIdx + destComponentCount) + (x + 1) + lineMapper 0 0 0 + + -- unsafeFreeze avoids making a second copy and it will be + -- safe because newArray can't be referenced as a mutable array + -- outside of this where block + V.unsafeFreeze newArr + +-- | Just like `pixelMap` only the function takes the pixel coordinates as +-- additional parameters. +pixelMapXY :: forall a b. (Pixel a, Pixel b) + => (Int -> Int -> a -> b) -> Image a -> Image b +{-# SPECIALIZE INLINE pixelMapXY :: (Int -> Int -> PixelYCbCr8 -> PixelRGB8) + -> Image PixelYCbCr8 -> Image PixelRGB8 #-} +{-# SPECIALIZE INLINE pixelMapXY :: (Int -> Int -> PixelRGB8 -> PixelYCbCr8) + -> Image PixelRGB8 -> Image PixelYCbCr8 #-} +{-# SPECIALIZE INLINE pixelMapXY :: (Int -> Int -> PixelRGB8 -> PixelRGB8) + -> Image PixelRGB8 -> Image PixelRGB8 #-} +{-# SPECIALIZE INLINE pixelMapXY :: (Int -> Int -> PixelRGB8 -> PixelRGBA8) + -> Image PixelRGB8 -> Image PixelRGBA8 #-} +{-# SPECIALIZE INLINE pixelMapXY :: (Int -> Int -> PixelRGBA8 -> PixelRGBA8) + -> Image PixelRGBA8 -> Image PixelRGBA8 #-} +{-# SPECIALIZE INLINE pixelMapXY :: (Int -> Int -> Pixel8 -> PixelRGB8) + -> Image Pixel8 -> Image PixelRGB8 #-} +pixelMapXY f Image { imageWidth = w, imageHeight = h, imageData = vec } = + Image w h pixels + where sourceComponentCount = componentCount (undefined :: a) + destComponentCount = componentCount (undefined :: b) + + pixels = runST $ do + newArr <- M.new (w * h * destComponentCount) + let lineMapper _ _ y | y >= h = return () + lineMapper readIdxLine writeIdxLine y = colMapper readIdxLine writeIdxLine 0 + where colMapper readIdx writeIdx x + | x >= w = lineMapper readIdx writeIdx $ y + 1 + | otherwise = do + unsafeWritePixel newArr writeIdx . f x y $ unsafePixelAt vec readIdx + colMapper (readIdx + sourceComponentCount) + (writeIdx + destComponentCount) + (x + 1) + lineMapper 0 0 0 + + -- unsafeFreeze avoids making a second copy and it will be + -- safe because newArray can't be referenced as a mutable array + -- outside of this where block + V.unsafeFreeze newArr + +-- | Combine, pixel by pixel and component by component +-- the values of 3 different images. Usage example: +-- +-- > averageBrightNess c1 c2 c3 = clamp $ toInt c1 + toInt c2 + toInt c3 +-- > where clamp = fromIntegral . min 0 . max 255 +-- > toInt :: a -> Int +-- > toInt = fromIntegral +-- > ziPixelComponent3 averageBrightNess img1 img2 img3 +-- +zipPixelComponent3 + :: forall px. ( V.Storable (PixelBaseComponent px)) + => (PixelBaseComponent px -> PixelBaseComponent px -> PixelBaseComponent px + -> PixelBaseComponent px) + -> Image px -> Image px -> Image px -> Image px +{-# INLINE zipPixelComponent3 #-} +zipPixelComponent3 f i1@(Image { imageWidth = w, imageHeight = h }) i2 i3 + | not isDimensionEqual = error "Different image size zipPairwisePixelComponent" + | otherwise = Image { imageWidth = w + , imageHeight = h + , imageData = V.zipWith3 f data1 data2 data3 + } + where data1 = imageData i1 + data2 = imageData i2 + data3 = imageData i3 + + isDimensionEqual = + w == imageWidth i2 && w == imageWidth i3 && + h == imageHeight i2 && h == imageHeight i3 + +-- | Helper class to help extract a luma plane out +-- of an image or a pixel +class (Pixel a, Pixel (PixelBaseComponent a)) => LumaPlaneExtractable a where + -- | Compute the luminance part of a pixel + computeLuma :: a -> (PixelBaseComponent a) + + -- | Extract a luma plane out of an image. This + -- method is in the typeclass to help performant + -- implementation. + -- + -- > jpegToGrayScale :: FilePath -> FilePath -> IO () + -- > jpegToGrayScale source dest + extractLumaPlane :: Image a -> Image (PixelBaseComponent a) + extractLumaPlane = pixelMap computeLuma + +instance LumaPlaneExtractable Pixel8 where + {-# INLINE computeLuma #-} + computeLuma = id + extractLumaPlane = id + +instance LumaPlaneExtractable Pixel16 where + {-# INLINE computeLuma #-} + computeLuma = id + extractLumaPlane = id + +instance LumaPlaneExtractable Pixel32 where + {-# INLINE computeLuma #-} + computeLuma = id + extractLumaPlane = id + +instance LumaPlaneExtractable PixelF where + {-# INLINE computeLuma #-} + computeLuma = id + extractLumaPlane = id + +instance LumaPlaneExtractable PixelRGBF where + {-# INLINE computeLuma #-} + computeLuma (PixelRGBF r g b) = + 0.3 * r + 0.59 * g + 0.11 * b + +instance LumaPlaneExtractable PixelRGBA8 where + {-# INLINE computeLuma #-} + computeLuma (PixelRGBA8 r g b _) = floor $ 0.3 * toRational r + + 0.59 * toRational g + + 0.11 * toRational b + +instance LumaPlaneExtractable PixelYCbCr8 where + {-# INLINE computeLuma #-} + computeLuma (PixelYCbCr8 y _ _) = y + extractLumaPlane = extractComponent PlaneLuma + +-- | Free promotion for identic pixel types +instance (Pixel a) => ColorConvertible a a where + {-# INLINE promotePixel #-} + promotePixel = id + + {-# INLINE promoteImage #-} + promoteImage = id + +{-# INLINE (.!!!.) #-} +(.!!!.) :: (PrimMonad m, Storable a) => M.STVector (PrimState m) a -> Int -> m a +(.!!!.) = M.read -- unsafeRead + +{-# INLINE (.<-.) #-} +(.<-.) :: (PrimMonad m, Storable a) => M.STVector (PrimState m) a -> Int -> a -> m () +(.<-.) = M.write -- unsafeWrite + +-------------------------------------------------- +---- Pixel8 instances +-------------------------------------------------- +instance Pixel Pixel8 where + type PixelBaseComponent Pixel8 = Word8 + + {-# INLINE mixWith #-} + mixWith f = f 0 + + {-# INLINE colorMap #-} + colorMap f = f + + componentCount _ = 1 + pixelAt (Image { imageWidth = w, imageData = arr }) x y = arr ! (x + y * w) + + readPixel image@(MutableImage { mutableImageData = arr }) x y = + arr `M.read` mutablePixelBaseIndex image x y + + writePixel image@(MutableImage { mutableImageData = arr }) x y = + arr `M.write` mutablePixelBaseIndex image x y + + unsafePixelAt = V.unsafeIndex + unsafeReadPixel = M.unsafeRead + unsafeWritePixel = M.unsafeWrite + +instance ColorConvertible Pixel8 PixelYA8 where + {-# INLINE promotePixel #-} + promotePixel c = PixelYA8 c 255 + +instance ColorConvertible Pixel8 PixelF where + {-# INLINE promotePixel #-} + promotePixel c = fromIntegral c / 255.0 + +instance ColorConvertible Pixel8 Pixel16 where + {-# INLINE promotePixel #-} + promotePixel c = fromIntegral c `unsafeShiftL` 8 + +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 + +-------------------------------------------------- +---- Pixel16 instances +-------------------------------------------------- +instance Pixel Pixel16 where + type PixelBaseComponent Pixel16 = Word16 + + {-# INLINE mixWith #-} + mixWith f = f 0 + + {-# INLINE colorMap #-} + colorMap f = f + + componentCount _ = 1 + pixelAt (Image { imageWidth = w, imageData = arr }) x y = arr ! (x + y * w) + + readPixel image@(MutableImage { mutableImageData = arr }) x y = + arr `M.read` mutablePixelBaseIndex image x y + + writePixel image@(MutableImage { mutableImageData = arr }) x y = + arr `M.write` mutablePixelBaseIndex image x y + + unsafePixelAt = V.unsafeIndex + unsafeReadPixel = M.unsafeRead + unsafeWritePixel = M.unsafeWrite + +instance ColorConvertible Pixel16 PixelYA16 where + {-# INLINE promotePixel #-} + promotePixel c = PixelYA16 c maxBound + +instance ColorConvertible Pixel16 PixelRGB16 where + {-# INLINE promotePixel #-} + promotePixel c = PixelRGB16 c c c + +instance ColorConvertible Pixel16 PixelRGBA16 where + {-# INLINE promotePixel #-} + promotePixel c = PixelRGBA16 c c c maxBound + +-------------------------------------------------- +---- Pixel32 instances +-------------------------------------------------- +instance Pixel Pixel32 where + type PixelBaseComponent Pixel32 = Word32 + + {-# INLINE mixWith #-} + mixWith f = f 0 + + {-# INLINE colorMap #-} + colorMap f = f + + componentCount _ = 1 + pixelAt (Image { imageWidth = w, imageData = arr }) x y = arr ! (x + y * w) + + readPixel image@(MutableImage { mutableImageData = arr }) x y = + arr `M.read` mutablePixelBaseIndex image x y + + writePixel image@(MutableImage { mutableImageData = arr }) x y = + arr `M.write` mutablePixelBaseIndex image x y + + unsafePixelAt = V.unsafeIndex + unsafeReadPixel = M.unsafeRead + unsafeWritePixel = M.unsafeWrite + +-------------------------------------------------- +---- PixelF instances +-------------------------------------------------- +instance Pixel PixelF where + type PixelBaseComponent PixelF = Float + + {-# INLINE mixWith #-} + mixWith f = f 0 + + {-# INLINE colorMap #-} + colorMap f = f + componentCount _ = 1 + pixelAt (Image { imageWidth = w, imageData = arr }) x y = arr ! (x + y * w) + + readPixel image@(MutableImage { mutableImageData = arr }) x y = + arr `M.read` mutablePixelBaseIndex image x y + + writePixel image@(MutableImage { mutableImageData = arr }) x y = + arr `M.write` mutablePixelBaseIndex image x y + + unsafePixelAt = V.unsafeIndex + unsafeReadPixel = M.unsafeRead + unsafeWritePixel = M.unsafeWrite + +instance ColorConvertible PixelF PixelRGBF where + {-# INLINE promotePixel #-} + promotePixel c = PixelRGBF c c c-- (c / 0.3) (c / 0.59) (c / 0.11) + +-------------------------------------------------- +---- PixelYA8 instances +-------------------------------------------------- +instance Pixel PixelYA8 where + type PixelBaseComponent PixelYA8 = Word8 + + {-# INLINE mixWith #-} + mixWith f (PixelYA8 ya aa) (PixelYA8 yb ab) = + PixelYA8 (f 0 ya yb) (f 1 aa ab) + + + {-# INLINE colorMap #-} + colorMap f (PixelYA8 y a) = PixelYA8 (f y) (f a) + 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 + + unsafePixelAt v idx = + PixelYA8 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) + unsafeReadPixel vec idx = + PixelYA8 `liftM` M.unsafeRead vec idx `ap` M.unsafeRead vec (idx + 1) + unsafeWritePixel v idx (PixelYA8 y a) = + M.unsafeWrite v idx y >> M.unsafeWrite v (idx + 1) a + +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 + +instance ColorPlane PixelYA8 PlaneLuma where + toComponentIndex _ _ = 0 + +instance ColorPlane PixelYA8 PlaneAlpha where + toComponentIndex _ _ = 1 + +instance TransparentPixel PixelYA8 Pixel8 where + {-# INLINE dropTransparency #-} + dropTransparency (PixelYA8 y _) = y + {-# INLINE getTransparency #-} + getTransparency (PixelYA8 _ a) = a + +instance LumaPlaneExtractable PixelYA8 where + {-# INLINE computeLuma #-} + computeLuma (PixelYA8 y _) = y + extractLumaPlane = extractComponent PlaneLuma + +-------------------------------------------------- +---- PixelYA16 instances +-------------------------------------------------- +instance Pixel PixelYA16 where + type PixelBaseComponent PixelYA16 = Word16 + + {-# INLINE mixWith #-} + mixWith f (PixelYA16 ya aa) (PixelYA16 yb ab) = + PixelYA16 (f 0 ya yb) (f 1 aa ab) + + {-# INLINE colorMap #-} + colorMap f (PixelYA16 y a) = PixelYA16 (f y) (f a) + componentCount _ = 2 + pixelAt image@(Image { imageData = arr }) x y = PixelYA16 (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 $ PixelYA16 yv av + where baseIdx = mutablePixelBaseIndex image x y + + writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelYA16 yv av) = do + let baseIdx = mutablePixelBaseIndex image x y + (arr .<-. (baseIdx + 0)) yv + (arr .<-. (baseIdx + 1)) av + + unsafePixelAt v idx = + PixelYA16 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) + unsafeReadPixel vec idx = + PixelYA16 `liftM` M.unsafeRead vec idx `ap` M.unsafeRead vec (idx + 1) + unsafeWritePixel v idx (PixelYA16 y a) = + M.unsafeWrite v idx y >> M.unsafeWrite v (idx + 1) a + +instance ColorConvertible PixelYA16 PixelRGBA16 where + {-# INLINE promotePixel #-} + promotePixel (PixelYA16 y a) = PixelRGBA16 y y y a + +instance ColorPlane PixelYA16 PlaneLuma where + toComponentIndex _ _ = 0 + +instance ColorPlane PixelYA16 PlaneAlpha where + toComponentIndex _ _ = 1 + +instance TransparentPixel PixelYA16 Pixel16 where + {-# INLINE dropTransparency #-} + dropTransparency (PixelYA16 y _) = y + {-# INLINE getTransparency #-} + getTransparency (PixelYA16 _ a) = a + +-------------------------------------------------- +---- PixelRGBF instances +-------------------------------------------------- +instance Pixel PixelRGBF where + type PixelBaseComponent PixelRGBF = PixelF + + {-# INLINE mixWith #-} + mixWith f (PixelRGBF ra ga ba) (PixelRGBF rb gb bb) = + PixelRGBF (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) + + {-# INLINE colorMap #-} + colorMap f (PixelRGBF r g b) = PixelRGBF (f r) (f g) (f b) + + componentCount _ = 3 + + pixelAt image@(Image { imageData = arr }) x y = PixelRGBF (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 $ PixelRGBF rv gv bv + where baseIdx = mutablePixelBaseIndex image x y + + writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelRGBF rv gv bv) = do + let baseIdx = mutablePixelBaseIndex image x y + (arr .<-. (baseIdx + 0)) rv + (arr .<-. (baseIdx + 1)) gv + (arr .<-. (baseIdx + 2)) bv + + unsafePixelAt v idx = + PixelRGBF (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) + unsafeReadPixel vec idx = + PixelRGBF `liftM` M.unsafeRead vec idx + `ap` M.unsafeRead vec (idx + 1) + `ap` M.unsafeRead vec (idx + 2) + unsafeWritePixel v idx (PixelRGBF r g b) = + M.unsafeWrite v idx r >> M.unsafeWrite v (idx + 1) g + >> M.unsafeWrite v (idx + 2) b + +instance ColorPlane PixelRGBF PlaneRed where + toComponentIndex _ _ = 0 + +instance ColorPlane PixelRGBF PlaneGreen where + toComponentIndex _ _ = 1 + +instance ColorPlane PixelRGBF PlaneBlue where + toComponentIndex _ _ = 2 + +-------------------------------------------------- +---- PixelRGB16 instances +-------------------------------------------------- +instance Pixel PixelRGB16 where + type PixelBaseComponent PixelRGB16 = Pixel16 + + {-# INLINE mixWith #-} + mixWith f (PixelRGB16 ra ga ba) (PixelRGB16 rb gb bb) = + PixelRGB16 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) + + {-# INLINE colorMap #-} + colorMap f (PixelRGB16 r g b) = PixelRGB16 (f r) (f g) (f b) + + componentCount _ = 3 + + pixelAt image@(Image { imageData = arr }) x y = PixelRGB16 (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 $ PixelRGB16 rv gv bv + where baseIdx = mutablePixelBaseIndex image x y + + writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelRGB16 rv gv bv) = do + let baseIdx = mutablePixelBaseIndex image x y + (arr .<-. (baseIdx + 0)) rv + (arr .<-. (baseIdx + 1)) gv + (arr .<-. (baseIdx + 2)) bv + + unsafePixelAt v idx = + PixelRGB16 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) + unsafeReadPixel vec idx = + PixelRGB16 `liftM` M.unsafeRead vec idx + `ap` M.unsafeRead vec (idx + 1) + `ap` M.unsafeRead vec (idx + 2) + unsafeWritePixel v idx (PixelRGB16 r g b) = + M.unsafeWrite v idx r >> M.unsafeWrite v (idx + 1) g + >> M.unsafeWrite v (idx + 2) b + +instance ColorPlane PixelRGB16 PlaneRed where + toComponentIndex _ _ = 0 + +instance ColorPlane PixelRGB16 PlaneGreen where + toComponentIndex _ _ = 1 + +instance ColorPlane PixelRGB16 PlaneBlue where + toComponentIndex _ _ = 2 + +instance ColorSpaceConvertible PixelRGB16 PixelCMYK16 where + {-# INLINE convertPixel #-} + convertPixel (PixelRGB16 r g b) = integralRGBToCMYK PixelCMYK16 (r, g, b) + +instance ColorConvertible PixelRGB16 PixelRGBA16 where + {-# INLINE promotePixel #-} + promotePixel (PixelRGB16 r g b) = PixelRGBA16 r g b maxBound + +instance LumaPlaneExtractable PixelRGB16 where + {-# INLINE computeLuma #-} + computeLuma (PixelRGB16 r g b) = floor $ 0.3 * toRational r + + 0.59 * toRational g + + 0.11 * toRational b +-------------------------------------------------- +---- PixelRGB8 instances +-------------------------------------------------- +instance Pixel PixelRGB8 where + type PixelBaseComponent PixelRGB8 = Word8 + + {-# INLINE mixWith #-} + mixWith f (PixelRGB8 ra ga ba) (PixelRGB8 rb gb bb) = + PixelRGB8 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) + + {-# INLINE colorMap #-} + colorMap f (PixelRGB8 r g b) = PixelRGB8 (f r) (f g) (f b) + + componentCount _ = 3 + + 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 + + unsafePixelAt v idx = + PixelRGB8 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) + unsafeReadPixel vec idx = + PixelRGB8 `liftM` M.unsafeRead vec idx + `ap` M.unsafeRead vec (idx + 1) + `ap` M.unsafeRead vec (idx + 2) + unsafeWritePixel v idx (PixelRGB8 r g b) = + M.unsafeWrite v idx r >> M.unsafeWrite v (idx + 1) g + >> M.unsafeWrite v (idx + 2) b + +instance ColorConvertible PixelRGB8 PixelRGBA8 where + {-# INLINE promotePixel #-} + promotePixel (PixelRGB8 r g b) = PixelRGBA8 r g b maxBound + +instance ColorConvertible PixelRGB8 PixelRGBF where + {-# INLINE promotePixel #-} + promotePixel (PixelRGB8 r g b) = PixelRGBF (toF r) (toF g) (toF b) + where toF v = fromIntegral v / 255.0 + +instance ColorPlane PixelRGB8 PlaneRed where + toComponentIndex _ _ = 0 + +instance ColorPlane PixelRGB8 PlaneGreen where + toComponentIndex _ _ = 1 + +instance ColorPlane PixelRGB8 PlaneBlue where + toComponentIndex _ _ = 2 + +instance LumaPlaneExtractable PixelRGB8 where + {-# INLINE computeLuma #-} + computeLuma (PixelRGB8 r g b) = floor $ 0.3 * toRational r + + 0.59 * toRational g + + 0.11 * toRational b + +-------------------------------------------------- +---- PixelRGBA8 instances +-------------------------------------------------- +instance Pixel PixelRGBA8 where + type PixelBaseComponent PixelRGBA8 = Word8 + + {-# INLINE mixWith #-} + mixWith f (PixelRGBA8 ra ga ba aa) (PixelRGBA8 rb gb bb ab) = + PixelRGBA8 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) (f 3 aa ab) + + {-# INLINE colorMap #-} + colorMap f (PixelRGBA8 r g b a) = PixelRGBA8 (f r) (f g) (f b) (f a) + + 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 + + unsafePixelAt v idx = + PixelRGBA8 (V.unsafeIndex v idx) + (V.unsafeIndex v $ idx + 1) + (V.unsafeIndex v $ idx + 2) + (V.unsafeIndex v $ idx + 3) + unsafeReadPixel vec idx = + PixelRGBA8 `liftM` M.unsafeRead vec idx + `ap` M.unsafeRead vec (idx + 1) + `ap` M.unsafeRead vec (idx + 2) + `ap` M.unsafeRead vec (idx + 3) + unsafeWritePixel v idx (PixelRGBA8 r g b a) = + M.unsafeWrite v idx r >> M.unsafeWrite v (idx + 1) g + >> M.unsafeWrite v (idx + 2) b + >> M.unsafeWrite v (idx + 3) a + +instance ColorPlane PixelRGBA8 PlaneRed where + toComponentIndex _ _ = 0 + +instance ColorPlane PixelRGBA8 PlaneGreen where + toComponentIndex _ _ = 1 + +instance ColorPlane PixelRGBA8 PlaneBlue where + toComponentIndex _ _ = 2 + +instance ColorPlane PixelRGBA8 PlaneAlpha where + toComponentIndex _ _ = 3 + +-------------------------------------------------- +---- PixelRGBA16 instances +-------------------------------------------------- +instance Pixel PixelRGBA16 where + type PixelBaseComponent PixelRGBA16 = Pixel16 + + {-# INLINE mixWith #-} + mixWith f (PixelRGBA16 ra ga ba aa) (PixelRGBA16 rb gb bb ab) = + PixelRGBA16 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) (f 3 aa ab) + + {-# INLINE colorMap #-} + colorMap f (PixelRGBA16 r g b a) = PixelRGBA16 (f r) (f g) (f b) (f a) + + componentCount _ = 4 + + pixelAt image@(Image { imageData = arr }) x y = + PixelRGBA16 (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 $ PixelRGBA16 rv gv bv av + where baseIdx = mutablePixelBaseIndex image x y + + writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelRGBA16 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 + + unsafePixelAt v idx = + PixelRGBA16 (V.unsafeIndex v idx) + (V.unsafeIndex v $ idx + 1) + (V.unsafeIndex v $ idx + 2) + (V.unsafeIndex v $ idx + 3) + unsafeReadPixel vec idx = + PixelRGBA16 `liftM` M.unsafeRead vec idx + `ap` M.unsafeRead vec (idx + 1) + `ap` M.unsafeRead vec (idx + 2) + `ap` M.unsafeRead vec (idx + 3) + unsafeWritePixel v idx (PixelRGBA16 r g b a) = + M.unsafeWrite v idx r >> M.unsafeWrite v (idx + 1) g + >> M.unsafeWrite v (idx + 2) b + >> M.unsafeWrite v (idx + 3) a + + +instance TransparentPixel PixelRGBA16 PixelRGB16 where + {-# INLINE dropTransparency #-} + dropTransparency (PixelRGBA16 r g b _) = PixelRGB16 r g b + {-# INLINE getTransparency #-} + getTransparency (PixelRGBA16 _ _ _ a) = a + +instance ColorPlane PixelRGBA16 PlaneRed where + toComponentIndex _ _ = 0 + +instance ColorPlane PixelRGBA16 PlaneGreen where + toComponentIndex _ _ = 1 + +instance ColorPlane PixelRGBA16 PlaneBlue where + toComponentIndex _ _ = 2 + +instance ColorPlane PixelRGBA16 PlaneAlpha where + toComponentIndex _ _ = 3 + +-------------------------------------------------- +---- PixelYCbCr8 instances +-------------------------------------------------- +instance Pixel PixelYCbCr8 where + type PixelBaseComponent PixelYCbCr8 = Word8 + + {-# INLINE mixWith #-} + mixWith f (PixelYCbCr8 ya cba cra) (PixelYCbCr8 yb cbb crb) = + PixelYCbCr8 (f 0 ya yb) (f 1 cba cbb) (f 2 cra crb) + + {-# INLINE colorMap #-} + colorMap f (PixelYCbCr8 y cb cr) = PixelYCbCr8 (f y) (f cb) (f cr) + 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 + + unsafePixelAt v idx = + PixelYCbCr8 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) + unsafeReadPixel vec idx = + PixelYCbCr8 `liftM` M.unsafeRead vec idx + `ap` M.unsafeRead vec (idx + 1) + `ap` M.unsafeRead vec (idx + 2) + unsafeWritePixel v idx (PixelYCbCr8 y cb cr) = + M.unsafeWrite v idx y >> M.unsafeWrite v (idx + 1) cb + >> M.unsafeWrite v (idx + 2) cr + +instance (Pixel a) => ColorSpaceConvertible a a where + convertPixel = id + convertImage = id + +scaleBits, oneHalf :: Int +scaleBits = 16 +oneHalf = 1 `unsafeShiftL` (scaleBits - 1) + +fix :: Float -> Int +fix x = floor $ x * fromIntegral ((1 :: Int) `unsafeShiftL` scaleBits) + 0.5 + + +rYTab, gYTab, bYTab, rCbTab, gCbTab, bCbTab, gCrTab, bCrTab :: V.Vector Int +rYTab = V.fromListN 256 [fix 0.29900 * i | i <- [0..255] ] +gYTab = V.fromListN 256 [fix 0.58700 * i | i <- [0..255] ] +bYTab = V.fromListN 256 [fix 0.11400 * i + oneHalf | i <- [0..255] ] +rCbTab = V.fromListN 256 [(- fix 0.16874) * i | i <- [0..255] ] +gCbTab = V.fromListN 256 [(- fix 0.33126) * i | i <- [0..255] ] +bCbTab = V.fromListN 256 [fix 0.5 * i + (128 `unsafeShiftL` scaleBits) + oneHalf - 1| i <- [0..255] ] +gCrTab = V.fromListN 256 [(- fix 0.41869) * i | i <- [0..255] ] +bCrTab = V.fromListN 256 [(- fix 0.08131) * i | i <- [0..255] ] + + +instance ColorSpaceConvertible PixelRGB8 PixelYCbCr8 where + {-# INLINE convertPixel #-} + convertPixel (PixelRGB8 r g b) = PixelYCbCr8 (fromIntegral y) (fromIntegral cb) (fromIntegral cr) + where ri = fromIntegral r + gi = fromIntegral g + bi = fromIntegral b + + y = (rYTab `V.unsafeIndex` ri + gYTab `V.unsafeIndex` gi + bYTab `V.unsafeIndex` bi) `unsafeShiftR` scaleBits + cb = (rCbTab `V.unsafeIndex` ri + gCbTab `V.unsafeIndex` gi + bCbTab `V.unsafeIndex` bi) `unsafeShiftR` scaleBits + cr = (bCbTab `V.unsafeIndex` ri + gCrTab `V.unsafeIndex` gi + bCrTab `V.unsafeIndex` bi) `unsafeShiftR` scaleBits + + convertImage Image { imageWidth = w, imageHeight = h, imageData = d } = Image w h newData + where maxi = w * h + + rY = fix 0.29900 + gY = fix 0.58700 + bY = fix 0.11400 + rCb = (- fix 0.16874) + gCb = (- fix 0.33126) + bCb = fix 0.5 + gCr = (- fix 0.41869) + bCr = (- fix 0.08131) + + newData = runST $ do + block <- M.new $ maxi * 3 + let traductor _ idx | idx >= maxi = return block + traductor readIdx idx = do + let ri = fromIntegral $ d `V.unsafeIndex` readIdx + gi = fromIntegral $ d `V.unsafeIndex` (readIdx + 1) + bi = fromIntegral $ d `V.unsafeIndex` (readIdx + 2) + + y = (rY * ri + gY * gi + bY * bi + oneHalf) `unsafeShiftR` scaleBits + cb = (rCb * ri + gCb * gi + bCb * bi + (128 `unsafeShiftL` scaleBits) + oneHalf - 1) `unsafeShiftR` scaleBits + cr = (bCb * ri + (128 `unsafeShiftL` scaleBits) + oneHalf - 1+ gCr * gi + bCr * bi) `unsafeShiftR` scaleBits + + (block `M.unsafeWrite` (readIdx + 0)) $ fromIntegral y + (block `M.unsafeWrite` (readIdx + 1)) $ fromIntegral cb + (block `M.unsafeWrite` (readIdx + 2)) $ fromIntegral cr + traductor (readIdx + 3) (idx + 1) + + traductor 0 0 >>= V.freeze + +crRTab, cbBTab, crGTab, cbGTab :: V.Vector Int +crRTab = V.fromListN 256 [(fix 1.40200 * x + oneHalf) `unsafeShiftR` scaleBits | x <- [-128 .. 127]] +cbBTab = V.fromListN 256 [(fix 1.77200 * x + oneHalf) `unsafeShiftR` scaleBits | x <- [-128 .. 127]] +crGTab = V.fromListN 256 [negate (fix 0.71414) * x | x <- [-128 .. 127]] +cbGTab = V.fromListN 256 [negate (fix 0.34414) * x + oneHalf | x <- [-128 .. 127]] + +instance ColorSpaceConvertible PixelYCbCr8 PixelRGB8 where + {-# INLINE convertPixel #-} + convertPixel (PixelYCbCr8 y cb cr) = PixelRGB8 (clampWord8 r) (clampWord8 g) (clampWord8 b) + where clampWord8 = fromIntegral . max 0 . min 255 + yi = fromIntegral y + cbi = fromIntegral cb + cri = fromIntegral cr + + r = yi + crRTab `V.unsafeIndex` cri + g = yi + (cbGTab `V.unsafeIndex` cbi + crGTab `V.unsafeIndex` cri) `unsafeShiftR` scaleBits + b = yi + cbBTab `V.unsafeIndex` cbi + + convertImage Image { imageWidth = w, imageHeight = h, imageData = d } = Image w h newData + where maxi = w * h + clampWord8 v | v < 0 = 0 + | v > 255 = 255 + | otherwise = fromIntegral v + + newData = runST $ do + block <- M.new $ maxi * 3 + let traductor _ idx | idx >= maxi = return block + traductor readIdx idx = do + let yi = fromIntegral $ d `V.unsafeIndex` readIdx + cbi = fromIntegral $ d `V.unsafeIndex` (readIdx + 1) + cri = fromIntegral $ d `V.unsafeIndex` (readIdx + 2) + + r = yi + crRTab `V.unsafeIndex` cri + g = yi + (cbGTab `V.unsafeIndex` cbi + crGTab `V.unsafeIndex` cri) `unsafeShiftR` scaleBits + b = yi + cbBTab `V.unsafeIndex` cbi + + (block `M.unsafeWrite` (readIdx + 0)) $ clampWord8 r + (block `M.unsafeWrite` (readIdx + 1)) $ clampWord8 g + (block `M.unsafeWrite` (readIdx + 2)) $ clampWord8 b + traductor (readIdx + 3) (idx + 1) + + traductor 0 0 >>= V.freeze + +instance ColorPlane PixelYCbCr8 PlaneLuma where + toComponentIndex _ _ = 0 + +instance ColorPlane PixelYCbCr8 PlaneCb where + toComponentIndex _ _ = 1 + +instance ColorPlane PixelYCbCr8 PlaneCr where + toComponentIndex _ _ = 2 + +-------------------------------------------------- +---- PixelCMYK8 instances +-------------------------------------------------- +instance Pixel PixelCMYK8 where + type PixelBaseComponent PixelCMYK8 = Word8 + + {-# INLINE mixWith #-} + mixWith f (PixelCMYK8 ca ma ya ka) (PixelCMYK8 cb mb yb kb) = + PixelCMYK8 (f 0 ca cb) (f 1 ma mb) (f 2 ya yb) (f 3 ka kb) + + {-# INLINE colorMap #-} + colorMap f (PixelCMYK8 c m y k) = PixelCMYK8 (f c) (f m) (f y) (f k) + + componentCount _ = 4 + + pixelAt image@(Image { imageData = arr }) x y = PixelCMYK8 (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 $ PixelCMYK8 rv gv bv av + where baseIdx = mutablePixelBaseIndex image x y + + writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelCMYK8 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 + + unsafePixelAt v idx = + PixelCMYK8 (V.unsafeIndex v idx) + (V.unsafeIndex v $ idx + 1) + (V.unsafeIndex v $ idx + 2) + (V.unsafeIndex v $ idx + 3) + unsafeReadPixel vec idx = + PixelCMYK8 `liftM` M.unsafeRead vec idx + `ap` M.unsafeRead vec (idx + 1) + `ap` M.unsafeRead vec (idx + 2) + `ap` M.unsafeRead vec (idx + 3) + unsafeWritePixel v idx (PixelCMYK8 r g b a) = + M.unsafeWrite v idx r >> M.unsafeWrite v (idx + 1) g + >> M.unsafeWrite v (idx + 2) b + >> M.unsafeWrite v (idx + 3) a + +instance ColorSpaceConvertible PixelCMYK8 PixelRGB8 where + convertPixel (PixelCMYK8 c m y k) = + PixelRGB8 (clampWord8 r) (clampWord8 g) (clampWord8 b) + where + clampWord8 = fromIntegral . (`unsafeShiftR` 8) + ik :: Int + ik = 255 - fromIntegral k + + r = (255 - fromIntegral c) * ik + g = (255 - fromIntegral m) * ik + b = (255 - fromIntegral y) * ik + + +{-# SPECIALIZE integralRGBToCMYK :: (Word8 -> Word8 -> Word8 -> Word8 -> b) + -> (Word8, Word8, Word8) -> b #-} +{-# SPECIALIZE integralRGBToCMYK :: (Word16 -> Word16 -> Word16 -> Word16 -> b) + -> (Word16, Word16, Word16) -> b #-} +integralRGBToCMYK :: (Bounded a, Integral a) + => (a -> a -> a -> a -> b) -- ^ Pixel building function + -> (a, a, a) -- ^ RGB sample + -> b -- ^ Resulting sample +integralRGBToCMYK build (r, g, b) = + build (clamp c) (clamp m) (clamp y) (fromIntegral kInt) + where maxi = maxBound + + ir = fromIntegral $ maxi - r :: Int + ig = fromIntegral $ maxi - g + ib = fromIntegral $ maxi - b + + kInt = minimum [ir, ig, ib] + ik = fromIntegral maxi - kInt + + c = (ir - kInt) `div` ik + m = (ig - kInt) `div` ik + y = (ib - kInt) `div` ik + + clamp = fromIntegral . (max 0) + +instance ColorSpaceConvertible PixelRGB8 PixelCMYK8 where + convertPixel (PixelRGB8 r g b) = integralRGBToCMYK PixelCMYK8 (r, g, b) + +instance ColorPlane PixelCMYK8 PlaneCyan where + toComponentIndex _ _ = 0 + +instance ColorPlane PixelCMYK8 PlaneMagenta where + toComponentIndex _ _ = 1 + +instance ColorPlane PixelCMYK8 PlaneYellow where + toComponentIndex _ _ = 2 + +instance ColorPlane PixelCMYK8 PlaneBlack where + toComponentIndex _ _ = 3 + +-------------------------------------------------- +---- PixelCMYK16 instances +-------------------------------------------------- +instance Pixel PixelCMYK16 where + type PixelBaseComponent PixelCMYK16 = Word16 + + {-# INLINE mixWith #-} + mixWith f (PixelCMYK16 ca ma ya ka) (PixelCMYK16 cb mb yb kb) = + PixelCMYK16 (f 0 ca cb) (f 1 ma mb) (f 2 ya yb) (f 3 ka kb) + + {-# INLINE colorMap #-} + colorMap f (PixelCMYK16 c m y k) = PixelCMYK16 (f c) (f m) (f y) (f k) + + componentCount _ = 4 + + pixelAt image@(Image { imageData = arr }) x y = PixelCMYK16 (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 $ PixelCMYK16 rv gv bv av + where baseIdx = mutablePixelBaseIndex image x y + + writePixel image@(MutableImage { mutableImageData = arr }) x y (PixelCMYK16 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 + + unsafePixelAt v idx = + PixelCMYK16 (V.unsafeIndex v idx) + (V.unsafeIndex v $ idx + 1) + (V.unsafeIndex v $ idx + 2) + (V.unsafeIndex v $ idx + 3) + unsafeReadPixel vec idx = + PixelCMYK16 `liftM` M.unsafeRead vec idx + `ap` M.unsafeRead vec (idx + 1) + `ap` M.unsafeRead vec (idx + 2) + `ap` M.unsafeRead vec (idx + 3) + unsafeWritePixel v idx (PixelCMYK16 r g b a) = + M.unsafeWrite v idx r >> M.unsafeWrite v (idx + 1) g + >> M.unsafeWrite v (idx + 2) b + >> M.unsafeWrite v (idx + 3) a + +instance ColorSpaceConvertible PixelCMYK16 PixelRGB16 where + convertPixel (PixelCMYK16 c m y k) = + PixelRGB16 (clampWord16 r) (clampWord16 g) (clampWord16 b) + where + clampWord16 = fromIntegral . (`unsafeShiftR` 16) + ik :: Int + ik = 65535 - fromIntegral k + + r = (65535 - fromIntegral c) * ik + g = (65535 - fromIntegral m) * ik + b = (65535 - fromIntegral y) * ik + +instance ColorPlane PixelCMYK16 PlaneCyan where + toComponentIndex _ _ = 0 + +instance ColorPlane PixelCMYK16 PlaneMagenta where + toComponentIndex _ _ = 1 + +instance ColorPlane PixelCMYK16 PlaneYellow where + toComponentIndex _ _ = 2 + +instance ColorPlane PixelCMYK16 PlaneBlack where + toComponentIndex _ _ = 3 + +-- | Perform a gamma correction for an image with HDR pixels. +gammaCorrection :: PixelF -- ^ Gamma value, should be between 0.5 and 3.0 + -> Image PixelRGBF -- ^ Image to treat. + -> Image PixelRGBF +gammaCorrection gammaVal = pixelMap gammaCorrector + where gammaExponent = 1.0 / gammaVal + fixVal v = v ** gammaExponent + gammaCorrector (PixelRGBF r g b) = + PixelRGBF (fixVal r) (fixVal g) (fixVal b) + +-- | Perform a tone mapping operation on an High dynamic range image. +toneMapping :: PixelF -- ^ Exposure parameter + -> Image PixelRGBF -- ^ Image to treat. + -> Image PixelRGBF +toneMapping exposure img = Image (imageWidth img) (imageHeight img) scaledData + where coeff = exposure * (exposure / maxBrightness + 1.0) / (exposure + 1.0); + maxBrightness = pixelFold (\luma _ _ px -> max luma $ computeLuma px) 0 img + scaledData = V.map (* coeff) $ imageData img +
+ src/Codec/Picture/VectorByteConversion.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +module Codec.Picture.VectorByteConversion( blitVector + , toByteString + , imageFromUnsafePtr ) where + +import Data.Word( Word8 ) +import Data.Vector.Storable( Vector, unsafeToForeignPtr, unsafeFromForeignPtr0 ) +import Foreign.ForeignPtr.Safe( ForeignPtr, castForeignPtr ) +import Foreign.Storable( Storable, sizeOf ) + +import qualified Data.ByteString as B +import qualified Data.ByteString.Internal as S + +import Codec.Picture.Types + +blitVector :: Vector Word8 -> Int -> Int -> B.ByteString +blitVector vec atIndex blitSize = S.PS ptr (offset + atIndex) blitSize + where (ptr, offset, _length) = unsafeToForeignPtr vec + +toByteString :: forall a. (Storable a) => Vector a -> B.ByteString +toByteString vec = S.PS (castForeignPtr ptr) offset (len * size) + where (ptr, offset, len) = unsafeToForeignPtr vec + size = sizeOf (undefined :: a) + +imageFromUnsafePtr :: forall px + . (Pixel px, (PixelBaseComponent px) ~ Word8) + => Int -> Int -> ForeignPtr Word8 -> Image px +imageFromUnsafePtr width height ptr = + Image width height $ unsafeFromForeignPtr0 ptr size + where compCount = componentCount (undefined :: px) + size = width * height * compCount +