diff --git a/CHANGES b/CHANGES index 111e3a379..bb62a3fc4 100644 --- a/CHANGES +++ b/CHANGES @@ -3204,6 +3204,7 @@ Version 1.5.1beta08 [January 23, 2011] skipping of unread data (e.g., if the reader marks a chunk to be skipped.) Version 1.5.1beta09 [January 23, 2011] +Subject: [PATCH] Enhance pngvalid, correct an error in gray_to_rgb, correct doc error. Send comments/corrections/commendations to png-mng-implement at lists.sf.net (subscription required; visit diff --git a/Makefile.am b/Makefile.am index 81e173270..bff416557 100644 --- a/Makefile.am +++ b/Makefile.am @@ -120,7 +120,8 @@ pngvalid.o pngtest.o: pnglibconf.h SYMBOL_CFLAGS = -DPNGLIB_LIBNAME='PNG@PNGLIB_MAJOR@@PNGLIB_MINOR@_0'\ -DPNGLIB_VERSION='@PNGLIB_VERSION@'\ - -DSYMBOL_PREFIX='$(SYMBOL_PREFIX)' + -DSYMBOL_PREFIX='$(SYMBOL_PREFIX)'\ + -DPNG_NO_USE_READ_MACROS .dfn.out: rm -f $@ dfn.c dfn?.out diff --git a/Makefile.in b/Makefile.in index 3a3119e7f..9576c2a99 100644 --- a/Makefile.in +++ b/Makefile.in @@ -357,7 +357,8 @@ config.sub configure depcomp install-sh ltmain.sh missing SUFFIXES = .chk .dfn .out SYMBOL_CFLAGS = -DPNGLIB_LIBNAME='PNG@PNGLIB_MAJOR@@PNGLIB_MINOR@_0'\ -DPNGLIB_VERSION='@PNGLIB_VERSION@'\ - -DSYMBOL_PREFIX='$(SYMBOL_PREFIX)' + -DSYMBOL_PREFIX='$(SYMBOL_PREFIX)'\ + -DPNG_NO_USE_READ_MACROS all: config.h $(MAKE) $(AM_MAKEFLAGS) all-am diff --git a/libpng-manual.txt b/libpng-manual.txt index 9d16edc30..9624c9c53 100644 --- a/libpng-manual.txt +++ b/libpng-manual.txt @@ -1341,16 +1341,12 @@ Copyright (c) 1998-01-04 Charles Poynton Y = 0.212671 * R + 0.715160 * G + 0.072169 * B -Libpng approximates this with +Libpng approximates this integers scaled by 32768: - Y = 0.21268 * R + 0.7151 * G + 0.07217 * B - -which can be expressed with integers as - - Y = (6969 * R + 23434 * G + 2365 * B)/32768 + Y = (6968 * R + 23434 * G + 2366 * B)/32768 The calculation is done in a linear colorspace, if the image gamma -is known. +can be determined. If you have a grayscale and you are using png_set_expand_depth(), png_set_expand(), or png_set_gray_to_rgb to change to truecolor or to diff --git a/png.h b/png.h index 112250efc..a32d6ea7e 100644 --- a/png.h +++ b/png.h @@ -1067,7 +1067,9 @@ PNG_EXPORT(30, void, png_set_bgr, (png_structp png_ptr)); #ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED /* Expand the grayscale to 24-bit RGB if necessary. */ PNG_EXPORT(31, void, png_set_gray_to_rgb, (png_structp png_ptr)); +#endif +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED /* Reduce RGB to grayscale. */ PNG_FP_EXPORT(32, void, png_set_rgb_to_gray, (png_structp png_ptr, int error_action, double red, double green)); @@ -2203,31 +2205,6 @@ PNG_EXPORT(216, png_uint_32, png_get_io_chunk_type, (png_uint_32)32767) / (png_uint_32)65535L) #endif /* PNG_READ_COMPOSITE_NODIV_SUPPORTED */ -#ifdef PNG_USE_READ_MACROS -/* Inline macros to do direct reads of bytes from the input buffer. - * The png_get_int_32() routine assumes we are using two's complement - * format for negative values, which is almost certainly true. - */ -# define png_get_uint_32(buf) \ - (((png_uint_32)(*(buf)) << 24) + \ - ((png_uint_32)(*((buf) + 1)) << 16) + \ - ((png_uint_32)(*((buf) + 2)) << 8) + \ - ((png_uint_32)(*((buf) + 3)))) - - /* From libpng-1.4.0 until 1.4.4, the png_get_uint_16 macro (but not the - * function) incorrectly returned a value of type png_uint_32. - */ -# define png_get_uint_16(buf) \ - ((png_uint_16) \ - (((unsigned int)(*(buf)) << 8) + \ - ((unsigned int)(*((buf) + 1))))) - -# define png_get_int_32(buf) \ - ((png_int_32)((*(buf) & 0x80) \ - ? -((png_int_32)((png_get_uint_32(buf) ^ 0xffffffffL) + 1)) \ - : (png_int_32)png_get_uint_32(buf))) -#endif - #ifdef PNG_READ_INT_FUNCTIONS_SUPPORTED PNG_EXPORT(201, png_uint_32, png_get_uint_32, (png_const_bytep buf)); PNG_EXPORT(202, png_uint_16, png_get_uint_16, (png_const_bytep buf)); @@ -2255,6 +2232,31 @@ PNG_EXPORT(207, void, png_save_uint_16, (png_bytep buf, unsigned int i)); /* No png_save_int_16 -- may be added if there's a real need for it. */ #endif +#ifdef PNG_USE_READ_MACROS +/* Inline macros to do direct reads of bytes from the input buffer. + * The png_get_int_32() routine assumes we are using two's complement + * format for negative values, which is almost certainly true. + */ +# define png_get_uint_32(buf) \ + (((png_uint_32)(*(buf)) << 24) + \ + ((png_uint_32)(*((buf) + 1)) << 16) + \ + ((png_uint_32)(*((buf) + 2)) << 8) + \ + ((png_uint_32)(*((buf) + 3)))) + + /* From libpng-1.4.0 until 1.4.4, the png_get_uint_16 macro (but not the + * function) incorrectly returned a value of type png_uint_32. + */ +# define png_get_uint_16(buf) \ + ((png_uint_16) \ + (((unsigned int)(*(buf)) << 8) + \ + ((unsigned int)(*((buf) + 1))))) + +# define png_get_int_32(buf) \ + ((png_int_32)((*(buf) & 0x80) \ + ? -((png_int_32)((png_get_uint_32(buf) ^ 0xffffffffL) + 1)) \ + : (png_int_32)png_get_uint_32(buf))) +#endif + /* Maintainer: Put new public prototypes here ^, in libpng.3, and project * defs */ diff --git a/pngrtran.c b/pngrtran.c index 41c3bc0fb..602affb54 100644 --- a/pngrtran.c +++ b/pngrtran.c @@ -692,8 +692,13 @@ png_set_gray_to_rgb(png_structp png_ptr) { png_debug(1, "in png_set_gray_to_rgb"); - png_ptr->transformations |= PNG_GRAY_TO_RGB; - png_ptr->flags &= ~PNG_FLAG_ROW_INIT; + if (png_ptr != NULL) + { + /* Because rgb must be 8 bits or more: */ + png_set_expand_gray_1_2_4_to_8(png_ptr); + png_ptr->transformations |= PNG_GRAY_TO_RGB; + png_ptr->flags &= ~PNG_FLAG_ROW_INIT; + } } #endif diff --git a/pngvalid.c b/pngvalid.c index 3c7a6a5c1..53e3d48f9 100644 --- a/pngvalid.c +++ b/pngvalid.c @@ -20,8 +20,40 @@ */ #include "png.h" +#if PNG_LIBPNG_VER < 10500 +/* This delibarately lacks the PNG_CONST. */ +typedef png_byte *png_const_bytep; + +/* This is copied from 1.5.1 png.h: */ +#define PNG_INTERLACE_ADAM7_PASSES 7 +#define PNG_PASS_START_ROW(pass) (((1U&~(pass))<<(3-((pass)>>1)))&7) +#define PNG_PASS_START_COL(pass) (((1U& (pass))<<(3-(((pass)+1)>>1)))&7) +#define PNG_PASS_ROW_SHIFT(pass) ((pass)>2?(8-(pass))>>1:3) +#define PNG_PASS_COL_SHIFT(pass) ((pass)>1?(7-(pass))>>1:3) +#define PNG_PASS_ROWS(height, pass) (((height)+(((1<>PNG_PASS_ROW_SHIFT(pass)) +#define PNG_PASS_COLS(width, pass) (((width)+(((1<>PNG_PASS_COL_SHIFT(pass)) +#define PNG_ROW_FROM_PASS_ROW(yIn, pass) \ + (((yIn)<>(((7-(off))-(pass))<<2)) & 0xFU) | \ + ((0x01145AF0U>>(((7-(off))-(pass))<<2)) & 0xF0U)) +#define PNG_ROW_IN_INTERLACE_PASS(y, pass) \ + ((PNG_PASS_MASK(pass,0) >> ((y)&7)) & 1) +#define PNG_COL_IN_INTERLACE_PASS(x, pass) \ + ((PNG_PASS_MASK(pass,1) >> ((x)&7)) & 1) + +/* These are needed too for the defualt build: */ +#define PNG_WRITE_16BIT_SUPPORTED +#define PNG_READ_16BIT_SUPPORTED +#endif + #include "zlib.h" /* For crc32 */ +#include /* For floating point constants */ #include /* For malloc */ #include /* For memcpy, memset */ #include /* For floor */ @@ -1277,16 +1309,19 @@ typedef struct png_modifier /* Run the odd-sized image and interlace read/write tests? */ unsigned int test_size :1; + /* Run tests on reading with a combiniation of transforms, */ + unsigned int test_transform :1; + /* When to use the use_input_precision option: */ unsigned int use_input_precision :1; unsigned int use_input_precision_sbit :1; unsigned int use_input_precision_16to8 :1; /* Which gamma tests to run: */ - unsigned int test_threshold :1; - unsigned int test_transform :1; /* main tests */ - unsigned int test_sbit :1; - unsigned int test_strip16 :1; + unsigned int test_gamma_threshold :1; + unsigned int test_gamma_transform :1; /* main tests */ + unsigned int test_gamma_sbit :1; + unsigned int test_gamma_strip16 :1; unsigned int log :1; /* Log max error */ @@ -1352,13 +1387,14 @@ modifier_init(png_modifier *pm) pm->interlace_type = PNG_INTERLACE_NONE; pm->test_standard = 1; pm->test_size = 0; + pm->test_transform = 1; pm->use_input_precision = 0; pm->use_input_precision_sbit = 0; pm->use_input_precision_16to8 = 0; - pm->test_threshold = 1; - pm->test_transform = 1; - pm->test_sbit = 1; - pm->test_strip16 = 1; + pm->test_gamma_threshold = 1; + pm->test_gamma_transform = 1; + pm->test_gamma_sbit = 1; + pm->test_gamma_strip16 = 1; pm->log = 0; /* Rely on the memset for all the other fields - there are no pointers */ @@ -2649,6 +2685,10 @@ typedef struct standard_display png_store* ps; /* Test parameters (passed to the function) */ png_byte colour_type; png_byte bit_depth; + png_byte red_sBIT; /* Input data sBIT values. */ + png_byte green_sBIT; + png_byte blue_sBIT; + png_byte alpha_sBIT; int interlace_type; png_uint_32 id; /* Calculated file ID */ png_uint_32 w; /* Width of image */ @@ -2658,6 +2698,20 @@ typedef struct standard_display png_uint_32 bit_width; /* Width of output row in bits */ size_t cbRow; /* Bytes in a row of the output image */ int do_interlace; /* Do interlacing internally */ + int is_transparent; /* Transparecy information was present. */ + struct + { + png_uint_16 red; + png_uint_16 green; + png_uint_16 blue; + } transparent; /* The transparent color, if set. */ + struct /* pngvalid format palette! */ + { + png_byte red; + png_byte green; + png_byte blue; + png_byte alpha; + } palette[256]; /* Always has 256 entries. */ } standard_display; static void @@ -2667,6 +2721,8 @@ standard_display_init(standard_display *dp, png_store* ps, png_uint_32 id, dp->ps = ps; dp->colour_type = COL_FROM_ID(id); dp->bit_depth = DEPTH_FROM_ID(id); + dp->alpha_sBIT = dp->blue_sBIT = dp->green_sBIT = dp->alpha_sBIT = + dp->bit_depth; dp->interlace_type = INTERLACE_FROM_ID(id); dp->id = id; /* All the rest are filled in after the read_info: */ @@ -2677,6 +2733,12 @@ standard_display_init(standard_display *dp, png_store* ps, png_uint_32 id, dp->bit_width = 0; dp->cbRow = 0; dp->do_interlace = do_interlace; + dp->is_transparent = 0; + /* Preset the transparent color to black: */ + memset(&dp->transparent, 0, sizeof dp->transparent); + /* Preset the palette to full intensity/opaque througout: */ + memset(dp->palette, 0xff, sizeof dp->palette); + } /* By passing a 'standard_display' the progressive callbacks can be used @@ -2715,6 +2777,61 @@ standard_info_part1(standard_display *dp, png_structp pp, png_infop pi) if (dp->h != standard_height(pp, dp->id)) png_error(pp, "validate: image height changed"); + /* Record (but don't check at present) the input sBIT according to the colour + * type information. + */ + { + png_color_8p sBIT = 0; + + if (png_get_sBIT(pp, pi, &sBIT) & PNG_INFO_sBIT) + { + int sBIT_invalid = 0; + + if (sBIT == 0) + png_error(pp, "validate: unexpected png_get_sBIT result"); + + if (dp->colour_type & PNG_COLOR_MASK_COLOR) + { + if (sBIT->red == 0 || sBIT->red > dp->bit_depth) + sBIT_invalid = 1; + else + dp->red_sBIT = sBIT->red; + + if (sBIT->green == 0 || sBIT->green > dp->bit_depth) + sBIT_invalid = 1; + else + dp->green_sBIT = sBIT->green; + + if (sBIT->blue == 0 || sBIT->blue > dp->bit_depth) + sBIT_invalid = 1; + else + dp->blue_sBIT = sBIT->blue; + } + + else /* !COLOR */ + { + if (sBIT->gray == 0 || sBIT->gray > dp->bit_depth) + sBIT_invalid = 1; + else + dp->blue_sBIT = dp->green_sBIT = dp->red_sBIT = sBIT->gray; + } + + /* All 8 bits in tRNS for a palette image are significant - see the + * spec. + */ + if (dp->colour_type & PNG_COLOR_MASK_ALPHA) + { + if (sBIT->alpha == 0 || sBIT->alpha > dp->bit_depth) + sBIT_invalid = 1; + else + dp->alpha_sBIT = sBIT->alpha; + } + + if (sBIT_invalid) + png_error(pp, "validate: sBIT value out of range"); + } + } + /* Important: this is validating the value *before* any transforms have been * put in place. It doesn't matter for the standard tests, where there are * no transforms, but it does for other tests where rowbytes may change after @@ -2723,14 +2840,20 @@ standard_info_part1(standard_display *dp, png_structp pp, png_infop pi) if (png_get_rowbytes(pp, pi) != standard_rowsize(pp, dp->id)) png_error(pp, "validate: row size changed"); + /* The palette is never read for non-palette images, even though it is valid + * - this could be changed. + */ if (dp->colour_type == 3) /* palette */ { png_colorp pal; + png_bytep trans_alpha; int num; /* This could be passed in but isn't - the values set above when the * standard images were made are just repeated here. */ + pal = 0; + num = -1; if (png_get_PLTE(pp, pi, &pal, &num) & PNG_INFO_PLTE) { int i; @@ -2739,12 +2862,77 @@ standard_info_part1(standard_display *dp, png_structp pp, png_infop pi) png_error(pp, "validate: color type 3 PLTE chunk size changed"); for (i=0; ipalette[i].red = pal[i].red; + dp->palette[i].green = pal[i].green; + dp->palette[i].blue = pal[i].blue; + /* alpha is left as opaque */ + } } - else + else /* !png_get_PLTE */ png_error(pp, "validate: missing PLTE with color type 3"); + + trans_alpha = 0; + num = -1; + if (png_get_tRNS(pp, pi, &trans_alpha, &num, 0) & PNG_INFO_tRNS) + { + int i; + + /* Any of these are crash-worthy - given the implementation of + * png_get_tRNS up to 1.5 an app won't crash if it just checks the + * result above and fails to check that the variables it passed have + * actually been filled in! Note that if the app were to pass the + * last, png_color_16p, variable too it couldn't rely on this. + */ + if (trans_alpha == 0 || num <= 0 || num > 256) + png_error(pp, "validate: unexpected png_get_tRNS (palette) result"); + + for (i=0; ipalette[i].alpha = trans_alpha[i]; + /* The remainder has already been set to opaque. */ + + dp->is_transparent = 1; + } + } + + /* In any case always check for a tranparent color: */ + { + png_color_16p trans_color = 0; + + if (png_get_tRNS(pp, pi, 0, 0, &trans_color) & PNG_INFO_tRNS) + { + if (trans_color == 0) + png_error(pp, "validate: unexpected png_get_tRNS (color) result"); + + switch (dp->colour_type) + { + case 0: + dp->transparent.red = dp->transparent.green = dp->transparent.blue = + trans_color->gray; + dp->is_transparent = 1; + break; + + case 2: + dp->transparent.red = trans_color->red; + dp->transparent.green = trans_color->green; + dp->transparent.blue = trans_color->blue; + dp->is_transparent = 1; + break; + + case 3: + /* Not expected because it should result in the array case + * above. + */ + png_error(pp, "validate: unexpected png_get_tRNS result"); + break; + + default: + png_error(pp, "validate: invalid tRNS chunk with alpha image"); + } + } } /* Read the number of passes - expected to match the value used when @@ -3220,6 +3408,1417 @@ perform_size_test(png_modifier *pm) } +/******************************* TRANSFORM TESTS ******************************/ +/* A set of tests to validate libpng image transforms. The possibilities here + * are legion because the transforms can be combined in a combinatorial + * fashion. To deal with this some measure of restraint is required, otherwise + * the tests would take forever. + */ +typedef struct image_pixel +{ + /* A local (pngvalid) representation of a PNG pixel, in all its + * various forms. + */ + unsigned int red, green, blue, alpha; /* For non-palette images. */ + unsigned int palette_index; /* For a palette image. */ + png_byte colour_type; /* As in the spec. */ + png_byte bit_depth; /* Defines max value */ + int have_tRNS; /* tRNS chunk may need processing */ + + /* For checking the code calculates double precision floating point values + * along with an error value, accumulated from the transforms. Because an + * sBIT setting allows larger error bounds (indeed, by the spec, apparently + * up to just less than +/-1 in the scaled value) the *lowest* sBIT for each + * channel is stored. This sBIT value is folded in to the stored error value + * at the end of the application of the transforms to the pixel. + */ + double redf, greenf, bluef, alphaf; + double rede, greene, bluee, alphae; + png_byte red_sBIT, green_sBIT, blue_sBIT, alpha_sBIT; +} image_pixel; + +/* Shared utility function, see below. */ +static void +image_pixel_setf(image_pixel *this, unsigned int max) +{ + this->redf = this->red / (double)max; + this->greenf = this->green / (double)max; + this->bluef = this->blue / (double)max; + this->alphaf = this->alpha / (double)max; + + if (this->red < max) + this->rede = this->redf * DBL_EPSILON; + else + this->rede = 0; + if (this->green < max) + this->greene = this->greenf * DBL_EPSILON; + else + this->greene = 0; + if (this->blue < max) + this->bluee = this->bluef * DBL_EPSILON; + else + this->bluee = 0; + if (this->alpha < max) + this->alphae = this->alphaf * DBL_EPSILON; + else + this->alphae = 0; +} + +/* Initialize the structure for the next pixel - call this before doing any + * transforms and call it for each pixel since all the fields may need to be + * reset. + */ +static void +image_pixel_init(image_pixel *this, png_const_bytep row, png_byte colour_type, + png_byte bit_depth, png_uint_32 x) +{ + unsigned int i = 0; + unsigned int max = (1U<palette_index = this->red = this->green = this->blue = + sample(row, colour_type, bit_depth, x, 0); + this->alpha = max; + this->red_sBIT = this->green_sBIT = this->blue_sBIT = this->alpha_sBIT = + bit_depth; + + /* Then override blue, green, alpha as appropriate: */ + if (colour_type & 2) + { + this->blue = sample(row, colour_type, bit_depth, x, 1); + this->green = sample(row, colour_type, bit_depth, x, 2); + i = 2; + } + if (colour_type & 4) + this->alpha = sample(row, colour_type, bit_depth, x, ++i); + + /* Calculate the scaled values, these are simply the values divided by + * 'max' and the error is initialized to the double precision epsilon value + * from the header file. + */ + image_pixel_setf(this, max); + + /* Store the input information for use in the transforms - these will + * modify the information. + */ + this->colour_type = colour_type; + this->bit_depth = bit_depth; + this->have_tRNS = 0; +} + +/* Convert a palette image to an rgb image. This necessarily converts the tRNS + * chunk at the same time, because the tRNS will be in palette form. + */ +static void +image_pixel_convert_PLTE(image_pixel *this, const standard_display *display) +{ + if (this->colour_type == PNG_COLOR_MASK_PALETTE) + { + unsigned int i = this->palette_index; + + this->bit_depth = 8; + this->red = display->palette[i].red; + this->green = display->palette[i].green; + this->blue = display->palette[i].blue; + this->red_sBIT = display->red_sBIT; + this->green_sBIT = display->green_sBIT; + this->blue_sBIT = display->blue_sBIT; + + if (this->have_tRNS) + { + this->alpha = display->palette[i].alpha; + this->colour_type = PNG_COLOR_TYPE_RGB_ALPHA; + this->have_tRNS = 0; + } + else + { + this->alpha = 255; + this->colour_type = PNG_COLOR_TYPE_RGB; + } + this->alpha_sBIT = 8; + + /* And regenerate the scaled values and all the errors, which are now set + * back to the initial values. + */ + image_pixel_setf(this, 255); + } +} + +/* Add an alpha channel, this will glom in the tRNS information because tRNS is + * not valid in an alpha image. The bit depth will invariably be set to at + * least 8. Palette images will be converted to alpha (using the above API). + */ +static void +image_pixel_add_alpha(image_pixel *this, const standard_display *display) +{ + if (this->colour_type == PNG_COLOR_TYPE_PALETTE) + image_pixel_convert_PLTE(this, display); + + if ((this->colour_type & PNG_COLOR_MASK_ALPHA) == 0) + { + if (this->colour_type == PNG_COLOR_TYPE_GRAY) + { + if (this->bit_depth < 8) + this->bit_depth = 8; + + if (this->have_tRNS) + { + this->have_tRNS = 0; + + /* Check the input, original, channel value here against the + * original tRNS gray chunk valie. + */ + if (this->red == display->transparent.red) + this->alphaf = 0; + else + this->alphaf = 1; + } + else + this->alphaf = 1; + + this->colour_type = PNG_COLOR_TYPE_GRAY_ALPHA; + } + + else if (this->colour_type == PNG_COLOR_TYPE_RGB) + { + if (this->have_tRNS) + { + this->have_tRNS = 0; + + /* Again, check the exact input values, not the current transformed + * value! + */ + if (this->red == display->transparent.red && + this->green == display->transparent.green && + this->blue == display->transparent.blue) + this->alphaf = 0; + else + this->alphaf = 1; + + this->colour_type = PNG_COLOR_TYPE_RGB_ALPHA; + } + } + + /* The error in the alpha is zero and the sBIT value comes from the + * original sBIT data (actually it will always be the original bit depth). + */ + this->alphae = 0; + this->alpha_sBIT = display->alpha_sBIT; + } +} + +struct transform_display; +typedef struct image_transform +{ + /* The global list of transforms; read only. */ + struct image_transform *PNG_CONST list; + + /* The global count of the number of times this transform has been set on an + * image. + */ + unsigned int global_use; + + /* The local count of the number of times this transform has been set. */ + unsigned int local_use; + + /* The next transform in the list, each transform must call its own next + * transform after it has processed the pixel successfully. + */ + PNG_CONST struct image_transform *next; + + /* A single transform for the image, expressed as a series of function + * callbacks and some space for values. + * + * First a callback to set the transform on the current png_read_struct: + */ + void (*set)(PNG_CONST struct image_transform *this, + struct transform_display *that, png_structp pp, png_infop pi); + + /* Then a transform that takes an input pixel in one PNG format or another + * and modifies it by a pngvalid implementation of the transform (thus + * duplicating the libpng intent without, we hope, duplicating the bugs + * in the libpng implementation!) The png_structp is solely to allow error + * reporting via png_error and png_warning. + */ + void (*mod)(PNG_CONST struct image_transform *this, image_pixel *that, + png_structp pp, PNG_CONST struct transform_display *display); + + /* Add this transform to the list and return true if the transform is + * meaningful for this colour type and bit depth - if false then the + * transform should have no effect on the image so there's not a lot of + * point running it. + */ + int (*add)(struct image_transform *this, + PNG_CONST struct image_transform **that, + char *name, size_t sizeof_name, size_t *pos, png_byte colour_type, + png_byte bit_depth); +} image_transform; + +typedef struct transform_display +{ + standard_display this; + + /* Parameters */ + png_modifier* pm; + PNG_CONST image_transform* transform_list; + + /* Local variables */ + png_byte output_colour_type; + png_byte output_bit_depth; +} transform_display; + +/* Two functions to end the list: */ +static void +image_transform_set_end(PNG_CONST image_transform *this, + transform_display *that, png_structp pp, png_infop pi) +{ + UNUSED(this); + UNUSED(that); + UNUSED(pp); + UNUSED(pi); +} + +/* At the end of the list recalculate the output image pixel value from the + * double precision values set up by the preceding 'mod' calls: + */ +static unsigned int +sample_scale(double sample_value, unsigned int scale) +{ + sample_value = floor(sample_value * scale + .5); + + /* Return NaN as 0: */ + if (!(sample_value > 0)) + sample_value = 0; + else if (sample_value > scale) + sample_value = scale; + + return (unsigned int)sample_value; +} + +static void +image_transform_mod_end(PNG_CONST image_transform *this, image_pixel *that, + png_structp pp, PNG_CONST transform_display *display) +{ + PNG_CONST unsigned int scale = (1U<bit_depth)-1; + + UNUSED(this); + UNUSED(pp); + UNUSED(display); + + /* For the moment expect a palette output image to be untransformed, + * so 'palette_index' (and 'red') contains the correct value. + */ + if (that->colour_type & PNG_COLOR_MASK_PALETTE) + return; + + /* The sample value is simply scaled to the maximum, checking for over + * and underflow (which can both happen for some image transforms, + * including simple size scaling, though libpng doesn't do that at present. + */ + that->red = sample_scale(that->redf, scale); + + /* The error value is increased, at the end, according to the lowest sBIT + * value seen. Common sense tells us that the intermediate integer + * representations are no more accurate than +/- 0.5 in the integral values, + * the sBIT allows the implementation to be worse than this. In addition the + * PNG specification actually permits any error within the range (-1..+1), + * but that is ignored at present. + */ + that->rede += 1./(2*((1U<red_sBIT)-1)); + + if (that->colour_type & PNG_COLOR_MASK_COLOR) + { + that->green = sample_scale(that->greenf, scale); + that->blue = sample_scale(that->bluef, scale); + that->greene += 1./(2*((1U<green_sBIT)-1)); + that->bluee += 1./(2*((1U<blue_sBIT)-1)); + } + else + { + that->blue = that->green = that->red; + that->bluef = that->greenf = that->redf; + that->bluee = that->greene = that->rede; + } + + if (that->colour_type & PNG_COLOR_MASK_ALPHA) + { + that->alpha = sample_scale(that->alphaf, scale); + that->alphae += 1./(2*((1U<alpha_sBIT)-1)); + } + else + { + that->alpha = scale; /* opaque */ + that->alpha = 1; /* Override this. */ + that->alphae = 0; /* It's exact ;-) */ + } +} + +/* Static 'end' structure: */ +static image_transform image_transform_end = +{ + 0, /* list */ + 0, /* global_use */ + 0, /* local_use */ + 0, /* next */ + image_transform_set_end, + image_transform_mod_end, + 0 /* never called, I want it to crash if it is! */ +}; + +/* Reader callbacks and implementations, where they differ from the standard + * ones. + */ +static void +transform_display_init(transform_display *dp, png_modifier *pm, png_uint_32 id, + PNG_CONST image_transform *transform_list) +{ + /* Standard fields */ + standard_display_init(&dp->this, &pm->this, id, 0/*do_interlace*/); + + /* Parameter fields */ + dp->pm = pm; + dp->transform_list = transform_list; + + /* Local variable fields */ + dp->output_colour_type = 255; /* invalid */ + dp->output_bit_depth = 255; /* invalid */ +} + +static void +transform_info_imp(transform_display *dp, png_structp pp, png_infop pi) +{ + /* Reuse the standard stuff as appropriate. */ + standard_info_part1(&dp->this, pp, pi); + + /* Now set the list of transforms. */ + dp->transform_list->set(dp->transform_list, dp, pp, pi); + + /* Update the info structure for these transforms: */ + png_read_update_info(pp, pi); + + /* And get the output information into the standard_display */ + standard_info_part2(&dp->this, pp, pi, 1/*images*/); + + /* Plus the extra stuff we need for the transform tests: */ + dp->output_colour_type = png_get_color_type(pp, pi); + dp->output_bit_depth = png_get_bit_depth(pp, pi); + + /* Validate the combination of colour type and bit depth that we are getting + * out of libpng; the semantics of something not in the PNG spec are, at + * best, unclear. + */ + switch (dp->output_colour_type) + { + case PNG_COLOR_TYPE_PALETTE: + if (dp->output_bit_depth > 8) goto error; + /*FALL THROUGH*/ + case PNG_COLOR_TYPE_GRAY: + if (dp->output_bit_depth == 1 || dp->output_bit_depth == 2 || + dp->output_bit_depth == 4) + break; + /*FALL THROUGH*/ + default: + if (dp->output_bit_depth == 8 || dp->output_bit_depth == 16) + break; + /*FALL THROUGH*/ + error: + { + char message[128]; + size_t pos; + + pos = safecat(message, sizeof message, 0, + "invalid final bit depth: colour type("); + pos = safecatn(message, sizeof message, pos, dp->output_colour_type); + pos = safecat(message, sizeof message, pos, ") with bit depth: "); + pos = safecatn(message, sizeof message, pos, dp->output_bit_depth); + + png_error(pp, message); + } + } + + /* Use a test pixel to check that the output agrees with what we expect - + * this avoids running the whole test if the output is unexpected. + */ + { + image_pixel test_pixel; + + memset(&test_pixel, 0, sizeof test_pixel); + test_pixel.colour_type = dp->this.colour_type; /* input */ + test_pixel.bit_depth = dp->this.bit_depth; + /* Don't need sBIT here */ + test_pixel.have_tRNS = dp->this.is_transparent; + + dp->transform_list->mod(dp->transform_list, &test_pixel, pp, dp); + + if (test_pixel.colour_type != dp->output_colour_type) + { + char message[128]; + size_t pos = safecat(message, sizeof message, 0, "colour type "); + + pos = safecatn(message, sizeof message, pos, dp->output_colour_type); + pos = safecat(message, sizeof message, pos, " expected "); + pos = safecatn(message, sizeof message, pos, test_pixel.colour_type); + + png_error(pp, message); + png_error(pp, "unexpected output colour type"); + } + + if (test_pixel.bit_depth != dp->output_bit_depth) + { + char message[128]; + size_t pos = safecat(message, sizeof message, 0, "bit depth "); + + pos = safecatn(message, sizeof message, pos, dp->output_bit_depth); + pos = safecat(message, sizeof message, pos, " expected "); + pos = safecatn(message, sizeof message, pos, test_pixel.bit_depth); + + png_error(pp, message); + } + } +} + +static void +transform_info(png_structp pp, png_infop pi) +{ + transform_info_imp(png_get_progressive_ptr(pp), pp, pi); +} + +static void +transform_range_check(png_structp pp, double in, double out, double err, + PNG_CONST char *name) +{ + if (fabs(out - in) > err) + { + char message[128]; + size_t pos; + + pos = safecat(message, sizeof message, 0, name); + pos = safecat(message, sizeof message, pos, " output value error: "); + pos = safecatd(message, sizeof message, pos, out, 6); + pos = safecat(message, sizeof message, pos, " expected: "); + pos = safecatd(message, sizeof message, pos, in-err, 6); + pos = safecat(message, sizeof message, pos, ".."); + pos = safecatd(message, sizeof message, pos, in+err, 6); + + png_error(pp, message); + } +} + +static void +transform_image_validate(transform_display *dp, png_structp pp, png_infop pi, + png_const_bytep pRow) +{ + UNUSED(pi); + + /* Constants for the loop below: */ + PNG_CONST png_byte in_ct = dp->this.colour_type; + PNG_CONST png_byte in_bd = dp->this.bit_depth; + PNG_CONST png_uint_32 w = dp->this.w; + PNG_CONST png_uint_32 h = dp->this.h; + PNG_CONST size_t cbRow = dp->this.cbRow; + PNG_CONST png_byte out_ct = dp->output_colour_type; + PNG_CONST png_byte out_bd = dp->output_bit_depth; + PNG_CONST png_byte red_sBIT = dp->this.red_sBIT; + PNG_CONST png_byte green_sBIT = dp->this.green_sBIT; + PNG_CONST png_byte blue_sBIT = dp->this.blue_sBIT; + PNG_CONST png_byte alpha_sBIT = dp->this.alpha_sBIT; + PNG_CONST int have_tRNS = dp->this.is_transparent; + + png_uint_32 y; + + for (y=0; ytransform_list->mod(dp->transform_list, &in_pixel, pp, dp); + + /* Read the output pixel and compare it to what we got, we don't + * use the error field here, so just pass NULL of the sBIT info. + */ + image_pixel_init(&out_pixel, pRow, out_ct, out_bd, x); + + if (out_ct == PNG_COLOR_TYPE_PALETTE) + { + if (in_pixel.palette_index != out_pixel.palette_index) + png_error(pp, "unexpected transformed palette index"); + } + + else /* not a palette image */ + { + if (in_pixel.red != out_pixel.red) + transform_range_check(pp, in_pixel.redf, out_pixel.redf, + in_pixel.rede, "red/gray"); + + if ((out_ct & PNG_COLOR_MASK_COLOR) != 0 && + in_pixel.green != out_pixel.green) + transform_range_check(pp, in_pixel.greenf, out_pixel.greenf, + in_pixel.greene, "green"); + + if ((out_ct & PNG_COLOR_MASK_COLOR) != 0 && + in_pixel.blue != out_pixel.blue) + transform_range_check(pp, in_pixel.bluef, out_pixel.bluef, + in_pixel.bluee, "blue"); + + if ((out_ct & PNG_COLOR_MASK_ALPHA) != 0 && + in_pixel.alpha != out_pixel.alpha) + transform_range_check(pp, in_pixel.alphaf, out_pixel.alphaf, + in_pixel.alphae, "alpha"); + } + } /* pixel (x) loop */ + } /* row (y) loop */ + + /* Record that something was actually checked to avoid a false positive. */ + dp->this.ps->validated = 1; +} + +static void +transform_end(png_structp pp, png_infop pi) +{ + transform_display *dp = png_get_progressive_ptr(pp); + + transform_image_validate(dp, pp, pi, dp->this.ps->image); +} + +/* A single test run. */ +static void +transform_test(png_modifier *pmIn, PNG_CONST png_uint_32 idIn, + PNG_CONST image_transform* transform_listIn, PNG_CONST char *name) +{ + transform_display d; + context(&pmIn->this, fault); + + transform_display_init(&d, pmIn, idIn, transform_listIn); + + Try + { + png_structp pp; + png_infop pi; + + /* Get a png_struct for writing the image. */ + pp = set_modifier_for_read(d.pm, &pi, d.this.id, name); + + /* Introduce the correct read function. */ + if (d.pm->this.progressive) + { + /* Share the row function with the standard implementation. */ + png_set_progressive_read_fn(pp, &d, transform_info, progressive_row, + transform_end); + + /* Now feed data into the reader until we reach the end: */ + modifier_progressive_read(d.pm, pp, pi); + } + else + { + /* modifier_read expects a png_modifier* */ + png_set_read_fn(pp, d.pm, modifier_read); + + /* Check the header values: */ + png_read_info(pp, pi); + + /* Process the 'info' requirements. Only one image is generated */ + transform_info_imp(&d, pp, pi); + + sequential_row(&d.this, pp, pi, NULL, d.this.ps->image); + + transform_image_validate(&d, pp, pi, d.this.ps->image); + } + + modifier_reset(d.pm); + } + + Catch(fault) + modifier_reset((png_modifier*)fault); +} + +/* The transforms: */ +#define ITSTRUCT(name) image_transform_##name +#define IT(name,prev)\ +static image_transform ITSTRUCT(name) =\ +{\ + &ITSTRUCT(prev), /*list*/\ + 0, /*global_use*/\ + 0, /*local_use*/\ + 0, /*next*/\ + image_transform_png_set_##name##_set,\ + image_transform_png_set_##name##_mod,\ + image_transform_png_set_##name##_add\ +} + +/* png_set_expand_gray_1_2_4_to_8 */ +static void +image_transform_png_set_expand_gray_1_2_4_to_8_set( + PNG_CONST image_transform *this, transform_display *that, png_structp pp, + png_infop pi) +{ + png_set_expand_gray_1_2_4_to_8(pp); + this->next->set(this->next, that, pp, pi); +} + +static void +image_transform_png_set_expand_gray_1_2_4_to_8_mod( + PNG_CONST image_transform *this, image_pixel *that, png_structp pp, + PNG_CONST transform_display *display) +{ + /* This only does anything if the data is grayscale and less than 8 bits in + * depth. + */ + if ((that->colour_type & ~PNG_COLOR_MASK_ALPHA) == PNG_COLOR_TYPE_GRAY && + that->bit_depth < 8) + that->bit_depth = 8; + + this->next->mod(this->next, that, pp, display); +} + +static int +image_transform_png_set_expand_gray_1_2_4_to_8_add(image_transform *this, + PNG_CONST image_transform **that, char *name, size_t sizeof_name, + size_t *pos, png_byte colour_type, png_byte bit_depth) +{ + this->next = *that; + *that = this; + + *pos = safecat(name, sizeof_name, *pos, " +expand_gray_1_2_4_to_8"); + + return (colour_type & ~PNG_COLOR_MASK_ALPHA) == PNG_COLOR_TYPE_GRAY && + bit_depth < 8; +} + +IT(expand_gray_1_2_4_to_8,end); + +/* png_set_palette_to_rgb */ +static void +image_transform_png_set_palette_to_rgb_set(PNG_CONST image_transform *this, + transform_display *that, png_structp pp, png_infop pi) +{ + png_set_palette_to_rgb(pp); + this->next->set(this->next, that, pp, pi); +} + +static void +image_transform_png_set_palette_to_rgb_mod(PNG_CONST image_transform *this, + image_pixel *that, png_structp pp, PNG_CONST transform_display *display) +{ + if (that->colour_type == PNG_COLOR_TYPE_PALETTE) + image_pixel_convert_PLTE(that, &display->this); + + this->next->mod(this->next, that, pp, display); +} + +static int +image_transform_png_set_palette_to_rgb_add(image_transform *this, + PNG_CONST image_transform **that, char *name, size_t sizeof_name, + size_t *pos, png_byte colour_type, png_byte bit_depth) +{ + UNUSED(bit_depth); + + this->next = *that; + *that = this; + + *pos = safecat(name, sizeof_name, *pos, " +palette_to_rgb"); + + return colour_type == PNG_COLOR_TYPE_PALETTE; +} + +IT(palette_to_rgb,expand_gray_1_2_4_to_8); + + +/* png_set_tRNS_to_alpha */ +static void +image_transform_png_set_tRNS_to_alpha_set(PNG_CONST image_transform *this, + transform_display *that, png_structp pp, png_infop pi) +{ + png_set_tRNS_to_alpha(pp); + this->next->set(this->next, that, pp, pi); +} + +static void +image_transform_png_set_tRNS_to_alpha_mod(PNG_CONST image_transform *this, + image_pixel *that, png_structp pp, PNG_CONST transform_display *display) +{ + /* This effectively does an 'expand' only if there is some transparency to + * covert to an alpha channel. + */ + if (that->have_tRNS) + image_pixel_add_alpha(that, &display->this); + + /* LIBPNG BUG: otherwise libpng still expands to 8 bits! */ + else if (that->bit_depth < 8) + that->bit_depth =8; + + this->next->mod(this->next, that, pp, display); +} + +static int +image_transform_png_set_tRNS_to_alpha_add(image_transform *this, + PNG_CONST image_transform **that, char *name, size_t sizeof_name, + size_t *pos, png_byte colour_type, png_byte bit_depth) +{ + UNUSED(bit_depth); + + this->next = *that; + *that = this; + + *pos = safecat(name, sizeof_name, *pos, " +tRNS_to_alpha"); + + /* We don't know yet whether there will be a tRNS chunk, but we know that + * this transformation should do nothing if there already is an alpha + * channel. + */ + return (colour_type & PNG_COLOR_MASK_ALPHA) == 0; +} + +IT(tRNS_to_alpha,palette_to_rgb); + +/* png_set_gray_to_rgb */ +static void +image_transform_png_set_gray_to_rgb_set(PNG_CONST image_transform *this, + transform_display *that, png_structp pp, png_infop pi) +{ + png_set_gray_to_rgb(pp); + this->next->set(this->next, that, pp, pi); +} + +static void +image_transform_png_set_gray_to_rgb_mod(PNG_CONST image_transform *this, + image_pixel *that, png_structp pp, PNG_CONST transform_display *display) +{ + /* NOTE: we can actually pend the tRNS processing at this point because we + * can correctly recognize the original pixel value even though we have + * mapped the one gray channel to the three RGB ones, but in fact libpng + * doesn't do this, so we don't either. + */ + if ((that->colour_type & PNG_COLOR_MASK_COLOR) == 0 && that->have_tRNS) + image_pixel_add_alpha(that, &display->this); + + /* Simply expand the bit depth and alter the colour type as required. */ + if (that->colour_type == PNG_COLOR_TYPE_GRAY) + { + /* RGB images have a bit depth at least equal to '8' */ + if (that->bit_depth < 8) + that->bit_depth = 8; + + /* And just changing the colour type works here because the green and blue + * channels are being maintained in lock-step with the red/gray: + */ + that->colour_type = PNG_COLOR_TYPE_RGB; + } + + else if (that->colour_type == PNG_COLOR_TYPE_GRAY_ALPHA) + that->colour_type = PNG_COLOR_TYPE_RGB_ALPHA; + + this->next->mod(this->next, that, pp, display); +} + +static int +image_transform_png_set_gray_to_rgb_add(image_transform *this, + PNG_CONST image_transform **that, char *name, size_t sizeof_name, + size_t *pos, png_byte colour_type, png_byte bit_depth) +{ + UNUSED(bit_depth); + + this->next = *that; + *that = this; + + *pos = safecat(name, sizeof_name, *pos, " +gray_to_rgb"); + + return (colour_type & PNG_COLOR_MASK_COLOR) == 0; +} + +IT(gray_to_rgb,tRNS_to_alpha); + +/* png_set_expand */ +static void +image_transform_png_set_expand_set(PNG_CONST image_transform *this, + transform_display *that, png_structp pp, png_infop pi) +{ + png_set_expand(pp); + this->next->set(this->next, that, pp, pi); +} + +static void +image_transform_png_set_expand_mod(PNG_CONST image_transform *this, + image_pixel *that, png_structp pp, PNG_CONST transform_display *display) +{ + /* The general expand case depends on what the colour type is: */ + if (that->bit_depth < 8) + that->bit_depth = 8; + + if (that->colour_type == PNG_COLOR_TYPE_PALETTE) + image_pixel_convert_PLTE(that, &display->this); + + if (that->have_tRNS) + image_pixel_add_alpha(that, &display->this); + + this->next->mod(this->next, that, pp, display); +} + +static int +image_transform_png_set_expand_add(image_transform *this, + PNG_CONST image_transform **that, char *name, size_t sizeof_name, + size_t *pos, png_byte colour_type, png_byte bit_depth) +{ + UNUSED(bit_depth); + + this->next = *that; + *that = this; + + *pos = safecat(name, sizeof_name, *pos, " +expand"); + + /* 'expand' should do nothing for RGBA or GA input - no tRNS and the bit + * depth is at least 8 already. + */ + return (colour_type & PNG_COLOR_MASK_ALPHA) == 0; +} + +IT(expand,gray_to_rgb); + +/* png_set_strip_16 */ +static void +image_transform_png_set_strip_16_set(PNG_CONST image_transform *this, + transform_display *that, png_structp pp, png_infop pi) +{ + png_set_strip_16(pp); + this->next->set(this->next, that, pp, pi); +} + +static void +image_transform_png_set_strip_16_mod(PNG_CONST image_transform *this, + image_pixel *that, png_structp pp, PNG_CONST transform_display *display) +{ + if (that->bit_depth == 16) + { + that->bit_depth = 8; + if (that->red_sBIT > 8) that->red_sBIT = 8; + if (that->green_sBIT > 8) that->green_sBIT = 8; + if (that->blue_sBIT > 8) that->blue_sBIT = 8; + if (that->alpha_sBIT > 8) that->alpha_sBIT = 8; + +# ifndef PNG_READ_16_TO_8_ACCURATE_SCALE_SUPPORTED + /* The strip 16 algoirithm drops the low 8 bits rather than calculating + * 1/257, so we need to adjust the permitted errors appropriately: + */ + { + PNG_CONST double d = (255-128.5)/65535; + that->rede += d; + that->greene += d; + that->bluee += d; + that->alphae += d; + } +# endif + } + + this->next->mod(this->next, that, pp, display); +} + +static int +image_transform_png_set_strip_16_add(image_transform *this, + PNG_CONST image_transform **that, char *name, size_t sizeof_name, + size_t *pos, png_byte colour_type, png_byte bit_depth) +{ + UNUSED(colour_type); + + this->next = *that; + *that = this; + + *pos = safecat(name, sizeof_name, *pos, " +strip_16"); + + return bit_depth > 8; +} + +IT(strip_16,expand); + +/* png_set_strip_alpha */ +static void +image_transform_png_set_strip_alpha_set(PNG_CONST image_transform *this, + transform_display *that, png_structp pp, png_infop pi) +{ + png_set_strip_alpha(pp); + this->next->set(this->next, that, pp, pi); +} + +static void +image_transform_png_set_strip_alpha_mod(PNG_CONST image_transform *this, + image_pixel *that, png_structp pp, PNG_CONST transform_display *display) +{ + if (that->colour_type == PNG_COLOR_TYPE_GRAY_ALPHA) + that->colour_type = PNG_COLOR_TYPE_GRAY; + else if (that->colour_type == PNG_COLOR_TYPE_RGB_ALPHA) + that->colour_type = PNG_COLOR_TYPE_RGB; + + this->next->mod(this->next, that, pp, display); +} + +static int +image_transform_png_set_strip_alpha_add(image_transform *this, + PNG_CONST image_transform **that, char *name, size_t sizeof_name, + size_t *pos, png_byte colour_type, png_byte bit_depth) +{ + UNUSED(bit_depth); + + this->next = *that; + *that = this; + + *pos = safecat(name, sizeof_name, *pos, " +strip_alpha"); + + return (colour_type & PNG_COLOR_MASK_ALPHA) != 0; +} + +IT(strip_alpha,strip_16); + +/* png_set_rgb_to_gray(png_structp, int err_action, double red, double green) + * png_set_rgb_to_gray_fixed(png_structp, int err_action, png_fixed_point red, + * png_fixed_point green) + * png_get_rgb_to_gray_status + * + * At present the APIs are simply tested using the 16.16 fixed point conversion + * values known to be used inside libpng: + * + * red: 6968 + * green: 23434 + * blue: 2366 + * + * NOTE: this currently ignores the gamma because no gamma is being set, the + * tests on gamma need to happen in the gamma test set. + */ +static void +image_transform_png_set_rgb_to_gray_set(PNG_CONST image_transform *this, + transform_display *that, png_structp pp, png_infop pi) +{ + PNG_CONST int error_action = 1; /* no error, no defines in png.h */ + +# ifdef PNG_FLOATING_POINT_SUPPORTED + png_set_rgb_to_gray(pp, error_action, -1, -1); +# else + png_set_rgb_to_gray_fixed(pp, error_action, -1, -1); +# endif + + this->next->set(this->next, that, pp, pi); +} + +static void +image_transform_png_set_rgb_to_gray_mod(PNG_CONST image_transform *this, + image_pixel *that, png_structp pp, PNG_CONST transform_display *display) +{ + if ((that->colour_type & PNG_COLOR_MASK_COLOR) != 0) + { + if (that->colour_type == PNG_COLOR_TYPE_PALETTE) + image_pixel_convert_PLTE(that, &display->this); + + /* Image now has RGB channels... */ + that->bluef = that->greenf = that->redf = (that->redf * 6968 + + that->greenf * 23434 + that->bluef * 2366) / 32768; + that->bluee = that->greene = that->rede = (that->rede * 6968 + + that->greene * 23434 + that->bluee * 2366) / 32768 * + (1 + DBL_EPSILON * 6); + + /* The sBIT is the minium of the three colour channel sBITs. */ + if (that->red_sBIT > that->green_sBIT) + that->red_sBIT = that->green_sBIT; + if (that->red_sBIT > that->blue_sBIT) + that->red_sBIT = that->blue_sBIT; + that->blue_sBIT = that->green_sBIT = that->red_sBIT; + + /* And zap the colour bit in the type: */ + if (that->colour_type == PNG_COLOR_TYPE_RGB) + that->colour_type = PNG_COLOR_TYPE_GRAY; + else if (that->colour_type == PNG_COLOR_TYPE_RGB_ALPHA) + that->colour_type = PNG_COLOR_TYPE_GRAY_ALPHA; + } + + this->next->mod(this->next, that, pp, display); +} + +static int +image_transform_png_set_rgb_to_gray_add(image_transform *this, + PNG_CONST image_transform **that, char *name, size_t sizeof_name, + size_t *pos, png_byte colour_type, png_byte bit_depth) +{ + UNUSED(bit_depth); + + this->next = *that; + *that = this; + + *pos = safecat(name, sizeof_name, *pos, " +rgb_to_gray"); + + return (colour_type & PNG_COLOR_MASK_COLOR) != 0; +} + +IT(rgb_to_gray,strip_alpha); + +/* png_set_background(png_structp, png_const_color_16p background_color, + * int background_gamma_code, int need_expand, double background_gamma) + * png_set_background_fixed(png_structp, png_const_color_16p background_color, + * int background_gamma_code, int need_expand, + * png_fixed_point background_gamma) + * + * As with rgb_to_gray this ignores the gamma. +*/ +static void +image_transform_png_set_background_set(PNG_CONST image_transform *this, + transform_display *that, png_structp pp, png_infop pi) +{ + png_color_16 back; + + /* Arbitrary fixed values of 0.5, we set no need for an expand. */ + back.index = 255; /* Should not be used */ + if (that->this.bit_depth > 8) + back.gray = back.blue = back.green = back.red = 127*257; + else + { + /* This code relies on 8 bit minium output below. */ + if (that->this.bit_depth < 8) + png_set_expand_gray_1_2_4_to_8(pp); + + back.gray = back.blue = back.green = back.red = 127; + } + +# ifdef PNG_FLOATING_POINT_SUPPORTED + png_set_background(pp, &back, PNG_BACKGROUND_GAMMA_FILE, 0, 0); +# else + png_set_background_fixed(pp, &back, PNG_BACKGROUND_GAMMA_FILE, 0, 0); +# endif + + this->next->set(this->next, that, pp, pi); +} + +static void +image_transform_png_set_background_mod(PNG_CONST image_transform *this, + image_pixel *that, png_structp pp, PNG_CONST transform_display *display) +{ + /* We expect set_background to convert palette to RGB: */ + if (that->colour_type == PNG_COLOR_TYPE_PALETTE) + image_pixel_convert_PLTE(that, &display->this); + + /* We need to apply the tRNS chunk at this point: */ + if (that->have_tRNS) + image_pixel_add_alpha(that, &display->this); + + /* Now there is only something to do if there is an alpha channel: */ + if ((that->colour_type & PNG_COLOR_MASK_ALPHA) != 0) + { + double component; + + /* We expanded above in the grayscale case, so do so here too: */ + if (that->bit_depth < 8) that->bit_depth = 8; + + /* Now we do the background calculation without any gamma correction, + * assuming 127/255 (same as 127*257/65535) + */ + component = 127./255; + + if (that->alphaf <= 0) + { + that->bluef = that->greenf = that->redf = component; + that->bluee = that->greene = that->rede = component * DBL_EPSILON; + that->blue_sBIT = that->green_sBIT = that->red_sBIT = that->bit_depth; + } + + else if (that->alphaf < 1) + { + component *= 1-that->alphaf; + that->redf = that->redf * that->alphaf + component; + that->rede = that->rede * that->alphaf + that->redf * 3 * DBL_EPSILON; + that->greenf = that->greenf * that->alphaf + component; + that->greene = that->greene * that->alphaf + that->greenf * 3 * + DBL_EPSILON; + that->bluef = that->bluef * that->alphaf + component; + that->bluee = that->bluee * that->alphaf + that->bluef * 3 * + DBL_EPSILON; + } + + /* Else nothing changes - opaque pixel */ + } + + else /* no alpha channel */ + { + /* LIBPNG BUG: libpng expands here too. */ + if (that->bit_depth < 8) that->bit_depth = 8; + } + + this->next->mod(this->next, that, pp, display); +} + +static int +image_transform_png_set_background_add(image_transform *this, + PNG_CONST image_transform **that, char *name, size_t sizeof_name, + size_t *pos, png_byte colour_type, png_byte bit_depth) +{ + UNUSED(colour_type); + UNUSED(bit_depth); + + this->next = *that; + *that = this; + + *pos = safecat(name, sizeof_name, *pos, " +background"); + + /* There may be a tRNS chunk, so return 1 all the time: */ + return 1; +} + +IT(background,rgb_to_gray); + +static image_transform *PNG_CONST image_transform_first = &ITSTRUCT(background); + +typedef enum +{ + all_combinations, /* Takes for ever */ + just_one, /* Tests each tranform in turn on its own. */ + max_2, /* Randomly tests each transform */ + max_4 +} image_transform_how; + +static png_uint_32 +image_transform_add(PNG_CONST image_transform **this, image_transform_how how, + png_uint_32 counter, char *name, size_t sizeof_name, size_t *pos, + png_byte colour_type, png_byte bit_depth) +{ + image_transform *next = image_transform_first; + png_uint_32 mask = 1; + int add_count = 0; + + *this = &image_transform_end; + for (;;) + { + int useful; + + if (counter == 0xffffffff) /* first time */ + next->local_use = 0; + + /* Add this transform if the counter selects it: */ + useful = 0; + if (mask & counter) + { + size_t saved_pos = *pos; + + ++add_count; + useful = next->add(next, this, name, sizeof_name, pos, colour_type, + bit_depth); + if (useful) ++(next->local_use); + + /* Do we want this transform now? */ + switch (how) + { + case just_one: + /* Only if it is useful: */ + if (useful) return counter & ~mask; + *this = next->next; + *pos = saved_pos; + --add_count; + break; + + case max_2: + if (!useful || next->local_use > 2) + { + *this = next->next; + *pos = saved_pos; + if (useful) next->local_use = 2; + --add_count; + } + break; + + case max_4: + if (!useful || next->local_use > 4) + { + *this = next->next; + *pos = saved_pos; + if (useful) next->local_use = 4; + --add_count; + } + break; + + case all_combinations: + default: + break; + } + } + + next = next->list; + if (next == &image_transform_end) + break; + + mask <<= 1; + if (mask == 0) abort(); /* Silly, you just added a 32nd transform ;-) */ + } + + /* Having built a list return a counter for the next list, if we didn't add + * anything this time round just return 0 to end the loop. + * NOTE: this actually terminates the random loops early. + */ + if (add_count == 0) + return 0; + + if (how == all_combinations) + return counter+1; /* This will take for ever */ + + /* Come up with a new, different, random combination of stuff to add, these + * are the 'max' cases above. + */ + { + png_byte mark[4]; + + store_pool_mark(mark); + return png_get_uint_32(mark); + } +} + +#ifdef THIS_IS_THE_PROFORMA +static void +image_transform_png_set_@_set(PNG_CONST image_transform *this, + transform_display *that, png_structp pp, png_infop pi) +{ + png_set_@(pp); + this->next->set(this->next, that, pp, pi); +} + +static void +image_transform_png_set_@_mod(PNG_CONST image_transform *this, + image_pixel *that, png_structp pp, PNG_CONST transform_display *display) +{ + this->next->mod(this->next, that, pp, display); +} + +static int +image_transform_png_set_@_add(image_transform *this, + PNG_CONST image_transform **that, char *name, size_t sizeof_name, + size_t *pos, png_byte colour_type, png_byte bit_depth) +{ + this->next = *that; + *that = this; + + *pos = safecat(name, sizeof_name, *pos, " +@"); + + return 1; +} + +IT(@); +#endif + +/* png_set_quantize(png_structp, png_colorp palette, int num_palette, + * int maximum_colors, png_const_uint_16p histogram, int full_quantize) + * + * Very difficult to validate this! + */ +/*NOTE: TBD NYI */ + +/* The data layout transforms are handled by swapping our own channel data, + * necessarily these need to happen at the end of the transform list because the + * semantic of the channels changes after these are executed. Some of these, + * like set_shift and set_packing, can't be done at present because they change + * the layout of the data at the sub-sample level so sample() won't get the + * right answer. + */ +/* png_set_invert_alpha */ +/*NOTE: TBD NYI */ + +/* png_set_bgr */ +/*NOTE: TBD NYI */ + +/* png_set_swap_alpha */ +/*NOTE: TBD NYI */ + +/* png_set_swap */ +/*NOTE: TBD NYI */ + +/* png_set_filler, (png_structp png_ptr, png_uint_32 filler, int flags)); */ +/*NOTE: TBD NYI */ + +/* png_set_add_alpha, (png_structp png_ptr, png_uint_32 filler, int flags)); */ +/*NOTE: TBD NYI */ + +/* png_set_packing */ +/*NOTE: TBD NYI */ + +/* png_set_packswap */ +/*NOTE: TBD NYI */ + +/* png_set_invert_mono */ +/*NOTE: TBD NYI */ + +/* png_set_shift(png_structp, png_const_color_8p true_bits) */ +/*NOTE: TBD NYI */ + +static int +test_transform(png_modifier* PNG_CONST pm, png_byte PNG_CONST colour_type, + int bdlo, int PNG_CONST bdhi, image_transform_how how) +{ + for (; bdlo <= bdhi; ++bdlo) + { + PNG_CONST png_byte bit_depth = DEPTH(bdlo); + png_uint_32 counter = 0xffffffff; + size_t base_pos; + char name[64]; + + base_pos = safecat(name, sizeof name, 0, "transform:"); + + for (;;) + { + size_t pos = base_pos; + PNG_CONST image_transform *list = 0; + + counter = image_transform_add(&list, how, counter, name, sizeof name, + &pos, colour_type, bit_depth); + + if (counter == 0) + break; + + /* The command line can change this to checking interlaced images. */ + transform_test(pm, FILEID(colour_type, bit_depth, pm->interlace_type, + 0, 0, 0), list, name); + + if (fail(pm)) + return 0; + } + } + + return 1; /* keep going */ +} + +static void +perform_transform_test(png_modifier *pm) +{ + /* Test each colour type over the valid range of bit depths (expressed as + * log2(bit_depth) in turn, stop as soon as any error is detected. + */ + if (!test_transform(pm, 0, 0, READ_BDHI, just_one)) + return; + + if (!test_transform(pm, 2, 3, READ_BDHI, just_one)) + return; + + if (!test_transform(pm, 3, 0, 3, just_one)) + return; + + if (!test_transform(pm, 4, 3, READ_BDHI, just_one)) + return; + + if (!test_transform(pm, 6, 3, READ_BDHI, just_one)) + return; +} + + /********************************* GAMMA TESTS ********************************/ /* Gamma test images. */ typedef struct gamma_modification @@ -4081,7 +5680,7 @@ static void perform_gamma_test(png_modifier *pm, int speed, int summary) { /* First some arbitrary no-transform tests: */ - if (!speed && pm->test_threshold) + if (!speed && pm->test_gamma_threshold) { perform_gamma_threshold_tests(pm); @@ -4090,7 +5689,7 @@ perform_gamma_test(png_modifier *pm, int speed, int summary) } /* Now some real transforms. */ - if (pm->test_transform) + if (pm->test_gamma_transform) { perform_gamma_transform_tests(pm, speed); @@ -4121,7 +5720,7 @@ perform_gamma_test(png_modifier *pm, int speed, int summary) } /* The sbit tests produce much larger errors: */ - if (pm->test_sbit) + if (pm->test_gamma_sbit) { pm->error_gray_2 = pm->error_gray_4 = pm->error_gray_8 = pm->error_gray_16 = pm->error_color_8 = pm->error_color_16 = 0; @@ -4147,7 +5746,7 @@ perform_gamma_test(png_modifier *pm, int speed, int summary) } #ifdef PNG_READ_16_TO_8_SUPPORTED - if (pm->test_strip16) + if (pm->test_gamma_strip16) { /* The 16 to 8 bit strip operations: */ pm->error_gray_2 = pm->error_gray_4 = pm->error_gray_8 = @@ -4577,6 +6176,9 @@ int main(int argc, PNG_CONST char **argv) */ pm.maxpc16 = .005; /* I.e., 1/200% - 1/20000 */ + /* WARNING: this is temporary until it passes! */ + pm.test_transform = 0; + /* Now parse the command line options. */ while (--argc >= 1) { @@ -4605,20 +6207,23 @@ int main(int argc, PNG_CONST char **argv) else if (strcmp(*argv, "--nostandard") == 0) pm.test_standard = 0; + else if (strcmp(*argv, "--notransform") == 0) + pm.test_transform = 0; + else if (strcmp(*argv, "--nogamma") == 0) pm.ngammas = 0; else if (strcmp(*argv, "--nogamma-threshold") == 0) - pm.test_threshold = 0; + pm.test_gamma_threshold = 0; else if (strcmp(*argv, "--nogamma-transform") == 0) - pm.test_transform = 0; + pm.test_gamma_transform = 0; else if (strcmp(*argv, "--nogamma-sbit") == 0) - pm.test_sbit = 0; + pm.test_gamma_sbit = 0; else if (strcmp(*argv, "--nogamma-16-to-8") == 0) - pm.test_strip16 = 0; + pm.test_gamma_strip16 = 0; else if (strcmp(*argv, "--progressive-read") == 0) pm.this.progressive = 1; @@ -4688,6 +6293,10 @@ int main(int argc, PNG_CONST char **argv) perform_size_test(&pm); } + /* Combinatorial transforms: */ + if (pm.test_transform) + perform_transform_test(&pm); + if (pm.ngammas > 0) perform_gamma_test(&pm, pm.this.speed != 0, summary && !pm.this.speed);