{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE CPP #-}
module Codec.Picture.Tiff( decodeTiff
, decodeTiffWithMetadata
, decodeTiffWithPaletteAndMetadata
, TiffSaveable
, encodeTiff
, writeTiff
) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative( (<$>), (<*>), pure )
import Data.Monoid( mempty )
#endif
import Control.Arrow( first )
import Control.Monad( when, foldM_, unless, forM_ )
import Control.Monad.ST( ST, runST )
import Control.Monad.Writer.Strict( execWriter, tell, Writer )
import Data.Int( Int8 )
import Data.Word( Word8, Word16, Word32 )
import Data.Bits( (.&.), (.|.), unsafeShiftL, unsafeShiftR )
import Data.Binary.Get( Get )
import Data.Binary.Put( runPut )
import qualified Data.Vector as V
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 Lb
import qualified Data.ByteString.Unsafe as BU
import Foreign.Storable( sizeOf )
import Codec.Picture.Metadata.Exif
import Codec.Picture.Metadata( Metadatas )
import Codec.Picture.InternalHelper
import Codec.Picture.BitWriter
import Codec.Picture.Types
import Codec.Picture.Gif.LZW
import Codec.Picture.Tiff.Types
import Codec.Picture.Tiff.Metadata
import Codec.Picture.VectorByteConversion( toByteString )
data TiffInfo = TiffInfo
{ tiffHeader :: TiffHeader
, tiffWidth :: Word32
, tiffHeight :: Word32
, tiffColorspace :: TiffColorspace
, tiffSampleCount :: Word32
, tiffRowPerStrip :: Word32
, tiffPlaneConfiguration :: TiffPlanarConfiguration
, tiffSampleFormat :: [TiffSampleFormat]
, tiffBitsPerSample :: V.Vector Word32
, tiffCompression :: TiffCompression
, tiffStripSize :: V.Vector Word32
, tiffOffsets :: V.Vector Word32
, tiffPalette :: Maybe (Image PixelRGB16)
, tiffYCbCrSubsampling :: V.Vector Word32
, tiffExtraSample :: Maybe ExtraSample
, tiffPredictor :: Predictor
, tiffMetadatas :: Metadatas
}
unLong :: String -> ExifData -> Get (V.Vector Word32)
unLong _ (ExifLong v) = pure $ V.singleton v
unLong _ (ExifShort v) = pure $ V.singleton (fromIntegral v)
unLong _ (ExifShorts v) = pure $ V.map fromIntegral v
unLong _ (ExifLongs v) = pure v
unLong errMessage _ = fail errMessage
findIFD :: String -> ExifTag -> [ImageFileDirectory]
-> Get ImageFileDirectory
findIFD errorMessage tag lst =
case [v | v <- lst, ifdIdentifier v == tag] of
[] -> fail errorMessage
(x:_) -> pure x
findPalette :: [ImageFileDirectory] -> Get (Maybe (Image PixelRGB16))
findPalette ifds =
case [v | v <- ifds, ifdIdentifier v == TagColorMap] of
(ImageFileDirectory { ifdExtended = ExifShorts vec }:_) ->
pure . Just . Image pixelCount 1 $ VS.generate (V.length vec) axx
where pixelCount = V.length vec `div` 3
axx v = vec `V.unsafeIndex` (idx + color * pixelCount)
where (idx, color) = v `divMod` 3
_ -> pure Nothing
findIFDData :: String -> ExifTag -> [ImageFileDirectory] -> Get Word32
findIFDData msg tag lst = ifdOffset <$> findIFD msg tag lst
findIFDDefaultData :: Word32 -> ExifTag -> [ImageFileDirectory] -> Get Word32
findIFDDefaultData d tag lst =
case [v | v <- lst, ifdIdentifier v == tag] of
[] -> pure d
(x:_) -> pure $ ifdOffset x
findIFDExt :: String -> ExifTag -> [ImageFileDirectory] -> Get ExifData
findIFDExt msg tag lst = do
val <- findIFD msg tag lst
case val of
ImageFileDirectory
{ ifdCount = 1, ifdOffset = ofs, ifdType = TypeShort } ->
pure . ExifShorts . V.singleton $ fromIntegral ofs
ImageFileDirectory
{ ifdCount = 1, ifdOffset = ofs, ifdType = TypeLong } ->
pure . ExifLongs . V.singleton $ fromIntegral ofs
ImageFileDirectory { ifdExtended = v } -> pure v
findIFDExtDefaultData :: [Word32] -> ExifTag -> [ImageFileDirectory]
-> Get [Word32]
findIFDExtDefaultData d tag lst =
case [v | v <- lst, ifdIdentifier v == tag] of
[] -> pure d
(ImageFileDirectory { ifdExtended = ExifNone }:_) -> return d
(x:_) -> V.toList <$> unLong errorMessage (ifdExtended x)
where errorMessage =
"Can't parse tag " ++ show tag ++ " " ++ show (ifdExtended x)
copyByteString :: B.ByteString -> M.STVector s Word8 -> Int -> Int -> (Word32, Word32)
-> ST s Int
copyByteString str vec stride startWrite (from, count) = inner startWrite fromi
where fromi = fromIntegral from
maxi = fromi + fromIntegral count
inner writeIdx i | i >= maxi = pure writeIdx
inner writeIdx i = do
let v = str `BU.unsafeIndex` i
(vec `M.unsafeWrite` writeIdx) v
inner (writeIdx + stride) $ i + 1
unpackPackBit :: B.ByteString -> M.STVector s Word8 -> Int -> Int
-> (Word32, Word32)
-> ST s Int
unpackPackBit str outVec stride writeIndex (offset, size) = loop fromi writeIndex
where fromi = fromIntegral offset
maxi = fromi + fromIntegral size
replicateByte writeIdx _ 0 = pure writeIdx
replicateByte writeIdx v count = do
(outVec `M.unsafeWrite` writeIdx) v
replicateByte (writeIdx + stride) v $ count - 1
loop i writeIdx | i >= maxi = pure writeIdx
loop i writeIdx = choice
where v = fromIntegral (str `B.index` i) :: Int8
choice
| 0 <= v =
copyByteString str outVec stride writeIdx
(fromIntegral $ i + 1, fromIntegral v + 1)
>>= loop (i + 2 + fromIntegral v)
| -127 <= v = do
let nextByte = str `B.index` (i + 1)
count = negate (fromIntegral v) + 1 :: Int
replicateByte writeIdx nextByte count
>>= loop (i + 2)
| otherwise = loop writeIdx $ i + 1
uncompressAt :: TiffCompression
-> B.ByteString -> M.STVector s Word8 -> Int -> Int -> (Word32, Word32)
-> ST s Int
uncompressAt CompressionNone = copyByteString
uncompressAt CompressionPackBit = unpackPackBit
uncompressAt CompressionLZW = \str outVec _stride writeIndex (offset, size) -> do
let toDecode = B.take (fromIntegral size) $ B.drop (fromIntegral offset) str
runBoolReader $ decodeLzwTiff toDecode outVec writeIndex
return 0
uncompressAt _ = error "Unhandled compression"
class Unpackable a where
type StorageType a :: *
outAlloc :: a -> Int -> ST s (M.STVector s (StorageType a))
allocTempBuffer :: a -> M.STVector s (StorageType a) -> Int
-> ST s (M.STVector s Word8)
offsetStride :: a -> Int -> Int -> (Int, Int)
mergeBackTempBuffer :: a
-> Endianness
-> M.STVector s Word8
-> Int
-> Int
-> Word32
-> Int
-> M.STVector s (StorageType a)
-> ST s ()
instance Unpackable Word8 where
type StorageType Word8 = Word8
offsetStride _ i stride = (i, stride)
allocTempBuffer _ buff _ = pure buff
mergeBackTempBuffer _ _ _ _ _ _ _ _ = pure ()
outAlloc _ count = M.replicate count 0
instance Unpackable Word16 where
type StorageType Word16 = Word16
offsetStride _ _ _ = (0, 1)
outAlloc _ = M.new
allocTempBuffer _ _ s = M.new $ s * 2
mergeBackTempBuffer _ EndianLittle tempVec _ index size stride outVec =
looperLe index 0
where looperLe _ readIndex | readIndex >= fromIntegral size = pure ()
looperLe writeIndex readIndex = do
v1 <- tempVec `M.read` readIndex
v2 <- tempVec `M.read` (readIndex + 1)
let finalValue =
(fromIntegral v2 `unsafeShiftL` 8) .|. fromIntegral v1
(outVec `M.write` writeIndex) finalValue
looperLe (writeIndex + stride) (readIndex + 2)
mergeBackTempBuffer _ EndianBig tempVec _ index size stride outVec =
looperBe index 0
where looperBe _ readIndex | readIndex >= fromIntegral size = pure ()
looperBe writeIndex readIndex = do
v1 <- tempVec `M.read` readIndex
v2 <- tempVec `M.read` (readIndex + 1)
let finalValue =
(fromIntegral v1 `unsafeShiftL` 8) .|. fromIntegral v2
(outVec `M.write` writeIndex) finalValue
looperBe (writeIndex + stride) (readIndex + 2)
instance Unpackable Word32 where
type StorageType Word32 = Word32
offsetStride _ _ _ = (0, 1)
outAlloc _ = M.new
allocTempBuffer _ _ s = M.new $ s * 4
mergeBackTempBuffer _ EndianLittle tempVec _ index size stride outVec =
looperLe index 0
where looperLe _ readIndex | readIndex >= fromIntegral size = pure ()
looperLe writeIndex readIndex = do
v1 <- tempVec `M.read` readIndex
v2 <- tempVec `M.read` (readIndex + 1)
v3 <- tempVec `M.read` (readIndex + 2)
v4 <- tempVec `M.read` (readIndex + 3)
let finalValue =
(fromIntegral v4 `unsafeShiftL` 24) .|.
(fromIntegral v3 `unsafeShiftL` 16) .|.
(fromIntegral v2 `unsafeShiftL` 8) .|.
fromIntegral v1
(outVec `M.write` writeIndex) finalValue
looperLe (writeIndex + stride) (readIndex + 4)
mergeBackTempBuffer _ EndianBig tempVec _ index size stride outVec =
looperBe index 0
where looperBe _ readIndex | readIndex >= fromIntegral size = pure ()
looperBe writeIndex readIndex = do
v1 <- tempVec `M.read` readIndex
v2 <- tempVec `M.read` (readIndex + 1)
v3 <- tempVec `M.read` (readIndex + 2)
v4 <- tempVec `M.read` (readIndex + 3)
let finalValue =
(fromIntegral v1 `unsafeShiftL` 24) .|.
(fromIntegral v2 `unsafeShiftL` 16) .|.
(fromIntegral v3 `unsafeShiftL` 8) .|.
fromIntegral v4
(outVec `M.write` writeIndex) finalValue
looperBe (writeIndex + stride) (readIndex + 4)
instance Unpackable Float where
type StorageType Float = Float
offsetStride _ _ _ = (0, 1)
outAlloc _ = M.new
allocTempBuffer _ _ s = M.new $ s * 4
mergeBackTempBuffer :: forall s. Float
-> Endianness
-> M.STVector s Word8
-> Int
-> Int
-> Word32
-> Int
-> M.STVector s (StorageType Float)
-> ST s ()
mergeBackTempBuffer _ endianness tempVec lineSize index size stride outVec =
let outVecWord32 :: M.STVector s Word32
outVecWord32 = M.unsafeCast outVec
in mergeBackTempBuffer (0 :: Word32)
endianness
tempVec
lineSize
index
size
stride
outVecWord32
data Pack4 = Pack4
instance Unpackable Pack4 where
type StorageType Pack4 = Word8
allocTempBuffer _ _ = M.new
offsetStride _ _ _ = (0, 1)
outAlloc _ = M.new
mergeBackTempBuffer _ _ tempVec lineSize index size stride outVec =
inner 0 index pxCount
where pxCount = lineSize `div` stride
maxWrite = M.length outVec
inner readIdx writeIdx _
| readIdx >= fromIntegral size || writeIdx >= maxWrite = pure ()
inner readIdx writeIdx line
| line <= 0 = inner readIdx (writeIdx + line * stride) pxCount
inner readIdx writeIdx line = do
v <- tempVec `M.read` readIdx
let high = (v `unsafeShiftR` 4) .&. 0xF
low = v .&. 0xF
(outVec `M.write` writeIdx) high
when (writeIdx + stride < maxWrite) $
(outVec `M.write` (writeIdx + stride)) low
inner (readIdx + 1) (writeIdx + 2 * stride) (line - 2)
data Pack2 = Pack2
instance Unpackable Pack2 where
type StorageType Pack2 = Word8
allocTempBuffer _ _ = M.new
offsetStride _ _ _ = (0, 1)
outAlloc _ = M.new
mergeBackTempBuffer _ _ tempVec lineSize index size stride outVec =
inner 0 index pxCount
where pxCount = lineSize `div` stride
maxWrite = M.length outVec
inner readIdx writeIdx _
| readIdx >= fromIntegral size || writeIdx >= maxWrite = pure ()
inner readIdx writeIdx line
| line <= 0 = inner readIdx (writeIdx + line * stride) pxCount
inner readIdx writeIdx line = do
v <- tempVec `M.read` readIdx
let v0 = (v `unsafeShiftR` 6) .&. 0x3
v1 = (v `unsafeShiftR` 4) .&. 0x3
v2 = (v `unsafeShiftR` 2) .&. 0x3
v3 = v .&. 0x3
(outVec `M.write` writeIdx) v0
when (writeIdx + 1 * stride < maxWrite) $
(outVec `M.write` (writeIdx + stride)) v1
when (writeIdx + 2 * stride < maxWrite) $
(outVec `M.write` (writeIdx + stride * 2)) v2
when (writeIdx + 3 * stride < maxWrite) $
(outVec `M.write` (writeIdx + stride * 3)) v3
inner (readIdx + 1) (writeIdx + 4 * stride) (line - 4)
data Pack12 = Pack12
instance Unpackable Pack12 where
type StorageType Pack12 = Word16
allocTempBuffer _ _ = M.new
offsetStride _ _ _ = (0, 1)
outAlloc _ = M.new
mergeBackTempBuffer _ _ tempVec lineSize index size stride outVec =
inner 0 index pxCount
where pxCount = lineSize `div` stride
maxWrite = M.length outVec
inner readIdx writeIdx _
| readIdx >= fromIntegral size || writeIdx >= maxWrite = pure ()
inner readIdx writeIdx line
| line <= 0 = inner readIdx (writeIdx + line * stride) pxCount
inner readIdx writeIdx line = do
v0 <- tempVec `M.read` readIdx
v1 <- if readIdx + 1 < fromIntegral size
then tempVec `M.read` (readIdx + 1)
else pure 0
v2 <- if readIdx + 2 < fromIntegral size
then tempVec `M.read` (readIdx + 2)
else pure 0
let high0 = fromIntegral v0 `unsafeShiftL` 4
low0 = (fromIntegral v1 `unsafeShiftR` 4) .&. 0xF
p0 = high0 .|. low0
high1 = (fromIntegral v1 .&. 0xF) `unsafeShiftL` 8
low1 = fromIntegral v2
p1 = high1 .|. low1
(outVec `M.write` writeIdx) p0
when (writeIdx + 1 * stride < maxWrite) $
(outVec `M.write` (writeIdx + stride)) p1
inner (readIdx + 3) (writeIdx + 2 * stride) (line - 2)
data YCbCrSubsampling = YCbCrSubsampling
{ ycbcrWidth :: !Int
, ycbcrHeight :: !Int
, ycbcrImageWidth :: !Int
, ycbcrStripHeight :: !Int
}
instance Unpackable YCbCrSubsampling where
type StorageType YCbCrSubsampling = Word8
offsetStride _ _ _ = (0, 1)
outAlloc _ = M.new
allocTempBuffer _ _ = M.new
mergeBackTempBuffer subSampling _ tempVec _ index size _ outVec =
foldM_ unpacker 0 [(bx, by) | by <- [0, h .. lineCount - 1]
, bx <- [0, w .. imgWidth - 1]]
where w = ycbcrWidth subSampling
h = ycbcrHeight subSampling
imgWidth = ycbcrImageWidth subSampling
lineCount = ycbcrStripHeight subSampling
lumaCount = w * h
blockSize = lumaCount + 2
maxOut = M.length outVec
unpacker readIdx _ | readIdx >= fromIntegral size * 3 = pure readIdx
unpacker readIdx (bx, by) = do
cb <- tempVec `M.read` (readIdx + lumaCount)
cr <- tempVec `M.read` (readIdx + lumaCount + 1)
let pixelIndices =
[index + ((by + y) * imgWidth + bx + x) * 3 | y <- [0 .. h - 1], x <- [0 .. w - 1]]
writer readIndex writeIdx | writeIdx + 3 > maxOut = pure readIndex
writer readIndex writeIdx = do
y <- tempVec `M.read` readIndex
(outVec `M.write` writeIdx) y
(outVec `M.write` (writeIdx + 1)) cb
(outVec `M.write` (writeIdx + 2)) cr
return $ readIndex + 1
foldM_ writer readIdx pixelIndices
return $ readIdx + blockSize
gatherStrips :: ( Unpackable comp
, Pixel pixel
, StorageType comp ~ PixelBaseComponent pixel
)
=> comp -> B.ByteString -> TiffInfo -> Image pixel
gatherStrips comp str nfo = runST $ do
let width = fromIntegral $ tiffWidth nfo
height = fromIntegral $ tiffHeight nfo
sampleCount = if tiffSampleCount nfo /= 0
then fromIntegral $ tiffSampleCount nfo
else V.length $ tiffBitsPerSample nfo
rowPerStrip = fromIntegral $ tiffRowPerStrip nfo
endianness = hdrEndianness $ tiffHeader nfo
stripCount = V.length $ tiffOffsets nfo
compression = tiffCompression nfo
outVec <- outAlloc comp $ width * height * sampleCount
tempVec <- allocTempBuffer comp outVec
(rowPerStrip * width * sampleCount)
let mutableImage = MutableImage
{ mutableImageWidth = fromIntegral width
, mutableImageHeight = fromIntegral height
, mutableImageData = outVec
}
case tiffPlaneConfiguration nfo of
PlanarConfigContig -> V.mapM_ unpacker sizes
where unpacker (idx, stripSampleCount, offset, packedSize) = do
let (writeIdx, tempStride) = offsetStride comp idx 1
_ <- uncompressAt compression str tempVec tempStride
writeIdx (offset, packedSize)
let typ :: M.MVector s a -> a
typ = const undefined
sampleSize = sizeOf (typ outVec)
mergeBackTempBuffer comp endianness tempVec (width * sampleCount)
idx (fromIntegral $ stripSampleCount * sampleSize) 1 outVec
fullStripSampleCount = rowPerStrip * width * sampleCount
startWriteOffset = V.generate stripCount (fullStripSampleCount *)
stripSampleCounts = V.map strip startWriteOffset
where
strip start = min fullStripSampleCount (width * height * sampleCount - start)
sizes = V.zip4 startWriteOffset stripSampleCounts
(tiffOffsets nfo) (tiffStripSize nfo)
PlanarConfigSeparate -> V.mapM_ unpacker sizes
where unpacker (idx, offset, size) = do
let (writeIdx, tempStride) = offsetStride comp idx stride
_ <- uncompressAt compression str tempVec tempStride
writeIdx (offset, size)
mergeBackTempBuffer comp endianness tempVec (width * sampleCount)
idx size stride outVec
stride = V.length $ tiffOffsets nfo
idxVector = V.enumFromN 0 stride
sizes = V.zip3 idxVector (tiffOffsets nfo) (tiffStripSize nfo)
when (tiffPredictor nfo == PredictorHorizontalDifferencing) $ do
let f _ c1 c2 = c1 + c2
forM_ [0 .. height - 1] $ \y ->
forM_ [1 .. width - 1] $ \x -> do
p <- readPixel mutableImage (x - 1) y
q <- readPixel mutableImage x y
writePixel mutableImage x y $ mixWith f p q
unsafeFreezeImage mutableImage
ifdSingleLong :: ExifTag -> Word32 -> Writer [ImageFileDirectory] ()
ifdSingleLong tag = ifdMultiLong tag . V.singleton
ifdSingleShort :: Endianness -> ExifTag -> Word16
-> Writer [ImageFileDirectory] ()
ifdSingleShort endian tag = ifdMultiShort endian tag . V.singleton . fromIntegral
ifdMultiLong :: ExifTag -> 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, ExifLongs v)
| otherwise = (V.head v, ExifNone)
ifdMultiShort :: Endianness -> ExifTag -> 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, ExifShorts $ 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, ExifNone)
EndianBig -> (v1 `unsafeShiftL` 16 .|. v2, ExifNone)
| otherwise = case endian of
EndianLittle -> (V.head v, ExifNone)
EndianBig -> (V.head v `unsafeShiftL` 16, ExifNone)
instance BinaryParam B.ByteString TiffInfo where
putP rawData nfo = putP rawData (tiffHeader nfo, [list]) where
endianness = hdrEndianness $ tiffHeader nfo
ifdShort = ifdSingleShort endianness
ifdShorts = ifdMultiShort endianness
list = 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
ifdMultiLong TagSampleFormat
. V.fromList
. map packSampleFormat
$ tiffSampleFormat nfo
ifdShort TagCompression . packCompression
$ tiffCompression nfo
ifdMultiLong TagStripOffsets $ tiffOffsets nfo
ifdMultiLong TagStripByteCounts $ tiffStripSize nfo
maybe (return ())
(ifdShort TagExtraSample . codeOfExtraSample)
$ tiffExtraSample nfo
let subSampling = tiffYCbCrSubsampling nfo
unless (V.null subSampling) $
ifdShorts TagYCbCrSubsampling subSampling
getP rawData = do
(hdr, cleanedFull :: [[ImageFileDirectory]]) <- getP rawData
let cleaned = concat cleanedFull
dataFind str tag = findIFDData str tag cleaned
dataDefault def tag = findIFDDefaultData def tag cleaned
extFind str tag = findIFDExt str tag cleaned
extDefault def tag = findIFDExtDefaultData def tag cleaned
TiffInfo hdr
<$> dataFind "Can't find width" TagImageWidth
<*> dataFind "Can't find height" TagImageLength
<*> (dataFind "Can't find color space" TagPhotometricInterpretation
>>= unpackPhotometricInterpretation)
<*> dataFind "Can't find sample per pixel" TagSamplesPerPixel
<*> dataFind "Can't find row per strip" TagRowPerStrip
<*> (dataDefault 1 TagPlanarConfiguration
>>= planarConfgOfConstant)
<*> (extDefault [1] TagSampleFormat
>>= mapM unpackSampleFormat)
<*> (extFind "Can't find bit per sample" TagBitsPerSample
>>= unLong "Can't find bit depth")
<*> (dataFind "Can't find Compression" TagCompression
>>= unPackCompression)
<*> (extFind "Can't find byte counts" TagStripByteCounts
>>= unLong "Can't find bit per sample")
<*> (extFind "Strip offsets missing" TagStripOffsets
>>= unLong "Can't find strip offsets")
<*> findPalette cleaned
<*> (V.fromList <$> extDefault [2, 2] TagYCbCrSubsampling)
<*> pure Nothing
<*> (dataDefault 1 TagPredictor
>>= predictorOfConstant)
<*> pure (extractTiffMetadata cleaned)
palette16Of :: Image PixelRGB16 -> Palette' PixelRGB16
palette16Of p = Palette'
{ _paletteSize = imageWidth p
, _paletteData = imageData p
}
unpack :: B.ByteString -> TiffInfo -> Either String PalettedImage
unpack file nfo@TiffInfo { tiffRowPerStrip = 0 } =
unpack file $ nfo { tiffRowPerStrip = tiffHeight nfo }
unpack file nfo@TiffInfo { tiffColorspace = TiffPaleted
, tiffBitsPerSample = lst
, tiffSampleFormat = format
, tiffPalette = Just p
}
| lst == V.singleton 8 && format == [TiffSampleUint] =
pure . PalettedRGB16 (gatherStrips (0 :: Word8) file nfo) $ palette16Of p
| lst == V.singleton 4 && format == [TiffSampleUint] =
pure . PalettedRGB16 (gatherStrips Pack4 file nfo) $ palette16Of p
| lst == V.singleton 2 && format == [TiffSampleUint] =
pure . PalettedRGB16 (gatherStrips Pack2 file nfo) $ palette16Of p
unpack file nfo@TiffInfo { tiffColorspace = TiffCMYK
, tiffBitsPerSample = lst
, tiffSampleFormat = format }
| lst == V.fromList [8, 8, 8, 8] && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageCMYK8 $ gatherStrips (0 :: Word8) file nfo
| lst == V.fromList [16, 16, 16, 16] && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageCMYK16 $ gatherStrips (0 :: Word16) file nfo
unpack file nfo@TiffInfo { tiffColorspace = TiffMonochromeWhite0 } = do
img <- unpack file (nfo { tiffColorspace = TiffMonochrome })
case img of
TrueColorImage (ImageY8 i) -> pure . TrueColorImage . ImageY8 $ pixelMap (maxBound -) i
TrueColorImage (ImageY16 i) -> pure . TrueColorImage . ImageY16 $ pixelMap (maxBound -) i
TrueColorImage (ImageYA8 i) -> let negative (PixelYA8 y a) = PixelYA8 (maxBound - y) a
in pure . TrueColorImage . ImageYA8 $ pixelMap negative i
TrueColorImage (ImageYA16 i) -> let negative (PixelYA16 y a) = PixelYA16 (maxBound - y) a
in pure . TrueColorImage . ImageYA16 $ pixelMap negative i
_ -> Left "Unsupported color type used with colorspace MonochromeWhite0"
unpack file nfo@TiffInfo { tiffColorspace = TiffMonochrome
, tiffBitsPerSample = lst
, tiffSampleFormat = format }
| lst == V.singleton 2 && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageY8 . pixelMap (colorMap (0x55 *)) $ gatherStrips Pack2 file nfo
| lst == V.singleton 4 && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageY8 . pixelMap (colorMap (0x11 *)) $ gatherStrips Pack4 file nfo
| lst == V.singleton 8 && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageY8 $ gatherStrips (0 :: Word8) file nfo
| lst == V.singleton 12 && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageY16 . pixelMap (colorMap expand12to16) $ gatherStrips Pack12 file nfo
| lst == V.singleton 16 && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageY16 $ gatherStrips (0 :: Word16) file nfo
| lst == V.singleton 32 && all (TiffSampleUint ==) format =
let img = gatherStrips (0 :: Word32) file nfo :: Image Pixel32
in pure $ TrueColorImage $ ImageY32 $ img
| lst == V.singleton 32 && all (TiffSampleFloat ==) format =
let img = gatherStrips (0 :: Float) file nfo :: Image PixelF
in pure $ TrueColorImage $ ImageYF $ img
| lst == V.singleton 64 = Left "Failure to unpack TIFF file, 64-bit samples unsupported."
| lst == V.fromList [2, 2] && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageYA8 . pixelMap (colorMap (0x55 *)) $ gatherStrips Pack2 file nfo
| lst == V.fromList [4, 4] && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageYA8 . pixelMap (colorMap (0x11 *)) $ gatherStrips Pack4 file nfo
| lst == V.fromList [8, 8] && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageYA8 $ gatherStrips (0 :: Word8) file nfo
| lst == V.fromList [12, 12] && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageYA16 . pixelMap (colorMap expand12to16) $ gatherStrips Pack12 file nfo
| lst == V.fromList [16, 16] && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageYA16 $ gatherStrips (0 :: Word16) file nfo
where
expand12to16 x = x `unsafeShiftL` 4 + x `unsafeShiftR` (12 - 4)
unpack file nfo@TiffInfo { tiffColorspace = TiffYCbCr
, tiffBitsPerSample = lst
, tiffPlaneConfiguration = PlanarConfigContig
, tiffSampleFormat = format }
| lst == V.fromList [8, 8, 8] && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageYCbCr8 $ gatherStrips cbcrConf file nfo
where defaulting 0 = 2
defaulting n = n
w = defaulting $ tiffYCbCrSubsampling nfo V.! 0
h = defaulting $ tiffYCbCrSubsampling nfo V.! 1
cbcrConf = YCbCrSubsampling
{ ycbcrWidth = fromIntegral w
, ycbcrHeight = fromIntegral h
, ycbcrImageWidth = fromIntegral $ tiffWidth nfo
, ycbcrStripHeight = fromIntegral $ tiffRowPerStrip nfo
}
unpack file nfo@TiffInfo { tiffColorspace = TiffRGB
, tiffBitsPerSample = lst
, tiffSampleFormat = format }
| lst == V.fromList [2, 2, 2] && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageRGB8 . pixelMap (colorMap (0x55 *)) $ gatherStrips Pack2 file nfo
| lst == V.fromList [4, 4, 4] && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageRGB8 . pixelMap (colorMap (0x11 *)) $ gatherStrips Pack4 file nfo
| lst == V.fromList [8, 8, 8] && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageRGB8 $ gatherStrips (0 :: Word8) file nfo
| lst == V.fromList [8, 8, 8, 8] && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageRGBA8 $ gatherStrips (0 :: Word8) file nfo
| lst == V.fromList [16, 16, 16] && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageRGB16 $ gatherStrips (0 :: Word16) file nfo
| lst == V.fromList [16, 16, 16, 16] && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageRGBA16 $ gatherStrips (0 :: Word16) file nfo
unpack file nfo@TiffInfo { tiffColorspace = TiffMonochrome
, tiffBitsPerSample = lst
, tiffSampleFormat = format }
| lst == V.fromList [8, 8, 8] && all (TiffSampleUint ==) format =
pure . TrueColorImage . ImageRGB8 $ gatherStrips (0 :: Word8) file nfo
unpack _ _ = Left "Failure to unpack TIFF file"
decodeTiff :: B.ByteString -> Either String DynamicImage
decodeTiff = fmap fst . decodeTiffWithMetadata
decodeTiffWithMetadata :: B.ByteString -> Either String (DynamicImage, Metadatas)
decodeTiffWithMetadata str = first palettedToTrueColor <$> decodeTiffWithPaletteAndMetadata str
decodeTiffWithPaletteAndMetadata :: B.ByteString -> Either String (PalettedImage, Metadatas)
decodeTiffWithPaletteAndMetadata file = runGetStrict (getP file) file >>= go
where
go tinfo = (, tiffMetadatas tinfo) <$> unpack file tinfo
class (Pixel px) => TiffSaveable px where
colorSpaceOfPixel :: px -> TiffColorspace
extraSampleCodeOfPixel :: px -> Maybe ExtraSample
extraSampleCodeOfPixel _ = Nothing
subSamplingInfo :: px -> V.Vector Word32
subSamplingInfo _ = V.empty
sampleFormat :: px -> [TiffSampleFormat]
sampleFormat _ = [TiffSampleUint]
instance TiffSaveable Pixel8 where
colorSpaceOfPixel _ = TiffMonochrome
instance TiffSaveable Pixel16 where
colorSpaceOfPixel _ = TiffMonochrome
instance TiffSaveable Pixel32 where
colorSpaceOfPixel _ = TiffMonochrome
instance TiffSaveable PixelF where
colorSpaceOfPixel _ = TiffMonochrome
sampleFormat _ = [TiffSampleFloat]
instance TiffSaveable PixelYA8 where
colorSpaceOfPixel _ = TiffMonochrome
extraSampleCodeOfPixel _ = Just ExtraSampleUnassociatedAlpha
instance TiffSaveable PixelYA16 where
colorSpaceOfPixel _ = TiffMonochrome
extraSampleCodeOfPixel _ = Just ExtraSampleUnassociatedAlpha
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
extraSampleCodeOfPixel _ = Just ExtraSampleUnassociatedAlpha
instance TiffSaveable PixelRGBA16 where
colorSpaceOfPixel _ = TiffRGB
extraSampleCodeOfPixel _ = Just ExtraSampleUnassociatedAlpha
instance TiffSaveable PixelYCbCr8 where
colorSpaceOfPixel _ = TiffYCbCr
subSamplingInfo _ = V.fromListN 2 [1, 1]
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 = sampleFormat (undefined :: px)
, tiffBitsPerSample = V.replicate intSampleCount bitPerSample
, tiffCompression = CompressionNone
, tiffStripSize = V.singleton imageSize
, tiffOffsets = V.singleton headerSize
, tiffPalette = Nothing
, tiffYCbCrSubsampling = subSamplingInfo (undefined :: px)
, tiffExtraSample = extraSampleCodeOfPixel (undefined :: px)
, tiffPredictor = PredictorNone
, tiffMetadatas = mempty
}
writeTiff :: (TiffSaveable pixel) => FilePath -> Image pixel -> IO ()
writeTiff path img = Lb.writeFile path $ encodeTiff img
{-# ANN module "HLint: ignore Reduce duplication" #-}