PageRenderTime 46ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Codec/Picture/Png/Internal/Type.hs

http://github.com/Twinside/Juicy.Pixels
Haskell | 450 lines | 318 code | 64 blank | 68 comment | 7 complexity | 78520f18bf8dbf051473ea3019f7b66e MD5 | raw file
Possible License(s): BSD-3-Clause
  1. {-# LANGUAGE CPP #-}
  2. -- | Low level png module, you should import 'Codec.Picture.Png.Internal' instead.
  3. module Codec.Picture.Png.Internal.Type( PngIHdr( .. )
  4. , PngFilter( .. )
  5. , PngInterlaceMethod( .. )
  6. , PngPalette
  7. , PngImageType( .. )
  8. , PngPhysicalDimension( .. )
  9. , PngGamma( .. )
  10. , PngUnit( .. )
  11. , APngAnimationControl( .. )
  12. , APngFrameDisposal( .. )
  13. , APngBlendOp( .. )
  14. , APngFrameControl( .. )
  15. , parsePalette
  16. , pngComputeCrc
  17. , pLTESignature
  18. , iDATSignature
  19. , iENDSignature
  20. , tRNSSignature
  21. , tEXtSignature
  22. , zTXtSignature
  23. , gammaSignature
  24. , pHYsSignature
  25. , animationControlSignature
  26. -- * Low level types
  27. , ChunkSignature
  28. , PngRawImage( .. )
  29. , PngChunk( .. )
  30. , PngRawChunk( .. )
  31. , PngLowLevel( .. )
  32. , chunksWithSig
  33. , mkRawChunk
  34. ) where
  35. #if !MIN_VERSION_base(4,8,0)
  36. import Control.Applicative( (<$>), (<*>), pure )
  37. #endif
  38. import Control.Monad( when, replicateM )
  39. import Data.Bits( xor, (.&.), unsafeShiftR )
  40. import Data.Binary( Binary(..), Get, get )
  41. import Data.Binary.Get( getWord8
  42. , getWord32be
  43. , getLazyByteString
  44. )
  45. import Data.Binary.Put( runPut
  46. , putWord8
  47. , putWord32be
  48. , putLazyByteString
  49. )
  50. import Data.Vector.Unboxed( Vector, fromListN, (!) )
  51. import qualified Data.Vector.Storable as V
  52. import Data.List( foldl' )
  53. import Data.Word( Word32, Word16, Word8 )
  54. import qualified Data.ByteString.Lazy as L
  55. import qualified Data.ByteString.Lazy.Char8 as LS
  56. import Codec.Picture.Types
  57. import Codec.Picture.InternalHelper
  58. --------------------------------------------------
  59. ---- Types
  60. --------------------------------------------------
  61. -- | Value used to identify a png chunk, must be 4 bytes long.
  62. type ChunkSignature = L.ByteString
  63. -- | Generic header used in PNG images.
  64. data PngIHdr = PngIHdr
  65. { width :: !Word32 -- ^ Image width in number of pixel
  66. , height :: !Word32 -- ^ Image height in number of pixel
  67. , bitDepth :: !Word8 -- ^ Number of bit per sample
  68. , colourType :: !PngImageType -- ^ Kind of png image (greyscale, true color, indexed...)
  69. , compressionMethod :: !Word8 -- ^ Compression method used
  70. , filterMethod :: !Word8 -- ^ Must be 0
  71. , interlaceMethod :: !PngInterlaceMethod -- ^ If the image is interlaced (for progressive rendering)
  72. }
  73. deriving Show
  74. data PngUnit
  75. = PngUnitUnknown -- ^ 0 value
  76. | PngUnitMeter -- ^ 1 value
  77. instance Binary PngUnit where
  78. get = do
  79. v <- getWord8
  80. pure $ case v of
  81. 0 -> PngUnitUnknown
  82. 1 -> PngUnitMeter
  83. _ -> PngUnitUnknown
  84. put v = case v of
  85. PngUnitUnknown -> putWord8 0
  86. PngUnitMeter -> putWord8 1
  87. data PngPhysicalDimension = PngPhysicalDimension
  88. { pngDpiX :: !Word32
  89. , pngDpiY :: !Word32
  90. , pngUnit :: !PngUnit
  91. }
  92. instance Binary PngPhysicalDimension where
  93. get = PngPhysicalDimension <$> getWord32be <*> getWord32be <*> get
  94. put (PngPhysicalDimension dpx dpy unit) =
  95. putWord32be dpx >> putWord32be dpy >> put unit
  96. newtype PngGamma = PngGamma { getPngGamma :: Double }
  97. instance Binary PngGamma where
  98. get = PngGamma . (/ 100000) . fromIntegral <$> getWord32be
  99. put = putWord32be . ceiling . (100000 *) . getPngGamma
  100. data APngAnimationControl = APngAnimationControl
  101. { animationFrameCount :: !Word32
  102. , animationPlayCount :: !Word32
  103. }
  104. deriving Show
  105. -- | Encoded in a Word8
  106. data APngFrameDisposal
  107. -- | No disposal is done on this frame before rendering the
  108. -- next; the contents of the output buffer are left as is.
  109. -- Has Value 0
  110. = APngDisposeNone
  111. -- | The frame's region of the output buffer is to be cleared
  112. -- to fully transparent black before rendering the next frame.
  113. -- Has Value 1
  114. | APngDisposeBackground
  115. -- | the frame's region of the output buffer is to be reverted
  116. -- to the previous contents before rendering the next frame.
  117. -- Has Value 2
  118. | APngDisposePrevious
  119. deriving Show
  120. -- | Encoded in a Word8
  121. data APngBlendOp
  122. -- | Overwrite output buffer. has value '0'
  123. = APngBlendSource
  124. -- | Alpha blend to the output buffer. Has value '1'
  125. | APngBlendOver
  126. deriving Show
  127. data APngFrameControl = APngFrameControl
  128. { frameSequenceNum :: !Word32 -- ^ Starting from 0
  129. , frameWidth :: !Word32 -- ^ Width of the following frame
  130. , frameHeight :: !Word32 -- ^ Height of the following frame
  131. , frameLeft :: !Word32 -- X position where to render the frame.
  132. , frameTop :: !Word32 -- Y position where to render the frame.
  133. , frameDelayNumerator :: !Word16
  134. , frameDelayDenuminator :: !Word16
  135. , frameDisposal :: !APngFrameDisposal
  136. , frameBlending :: !APngBlendOp
  137. }
  138. deriving Show
  139. -- | What kind of information is encoded in the IDAT section
  140. -- of the PngFile
  141. data PngImageType =
  142. PngGreyscale
  143. | PngTrueColour
  144. | PngIndexedColor
  145. | PngGreyscaleWithAlpha
  146. | PngTrueColourWithAlpha
  147. deriving Show
  148. -- | Raw parsed image which need to be decoded.
  149. data PngRawImage = PngRawImage
  150. { header :: PngIHdr
  151. , chunks :: [PngRawChunk]
  152. }
  153. -- | Palette with indices beginning at 0 to elemcount - 1
  154. type PngPalette = Palette' PixelRGB8
  155. -- | Parse a palette from a png chunk.
  156. parsePalette :: PngRawChunk -> Either String PngPalette
  157. parsePalette plte
  158. | chunkLength plte `mod` 3 /= 0 = Left "Invalid palette size"
  159. | otherwise = Palette' pixelCount . V.fromListN (3 * pixelCount) <$> pixels
  160. where pixelUnpacker = replicateM (fromIntegral pixelCount * 3) get
  161. pixelCount = fromIntegral $ chunkLength plte `div` 3
  162. pixels = runGet pixelUnpacker (chunkData plte)
  163. -- | Data structure during real png loading/parsing
  164. data PngRawChunk = PngRawChunk
  165. { chunkLength :: Word32
  166. , chunkType :: ChunkSignature
  167. , chunkCRC :: Word32
  168. , chunkData :: L.ByteString
  169. }
  170. mkRawChunk :: ChunkSignature -> L.ByteString -> PngRawChunk
  171. mkRawChunk sig binaryData = PngRawChunk
  172. { chunkLength = fromIntegral $ L.length binaryData
  173. , chunkType = sig
  174. , chunkCRC = pngComputeCrc [sig, binaryData]
  175. , chunkData = binaryData
  176. }
  177. -- | PNG chunk representing some extra information found in the parsed file.
  178. data PngChunk = PngChunk
  179. { pngChunkData :: L.ByteString -- ^ The raw data inside the chunk
  180. , pngChunkSignature :: ChunkSignature -- ^ The name of the chunk.
  181. }
  182. -- | Low level access to PNG information
  183. data PngLowLevel a = PngLowLevel
  184. { pngImage :: Image a -- ^ The real uncompressed image
  185. , pngChunks :: [PngChunk] -- ^ List of raw chunk where some user data might be present.
  186. }
  187. -- | The pixels value should be :
  188. -- +---+---+
  189. -- | c | b |
  190. -- +---+---+
  191. -- | a | x |
  192. -- +---+---+
  193. -- x being the current filtered pixel
  194. data PngFilter =
  195. -- | Filt(x) = Orig(x), Recon(x) = Filt(x)
  196. FilterNone
  197. -- | Filt(x) = Orig(x) - Orig(a), Recon(x) = Filt(x) + Recon(a)
  198. | FilterSub
  199. -- | Filt(x) = Orig(x) - Orig(b), Recon(x) = Filt(x) + Recon(b)
  200. | FilterUp
  201. -- | Filt(x) = Orig(x) - floor((Orig(a) + Orig(b)) / 2),
  202. -- Recon(x) = Filt(x) + floor((Recon(a) + Recon(b)) / 2)
  203. | FilterAverage
  204. -- | Filt(x) = Orig(x) - PaethPredictor(Orig(a), Orig(b), Orig(c)),
  205. -- Recon(x) = Filt(x) + PaethPredictor(Recon(a), Recon(b), Recon(c))
  206. | FilterPaeth
  207. deriving (Enum, Show)
  208. -- | Different known interlace methods for PNG image
  209. data PngInterlaceMethod =
  210. -- | No interlacing, basic data ordering, line by line
  211. -- from left to right.
  212. PngNoInterlace
  213. -- | Use the Adam7 ordering, see `adam7Reordering`
  214. | PngInterlaceAdam7
  215. deriving (Enum, Show)
  216. --------------------------------------------------
  217. ---- Instances
  218. --------------------------------------------------
  219. instance Binary PngFilter where
  220. put = putWord8 . toEnum . fromEnum
  221. get = getWord8 >>= \w -> case w of
  222. 0 -> return FilterNone
  223. 1 -> return FilterSub
  224. 2 -> return FilterUp
  225. 3 -> return FilterAverage
  226. 4 -> return FilterPaeth
  227. _ -> fail "Invalid scanline filter"
  228. instance Binary PngRawImage where
  229. put img = do
  230. putLazyByteString pngSignature
  231. put $ header img
  232. mapM_ put $ chunks img
  233. get = parseRawPngImage
  234. instance Binary PngRawChunk where
  235. put chunk = do
  236. putWord32be $ chunkLength chunk
  237. putLazyByteString $ chunkType chunk
  238. when (chunkLength chunk /= 0)
  239. (putLazyByteString $ chunkData chunk)
  240. putWord32be $ chunkCRC chunk
  241. get = do
  242. size <- getWord32be
  243. chunkSig <- getLazyByteString (fromIntegral $ L.length iHDRSignature)
  244. imgData <- if size == 0
  245. then return L.empty
  246. else getLazyByteString (fromIntegral size)
  247. crc <- getWord32be
  248. let computedCrc = pngComputeCrc [chunkSig, imgData]
  249. when (computedCrc `xor` crc /= 0)
  250. (fail $ "Invalid CRC : " ++ show computedCrc ++ ", "
  251. ++ show crc)
  252. return PngRawChunk {
  253. chunkLength = size,
  254. chunkData = imgData,
  255. chunkCRC = crc,
  256. chunkType = chunkSig
  257. }
  258. instance Binary PngIHdr where
  259. put hdr = do
  260. putWord32be 13
  261. let inner = runPut $ do
  262. putLazyByteString iHDRSignature
  263. putWord32be $ width hdr
  264. putWord32be $ height hdr
  265. putWord8 $ bitDepth hdr
  266. put $ colourType hdr
  267. put $ compressionMethod hdr
  268. put $ filterMethod hdr
  269. put $ interlaceMethod hdr
  270. crc = pngComputeCrc [inner]
  271. putLazyByteString inner
  272. putWord32be crc
  273. get = do
  274. _size <- getWord32be
  275. ihdrSig <- getLazyByteString (L.length iHDRSignature)
  276. when (ihdrSig /= iHDRSignature)
  277. (fail "Invalid PNG file, wrong ihdr")
  278. w <- getWord32be
  279. h <- getWord32be
  280. depth <- get
  281. colorType <- get
  282. compression <- get
  283. filtermethod <- get
  284. interlace <- get
  285. _crc <- getWord32be
  286. return PngIHdr {
  287. width = w,
  288. height = h,
  289. bitDepth = depth,
  290. colourType = colorType,
  291. compressionMethod = compression,
  292. filterMethod = filtermethod,
  293. interlaceMethod = interlace
  294. }
  295. -- | Parse method for a png chunk, without decompression.
  296. parseChunks :: Get [PngRawChunk]
  297. parseChunks = do
  298. chunk <- get
  299. if chunkType chunk == iENDSignature
  300. then return [chunk]
  301. else (chunk:) <$> parseChunks
  302. instance Binary PngInterlaceMethod where
  303. get = getWord8 >>= \w -> case w of
  304. 0 -> return PngNoInterlace
  305. 1 -> return PngInterlaceAdam7
  306. _ -> fail "Invalid interlace method"
  307. put PngNoInterlace = putWord8 0
  308. put PngInterlaceAdam7 = putWord8 1
  309. -- | Implementation of the get method for the PngRawImage,
  310. -- unpack raw data, without decompressing it.
  311. parseRawPngImage :: Get PngRawImage
  312. parseRawPngImage = do
  313. sig <- getLazyByteString (L.length pngSignature)
  314. when (sig /= pngSignature)
  315. (fail "Invalid PNG file, signature broken")
  316. ihdr <- get
  317. chunkList <- parseChunks
  318. return PngRawImage { header = ihdr, chunks = chunkList }
  319. --------------------------------------------------
  320. ---- functions
  321. --------------------------------------------------
  322. -- | Signature signalling that the following data will be a png image
  323. -- in the png bit stream
  324. pngSignature :: ChunkSignature
  325. pngSignature = L.pack [137, 80, 78, 71, 13, 10, 26, 10]
  326. -- | Helper function to help pack signatures.
  327. signature :: String -> ChunkSignature
  328. signature = LS.pack
  329. -- | Signature for the header chunk of png (must be the first)
  330. iHDRSignature :: ChunkSignature
  331. iHDRSignature = signature "IHDR"
  332. -- | Signature for a palette chunk in the pgn file. Must
  333. -- occure before iDAT.
  334. pLTESignature :: ChunkSignature
  335. pLTESignature = signature "PLTE"
  336. -- | Signature for a data chuck (with image parts in it)
  337. iDATSignature :: ChunkSignature
  338. iDATSignature = signature "IDAT"
  339. -- | Signature for the last chunk of a png image, telling
  340. -- the end.
  341. iENDSignature :: ChunkSignature
  342. iENDSignature = signature "IEND"
  343. tRNSSignature :: ChunkSignature
  344. tRNSSignature = signature "tRNS"
  345. gammaSignature :: ChunkSignature
  346. gammaSignature = signature "gAMA"
  347. pHYsSignature :: ChunkSignature
  348. pHYsSignature = signature "pHYs"
  349. tEXtSignature :: ChunkSignature
  350. tEXtSignature = signature "tEXt"
  351. zTXtSignature :: ChunkSignature
  352. zTXtSignature = signature "zTXt"
  353. animationControlSignature :: ChunkSignature
  354. animationControlSignature = signature "acTL"
  355. instance Binary PngImageType where
  356. put PngGreyscale = putWord8 0
  357. put PngTrueColour = putWord8 2
  358. put PngIndexedColor = putWord8 3
  359. put PngGreyscaleWithAlpha = putWord8 4
  360. put PngTrueColourWithAlpha = putWord8 6
  361. get = get >>= imageTypeOfCode
  362. imageTypeOfCode :: Word8 -> Get PngImageType
  363. imageTypeOfCode 0 = return PngGreyscale
  364. imageTypeOfCode 2 = return PngTrueColour
  365. imageTypeOfCode 3 = return PngIndexedColor
  366. imageTypeOfCode 4 = return PngGreyscaleWithAlpha
  367. imageTypeOfCode 6 = return PngTrueColourWithAlpha
  368. imageTypeOfCode _ = fail "Invalid png color code"
  369. -- | From the Annex D of the png specification.
  370. pngCrcTable :: Vector Word32
  371. pngCrcTable = fromListN 256 [ foldl' updateCrcConstant c [zero .. 7] | c <- [0 .. 255] ]
  372. where zero = 0 :: Int -- To avoid defaulting to Integer
  373. updateCrcConstant c _ | c .&. 1 /= 0 = magicConstant `xor` (c `unsafeShiftR` 1)
  374. | otherwise = c `unsafeShiftR` 1
  375. magicConstant = 0xedb88320 :: Word32
  376. -- | Compute the CRC of a raw buffer, as described in annex D of the PNG
  377. -- specification.
  378. pngComputeCrc :: [L.ByteString] -> Word32
  379. pngComputeCrc = (0xFFFFFFFF `xor`) . L.foldl' updateCrc 0xFFFFFFFF . L.concat
  380. where updateCrc crc val =
  381. let u32Val = fromIntegral val
  382. lutVal = pngCrcTable ! (fromIntegral ((crc `xor` u32Val) .&. 0xFF))
  383. in lutVal `xor` (crc `unsafeShiftR` 8)
  384. chunksWithSig :: PngRawImage -> ChunkSignature -> [LS.ByteString]
  385. chunksWithSig rawImg sig =
  386. [chunkData chunk | chunk <- chunks rawImg, chunkType chunk == sig]