{-# LANGUAGE CPP #-}
module Codec.Picture.Png.Type( PngIHdr( .. )
, PngFilter( .. )
, PngInterlaceMethod( .. )
, PngPalette
, PngImageType( .. )
, PngPhysicalDimension( .. )
, PngGamma( .. )
, PngUnit( .. )
, APngAnimationControl( .. )
, APngFrameDisposal( .. )
, APngBlendOp( .. )
, APngFrameControl( .. )
, parsePalette
, pngComputeCrc
, pLTESignature
, iDATSignature
, iENDSignature
, tRNSSignature
, tEXtSignature
, zTXtSignature
, gammaSignature
, pHYsSignature
, animationControlSignature
, ChunkSignature
, PngRawImage( .. )
, PngChunk( .. )
, PngRawChunk( .. )
, PngLowLevel( .. )
, chunksWithSig
, mkRawChunk
) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative( (<$>), (<*>), pure )
#endif
import Control.Monad( when, replicateM )
import Data.Bits( xor, (.&.), unsafeShiftR )
import Data.Binary( Binary(..), Get, get )
import Data.Binary.Get( getWord8
, getWord32be
, getLazyByteString
)
import Data.Binary.Put( runPut
, putWord8
, putWord32be
, putLazyByteString
)
import Data.Vector.Unboxed( Vector, fromListN, (!) )
import qualified Data.Vector.Storable as V
import Data.List( foldl' )
import Data.Word( Word32, Word16, Word8 )
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Lazy.Char8 as LS
import Codec.Picture.Types
import Codec.Picture.InternalHelper
type ChunkSignature = L.ByteString
data PngIHdr = PngIHdr
{ width :: !Word32
, height :: !Word32
, bitDepth :: !Word8
, colourType :: !PngImageType
, compressionMethod :: !Word8
, filterMethod :: !Word8
, interlaceMethod :: !PngInterlaceMethod
}
deriving Show
data PngUnit
= PngUnitUnknown
| PngUnitMeter
instance Binary PngUnit where
get = do
v <- getWord8
pure $ case v of
0 -> PngUnitUnknown
1 -> PngUnitMeter
_ -> PngUnitUnknown
put v = case v of
PngUnitUnknown -> putWord8 0
PngUnitMeter -> putWord8 1
data PngPhysicalDimension = PngPhysicalDimension
{ pngDpiX :: !Word32
, pngDpiY :: !Word32
, pngUnit :: !PngUnit
}
instance Binary PngPhysicalDimension where
get = PngPhysicalDimension <$> getWord32be <*> getWord32be <*> get
put (PngPhysicalDimension dpx dpy unit) =
putWord32be dpx >> putWord32be dpy >> put unit
newtype PngGamma = PngGamma { getPngGamma :: Double }
instance Binary PngGamma where
get = PngGamma . (/ 100000) . fromIntegral <$> getWord32be
put = putWord32be . ceiling . (100000 *) . getPngGamma
data APngAnimationControl = APngAnimationControl
{ animationFrameCount :: !Word32
, animationPlayCount :: !Word32
}
deriving Show
data APngFrameDisposal
= APngDisposeNone
| APngDisposeBackground
| APngDisposePrevious
deriving Show
data APngBlendOp
= APngBlendSource
| APngBlendOver
deriving Show
data APngFrameControl = APngFrameControl
{ frameSequenceNum :: !Word32
, frameWidth :: !Word32
, frameHeight :: !Word32
, frameLeft :: !Word32
, frameTop :: !Word32
, frameDelayNumerator :: !Word16
, frameDelayDenuminator :: !Word16
, frameDisposal :: !APngFrameDisposal
, frameBlending :: !APngBlendOp
}
deriving Show
data PngImageType =
PngGreyscale
| PngTrueColour
| PngIndexedColor
| PngGreyscaleWithAlpha
| PngTrueColourWithAlpha
deriving Show
data PngRawImage = PngRawImage
{ header :: PngIHdr
, chunks :: [PngRawChunk]
}
type PngPalette = Palette' PixelRGB8
parsePalette :: PngRawChunk -> Either String PngPalette
parsePalette plte
| chunkLength plte `mod` 3 /= 0 = Left "Invalid palette size"
| otherwise = Palette' pixelCount . V.fromListN (3 * pixelCount) <$> pixels
where pixelUnpacker = replicateM (fromIntegral pixelCount * 3) get
pixelCount = fromIntegral $ chunkLength plte `div` 3
pixels = runGet pixelUnpacker (chunkData plte)
data PngRawChunk = PngRawChunk
{ chunkLength :: Word32
, chunkType :: ChunkSignature
, chunkCRC :: Word32
, chunkData :: L.ByteString
}
mkRawChunk :: ChunkSignature -> L.ByteString -> PngRawChunk
mkRawChunk sig binaryData = PngRawChunk
{ chunkLength = fromIntegral $ L.length binaryData
, chunkType = sig
, chunkCRC = pngComputeCrc [sig, binaryData]
, chunkData = binaryData
}
data PngChunk = PngChunk
{ pngChunkData :: L.ByteString
, pngChunkSignature :: ChunkSignature
}
data PngLowLevel a = PngLowLevel
{ pngImage :: Image a
, pngChunks :: [PngChunk]
}
data PngFilter =
FilterNone
| FilterSub
| FilterUp
| FilterAverage
| FilterPaeth
deriving (Enum, Show)
data PngInterlaceMethod =
PngNoInterlace
| PngInterlaceAdam7
deriving (Enum, Show)
instance Binary PngFilter where
put = putWord8 . toEnum . fromEnum
get = getWord8 >>= \w -> case w of
0 -> return FilterNone
1 -> return FilterSub
2 -> return FilterUp
3 -> return FilterAverage
4 -> return FilterPaeth
_ -> fail "Invalid scanline filter"
instance Binary PngRawImage where
put img = do
putLazyByteString pngSignature
put $ header img
mapM_ put $ chunks img
get = parseRawPngImage
instance Binary PngRawChunk where
put chunk = do
putWord32be $ chunkLength chunk
putLazyByteString $ chunkType chunk
when (chunkLength chunk /= 0)
(putLazyByteString $ chunkData chunk)
putWord32be $ chunkCRC chunk
get = do
size <- getWord32be
chunkSig <- getLazyByteString (fromIntegral $ L.length iHDRSignature)
imgData <- if size == 0
then return L.empty
else getLazyByteString (fromIntegral size)
crc <- getWord32be
let computedCrc = pngComputeCrc [chunkSig, imgData]
when (computedCrc `xor` crc /= 0)
(fail $ "Invalid CRC : " ++ show computedCrc ++ ", "
++ show crc)
return PngRawChunk {
chunkLength = size,
chunkData = imgData,
chunkCRC = crc,
chunkType = chunkSig
}
instance Binary PngIHdr where
put hdr = do
putWord32be 13
let inner = runPut $ do
putLazyByteString iHDRSignature
putWord32be $ width hdr
putWord32be $ height hdr
putWord8 $ bitDepth hdr
put $ colourType hdr
put $ compressionMethod hdr
put $ filterMethod hdr
put $ interlaceMethod hdr
crc = pngComputeCrc [inner]
putLazyByteString inner
putWord32be crc
get = do
_size <- getWord32be
ihdrSig <- getLazyByteString (L.length iHDRSignature)
when (ihdrSig /= iHDRSignature)
(fail "Invalid PNG file, wrong ihdr")
w <- getWord32be
h <- getWord32be
depth <- get
colorType <- get
compression <- get
filtermethod <- get
interlace <- get
_crc <- getWord32be
return PngIHdr {
width = w,
height = h,
bitDepth = depth,
colourType = colorType,
compressionMethod = compression,
filterMethod = filtermethod,
interlaceMethod = interlace
}
parseChunks :: Get [PngRawChunk]
parseChunks = do
chunk <- get
if chunkType chunk == iENDSignature
then return [chunk]
else (chunk:) <$> parseChunks
instance Binary PngInterlaceMethod where
get = getWord8 >>= \w -> case w of
0 -> return PngNoInterlace
1 -> return PngInterlaceAdam7
_ -> fail "Invalid interlace method"
put PngNoInterlace = putWord8 0
put PngInterlaceAdam7 = putWord8 1
parseRawPngImage :: Get PngRawImage
parseRawPngImage = do
sig <- getLazyByteString (L.length pngSignature)
when (sig /= pngSignature)
(fail "Invalid PNG file, signature broken")
ihdr <- get
chunkList <- parseChunks
return PngRawImage { header = ihdr, chunks = chunkList }
pngSignature :: ChunkSignature
pngSignature = L.pack [137, 80, 78, 71, 13, 10, 26, 10]
signature :: String -> ChunkSignature
signature = LS.pack
iHDRSignature :: ChunkSignature
iHDRSignature = signature "IHDR"
pLTESignature :: ChunkSignature
pLTESignature = signature "PLTE"
iDATSignature :: ChunkSignature
iDATSignature = signature "IDAT"
iENDSignature :: ChunkSignature
iENDSignature = signature "IEND"
tRNSSignature :: ChunkSignature
tRNSSignature = signature "tRNS"
gammaSignature :: ChunkSignature
gammaSignature = signature "gAMA"
pHYsSignature :: ChunkSignature
pHYsSignature = signature "pHYs"
tEXtSignature :: ChunkSignature
tEXtSignature = signature "tEXt"
zTXtSignature :: ChunkSignature
zTXtSignature = signature "zTXt"
animationControlSignature :: ChunkSignature
animationControlSignature = signature "acTL"
instance Binary PngImageType where
put PngGreyscale = putWord8 0
put PngTrueColour = putWord8 2
put PngIndexedColor = putWord8 3
put PngGreyscaleWithAlpha = putWord8 4
put PngTrueColourWithAlpha = putWord8 6
get = get >>= imageTypeOfCode
imageTypeOfCode :: Word8 -> Get PngImageType
imageTypeOfCode 0 = return PngGreyscale
imageTypeOfCode 2 = return PngTrueColour
imageTypeOfCode 3 = return PngIndexedColor
imageTypeOfCode 4 = return PngGreyscaleWithAlpha
imageTypeOfCode 6 = return PngTrueColourWithAlpha
imageTypeOfCode _ = fail "Invalid png color code"
pngCrcTable :: Vector Word32
pngCrcTable = fromListN 256 [ foldl' updateCrcConstant c [zero .. 7] | c <- [0 .. 255] ]
where zero = 0 :: Int
updateCrcConstant c _ | c .&. 1 /= 0 = magicConstant `xor` (c `unsafeShiftR` 1)
| otherwise = c `unsafeShiftR` 1
magicConstant = 0xedb88320 :: Word32
pngComputeCrc :: [L.ByteString] -> Word32
pngComputeCrc = (0xFFFFFFFF `xor`) . L.foldl' updateCrc 0xFFFFFFFF . L.concat
where updateCrc crc val =
let u32Val = fromIntegral val
lutVal = pngCrcTable ! (fromIntegral ((crc `xor` u32Val) .&. 0xFF))
in lutVal `xor` (crc `unsafeShiftR` 8)
chunksWithSig :: PngRawImage -> ChunkSignature -> [LS.ByteString]
chunksWithSig rawImg sig =
[chunkData chunk | chunk <- chunks rawImg, chunkType chunk == sig]