2
votes

I am writing a PNG decoder and I am encountering some weirdness. Going through the PNG file format, I have managed to decode PNGs with Color Index + tRNS (alpha) and True Color + tRNS (alpha) correctly. I am currently not sure why I cannot decode a PNG with True Color with Alpha type PNG. I have verified that my inflate of IDAT chunk is correct. Here's what the chunks looks like:

Width: 256
Height: 256
Bit Depth: 8
Color Type: 6
Compression: 0
Filter: 0
Interlace: 0

Length: 25
Type: tEXt
Data: 53 6f 66 74 77 61 72 65 00 41 64 6f 62 65 20 49 6d 61 67 65 52 65 61 64 79 
CRC: 71c9653c

Length: 20690
Type: IDAT
Data: 78 da ec 7d 09 9c 1c 57 99 df f7 7a a6 e7 3e 5a a3 fb ...
CRC: 21550259

The actual data is too long to be printed here. Here's my logic of decoding this, please correct me if I'm wrong:

  1. Inflate all the bytes given in the IDAT chunk
  2. Un-filter the inflated chunks. In this case, all filters are of type 0 and therefore, we simply discard the filter byte.
  3. Since this is color type 6, a pixel is represented by RGBA channels with 1 byte each. This means we need to interpret 4 bytes at a time. The following code is used:

    ByteBuffer image = BufferUtil.getDirectByteBuffer(data.length, ByteOrder.BIG_ENDIAN);
    int i = 0;
    while(i < data.length){
    int color = ( (data[i] & 0xff) << 24) | ( (data[i+1] & 0xff) << 16) | ( (data[i+2] & 0xff) << 8) | (data[i+3] & 0xff);
    image.putInt(color);
    i += 4;
    

What's strange is that I get mostly RRGGBBAA = 0x00000000 data resulting in a clear image with little color.

Decoded Image

Original Image

1

1 Answers

1
votes

The problem is you are neglecting to observe the filtering for each scanline.

From the image provided the decompressed data looks like

1 ffffffff 0 0 0 ...
2 0 0 0 0 0 0 ...
..

the first value in each line conforms to the filter method used [http://www.w3.org/TR/PNG/#9Filters]

the scanlines post processing will look like

ffffffff ffffffff ffffffff ...
ffffffff ffffffff ffffffff ...
...

here is some example code that handles methods 0, 1 and 2.

private static void processScanLine(byte filterValue, byte[] scanLine, byte[] previousScanLine) {
    switch(filterValue){
        case 0:break;
        case 1:
            for (int i =4;i<scanLine.length;i++){
                scanLine[i] = (byte)(scanLine[i]+scanLine[i-4]);
            }
        break;
        case 2:
            for (int i =0;i<scanLine.length;i++){
                scanLine[i] = (byte)(scanLine[i]+previousScanLine[i]);
            }
        break;
    }
}