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

Fixes pertaining background color and grayscale conversions #270

Open
wants to merge 2 commits into
base: libpng16
Choose a base branch
from

Conversation

CyberShadow
Copy link

After adding libpng support to my image library, I wrote some test code which generated PNG files with all valid combinations of relevant parameters, and asked libpng to parse each into every compatible format supported by my image library. Some of these combinations produced unexpected results, which seem to be caused by bugs in libpng.

This pull request includes two patches which attempt to rectify these bugs. Please see the individual commits' commit messages for details.

FWIW - the source code for my test suite that identified this bug is here:
https://github.com/CyberShadow/ae/blob/master/utils/graphics/libpng.d

When processing transparent pixels (with png_set_strip_alpha) while
converting to grayscale (with png_set_rgb_to_gray), libpng failed to
convert the background color specified to the user to grayscale as
well. This caused the documented pattern of handling bKGD chunks to
fail for this corner case.

Note that in case of conversions in the opposite direction (grayscale
to RGB), the background color was already properly converted to RGB.

This patch fixes this inconsistency, and adds a test program to the
test suite to verify the fix.
When expanding low-bit (4-bit and lower) images to 8 or 16 bits, the
background color (as set by png_set_background) only had its
red/green/blue fields populated to the correctly expanded values, but
the gray values were erroneously left at their unexpanded values. This
caused transparency processing (as enabled by png_set_strip_alpha) to
use the unexpanded background color when substituting transparent
pixels.

Rectify this by updating the .gray fields of the background color, and
add a test.
@@ -0,0 +1,246 @@
/* pngadhoc.c
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After looking for a while, I couldn't find an existing place in the test suite where such tests would be really appropriate, so I added a new file.

PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
png_set_bKGD(png_ptr, info_ptr, &write_background);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, adding bKGD to the test PNG (here and in the second test) is not necessary, but I wanted to illustrate that the bug happens with the png_set_background usage as suggested by the manual.

@@ -1202,6 +1202,10 @@ png_init_palette_transformations(png_structrp png_ptr)
#endif /* READ_EXPAND && READ_BACKGROUND */
}

#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED
static int png_do_rgb_to_gray_1(png_structrp png_ptr, png_color_16p color, png_byte bit_depth);
#endif
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what the policy is regarding forward declarations versus shuffling function order around.

if ((png_ptr->transformations & PNG_RGB_TO_GRAY) ==
PNG_RGB_TO_GRAY_ERR)
png_error(png_ptr, "nongray background color");
}
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't add code to do the same conversion of png_ptr->trans_color. I couldn't find a situation where doing so would matter, and I don't understand why the grayscale code above is doing it. In any case, the conversion here is lossy, and since its result can't be used to actually perform color-key transparency, it would only have informational value, but even then, there's no way to query it.

*(dp++) = (png_byte)((color->green >> 8) & 0xff);
*(dp++) = (png_byte)(color->green & 0xff);
*(dp++) = (png_byte)((color->blue >> 8) & 0xff);
*(dp++) = (png_byte)(color->blue & 0xff);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ugly (encode / swap bytes just so png_do_rgb_to_gray decodes them again), but the alternative is an even uglier refactor.

@tangyaofang
Copy link
Contributor

Thank you for your use and contribution to libpng.

@jbowler
Copy link
Contributor

jbowler commented Dec 30, 2023

Yes; the transforms do not always compose correctly. I identified a number of cases (I'm not sure if this is one, probably not because memory format conversions are not a high priority).

The problem is that it is whack-a-mole with the 1.6 (and earlier) implementation. I tried to fix this in 1.7 and believe I got it all right but that's my version of 1.7; this (the transform code) is one place where a complete rewrite is in order (that's how I fixed it.)

As a result it's impossible to review a fix like this as just a one file change (to pngrtran.c). The first step is to come up with a test case using pngstest - i.e. a set of arguments to pngstest that demonstrate the problem or, possibly, since pngstest has no support at present for the swap transforms, to implement a new test program.

Without a test program accepting such a patch is inadvisable. Maybe in a major release if someone is committed to testing everything.

A better patch for libpng 1.6 is to detect the incompatible transforms when they are set and simply png_error out; that is way better than producing a completely valid PNG full of garbage. Clearly since this bug has not been widely reported people who encounter it have already worked round it by doing one or other of the composed transforms themselves.

@jbowler
Copy link
Contributor

jbowler commented Oct 4, 2024

@CyberShadow do you get a warning from inside png_init_read_transformations, something like:

"libpng does not support gamma+background+rgb_to_gray"

but any warning should be reported.

@CyberShadow
Copy link
Author

No, I do not remember encountering such a warning.

Worth pointing out that this pull request is over five years old, so my recall might not be perfect.

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 this pull request may close these issues.

3 participants