JuicyPixels 3.1.1.1 → 3.1.2
raw patch · 12 files changed
+2023/−1355 lines, 12 filesdep ~binary
Dependency ranges changed: binary
Files
- Codec/Picture/BitWriter.hs +35/−3
- Codec/Picture/Bitmap.hs +10/−2
- Codec/Picture/Jpg.hs +713/−1300
- Codec/Picture/Jpg/Common.hs +232/−0
- Codec/Picture/Jpg/DefaultTable.hs +15/−2
- Codec/Picture/Jpg/FastDct.hs +10/−10
- Codec/Picture/Jpg/Progressive.hs +309/−0
- Codec/Picture/Jpg/Types.hs +518/−0
- Codec/Picture/Tiff.hs +67/−33
- Codec/Picture/Types.hs +35/−1
- JuicyPixels.cabal +8/−4
- changelog +71/−0
Codec/Picture/BitWriter.hs view
@@ -2,7 +2,8 @@ -- | This module implement helper functions to read & write data -- at bits level. module Codec.Picture.BitWriter( BoolReader - , BoolState( .. ) + , emptyBoolState + , BoolState , byteAlignJpg , getNextBitsLSBFirst , getNextBitsMSBFirst @@ -15,6 +16,11 @@ , newWriteStateRef , finalizeBoolWriter , writeBits' + + , initBoolState + , initBoolStateJpg + , execBoolReader + , runBoolReaderWith ) where import Data.STRef @@ -39,12 +45,36 @@ {-# 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 @@ -115,9 +145,11 @@ 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) -> S.put $ BoolState 7 0xFF afterMarker + Just (0x00, afterMarker) -> -- trace "00" $ + S.put $ BoolState 7 0xFF afterMarker Just (_ , afterMarker) -> setDecodedStringJpg afterMarker - Just (v, rest) -> S.put $ BoolState 7 v rest + Just (v, rest) -> + S.put $ BoolState 7 v rest -------------------------------------------------- ---- Writer
Codec/Picture/Bitmap.hs view
@@ -27,6 +27,8 @@ import Data.Binary.Get( Get , getWord16le , getWord32le + , bytesRead + , skip ) import Data.Word( Word32, Word16, Word8 ) @@ -257,15 +259,21 @@ -- decodeBitmap :: B.ByteString -> Either String DynamicImage decodeBitmap str = flip runGetStrict str $ do - _hdr <- get :: Get BmpHeader + 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 - _ -> fail "Can't handle BMP file" + a -> fail $ "Can't handle BMP file " ++ show a -- | Write an image in a file use the bitmap format.
Codec/Picture/Jpg.hs view
@@ -5,1303 +5,716 @@ {-# LANGUAGE BangPatterns #-} {-# OPTIONS_GHC -fspec-constr-count=5 #-} -- | Module used for JPEG file loading and writing. -module Codec.Picture.Jpg( decodeJpeg, encodeJpegAtQuality, encodeJpeg ) where - -import Control.Arrow( (>>>) ) -import Control.Applicative( (<$>), (<*>)) -import Control.Monad( when, replicateM, forM, forM_, foldM_, unless ) -import Control.Monad.ST( ST, runST ) -import Control.Monad.Trans( lift ) -import qualified Control.Monad.Trans.State.Strict as S - -import Data.Maybe( fromMaybe ) -import Data.List( find, foldl' ) -import Data.Bits( (.|.), (.&.), unsafeShiftL, unsafeShiftR ) -import Data.Int( Int16, Int32 ) -import Data.Word(Word8, Word16, Word32) -import Data.Binary( Binary(..), encode ) - -import Data.Binary.Get( Get - , getWord8 - , getWord16be - , getByteString - , skip - , bytesRead - ) - -import Data.Binary.Put( Put - , putWord8 - , putWord16be - , putLazyByteString - ) - -import Data.Maybe( fromJust ) -import qualified Data.Vector as V -import Data.Vector.Unboxed( (!) ) -import qualified Data.Vector.Unboxed as VU -import qualified Data.Vector.Storable as VS -import qualified Data.Vector.Storable.Mutable as M --- import Data.Array.Unboxed( Array, UArray, elems, listArray, (!) ) -import qualified Data.ByteString as B -import qualified Data.ByteString.Lazy as L -import Foreign.Storable ( Storable ) - -import Codec.Picture.InternalHelper -import Codec.Picture.BitWriter -import Codec.Picture.Types -import Codec.Picture.Jpg.Types -import Codec.Picture.Jpg.DefaultTable -import Codec.Picture.Jpg.FastIdct -import Codec.Picture.Jpg.FastDct - --------------------------------------------------- ----- Types --------------------------------------------------- -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) - -type HuffmanTreeInfo = HuffmanPackedTree - -data JpgFrame = - JpgAppFrame !Word8 B.ByteString - | JpgExtension !Word8 B.ByteString - | JpgQuantTable ![JpgQuantTableSpec] - | JpgHuffmanTable ![(JpgHuffmanTableSpec, HuffmanTreeInfo)] - | 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 - --- | Type introduced only to avoid some typeclass overlapping --- problem -newtype TableList a = TableList [a] - -class SizeCalculable a where - calculateSize :: a -> Int - -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 - -buildPackedHuffmanTree :: V.Vector (VU.Vector Word8) -> HuffmanTree -buildPackedHuffmanTree = buildHuffmanTree . map VU.toList . V.toList - -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 `VS.unsafeIndex` fromIntegral tableIndex - --------------------------------------------------- ----- Serialization instances --------------------------------------------------- -commonMarkerFirstByte :: Word8 -commonMarkerFirstByte = 0xFF - -checkMarker :: Word8 -> Word8 -> Get () -checkMarker b1 b2 = do - rb1 <- getWord8 - rb2 <- getWord8 - when (rb1 /= b1 || rb2 /= b2) - (fail "Invalid marker used") - -eatUntilCode :: Get () -eatUntilCode = do - code <- getWord8 - unless (code == 0xFF) eatUntilCode - -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 } - -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 - -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 - -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 - -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 - --- | 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 - - --- | 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 - -packInt :: [Bool] -> Int32 -packInt = foldl' bitStep 0 - where bitStep acc True = (acc `unsafeShiftL` 1) + 1 - bitStep acc False = acc `unsafeShiftL` 1 - --- | Unpack an int of the given size encoded from MSB to LSB. -unpackInt :: Int32 -> BoolReader s Int32 -unpackInt bitCount = packInt <$> replicateM (fromIntegral bitCount) getNextBitJpg - -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) - -{-# INLINE decodeInt #-} -decodeInt :: Int32 -> 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 - -dcCoefficientDecode :: HuffmanTreeInfo - -> BoolReader s DcCoefficient -dcCoefficientDecode dcTree = do - ssss <- huffmanPackedDecode dcTree - if ssss == 0 - then return 0 - else fromIntegral <$> decodeInt (fromIntegral ssss) - --- | Assume the macro block is initialized with zeroes -acCoefficientsDecode :: HuffmanTreeInfo -> MutableMacroBlock s Int16 - -> BoolReader s (MutableMacroBlock s Int16) -acCoefficientsDecode acTree mutableBlock = parseAcCoefficient 1 >> return mutableBlock - where parseAcCoefficient n | n >= 64 = return () - | otherwise = do - rrrrssss <- huffmanPackedDecode acTree - let rrrr = fromIntegral $ (rrrrssss `unsafeShiftR` 4) .&. 0xF - ssss = rrrrssss .&. 0xF - case (rrrr, ssss) of - ( 0, 0) -> return () - (0xF, 0) -> parseAcCoefficient (n + 16) - _ -> do - decoded <- fromIntegral <$> decodeInt (fromIntegral ssss) - lift $ (mutableBlock `M.unsafeWrite` (n + rrrr)) decoded - parseAcCoefficient (n + rrrr + 1) - -{- -decodeEOB :: HuffmanTreeInfo -> BoolReader s Int32 -decodeEOB tree = do - rrrrssss <- huffmanPackedDecode tree - -} - --- | Decompress a macroblock from a bitstream given the current configuration --- from the frame. -decompressMacroBlock :: HuffmanTreeInfo -- ^ Tree used for DC coefficient - -> HuffmanTreeInfo -- ^ 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) - -gatherQuantTables :: JpgImage -> [JpgQuantTableSpec] -gatherQuantTables img = concat [t | JpgQuantTable t <- jpgFrame img] - -gatherHuffmanTables :: JpgImage -> [(JpgHuffmanTableSpec, HuffmanTreeInfo)] -gatherHuffmanTables img = - concat [lst | JpgHuffmanTable lst <- jpgFrame img] ++ defaultHuffmanTables - - -gatherScanInfo :: JpgImage -> (JpgFrameKind, JpgFrameHeader) -gatherScanInfo img = fromJust $ unScan <$> find scanDesc (jpgFrame img) - where scanDesc (JpgScans _ _) = True - scanDesc _ = False - - unScan (JpgScans a b) = (a,b) - unScan _ = error "If this can happen, the JPEG image is ill-formed" - -pixelClamp :: Int16 -> Word8 -pixelClamp n = fromIntegral . min 255 $ max 0 n - -unpack444Y :: 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 * 8 + y * 8 * imgWidth - - blockVert _ _ j | j >= 8 = return () - blockVert writeIdx readingIdx j = blockHoriz writeIdx readingIdx zero - where blockHoriz _ readIdx i | i >= 8 = 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 * 8 + y * 8 * imgWidth) * 3 + compIdx - - blockVert _ _ j | j >= 8 = 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 + 8) $ 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-} - -unpack422Ycbcr :: Int -- ^ Component index - -> Int -- ^ x - -> Int -- ^ y - -> MutableImage s PixelYCbCr8 - -> MutableMacroBlock s Int16 - -> ST s () -unpack422Ycbcr compIdx x y - (MutableImage { mutableImageWidth = imgWidth, - mutableImageHeight = _, mutableImageData = img }) - block = blockVert baseIdx 0 zero - where zero = 0 :: Int - baseIdx = (x * 8 * 2 + y * 8 * imgWidth) * 3 + compIdx - lineOffset = imgWidth * 3 - - blockVert _ _ j | j >= 8 = 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 + 8) $ j + 1 - --- | Given a size coefficient (how much a pixel span horizontally --- and vertically), the position of the macroblock, return a list --- of indices and value to be stored in an array (like the final --- image) -unpackMacroBlock :: Int -- ^ Component count - -> Int -- ^ Component index - -> Int -- ^ Width coefficient - -> Int -- ^ Height coefficient - -> Int -- ^ x - -> Int -- ^ y - -> MutableImage s PixelYCbCr8 - -> MutableMacroBlock s Int16 - -> ST s () -unpackMacroBlock compCount compIdx wCoeff hCoeff x y - (MutableImage { mutableImageWidth = imgWidth, - mutableImageHeight = imgHeight, mutableImageData = img }) - block = -- trace (printf "w:%d h:%d x:%d y:%d wCoeff:%d hCoeff:%d" imgWidth imgHeight x y wCoeff hCoeff) $ - blockVert 0 - where blockVert j | j >= 8 = return () - blockVert j = blockHoriz 0 - where yBase = (y * 8 + j) * hCoeff - blockHoriz i | i >= 8 = blockVert $ j + 1 - blockHoriz i = (pixelClamp <$> (block `M.unsafeRead` (i + j * 8))) >>= horizDup 0 - where xBase = (x * 8 + i) * wCoeff - horizDup wDup _ | wDup >= wCoeff = blockHoriz $ i + 1 - horizDup wDup compVal = vertDup 0 - where vertDup hDup | hDup >= hCoeff = horizDup (wDup + 1) compVal - vertDup hDup = do - let xPos = xBase + wDup - yPos = yBase + hDup - - when (xPos < imgWidth && yPos < imgHeight) - (do let mutableIdx = (xPos + yPos * imgWidth) * compCount + compIdx - (img `M.unsafeWrite` mutableIdx) compVal) - - vertDup $ hDup + 1 - --- | Type only used to make clear what kind of integer we are carrying --- Might be transformed into newtype in the future -type DcCoefficient = Int16 - --- | Same as for DcCoefficient, to provide nicer type signatures -type DctCoefficients = DcCoefficient - -decodeRestartInterval :: BoolReader s Int32 -decodeRestartInterval = return (-1) {- do - bits <- replicateM 8 getNextBitJpg - if bits == replicate 8 True - then do - marker <- replicateM 8 getNextBitJpg - return $ packInt marker - else return (-1) - -} - -decodeImage :: JpgImage - -> Int -- ^ Component count - -> MutableImage s PixelYCbCr8 -- ^ Result image to write into - -> BoolReader s () -decodeImage img compCount outImage = do - zigZagArray <- lift $ createEmptyMutableMacroBlock - dcArray <- lift (M.replicate compCount 0 :: ST s (M.STVector s DcCoefficient)) - - let huffmans = gatherHuffmanTables img - huffmanForComponent dcOrAc dest = - case [t | (h,t) <- huffmans - , huffmanTableClass h == dcOrAc - , huffmanTableDest h == dest] of - (v:_) -> v - [] -> error "No Huffman table" - - mcuBeforeRestart = case [i | JpgIntervalRestart i <- jpgFrame img] of - [] -> maxBound :: Int -- HUUUUUUGE value (enough to parse all MCU) - (x:_) -> fromIntegral x - - quants = gatherQuantTables img - quantForComponent dest = - case [quantTable q | q <- quants, quantDestination q == dest] of - (v:_) -> v - [] -> error "No quant table" - - hdr = case [h | JpgScanBlob h _ <- jpgFrame img] of - [] -> error "No scan blob" - (v:_) -> v - - (_, scanInfo) = gatherScanInfo img - imgWidth = fromIntegral $ jpgWidth scanInfo - imgHeight = fromIntegral $ jpgHeight scanInfo - - blockSizeOfDim fullDim maxBlockSize = block + (if rest /= 0 then 1 else 0) - where (block, rest) = fullDim `divMod` maxBlockSize - - horizontalSamplings = [horiz | (horiz, _, _, _, _, _) <- componentsInfo] - - imgComponentCount = fromIntegral $ jpgImageComponentCount scanInfo :: Int - isImageLumanOnly = imgComponentCount == 1 - maxHorizFactor | not isImageLumanOnly && - not (allElementsEqual horizontalSamplings) = maximum horizontalSamplings - | otherwise = 1 - - verticalSamplings = [vert | (_, vert, _, _, _, _) <- componentsInfo] - maxVertFactor | not isImageLumanOnly && - not (allElementsEqual verticalSamplings) = maximum verticalSamplings - | otherwise = 1 - - horizontalBlockCount = - blockSizeOfDim imgWidth $ fromIntegral (maxHorizFactor * 8) - - verticalBlockCount = - blockSizeOfDim imgHeight $ fromIntegral (maxVertFactor * 8) - - fetchTablesForComponent component = (horizCount, vertCount, dcTree, acTree, qTable, unpacker) - where idx = componentIdentifier component - descr = case [c | c <- scans hdr, componentSelector c == idx] of - (v:_) -> v - [] -> error "No scan" - dcTree = huffmanForComponent DcComponent $ dcEntropyCodingTable descr - acTree = huffmanForComponent AcComponent $ acEntropyCodingTable descr - qTable = quantForComponent $ if idx == 1 then 0 else 1 - horizCount = if not isImageLumanOnly - then fromIntegral $ horizontalSamplingFactor component - else 1 - vertCount = if not isImageLumanOnly - then fromIntegral $ verticalSamplingFactor component - else 1 - - xScalingFactor = maxHorizFactor - horizCount + 1 - yScalingFactor = maxVertFactor - vertCount + 1 - - unpacker = unpackerDecision xScalingFactor yScalingFactor - - unpackerDecision 1 1 | isImageLumanOnly = unpack444Y - | otherwise = unpack444Ycbcr . fromIntegral $ idx - 1 - unpackerDecision 2 1 = unpack422Ycbcr . fromIntegral $ idx - 1 - unpackerDecision _ _ = unpackMacroBlock compCount (fromIntegral $ idx - 1) xScalingFactor yScalingFactor - - componentsInfo = map fetchTablesForComponent $ jpgComponents scanInfo - - let blockIndices = [(x,y) | y <- [0 .. verticalBlockCount - 1] - , x <- [0 .. horizontalBlockCount - 1] ] - blockBeforeRestart = mcuBeforeRestart - - folder f = foldM_ f blockBeforeRestart blockIndices - - folder (\resetCounter (x,y) -> do - when (resetCounter == 0) - (do forM_ [0.. compCount - 1] $ - \c -> lift $ (dcArray `M.unsafeWrite` c) 0 - byteAlignJpg - _restartCode <- decodeRestartInterval - -- if 0xD0 <= restartCode && restartCode <= 0xD7 - return ()) - - let comp _ [] = return () - comp compIdx ((horizCount, vertCount, dcTree, acTree, qTable, unpack):comp_rest) = liner 0 - where liner yd | yd >= vertCount = comp (compIdx + 1) comp_rest - liner yd = columner 0 - where verticalLimited = yd == horizCount - 1 || y == verticalBlockCount - 1 - columner xd | xd >= horizCount = liner (yd + 1) - columner xd | (xd == horizCount - 1 && x == horizontalBlockCount - 1) || verticalLimited = do - dc <- lift $ dcArray `M.unsafeRead` compIdx - (dcCoeff, block) <- - decompressMacroBlock dcTree acTree qTable zigZagArray $ fromIntegral dc - lift $ unpackMacroBlock imgComponentCount compIdx (maxHorizFactor - horizCount + 1) - (maxVertFactor - vertCount + 1) - (x * horizCount + xd) (y * vertCount + yd) outImage block - lift $ (dcArray `M.unsafeWrite` compIdx) dcCoeff - columner $ xd + 1 - - columner xd = do - dc <- lift $ dcArray `M.unsafeRead` compIdx - (dcCoeff, block) <- - decompressMacroBlock dcTree acTree qTable zigZagArray $ fromIntegral dc - _ <- lift $ unpack (x * horizCount + xd) (y * vertCount + yd) outImage block - lift $ (dcArray `M.unsafeWrite` compIdx) dcCoeff - columner $ xd + 1 - comp 0 componentsInfo - - if resetCounter /= 0 then return $ resetCounter - 1 - -- we use blockBeforeRestart - 1 to count - -- the current MCU - else return $ blockBeforeRestart - 1) - -allElementsEqual :: (Eq a) => [a] -> Bool -allElementsEqual [] = True -allElementsEqual (x:xs) = all (== x) xs - -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 - - --- | 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" - (_, Just ProgressiveDCT) -> Left "Unsupported Progressive JPEG image" - (1, _) -> Right . ImageY8 $ Image imgWidth imgHeight pixelData - (3, _) -> Right . ImageYCbCr8 $ Image imgWidth imgHeight pixelData - _ -> Left "Wrong component count" - - where (imgData:_) = [d | JpgScanBlob _kind d <- jpgFrame img] - (_, scanInfo) = gatherScanInfo img - compCount = length $ jpgComponents scanInfo - - imgKind = gatherImageKind $ jpgFrame img - imgWidth = fromIntegral $ jpgWidth scanInfo - imgHeight = fromIntegral $ jpgHeight scanInfo - - imageSize = imgWidth * imgHeight * compCount - - pixelData = runST $ VS.unsafeFreeze =<< S.evalStateT (do - resultImage <- lift $ M.new imageSize - let wrapped = MutableImage imgWidth imgHeight resultImage - setDecodedStringJpg . B.concat $ L.toChunks imgData - decodeImage img compCount wrapped - return resultImage) (BoolState (-1) 0 B.empty) - -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 * 8) + 7 < w && (by * 8) + 7 < h = do - let baseReadIdx = (by * 8 * w) + bx * 8 - sequence_ [(block `M.unsafeWrite` (y * 8 + x)) val - | y <- [0 .. 7] - , let blockReadIdx = baseReadIdx + y * w - , x <- [0 .. 7] - , 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 * 8 * sampWidth - blockYBegin = by * 8 * sampHeight - - sequence_ [(block `M.unsafeWrite` (y * 8 + 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, HuffmanTreeInfo) -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, HuffmanTreeInfo)] -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` (8 * maxSampling) - verticalMetaBlockCount = h `divUpward` (8 * 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 blockLine my | my >= verticalMetaBlockCount = return () - blockLine my = blockColumn 0 - where blockColumn mx | mx >= horizontalMetaBlockCount = blockLine (my + 1) - blockColumn mx = component $ zip [0..] componentDef - where component [] = blockColumn (mx + 1) - component ((comp, (sizeX, sizeY, table, dc, ac)) : comp_rest) = line 0 - where xSamplingFactor = maxSampling - sizeX + 1 - ySamplingFactor = maxSampling - sizeY + 1 - extractor = extractBlock img block xSamplingFactor ySamplingFactor imageComponentCount - line subY | subY >= sizeY = component comp_rest - line subY = column 0 - where blockY = my * sizeY + subY - - column subX | subX >= sizeX = line (subY + 1) - column subX = do - let 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 - column $ subX + 1 - blockLine 0 - - finalizeBoolWriter writeState +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 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 +
Codec/Picture/Jpg/DefaultTable.hs view
@@ -14,6 +14,7 @@ , makeInverseTable , buildHuffmanTree , packHuffmanTree + , huffmanPackedDecode , defaultChromaQuantizationTable @@ -37,11 +38,13 @@ import Control.Monad.ST( runST ) import qualified Data.Vector.Storable as SV import qualified Data.Vector as V -import Data.Bits( unsafeShiftL, (.|.) ) +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 @@ -72,7 +75,7 @@ aux (Branch i1 i2@(Leaf _)) idx = do ix1 <- aux i1 (idx + 2) _ <- aux i2 (idx + 1) - (table `M.unsafeWrite` idx) (fromIntegral ix1) + (table `M.unsafeWrite` idx) . fromIntegral $ idx + 2 return ix1 aux (Branch i1 i2) idx = do @@ -137,6 +140,16 @@ 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
Codec/Picture/Jpg/FastDct.hs view
@@ -16,13 +16,13 @@ referenceDct :: MutableMacroBlock s Int32 -> MutableMacroBlock s Int16 -> ST s (MutableMacroBlock s Int32) -referenceDct workData block = forM_ [(u, v) | u <- [0 :: Int .. 7], v <- [0..7]] (\(u,v) -> do +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 * 8 + u)) . truncate $ (1 / 4) * c u * c v * val) + (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..7], y <- [0..7 :: Int]] $ \(x,y) -> do - sample <- fromIntegral <$> (block `M.unsafeRead` (y * 8 + x)) + 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 @@ -65,9 +65,9 @@ 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 _ 8 = return () + firstPass _ i | i == dctBlockSize = return () firstPass dataBlock i = do - let baseIdx = i * 8 + let baseIdx = i * dctBlockSize readAt idx = fromIntegral <$> sample_block `M.unsafeRead` (baseIdx + idx) mult = (*) writeAt idx n = (dataBlock `M.unsafeWrite` (baseIdx + idx)) n @@ -99,7 +99,7 @@ tmp3' = blk3 - blk4 -- Stage 4 and output - writeAt 0 $ (tmp10 + tmp11 - 8 * cENTERJSAMPLE) `unsafeShiftL` pASS1_BITS + writeAt 0 $ (tmp10 + tmp11 - dctBlockSize * cENTERJSAMPLE) `unsafeShiftL` pASS1_BITS writeAt 4 $ (tmp10 - tmp11) `unsafeShiftL` pASS1_BITS let z1 = mult (tmp12 + tmp13) fIX_0_541196100 @@ -138,10 +138,10 @@ secondPass :: M.STVector s Int32 -> Int -> ST s () secondPass _ (-1) = return () secondPass block i = do - let readAt idx = block `M.unsafeRead` ((7 - i) + idx * 8) + let readAt idx = block `M.unsafeRead` ((7 - i) + idx * dctBlockSize) mult = (*) - writeAt idx n = (block `M.unsafeWrite` (8 * idx + (7 - i))) n - writeAtPos idx n = (block `M.unsafeWrite` (8 * idx + (7 - i))) $ n `unsafeShiftR` (cONST_BITS + pASS1_BITS + 3) + 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
+ Codec/Picture/Jpg/Progressive.hs view
@@ -0,0 +1,309 @@+{-# 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 view
@@ -1,17 +1,523 @@+{-# 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) @@ -26,3 +532,15 @@ 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/Tiff.hs view
@@ -334,7 +334,7 @@ instance BinaryParam Endianness TiffTag where getP endianness = tagOfWord16 <$> getP endianness - putP endianness = putP endianness . word16OfTag + putP endianness = putP endianness . word16OfTag data ExtendedDirectoryData = ExtendedDataNone @@ -406,11 +406,11 @@ unLong _ (ExtendedDataLong v) = pure v unLong errMessage _ = fail errMessage -cleanImageFileDirectory :: ImageFileDirectory -> ImageFileDirectory -cleanImageFileDirectory ifd@(ImageFileDirectory { ifdCount = 1 }) = aux $ ifdType ifd +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 +cleanImageFileDirectory _ ifd = ifd instance BinaryParam Endianness ImageFileDirectory where getP endianness = @@ -433,7 +433,7 @@ 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 @@ -490,8 +490,10 @@ findIFDExtDefaultData d tag lst = case [v | v <- lst, ifdIdentifier v == tag] of [] -> pure d - (x:_) -> V.toList <$> - unLong ("Can't unlong " ++ (show tag)) (ifdExtended x) + (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. @@ -688,6 +690,45 @@ 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 @@ -789,7 +830,7 @@ inner (readIdx + 3) (writeIdx + 2 * stride) (line - 2) -data YCbCrSubsampling = YCbCrSubsampling +data YCbCrSubsampling = YCbCrSubsampling { ycbcrWidth :: !Int , ycbcrHeight :: !Int , ycbcrImageWidth :: !Int @@ -832,7 +873,7 @@ return $ readIndex + 1 foldM_ writer readIdx pixelIndices - + return $ readIdx + blockSize gatherStrips :: ( Unpackable comp @@ -951,8 +992,8 @@ ifdSize = 12 ifdCountSize = 2 nextOffsetSize = 4 - startExtended = initialOffset - + ifdElementCount * ifdSize + startExtended = initialOffset + + ifdElementCount * ifdSize + ifdCountSize + nextOffsetSize updater ix ifd@(ImageFileDirectory { ifdExtended = ExtendedDataAscii b }) = @@ -980,10 +1021,10 @@ ifdShorts TagBitsPerSample $ tiffBitsPerSample nfo ifdSingleLong TagSamplesPerPixel $ tiffSampleCount nfo ifdSingleLong TagRowPerStrip $ tiffRowPerStrip nfo - ifdShort TagPhotometricInterpretation - . packPhotometricInterpretation + ifdShort TagPhotometricInterpretation + . packPhotometricInterpretation $ tiffColorspace nfo - ifdShort TagPlanarConfiguration + ifdShort TagPlanarConfiguration . constantToPlaneConfiguration $ tiffPlaneConfiguration nfo ifdShort TagCompression . packCompression $ tiffCompression nfo @@ -1005,7 +1046,7 @@ skip . fromIntegral $ fromIntegral (hdrOffset hdr) - readed let endian = hdrEndianness hdr - ifd <- fmap cleanImageFileDirectory <$> getP endian + ifd <- fmap (cleanImageFileDirectory endian) <$> getP endian cleaned <- fetchExtended endian ifd let dataFind str tag = findIFDData str tag cleaned @@ -1036,13 +1077,15 @@ <*> (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 - , tiffRowPerStrip = rowPerStrip } - | rowPerStrip == 0 = fail "Invalid row per strip" | lst == V.singleton 8 && format == [TiffSampleUint] = let applyPalette = pixelMap (\v -> pixelAt p (fromIntegral v) 0) gathered :: Image Pixel8 @@ -1065,20 +1108,15 @@ pure . ImageRGB16 $ applyPalette gathered unpack file nfo@TiffInfo { tiffColorspace = TiffCMYK - , tiffRowPerStrip = rowPerStrip , tiffBitsPerSample = lst , tiffSampleFormat = format } - | rowPerStrip == 0 = fail "Invalid row per strip" | 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 - , tiffRowPerStrip = rowPerStrip - } - | rowPerStrip == 0 = fail "Invalid row per strip" +unpack file nfo@TiffInfo { tiffColorspace = TiffMonochromeWhite0 } | otherwise = do img <- unpack file (nfo { tiffColorspace = TiffMonochrome }) case img of @@ -1087,10 +1125,8 @@ _ -> pure img unpack file nfo@TiffInfo { tiffColorspace = TiffMonochrome - , tiffRowPerStrip = rowPerStrip , tiffBitsPerSample = lst , tiffSampleFormat = format } - | rowPerStrip == 0 = fail "Invalid row per strip" | lst == V.singleton 2 && all (TiffSampleUint ==) format = pure . ImageY8 . pixelMap (colorMap (64 *)) $ gatherStrips Pack2 file nfo | lst == V.singleton 4 && all (TiffSampleUint ==) format = @@ -1101,13 +1137,15 @@ 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 - , tiffRowPerStrip = rowPerStrip , tiffPlaneConfiguration = PlanarConfigContig , tiffSampleFormat = format } - | rowPerStrip == 0 = fail "Invalid row per strip" | lst == V.fromList [8, 8, 8] && all (TiffSampleUint ==) format = pure . ImageYCbCr8 $ gatherStrips cbcrConf file nfo where defaulting 0 = 2 @@ -1123,10 +1161,8 @@ } unpack file nfo@TiffInfo { tiffColorspace = TiffRGB - , tiffRowPerStrip = rowPerStrip , tiffBitsPerSample = lst , tiffSampleFormat = format } - | rowPerStrip == 0 = fail "Invalid row per strip" | 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 = @@ -1140,10 +1176,8 @@ | lst == V.fromList [16, 16, 16, 16] && all (TiffSampleUint ==) format = pure . ImageRGBA16 $ gatherStrips (0 :: Word16) file nfo unpack file nfo@TiffInfo { tiffColorspace = TiffMonochrome - , tiffRowPerStrip = rowPerStrip , tiffBitsPerSample = lst , tiffSampleFormat = format } - | rowPerStrip == 0 = fail "Invalid row per strip" -- some files are a little bit borked... | lst == V.fromList [8, 8, 8] && all (TiffSampleUint ==) format = pure . ImageRGB8 $ gatherStrips (0 :: Word8) file nfo @@ -1211,7 +1245,7 @@ encodeTiff :: forall px. (TiffSaveable px) => Image px -> Lb.ByteString encodeTiff img = runPut $ putP rawPixelData hdr where intSampleCount = componentCount (undefined :: px) - sampleCount = fromIntegral intSampleCount + sampleCount = fromIntegral intSampleCount sampleType = (undefined :: PixelBaseComponent px) pixelData = imageData img @@ -1226,7 +1260,7 @@ headerSize = 8 hdr = TiffInfo - { tiffHeader = TiffHeader + { tiffHeader = TiffHeader { hdrEndianness = EndianLittle , hdrOffset = headerSize + imageSize }
Codec/Picture/Types.hs view
@@ -22,6 +22,7 @@ -- ** Pixel types , Pixel8 , Pixel16 + , Pixel32 , PixelF , PixelYA8( .. ) , PixelYA16( .. ) @@ -79,7 +80,7 @@ import Control.Monad.Primitive ( PrimMonad, PrimState ) import Foreign.Storable ( Storable ) import Data.Bits( unsafeShiftL, unsafeShiftR ) -import Data.Word( Word8, Word16 ) +import Data.Word( Word8, Word16, Word32 ) import Data.List( foldl' ) import Data.Vector.Storable ( (!) ) import qualified Data.Vector.Storable as V @@ -361,6 +362,9 @@ -- | 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 @@ -767,6 +771,11 @@ computeLuma = id extractLumaPlane = id +instance LumaPlaneExtractable Pixel32 where + {-# INLINE computeLuma #-} + computeLuma = id + extractLumaPlane = id + instance LumaPlaneExtractable PixelF where {-# INLINE computeLuma #-} computeLuma = id @@ -885,6 +894,31 @@ 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
JuicyPixels.cabal view
@@ -1,5 +1,5 @@ Name: JuicyPixels -Version: 3.1.1.1 +Version: 3.1.2 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==>> @@ -15,8 +15,10 @@ Build-type: Simple -- Constraint on the version of Cabal needed to build this package. -Cabal-version: >= 1.10 +Cabal-version: >= 1.16 +Extra-source-files: changelog + Source-Repository head Type: git Location: git://github.com/Twinside/Juicy.Pixels.git @@ -24,7 +26,7 @@ Source-Repository this Type: git Location: git://github.com/Twinside/Juicy.Pixels.git - Tag: v3.1.1.1 + Tag: v3.1.2 Flag Mmap Description: Enable the file loading via mmap (memory map) @@ -46,7 +48,7 @@ Build-depends: base >= 4 && < 5, bytestring >= 0.9 && < 0.11, mtl >= 1.1 && < 2.2, - binary >= 0.6.4.0 && < 0.7.2, + binary >= 0.6.4.0 && < 0.8, zlib >= 0.5.3.1 && < 0.6, transformers >= 0.2.2 && < 0.4, vector >= 0.9 && < 0.11, @@ -62,6 +64,8 @@ Codec.Picture.Jpg.FastIdct, Codec.Picture.Jpg.FastDct, Codec.Picture.Jpg.Types, + Codec.Picture.Jpg.Common, + Codec.Picture.Jpg.Progressive, Codec.Picture.Gif.LZW, Codec.Picture.Png.Export, Codec.Picture.Png.Type,
+ changelog view
@@ -0,0 +1,71 @@+-*-change-log-*- + +v3.1.2 December 2013 + * Adding support for progressive jpeg. + * Adding support for plane separated MCU jpeg. + * Adding support for grayscale 32bit decoding (with reduced precision to + 16bits). + * Fixing erroneous bitmap decoding in case of excessive padding (#31). + +v3.1.1.1 October 2013 + * Fixing some spurious crash while decoding some JPEG image (#30). + +v3.1.1 October 2013 + * Adding uncompresed TIFF saving. + * Adding error message to avoid loading progressive loading. + * Made MMAP optional, controled by a cabal flag. + * adding dynamicPixelMap helper function. + * Handling png transparency using color key (#26). + +v3.1 June 2013 + * Adding basic handling of 16bits pixel types. + * Addition of Tiff reading: + - 2, 4, 8, 16 bit depth reading (planar and contiguous for each). + - CMYK, YCbCr, RGB, Paletted, Greyscale. + - Uncompressed, PackBits, LZW. + * Some new tiny helper functions (nothing too fancy). + * Huge performances improvement. + +v3.0 January 2013 + * Simplification of the 'Pixel' typeclass, removed many unused part. + * Removal of some Storable instances for pixel types. + * Amelioration of the documentation. + * Support for High Dynamic range images, opening support for different pixel + base component. + * Support for the Radiance file format (or RGBE, file extension .pic and .hdr). + * Dropped the cereal library in favor of the last version of Binary, present + in the Haskell platform. Every dependencies are now present in the platform. + +v2.0.2 October 2012 + * Decoding of interleaved gif image. + * Decoding delta coded gif animation. + * Bumping dependencies. + +v2.0.1 September 2012 + * Documentation enhancements. + * Fixing some huge gif file loading. + * Fixing performance problem of Bitmap and Jpeg savings. + +v2.0 September 2012 + * New extractComponent version with type safe plane extraction. + * Gif file reading. + +v1.3 June 2012 + * Fix extractComponent function. + * Adding saving for YA8 functions. + +v1.2.1 April 2012 + * Dependencies version bump. + * Dropping array dependency. + +v1.2 March 2012 + * Adding a generateImage helper function. + * Adding NFData instances for image. + * Adding JPEG writing. + +v1.1 February 2012 + * Switching to vector for arrays, big performance improvement. + +v1.0 January 2012 + * Initial release +