{-# LANGUAGE CPP #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
module Codec.Picture.Tiff.Types
( BinaryParam( .. )
, Endianness( .. )
, TiffHeader( .. )
, TiffPlanarConfiguration( .. )
, TiffCompression( .. )
, IfdType( .. )
, TiffColorspace( .. )
, TiffSampleFormat( .. )
, ImageFileDirectory( .. )
, ExtraSample( .. )
, Predictor( .. )
, planarConfgOfConstant
, constantToPlaneConfiguration
, unpackSampleFormat
, packSampleFormat
, word16OfTag
, unpackPhotometricInterpretation
, packPhotometricInterpretation
, codeOfExtraSample
, unPackCompression
, packCompression
, predictorOfConstant
) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative( (<$>), (<*>), pure )
#endif
import Control.Monad( forM_, when, replicateM, )
import Data.Bits( (.&.), unsafeShiftR )
import Data.Binary( Binary( .. ) )
import Data.Binary.Get( Get
, getWord16le, getWord16be
, getWord32le, getWord32be
, bytesRead
, skip
, getByteString
)
import Data.Binary.Put( Put
, putWord16le, putWord16be
, putWord32le, putWord32be
, putByteString
)
import Data.Function( on )
import Data.List( sortBy, mapAccumL )
import qualified Data.Vector as V
import qualified Data.ByteString as B
import Data.Int( Int32 )
import Data.Word( Word8, Word16, Word32 )
import Codec.Picture.Metadata.Exif
data Endianness
= EndianLittle
| EndianBig
deriving (Eq, Show)
instance Binary Endianness where
put EndianLittle = putWord16le 0x4949
put EndianBig = putWord16le 0x4D4D
get = do
tag <- getWord16le
case tag of
0x4949 -> return EndianLittle
0x4D4D -> return EndianBig
_ -> fail "Invalid endian tag value"
class BinaryParam a b where
getP :: a -> Get b
putP :: a -> b -> Put
data TiffHeader = TiffHeader
{ hdrEndianness :: !Endianness
, hdrOffset :: {-# UNPACK #-} !Word32
}
deriving (Eq, Show)
instance BinaryParam Endianness Word16 where
putP EndianLittle = putWord16le
putP EndianBig = putWord16be
getP EndianLittle = getWord16le
getP EndianBig = getWord16be
instance BinaryParam Endianness Int32 where
putP en v = putP en $ (fromIntegral v :: Word32)
getP en = fromIntegral <$> (getP en :: Get Word32)
instance BinaryParam Endianness Word32 where
putP EndianLittle = putWord32le
putP EndianBig = putWord32be
getP EndianLittle = getWord32le
getP EndianBig = getWord32be
instance Binary TiffHeader where
put hdr = do
let endian = hdrEndianness hdr
put endian
putP endian (42 :: Word16)
putP endian $ hdrOffset hdr
get = do
endian <- get
magic <- getP endian
let magicValue = 42 :: Word16
when (magic /= magicValue)
(fail "Invalid TIFF magic number")
TiffHeader endian <$> getP endian
data TiffPlanarConfiguration
= PlanarConfigContig
| PlanarConfigSeparate
planarConfgOfConstant :: Word32 -> Get TiffPlanarConfiguration
planarConfgOfConstant 0 = pure PlanarConfigContig
planarConfgOfConstant 1 = pure PlanarConfigContig
planarConfgOfConstant 2 = pure PlanarConfigSeparate
planarConfgOfConstant v = fail $ "Unknown planar constant (" ++ show v ++ ")"
constantToPlaneConfiguration :: TiffPlanarConfiguration -> Word16
constantToPlaneConfiguration PlanarConfigContig = 1
constantToPlaneConfiguration PlanarConfigSeparate = 2
data TiffCompression
= CompressionNone
| CompressionModifiedRLE
| CompressionLZW
| CompressionJPEG
| CompressionPackBit
data IfdType
= TypeByte
| TypeAscii
| TypeShort
| TypeLong
| TypeRational
| TypeSByte
| TypeUndefined
| TypeSignedShort
| TypeSignedLong
| TypeSignedRational
| TypeFloat
| TypeDouble
deriving Show
instance BinaryParam Endianness IfdType where
getP endianness = getP endianness >>= conv where
conv :: Word16 -> Get IfdType
conv v = case v of
1 -> return TypeByte
2 -> return TypeAscii
3 -> return TypeShort
4 -> return TypeLong
5 -> return TypeRational
6 -> return TypeSByte
7 -> return TypeUndefined
8 -> return TypeSignedShort
9 -> return TypeSignedLong
10 -> return TypeSignedRational
11 -> return TypeFloat
12 -> return TypeDouble
_ -> fail "Invalid TIF directory type"
putP endianness = putP endianness . conv where
conv :: IfdType -> Word16
conv v = case v of
TypeByte -> 1
TypeAscii -> 2
TypeShort -> 3
TypeLong -> 4
TypeRational -> 5
TypeSByte -> 6
TypeUndefined -> 7
TypeSignedShort -> 8
TypeSignedLong -> 9
TypeSignedRational -> 10
TypeFloat -> 11
TypeDouble -> 12
instance BinaryParam Endianness ExifTag where
getP endianness = tagOfWord16 <$> getP endianness
putP endianness = putP endianness . word16OfTag
data Predictor
= PredictorNone
| PredictorHorizontalDifferencing
deriving Eq
predictorOfConstant :: Word32 -> Get Predictor
predictorOfConstant 1 = pure PredictorNone
predictorOfConstant 2 = pure PredictorHorizontalDifferencing
predictorOfConstant v = fail $ "Unknown predictor (" ++ show v ++ ")"
paddWrite :: B.ByteString -> Put
paddWrite str = putByteString str >> padding where
zero = 0 :: Word8
padding = when (odd (B.length str)) $ put zero
instance BinaryParam (Endianness, Int, ImageFileDirectory) ExifData where
putP (endianness, _, _) = dump
where
dump ExifNone = pure ()
dump (ExifLong _) = pure ()
dump (ExifShort _) = pure ()
dump (ExifIFD _) = pure ()
dump (ExifString bstr) = paddWrite bstr
dump (ExifUndefined bstr) = paddWrite bstr
dump (ExifShorts shorts) = V.mapM_ (putP endianness) shorts
dump (ExifLongs longs) = V.mapM_ (putP endianness) longs
dump (ExifRational a b) = putP endianness a >> putP endianness b
dump (ExifSignedRational a b) = putP endianness a >> putP endianness b
getP (endianness, maxi, ifd) = fetcher ifd
where
align ImageFileDirectory { ifdOffset = offset } act = do
readed <- bytesRead
let delta = fromIntegral offset - readed
if offset >= fromIntegral maxi || fromIntegral readed > offset then
pure ExifNone
else do
skip $ fromIntegral delta
act
getE :: (BinaryParam Endianness a) => Get a
getE = getP endianness
getVec count = V.replicateM (fromIntegral count)
fetcher ImageFileDirectory { ifdIdentifier = TagExifOffset
, ifdType = TypeLong
, ifdCount = 1 } = do
align ifd $ do
let byOffset = sortBy (compare `on` ifdOffset)
cleansIfds = fmap (cleanImageFileDirectory endianness)
subIfds <- cleansIfds . byOffset <$> getP endianness
cleaned <- fetchExtended endianness maxi $ sortBy (compare `on` ifdOffset) subIfds
pure $ ExifIFD [(ifdIdentifier fd, ifdExtended fd) | fd <- cleaned]
fetcher ImageFileDirectory { ifdType = TypeUndefined, ifdCount = count } | count > 4 =
align ifd $ ExifUndefined <$> getByteString (fromIntegral count)
fetcher ImageFileDirectory { ifdType = TypeUndefined, ifdOffset = ofs } =
pure . ExifUndefined . B.pack $ take (fromIntegral $ ifdCount ifd)
[fromIntegral $ ofs .&. 0xFF000000 `unsafeShiftR` (3 * 8)
,fromIntegral $ ofs .&. 0x00FF0000 `unsafeShiftR` (2 * 8)
,fromIntegral $ ofs .&. 0x0000FF00 `unsafeShiftR` (1 * 8)
,fromIntegral $ ofs .&. 0x000000FF
]
fetcher ImageFileDirectory { ifdType = TypeAscii, ifdCount = count } | count > 1 =
align ifd $ ExifString <$> getByteString (fromIntegral count)
fetcher ImageFileDirectory { ifdType = TypeShort, ifdCount = 2, ifdOffset = ofs } =
pure . ExifShorts $ 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 = TypeRational, ifdCount = 1 } = do
align ifd $ ExifRational <$> getP EndianLittle <*> getP EndianLittle
fetcher ImageFileDirectory { ifdType = TypeSignedRational, ifdCount = 1 } = do
align ifd $ ExifSignedRational <$> getP EndianLittle <*> getP EndianLittle
fetcher ImageFileDirectory { ifdType = TypeShort, ifdCount = 1 } =
pure . ExifShort . fromIntegral $ ifdOffset ifd
fetcher ImageFileDirectory { ifdType = TypeShort, ifdCount = count } | count > 2 =
align ifd $ ExifShorts <$> getVec count getE
fetcher ImageFileDirectory { ifdType = TypeLong, ifdCount = 1 } =
pure . ExifLong . fromIntegral $ ifdOffset ifd
fetcher ImageFileDirectory { ifdType = TypeLong, ifdCount = count } | count > 1 =
align ifd $ ExifLongs <$> getVec count getE
fetcher _ = pure ExifNone
cleanImageFileDirectory :: Endianness -> ImageFileDirectory -> ImageFileDirectory
cleanImageFileDirectory EndianBig ifd@(ImageFileDirectory { ifdCount = 1 }) = aux $ ifdType ifd
where
aux TypeShort = ifd { ifdOffset = ifdOffset ifd `unsafeShiftR` 16 }
aux _ = ifd
cleanImageFileDirectory _ ifd = ifd
fetchExtended :: Endianness -> Int -> [ImageFileDirectory] -> Get [ImageFileDirectory]
fetchExtended endian maxi = mapM $ \ifd -> do
v <- getP (endian, maxi, ifd)
pure $ ifd { ifdExtended = v }
orderIfdByTag :: [ImageFileDirectory] -> [ImageFileDirectory]
orderIfdByTag = sortBy comparer where
comparer a b = compare t1 t2 where
t1 = word16OfTag $ ifdIdentifier a
t2 = word16OfTag $ ifdIdentifier b
setupIfdOffsets :: Word32 -> [ImageFileDirectory] -> (Word32, [ImageFileDirectory])
setupIfdOffsets initialOffset lst = mapAccumL updater startExtended lst
where ifdElementCount = fromIntegral $ length lst
ifdSize = 12
ifdCountSize = 2
nextOffsetSize = 4
startExtended = initialOffset
+ ifdElementCount * ifdSize
+ ifdCountSize + nextOffsetSize
paddedSize blob = fromIntegral $ blobLength + padding where
blobLength = B.length blob
padding = if odd blobLength then 1 else 0
updater ix ifd@(ImageFileDirectory { ifdIdentifier = TagExifOffset }) =
(ix, ifd { ifdOffset = ix } )
updater ix ifd@(ImageFileDirectory { ifdExtended = ExifUndefined b }) =
(ix + paddedSize b, ifd { ifdOffset = ix } )
updater ix ifd@(ImageFileDirectory { ifdExtended = ExifString b }) =
(ix + paddedSize b, ifd { ifdOffset = ix } )
updater ix ifd@(ImageFileDirectory { ifdExtended = ExifLongs v })
| V.length v > 1 = ( ix + fromIntegral (V.length v * 4)
, ifd { ifdOffset = ix } )
updater ix ifd@(ImageFileDirectory { ifdExtended = ExifShorts v })
| V.length v > 2 = ( ix + fromIntegral (V.length v * 2)
, ifd { ifdOffset = ix })
updater ix ifd = (ix, ifd)
instance BinaryParam B.ByteString (TiffHeader, [[ImageFileDirectory]]) where
putP rawData (hdr, ifds) = do
put hdr
putByteString rawData
let endianness = hdrEndianness hdr
(_, offseted) = mapAccumL
(\ix ifd -> setupIfdOffsets ix $ orderIfdByTag ifd)
(hdrOffset hdr)
ifds
forM_ offseted $ \list -> do
putP endianness list
mapM_ (\field -> putP (endianness, (0::Int), field) $ ifdExtended field) list
getP raw = do
hdr <- get
readed <- bytesRead
skip . fromIntegral $ fromIntegral (hdrOffset hdr) - readed
let endian = hdrEndianness hdr
byOffset = sortBy (compare `on` ifdOffset)
cleanIfds = fmap (cleanImageFileDirectory endian)
ifd <- cleanIfds . byOffset <$> getP endian
cleaned <- fetchExtended endian (B.length raw) ifd
return (hdr, [cleaned])
data TiffSampleFormat
= TiffSampleUint
| TiffSampleInt
| TiffSampleFloat
| TiffSampleUnknown
deriving Eq
unpackSampleFormat :: Word32 -> Get TiffSampleFormat
unpackSampleFormat v = case v of
1 -> pure TiffSampleUint
2 -> pure TiffSampleInt
3 -> pure TiffSampleFloat
4 -> pure TiffSampleUnknown
vv -> fail $ "Undefined data format (" ++ show vv ++ ")"
packSampleFormat :: TiffSampleFormat -> Word32
packSampleFormat TiffSampleUint = 1
packSampleFormat TiffSampleInt = 2
packSampleFormat TiffSampleFloat = 3
packSampleFormat TiffSampleUnknown = 4
data ImageFileDirectory = ImageFileDirectory
{ ifdIdentifier :: !ExifTag
, ifdType :: !IfdType
, ifdCount :: !Word32
, ifdOffset :: !Word32
, ifdExtended :: !ExifData
}
deriving Show
instance BinaryParam Endianness ImageFileDirectory where
getP endianness =
ImageFileDirectory <$> getE <*> getE <*> getE <*> getE
<*> pure ExifNone
where getE :: (BinaryParam Endianness a) => Get a
getE = getP endianness
putP endianness ifd = do
let putE :: (BinaryParam Endianness a) => a -> Put
putE = putP endianness
putE $ ifdIdentifier ifd
putE $ ifdType ifd
putE $ ifdCount ifd
putE $ ifdOffset ifd
instance BinaryParam Endianness [ImageFileDirectory] where
getP endianness = do
count <- getP endianness :: Get Word16
rez <- replicateM (fromIntegral count) $ getP endianness
_ <- getP endianness :: Get Word32
pure rez
putP endianness lst = do
let count = fromIntegral $ length lst :: Word16
putP endianness count
mapM_ (putP endianness) lst
putP endianness (0 :: Word32)
data TiffColorspace
= TiffMonochromeWhite0
| TiffMonochrome
| TiffRGB
| TiffPaleted
| TiffTransparencyMask
| TiffCMYK
| TiffYCbCr
| TiffCIELab
packPhotometricInterpretation :: TiffColorspace -> Word16
packPhotometricInterpretation v = case v of
TiffMonochromeWhite0 -> 0
TiffMonochrome -> 1
TiffRGB -> 2
TiffPaleted -> 3
TiffTransparencyMask -> 4
TiffCMYK -> 5
TiffYCbCr -> 6
TiffCIELab -> 8
unpackPhotometricInterpretation :: Word32 -> Get TiffColorspace
unpackPhotometricInterpretation v = case v of
0 -> pure TiffMonochromeWhite0
1 -> pure TiffMonochrome
2 -> pure TiffRGB
3 -> pure TiffPaleted
4 -> pure TiffTransparencyMask
5 -> pure TiffCMYK
6 -> pure TiffYCbCr
8 -> pure TiffCIELab
vv -> fail $ "Unrecognized color space " ++ show vv
data ExtraSample
= ExtraSampleUnspecified
| ExtraSampleAssociatedAlpha
| ExtraSampleUnassociatedAlpha
codeOfExtraSample :: ExtraSample -> Word16
codeOfExtraSample v = case v of
ExtraSampleUnspecified -> 0
ExtraSampleAssociatedAlpha -> 1
ExtraSampleUnassociatedAlpha -> 2
unPackCompression :: Word32 -> Get TiffCompression
unPackCompression v = case v of
0 -> pure CompressionNone
1 -> pure CompressionNone
2 -> pure CompressionModifiedRLE
5 -> pure CompressionLZW
6 -> pure CompressionJPEG
32773 -> pure CompressionPackBit
vv -> fail $ "Unknown compression scheme " ++ show vv
packCompression :: TiffCompression -> Word16
packCompression v = case v of
CompressionNone -> 1
CompressionModifiedRLE -> 2
CompressionLZW -> 5
CompressionJPEG -> 6
CompressionPackBit -> 32773