JuicyPixels 3.1 → 3.1.1
raw patch · 10 files changed
+804/−263 lines, 10 files
Files
- Codec/Picture.hs +28/−5
- Codec/Picture/Jpg.hs +135/−75
- Codec/Picture/Png.hs +31/−13
- Codec/Picture/Png/Export.hs +2/−2
- Codec/Picture/Png/Type.hs +12/−7
- Codec/Picture/Saving.hs +20/−0
- Codec/Picture/Tiff.hs +440/−132
- Codec/Picture/Types.hs +116/−24
- Codec/Picture/VectorByteConversion.hs +9/−1
- JuicyPixels.cabal +11/−4
Codec/Picture.hs view
@@ -23,6 +23,7 @@ , saveBmpImage , saveJpgImage , savePngImage + , saveTiffImage , saveRadianceImage -- * Specific image format functions @@ -56,7 +57,11 @@ , writeDynamicPng -- ** Tiff handling + , readTiff + , TiffSaveable , decodeTiff + , encodeTiff + , writeTiff -- ** HDR (Radiance/RGBE) handling , readHDR @@ -101,11 +106,16 @@ , encodeHDR , writeHDR ) -import Codec.Picture.Tiff( decodeTiff ) +import Codec.Picture.Tiff( decodeTiff + , TiffSaveable + , encodeTiff + , writeTiff ) import Codec.Picture.Saving import Codec.Picture.Types -- import System.IO ( withFile, IOMode(ReadMode) ) +#ifdef WITH_MMAP_BYTESTRING import System.IO.MMap ( mmapFileByteString ) +#endif import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L @@ -125,7 +135,12 @@ -> IO (Either String a) withImageDecoder decoder path = Exc.catch doit (\e -> return . Left $ show (e :: Exc.IOException)) - where doit = force . decoder <$> mmapFileByteString path Nothing + where doit = force . decoder <$> get +#ifdef WITH_MMAP_BYTESTRING + get = mmapFileByteString path Nothing +#else + get = B.readFile path +#endif -- force appeared in deepseq 1.3, Haskell Platform -- provide 1.1 force x = x `deepseq` x @@ -156,6 +171,10 @@ readGif :: FilePath -> IO (Either String DynamicImage) readGif = withImageDecoder decodeGif +-- | Helper function trying to load tiff file from a file on disk. +readTiff :: FilePath -> IO (Either String DynamicImage) +readTiff = withImageDecoder decodeTiff + -- | Helper function trying to load all the images of an animated -- gif file. readGifImages :: FilePath -> IO (Either String [Image PixelRGB8]) @@ -177,11 +196,15 @@ readHDR = withImageDecoder decodeHDR -- | Save an image to a '.jpg' file, will do everything it can to save an image. -saveJpgImage :: Int -> String -> DynamicImage -> IO () +saveJpgImage :: Int -> FilePath -> DynamicImage -> IO () saveJpgImage quality path img = L.writeFile path $ imageToJpg quality img +-- | Save an image to a '.tiff' file, will do everything it can to save an image. +saveTiffImage :: FilePath -> DynamicImage -> IO () +saveTiffImage path img = L.writeFile path $ imageToTiff img + -- | Save an image to a '.hdr' file, will do everything it can to save an image. -saveRadianceImage :: String -> DynamicImage -> IO () +saveRadianceImage :: FilePath -> DynamicImage -> IO () saveRadianceImage path = L.writeFile path . imageToRadiance -- | Save an image to a '.png' file, will do everything it can to save an image. @@ -189,7 +212,7 @@ -- -- > transcodeToPng :: FilePath -> FilePath -> IO () -- > transcodeToPng pathIn pathOut = do --- > eitherImg <- decodeImage pathIn +-- > eitherImg <- readImage pathIn -- > case eitherImg of -- > Left _ -> return () -- > Right img -> savePngImage pathOut img
Codec/Picture/Jpg.hs view
@@ -32,7 +32,7 @@ import Data.Binary.Put( Put , putWord8 , putWord16be - , putLazyByteString + , putLazyByteString ) import Data.Maybe( fromJust ) @@ -74,10 +74,12 @@ | JpgQuantizationTable | JpgHuffmanTableMarker | JpgStartOfScan + | JpgEndOfImage | JpgAppSegment Word8 | JpgExtensionSegment Word8 | JpgRestartInterval + | JpgRestartIntervalEnd Word8 deriving (Eq, Show) type HuffmanTreeInfo = HuffmanPackedTree @@ -103,7 +105,7 @@ deriving Show instance SizeCalculable JpgFrameHeader where - calculateSize hdr = 2 + 1 + 2 + 2 + 1 + calculateSize hdr = 2 + 1 + 2 + 2 + 1 + sum [calculateSize c | c <- jpgComponents hdr] data JpgComponent = JpgComponent @@ -156,7 +158,7 @@ + sum [calculateSize c | c <- scans hdr] + 2 + 1 - + data JpgQuantTableSpec = JpgQuantTableSpec { -- | Stored on 4 bits quantPrecision :: !Word8 @@ -315,6 +317,17 @@ 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 @@ -327,6 +340,7 @@ parseFrames case kind of + JpgEndOfImage -> return [] JpgAppSegment c -> (\frm lst -> JpgAppFrame c frm : lst) <$> takeCurrentFrame <*> parseNextFrame JpgExtensionSegment c -> @@ -340,32 +354,43 @@ JpgHuffmanTable [(t, packHuffmanTree . buildPackedHuffmanTree $ huffCodes t) | t <- huffTables] : lst) <$> get <*> parseNextFrame JpgStartOfScan -> - (\frm imgData -> [JpgScanBlob frm imgData]) - <$> get <*> getRemainingLazyBytes + (\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 JpgBaselineDCTHuffman = 0xC0 -secondStartOfFrameByteOfKind JpgExtendedSequentialDCTHuffman = 0xC1 -secondStartOfFrameByteOfKind JpgProgressiveDCTHuffman = 0xC2 -secondStartOfFrameByteOfKind JpgLosslessHuffman = 0xC3 -secondStartOfFrameByteOfKind JpgDifferentialSequentialDCTHuffman = 0xC5 -secondStartOfFrameByteOfKind JpgDifferentialProgressiveDCTHuffman = 0xC6 -secondStartOfFrameByteOfKind JpgDifferentialLosslessHuffman = 0xC7 -secondStartOfFrameByteOfKind JpgExtendedSequentialArithmetic = 0xC9 -secondStartOfFrameByteOfKind JpgProgressiveDCTArithmetic = 0xCA -secondStartOfFrameByteOfKind JpgLosslessArithmetic = 0xCB -secondStartOfFrameByteOfKind JpgHuffmanTableMarker = 0xC4 -secondStartOfFrameByteOfKind JpgDifferentialSequentialDCTArithmetic = 0xCD -secondStartOfFrameByteOfKind JpgDifferentialProgressiveDCTArithmetic = 0xCE -secondStartOfFrameByteOfKind JpgDifferentialLosslessArithmetic = 0xCF -secondStartOfFrameByteOfKind JpgQuantizationTable = 0xDB -secondStartOfFrameByteOfKind JpgStartOfScan = 0xDA -secondStartOfFrameByteOfKind JpgRestartInterval = 0xDD -secondStartOfFrameByteOfKind (JpgAppSegment a) = a -secondStartOfFrameByteOfKind (JpgExtensionSegment a) = a +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 @@ -387,11 +412,13 @@ 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 @@ -478,6 +505,7 @@ specBeg <- get specEnd <- get (approxHigh, approxLow) <- get4BitOfEach + return JpgScanHeader { scanLength = thisScanLength, scanComponentCount = compCount, @@ -651,6 +679,12 @@ 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 @@ -672,8 +706,10 @@ gatherQuantTables img = concat [t | JpgQuantTable t <- jpgFrame img] gatherHuffmanTables :: JpgImage -> [(JpgHuffmanTableSpec, HuffmanTreeInfo)] -gatherHuffmanTables img = concat [lst | JpgHuffmanTable lst <- jpgFrame img] +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 @@ -699,7 +735,7 @@ 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) + val <- pixelClamp <$> (block `M.unsafeRead` readIdx) (img `M.unsafeWrite` idx) val blockHoriz (idx + 1) (readIdx + 1) $ i + 1 @@ -709,7 +745,7 @@ -> MutableImage s PixelYCbCr8 -> MutableMacroBlock s Int16 -> ST s () -unpack444Ycbcr compIdx x y +unpack444Ycbcr compIdx x y (MutableImage { mutableImageWidth = imgWidth, mutableImageData = img }) block = blockVert baseIdx 0 zero where zero = 0 :: Int @@ -717,14 +753,14 @@ 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)) + 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 @@ -750,7 +786,7 @@ -> MutableImage s PixelYCbCr8 -> MutableMacroBlock s Int16 -> ST s () -unpack422Ycbcr compIdx x y +unpack422Ycbcr compIdx x y (MutableImage { mutableImageWidth = imgWidth, mutableImageHeight = _, mutableImageData = img }) block = blockVert baseIdx 0 zero @@ -760,7 +796,7 @@ blockVert _ _ j | j >= 8 = return () blockVert idx readIdx j = do - v0 <- pixelClamp <$> (block `M.unsafeRead` readIdx) + 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)) @@ -808,7 +844,7 @@ -> MutableImage s PixelYCbCr8 -> MutableMacroBlock s Int16 -> ST s () -unpackMacroBlock compCount compIdx wCoeff hCoeff x y +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) $ @@ -849,7 +885,7 @@ else return (-1) -} -decodeImage :: JpgImage +decodeImage :: JpgImage -> Int -- ^ Component count -> MutableImage s PixelYCbCr8 -- ^ Result image to write into -> BoolReader s () @@ -859,48 +895,57 @@ let huffmans = gatherHuffmanTables img huffmanForComponent dcOrAc dest = - head [t | (h,t) <- huffmans, huffmanTableClass h == dcOrAc - , huffmanTableDest h == 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 = - head [quantTable q | q <- quants, quantDestination q == dest] - - hdr = head [h | JpgScanBlob h _ <- jpgFrame img] - + 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 = head [c | c <- scans hdr, componentSelector c == idx] + 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 @@ -916,11 +961,11 @@ unpacker = unpackerDecision xScalingFactor yScalingFactor - unpackerDecision 1 1 | isImageLumanOnly = unpack444Y + 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] @@ -971,6 +1016,16 @@ 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' @@ -985,15 +1040,18 @@ decodeJpeg :: B.ByteString -> Either String DynamicImage decodeJpeg file = case runGetStrict get file of Left err -> Left err - Right img -> case compCount of - 1 -> Right . ImageY8 $ Image imgWidth imgHeight pixelData - 3 -> Right . ImageYCbCr8 $ Image imgWidth imgHeight pixelData + 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 @@ -1100,7 +1158,7 @@ prepareHuffmanTable :: DctComponent -> Word8 -> HuffmanTable -> (JpgHuffmanTableSpec, HuffmanTreeInfo) -prepareHuffmanTable classVal dest tableDef = +prepareHuffmanTable classVal dest tableDef = (JpgHuffmanTableSpec { huffmanTableClass = classVal , huffmanTableDest = dest , huffSizes = sizes @@ -1108,7 +1166,7 @@ [VU.fromListN (fromIntegral $ sizes ! i) lst | (i, lst) <- zip [0..] tableDef ] }, VS.singleton 0) - where sizes = VU.fromListN 16 $ map (fromIntegral . length) tableDef + 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 @@ -1116,6 +1174,14 @@ 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). @@ -1125,16 +1191,10 @@ encodeJpegAtQuality quality img@(Image { imageWidth = w, imageHeight = h }) = encode finalImage where finalImage = JpgImage [ JpgQuantTable quantTables , JpgScans JpgBaselineDCTHuffman hdr - , JpgHuffmanTable huffTables + , JpgHuffmanTable defaultHuffmanTables , JpgScanBlob scanHeader encodedImage ] - huffTables = [ prepareHuffmanTable DcComponent 0 defaultDcLumaHuffmanTable - , prepareHuffmanTable AcComponent 0 defaultAcLumaHuffmanTable - , prepareHuffmanTable DcComponent 1 defaultDcChromaHuffmanTable - , prepareHuffmanTable AcComponent 1 defaultAcChromaHuffmanTable - ] - outputComponentCount = 3 scanHeader = scanHeader'{ scanLength = fromIntegral $ calculateSize scanHeader' } @@ -1161,7 +1221,7 @@ } hdr = hdr' { jpgFrameHeaderLength = fromIntegral $ calculateSize hdr' } - hdr' = JpgFrameHeader{ jpgFrameHeaderLength = 0 + hdr' = JpgFrameHeader { jpgFrameHeaderLength = 0 , jpgSamplePrecision = 8 , jpgHeight = fromIntegral h , jpgWidth = fromIntegral w @@ -1186,12 +1246,12 @@ } lumaQuant = scaleQuantisationMatrix (fromIntegral quality) - defaultLumaQuantizationTable + defaultLumaQuantizationTable chromaQuant = scaleQuantisationMatrix (fromIntegral quality) defaultChromaQuantizationTable zigzagedLumaQuant = zigZagReorderForwardv lumaQuant - zigzagedChromaQuant = zigZagReorderForwardv chromaQuant + zigzagedChromaQuant = zigZagReorderForwardv chromaQuant quantTables = [ JpgQuantTableSpec { quantPrecision = 0, quantDestination = 0 , quantTable = zigzagedLumaQuant } , JpgQuantTableSpec { quantPrecision = 0, quantDestination = 1 @@ -1209,7 +1269,7 @@ , makeInverseTable defaultDcChromaHuffmanTree , makeInverseTable defaultAcChromaHuffmanTree) componentDef = [lumaSamplingSize, chromaSamplingSize, chromaSamplingSize] - + imageComponentCount = length componentDef dc_table <- M.replicate 3 0 @@ -1227,16 +1287,16 @@ 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 + 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 >>= + (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
Codec/Picture/Png.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE ViewPatterns #-} -- | Module used for loading & writing \'Portable Network Graphics\' (PNG) -- files. The API has two layers, the high level, which load the image without -- looking deeply about it and the low level, allowing access to data chunks contained @@ -402,6 +403,13 @@ pixels = concat [[r, g, b] | ipx <- V.toList img , let PixelRGB8 r g b = pixelAt pal (fromIntegral ipx) 0] +applyPaletteWithTransparency :: PngPalette -> Word8 -> V.Vector Word8 -> V.Vector Word8 +applyPaletteWithTransparency pal transp img = V.fromListN ((initSize + 1) * 4) pixels + where (_, initSize) = bounds img + pixels = concat [ if ipx == transp then [0, 0, 0, 0] else [r, g, b, 255] + | ipx <- V.toList img + , let PixelRGB8 r g b = pixelAt pal (fromIntegral ipx) 0] + -- | Transform a raw png image to an image, without modifying the -- underlying pixel type. If the image is greyscale and < 8 bits, -- a transformation to RGBA8 is performed. This should change @@ -442,31 +450,41 @@ toImage _const1 const2 (Right a) = Right . const2 $ Image (fromIntegral w) (fromIntegral h) a - palette8 palette (Left img) = + palette8 palette [(Lb.uncons -> Just (c,_))] (Left img) = + Right . ImageRGBA8 + . Image (fromIntegral w) (fromIntegral h) + $ applyPaletteWithTransparency palette c img + + palette8 palette _ (Left img) = Right . ImageRGB8 . Image (fromIntegral w) (fromIntegral h) $ applyPalette palette img - palette8 _ (Right _) = Left "Invalid bit depth for paleted image" - unparse _ PngGreyscale bytes - | bitDepth ihdr == 1 = unparse (Just paletteRGBA1) PngIndexedColor bytes - | bitDepth ihdr == 2 = unparse (Just paletteRGBA2) PngIndexedColor bytes - | bitDepth ihdr == 4 = unparse (Just paletteRGBA4) PngIndexedColor bytes + palette8 _ _ (Right _) = Left "Invalid bit depth for paleted image" + + transparencyColor = + [ chunkData chunk | chunk <- chunks rawImg + , chunkType chunk == tRNSSignature ] + + unparse _ t PngGreyscale bytes + | bitDepth ihdr == 1 = unparse (Just paletteRGBA1) t PngIndexedColor bytes + | bitDepth ihdr == 2 = unparse (Just paletteRGBA2) t PngIndexedColor bytes + | bitDepth ihdr == 4 = unparse (Just paletteRGBA4) t PngIndexedColor bytes | otherwise = toImage ImageY8 ImageY16 $ runST stArray where stArray = deinterlacer ihdr bytes - unparse Nothing PngIndexedColor _ = Left "no valid palette found" - unparse _ PngTrueColour bytes = + unparse Nothing _ PngIndexedColor _ = Left "no valid palette found" + unparse _ _ PngTrueColour bytes = toImage ImageRGB8 ImageRGB16 $ runST stArray where stArray = deinterlacer ihdr bytes - unparse _ PngGreyscaleWithAlpha bytes = + unparse _ _ PngGreyscaleWithAlpha bytes = toImage ImageYA8 ImageYA16 $ runST stArray where stArray = deinterlacer ihdr bytes - unparse _ PngTrueColourWithAlpha bytes = + unparse _ _ PngTrueColourWithAlpha bytes = toImage ImageRGBA8 ImageRGBA16 $ runST stArray where stArray = deinterlacer ihdr bytes - unparse (Just plte) PngIndexedColor bytes = - palette8 plte $ runST stArray + unparse (Just plte) transparency PngIndexedColor bytes = + palette8 plte transparency $ runST stArray where stArray = deinterlacer ihdr bytes if Lb.length compressedImageData <= zlibHeaderSize @@ -478,5 +496,5 @@ Just p -> case parsePalette p of Left _ -> Nothing Right plte -> Just plte - in unparse palette (colourType ihdr) parseableData + in unparse palette transparencyColor (colourType ihdr) parseableData
Codec/Picture/Png/Export.hs view
@@ -23,12 +23,12 @@ import Codec.Picture.Types import Codec.Picture.Png.Type -import Codec.Picture.VectorByteConversion +import Codec.Picture.VectorByteConversion( blitVector ) -- | Encode an image into a png if possible. class PngSavable a where -- | Transform an image into a png encoded bytestring, ready - -- to be writte as a file. + -- to be written as a file. encodePng :: Image a -> Lb.ByteString preparePngHeader :: Image a -> PngImageType -> Word8 -> PngIHdr
Codec/Picture/Png/Type.hs view
@@ -9,6 +9,7 @@ , pLTESignature , iDATSignature , iENDSignature + , tRNSSignature -- * Low level types , ChunkSignature , PngRawImage( .. ) @@ -35,6 +36,7 @@ import Data.List( foldl' ) import Data.Word( Word32, Word8 ) import qualified Data.ByteString.Lazy as L +import qualified Data.ByteString.Lazy.Char8 as LS import Codec.Picture.Types import Codec.Picture.InternalHelper @@ -264,29 +266,32 @@ -- | Signature signalling that the following data will be a png image -- in the png bit stream pngSignature :: ChunkSignature -pngSignature = signature [137, 80, 78, 71, 13, 10, 26, 10] +pngSignature = L.pack [137, 80, 78, 71, 13, 10, 26, 10] -- | Helper function to help pack signatures. -signature :: [Word8] -> ChunkSignature -signature = L.pack . map (toEnum . fromEnum) +signature :: String -> ChunkSignature +signature = LS.pack -- | Signature for the header chunk of png (must be the first) iHDRSignature :: ChunkSignature -iHDRSignature = signature [73, 72, 68, 82] +iHDRSignature = signature "IHDR" -- | Signature for a palette chunk in the pgn file. Must -- occure before iDAT. pLTESignature :: ChunkSignature -pLTESignature = signature [80, 76, 84, 69] +pLTESignature = signature "PLTE" -- | Signature for a data chuck (with image parts in it) iDATSignature :: ChunkSignature -iDATSignature = signature [73, 68, 65, 84] +iDATSignature = signature "IDAT" -- | Signature for the last chunk of a png image, telling -- the end. iENDSignature :: ChunkSignature -iENDSignature = signature [73, 69, 78, 68] +iENDSignature = signature "IEND" + +tRNSSignature :: ChunkSignature +tRNSSignature = signature "tRNS" instance Binary PngImageType where put PngGreyscale = putWord8 0
Codec/Picture/Saving.hs view
@@ -4,6 +4,7 @@ module Codec.Picture.Saving( imageToJpg , imageToPng , imageToBitmap + , imageToTiff , imageToRadiance ) where @@ -15,6 +16,7 @@ import Codec.Picture.Png import Codec.Picture.HDR import Codec.Picture.Types +import Codec.Picture.Tiff import qualified Data.Vector.Storable as V @@ -124,6 +126,24 @@ imageToPng (ImageYA16 img) = encodePng img imageToPng (ImageRGB16 img) = encodePng img imageToPng (ImageRGBA16 img) = encodePng img + +-- | This function will try to do anything to encode an image +-- as a Tiff, make all color conversion and such. Equivalent +-- of 'decodeImage' for Tiff encoding +imageToTiff :: DynamicImage -> L.ByteString +imageToTiff (ImageYCbCr8 img) = encodeTiff img +imageToTiff (ImageCMYK8 img) = encodeTiff img +imageToTiff (ImageCMYK16 img) = encodeTiff img +imageToTiff (ImageRGB8 img) = encodeTiff img +imageToTiff (ImageRGBF img) = encodeTiff $ toStandardDef img +imageToTiff (ImageRGBA8 img) = encodeTiff img +imageToTiff (ImageY8 img) = encodeTiff img +imageToTiff (ImageYF img) = encodeTiff $ greyScaleToStandardDef img +imageToTiff (ImageYA8 img) = encodeTiff $ dropAlphaLayer img +imageToTiff (ImageY16 img) = encodeTiff img +imageToTiff (ImageYA16 img) = encodeTiff $ dropAlphaLayer img +imageToTiff (ImageRGB16 img) = encodeTiff img +imageToTiff (ImageRGBA16 img) = encodeTiff img -- | This function will try to do anything to encode an image -- as bitmap, make all color conversion and such. Equivalent
Codec/Picture/Tiff.hs view
@@ -2,6 +2,8 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE ScopedTypeVariables #-} -- | Module implementing TIFF decoding. -- -- Supported compression schemes : @@ -22,11 +24,12 @@ -- -- * 16 bits -- -module Codec.Picture.Tiff( decodeTiff ) where +module Codec.Picture.Tiff( decodeTiff, TiffSaveable, encodeTiff, writeTiff ) where import Control.Applicative( (<$>), (<*>), pure ) import Control.Monad( when, replicateM, foldM_ ) import Control.Monad.ST( ST, runST ) +import Control.Monad.Writer.Strict( execWriter, tell, Writer ) import Data.Int( Int8 ) import Data.Word( Word8, Word16 ) import Data.Bits( (.&.), (.|.), unsafeShiftL, unsafeShiftR ) @@ -38,24 +41,28 @@ , skip , getByteString ) -import Data.Binary.Put( Put +import Data.Binary.Put( Put, runPut , putWord16le, putWord16be - , putWord32le, putWord32be ) + , putWord32le, putWord32be + , putByteString + ) +import Data.List( sortBy, mapAccumL ) import qualified Data.Vector as V import qualified Data.Vector.Storable as VS import qualified Data.Vector.Storable.Mutable as M import Data.Word( Word32 ) import qualified Data.ByteString as B +import qualified Data.ByteString.Lazy as Lb import qualified Data.ByteString.Unsafe as BU -{-import Debug.Trace-} -{-import Text.Groom-} +import Foreign.Storable( sizeOf ) import Codec.Picture.InternalHelper import Codec.Picture.BitWriter import Codec.Picture.Types import Codec.Picture.Gif.LZW +import Codec.Picture.VectorByteConversion( toByteString ) data Endianness = EndianLittle | EndianBig @@ -72,46 +79,52 @@ 0x4D4D -> return EndianBig _ -> fail "Invalid endian tag value" +-- | Because having a polymorphic get with endianness is to nice +-- to pass on, introducing this helper type class, which is just +-- a superset of Binary, but formalising a parameter passing +-- into it. +class BinaryParam a b where + getP :: a -> Get b + putP :: a -> b -> Put + data TiffHeader = TiffHeader { hdrEndianness :: !Endianness , hdrOffset :: {-# UNPACK #-} !Word32 } deriving (Eq, Show) -putWord16Endian :: Endianness -> Word16 -> Put -putWord16Endian EndianLittle = putWord16le -putWord16Endian EndianBig = putWord16be +instance BinaryParam Endianness Word16 where + putP EndianLittle = putWord16le + putP EndianBig = putWord16be -getWord16Endian :: Endianness -> Get Word16 -getWord16Endian EndianLittle = getWord16le -getWord16Endian EndianBig = getWord16be + getP EndianLittle = getWord16le + getP EndianBig = getWord16be -putWord32Endian :: Endianness -> Word32 -> Put -putWord32Endian EndianLittle = putWord32le -putWord32Endian EndianBig = putWord32be +instance BinaryParam Endianness Word32 where + putP EndianLittle = putWord32le + putP EndianBig = putWord32be -getWord32Endian :: Endianness -> Get Word32 -getWord32Endian EndianLittle = getWord32le -getWord32Endian EndianBig = getWord32be + getP EndianLittle = getWord32le + getP EndianBig = getWord32be instance Binary TiffHeader where put hdr = do let endian = hdrEndianness hdr put endian - putWord16Endian endian 42 - putWord32Endian endian $ hdrOffset hdr + putP endian (42 :: Word16) + putP endian $ hdrOffset hdr get = do endian <- get - magic <- getWord16Endian endian - when (magic /= 42) + magic <- getP endian + let magicValue = 42 :: Word16 + when (magic /= magicValue) (fail "Invalid TIFF magic number") - TiffHeader endian <$> getWord32Endian endian + TiffHeader endian <$> getP endian data TiffPlanarConfiguration = PlanarConfigContig -- = 1 | PlanarConfigSeparate -- = 2 - deriving (Eq, Show) planarConfgOfConstant :: Word32 -> Get TiffPlanarConfiguration planarConfgOfConstant 0 = pure PlanarConfigContig @@ -119,13 +132,16 @@ planarConfgOfConstant 2 = pure PlanarConfigSeparate planarConfgOfConstant v = fail $ "Unknown planar constant (" ++ show v ++ ")" +constantToPlaneConfiguration :: TiffPlanarConfiguration -> Word16 +constantToPlaneConfiguration PlanarConfigContig = 1 +constantToPlaneConfiguration PlanarConfigSeparate = 2 + data TiffCompression = CompressionNone -- 1 | CompressionModifiedRLE -- 2 | CompressionLZW -- 5 | CompressionJPEG -- 6 | CompressionPackBit -- 32273 - deriving (Eq, Show) data IfdType = TypeByte | TypeAscii @@ -139,62 +155,64 @@ | TypeSignedRational | TypeFloat | TypeDouble - deriving (Eq, Show) -{- -wordOfType :: IfdType -> Word16 -wordOfType TypeByte = 1 -wordOfType TypeAscii = 2 -wordOfType TypeShort = 3 -wordOfType TypeLong = 4 -wordOfType TypeRational = 5 -wordOfType TypeSByte = 6 -wordOfType TypeUndefined = 7 -wordOfType TypeSignedShort = 8 -wordOfType TypeSignedLong = 9 -wordOfType TypeSignedRational = 10 -wordOfType TypeFloat = 11 -wordOfType TypeDouble = 12 - -- -} +instance BinaryParam Endianness IfdType where + getP endianness = getP endianness >>= conv + where + conv :: Word16 -> Get IfdType + conv 1 = return TypeByte + conv 2 = return TypeAscii + conv 3 = return TypeShort + conv 4 = return TypeLong + conv 5 = return TypeRational + conv 6 = return TypeSByte + conv 7 = return TypeUndefined + conv 8 = return TypeSignedShort + conv 9 = return TypeSignedLong + conv 10 = return TypeSignedRational + conv 11 = return TypeFloat + conv 12 = return TypeDouble + conv _ = fail "Invalid TIF directory type" -typeOfWord :: Word16 -> Get IfdType -typeOfWord 1 = return TypeByte -typeOfWord 2 = return TypeAscii -typeOfWord 3 = return TypeShort -typeOfWord 4 = return TypeLong -typeOfWord 5 = return TypeRational -typeOfWord 6 = return TypeSByte -typeOfWord 7 = return TypeUndefined -typeOfWord 8 = return TypeSignedShort -typeOfWord 9 = return TypeSignedLong -typeOfWord 10 = return TypeSignedRational -typeOfWord 11 = return TypeFloat -typeOfWord 12 = return TypeDouble -typeOfWord _ = fail "Invalid TIF directory type" + putP endianness = putP endianness . conv + where + conv :: IfdType -> Word16 + conv TypeByte = 1 + conv TypeAscii = 2 + conv TypeShort = 3 + conv TypeLong = 4 + conv TypeRational = 5 + conv TypeSByte = 6 + conv TypeUndefined = 7 + conv TypeSignedShort = 8 + conv TypeSignedLong = 9 + conv TypeSignedRational = 10 + conv TypeFloat = 11 + conv TypeDouble = 12 data TiffTag = TagPhotometricInterpretation - | TagCompression - | TagImageWidth - | TagImageLength - | TagXResolution - | TagYResolution - | TagResolutionUnit - | TagRowPerStrip - | TagStripByteCounts - | TagStripOffsets - | TagBitsPerSample - | TagColorMap + | TagCompression -- ^ Short type + | TagImageWidth -- ^ Short or long type + | TagImageLength -- ^ Short or long type + | TagXResolution -- ^ Rational type + | TagYResolution -- ^ Rational type + | TagResolutionUnit -- ^ Short type + | TagRowPerStrip -- ^ Short or long type + | TagStripByteCounts -- ^ Short or long + | TagStripOffsets -- ^ Short or long + | TagBitsPerSample -- ^ Short + | TagColorMap -- ^ Short | TagTileWidth | TagTileLength | TagTileOffset | TagTileByteCount - | TagSamplesPerPixel + | TagSamplesPerPixel -- ^ Short | TagArtist | TagDocumentName | TagSoftware - | TagPlanarConfiguration + | TagPlanarConfiguration -- ^ Short | TagOrientation - | TagSampleFormat + | TagSampleFormat -- ^ Short | TagInkSet | TagSubfileType | TagFillOrder @@ -267,6 +285,57 @@ aux 532 = TagReferenceBlackWhite aux v = TagUnknown v +word16OfTag :: TiffTag -> Word16 +word16OfTag = aux + where aux TagSubfileType = 255 + aux TagImageWidth = 256 + aux TagImageLength = 257 + aux TagBitsPerSample = 258 + aux TagCompression = 259 + aux TagPhotometricInterpretation = 262 + aux TagFillOrder = 266 + aux TagDocumentName = 269 + aux TagImageDescription = 270 + aux TagStripOffsets = 273 + aux TagOrientation = 274 + aux TagSamplesPerPixel = 277 + aux TagRowPerStrip = 278 + aux TagStripByteCounts = 279 + aux TagXResolution = 282 + aux TagYResolution = 283 + aux TagPlanarConfiguration = 284 + aux TagXPosition = 286 + aux TagYPosition = 287 + aux TagResolutionUnit = 296 + aux TagSoftware = 305 + aux TagArtist = 315 + aux TagColorMap = 320 + aux TagTileWidth = 322 + aux TagTileLength = 323 + aux TagTileOffset = 324 + aux TagTileByteCount = 325 + aux TagInkSet = 332 + aux TagExtraSample = 338 + aux TagSampleFormat = 339 + aux TagYCbCrCoeff = 529 + aux TagJpegProc = 512 + aux TagJPEGInterchangeFormat = 513 + aux TagJPEGInterchangeFormatLength = 514 + aux TagJPEGRestartInterval = 515 + aux TagJPEGLosslessPredictors = 517 + aux TagJPEGPointTransforms = 518 + aux TagJPEGQTables = 519 + aux TagJPEGDCTables = 520 + aux TagJPEGACTables = 521 + aux TagYCbCrSubsampling = 530 + aux TagYCbCrPositioning = 531 + aux TagReferenceBlackWhite = 532 + aux (TagUnknown v) = v + +instance BinaryParam Endianness TiffTag where + getP endianness = tagOfWord16 <$> getP endianness + putP endianness = putP endianness . word16OfTag + data ExtendedDirectoryData = ExtendedDataNone | ExtendedDataAscii !B.ByteString @@ -274,20 +343,55 @@ | ExtendedDataLong !(V.Vector Word32) deriving (Eq, Show) +instance BinaryParam (Endianness, ImageFileDirectory) ExtendedDirectoryData where + putP (endianness, _) = dump + where + dump ExtendedDataNone = pure () + dump (ExtendedDataAscii bstr) = putByteString bstr + dump (ExtendedDataShort shorts) = V.mapM_ (putP endianness) shorts + dump (ExtendedDataLong longs) = V.mapM_ (putP endianness) longs + + getP (endianness, ifd) = fetcher ifd + where + align ImageFileDirectory { ifdOffset = offset } = do + readed <- bytesRead + skip . fromIntegral $ fromIntegral offset - readed + + getE :: (BinaryParam Endianness a) => Get a + getE = getP endianness + + getVec count = V.replicateM (fromIntegral count) + + fetcher ImageFileDirectory { ifdType = TypeAscii, ifdCount = count } | count > 1 = + align ifd >> (ExtendedDataAscii <$> getByteString (fromIntegral count)) + fetcher ImageFileDirectory { ifdType = TypeShort, ifdCount = 2, ifdOffset = ofs } = + pure . ExtendedDataShort $ V.fromListN 2 valList + where high = fromIntegral $ ofs `unsafeShiftR` 16 + low = fromIntegral $ ofs .&. 0xFFFF + valList = case endianness of + EndianLittle -> [low, high] + EndianBig -> [high, low] + fetcher ImageFileDirectory { ifdType = TypeShort, ifdCount = count } | count > 2 = + align ifd >> (ExtendedDataShort <$> getVec count getE) + fetcher ImageFileDirectory { ifdType = TypeLong, ifdCount = count } | count > 1 = + align ifd >> (ExtendedDataLong <$> getVec count getE) + fetcher _ = pure ExtendedDataNone + data TiffSampleFormat = TiffSampleUint | TiffSampleInt | TiffSampleDouble | TiffSampleUnknown - deriving (Eq, Show) + deriving Eq unpackSampleFormat :: Word32 -> Get TiffSampleFormat unpackSampleFormat = aux - where aux 1 = pure TiffSampleUint - aux 2 = pure TiffSampleInt - aux 3 = pure TiffSampleDouble - aux 4 = pure TiffSampleUnknown - aux v = fail $ "Undefined data format (" ++ show v ++ ")" + where + aux 1 = pure TiffSampleUint + aux 2 = pure TiffSampleInt + aux 3 = pure TiffSampleDouble + aux 4 = pure TiffSampleUnknown + aux v = fail $ "Undefined data format (" ++ show v ++ ")" data ImageFileDirectory = ImageFileDirectory { ifdIdentifier :: !TiffTag @@ -296,7 +400,6 @@ , ifdOffset :: !Word32 , ifdExtended :: !ExtendedDirectoryData } - deriving (Eq, Show) unLong :: String -> ExtendedDirectoryData -> Get (V.Vector Word32) unLong _ (ExtendedDataShort v) = pure $ V.map fromIntegral v @@ -309,46 +412,38 @@ aux _ = ifd cleanImageFileDirectory ifd = ifd -getImageFileDirectory :: Endianness -> Get ImageFileDirectory -getImageFileDirectory endianness = - ImageFileDirectory <$> (tagOfWord16 <$> getWord16) - <*> (getWord16 >>= typeOfWord) - <*> getWord32 - <*> getWord32 - <*> pure ExtendedDataNone - where getWord16 = getWord16Endian endianness - getWord32 = getWord32Endian endianness - -getImageFileDirectories :: Endianness -> Get [ImageFileDirectory] -getImageFileDirectories endianness = do - count <- getWord16Endian endianness - replicateM (fromIntegral count) $ getImageFileDirectory endianness - -fetchExtended :: Endianness -> [ImageFileDirectory] -> Get [ImageFileDirectory] -fetchExtended endian = mapM fetcher - where align ImageFileDirectory { ifdOffset = offset } = do - readed <- bytesRead - skip . fromIntegral $ fromIntegral offset - readed - - getWord16 = getWord16Endian endian - getWord32 = getWord32Endian endian - - update ifd v = ifd { ifdExtended = v } - getVec count = V.replicateM (fromIntegral count) +instance BinaryParam Endianness ImageFileDirectory where + getP endianness = + ImageFileDirectory <$> getE <*> getE <*> getE <*> getE + <*> pure ExtendedDataNone + where getE :: (BinaryParam Endianness a) => Get a + getE = getP endianness - fetcher ifd@ImageFileDirectory { ifdType = TypeAscii, ifdCount = count } | count > 1 = - align ifd >> (update ifd . ExtendedDataAscii <$> getByteString (fromIntegral count)) - fetcher ifd@ImageFileDirectory { ifdType = TypeShort, ifdCount = 2, ifdOffset = ofs } = - pure . update ifd . ExtendedDataShort $ V.fromListN 2 [high, low] - where high = fromIntegral $ ofs `unsafeShiftL` 16 - low = fromIntegral $ ofs .&. 0xFFFF + putP endianness ifd =do + let putE :: (BinaryParam Endianness a) => a -> Put + putE = putP endianness + putE $ ifdIdentifier ifd + putE $ ifdType ifd + putE $ ifdCount ifd + putE $ ifdOffset ifd - fetcher ifd@ImageFileDirectory { ifdType = TypeShort, ifdCount = count } | count > 2 = - align ifd >> (update ifd . ExtendedDataShort <$> getVec count getWord16) - fetcher ifd@ImageFileDirectory { ifdType = TypeLong, ifdCount = count } | count > 1 = - align ifd >> (update ifd . ExtendedDataLong <$> getVec count getWord32) +instance BinaryParam Endianness [ImageFileDirectory] where + getP endianness = do + count <- getP endianness :: Get Word16 + rez <- replicateM (fromIntegral count) $ getP endianness + _ <- getP endianness :: Get Word32 + pure rez + + putP endianness lst = do + let count = fromIntegral $ length lst :: Word16 + putP endianness count + mapM_ (putP endianness) lst + putP endianness (0 :: Word32) - fetcher ifd = pure ifd +fetchExtended :: Endianness -> [ImageFileDirectory] -> Get [ImageFileDirectory] +fetchExtended endian = mapM $ \ifd -> do + v <- getP (endian, ifd) + pure $ ifd { ifdExtended = v } findIFD :: String -> TiffTag -> [ImageFileDirectory] -> Get ImageFileDirectory @@ -382,20 +477,26 @@ val <- findIFD msg tag lst case val of ImageFileDirectory - { ifdCount = 1, ifdOffset = ofs } -> + { ifdCount = 1, ifdOffset = ofs, ifdType = TypeShort } -> pure . ExtendedDataShort . V.singleton $ fromIntegral ofs + ImageFileDirectory + { ifdCount = 1, ifdOffset = ofs, ifdType = TypeLong } -> + pure . ExtendedDataLong . V.singleton $ fromIntegral ofs ImageFileDirectory { ifdExtended = v } -> pure v -findIFDExtDefaultData :: [Word32] -> TiffTag -> [ImageFileDirectory] -> Get [Word32] +findIFDExtDefaultData :: [Word32] -> TiffTag -> [ImageFileDirectory] + -> Get [Word32] findIFDExtDefaultData d tag lst = case [v | v <- lst, ifdIdentifier v == tag] of [] -> pure d - (x:_) -> V.toList <$> unLong "Can't unlong" (ifdExtended x) + (x:_) -> V.toList <$> + unLong ("Can't unlong " ++ (show tag)) (ifdExtended x) -- It's temporary, remove once tiff decoding is better -- handled. -{- instance Show (Image PixelRGB16) where +{- +instance Show (Image PixelRGB16) where show _ = "Image PixelRGB16" -} @@ -425,8 +526,19 @@ | TiffCMYK -- ^ 5 | TiffYCbCr -- ^ 6 | TiffCIELab -- ^ 8 - deriving (Eq, Show) +packPhotometricInterpretation :: TiffColorspace -> Word16 +packPhotometricInterpretation = aux + where + aux TiffMonochromeWhite0 = 0 + aux TiffMonochrome = 1 + aux TiffRGB = 2 + aux TiffPaleted = 3 + aux TiffTransparencyMask = 4 + aux TiffCMYK = 5 + aux TiffYCbCr = 6 + aux TiffCIELab = 8 + unpackPhotometricInterpretation :: Word32 -> Get TiffColorspace unpackPhotometricInterpretation = aux where aux 0 = pure TiffMonochromeWhite0 @@ -437,17 +549,28 @@ aux 5 = pure TiffCMYK aux 6 = pure TiffYCbCr aux 8 = pure TiffCIELab - aux _ = fail "Unrecognized color space" + aux v = fail $ "Unrecognized color space " ++ show v unPackCompression :: Word32 -> Get TiffCompression -unPackCompression 0 = pure CompressionNone -unPackCompression 1 = pure CompressionNone -unPackCompression 2 = pure CompressionModifiedRLE -unPackCompression 5 = pure CompressionLZW -unPackCompression 6 = pure CompressionJPEG -unPackCompression 32773 = pure CompressionPackBit -unPackCompression v = fail $ "Unknown compression scheme " ++ show v +unPackCompression = aux + where + aux 0 = pure CompressionNone + aux 1 = pure CompressionNone + aux 2 = pure CompressionModifiedRLE + aux 5 = pure CompressionLZW + aux 6 = pure CompressionJPEG + aux 32773 = pure CompressionPackBit + aux v = fail $ "Unknown compression scheme " ++ show v +packCompression :: TiffCompression -> Word16 +packCompression = aux + where + aux CompressionNone = 1 + aux CompressionModifiedRLE = 2 + aux CompressionLZW = 5 + aux CompressionJPEG = 6 + aux CompressionPackBit = 32773 + copyByteString :: B.ByteString -> M.STVector s Word8 -> Int -> Int -> (Word32, Word32) -> ST s Int copyByteString str vec stride startWrite (from, count) = inner startWrite fromi @@ -768,22 +891,129 @@ unsafeFreezeImage mutableImage -getTiffInfo :: Get TiffInfo -getTiffInfo = do +ifdSingleLong :: TiffTag -> Word32 -> Writer [ImageFileDirectory] () +ifdSingleLong tag = ifdMultiLong tag . V.singleton + +ifdSingleShort :: Endianness -> TiffTag -> Word16 + -> Writer [ImageFileDirectory] () +ifdSingleShort endian tag = ifdMultiShort endian tag . V.singleton . fromIntegral + +ifdMultiLong :: TiffTag -> V.Vector Word32 -> Writer [ImageFileDirectory] () +ifdMultiLong tag v = tell . pure $ ImageFileDirectory + { ifdIdentifier = tag + , ifdType = TypeLong + , ifdCount = fromIntegral $ V.length v + , ifdOffset = offset + , ifdExtended = extended + } + where (offset, extended) + | V.length v > 1 = (0, ExtendedDataLong v) + | otherwise = (V.head v, ExtendedDataNone) + +ifdMultiShort :: Endianness -> TiffTag -> V.Vector Word32 + -> Writer [ImageFileDirectory] () +ifdMultiShort endian tag v = tell . pure $ ImageFileDirectory + { ifdIdentifier = tag + , ifdType = TypeShort + , ifdCount = size + , ifdOffset = offset + , ifdExtended = extended + } + where size = fromIntegral $ V.length v + (offset, extended) + | size > 2 = (0, ExtendedDataShort $ V.map fromIntegral v) + | size == 2 = + let v1 = fromIntegral $ V.head v + v2 = fromIntegral $ v `V.unsafeIndex` 1 + in + case endian of + EndianLittle -> (v2 `unsafeShiftL` 16 .|. v1, ExtendedDataNone) + EndianBig -> (v1 `unsafeShiftL` 16 .|. v2, ExtendedDataNone) + + | otherwise = case endian of + EndianLittle -> (V.head v, ExtendedDataNone) + EndianBig -> (V.head v `unsafeShiftL` 16, ExtendedDataNone) + +-- | All the IFD must be written in order according to the tag +-- value of the IFD. To avoid getting to much restriction in the +-- serialization code, just sort it. +orderIfdByTag :: [ImageFileDirectory] -> [ImageFileDirectory] +orderIfdByTag = sortBy comparer + where comparer a b = compare t1 t2 + where t1 = word16OfTag $ ifdIdentifier a + t2 = word16OfTag $ ifdIdentifier b + +-- | Given an official offset and a list of IFD, update the offset information +-- of the IFD with extended data. +setupIfdOffsets :: Word32 -> [ImageFileDirectory] -> [ImageFileDirectory] +setupIfdOffsets initialOffset lst = snd $ mapAccumL updater startExtended lst + where ifdElementCount = fromIntegral $ length lst + ifdSize = 12 + ifdCountSize = 2 + nextOffsetSize = 4 + startExtended = initialOffset + + ifdElementCount * ifdSize + + ifdCountSize + nextOffsetSize + + updater ix ifd@(ImageFileDirectory { ifdExtended = ExtendedDataAscii b }) = + (ix + fromIntegral (B.length b), ifd { ifdOffset = ix } ) + updater ix ifd@(ImageFileDirectory { ifdExtended = ExtendedDataLong v }) + | V.length v > 1 = ( ix + fromIntegral (V.length v * 4) + , ifd { ifdOffset = ix } ) + updater ix ifd@(ImageFileDirectory { ifdExtended = ExtendedDataShort v }) + | V.length v > 2 = ( ix + fromIntegral (V.length v * 2) + , ifd { ifdOffset = ix }) + updater ix ifd = (ix, ifd) + +instance BinaryParam B.ByteString TiffInfo where + putP rawData nfo = do + put $ tiffHeader nfo + + let ifdStartOffset = hdrOffset $ tiffHeader nfo + endianness = hdrEndianness $ tiffHeader nfo + + ifdShort = ifdSingleShort endianness + ifdShorts = ifdMultiShort endianness + list = setupIfdOffsets ifdStartOffset . orderIfdByTag . execWriter $ do + ifdSingleLong TagImageWidth $ tiffWidth nfo + ifdSingleLong TagImageLength $ tiffHeight nfo + ifdShorts TagBitsPerSample $ tiffBitsPerSample nfo + ifdSingleLong TagSamplesPerPixel $ tiffSampleCount nfo + ifdSingleLong TagRowPerStrip $ tiffRowPerStrip nfo + ifdShort TagPhotometricInterpretation + . packPhotometricInterpretation + $ tiffColorspace nfo + ifdShort TagPlanarConfiguration + . constantToPlaneConfiguration $ tiffPlaneConfiguration nfo + ifdShort TagCompression . packCompression + $ tiffCompression nfo + ifdMultiLong TagStripOffsets $ tiffOffsets nfo + + ifdMultiLong TagStripByteCounts $ tiffStripSize nfo + + let subSampling = tiffYCbCrSubsampling nfo + when (not $ V.null subSampling) $ + ifdShorts TagYCbCrSubsampling subSampling + + putByteString rawData + putP endianness list + mapM_ (\ifd -> putP (endianness, ifd) $ ifdExtended ifd) list + + getP _ = do hdr <- get readed <- bytesRead skip . fromIntegral $ fromIntegral (hdrOffset hdr) - readed let endian = hdrEndianness hdr - ifd <- fmap cleanImageFileDirectory <$> getImageFileDirectories endian - cleaned <- {- (\a -> trace (groom a) a) <$> -} fetchExtended endian ifd + ifd <- fmap cleanImageFileDirectory <$> getP endian + cleaned <- fetchExtended endian ifd let dataFind str tag = findIFDData str tag cleaned dataDefault def tag = findIFDDefaultData def tag cleaned extFind str tag = findIFDExt str tag cleaned extDefault def tag = findIFDExtDefaultData def tag cleaned - (\a -> {- trace (groom a) -}a) <$> (TiffInfo hdr + TiffInfo hdr <$> dataFind "Can't find width" TagImageWidth <*> dataFind "Can't find height" TagImageLength <*> (dataFind "Can't find color space" TagPhotometricInterpretation @@ -804,7 +1034,6 @@ >>= unLong "Can't find strip offsets") <*> findPalette cleaned <*> (V.fromList <$> extDefault [2, 2] TagYCbCrSubsampling) - ) unpack :: B.ByteString -> TiffInfo -> Either String DynamicImage unpack file nfo@TiffInfo { tiffColorspace = TiffPaleted @@ -939,5 +1168,84 @@ -- * PixelCMYK16 -- decodeTiff :: B.ByteString -> Either String DynamicImage -decodeTiff file = runGetStrict getTiffInfo file >>= unpack file +decodeTiff file = runGetStrict (getP file) file >>= unpack file + +-- | Class defining which pixel types can be serialized in a +-- Tiff file. +class (Pixel px) => TiffSaveable px where + colorSpaceOfPixel :: px -> TiffColorspace + + subSamplingInfo :: px -> V.Vector Word32 + subSamplingInfo _ = V.empty + +instance TiffSaveable Pixel8 where + colorSpaceOfPixel _ = TiffMonochrome + +instance TiffSaveable Pixel16 where + colorSpaceOfPixel _ = TiffMonochrome + +instance TiffSaveable PixelCMYK8 where + colorSpaceOfPixel _ = TiffCMYK + +instance TiffSaveable PixelCMYK16 where + colorSpaceOfPixel _ = TiffCMYK + +instance TiffSaveable PixelRGB8 where + colorSpaceOfPixel _ = TiffRGB + +instance TiffSaveable PixelRGB16 where + colorSpaceOfPixel _ = TiffRGB + +instance TiffSaveable PixelRGBA8 where + colorSpaceOfPixel _ = TiffRGB + +instance TiffSaveable PixelRGBA16 where + colorSpaceOfPixel _ = TiffRGB + +instance TiffSaveable PixelYCbCr8 where + colorSpaceOfPixel _ = TiffYCbCr + subSamplingInfo _ = V.fromListN 2 [1, 1] + +-- | Transform an image into a Tiff encoded bytestring, reade to be +-- written as a file. +encodeTiff :: forall px. (TiffSaveable px) => Image px -> Lb.ByteString +encodeTiff img = runPut $ putP rawPixelData hdr + where intSampleCount = componentCount (undefined :: px) + sampleCount = fromIntegral intSampleCount + + sampleType = (undefined :: PixelBaseComponent px) + pixelData = imageData img + + rawPixelData = toByteString pixelData + width = fromIntegral $ imageWidth img + height = fromIntegral $ imageHeight img + intSampleSize = sizeOf sampleType + sampleSize = fromIntegral intSampleSize + bitPerSample = sampleSize * 8 + imageSize = width * height * sampleCount * sampleSize + headerSize = 8 + + hdr = TiffInfo + { tiffHeader = TiffHeader + { hdrEndianness = EndianLittle + , hdrOffset = headerSize + imageSize + } + , tiffWidth = width + , tiffHeight = height + , tiffColorspace = colorSpaceOfPixel (undefined :: px) + , tiffSampleCount = fromIntegral $ sampleCount + , tiffRowPerStrip = fromIntegral $ imageHeight img + , tiffPlaneConfiguration = PlanarConfigContig + , tiffSampleFormat = [TiffSampleUint] + , tiffBitsPerSample = V.replicate intSampleCount bitPerSample + , tiffCompression = CompressionNone + , tiffStripSize = V.singleton imageSize + , tiffOffsets = V.singleton headerSize + , tiffPalette = Nothing + , tiffYCbCrSubsampling = subSamplingInfo (undefined :: px) + } + +-- | Helper function to directly write an image as a tiff on disk. +writeTiff :: (TiffSaveable pixel) => FilePath -> Image pixel -> IO () +writeTiff path img = Lb.writeFile path $ encodeTiff img
Codec/Picture/Types.hs view
@@ -17,7 +17,7 @@ -- ** Image functions , freezeImage - , unsafeFreezeImage + , unsafeFreezeImage -- ** Pixel types , Pixel8 @@ -43,9 +43,10 @@ , TransparentPixel( .. ) -- * Helper functions - , dynamicMap , pixelMap , pixelFold + , dynamicMap + , dynamicPixelMap , dropAlphaLayer , withImage , generateImage @@ -133,15 +134,15 @@ -- | Define the plane for the Cb component data PlaneCb = PlaneCb --- | Define plane for the cyan component of the +-- | Define plane for the cyan component of the -- CMYK color space. data PlaneCyan = PlaneCyan --- | Define plane for the magenta component of the +-- | Define plane for the magenta component of the -- CMYK color space. data PlaneMagenta = PlaneMagenta --- | Define plane for the yellow component of the +-- | Define plane for the yellow component of the -- CMYK color space. data PlaneYellow = PlaneYellow @@ -227,10 +228,10 @@ -- position first, then the vertical one. The image can be transformed in place. data MutableImage s a = MutableImage { -- | Width of the image in pixels - mutableImageWidth :: {-# UNPACK #-} !Int + mutableImageWidth :: {-# UNPACK #-} !Int -- | Height of the image in pixels. - , mutableImageHeight :: {-# UNPACK #-} !Int + , mutableImageHeight :: {-# UNPACK #-} !Int -- | The real image, to extract pixels at some position -- you should use the helpers functions. @@ -261,7 +262,7 @@ ImageY8 (Image Pixel8) -- | A greyscale image with 16bit components | ImageY16 (Image Pixel16) - -- | A greyscale HDR image + -- | A greyscale HDR image | ImageYF (Image PixelF) -- | An image in greyscale with an alpha channel. | ImageYA8 (Image PixelYA8) @@ -307,6 +308,38 @@ dynamicMap f (ImageCMYK8 i) = f i dynamicMap f (ImageCMYK16 i) = f i +-- | Equivalent of the `pixelMap` function for the dynamic images. +-- You can perform pixel colorspace independant operations with this +-- function. +-- +-- For instance, if you wan't to extract a square crop of any image, +-- without caring about colorspace, you can use the following snippet. +-- +-- > dynSquare :: DynamicImage -> DynamicImage +-- > dynSquare = dynMap squareImage +-- > +-- > squareImage :: Pixel a => Image a -> Image a +-- > squareImage img = generateImage (\x y -> pixelAt img x y) edge edge +-- > where edge = min (imageWidth img) (imageHeight img) +-- +dynamicPixelMap :: (forall pixel . (Pixel pixel) => Image pixel -> Image pixel) + -> DynamicImage -> DynamicImage +dynamicPixelMap f = aux + where + aux (ImageY8 i) = ImageY8 (f i) + aux (ImageY16 i) = ImageY16 (f i) + aux (ImageYF i) = ImageYF (f i) + aux (ImageYA8 i) = ImageYA8 (f i) + aux (ImageYA16 i) = ImageYA16 (f i) + aux (ImageRGB8 i) = ImageRGB8 (f i) + aux (ImageRGB16 i) = ImageRGB16 (f i) + aux (ImageRGBF i) = ImageRGBF (f i) + aux (ImageRGBA8 i) = ImageRGBA8 (f i) + aux (ImageRGBA16 i) = ImageRGBA16 (f i) + aux (ImageYCbCr8 i) = ImageYCbCr8 (f i) + aux (ImageCMYK8 i) = ImageCMYK8 (f i) + aux (ImageCMYK16 i) = ImageCMYK16 (f i) + instance NFData DynamicImage where rnf (ImageY8 img) = rnf img rnf (ImageY16 img) = rnf img @@ -491,6 +524,15 @@ -- HDR image would have Float for instance type PixelBaseComponent a :: * + -- | Call the function for every component of the pixels. + -- For example for RGB pixels mixWith is declared like this : + -- + -- > mixWith f (PixelRGB8 ra ga ba) (PixelRGB8 rb gb bb) = + -- > PixelRGB8 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) + -- + mixWith :: (Int -> PixelBaseComponent a -> PixelBaseComponent a -> PixelBaseComponent a) + -> a -> a -> a + -- | Return the number of component of the pixel componentCount :: a -> Int @@ -651,14 +693,14 @@ -- | Fold over the pixel of an image with a raster scan order : -- from top to bottom, left to right {-# INLINE pixelFold #-} -pixelFold :: (Pixel pixel) +pixelFold :: (Pixel pixel) => (acc -> Int -> Int -> pixel -> acc) -> acc -> Image pixel -> acc pixelFold f initialAccumulator img@(Image { imageWidth = w, imageHeight = h }) = - lineFold + lineFold where pixelFolder y acc x = f acc x y $ pixelAt img x y columnFold lineAcc y = foldl' (pixelFolder y) lineAcc [0 .. w - 1] lineFold = foldl' columnFold initialAccumulator [0 .. h - 1] - + -- | `map` equivalent for an image, working at the pixel level. -- Little example : a brightness function for an rgb image -- @@ -711,7 +753,7 @@ -- implementation. -- -- > jpegToGrayScale :: FilePath -> FilePath -> IO () - -- > jpegToGrayScale source dest + -- > jpegToGrayScale source dest extractLumaPlane :: Image a -> Image (PixelBaseComponent a) extractLumaPlane = pixelMap computeLuma @@ -768,6 +810,9 @@ instance Pixel Pixel8 where type PixelBaseComponent Pixel8 = Word8 + {-# INLINE mixWith #-} + mixWith f = f 0 + {-# INLINE colorMap #-} colorMap f = f @@ -810,6 +855,9 @@ instance Pixel Pixel16 where type PixelBaseComponent Pixel16 = Word16 + {-# INLINE mixWith #-} + mixWith f = f 0 + {-# INLINE colorMap #-} colorMap f = f @@ -844,6 +892,9 @@ instance Pixel PixelF where type PixelBaseComponent PixelF = Float + {-# INLINE mixWith #-} + mixWith f = f 0 + {-# INLINE colorMap #-} colorMap f = f componentCount _ = 1 @@ -869,6 +920,11 @@ instance Pixel PixelYA8 where type PixelBaseComponent PixelYA8 = Word8 + {-# INLINE mixWith #-} + mixWith f (PixelYA8 ya aa) (PixelYA8 yb ab) = + PixelYA8 (f 0 ya yb) (f 1 aa ab) + + {-# INLINE colorMap #-} colorMap f (PixelYA8 y a) = PixelYA8 (f y) (f a) componentCount _ = 2 @@ -887,7 +943,7 @@ (arr .<-. (baseIdx + 0)) yv (arr .<-. (baseIdx + 1)) av - unsafePixelAt v idx = + unsafePixelAt v idx = PixelYA8 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) unsafeReadPixel vec idx = PixelYA8 `liftM` M.unsafeRead vec idx `ap` M.unsafeRead vec (idx + 1) @@ -923,6 +979,10 @@ instance Pixel PixelYA16 where type PixelBaseComponent PixelYA16 = Word16 + {-# INLINE mixWith #-} + mixWith f (PixelYA16 ya aa) (PixelYA16 yb ab) = + PixelYA16 (f 0 ya yb) (f 1 aa ab) + {-# INLINE colorMap #-} colorMap f (PixelYA16 y a) = PixelYA16 (f y) (f a) componentCount _ = 2 @@ -941,7 +1001,7 @@ (arr .<-. (baseIdx + 0)) yv (arr .<-. (baseIdx + 1)) av - unsafePixelAt v idx = + unsafePixelAt v idx = PixelYA16 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) unsafeReadPixel vec idx = PixelYA16 `liftM` M.unsafeRead vec idx `ap` M.unsafeRead vec (idx + 1) @@ -967,6 +1027,10 @@ instance Pixel PixelRGBF where type PixelBaseComponent PixelRGBF = PixelF + {-# INLINE mixWith #-} + mixWith f (PixelRGBF ra ga ba) (PixelRGBF rb gb bb) = + PixelRGBF (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) + {-# INLINE colorMap #-} colorMap f (PixelRGBF r g b) = PixelRGBF (f r) (f g) (f b) @@ -990,7 +1054,7 @@ (arr .<-. (baseIdx + 1)) gv (arr .<-. (baseIdx + 2)) bv - unsafePixelAt v idx = + unsafePixelAt v idx = PixelRGBF (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) unsafeReadPixel vec idx = PixelRGBF `liftM` M.unsafeRead vec idx @@ -1015,6 +1079,10 @@ instance Pixel PixelRGB16 where type PixelBaseComponent PixelRGB16 = Pixel16 + {-# INLINE mixWith #-} + mixWith f (PixelRGB16 ra ga ba) (PixelRGB16 rb gb bb) = + PixelRGB16 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) + {-# INLINE colorMap #-} colorMap f (PixelRGB16 r g b) = PixelRGB16 (f r) (f g) (f b) @@ -1038,7 +1106,7 @@ (arr .<-. (baseIdx + 1)) gv (arr .<-. (baseIdx + 2)) bv - unsafePixelAt v idx = + unsafePixelAt v idx = PixelRGB16 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) unsafeReadPixel vec idx = PixelRGB16 `liftM` M.unsafeRead vec idx @@ -1076,6 +1144,10 @@ instance Pixel PixelRGB8 where type PixelBaseComponent PixelRGB8 = Word8 + {-# INLINE mixWith #-} + mixWith f (PixelRGB8 ra ga ba) (PixelRGB8 rb gb bb) = + PixelRGB8 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) + {-# INLINE colorMap #-} colorMap f (PixelRGB8 r g b) = PixelRGB8 (f r) (f g) (f b) @@ -1099,7 +1171,7 @@ (arr .<-. (baseIdx + 1)) gv (arr .<-. (baseIdx + 2)) bv - unsafePixelAt v idx = + unsafePixelAt v idx = PixelRGB8 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) unsafeReadPixel vec idx = PixelRGB8 `liftM` M.unsafeRead vec idx @@ -1139,6 +1211,10 @@ instance Pixel PixelRGBA8 where type PixelBaseComponent PixelRGBA8 = Word8 + {-# INLINE mixWith #-} + mixWith f (PixelRGBA8 ra ga ba aa) (PixelRGBA8 rb gb bb ab) = + PixelRGBA8 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) (f 3 aa ab) + {-# INLINE colorMap #-} colorMap f (PixelRGBA8 r g b a) = PixelRGBA8 (f r) (f g) (f b) (f a) @@ -1165,7 +1241,7 @@ (arr .<-. (baseIdx + 2)) bv (arr .<-. (baseIdx + 3)) av - unsafePixelAt v idx = + unsafePixelAt v idx = PixelRGBA8 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) @@ -1198,6 +1274,10 @@ instance Pixel PixelRGBA16 where type PixelBaseComponent PixelRGBA16 = Pixel16 + {-# INLINE mixWith #-} + mixWith f (PixelRGBA16 ra ga ba aa) (PixelRGBA16 rb gb bb ab) = + PixelRGBA16 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) (f 3 aa ab) + {-# INLINE colorMap #-} colorMap f (PixelRGBA16 r g b a) = PixelRGBA16 (f r) (f g) (f b) (f a) @@ -1223,7 +1303,7 @@ (arr .<-. (baseIdx + 2)) bv (arr .<-. (baseIdx + 3)) av - unsafePixelAt v idx = + unsafePixelAt v idx = PixelRGBA16 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) @@ -1261,6 +1341,10 @@ instance Pixel PixelYCbCr8 where type PixelBaseComponent PixelYCbCr8 = Word8 + {-# INLINE mixWith #-} + mixWith f (PixelYCbCr8 ya cba cra) (PixelYCbCr8 yb cbb crb) = + PixelYCbCr8 (f 0 ya yb) (f 1 cba cbb) (f 2 cra crb) + {-# INLINE colorMap #-} colorMap f (PixelYCbCr8 y cb cr) = PixelYCbCr8 (f y) (f cb) (f cr) componentCount _ = 3 @@ -1282,7 +1366,7 @@ (arr .<-. (baseIdx + 1)) cbv (arr .<-. (baseIdx + 2)) crv - unsafePixelAt v idx = + unsafePixelAt v idx = PixelYCbCr8 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) unsafeReadPixel vec idx = PixelYCbCr8 `liftM` M.unsafeRead vec idx @@ -1415,6 +1499,10 @@ instance Pixel PixelCMYK8 where type PixelBaseComponent PixelCMYK8 = Word8 + {-# INLINE mixWith #-} + mixWith f (PixelCMYK8 ca ma ya ka) (PixelCMYK8 cb mb yb kb) = + PixelCMYK8 (f 0 ca cb) (f 1 ma mb) (f 2 ya yb) (f 3 ka kb) + {-# INLINE colorMap #-} colorMap f (PixelCMYK8 c m y k) = PixelCMYK8 (f c) (f m) (f y) (f k) @@ -1441,7 +1529,7 @@ (arr .<-. (baseIdx + 2)) bv (arr .<-. (baseIdx + 3)) av - unsafePixelAt v idx = + unsafePixelAt v idx = PixelCMYK8 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) @@ -1459,7 +1547,7 @@ instance ColorSpaceConvertible PixelCMYK8 PixelRGB8 where convertPixel (PixelCMYK8 c m y k) = PixelRGB8 (clampWord8 r) (clampWord8 g) (clampWord8 b) - where + where clampWord8 = fromIntegral . (`unsafeShiftR` 8) ik :: Int ik = 255 - fromIntegral k @@ -1515,6 +1603,10 @@ instance Pixel PixelCMYK16 where type PixelBaseComponent PixelCMYK16 = Word16 + {-# INLINE mixWith #-} + mixWith f (PixelCMYK16 ca ma ya ka) (PixelCMYK16 cb mb yb kb) = + PixelCMYK16 (f 0 ca cb) (f 1 ma mb) (f 2 ya yb) (f 3 ka kb) + {-# INLINE colorMap #-} colorMap f (PixelCMYK16 c m y k) = PixelCMYK16 (f c) (f m) (f y) (f k) @@ -1541,7 +1633,7 @@ (arr .<-. (baseIdx + 2)) bv (arr .<-. (baseIdx + 3)) av - unsafePixelAt v idx = + unsafePixelAt v idx = PixelCMYK16 (V.unsafeIndex v idx) (V.unsafeIndex v $ idx + 1) (V.unsafeIndex v $ idx + 2) @@ -1559,7 +1651,7 @@ instance ColorSpaceConvertible PixelCMYK16 PixelRGB16 where convertPixel (PixelCMYK16 c m y k) = PixelRGB16 (clampWord16 r) (clampWord16 g) (clampWord16 b) - where + where clampWord16 = fromIntegral . (`unsafeShiftR` 16) ik :: Int ik = 65535 - fromIntegral k
Codec/Picture/VectorByteConversion.hs view
@@ -1,7 +1,10 @@-module Codec.Picture.VectorByteConversion( blitVector ) where +{-# LANGUAGE ScopedTypeVariables #-} +module Codec.Picture.VectorByteConversion( blitVector, toByteString ) where import Data.Word( Word8 ) import Data.Vector.Storable( Vector, unsafeToForeignPtr ) +import Foreign.ForeignPtr.Safe( castForeignPtr ) +import Foreign.Storable( Storable, sizeOf ) import qualified Data.ByteString as B import qualified Data.ByteString.Internal as S @@ -9,4 +12,9 @@ blitVector :: Vector Word8 -> Int -> Int -> B.ByteString blitVector vec atIndex blitSize = S.PS ptr (offset + atIndex) blitSize where (ptr, offset, _length) = unsafeToForeignPtr vec + +toByteString :: forall a. (Storable a) => Vector a -> B.ByteString +toByteString vec = S.PS (castForeignPtr ptr) offset (len * size) + where (ptr, offset, len) = unsafeToForeignPtr vec + size = sizeOf (undefined :: a)
JuicyPixels.cabal view
@@ -1,5 +1,5 @@ Name: JuicyPixels -Version: 3.1 +Version: 3.1.1 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==>> @@ -24,8 +24,12 @@ Source-Repository this Type: git Location: git://github.com/Twinside/Juicy.Pixels.git - Tag: v3.1 + Tag: v3.1.1 +Flag Mmap + Description: Enable the file loading via mmap (memory map) + Default: False + Library Default-Language: Haskell2010 Exposed-modules: Codec.Picture, @@ -47,8 +51,11 @@ transformers >= 0.2.2 && < 0.4, vector >= 0.9 && < 0.11, primitive >= 0.5 && < 0.6, - deepseq >= 1.1 && < 1.4, - mmap + deepseq >= 1.1 && < 1.4 + + if flag(Mmap) + Build-depends: mmap + CC-Options: "-DWITH_MMAP_BYTESTRING" -- Modules not exported by this package. Other-modules: Codec.Picture.Jpg.DefaultTable,