Xhendos
Xhendos

Reputation: 33

PNG decompressed IDAT chunk. How to read?

I have read the PNG specifications too much times and still confused how I should interpret the IDAT chunk. I have it decompressed using zlib and got all of the bytes that my IDAT chunk got.

I made an example image using krita. It's an 3x2 PNG image containing a different color every pixel. See the 3 by 2 PNG image here

According to the PNG specification about filters it says that when the first byte of the IDAT chunk is 1 the filter method that have been applied is

Filtered(byte) = Original(byte) - Original(previous_byte)

With that formula in mind I decompressed my IDAT chunk (which was 29 bytes in length to store only 6 pixels). The first byte (which is byte number 0) contains the value 1. That is where the formula comes from.

Byte#    Vaue
0        1
1        224
2        215
3        200
4        227
5        241
6        48
7        2
8        36
9        225
10       1
11       253
12       255
13       195
14       245
15       182
16       244
17       232
18       245
19       57
20       0
21       0
22       0
23       0
24       0
25       0
26       0
27       0
28       0

The first pixel is supposed to be RGB(224, 215, 200) which I reconstructed with a RGB to color converter. This seems pretty much the same color as the original pixel in the image. Here are my thoughts about all the color pixels.

Pixel 1: RGB(224, 215, 200) [read from byte 1, byte2 and byte3]
Pixel 2: RGB(195, 200, 248) [because byte 4:227 byte5:241 byte6:48]
Pixel 3: RGB(197, 236, 217) [because byte 7:2 byte8:36 byte9:225]
Pixel 4: RGB(198, 233, 217) [because byte10:1 byte11:253 byte12:255]
Pixel 5: RGB(137, 222, 142) [because byte13:195 byte14:245 byte15:182]
Pixel 6: RGB(107, 198, 131) [because byte16:244 byte17:232 byte18:245]

I have used the formula to get all the values from the pixels. Reconstructing pixel 1, 2 and 3 looks pretty much the same, but pixel 4, 5 and 6 are not what I have expected. I think I am not reading the IDAT chunk the correct way. That could explain why there are 29 bytes for only 6 pixels RGB. I expected 19 bytes because 3 times 6 is 18 and 1 byte for the filtering method.

The IHDR says that the bit depth is 8 and the color type is 2. From the table in the specifications it says that each pixel is an R, G and B triple. Could someone point me to the right direction to read the IDAT chunk and explain it's length?

Upvotes: 1

Views: 6493

Answers (1)

Jongware
Jongware

Reputation: 22457

Your decompressed result length of 29 is not correct, which may have lead to your confusion.

Your image is 3x2 RGB pixels. That would be 3*3 * 2 = 18 bytes of data, plus 1 extra byte per row; a total of 20 bytes. Somehow you got an extra 9 dummy bytes, not part of the compressed data.

(I reconstructed your tiny image from the larger one and happily got the exact same numbers, else the explanation would necessarily be purely theoretical. For ease, I determined the offset of the zipped data with a hex viewer.)

>>> with open ('3x2b.png','rb') as f:
...   result = f.seek (0x6a)
...   data = f.read()
... 
>>> d = zlib.decompress(data)
>>> print ([x for x in d])
[1, 224, 215, 200, 227, 241, 48, 2, 36, 225, 1, 253, 255, 195, 245, 182, 244, 232, 245, 57]

This 'unpacks' to the following two rows, with 3 RGB pixel values each:

filter  RGB          RGB           RGB
1      (224,215,200) (227,241,48)  (2,36,225)
1      (253,255,195) (245,182,244, (232,245,57)

All these values may be relative to an earlier result: the last complete row read before it, or the pixel to its left. For the first row, you must assume a row of all zeroes; the value "left" of the first pixel must be assumed to be 0 as well.

You see the two bytes marked 'filter'? That is where you went wrong. Each row has a filter byte of its own. You used the filter byte itself for the calculation of the second row.

Adding (the inverse of the "Sub" filter as indicated by the filter 1) yields in

; start of row 0, filter is 1 and 'initial pixel' is (0,0,0)
(224,215,200) (224+227,215+241,200+48)
             =(195,200,248)
                            (195+2,200+36,248+225)
                           =(197,236,217)
; restart for row 1, filter is 1 again and start value (0,0,0):
(253,255,195) (253+245,255+182,195+244)
             =(242,181,183)
                            (242+232,181+245,183+57)
                           =(218,170,240)

... exactly the colors I started out with.

This is Filter 1 ("Sub") and so uses the values to its left; for Filter 2 ("Up"), you need to use the corresponding byte in the previously decoded row, and for Average and Paeth, you need both.

Upvotes: 3

Related Questions