Image corruption with invalid transparency
Advanced PNG optimization program
Status: Beta
Brought to you by:
cosmin
Tested with current v0.7.8 Win32 binary. Using: optipng.exe -q -i0 -zc1 -zm8 -zs3 -f0 -force "%~1"
(but issue will occur with most conversions / typical parameters).
When getting transparency chunk via libpng, validity is not tested before converting color type. Here, the RGB transparency index does not appear in the palette.
This can lead to image corruption. See attached.
I have highlighted the defective pixel (that becomes full transparent), to red for clarity - attached.
I started to debug this before, but lost my notes...
I observe the issue with non-paletted images (RGB/A), that have a
tRNS
chunk that specifies an RGB value not present in an image. May affect grayscale images too (G/A) ?When the image is optimised as indexed, the transparency is wrongly applied to the palette index.
I think the issue is in the use of libpng
png_get_tRNS
…?“…For non-paletted images, the function retrieves the single color value which is treated as fully transparent. If the transparency information is valid, i.e. PNG_INFO_tRNS bit is set for info_ptr >valid: * trans shall be set to the transparency values for a paletted image. Values for the data shall be in range [0,255], ranging from fully transparent to fully opaque, respectively. * num_trans shall be set to the number of transparency values * trans_values shall be set to the single color value specified for non-paletted images. …”
Last edit: Andrew 2024-01-26
Andrew, I tried to reproduce the corruption that you're seeing, but I couldn't. I tried it both on Windows and Linux, and both with the embedded libpng and with the system libpng. If you could please take a look at my command line and let me know what I am missing.
The resulting file
out.png
is identical toactions.png
.And then I tried the same but with
actions-broken.png
:The resulting file
out.png
is not identical toactions-broken.png
, but the image content is the same. I can even see the red pixel, which is the difference in transparency that you mentioned, but that is NOT the output of optipng, from what I'm seeing:My conclusion, from my own experiments, is that
actions-broken.png
must have come from some other place, because I could not breakactions.png
by using optipng in the way that you reported.Please ignore my previous message. I must have done something in which I did not manipulate the test images correctly, before running my tests; but after a full reset of my test environment, now I confirm that you bug report is 100% legit.
Example 2
To aid debugging, this test case is perhaps more obvious?
See attached 'toolbar.png'. The image is RGB with a
tRNS
chunk defining RGB(0,0,1) as transparent. Critically, this colour doesn't exist in the image, so there should be no transparency. When viewing, the image has a bright green background. Details are also confirmed with TweakPNG.Image is optimized on Win32 with Batch command line
optipng.exe -v -f0 -force "%~1" -out "%~1.out.png"
,producing:
The resulting output image is now corrupted. Viewing the image, we no longer have a green background- it has become transparent. During optimization, the RGB image was reduced to indexed (correctly). As the transparent colour was never present in the image, the first palette entry is selected (in error!) as being transparent. This can be confirmed with TweakPNG. This may also occur with greyscale + transparency - but is untested. If the original image is edited, so that the
tRNS
chunk references a present colour (e.g. magenta RGB(255,0,255)...), everything functions correctly.Expected behaviour is no transparency is set, when the defined transparent colour is not present.
This patch should fix the problem, but I'd like to take another day or two so that I can see what other changes I could make to the image reduction code. For example: opportunities to remove tRNS from grayscale images exists, just as with RGB images, but they are ignored.
Another thing that I want to point out is that this kind of fix ought to be backported to older OptiPNG versions also, e.g. to give a chance to downstream package maintainers to apply the fix even if they don't plan on upgrading to the latest version.