JuicyPixels 3.2.6.4 → 3.2.7
raw patch · 12 files changed
+742/−362 lines, 12 filesdep ~basedep ~binaryPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base, binary
API changes (from Hackage documentation)
+ Codec.Picture: convertRGB8 :: DynamicImage -> Image PixelRGB8
+ Codec.Picture: convertRGBA8 :: DynamicImage -> Image PixelRGBA8
+ Codec.Picture: instance Codec.Picture.Decimable Codec.Picture.Types.Pixel16 Codec.Picture.Types.Pixel8
+ Codec.Picture: instance Codec.Picture.Decimable Codec.Picture.Types.PixelCMYK16 Codec.Picture.Types.PixelCMYK8
+ Codec.Picture: instance Codec.Picture.Decimable Codec.Picture.Types.PixelF Codec.Picture.Types.Pixel8
+ Codec.Picture: instance Codec.Picture.Decimable Codec.Picture.Types.PixelRGB16 Codec.Picture.Types.PixelRGB8
+ Codec.Picture: instance Codec.Picture.Decimable Codec.Picture.Types.PixelRGBA16 Codec.Picture.Types.PixelRGBA8
+ Codec.Picture: instance Codec.Picture.Decimable Codec.Picture.Types.PixelRGBF Codec.Picture.Types.PixelRGB8
+ Codec.Picture: instance Codec.Picture.Decimable Codec.Picture.Types.PixelYA16 Codec.Picture.Types.PixelYA8
+ Codec.Picture.Jpg: class (Pixel px, PixelBaseComponent px ~ Word8) => JpgEncodable px where additionalBlocks _ = [] imageHuffmanTables _ = defaultHuffmanTables quantTableSpec _ qual = take 1 $ zigzaggedQuantificationSpec qual maximumSubSamplingOf _ = 1
+ Codec.Picture.Jpg: encodeDirectJpegAtQualityWithMetadata :: (JpgEncodable px) => Word8 -> Metadatas -> Image px -> ByteString
+ Codec.Picture.Jpg: instance Codec.Picture.Jpg.JpgEncodable Codec.Picture.Types.Pixel8
+ Codec.Picture.Jpg: instance Codec.Picture.Jpg.JpgEncodable Codec.Picture.Types.PixelCMYK8
+ Codec.Picture.Jpg: instance Codec.Picture.Jpg.JpgEncodable Codec.Picture.Types.PixelRGB8
+ Codec.Picture.Jpg: instance Codec.Picture.Jpg.JpgEncodable Codec.Picture.Types.PixelYCbCr8
Files
- JuicyPixels.cabal +3/−4
- README.md +4/−1
- changelog +14/−1
- src/Codec/Picture.hs +90/−1
- src/Codec/Picture/BitWriter.hs +47/−16
- src/Codec/Picture/Bitmap.hs +67/−14
- src/Codec/Picture/Gif/LZW.hs +194/−191
- src/Codec/Picture/Jpg.hs +293/−110
- src/Codec/Picture/Jpg/Common.hs +2/−9
- src/Codec/Picture/Jpg/Types.hs +2/−1
- src/Codec/Picture/Saving.hs +11/−4
- src/Codec/Picture/Types.hs +15/−10
JuicyPixels.cabal view
@@ -1,5 +1,5 @@ Name: JuicyPixels-Version: 3.2.6.4+Version: 3.2.7 Synopsis: Picture loading/serialization (in png, jpeg, bitmap, gif, tga, tiff and radiance) Description: <<data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADABAMAAACg8nE0AAAAElBMVEUAAABJqDSTWEL/qyb///8AAABH/1GTAAAAAXRSTlMAQObYZgAAAN5JREFUeF7s1sEJgFAQxFBbsAV72v5bEVYWPwT/XDxmCsi7zvHXavYREBDI3XP2GgICqBBYuwIC+/rVayPUAyAg0HvIXBcQoDFDGnUBgWQQ2Bx3AYFaRoBpAQHWb3bt2ARgGAiCYFFuwf3X5HA/McgGJWI2FdykCv4aBYzmKwDwvl6NVmUAAK2vlwEALK7fo88GANB6HQsAAAAAAAAA7P94AQCzswEAAAAAAAAAAAAAAAAAAICzh4UAO4zWAYBfRutHA4Bn5C69JhowAMGoBaMWDG0wCkbBKBgFo2AUAACPmegUST/IJAAAAABJRU5ErkJggg==>>@@ -28,7 +28,7 @@ Source-Repository this Type: git Location: git://github.com/Twinside/Juicy.Pixels.git- Tag: v3.2.6.4+ Tag: v3.2.7 Flag Mmap Description: Enable the file loading via mmap (memory map)@@ -52,11 +52,10 @@ Codec.Picture.ColorQuant Ghc-options: -O3 -Wall- Ghc-prof-options: -rtsopts -Wall -prof -auto-all Build-depends: base >= 4.5 && < 5, bytestring >= 0.9 && < 0.11, mtl >= 1.1 && < 2.3,- binary >= 0.5 && < 0.8,+ binary >= 0.5 && < 0.9, zlib >= 0.5.3.1 && < 0.7, transformers >= 0.2, vector >= 0.9 && < 0.12,
README.md view
@@ -52,6 +52,7 @@ - Bitmap (.bmp) (mainly used as a debug output format) * Reading + - 32bits (RGBA) images - 24bits (RGB) images - 8bits (greyscale & paletted) images @@ -62,10 +63,12 @@ * Metadata (reading/writing): DPI information - - Jpeg (.jpg, .jpeg) + - Jpeg (.jpg, .jpeg) * Reading normal and interlaced baseline DCT image - YCbCr (default) CMYK/YCbCrK/RGB colorspaces + * Writing non-interlaced JPG + - YCbCr (favored), Y, RGB & CMYK colorspaces * Metadata: - Reading and writing DpiX & DpiY from JFIF header.
changelog view
@@ -1,7 +1,20 @@ Change log ========== -v3.2.6.3 December 2015+v3.2.7 January 2016+-------------------+ * Addition: convertRGB8 and convertRGBA8 helper functions+ * Addition: new output colorspace for JPEG format: Y, RGB & CMYK+ * Addition: RGBA8 bitmap reading (thanks to mtolly)+ * Enhancement: Optimized JPG & Tiff reading (thanks to Calvin Beck)+ * Enhancement: INLINE SPECIALIZE for pixelMap (Pixel8 -> Pixel8) (thx to Calvin Beck)+ * Fix: GHC 8.0 compilation (thanks to phadej)++v3.2.6.5 December 2015+----------------------+ * Fix: Compilation on GHC 7.6/7.8++v3.2.6.4 December 2015 ---------------------- * Fix: previous broken bugfix.
src/Codec/Picture.hs view
@@ -1,7 +1,10 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TupleSections #-} +{-# LANGUAGE FlexibleInstances #-} -- | Main module for image import/export into various image formats. -- -- To use the library without thinking about it, look after 'decodeImage' and @@ -23,6 +26,10 @@ , generateFoldImage , withImage + -- * RGB helper functions + , convertRGB8 + , convertRGBA8 + -- * Lens compatibility , Traversal , imagePixels @@ -138,6 +145,7 @@ import Control.Applicative( (<$>) ) #endif +import Data.Bits( unsafeShiftR ) import Control.DeepSeq( NFData, deepseq ) import qualified Control.Exception as Exc ( catch, IOException ) import Codec.Picture.Metadata( Metadatas ) @@ -200,6 +208,7 @@ import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L +import qualified Data.Vector.Storable as VS -- | Return the first Right thing, accumulating error eitherLoad :: c -> [(String, c -> Either String b)] -> Either String b @@ -263,6 +272,7 @@ readImageWithMetadata :: FilePath -> IO (Either String (DynamicImage, Metadatas)) readImageWithMetadata = withImageDecoder decodeImageWithMetadata + -- | 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 succeeds, it will return @@ -270,6 +280,85 @@ decodeImage :: B.ByteString -> Either String DynamicImage decodeImage = fmap fst . decodeImageWithMetadata +class Decimable px1 px2 where + decimateBitDepth :: Image px1 -> Image px2 + +decimateWord16 :: ( Pixel px1, Pixel px2 + , PixelBaseComponent px1 ~ Pixel16 + , PixelBaseComponent px2 ~ Pixel8 + ) => Image px1 -> Image px2 +decimateWord16 (Image w h da) = + Image w h $ VS.map (\v -> fromIntegral $ v `unsafeShiftR` 8) da + +decimateFloat :: ( Pixel px1, Pixel px2 + , PixelBaseComponent px1 ~ PixelF + , PixelBaseComponent px2 ~ Pixel8 + ) => Image px1 -> Image px2 +decimateFloat (Image w h da) = + Image w h $ VS.map (floor . (255*) . max 0 . min 1) da + +instance Decimable Pixel16 Pixel8 where + decimateBitDepth = decimateWord16 + +instance Decimable PixelYA16 PixelYA8 where + decimateBitDepth = decimateWord16 + +instance Decimable PixelRGB16 PixelRGB8 where + decimateBitDepth = decimateWord16 + +instance Decimable PixelRGBA16 PixelRGBA8 where + decimateBitDepth = decimateWord16 + +instance Decimable PixelCMYK16 PixelCMYK8 where + decimateBitDepth = decimateWord16 + +instance Decimable PixelF Pixel8 where + decimateBitDepth = decimateFloat + +instance Decimable PixelRGBF PixelRGB8 where + decimateBitDepth = decimateFloat + +-- | Convert by any mean possible a dynamic image to an image +-- in RGBA. The process can lose precision while converting from +-- 16bits pixels or Floating point pixels. +convertRGBA8 :: DynamicImage -> Image PixelRGBA8 +convertRGBA8 dynImage = case dynImage of + ImageY8 img -> promoteImage img + ImageY16 img -> promoteImage (decimateBitDepth img :: Image Pixel8) + ImageYF img -> promoteImage (decimateBitDepth img :: Image Pixel8) + ImageYA8 img -> promoteImage img + ImageYA16 img -> promoteImage (decimateBitDepth img :: Image PixelYA8) + ImageRGB8 img -> promoteImage img + ImageRGB16 img -> promoteImage (decimateBitDepth img :: Image PixelRGB8) + ImageRGBF img -> promoteImage (decimateBitDepth img :: Image PixelRGB8) + ImageRGBA8 img -> promoteImage img + ImageRGBA16 img -> decimateBitDepth img + ImageYCbCr8 img -> promoteImage (convertImage img :: Image PixelRGB8) + ImageCMYK8 img -> promoteImage (convertImage img :: Image PixelRGB8) + ImageCMYK16 img -> + promoteImage (convertImage (decimateBitDepth img :: Image PixelCMYK8) :: Image PixelRGB8) + +-- | Convert by any mean possible a dynamic image to an image +-- in RGB. The process can lose precision while converting from +-- 16bits pixels or Floating point pixels. Any alpha layer will +-- be dropped +convertRGB8 :: DynamicImage -> Image PixelRGB8 +convertRGB8 dynImage = case dynImage of + ImageY8 img -> promoteImage img + ImageY16 img -> promoteImage (decimateBitDepth img :: Image Pixel8) + ImageYF img -> promoteImage (decimateBitDepth img :: Image Pixel8) + ImageYA8 img -> promoteImage img + ImageYA16 img -> promoteImage (decimateBitDepth img :: Image PixelYA8) + ImageRGB8 img -> img + ImageRGB16 img -> decimateBitDepth img + ImageRGBF img -> decimateBitDepth img :: Image PixelRGB8 + ImageRGBA8 img -> dropAlphaLayer img + ImageRGBA16 img -> dropAlphaLayer (decimateBitDepth img :: Image PixelRGBA8) + ImageYCbCr8 img -> convertImage img + ImageCMYK8 img -> convertImage img + ImageCMYK16 img -> convertImage (decimateBitDepth img :: Image PixelCMYK8) + + -- | Equivalent to 'decodeImage', but also provide potential metadatas -- present in the given file. decodeImageWithMetadata :: B.ByteString -> Either String (DynamicImage, Metadatas) @@ -306,7 +395,7 @@ readJpeg :: FilePath -> IO (Either String DynamicImage) readJpeg = withImageDecoder decodeJpeg --- | Try to load a .bmp file. The colorspace would be RGB or Y. +-- | Try to load a .bmp file. The colorspace would be RGB, RGBA or Y. readBitmap :: FilePath -> IO (Either String DynamicImage) readBitmap = withImageDecoder decodeBitmap
src/Codec/Picture/BitWriter.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE Rank2Types #-}+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} -- | This module implement helper functions to read & write data -- at bits level.@@ -9,7 +10,9 @@ , getNextBitsLSBFirst , getNextBitsMSBFirst , getNextBitJpg+ , getNextIntJpg , setDecodedString+ , setDecodedStringMSB , setDecodedStringJpg , runBoolReader @@ -33,6 +36,7 @@ import Control.Monad( when ) import Control.Monad.ST( ST ) import qualified Control.Monad.Trans.State.Strict as S+import Data.Int ( Int32 ) import Data.Word( Word8, Word32 ) import Data.Bits( (.&.), (.|.), unsafeShiftR, unsafeShiftL ) @@ -42,6 +46,7 @@ import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L + -------------------------------------------------- ---- Reader --------------------------------------------------@@ -93,8 +98,8 @@ BoolState idx _ chain <- S.get when (idx /= 7) (setDecodedStringJpg chain) -{-# INLINE getNextBitJpg #-} getNextBitJpg :: BoolReader s Bool+{-# INLINE getNextBitJpg #-} getNextBitJpg = do BoolState idx v chain <- S.get let val = (v .&. (1 `unsafeShiftL` idx)) /= 0@@ -103,25 +108,51 @@ else S.put $ BoolState (idx - 1) v chain return val -{-# INLINE getNextBitMSB #-}-getNextBitMSB :: BoolReader s Bool-getNextBitMSB = do+getNextIntJpg :: Int -> BoolReader s Int32+{-# INLINE getNextIntJpg #-}+getNextIntJpg = go 0 where+ go !acc !0 = return acc+ go !acc !n = 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+ let !leftBits = 1 + fromIntegral idx+ if n >= leftBits then do+ setDecodedStringJpg chain+ let !remaining = n - leftBits+ !mask = (1 `unsafeShiftL` leftBits) - 1+ !finalV = fromIntegral v .&. mask+ !theseBits = finalV `unsafeShiftL` remaining+ go (acc .|. theseBits) remaining+ else do+ let !remaining = leftBits - n+ !mask = (1 `unsafeShiftL` n) - 1+ !finalV = fromIntegral v `unsafeShiftR` remaining+ S.put $ BoolState (fromIntegral remaining - 1) v chain+ return $ (finalV .&. mask) .|. acc ++setDecodedStringMSB :: B.ByteString -> BoolReader s ()+setDecodedStringMSB str = case B.uncons str of+ Nothing -> S.put $ BoolState 8 0 B.empty+ Just (v, rest) -> S.put $ BoolState 8 v rest++ {-# INLINE getNextBitsMSBFirst #-} getNextBitsMSBFirst :: Int -> BoolReader s Word32-getNextBitsMSBFirst = aux 0- 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)+getNextBitsMSBFirst requested = go 0 requested where+ go :: Word32 -> Int -> BoolReader s Word32+ go !acc !0 = return acc+ go !acc !n = do+ BoolState idx v chain <- S.get+ let !leftBits = fromIntegral idx+ if n >= leftBits then do+ setDecodedStringMSB chain+ let !theseBits = fromIntegral v `unsafeShiftL` (n - leftBits)+ go (acc .|. theseBits) (n - leftBits)+ else do+ let !remaining = leftBits - n+ !mask = (1 `unsafeShiftL` remaining) - 1+ S.put $ BoolState (fromIntegral remaining) (v .&. mask) chain+ return $ (fromIntegral v `unsafeShiftR` remaining) .|. acc {-# INLINE getNextBitsLSBFirst #-} getNextBitsLSBFirst :: Int -> BoolReader s Word32
src/Codec/Picture/Bitmap.hs view
@@ -41,6 +41,7 @@ , getWord8 , getWord16le , getWord32le + , getWord32be , bytesRead , skip ) @@ -253,6 +254,34 @@ inner 0 0 initialIndex VS.unsafeFreeze buff +decodeImageRGBA8 :: BmpInfoHeader -> (Int, Int, Int, Int) -> B.ByteString -> Image PixelRGBA8 +decodeImageRGBA8 (BmpInfoHeader { width = w, height = h }) (posR, posG, posB, posA) str = Image wi hi stArray where + wi = fromIntegral w + hi = abs $ fromIntegral h + stArray = runST $ do + arr <- M.new (fromIntegral $ w * abs h * 4) + if h > 0 then + foldM_ (readLine arr) 0 [0 .. hi - 1] + else + foldM_ (readLine arr) 0 [hi - 1, hi - 2 .. 0] + VS.unsafeFreeze arr + + stride = linePadding 32 wi -- will be 0 + + readLine :: forall s. M.MVector s Word8 -> Int -> Int -> ST s Int + readLine arr readIndex line = inner readIndex writeIndex where + lastIndex = wi * (hi - 1 - line + 1) * 4 + writeIndex = wi * (hi - 1 - line) * 4 + + inner readIdx writeIdx | writeIdx >= lastIndex = return $ readIdx + stride + inner readIdx writeIdx = do + -- 32-bit BMP pixels are BGRA + (arr `M.unsafeWrite` writeIdx ) (str `B.index` (readIdx + posR)) + (arr `M.unsafeWrite` (writeIdx + 1)) (str `B.index` (readIdx + posG)) + (arr `M.unsafeWrite` (writeIdx + 2)) (str `B.index` (readIdx + posB)) + (arr `M.unsafeWrite` (writeIdx + 3)) (str `B.index` (readIdx + posA)) + inner (readIdx + 4) (writeIdx + 4) + decodeImageRGB8 :: BmpInfoHeader -> B.ByteString -> Image PixelRGB8 decodeImageRGB8 (BmpInfoHeader { width = w, height = h }) str = Image wi hi stArray where wi = fromIntegral w @@ -322,6 +351,8 @@ -- | Try to decode a bitmap image. -- Right now this function can output the following pixel types : -- +-- * PixelRGBA8 +-- -- * PixelRGB8 -- -- * Pixel8 @@ -349,26 +380,48 @@ paletteColorCount | colorCount bmpHeader == 0 = 2 ^ bpp | otherwise = fromIntegral $ colorCount bmpHeader - - table <- if bpp > 8 - then return V.empty - else V.replicateM paletteColorCount pixelGet - - readed' <- bytesRead + getData = do + readed' <- bytesRead + skip . fromIntegral $ dataOffset hdr - fromIntegral readed' + getRemainingBytes + addMetadata i = (i, metadataOfHeader bmpHeader) - skip . fromIntegral $ dataOffset hdr - fromIntegral readed' - rest <- getRemainingBytes - let addMetadata i = (i, metadataOfHeader bmpHeader) case (bitPerPixel bmpHeader, planes bmpHeader, bitmapCompression bmpHeader) of - -- (32, 1, 0) -> {- ImageRGBA8 <$>-} fail "Meuh" - (24, 1, 0) -> return . addMetadata . ImageRGB8 $ decodeImageRGB8 bmpHeader rest - ( 8, 1, 0) -> - let indexer v = table V.! fromIntegral v in - return . addMetadata . ImageRGB8 . pixelMap indexer $ decodeImageY8 bmpHeader rest + (32, 1, 0) -> do + rest <- getData + return . addMetadata . ImageRGBA8 $ decodeImageRGBA8 bmpHeader (2, 1, 0, 3) rest + -- (2, 1, 0, 3) means BGRA pixel order + (32, 1, 3) -> do + posRed <- getBitfield + posGreen <- getBitfield + posBlue <- getBitfield + posAlpha <- getBitfield + rest <- getData + return . addMetadata . ImageRGBA8 $ + decodeImageRGBA8 bmpHeader (posRed, posGreen, posBlue, posAlpha) rest + (24, 1, 0) -> do + rest <- getData + return . addMetadata . ImageRGB8 $ decodeImageRGB8 bmpHeader rest + ( 8, 1, 0) -> do + table <- V.replicateM paletteColorCount pixelGet + rest <- getData + let indexer v = table V.! fromIntegral v + return . addMetadata . ImageRGB8 . pixelMap indexer $ decodeImageY8 bmpHeader rest a -> fail $ "Can't handle BMP file " ++ show a + +getBitfield :: Get Int +getBitfield = do + w32 <- getWord32be + case w32 of + 0xFF000000 -> return 0 + 0x00FF0000 -> return 1 + 0x0000FF00 -> return 2 + 0x000000FF -> return 3 + _ -> fail $ + "Codec.Picture.Bitmap.getBitfield: unsupported bitfield of " ++ show w32 -- | Write an image in a file use the bitmap format. writeBitmap :: (BmpEncodable pixel)
src/Codec/Picture/Gif/LZW.hs view
@@ -1,191 +1,194 @@-{-# LANGUAGE CPP #-} -module Codec.Picture.Gif.LZW( decodeLzw, decodeLzwTiff ) where - -#if !MIN_VERSION_base(4,8,0) -import Control.Applicative( (<$>) ) -#endif - -import Data.Word( Word8 ) -import Control.Monad( when, unless ) - -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 - unless (toOutput == endOfInfo) $ 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 - +{-# LANGUAGE CPP #-}+module Codec.Picture.Gif.LZW( decodeLzw, decodeLzwTiff ) where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative( (<$>) )+#endif++import Data.Word( Word8 )+import Control.Monad( when, unless )++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+ if isOldTiffLZW str then+ setDecodedString str+ else+ setDecodedStringMSB 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+ unless (toOutput == endOfInfo) $ 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/Jpg.hs view
@@ -11,7 +11,9 @@ , decodeJpegWithMetadata , encodeJpegAtQuality , encodeJpegAtQualityWithMetadata + , encodeDirectJpegAtQualityWithMetadata , encodeJpeg + , JpgEncodable ) where #if !MIN_VERSION_base(4,8,0) @@ -546,6 +548,8 @@ -- -- * PixelRGB8 -- +-- * PixelCMYK8 +-- -- * PixelYCbCr8 -- decodeJpeg :: B.ByteString -> Either String DynamicImage @@ -623,7 +627,8 @@ frozen <- unsafeFreezeImage fImg return (st, imageData frozen) -extractBlock :: Image PixelYCbCr8 -- ^ Source image +extractBlock :: forall s px. (PixelBaseComponent px ~ Word8) + => Image px -- ^ Source image -> MutableMacroBlock s Int16 -- ^ Mutable block where to put extracted block -> Int -- ^ Plane -> Int -- ^ X sampling factor @@ -741,6 +746,22 @@ , prepareHuffmanTable AcComponent 1 defaultAcChromaHuffmanTable ] +lumaQuantTableAtQuality :: Int -> QuantificationTable +lumaQuantTableAtQuality qual = scaleQuantisationMatrix qual defaultLumaQuantizationTable + +chromaQuantTableAtQuality :: Int -> QuantificationTable +chromaQuantTableAtQuality qual = + scaleQuantisationMatrix qual defaultChromaQuantizationTable + +zigzaggedQuantificationSpec :: Int -> [JpgQuantTableSpec] +zigzaggedQuantificationSpec qual = + [ JpgQuantTableSpec { quantPrecision = 0, quantDestination = 0, quantTable = luma } + , JpgQuantTableSpec { quantPrecision = 0, quantDestination = 1, quantTable = chroma } + ] + where + luma = zigZagReorderForwardv $ lumaQuantTableAtQuality qual + chroma = zigZagReorderForwardv $ chromaQuantTableAtQuality qual + -- | Function to call to encode an image to jpeg. -- The quality factor should be between 0 and 100 (100 being -- the best quality). @@ -749,6 +770,203 @@ -> L.ByteString -- ^ Encoded JPEG encodeJpegAtQuality quality = encodeJpegAtQualityWithMetadata quality mempty +-- | Record gathering all information to encode a component +-- from the source image. Previously was a huge tuple +-- burried in the code +data EncoderState = EncoderState + { _encComponentIndex :: !Int + , _encBlockWidth :: !Int + , _encBlockHeight :: !Int + , _encQuantTable :: !QuantificationTable + , _encDcHuffman :: !HuffmanWriterCode + , _encAcHuffman :: !HuffmanWriterCode + } + + +-- | Helper type class describing all JPG-encodable pixel types +class (Pixel px, PixelBaseComponent px ~ Word8) => JpgEncodable px where + additionalBlocks :: Image px -> [JpgFrame] + additionalBlocks _ = [] + + componentsOfColorSpace :: Image px -> [JpgComponent] + + encodingState :: Int -> Image px -> V.Vector EncoderState + + imageHuffmanTables :: Image px -> [(JpgHuffmanTableSpec, HuffmanPackedTree)] + imageHuffmanTables _ = defaultHuffmanTables + + scanSpecificationOfColorSpace :: Image px -> [JpgScanSpecification] + + quantTableSpec :: Image px -> Int -> [JpgQuantTableSpec] + quantTableSpec _ qual = take 1 $ zigzaggedQuantificationSpec qual + + maximumSubSamplingOf :: Image px -> Int + maximumSubSamplingOf _ = 1 + +instance JpgEncodable Pixel8 where + scanSpecificationOfColorSpace _ = + [ JpgScanSpecification { componentSelector = 1 + , dcEntropyCodingTable = 0 + , acEntropyCodingTable = 0 + } + ] + + componentsOfColorSpace _ = + [ JpgComponent { componentIdentifier = 1 + , horizontalSamplingFactor = 1 + , verticalSamplingFactor = 1 + , quantizationTableDest = 0 + } + ] + + imageHuffmanTables _ = + [ prepareHuffmanTable DcComponent 0 defaultDcLumaHuffmanTable + , prepareHuffmanTable AcComponent 0 defaultAcLumaHuffmanTable + ] + + encodingState qual _ = V.singleton EncoderState + { _encComponentIndex = 0 + , _encBlockWidth = 1 + , _encBlockHeight = 1 + , _encQuantTable = zigZagReorderForwardv $ lumaQuantTableAtQuality qual + , _encDcHuffman = makeInverseTable defaultDcLumaHuffmanTree + , _encAcHuffman = makeInverseTable defaultAcLumaHuffmanTree + } + + +instance JpgEncodable PixelYCbCr8 where + maximumSubSamplingOf _ = 2 + quantTableSpec _ qual = zigzaggedQuantificationSpec qual + scanSpecificationOfColorSpace _ = + [ JpgScanSpecification { componentSelector = 1 + , dcEntropyCodingTable = 0 + , acEntropyCodingTable = 0 + } + , JpgScanSpecification { componentSelector = 2 + , dcEntropyCodingTable = 1 + , acEntropyCodingTable = 1 + } + , JpgScanSpecification { componentSelector = 3 + , dcEntropyCodingTable = 1 + , acEntropyCodingTable = 1 + } + ] + + componentsOfColorSpace _ = + [ 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 + } + ] + + encodingState qual _ = V.fromListN 3 [lumaState, chromaState, chromaState { _encComponentIndex = 2 }] + where + lumaState = EncoderState + { _encComponentIndex = 0 + , _encBlockWidth = 2 + , _encBlockHeight = 2 + , _encQuantTable = zigZagReorderForwardv $ lumaQuantTableAtQuality qual + , _encDcHuffman = makeInverseTable defaultDcLumaHuffmanTree + , _encAcHuffman = makeInverseTable defaultAcLumaHuffmanTree + } + chromaState = EncoderState + { _encComponentIndex = 1 + , _encBlockWidth = 1 + , _encBlockHeight = 1 + , _encQuantTable = zigZagReorderForwardv $ chromaQuantTableAtQuality qual + , _encDcHuffman = makeInverseTable defaultDcChromaHuffmanTree + , _encAcHuffman = makeInverseTable defaultAcChromaHuffmanTree + } + +instance JpgEncodable PixelRGB8 where + additionalBlocks _ = [] where + _adobe14 = JpgAdobeApp14 + { _adobeDctVersion = 100 + , _adobeFlag0 = 0 + , _adobeFlag1 = 0 + , _adobeTransform = AdobeUnknown + } + + imageHuffmanTables _ = + [ prepareHuffmanTable DcComponent 0 defaultDcLumaHuffmanTable + , prepareHuffmanTable AcComponent 0 defaultAcLumaHuffmanTable + ] + + scanSpecificationOfColorSpace _ = fmap build "RGB" where + build c = JpgScanSpecification + { componentSelector = fromIntegral $ fromEnum c + , dcEntropyCodingTable = 0 + , acEntropyCodingTable = 0 + } + + componentsOfColorSpace _ = fmap build "RGB" where + build c = JpgComponent + { componentIdentifier = fromIntegral $ fromEnum c + , horizontalSamplingFactor = 1 + , verticalSamplingFactor = 1 + , quantizationTableDest = 0 + } + + encodingState qual _ = V.fromListN 3 $ fmap build [0 .. 2] where + build ix = EncoderState + { _encComponentIndex = ix + , _encBlockWidth = 1 + , _encBlockHeight = 1 + , _encQuantTable = zigZagReorderForwardv $ lumaQuantTableAtQuality qual + , _encDcHuffman = makeInverseTable defaultDcLumaHuffmanTree + , _encAcHuffman = makeInverseTable defaultAcLumaHuffmanTree + } + +instance JpgEncodable PixelCMYK8 where + additionalBlocks _ = [] where + _adobe14 = JpgAdobeApp14 + { _adobeDctVersion = 100 + , _adobeFlag0 = 32768 + , _adobeFlag1 = 0 + , _adobeTransform = AdobeYCck + } + + imageHuffmanTables _ = + [ prepareHuffmanTable DcComponent 0 defaultDcLumaHuffmanTable + , prepareHuffmanTable AcComponent 0 defaultAcLumaHuffmanTable + ] + + scanSpecificationOfColorSpace _ = fmap build "CMYK" where + build c = JpgScanSpecification + { componentSelector = fromIntegral $ fromEnum c + , dcEntropyCodingTable = 0 + , acEntropyCodingTable = 0 + } + + componentsOfColorSpace _ = fmap build "CMYK" where + build c = JpgComponent + { componentIdentifier = fromIntegral $ fromEnum c + , horizontalSamplingFactor = 1 + , verticalSamplingFactor = 1 + , quantizationTableDest = 0 + } + + encodingState qual _ = V.fromListN 4 $ fmap build [0 .. 3] where + build ix = EncoderState + { _encComponentIndex = ix + , _encBlockWidth = 1 + , _encBlockHeight = 1 + , _encQuantTable = zigZagReorderForwardv $ lumaQuantTableAtQuality qual + , _encDcHuffman = makeInverseTable defaultDcLumaHuffmanTree + , _encAcHuffman = makeInverseTable defaultAcLumaHuffmanTree + } + -- | Equivalent to 'encodeJpegAtQuality', but will store the following -- metadatas in the file using a JFIF block: -- @@ -759,121 +977,86 @@ -> Metadatas -> Image PixelYCbCr8 -- ^ Image to encode -> L.ByteString -- ^ Encoded JPEG -encodeJpegAtQualityWithMetadata quality metas img@(Image { imageWidth = w, imageHeight = h }) = encode finalImage - where finalImage = JpgImage $ - encodeMetadatas metas ++ - [ 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 +encodeJpegAtQualityWithMetadata = encodeDirectJpegAtQualityWithMetadata - zigzagedLumaQuant = zigZagReorderForwardv lumaQuant - zigzagedChromaQuant = zigZagReorderForwardv chromaQuant - quantTables = [ JpgQuantTableSpec { quantPrecision = 0, quantDestination = 0 - , quantTable = zigzagedLumaQuant } - , JpgQuantTableSpec { quantPrecision = 0, quantDestination = 1 - , quantTable = zigzagedChromaQuant } - ] +-- | Equivalent to 'encodeJpegAtQuality', but will store the following +-- metadatas in the file using a JFIF block: +-- +-- * 'Codec.Picture.Metadata.DpiX' +-- * 'Codec.Picture.Metadata.DpiY' +-- +-- This function also allow to create JPEG files with the following color +-- space: +-- +-- * Y (Pixel8) for greyscale. +-- * RGB (PixelRGB8) with no color downsampling on any plane +-- * CMYK (PixelCMYK8) with no color downsampling on any plane +-- +encodeDirectJpegAtQualityWithMetadata :: forall px. (JpgEncodable px) + => Word8 -- ^ Quality factor + -> Metadatas + -> Image px -- ^ Image to encode + -> L.ByteString -- ^ Encoded JPEG +encodeDirectJpegAtQualityWithMetadata quality metas img = encode finalImage where + !w = imageWidth img + !h = imageHeight img + finalImage = JpgImage $ + encodeMetadatas metas ++ + additionalBlocks img ++ + [ JpgQuantTable $ quantTableSpec img (fromIntegral quality) + , JpgScans JpgBaselineDCTHuffman hdr + , JpgHuffmanTable $ imageHuffmanTables img + , JpgScanBlob scanHeader encodedImage + ] - 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] + !outputComponentCount = componentCount (undefined :: px) - imageComponentCount = length componentDef + scanHeader = scanHeader'{ scanLength = fromIntegral $ calculateSize scanHeader' } + scanHeader' = JpgScanHeader + { scanLength = 0 + , scanComponentCount = fromIntegral outputComponentCount + , scans = scanSpecificationOfColorSpace img + , spectralSelection = (0, 63) + , successiveApproxHigh = 0 + , successiveApproxLow = 0 + } - dc_table <- M.replicate 3 0 - block <- createEmptyMutableMacroBlock - workData <- createEmptyMutableMacroBlock - zigzaged <- createEmptyMutableMacroBlock - writeState <- newWriteStateRef + hdr = hdr' { jpgFrameHeaderLength = fromIntegral $ calculateSize hdr' } + hdr' = JpgFrameHeader + { jpgFrameHeaderLength = 0 + , jpgSamplePrecision = 8 + , jpgHeight = fromIntegral h + , jpgWidth = fromIntegral w + , jpgImageComponentCount = fromIntegral outputComponentCount + , jpgComponents = componentsOfColorSpace img + } - -- 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 + !maxSampling = maximumSubSamplingOf img + !horizontalMetaBlockCount = w `divUpward` (dctBlockSize * maxSampling) + !verticalMetaBlockCount = h `divUpward` (dctBlockSize * maxSampling) + !componentDef = encodingState (fromIntegral quality) img - 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 + encodedImage = runST $ do + dc_table <- M.replicate outputComponentCount 0 + block <- createEmptyMutableMacroBlock + workData <- createEmptyMutableMacroBlock + zigzaged <- createEmptyMutableMacroBlock + writeState <- newWriteStateRef - rasterMap - horizontalMetaBlockCount verticalMetaBlockCount - blockDecoder + rasterMap horizontalMetaBlockCount verticalMetaBlockCount $ \mx my -> + V.forM_ componentDef $ \(EncoderState comp sizeX sizeY table dc ac) -> + let !xSamplingFactor = maxSampling - sizeX + 1 + !ySamplingFactor = maxSampling - sizeY + 1 + !extractor = extractBlock img block xSamplingFactor ySamplingFactor outputComponentCount + in + rasterMap sizeX sizeY $ \subX subY -> do + let !blockY = my * sizeY + subY + !blockX = mx * sizeX + subX + prev_dc <- dc_table `M.unsafeRead` comp + extracted <- extractor comp blockX blockY + (dc_coeff, neo_block) <- encodeMacroBlock table workData zigzaged prev_dc extracted + (dc_table `M.unsafeWrite` comp) $ fromIntegral dc_coeff + serializeMacroBlock writeState dc ac neo_block - finalizeBoolWriter writeState + finalizeBoolWriter writeState
src/Codec/Picture/Jpg/Common.hs view
@@ -23,11 +23,10 @@ import Control.Applicative( pure, (<$>) ) #endif -import Control.Monad( replicateM, when )+import Control.Monad( 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@@ -174,8 +173,7 @@ -- | Unpack an int of the given size encoded from MSB to LSB. unpackInt :: Int -> BoolReader s Int32-unpackInt bitCount = packInt <$> replicateM bitCount getNextBitJpg-+unpackInt = getNextIntJpg {-# INLINE rasterMap #-} rasterMap :: (Monad m)@@ -186,11 +184,6 @@ 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
src/Codec/Picture/Jpg/Types.hs view
@@ -427,7 +427,8 @@ getByteString (fromIntegral size - 2) putFrame :: JpgFrame -> Put-putFrame (JpgAdobeAPP14 _adobe) = return ()+putFrame (JpgAdobeAPP14 adobe) = + put (JpgAppSegment 14) >> putWord16be 14 >> put adobe putFrame (JpgJFIF jfif) = put (JpgAppSegment 0) >> putWord16be (14+2) >> put jfif putFrame (JpgExif _exif) =
src/Codec/Picture/Saving.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE CPP #-} -- | Helper functions to save dynamic images to other file format -- with automatic color space/sample format conversion done automatically. module Codec.Picture.Saving( imageToJpg@@ -10,6 +11,10 @@ , imageToTga ) where +#if !MIN_VERSION_base(4,8,0)+import Data.Monoid( mempty )+#endif+ import Data.Bits( unsafeShiftR ) import Data.Word( Word8, Word16 ) import qualified Data.ByteString.Lazy as L@@ -94,9 +99,13 @@ -- | 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+-- Save Y or YCbCr Jpeg only, all other colorspaces are converted.+-- To save a RGB or CMYK JPEG file, use the+-- 'Codec.Picture.Jpg.encodeDirectJpegAtQualityWithMetadata' function imageToJpg :: Int -> DynamicImage -> L.ByteString imageToJpg quality dynImage = let encodeAtQuality = encodeJpegAtQuality (fromIntegral quality)+ encodeWithMeta = encodeDirectJpegAtQualityWithMetadata (fromIntegral quality) mempty in case dynImage of ImageYCbCr8 img -> encodeAtQuality img ImageCMYK8 img -> imageToJpg quality . ImageRGB8 $ convertImage img@@ -105,10 +114,8 @@ 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)+ ImageY8 img -> encodeWithMeta img+ ImageYA8 img -> encodeWithMeta $ dropAlphaLayer img ImageY16 img -> imageToJpg quality . ImageY8 $ from16to8 img ImageYA16 img -> imageToJpg quality . ImageYA8 $ from16to8 img ImageRGB16 img -> imageToJpg quality . ImageRGB8 $ from16to8 img
src/Codec/Picture/Types.hs view
@@ -739,7 +739,7 @@ -> Int -- ^ Height in pixels -> m (MutableImage (PrimState m) px) {-# INLINE generateMutableImage #-} -generateMutableImage f w h = MutableImage w h <$> generated where +generateMutableImage f w h = MutableImage w h `liftM` generated where compCount = componentCount (undefined :: px) generated = do @@ -897,6 +897,7 @@ {-# 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 #-} +{-# SPECIALIZE INLINE pixelMap :: (Pixel8 -> Pixel8) -> Image Pixel8 -> Image Pixel8 #-} pixelMap f Image { imageWidth = w, imageHeight = h, imageData = vec } = Image w h pixels where sourceComponentCount = componentCount (undefined :: a) @@ -1105,9 +1106,10 @@ instance LumaPlaneExtractable PixelRGBA8 where {-# INLINE computeLuma #-} - computeLuma (PixelRGBA8 r g b _) = floor $ 0.3 * toRational r + - 0.59 * toRational g + - 0.11 * toRational b + computeLuma (PixelRGBA8 r g b _) = + floor $ (0.3 :: Double) * fromIntegral r + + 0.59 * fromIntegral g + + 0.11 * fromIntegral b instance LumaPlaneExtractable PixelYCbCr8 where {-# INLINE computeLuma #-} @@ -1570,9 +1572,11 @@ instance LumaPlaneExtractable PixelRGB16 where {-# INLINE computeLuma #-} - computeLuma (PixelRGB16 r g b) = floor $ 0.3 * toRational r + - 0.59 * toRational g + - 0.11 * toRational b + computeLuma (PixelRGB16 r g b) = + floor $ (0.3 :: Double) * fromIntegral r + + 0.59 * fromIntegral g + + 0.11 * fromIntegral b + -------------------------------------------------- ---- PixelRGB8 instances -------------------------------------------------- @@ -1654,9 +1658,10 @@ instance LumaPlaneExtractable PixelRGB8 where {-# INLINE computeLuma #-} - computeLuma (PixelRGB8 r g b) = floor $ 0.3 * toRational r + - 0.59 * toRational g + - 0.11 * toRational b + computeLuma (PixelRGB8 r g b) = + floor $ (0.3 :: Double) * fromIntegral r + + 0.59 * fromIntegral g + + 0.11 * fromIntegral b -------------------------------------------------- ---- PixelRGBA8 instances