-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathPngType.hs
487 lines (412 loc) · 16.5 KB
/
PngType.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
-- Taken from the JuicyPixels source so that PalettedPng.hs can work properly.
{-# LANGUAGE CPP #-}
-- | Low level png module, you should import 'Codec.Picture.Png' instead.
module PngType( PngIHdr( .. )
, PngFilter( .. )
, PngInterlaceMethod( .. )
, PngPalette
, PngImageType( .. )
, PngPhysicalDimension( .. )
, PngGamma( .. )
, PngUnit( .. )
, APngAnimationControl( .. )
, APngFrameDisposal( .. )
, APngBlendOp( .. )
, APngFrameControl( .. )
, parsePalette
, pngComputeCrc
, pLTESignature
, iDATSignature
, iENDSignature
, tRNSSignature
, tEXtSignature
, zTXtSignature
, gammaSignature
, pHYsSignature
, animationControlSignature
-- * Low level types
, ChunkSignature
, PngRawImage( .. )
, PngChunk( .. )
, PngRawChunk( .. )
, PngLowLevel( .. )
, chunksWithSig
, mkRawChunk
, runGetStrict
) where
import Control.Applicative( (<$>), (<*>), pure )
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 LChar
import Codec.Picture.Types
import qualified Data.ByteString as B
import Data.Binary( Binary( get ) )
import Data.Binary.Get( Get
, getRemainingLazyByteString
)
import qualified Data.Binary.Get as G
import Control.Applicative( (<$>) )
import qualified Control.Exception as E
-- I feel so dirty. :(
import System.IO.Unsafe( unsafePerformIO )
decode :: (Binary a) => B.ByteString -> Either String a
decode = runGetStrict get
runGet :: Get a -> L.ByteString -> Either String a
runGet act = unpack . G.runGetOrFail act
where unpack (Left (_, _, str)) = Left str
unpack (Right (_, _, element)) = Right element
runGetStrict :: Get a -> B.ByteString -> Either String a
runGetStrict act buffer = runGet act $ L.fromChunks [buffer]
getRemainingBytes :: Get B.ByteString
getRemainingBytes = do
rest <- getRemainingLazyByteString
return $ case L.toChunks rest of
[] -> B.empty
[a] -> a
lst -> B.concat lst
getRemainingLazyBytes :: Get L.ByteString
getRemainingLazyBytes = getRemainingLazyByteString
--------------------------------------------------
---- Types
--------------------------------------------------
-- | Value used to identify a png chunk, must be 4 bytes long.
type ChunkSignature = L.ByteString
-- | Generic header used in PNG images.
data PngIHdr = PngIHdr
{ width :: !Word32 -- ^ Image width in number of pixel
, height :: !Word32 -- ^ Image height in number of pixel
, bitDepth :: !Word8 -- ^ Number of bit per sample
, colourType :: !PngImageType -- ^ Kind of png image (greyscale, true color, indexed...)
, compressionMethod :: !Word8 -- ^ Compression method used
, filterMethod :: !Word8 -- ^ Must be 0
, interlaceMethod :: !PngInterlaceMethod -- ^ If the image is interlaced (for progressive rendering)
}
deriving Show
data PngUnit
= PngUnitUnknown -- ^ 0 value
| PngUnitMeter -- ^ 1 value
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
-- | Encoded in a Word8
data APngFrameDisposal
-- | No disposal is done on this frame before rendering the
-- next; the contents of the output buffer are left as is.
-- Has Value 0
= APngDisposeNone
-- | The frame's region of the output buffer is to be cleared
-- to fully transparent black before rendering the next frame.
-- Has Value 1
| APngDisposeBackground
-- | the frame's region of the output buffer is to be reverted
-- to the previous contents before rendering the next frame.
-- Has Value 2
| APngDisposePrevious
deriving Show
-- | Encoded in a Word8
data APngBlendOp
-- | Overwrite output buffer. has value '0'
= APngBlendSource
-- | Alpha blend to the output buffer. Has value '1'
| APngBlendOver
deriving Show
data APngFrameControl = APngFrameControl
{ frameSequenceNum :: !Word32 -- ^ Starting from 0
, frameWidth :: !Word32 -- ^ Width of the following frame
, frameHeight :: !Word32 -- ^ Height of the following frame
, frameLeft :: !Word32 -- X position where to render the frame.
, frameTop :: !Word32 -- Y position where to render the frame.
, frameDelayNumerator :: !Word16
, frameDelayDenuminator :: !Word16
, frameDisposal :: !APngFrameDisposal
, frameBlending :: !APngBlendOp
}
deriving Show
-- | What kind of information is encoded in the IDAT section
-- of the PngFile
data PngImageType =
PngGreyscale
| PngTrueColour
| PngIndexedColor
| PngGreyscaleWithAlpha
| PngTrueColourWithAlpha
deriving Show
-- | Raw parsed image which need to be decoded.
data PngRawImage = PngRawImage
{ header :: PngIHdr
, chunks :: [PngRawChunk]
}
-- | Palette with indices beginning at 0 to elemcount - 1
type PngPalette = Image PixelRGB8
-- | Parse a palette from a png chunk.
parsePalette :: PngRawChunk -> Either String PngPalette
parsePalette plte
| chunkLength plte `mod` 3 /= 0 = Left "Invalid palette size"
| otherwise = Image pixelCount 1 . V.fromListN (3 * pixelCount) <$> pixels
where pixelUnpacker = replicateM (fromIntegral pixelCount * 3) get
pixelCount = fromIntegral $ chunkLength plte `div` 3
pixels = runGet pixelUnpacker (chunkData plte)
-- | Data structure during real png loading/parsing
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
}
-- | PNG chunk representing some extra information found in the parsed file.
data PngChunk = PngChunk
{ pngChunkData :: L.ByteString -- ^ The raw data inside the chunk
, pngChunkSignature :: ChunkSignature -- ^ The name of the chunk.
}
-- | Low level access to PNG information
data PngLowLevel a = PngLowLevel
{ pngImage :: Image a -- ^ The real uncompressed image
, pngChunks :: [PngChunk] -- ^ List of raw chunk where some user data might be present.
}
-- | The pixels value should be :
-- +---+---+
-- | c | b |
-- +---+---+
-- | a | x |
-- +---+---+
-- x being the current filtered pixel
data PngFilter =
-- | Filt(x) = Orig(x), Recon(x) = Filt(x)
FilterNone
-- | Filt(x) = Orig(x) - Orig(a), Recon(x) = Filt(x) + Recon(a)
| FilterSub
-- | Filt(x) = Orig(x) - Orig(b), Recon(x) = Filt(x) + Recon(b)
| FilterUp
-- | Filt(x) = Orig(x) - floor((Orig(a) + Orig(b)) / 2),
-- Recon(x) = Filt(x) + floor((Recon(a) + Recon(b)) / 2)
| FilterAverage
-- | Filt(x) = Orig(x) - PaethPredictor(Orig(a), Orig(b), Orig(c)),
-- Recon(x) = Filt(x) + PaethPredictor(Recon(a), Recon(b), Recon(c))
| FilterPaeth
deriving (Enum, Show)
-- | Different known interlace methods for PNG image
data PngInterlaceMethod =
-- | No interlacing, basic data ordering, line by line
-- from left to right.
PngNoInterlace
-- | Use the Adam7 ordering, see `adam7Reordering`
| PngInterlaceAdam7
deriving (Enum, Show)
--------------------------------------------------
---- Instances
--------------------------------------------------
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
}
-- | Parse method for a png chunk, without decompression.
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
-- | Implementation of the get method for the PngRawImage,
-- unpack raw data, without decompressing it.
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 }
--------------------------------------------------
---- functions
--------------------------------------------------
-- | Signature signalling that the following data will be a png image
-- in the png bit stream
pngSignature :: ChunkSignature
pngSignature = L.pack [137, 80, 78, 71, 13, 10, 26, 10]
-- | Helper function to help pack signatures.
signature :: String -> ChunkSignature
signature = LChar.pack
-- | Signature for the header chunk of png (must be the first)
iHDRSignature :: ChunkSignature
iHDRSignature = signature "IHDR"
-- | Signature for a palette chunk in the pgn file. Must
-- occure before iDAT.
pLTESignature :: ChunkSignature
pLTESignature = signature "PLTE"
-- | Signature for a data chuck (with image parts in it)
iDATSignature :: ChunkSignature
iDATSignature = signature "IDAT"
-- | Signature for the last chunk of a png image, telling
-- the end.
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"
-- | From the Annex D of the png specification.
pngCrcTable :: Vector Word32
pngCrcTable = fromListN 256 [ foldl' updateCrcConstant c [zero .. 7] | c <- [0 .. 255] ]
where zero = 0 :: Int -- To avoid defaulting to Integer
updateCrcConstant c _ | c .&. 1 /= 0 = magicConstant `xor` (c `unsafeShiftR` 1)
| otherwise = c `unsafeShiftR` 1
magicConstant = 0xedb88320 :: Word32
-- | Compute the CRC of a raw buffer, as described in annex D of the PNG
-- specification.
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 -> [LChar.ByteString]
chunksWithSig rawImg sig =
[chunkData chunk | chunk <- chunks rawImg, chunkType chunk == sig]