Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some Indexed PNGs Rendering Improperly #97

Closed
FoamyGuy opened this issue Feb 21, 2025 · 1 comment · Fixed by #98
Closed

Some Indexed PNGs Rendering Improperly #97

FoamyGuy opened this issue Feb 21, 2025 · 1 comment · Fixed by #98

Comments

@FoamyGuy
Copy link
Contributor

Medium sized brain dump about this issue.

With these two images:

blue heart image: Image

pink heart image: Image

The blue one renders properly and looks correct.

The pink one looks to be mostly corrupted or distorted. Here is it decoded with this library and put into a scaled Group to enlarge it

Image

The 3 rows of pixels above the last row that contains pink pixels look correct to my eye, but the rest of the rows are mostly messed up in a way that doesn't seem to have a discernible pattern.

In comparing the two images to try to understand what is different about them I noticed the blue one has an sRGB chunk with value set to 0 and the pink one does not. But our code seems to be ignoring that chunk anyhow. I confirmed it was getting skipped by adding a print statement here: https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad/blob/main/adafruit_imageload/png.py#L101

I also tried modifying the pink image to add the sRGB chunk and get the same results when decoded and rendered by this library. I can provide that file if someone else is working on that and thinks it may help.

The other main difference between the two images is that the blue one has 3 colors in its palette: black, dark blue, light blue. The black is set to transparent with the tRNS chunk having size 1 and value 0x00

The pink one has 4 colors in its palette: grey, black, light pink, dark pink. The grey and black are set to transparent with the tRNS chunk having size 2 and value 0x00, 0x00

They both render correctly inside of gimp, so I believe there must be something our implementation is doing wrong or missing, but I haven't figured out what. I guess the 4th color in the palette could be some how related to it not working, but I don't see any clear mechanism for it cause trouble in the code.

My suspicion was on the decompression of the data, but as far as I can tell it seems to be going fine. I've tested zlib.decompress() on the same 49 IDAT bytes on PC and MCU and verified the ouput results are the same in both cases.

# compressed: 
b'x\xdac\x0cex\xcd\xcc\xf0\xfa4\xe3~\x06F\xa6?\x0e\x0cL\xff\x19\x18\x98X\x19\x18\x18\xf4\xff30p\xffa``\xfa\xc0\xc0\xfc\x9f\xe3\x00\x00\xdd\xdc\n\xf3'

# decompressed
b'\x01U\x00\xeb\x03\x00\xeb\xcb\x01\xbf\x00\x01\x02\xfc@\x00\x02\xff\x00\x00\x02\x05\x00\x00\x00/\xff\x00\x00\x0b\xfc\x00\x00\x02\xf0\x00\x03\xff\x08\xc0'

If I use this to "undo" the automatic transparency setting I can see that some of the "empty" pixels are actually the grey (color index 0), and others are black (color index 1)

palette.make_opaque(0)
palette.make_opaque(1)

Image

I've printed out the raw bits of the decompressed data and confirmed that the seem to align with the visual appearance of this.

@FoamyGuy
Copy link
Contributor Author

I think I have a lead on the cause of this. If google AI is to be trusted (sometimes I guess?) then all PNGs including indexed PNGs use filtering.

Image

It seems to me that our implementation does not handle any filtering for indexed images. For RGB images the code here reads the filter byte per scanline and then decides what to do about it immediately after

filter_ = data_bytes[src]
src += 1
if filter_ == 0:
line[0:scanline] = data_bytes[src : src + scanline]
elif filter_ == 1: # sub
for i in range(scanline):
a = line[i - unit] if i >= unit else 0
line[i] = (data_bytes[src] + a) & 0xFF
src += 1
elif filter_ == 2: # up
for i in range(scanline):
b = prev[i]
line[i] = (data_bytes[src] + b) & 0xFF
src += 1
elif filter_ == 3: # average
for i in range(scanline):
a = line[i - unit] if i >= unit else 0
b = prev[i]
line[i] = (data_bytes[src] + ((a + b) >> 1)) & 0xFF
src += 1
elif filter_ == 4: # paeth

As far as I can tell for indexed images this code is basically ignoring the filter byte by the way it's adding an "extra" 1.

With the blue heart image the filter byte seems to always be 0x00, however with the pink heart there is mix of 0x01, 0x02, 0x03and a few0x00` (the rows that render properly presumably.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant