diff --git a/libpng-manual.txt b/libpng-manual.txt index 23ef707a3044f72a589c898c3c324c6b942b8b0e..c889aa42c5dcd31e394fe549b4c839703e6269c2 100644 --- a/libpng-manual.txt +++ b/libpng-manual.txt @@ -1472,6 +1472,11 @@ on gamma in the PNG specification for an excellent description of what gamma is, and why all applications should support it. It is strongly recommended that PNG viewers support gamma correction. +This API unconditionally sets the screen and file gamma values, so it will +override the value in the PNG file unless it is called before the PNG file +reading starts. For this reason you must always call it with the PNG file +value when you call it in this position: + if (png_get_gAMA(png_ptr, info_ptr, &file_gamma)) png_set_gamma(png_ptr, screen_gamma, file_gamma); diff --git a/png.c b/png.c index 47ec42eec9270c28f9e426c4fc15b477bbc33457..c30385bbaeee2887ad83cf779388f01feaf0b91a 100644 --- a/png.c +++ b/png.c @@ -586,17 +586,17 @@ png_convert_to_rfc1123(png_structp png_ptr, png_const_timep ptime) if (pos < (sizeof png_ptr->time_buffer)-1)\ png_ptr->time_buffer[pos++] = (ch) - APPEND_NUMBER(PNG_NUMBER_FORMAT_u, ptime->day % 32); + APPEND_NUMBER(PNG_NUMBER_FORMAT_u, (unsigned)ptime->day % 32); APPEND(' '); APPEND_STRING(short_months[(ptime->month - 1) % 12]); APPEND(' '); APPEND_NUMBER(PNG_NUMBER_FORMAT_u, ptime->year); APPEND(' '); - APPEND_NUMBER(PNG_NUMBER_FORMAT_02u, ptime->hour % 24); + APPEND_NUMBER(PNG_NUMBER_FORMAT_02u, (unsigned)ptime->hour % 24); APPEND(':'); - APPEND_NUMBER(PNG_NUMBER_FORMAT_02u, ptime->minute % 60); + APPEND_NUMBER(PNG_NUMBER_FORMAT_02u, (unsigned)ptime->minute % 60); APPEND(':'); - APPEND_NUMBER(PNG_NUMBER_FORMAT_02u, ptime->second % 61); + APPEND_NUMBER(PNG_NUMBER_FORMAT_02u, (unsigned)ptime->second % 61); APPEND_STRING(" +0000"); /* This reliably terminates the buffer */ # undef APPEND @@ -2322,8 +2322,9 @@ png_build_gamma_table(png_structp png_ptr, int bit_depth) png_ptr->screen_gamma) : PNG_FP_1); #if defined(PNG_READ_BACKGROUND_SUPPORTED) || \ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) || \ defined(PNG_READ_RGB_TO_GRAY_SUPPORTED) - if (png_ptr->transformations & ((PNG_BACKGROUND) | PNG_RGB_TO_GRAY)) + if (png_ptr->transformations & (PNG_COMPOSE | PNG_RGB_TO_GRAY)) { png_build_8bit_table(png_ptr, &png_ptr->gamma_to_1, png_reciprocal(png_ptr->gamma)); @@ -2332,7 +2333,7 @@ png_build_gamma_table(png_structp png_ptr, int bit_depth) png_ptr->screen_gamma > 0 ? png_reciprocal(png_ptr->screen_gamma) : png_ptr->gamma/* Probably doing rgb_to_gray */); } -#endif /* PNG_READ_BACKGROUND_SUPPORTED || PNG_RGB_TO_GRAY_SUPPORTED */ +#endif /* READ_BACKGROUND || READ_ALPHA_MODE || RGB_TO_GRAY */ } else { @@ -2391,7 +2392,12 @@ png_build_gamma_table(png_structp png_ptr, int bit_depth) png_ptr->gamma_shift = shift; #ifdef PNG_16BIT_SUPPORTED - if (png_ptr->transformations & (PNG_16_TO_8 | PNG_BACKGROUND)) + /* NOTE: prior to 1.5.3 this test used to include PNG_BACKGROUND (now + * PNG_COMPOSE). This effectively smashed the background calculation for + * 16 bit output because the 8 bit table assumes the result will be reduced + * to 8 bits. + */ + if (png_ptr->transformations & PNG_16_TO_8) #endif png_build_16to8_table(png_ptr, &png_ptr->gamma_16_table, shift, png_ptr->screen_gamma > 0 ? png_product2(png_ptr->gamma, @@ -2405,8 +2411,9 @@ png_build_gamma_table(png_structp png_ptr, int bit_depth) #endif #if defined(PNG_READ_BACKGROUND_SUPPORTED) || \ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) || \ defined(PNG_READ_RGB_TO_GRAY_SUPPORTED) - if (png_ptr->transformations & (PNG_BACKGROUND | PNG_RGB_TO_GRAY)) + if (png_ptr->transformations & (PNG_COMPOSE | PNG_RGB_TO_GRAY)) { png_build_16bit_table(png_ptr, &png_ptr->gamma_16_to_1, shift, png_reciprocal(png_ptr->gamma)); @@ -2419,7 +2426,7 @@ png_build_gamma_table(png_structp png_ptr, int bit_depth) png_ptr->screen_gamma > 0 ? png_reciprocal(png_ptr->screen_gamma) : png_ptr->gamma/* Probably doing rgb_to_gray */); } -#endif /* PNG_READ_BACKGROUND_SUPPORTED || PNG_RGB_TO_GRAY_SUPPORTED */ +#endif /* READ_BACKGROUND || READ_ALPHA_MODE || RGB_TO_GRAY */ } } #endif /* READ_GAMMA */ diff --git a/png.h b/png.h index 312956de0932e006ede5006647b86c6f4232e439..6cae5e1b01c6d1ee9c5b72447f8ed73c77fcdcf0 100644 --- a/png.h +++ b/png.h @@ -690,6 +690,8 @@ typedef png_info FAR * FAR * png_infopp; */ #define PNG_FP_1 100000 #define PNG_FP_HALF 50000 +#define PNG_FP_MAX ((png_fixed_point)0x7fffffffL) +#define PNG_FP_MIN (-PNG_FP_MAX) /* These describe the color_type field in png_info. */ /* color type masks */ @@ -1112,6 +1114,202 @@ PNG_EXPORT(35, void, png_build_grayscale_palette, (int bit_depth, png_colorp palette)); #endif +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED +/* How the alpha channel is interpreted - this affects how the color channels of + * a PNG file are output when an alpha channel, or tRNS chunk is a palette file, + * is present. + * + * The default is to output data according to the PNG specification: the alpha + * channel is a linear measure of the contribution of the pixel to the + * corresponding output pixel. The gamma encoded color channels must be scaled + * according to the contribution and to do this it is necessary to undo the + * encoding, scale the color values, perform the composition and reencode the + * values. This is the 'PNG' format. + * + * The alternative is to 'associate' the alpha with the color information by + * storing color channel values that have been scaled by the alpha. The + * advantage is that the color channels can be resampled (the image can be + * scaled) in this form. The disadvantage is that normal practice is to store + * linear, not (gamma) encoded, values and this requires 16 bit channels for + * still images rather than the 8 bit channels that are just about sufficient if + * gamma encoding is used. In addition all non-transparent pixel values, + * including completely opaque ones, must be gamma encoded to produce the final + * image. This is the 'STANDARD', 'ASSOCIATED' or 'PREMULTIPLIED' format (the + * latter being the two common names for associated alpha color channels.) + * + * Since it is not necessary to perform arithmetic on opaque color values so + * long as they are not to be resampled and are in the final color space it is + * possible to optimize the handling of alpha by storing the opaque pixels in + * the PNG format (adjusted for the output color space) while storing partially + * opaque pixels in the standard, linear, format. The accuracy required for + * standard alpha composition is relatively low, because the pixels are + * isolated, therefore typically the accuracy loss in storing 8 bit linear + * values is acceptable. (This is not true if the alpha channel is used to + * simiulate transparency over large areas - use 16 bits or the PNG format in + * this case!) This is the 'OPTIMIZED' format. For this format a pixel is + * treated as opaque only if the alpha value is equal to the maximum value. + * + * The final choice is to gamma encode the alpha channel as well. This is + * broken because, in practice, no implementation that uses this choice + * correctly undoes the encoding before handling alpha composition. Use this + * choice only if other serious errors in the software or hardware you use + * mandate it; the typical serious error is for dark halos to appear around + * opaque areas of the composited PNG image because of arithmetic overflow. + * + * The API function png_set_alpha_mode specifies which of these choices to use + * with a enumerated 'mode' value and the gamma of the required output: + */ +#define PNG_ALPHA_PNG 0 /* according to the PNG standard */ +#define PNG_ALPHA_STANDARD 1 /* according to Porter/Duff */ +#define PNG_ALPHA_ASSOCIATED 1 /* as above; this is the normal practice */ +#define PNG_ALPHA_PREMULTIPLIED 1 /* as above */ +#define PNG_ALPHA_OPTIMIZED 2 /* 'PNG' for opaque pixels, else 'STANDARD' */ +#define PNG_ALPHA_BROKEN 3 /* the alpha channel is gamma encoded */ + +PNG_FP_EXPORT(227, void, png_set_alpha_mode, (png_structp png_ptr, int mode, + double output_gamma)); +PNG_FIXED_EXPORT(228, void, png_set_alpha_mode_fixed, (png_structp png_ptr, + int mode, png_fixed_point output_gamma)); + +/* The output_gamma value is a screen gamma in libpng terminology: it expresses + * how to decode the output values, not how they are encoded. The values used + * correspond to the normal numbers used to describe the overall gamma of a + * computer display system; for example 2.2 for an sRGB conformant system. The + * values are scaled by 100000 in the _fixed version of the API (so 220000 for + * sRGB.) + * + * The inverse of the value is always used to provide a default for the PNG file + * encoding if it has no gAMA chunk and if png_set_gamma() has not been called + * to override the PNG gamma information. + * + * When the ALPHA_OPTIMIZED mode is selected the output gamma is used to encode + * opaque pixels however pixels with lower alpha values are not encoded, + * regardless of the output gamma setting. + * + * When the standard Porter Duff handling is requested with mode 1 the output + * encoding is set to be linear and the output_gamma value is only relevant + * as a default for input data that has no gamma information. The linear output + * encoding will be overridden if png_set_gamma() is called - the results may be + * highly unexpected! + * + * The following numbers are derived from the sRGB standard and the research + * behind it. sRGB is defined to be approximated by a PNG gAMA chunk value of + * 0.45455 (1/2.2) for PNG. The value implicitly includes any viewing + * correction required to take account of any differences in the color + * environment of the original scene and the intended display environment; the + * value expresses how to *decode* the image for display, not how the original + * data was *encoded*. + * + * sRGB provides a peg for the PNG standard by defining a viewing environment. + * sRGB itself, and earlier TV standards, actually use a more complex transform + * (a linear portion then a gamma 2.4 power law) than PNG can express. (PNG is + * limited to simple power laws.) By saying that an image for direct display on + * an sRGB conformant system should be stored with a gAMA chunk value of 45455 + * (11.3.3.2 and 11.3.3.5 of the ISO PNG specification) the PNG specification + * makes it possible to derive values for other display systems and + * environments. + * + * The Mac value is deduced from the sRGB based on an assumption that the actual + * extra viewing correction used in early Mac display systems was implemented as + * a power 1.45 lookup table. + * + * Any system where a programmable lookup table is used or where the behavior of + * the final display device characteristics can be changed requires system + * specific code to obtain the current characteristic. However this can be + * difficult and most PNG gamma correction only requires an approximate value. + * + * By default, if png_set_alpha_mode() is not called, libpng assumes that all + * values are unencoded, linear, values and that the output device also has a + * linear characteristic. This is only very rarely correct - it is invariably + * better to call png_set_alpha_mode() with PNG_DEFAULT_sRGB than rely on the + * default if you don't know what the right answer is! + * + * NOTE: the following values can be passed to either the fixed or floating + * point APIs, but the floating point API will also accept floating point + * values. + */ +#define PNG_DEFAULT_sRGB 0 /* sRGB gamma and color space */ +#define PNG_GAMMA_sRGB 220000 /* Television standards - matchs sRGB gamma */ +#define PNG_GAMMA_MAC 151724 /* Television with a 1.45 correction table */ +#define PNG_GAMMA_LINEAR PNG_FP_1 /* Linear */ + +/* The following are examples of calls to png_set_alpha_mode to achieve the + * required overall gamma correction and, where necessary, alpha + * premultiplication. + * + * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_DEFAULT_sRGB); + * This is the default libpng handling of the alpha channel - it is not + * pre-multiplied into the color components. In addition the call states + * that the output is for a sRGB system and causes all PNG files without gAMA + * chunks to be assumed to be encoded using sRGB. + * + * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_GAMMA_MAC); + * In this case the output is assumed to be something like an sRGB conformant + * display preceeded by a power-law lookup table of power 1.45. This is how + * early Mac systems behaved. + * + * png_set_alpha_mode(pp, PNG_ALPHA_STANDRAD, PNG_GAMMA_LINEAR); + * This is the classic Jim Blinn approach and will work in academic + * environments where everything is done by the book. It has the shortcoming + * of assuming that input PNG data with no gamma information is linear - this + * is unlikely to be correct unless the PNG files where generated locally. + * Most of the time the output precision will be so low as to show + * significant banding in dark areas of the image. + * + * png_set_expand_16(pp); + * png_set_alpha_mode(pp, PNG_ALPHA_STANDARD, PNG_DEFAULT_sRGB); + * This is a somewhat more realistic Jim Blinn inspired approach. PNG files + * are assumed to have the sRGB encoding if not marked with a gamma value and + * the output is always 16 bits per component. This permits accurate scaling + * and processing of the data. If you know that your input PNG files were + * generated locally you might need to replace PNG_DEFAULT_sRGB with the + * correct value for your system. + * + * png_set_alpha_mode(pp, PNG_ALPHA_OPTIMIZED, PNG_DEFAULT_sRGB); + * If you just need to composite the PNG image onto an existing background + * and if you control the code that does this you can use the optimization + * setting. In this case you just copy completely opaque pixels to the + * output. For pixels that are not completely transparent (you just skip + * those) you do the composition math using png_composite or png_composite_16 + * below then encode the resultant 8 or 16 bit values to match the output + * encoding. + * + * Other cases + * If neither the PNG nor the standard linear encoding work for you because + * of the software or hardware you use then you have a big problem. The PNG + * case will probably result in halos around the image. The linear encoding + * will probably result in a washed out, too bright, image (it's actually too + * contrasty.) Try the ALPHA_OPTIMIZED mode above - this will probably + * substantially reduce the halos. Alternatively try: + * + * png_set_alpha_mode(pp, PNG_ALPHA_BROKEN, PNG_DEFAULT_sRGB); + * This option will also reduce the halos, but there will be slight dark + * halos round the opaque parts of the image where the background is light. + * In the OPTIMIZED mode the halos will be light halos where the background + * is dark. Take your pick - the halos are unavoidable unless you can get + * your hardware/software fixed! (The OPTIMIZED approach is slightly + * faster.) + * + * When the default gamma of PNG files doesn't match the output gamma. + * If you have PNG files with no gamma information png_set_alpha_mode allows + * you to provide a default gamma, but it also sets the ouput gamma to the + * matching value. If you know your PNG files have a gamma that doesn't + * match the output you can take advantage of the fact that + * png_set_alpha_mode always sets the output gamma but only sets the PNG + * default if it is not already set: + * + * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_DEFAULT_sRGB); + * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_GAMMA_MAC); + * The first call sets both the default and the output gamma values, the + * second call overrides the output gamma without changing the default. This + * is easier than achieving the same effect with png_set_gamma. You must use + * PNG_ALPHA_PNG for the first call - internal checking in png_set_alpha will + * fire if more than one call to png_set_alpha_mode and png_set_background is + * made in the same read operation, however multiple calls with PNG_ALPHA_PNG + * are ignored. + */ +#endif + #ifdef PNG_READ_STRIP_ALPHA_SUPPORTED PNG_EXPORT(36, void, png_set_strip_alpha, (png_structp png_ptr)); #endif @@ -1214,12 +1412,16 @@ PNG_EXPORT(49, void, png_set_quantize, */ #define PNG_GAMMA_THRESHOLD (PNG_GAMMA_THRESHOLD_FIXED*.00001) -/* Handle gamma correction. Screen_gamma=(display_exponent) */ +/* Handle gamma correction. Screen_gamma=(display_exponent). + * NOTE: this API simply sets the screen and file gamma values, it will + * therefore override the value for gamma in a PNG file if it is called after + * the file header has been read - use with care! + */ PNG_FP_EXPORT(50, void, png_set_gamma, (png_structp png_ptr, double screen_gamma, - double default_file_gamma)); + double override_file_gamma)); PNG_FIXED_EXPORT(208, void, png_set_gamma_fixed, (png_structp png_ptr, - png_fixed_point screen_gamma, png_fixed_point default_file_gamma)); + png_fixed_point screen_gamma, png_fixed_point override_file_gamma)); #endif #ifdef PNG_WRITE_FLUSH_SUPPORTED @@ -2328,7 +2530,7 @@ PNG_EXPORT(207, void, png_save_uint_16, (png_bytep buf, unsigned int i)); * scripts/symbols.def as well. */ #ifdef PNG_EXPORT_LAST_ORDINAL - PNG_EXPORT_LAST_ORDINAL(226); + PNG_EXPORT_LAST_ORDINAL(228); #endif #ifdef __cplusplus diff --git a/pngpriv.h b/pngpriv.h index 9c3a369b720fdc3169d7574cf63ade095ca8623d..d6849c317fab7d438294995039ee30969aea3901 100644 --- a/pngpriv.h +++ b/pngpriv.h @@ -285,7 +285,7 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; #define PNG_SWAP_BYTES 0x0010 #define PNG_INVERT_MONO 0x0020 #define PNG_QUANTIZE 0x0040 -#define PNG_BACKGROUND 0x0080 +#define PNG_COMPOSE 0x0080 /* Was PNG_BACKGROUND */ #define PNG_BACKGROUND_EXPAND 0x0100 #define PNG_EXPAND_16 0x0200 /* Added to libpng 1.5.2 */ #define PNG_16_TO_8 0x0400 @@ -302,7 +302,7 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; #define PNG_RGB_TO_GRAY_ERR 0x200000L #define PNG_RGB_TO_GRAY_WARN 0x400000L #define PNG_RGB_TO_GRAY 0x600000L /* two bits, RGB_TO_GRAY_ERR|WARN */ - /* 0x800000L Unused */ +#define PNG_ENCODE_ALPHA 0x800000L /* Added to libpng-1.5.3 */ #define PNG_ADD_ALPHA 0x1000000L /* Added to libpng-1.2.7 */ #define PNG_EXPAND_tRNS 0x2000000L /* Added to libpng-1.2.9 */ /* 0x4000000L unused */ @@ -332,9 +332,9 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; #define PNG_FLAG_CRC_ANCILLARY_NOWARN 0x0200 #define PNG_FLAG_CRC_CRITICAL_USE 0x0400 #define PNG_FLAG_CRC_CRITICAL_IGNORE 0x0800 - /* 0x1000 unused */ - /* 0x2000 unused */ - /* 0x4000 unused */ +#define PNG_FLAG_ASSUME_sRGB 0x1000 /* Added to libpng-1.5.3 */ +#define PNG_FLAG_OPTIMIZE_ALPHA 0x2000 /* Added to libpng-1.5.3 */ +#define PNG_FLAG_DETECT_UNINITIALIZED 0x4000 /* Added to libpng-1.5.3 */ #define PNG_FLAG_KEEP_UNKNOWN_CHUNKS 0x8000L #define PNG_FLAG_KEEP_UNSAFE_CHUNKS 0x10000L #define PNG_FLAG_LIBRARY_MISMATCH 0x20000L @@ -852,8 +852,9 @@ PNG_EXTERN void png_do_shift PNGARG((png_row_infop row_info, png_bytep row, png_const_color_8p bit_depth)); #endif -#ifdef PNG_READ_BACKGROUND_SUPPORTED -PNG_EXTERN void png_do_background PNGARG((png_row_infop row_info, +#if defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) +PNG_EXTERN void png_do_compose PNGARG((png_row_infop row_info, png_bytep row, png_structp png_ptr)); #endif @@ -862,6 +863,11 @@ PNG_EXTERN void png_do_gamma PNGARG((png_row_infop row_info, png_bytep row, png_structp png_ptr)); #endif +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED +PNG_EXTERN void png_do_encode_alpha PNGARG((png_row_infop row_info, + png_bytep row, png_structp png_ptr)); +#endif + #ifdef PNG_READ_EXPAND_SUPPORTED PNG_EXTERN void png_do_expand_palette PNGARG((png_row_infop row_info, png_bytep row, png_const_colorp palette, png_const_bytep trans, diff --git a/pngrtran.c b/pngrtran.c index c05eeb7a9a9c678e8c2d0cec31f74ea0cba143ec..7c9c5518d98ac709203c31ed9da5f2beac8e071b 100644 --- a/pngrtran.c +++ b/pngrtran.c @@ -106,12 +106,18 @@ png_set_background_fixed(png_structp png_ptr, return; } - png_ptr->transformations |= PNG_BACKGROUND; + png_ptr->transformations |= PNG_COMPOSE | PNG_STRIP_ALPHA; + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + png_memcpy(&(png_ptr->background), background_color, png_sizeof(png_color_16)); png_ptr->background_gamma = background_gamma; png_ptr->background_gamma_type = (png_byte)(background_gamma_code); - png_ptr->transformations |= (need_expand ? PNG_BACKGROUND_EXPAND : 0); + if (need_expand) + png_ptr->transformations |= PNG_BACKGROUND_EXPAND; + else + png_ptr->transformations &= ~PNG_BACKGROUND_EXPAND; } # ifdef PNG_FLOATING_POINT_SUPPORTED @@ -153,6 +159,150 @@ png_set_strip_alpha(png_structp png_ptr) } #endif +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED +void PNGFAPI +png_set_alpha_mode_fixed(png_structp png_ptr, int mode, + png_fixed_point output_gamma) +{ + int compose = 0; + png_fixed_point file_gamma; + + png_debug(1, "in png_set_alpha_mode"); + + if (png_ptr == NULL) + return; + + /* If the default_gamma value is 0 then switch on the whole sRGB + * edifice by this flag (note: this is a flag to the later code, + * not a transformation!) + */ + if (output_gamma == PNG_DEFAULT_sRGB) + { + /* If there is no sRGB support this just sets the gamma to the standard + * sRGB value. + */ +# ifdef PNG_READ_sRGB_SUPPORTED + png_ptr->flags |= PNG_FLAG_ASSUME_sRGB; +# endif + output_gamma = PNG_GAMMA_sRGB; + } + + /* Else validate the value to ensure it is in a reasonable range, the value + * is expected to be 1 or greater, but this range test allows for some + * viewing correction values. The intent is to weed out users of this API + * who use the inverse of the gamma value accidentally! Since some of these + * values are reasonable this may have to be changed. + */ + else if (output_gamma < 70000 || output_gamma > 300000) + png_error(png_ptr, "output gamma out of expected range"); + + /* The default file gamma is the inverse of the output gamma, the output + * gamma may be changed below so get the file value first: + */ + file_gamma = png_reciprocal(output_gamma); + + /* There are really 8 possibilities here, composed of any combination + * of: + * + * premultiply the color channels + * do not encode non-opaque pixels + * encode the alpha as well as the color channels + * + * The differences disappear if the input/output ('screen') gamma is 1.0, + * because then the encoding is a no-op and there is only the choice of + * premultiplying the color channels or not. + * + * png_set_alpha_mode and png_set_background interact because both use + * png_compose to do the work. Calling both is only useful when + * png_set_alpha_mode is used to set the default mode - PNG_ALPHA_PNG - along + * with a default gamma value. Otherwise PNG_COMPOSE must not be set. + */ + switch (mode) + { + case PNG_ALPHA_PNG: /* default: png standard */ + /* No compose, but it may be set by png_set_background! */ + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + break; + + case PNG_ALPHA_ASSOCIATED: /* color channels premultiplied */ + compose = 1; + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + /* The output is linear: */ + output_gamma = PNG_FP_1; + break; + + case PNG_ALPHA_OPTIMIZED: /* associated, non-opaque pixels linear */ + compose = 1; + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags |= PNG_FLAG_OPTIMIZE_ALPHA; + /* output_gamma records the encoding of opaque pixels! */ + break; + + case PNG_ALPHA_BROKEN: /* associated, non-linear, alpha encoded */ + compose = 1; + png_ptr->transformations |= PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + break; + + default: + png_error(png_ptr, "invalid alpha mode"); + } + + /* Only set the default gamma if the file gamma has not been set (this has + * the side effect that the gamma in a second call to png_set_alpha_mode will + * be ignored.) + */ + if (png_ptr->gamma == 0) + png_ptr->gamma = file_gamma; + + /* But always set the output gamma: */ + png_ptr->screen_gamma = output_gamma; + + /* Finally, if pre-multiplying, set the background fields to achive the + * desired result. + */ + if (compose) + { + /* And obtain alpha pre-multiplication by composing on black: */ + png_memset(&png_ptr->background, 0, sizeof png_ptr->background); + png_ptr->background_gamma = png_ptr->gamma; /* just in case */ + png_ptr->background_gamma_type = PNG_BACKGROUND_GAMMA_FILE; + png_ptr->transformations &= ~PNG_BACKGROUND_EXPAND; + + if (png_ptr->transformations & PNG_COMPOSE) + png_error(png_ptr, + "conflicting calls to set alpha mode and background"); + + png_ptr->transformations |= PNG_COMPOSE; + } + + /* New API, make sure apps call the correct initializers: */ + png_ptr->flags |= PNG_FLAG_DETECT_UNINITIALIZED; +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_alpha_mode(png_structp png_ptr, int mode, double output_gamma) +{ + /* The following silently ignores cases where fixed point (times 100,000) + * gamma values are passed to the floating point API. This is safe and it + * means the fixed point constants work just fine with the floating point + * API. The alternative would just lead to undetected errors and spurious + * bug reports. Negative values fail inside the _fixed API. + */ + if (output_gamma > -128 && output_gamma < 128) + output_gamma *= PNG_FP_1; + + if (output_gamma <= PNG_FP_MAX && output_gamma >= PNG_FP_MIN) + png_set_alpha_mode_fixed(png_ptr, mode, (png_fixed_point)output_gamma); + else + png_fixed_error(png_ptr, "png_set_alpha_mode gamma"); +} +# endif +#endif + #ifdef PNG_READ_QUANTIZE_SUPPORTED /* Dither file to 8 bit. Supply a palette, the current number * of elements in the palette, the maximum number of elements @@ -560,30 +710,6 @@ png_set_quantize(png_structp png_ptr, png_colorp palette, #endif /* PNG_READ_QUANTIZE_SUPPORTED */ #ifdef PNG_READ_GAMMA_SUPPORTED -/* Transform the image from the file_gamma to the screen_gamma. We - * only do transformations on images where the file_gamma and screen_gamma - * are not close reciprocals, otherwise it slows things down slightly, and - * also needlessly introduces small errors. - * - * We will turn off gamma transformation later if no semitransparent entries - * are present in the tRNS array for palette images. We can't do it here - * because we don't necessarily have the tRNS chunk yet. - */ -static int /* PRIVATE */ -png_gamma_threshold(png_fixed_point scrn_gamma, png_fixed_point file_gamma) -{ - /* PNG_GAMMA_THRESHOLD is the threshold for performing gamma - * correction as a difference of the overall transform from 1.0 - * - * We want to compare the threshold with s*f - 1, if we get - * overflow here it is because of wacky gamma values so we - * turn on processing anyway. - */ - png_fixed_point gtest; - return !png_muldiv(>est, scrn_gamma, file_gamma, PNG_FP_1) || - png_gamma_significant(gtest); -} - void PNGFAPI png_set_gamma_fixed(png_structp png_ptr, png_fixed_point scrn_gamma, png_fixed_point file_gamma) @@ -593,10 +719,28 @@ png_set_gamma_fixed(png_structp png_ptr, png_fixed_point scrn_gamma, if (png_ptr == NULL) return; - if ((png_ptr->color_type & PNG_COLOR_MASK_ALPHA) || - (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) || - png_gamma_threshold(scrn_gamma, file_gamma)) - png_ptr->transformations |= PNG_GAMMA; +#if PNG_LIBPNG_VER >= 10600 + /* Checking the gamma values for being >0 was added in 1.5.3 along with the + * premultiplied alpha support; this actually hides an undocumented feature + * of the previous implementation which allowed gamma processing to be + * disabled in background handling. There is no evidence (so far) that this + * was being used, however png_set_background itself accepted and must still + * accept '0' for the gamma value it takes, because it isn't always used. + * + * Since this is an API change (albeit a very minor one that removes an + * undocumented API feature) it will only be made in 1.6. + */ + if (file_gamma <= 0) + png_error("invalid file gamma to png_set_gamma"); + + if (scrn_gamma <= 0) + png_error("invalid screen gamma to png_set_gamma"); +#endif + + /* Set the gamma values unconditionally - this overrides the value in the PNG + * file if a gAMA chunk was present. png_set_alpha_mode provides a + * different, easier, way to default the file gamma. + */ png_ptr->gamma = file_gamma; png_ptr->screen_gamma = scrn_gamma; } @@ -700,6 +844,9 @@ png_set_expand_16(png_structp png_ptr) png_ptr->transformations |= (PNG_EXPAND_16 | PNG_EXPAND | PNG_EXPAND_tRNS); png_ptr->flags &= ~PNG_FLAG_ROW_INIT; + + /* New API, make sure apps call the correct initializers: */ + png_ptr->flags |= PNG_FLAG_DETECT_UNINITIALIZED; } #endif @@ -825,55 +972,159 @@ png_set_read_user_transform_fn(png_structp png_ptr, png_user_transform_ptr #endif #ifdef PNG_READ_TRANSFORMS_SUPPORTED +#ifdef PNG_READ_GAMMA_SUPPORTED +/* In the case of gamma transformations only do transformations on images where + * the [file] gamma and screen_gamma are not close reciprocals, otherwise it + * slows things down slightly, and also needlessly introduces small errors. + */ +static int /* PRIVATE */ +png_gamma_threshold(png_fixed_point screen_gamma, png_fixed_point file_gamma) +{ + /* PNG_GAMMA_THRESHOLD is the threshold for performing gamma + * correction as a difference of the overall transform from 1.0 + * + * We want to compare the threshold with s*f - 1, if we get + * overflow here it is because of wacky gamma values so we + * turn on processing anyway. + */ + png_fixed_point gtest; + return !png_muldiv(>est, screen_gamma, file_gamma, PNG_FP_1) || + png_gamma_significant(gtest); +} +#endif + /* Initialize everything needed for the read. This includes modifying * the palette. */ -void /* PRIVATE */ -png_init_read_transformations(png_structp png_ptr) + +/*For the moment 'png_init_palette_transformations' and + * 'png_init_rgb_transformations' only do some flag canceling optimizations. + * The intent is that these two routines should have palette or rgb operations + * extracted from 'png_init_read_transformations'. + */ +static void /* PRIVATE */ +png_init_palette_transformations(png_structp png_ptr) { - png_debug(1, "in png_init_read_transformations"); + /* Called to handle the (input) palette case. In png_do_read_transformations + * the first step is to expand the palette if requested, so this code must + * take care to only make changes that are invariant with respect to the + * palette expansion, or only do them if there is no expansion. + * + * STRIP_ALPHA has already been handled in the caller (by setting num_trans + * to 0.) + */ + int input_has_alpha = 0; + int input_has_transparency = 0; - { -#if defined(PNG_READ_BACKGROUND_SUPPORTED) || \ - defined(PNG_READ_SHIFT_SUPPORTED) || \ - defined(PNG_READ_GAMMA_SUPPORTED) - int color_type = png_ptr->color_type; -#endif + if (png_ptr->num_trans > 0) + { + int i; + + /* Ignore if all the entries are opaque (unlikely!) */ + for (i=0; inum_trans; ++i) + if (png_ptr->trans_alpha[i] == 255) + continue; + else if (png_ptr->trans_alpha[i] == 0) + input_has_transparency = 1; + else + input_has_alpha = 1; + } + + /* If no alpha we can optimize. */ + if (!input_has_alpha) + { + /* Any alpha means background and associative alpha processing is + * required, however if the alpha is 0 or 1 throughout OPTIIMIZE_ALPHA + * and ENCODE_ALPHA are irrelevant. + */ + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + + if (!input_has_transparency) + png_ptr->transformations &= ~(PNG_COMPOSE | PNG_BACKGROUND_EXPAND); + } #if defined(PNG_READ_EXPAND_SUPPORTED) && defined(PNG_READ_BACKGROUND_SUPPORTED) + /* png_set_background handling - deals with the complexity of whether the + * background color is in the file format or the screen format in the case + * where an 'expand' will happen. + */ -#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED - /* Detect gray background and attempt to enable optimization - * for gray --> RGB case - * - * Note: if PNG_BACKGROUND_EXPAND is set and color_type is either RGB or - * RGB_ALPHA (in which case need_expand is superfluous anyway), the - * background color might actually be gray yet not be flagged as such. - * This is not a problem for the current code, which uses - * PNG_BACKGROUND_IS_GRAY only to decide when to do the - * png_do_gray_to_rgb() transformation. + /* The following code cannot be entered in the alpha pre-multiplication case + * because PNG_BACKGROUND_EXPAND is cancelled below. */ if ((png_ptr->transformations & PNG_BACKGROUND_EXPAND) && - !(color_type & PNG_COLOR_MASK_COLOR)) + (png_ptr->transformations & PNG_EXPAND)) { - png_ptr->mode |= PNG_BACKGROUND_IS_GRAY; - } + { + png_ptr->background.red = + png_ptr->palette[png_ptr->background.index].red; + png_ptr->background.green = + png_ptr->palette[png_ptr->background.index].green; + png_ptr->background.blue = + png_ptr->palette[png_ptr->background.index].blue; - else if ((png_ptr->transformations & PNG_BACKGROUND) && - !(png_ptr->transformations & PNG_BACKGROUND_EXPAND) && - (png_ptr->transformations & PNG_GRAY_TO_RGB) && - png_ptr->background.red == png_ptr->background.green && - png_ptr->background.red == png_ptr->background.blue) +#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED + if (png_ptr->transformations & PNG_INVERT_ALPHA) + { + if (!(png_ptr->transformations & PNG_EXPAND_tRNS)) + { + /* Invert the alpha channel (in tRNS) unless the pixels are + * going to be expanded, in which case leave it for later + */ + int i, istop = png_ptr->num_trans; + + for (i=0; itrans_alpha[i] = (png_byte)(255 - + png_ptr->trans_alpha[i]); + } + } +#endif /* PNG_READ_INVERT_ALPHA_SUPPORTED */ + } + } /* background expand and (therefore) no alpha association. */ +#endif /* PNG_READ_EXPAND_SUPPORTED && PNG_READ_BACKGROUND_SUPPORTED */ +} + +static void /* PRIVATE */ +png_init_rgb_transformations(png_structp png_ptr) +{ + /* Added to libpng-1.5.3: check the color type to determine whether there + * is any alpha or transparency in the image and simply cancel the + * background and alpha mode stuff if there isn't. + */ + int input_has_alpha = (png_ptr->color_type & PNG_COLOR_MASK_ALPHA) != 0; + int input_has_transparency = png_ptr->num_trans > 0; + + /* If no alpha we can optimize. */ + if (!input_has_alpha) { - png_ptr->mode |= PNG_BACKGROUND_IS_GRAY; - png_ptr->background.gray = png_ptr->background.red; + /* Any alpha means background and associative alpha processing is + * required, however if the alpha is 0 or 1 throughout OPTIIMIZE_ALPHA + * and ENCODE_ALPHA are irrelevant. + */ +# ifdef PNG_READ_ALPHA_MODE_SUPPORTED + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; +# endif + + if (!input_has_transparency) + png_ptr->transformations &= ~(PNG_COMPOSE | PNG_BACKGROUND_EXPAND); } -#endif +#if defined(PNG_READ_EXPAND_SUPPORTED) && defined(PNG_READ_BACKGROUND_SUPPORTED) + /* png_set_background handling - deals with the complexity of whether the + * background color is in the file format or the screen format in the case + * where an 'expand' will happen. + */ + + /* The following code cannot be entered in the alpha pre-multiplication case + * because PNG_BACKGROUND_EXPAND is cancelled below. + */ if ((png_ptr->transformations & PNG_BACKGROUND_EXPAND) && - (png_ptr->transformations & PNG_EXPAND)) + (png_ptr->transformations & PNG_EXPAND) && + !(png_ptr->color_type & PNG_COLOR_MASK_COLOR)) + /* i.e., GRAY or GRAY_ALPHA */ { - if (!(color_type & PNG_COLOR_MASK_COLOR)) /* i.e., GRAY or GRAY_ALPHA */ { /* Expand background and tRNS chunks */ switch (png_ptr->bit_depth) @@ -924,68 +1175,257 @@ png_init_read_transformations(png_structp png_ptr) break; } } - else if (color_type == PNG_COLOR_TYPE_PALETTE) - { - png_ptr->background.red = - png_ptr->palette[png_ptr->background.index].red; - png_ptr->background.green = - png_ptr->palette[png_ptr->background.index].green; - png_ptr->background.blue = - png_ptr->palette[png_ptr->background.index].blue; + } /* background expand and (therefore) no alpha association. */ +#endif /* PNG_READ_EXPAND_SUPPORTED && PNG_READ_BACKGROUND_SUPPORTED */ +} -#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED - if (png_ptr->transformations & PNG_INVERT_ALPHA) - { -#ifdef PNG_READ_EXPAND_SUPPORTED - if (!(png_ptr->transformations & PNG_EXPAND_tRNS)) -#endif - { - /* Invert the alpha channel (in tRNS) unless the pixels are - * going to be expanded, in which case leave it for later - */ - int i, istop; - istop=(int)png_ptr->num_trans; - for (i=0; itrans_alpha[i] = (png_byte)(255 - - png_ptr->trans_alpha[i]); - } - } -#endif +void /* PRIVATE */ +png_init_read_transformations(png_structp png_ptr) +{ + png_debug(1, "in png_init_read_transformations"); + + /* This internal function is called from png_read_start_row in pngrutil.c + * and it is called before the 'rowbytes' calculation is done, so the code + * in here can change or update the transformations flags. + * + * First do updates that do not depend on the details of the PNG image data + * being processed. + */ + +#ifdef PNG_READ_GAMMA_SUPPORTED + /* Prior to 1.5.3 these tests were performed from png_set_gamma, 1.5.3 adds + * png_set_alpha_mode and this is another source for a default file gamma so + * the test needs to be performed later - here. In addition prior to 1.5.3 + * the tests were repeated for the PALETTE color type here - this is no + * longer necessary (and doesn't seem to have been necessary before.) + */ + { + /* The following temporary indicates if overall gamma correction is + * required. + */ + int gamma_correction = 0; + if (png_ptr->gamma != 0) /* has been set */ + { + if (png_ptr->screen_gamma != 0) /* screen set too */ + gamma_correction = png_gamma_threshold(png_ptr->gamma, + png_ptr->screen_gamma); + + else + /* Assume the output matches the input; a long time default behavior + * of libpng, although the standard has nothing to say about this. + */ + png_ptr->screen_gamma = png_reciprocal(png_ptr->gamma); } + + else if (png_ptr->screen_gamma != 0) + /* The converse - assume the file matches the screen, note that this + * perhaps undesireable default can (from 1.5.3) be changed by calling + * png_set_alpha_mode (even if the alpha handling mode isn't required + * or isn't changed from the default.) + */ + png_ptr->gamma = png_reciprocal(png_ptr->screen_gamma); + + else /* neither are set */ + /* Just in case the following prevents any processing - file and screen + * are both assumed to be linear and there is no way to introduce a + * third gamma value other than png_set_background with 'UNIQUE', and, + * prior to 1.5.3 + */ + png_ptr->screen_gamma = png_ptr->gamma = PNG_FP_1; + + /* Now turn the gamma transformation on or off as appropriate. Notice + * that PNG_GAMMA just refers to the file->screen correction. Alpha + * composition may independently cause gamma correction because it needs + * linear data (e.g. if the file has a gAMA chunk but the screen gamma + * hasn't been specified.) In any case this flag may get turned off in + * the code immediately below if the transform can be handled outside the + * row loop. + */ + if (gamma_correction) + png_ptr->transformations |= PNG_GAMMA; + + else + png_ptr->transformations &= ~PNG_GAMMA; } #endif -#if defined(PNG_READ_BACKGROUND_SUPPORTED) && defined(PNG_READ_GAMMA_SUPPORTED) - png_ptr->background_1 = png_ptr->background; + /* Certain transformations have the effect of preventing other + * transformations that happen afterward in png_do_read_transformations, + * resolve the interdependencies here. From the code of + * png_do_read_transformations the order is: + * + * 1) PNG_EXPAND (including PNG_EXPAND_tRNS) + * 2) PNG_STRIP_ALPHA (if no compose) + * 3) PNG_RGB_TO_GRAY + * 4) PNG_GRAY_TO_RGB iff !PNG_BACKGROUND_IS_GRAY + * 5) PNG_COMPOSE + * 6) PNG_GAMMA + * 7) PNG_STRIP_ALPHA (if compose) + * 8) PNG_ENCODE_ALPHA + * 9) PNG_16_TO_8 (strip16) + * 10) PNG_QUANTIZE (converts to palette) + * 11) PNG_EXPAND_16 [NOTE: temporarily moved to (3) for accuracy!] + * 12) PNG_GRAY_TO_RGB iff PNG_BACKGROUND_IS_GRAY + * 13) PNG_INVERT_MONO + * 14) PNG_SHIFT + * 15) PNG_PACK + * 16) PNG_BGR + * 17) PNG_PACKSWAP + * 18) PNG_FILLER (includes PNG_ADD_ALPHA) + * 19) PNG_INVERT_ALPHA + * 20) PNG_SWAP_ALPHA + * 21) PNG_SWAP_BYTES + * 22) PNG_USER_TRANSFORM [must be last] + */ +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED + if ((png_ptr->transformations & PNG_STRIP_ALPHA) && + !(png_ptr->transformations & PNG_COMPOSE)) + { + /* Stripping the alpha channel happens immediately after the 'expand' + * transformations, before all other transformation, so it cancels out + * the alpha handling. It has the side effect negating the effect of + * PNG_EXPAND_tRNS too: + */ + png_ptr->transformations &= ~(PNG_BACKGROUND_EXPAND | PNG_ENCODE_ALPHA | + PNG_EXPAND_tRNS); + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + + /* Kill the tRNS chunk itself too. Prior to 1.5.3 this did not happen + * so transparency information would remain just so long as it wasn't + * expanded. This produces unexpected API changes if the set of things + * that do PNG_EXPAND_tRNS changes (perfectly possible given the + * documentation - which says ask for what you want, accept what you + * get.) This makes the behavior consistent from 1.5.3: + */ + png_ptr->num_trans = 0; + } +#endif /* STRIP_ALPHA supported, no COMPOSE */ + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED + /* If the screen gamma is about 1.0 then the OPTIMIZE_ALPHA and ENCODE_ALPHA + * settings will have no effect. + */ + if (!png_gamma_significant(png_ptr->screen_gamma)) + { + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + } #endif -#ifdef PNG_READ_GAMMA_SUPPORTED - if ((color_type == PNG_COLOR_TYPE_PALETTE && png_ptr->num_trans != 0) - && png_gamma_threshold(png_ptr->screen_gamma, png_ptr->gamma)) +#if defined(PNG_READ_EXPAND_SUPPORTED) && \ + defined(PNG_READ_BACKGROUND_SUPPORTED) && \ + defined(PNG_READ_GRAY_TO_RGB_SUPPORTED) + /* Detect gray background and attempt to enable optimization for + * gray --> RGB case. + * + * Note: if PNG_BACKGROUND_EXPAND is set and color_type is either RGB or + * RGB_ALPHA (in which case need_expand is superfluous anyway), the + * background color might actually be gray yet not be flagged as such. + * This is not a problem for the current code, which uses + * PNG_BACKGROUND_IS_GRAY only to decide when to do the + * png_do_gray_to_rgb() transformation. + * + * NOTE: this code needs to be revised to avoid the complexity and + * interdependencies. The color type of the background should be recorded in + * png_set_background, along with the bit depth, then the code has a record + * of exactly what color space the background is currently in. + */ + if (png_ptr->transformations & PNG_BACKGROUND_EXPAND) { - int i, k; - k=0; - for (i=0; inum_trans; i++) + /* PNG_BACKGROUND_EXPAND: the background is in the file color space, so if + * the file was greyscale the background value is gray. + */ + if (!(png_ptr->color_type & PNG_COLOR_MASK_COLOR)) + png_ptr->mode |= PNG_BACKGROUND_IS_GRAY; + } + + else if (png_ptr->transformations & PNG_COMPOSE) + { + /* PNG_COMPOSE: png_set_background was called with need_expand false, + * so the color is in the color space of the output or png_set_alpha_mode + * was called and the color is black. Ignore RGB_TO_GRAY because that + * happens before GRAY_TO_RGB. + */ + if (png_ptr->transformations & PNG_GRAY_TO_RGB) { - if (png_ptr->trans_alpha[i] != 0 && png_ptr->trans_alpha[i] != 0xff) - k=1; /* Partial transparency is present */ + if (png_ptr->background.red == png_ptr->background.green && + png_ptr->background.red == png_ptr->background.blue) + { + png_ptr->mode |= PNG_BACKGROUND_IS_GRAY; + png_ptr->background.gray = png_ptr->background.red; + } } - if (k == 0) - png_ptr->transformations &= ~PNG_GAMMA; } +#endif /* PNG_READ_GRAY_TO_RGB_SUPPORTED (etc) */ - if ((png_ptr->transformations & (PNG_GAMMA | PNG_RGB_TO_GRAY)) && - png_ptr->gamma != 0) + /* For indexed PNG data (PNG_COLOR_TYPE_PALETTE) many of the transformations + * can be performed directly on the palette, and some (such as rgb to gray) + * can be optimized inside the palette. This is particularly true of the + * composite (background and alpha) stuff, which can be pretty much all done + * in the palette even if the result is expanded to RGB or gray afterward. + * + * NOTE: this is Not Yet Implemented, the code behaves as in 1.5.1 and + * earlier and the palette stuff is actually handled on the first row. This + * leads to the reported bug that the palette returned by png_get_PLTE is not + * updated. + */ + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + png_init_palette_transformations(png_ptr); + + else + png_init_rgb_transformations(png_ptr); + + /* NOTE: below 'PNG_READ_ALPHA_MODE_SUPPORTED' is presumed to also enable the + * background support (see the comments in scripts/pnglibconf.dfa), this + * allows pre-multiplication of the alpha channel to be implemented as + * compositing on black. This is probably sub-optimal and has been done in + * 1.5.3 betas simply to enable external critique and testing (i.e. to + * implement the new API quickly, without lots of internal changes.) + */ + +#ifdef PNG_READ_GAMMA_SUPPORTED +# ifdef PNG_READ_BACKGROUND_SUPPORTED + /* Includes ALPHA_MODE */ + png_ptr->background_1 = png_ptr->background; +# endif + + /* This needs to change - in the palette image case a whole set of tables are + * built when it would be quicker to just calculate the correct value for + * each palette entry directly. Also, the test is too tricky - why check + * PNG_RGB_TO_GRAY if PNG_GAMMA is not set? The answer seems to be that + * PNG_GAMMA is cancelled even if the gamma is known? The test excludes the + * PNG_COMPOSE case, so apparently if there is no *overall* gamma correction + * the gamma tables will not be built even if composition is required on a + * gamma encoded value. + * + * In 1.5.3 this is addressed below by an additional check on the individual + * file gamma - if it is not 1.0 both RGB_TO_GRAY and COMPOSE need the + * tables. + */ + if ((png_ptr->transformations & PNG_GAMMA) + || ((png_ptr->transformations & PNG_RGB_TO_GRAY) + && (png_gamma_significant(png_ptr->gamma) || + png_gamma_significant(png_ptr->screen_gamma))) + || ((png_ptr->transformations & PNG_COMPOSE) + && (png_gamma_significant(png_ptr->gamma) + || png_gamma_significant(png_ptr->screen_gamma) + || (png_ptr->background_gamma_type == PNG_BACKGROUND_GAMMA_UNIQUE + && png_gamma_significant(png_ptr->background_gamma)))) + || ((png_ptr->transformations & PNG_ENCODE_ALPHA) + && png_gamma_significant(png_ptr->screen_gamma)) + ) { png_build_gamma_table(png_ptr, png_ptr->bit_depth); #ifdef PNG_READ_BACKGROUND_SUPPORTED - if (png_ptr->transformations & PNG_BACKGROUND) + if (png_ptr->transformations & PNG_COMPOSE) { - if (color_type == PNG_COLOR_TYPE_PALETTE) + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) { - /* Could skip if no transparency */ + /* We don't get to here unless there is a tRNS chunk with non-opaque + * entries - see the checking code at the start of this function. + */ png_color back, back_1; png_colorp palette = png_ptr->palette; int num_palette = png_ptr->num_palette; @@ -1030,27 +1470,40 @@ png_init_read_transformations(png_structp png_ptr) } if (png_gamma_significant(gs)) + { + back.red = png_gamma_8bit_correct(png_ptr->background.red, + gs); + back.green = png_gamma_8bit_correct(png_ptr->background.green, + gs); + back.blue = png_gamma_8bit_correct(png_ptr->background.blue, + gs); + } + + else { back.red = (png_byte)png_ptr->background.red; back.green = (png_byte)png_ptr->background.green; back.blue = (png_byte)png_ptr->background.blue; } + if (png_gamma_significant(g)) + { + back_1.red = png_gamma_8bit_correct(png_ptr->background.red, + g); + back_1.green = png_gamma_8bit_correct( + png_ptr->background.green, g); + back_1.blue = png_gamma_8bit_correct(png_ptr->background.blue, + g); + } + else { - back.red = png_gamma_8bit_correct(png_ptr->background.red, - gs); - back.green = png_gamma_8bit_correct(png_ptr->background.green, - gs); - back.blue = png_gamma_8bit_correct(png_ptr->background.blue, - gs); + back_1.red = (png_byte)png_ptr->background.red; + back_1.green = (png_byte)png_ptr->background.green; + back_1.blue = (png_byte)png_ptr->background.blue; } - back_1.red = png_gamma_8bit_correct(png_ptr->background.red, g); - back_1.green = png_gamma_8bit_correct(png_ptr->background.green, - g); - back_1.blue = png_gamma_8bit_correct(png_ptr->background.blue, - g); } + for (i = 0; i < num_palette; i++) { if (i < (int)png_ptr->num_trans && @@ -1084,19 +1537,18 @@ png_init_read_transformations(png_structp png_ptr) palette[i].blue = png_ptr->gamma_table[palette[i].blue]; } } - /* Prevent the transformations being done again, and make sure - * that the now spurious alpha channel is stripped - the code - * has just reduced background composition and gamma correction - * to a simple alpha channel strip. + + /* Prevent the transformations being done again. + * + * NOTE: this is highly dubious, it zaps the transformations in + * place. This seems inconsistent with the general treatment of the + * transformations elsewhere. */ - png_ptr->transformations &= ~PNG_BACKGROUND; - png_ptr->transformations &= ~PNG_GAMMA; - png_ptr->transformations |= PNG_STRIP_ALPHA; - } + png_ptr->transformations &= ~(PNG_COMPOSE | PNG_GAMMA); + } /* color_type == PNG_COLOR_TYPE_PALETTE */ /* if (png_ptr->background_gamma_type!=PNG_BACKGROUND_GAMMA_UNKNOWN) */ - else - /* color_type != PNG_COLOR_TYPE_PALETTE */ + else /* color_type != PNG_COLOR_TYPE_PALETTE */ { png_fixed_point g = PNG_FP_1; png_fixed_point gs = PNG_FP_1; @@ -1162,17 +1614,21 @@ png_init_read_transformations(png_structp png_ptr) png_ptr->background.red = png_ptr->background.green = png_ptr->background.blue = png_ptr->background.gray; } - } - } + } /* color_type != PNG_COLOR_TYPE_PALETTE */ + }/* png_ptr->transformations & PNG_BACKGROUND */ + else /* Transformation does not include PNG_BACKGROUND */ #endif /* PNG_READ_BACKGROUND_SUPPORTED */ - if (color_type == PNG_COLOR_TYPE_PALETTE) + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) { png_colorp palette = png_ptr->palette; int num_palette = png_ptr->num_palette; int i; + /*NOTE: there are other transformations that should probably be in here + * too. + */ for (i = 0; i < num_palette; i++) { palette[i].red = png_ptr->gamma_table[palette[i].red]; @@ -1182,16 +1638,17 @@ png_init_read_transformations(png_structp png_ptr) /* Done the gamma correction. */ png_ptr->transformations &= ~PNG_GAMMA; - } + } /* color_type == PALETTE && !PNG_BACKGROUND transformation */ } #ifdef PNG_READ_BACKGROUND_SUPPORTED else #endif #endif /* PNG_READ_GAMMA_SUPPORTED */ + #ifdef PNG_READ_BACKGROUND_SUPPORTED - /* No GAMMA transformation */ - if ((png_ptr->transformations & PNG_BACKGROUND) && - (color_type == PNG_COLOR_TYPE_PALETTE)) + /* No GAMMA transformation (see the hanging else 4 lines above) */ + if ((png_ptr->transformations & PNG_COMPOSE) && + (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)) { int i; int istop = (int)png_ptr->num_trans; @@ -1223,15 +1680,13 @@ png_init_read_transformations(png_structp png_ptr) } } - /* Handled alpha, still need to strip the channel. */ - png_ptr->transformations &= ~PNG_BACKGROUND; - png_ptr->transformations |= PNG_STRIP_ALPHA; + png_ptr->transformations &= ~PNG_COMPOSE; } #endif /* PNG_READ_BACKGROUND_SUPPORTED */ #ifdef PNG_READ_SHIFT_SUPPORTED if ((png_ptr->transformations & PNG_SHIFT) && - (color_type == PNG_COLOR_TYPE_PALETTE)) + (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)) { png_uint_16 i; png_uint_16 istop = png_ptr->num_palette; @@ -1256,12 +1711,6 @@ png_init_read_transformations(png_structp png_ptr) } } #endif /* PNG_READ_SHIFT_SUPPORTED */ - } -#if !defined(PNG_READ_GAMMA_SUPPORTED) && !defined(PNG_READ_SHIFT_SUPPORTED) \ - && !defined(PNG_READ_BACKGROUND_SUPPORTED) - if (png_ptr) - return; -#endif } /* Modify the info structure to reflect the transformations. The @@ -1311,21 +1760,22 @@ png_read_transform_info(png_structp png_ptr, png_infop info_ptr) } #endif -#ifdef PNG_READ_BACKGROUND_SUPPORTED - if (png_ptr->transformations & PNG_BACKGROUND) - { - info_ptr->color_type = (png_byte)(info_ptr->color_type & - ~PNG_COLOR_MASK_ALPHA); - info_ptr->num_trans = 0; +#if defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) + /* The following is almost certainly wrong unless the background value is in + * the screen space! + */ + if (png_ptr->transformations & PNG_COMPOSE) info_ptr->background = png_ptr->background; - } #endif #ifdef PNG_READ_GAMMA_SUPPORTED - if (png_ptr->transformations & PNG_GAMMA) - { - info_ptr->gamma = png_ptr->gamma; - } + /* The following used to be conditional on PNG_GAMMA (prior to 1.5.3), + * however it seems that the code in png_init_read_transformations, which has + * been called before this from png_read_update_info->png_read_start_row + * sometimes does the gamma transform and cancels the flag. + */ + info_ptr->gamma = png_ptr->gamma; #endif #ifdef PNG_READ_16_TO_8_SUPPORTED @@ -1380,7 +1830,10 @@ png_read_transform_info(png_structp png_ptr, png_infop info_ptr) #ifdef PNG_READ_STRIP_ALPHA_SUPPORTED if (png_ptr->transformations & PNG_STRIP_ALPHA) + { info_ptr->color_type &= ~PNG_COLOR_MASK_ALPHA; + info_ptr->num_trans = 0; + } #endif if (info_ptr->color_type & PNG_COLOR_MASK_ALPHA) @@ -1439,18 +1892,21 @@ png_do_read_transformations(png_structp png_ptr) */ png_error(png_ptr, "NULL row buffer"); } -#ifdef PNG_WARN_UNINITIALIZED_ROW - if (!(png_ptr->flags & PNG_FLAG_ROW_INIT)) - /* Application has failed to call either png_read_start_image() - * or png_read_update_info() after setting transforms that expand - * pixels. This check added to libpng-1.2.19 + + /* The following is debugging, prior to 1.5.3 the code was never compiled in, + * in 1.5.3 PNG_FLAG_DETECT_UNINITIALIZED was added and the macro + * PNG_WARN_UNINITIALIZED_ROW removed. In 1.5 the new flag is set only for + * selected new APIs to ensure that there is no API change. + */ + if ((png_ptr->flags & PNG_FLAG_DETECT_UNINITIALIZED) != 0 && + !(png_ptr->flags & PNG_FLAG_ROW_INIT)) + { + /* Application has failed to call either png_read_start_image() or + * png_read_update_info() after setting transforms that expand pixels. + * This check added to libpng-1.2.19 (but not enabled until 1.5.3). */ -#if (PNG_WARN_UNINITIALIZED_ROW==1) png_error(png_ptr, "Uninitialized row"); -#else - png_warning(png_ptr, "Uninitialized row"); -#endif -#endif + } #ifdef PNG_READ_EXPAND_SUPPORTED if (png_ptr->transformations & PNG_EXPAND) @@ -1475,12 +1931,22 @@ png_do_read_transformations(png_structp png_ptr) } #endif - /* Delay the 'expand 16' step until later for efficiency, so that the + /* TODO: Delay the 'expand 16' step until later for efficiency, so that the * intermediate steps work with 8 bit data. */ +#ifdef PNG_READ_EXPAND_16_SUPPORTED + /* Do the expansion now, after all the arithmetic has been done. Notice + * that previous transformations can handle the PNG_EXPAND_16 flag if this + * is efficient (particularly true in the case of gamma correction, where + * better accuracy results faster!) + */ + if (png_ptr->transformations & PNG_EXPAND_16) + png_do_expand_16(&png_ptr->row_info, png_ptr->row_buf + 1); +#endif #ifdef PNG_READ_STRIP_ALPHA_SUPPORTED if ((png_ptr->transformations & PNG_STRIP_ALPHA) && + !(png_ptr->transformations & PNG_COMPOSE) && (png_ptr->row_info.color_type == PNG_COLOR_TYPE_RGB_ALPHA || png_ptr->row_info.color_type == PNG_COLOR_TYPE_GRAY_ALPHA)) png_do_strip_channel(&(png_ptr->row_info), png_ptr->row_buf + 1, @@ -1548,17 +2014,17 @@ png_do_read_transformations(png_structp png_ptr) png_do_gray_to_rgb(&(png_ptr->row_info), png_ptr->row_buf + 1); #endif -#ifdef PNG_READ_BACKGROUND_SUPPORTED - if ((png_ptr->transformations & PNG_BACKGROUND) && - ((png_ptr->num_trans != 0) || - (png_ptr->color_type & PNG_COLOR_MASK_ALPHA))) - png_do_background(&(png_ptr->row_info), png_ptr->row_buf + 1, png_ptr); +#if (defined PNG_READ_BACKGROUND_SUPPORTED) ||\ + (defined PNG_READ_ALPHA_MODE_SUPPORTED) + if (png_ptr->transformations & PNG_COMPOSE) + png_do_compose(&(png_ptr->row_info), png_ptr->row_buf + 1, png_ptr); #endif #ifdef PNG_READ_GAMMA_SUPPORTED if ((png_ptr->transformations & PNG_GAMMA) && -#ifdef PNG_READ_BACKGROUND_SUPPORTED - !((png_ptr->transformations & PNG_BACKGROUND) && +#if (defined PNG_READ_BACKGROUND_SUPPORTED) ||\ + (defined PNG_READ_ALPHA_MODE_SUPPORTED) + !((png_ptr->transformations & PNG_COMPOSE) && ((png_ptr->num_trans != 0) || (png_ptr->color_type & PNG_COLOR_MASK_ALPHA))) && #endif @@ -1566,6 +2032,21 @@ png_do_read_transformations(png_structp png_ptr) png_do_gamma(&(png_ptr->row_info), png_ptr->row_buf + 1, png_ptr); #endif +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED + if ((png_ptr->transformations & PNG_STRIP_ALPHA) && + (png_ptr->transformations & PNG_COMPOSE) && + (png_ptr->row_info.color_type == PNG_COLOR_TYPE_RGB_ALPHA || + png_ptr->row_info.color_type == PNG_COLOR_TYPE_GRAY_ALPHA)) + png_do_strip_channel(&(png_ptr->row_info), png_ptr->row_buf + 1, + 0 /* at_start == false, because SWAP_ALPHA happens later */); +#endif + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED + if ((png_ptr->transformations & PNG_ENCODE_ALPHA) && + (png_ptr->row_info.color_type & PNG_COLOR_MASK_ALPHA)) + png_do_encode_alpha(&(png_ptr->row_info), png_ptr->row_buf + 1, png_ptr); +#endif + #ifdef PNG_READ_16_TO_8_SUPPORTED if (png_ptr->transformations & PNG_16_TO_8) png_do_chop(&(png_ptr->row_info), png_ptr->row_buf + 1); @@ -1582,6 +2063,11 @@ png_do_read_transformations(png_structp png_ptr) } #endif /* PNG_READ_QUANTIZE_SUPPORTED */ +#if 0 + /* This is where this code *should* be for efficiency, but that requires all + * the inaccurate calculations above to output 16 bit values if expand_16 is + * set! + */ #ifdef PNG_READ_EXPAND_16_SUPPORTED /* Do the expansion now, after all the arithmetic has been done. Notice * that previous transformations can handle the PNG_EXPAND_16 flag if this @@ -1591,6 +2077,14 @@ png_do_read_transformations(png_structp png_ptr) if (png_ptr->transformations & PNG_EXPAND_16) png_do_expand_16(&png_ptr->row_info, png_ptr->row_buf + 1); #endif +#endif /*commented out*/ + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED + /*NOTE: moved here in 1.5.3 (from much later in this list.) */ + if ((png_ptr->transformations & PNG_GRAY_TO_RGB) && + (png_ptr->mode & PNG_BACKGROUND_IS_GRAY)) + png_do_gray_to_rgb(&(png_ptr->row_info), png_ptr->row_buf + 1); +#endif #ifdef PNG_READ_INVERT_SUPPORTED if (png_ptr->transformations & PNG_INVERT_MONO) @@ -1618,16 +2112,6 @@ png_do_read_transformations(png_structp png_ptr) png_do_packswap(&(png_ptr->row_info), png_ptr->row_buf + 1); #endif -#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED - /*NOTE: this must be in the wrong place - what happens if BGR is set too? - * Need pngvalid to test this combo. - */ - /* If gray -> RGB, do so now only if we did not do so above */ - if ((png_ptr->transformations & PNG_GRAY_TO_RGB) && - (png_ptr->mode & PNG_BACKGROUND_IS_GRAY)) - png_do_gray_to_rgb(&(png_ptr->row_info), png_ptr->row_buf + 1); -#endif - #ifdef PNG_READ_FILLER_SUPPORTED if (png_ptr->transformations & PNG_FILLER) png_do_read_filler(&(png_ptr->row_info), png_ptr->row_buf + 1, @@ -1679,7 +2163,6 @@ png_do_read_transformations(png_structp png_ptr) png_ptr->row_info.width); } #endif - } #ifdef PNG_READ_PACK_SUPPORTED @@ -2769,7 +3252,7 @@ png_build_grayscale_palette(int bit_depth, png_colorp palette) * at a gamma of 1.0. Paletted files have already been taken care of. */ void /* PRIVATE */ -png_do_background(png_row_infop row_info, png_bytep row, png_structp png_ptr) +png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) { #ifdef PNG_READ_GAMMA_SUPPORTED png_const_bytep gamma_table = png_ptr->gamma_table; @@ -2781,15 +3264,14 @@ png_do_background(png_row_infop row_info, png_bytep row, png_structp png_ptr) int gamma_shift = png_ptr->gamma_shift; #endif - png_bytep sp, dp; + png_bytep sp; png_uint_32 i; png_uint_32 row_width = row_info->width; + int optimize = (png_ptr->flags & PNG_FLAG_OPTIMIZE_ALPHA) != 0; int shift; - png_debug(1, "in png_do_background"); + png_debug(1, "in png_do_compose"); - if (!(row_info->color_type & PNG_COLOR_MASK_ALPHA) || - row_info->color_type != PNG_COLOR_TYPE_PALETTE) { switch (row_info->color_type) { @@ -3159,18 +3641,17 @@ png_do_background(png_row_infop row_info, png_bytep row, png_structp png_ptr) gamma_table != NULL) { sp = row; - dp = row; - for (i = 0; i < row_width; i++, sp += 2, dp++) + for (i = 0; i < row_width; i++, sp += 2) { png_uint_16 a = *(sp + 1); if (a == 0xff) - *dp = gamma_table[*sp]; + *sp = gamma_table[*sp]; else if (a == 0) { /* Background is already in screen gamma */ - *dp = (png_byte)png_ptr->background.gray; + *sp = (png_byte)png_ptr->background.gray; } else @@ -3179,7 +3660,9 @@ png_do_background(png_row_infop row_info, png_bytep row, png_structp png_ptr) v = gamma_to_1[*sp]; png_composite(w, v, a, png_ptr->background_1.gray); - *dp = gamma_from_1[w]; + if (!optimize) + w = gamma_from_1[w]; + *sp = w; } } } @@ -3187,24 +3670,15 @@ png_do_background(png_row_infop row_info, png_bytep row, png_structp png_ptr) #endif { sp = row; - dp = row; - for (i = 0; i < row_width; i++, sp += 2, dp++) + for (i = 0; i < row_width; i++, sp += 2) { png_byte a = *(sp + 1); - if (a == 0xff) - *dp = *sp; - -#ifdef PNG_READ_GAMMA_SUPPORTED - else if (a == 0) - *dp = (png_byte)png_ptr->background.gray; - - else - png_composite(*dp, *sp, a, png_ptr->background_1.gray); + if (a == 0) + *sp = (png_byte)png_ptr->background.gray; -#else - *dp = (png_byte)png_ptr->background.gray; -#endif + else if (a < 0xff) + png_composite(*sp, *sp, a, png_ptr->background_1.gray); } } } @@ -3215,8 +3689,7 @@ png_do_background(png_row_infop row_info, png_bytep row, png_structp png_ptr) gamma_16_to_1 != NULL) { sp = row; - dp = row; - for (i = 0; i < row_width; i++, sp += 4, dp += 2) + for (i = 0; i < row_width; i++, sp += 4) { png_uint_16 a = (png_uint_16)(((*(sp + 2)) << 8) + *(sp + 3)); @@ -3226,69 +3699,56 @@ png_do_background(png_row_infop row_info, png_bytep row, png_structp png_ptr) png_uint_16 v; v = gamma_16[*(sp + 1) >> gamma_shift][*sp]; - *dp = (png_byte)((v >> 8) & 0xff); - *(dp + 1) = (png_byte)(v & 0xff); + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); } -#ifdef PNG_READ_GAMMA_SUPPORTED else if (a == 0) -#else - else -#endif { /* Background is already in screen gamma */ - *dp = (png_byte)((png_ptr->background.gray >> 8) & 0xff); - *(dp + 1) = (png_byte)(png_ptr->background.gray & 0xff); + *sp = (png_byte)((png_ptr->background.gray >> 8) & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.gray & 0xff); } -#ifdef PNG_READ_GAMMA_SUPPORTED else { png_uint_16 g, v, w; g = gamma_16_to_1[*(sp + 1) >> gamma_shift][*sp]; png_composite_16(v, g, a, png_ptr->background_1.gray); - w = gamma_16_from_1[(v&0xff) >> gamma_shift][v >> 8]; - *dp = (png_byte)((w >> 8) & 0xff); - *(dp + 1) = (png_byte)(w & 0xff); + if (optimize) + w = v; + else + w = gamma_16_from_1[(v&0xff) >> gamma_shift][v >> 8]; + *sp = (png_byte)((w >> 8) & 0xff); + *(sp + 1) = (png_byte)(w & 0xff); } -#endif } } else #endif { sp = row; - dp = row; - for (i = 0; i < row_width; i++, sp += 4, dp += 2) + for (i = 0; i < row_width; i++, sp += 4) { png_uint_16 a = (png_uint_16)(((*(sp + 2)) << 8) + *(sp + 3)); - if (a == (png_uint_16)0xffff) - png_memcpy(dp, sp, 2); - -#ifdef PNG_READ_GAMMA_SUPPORTED - else if (a == 0) -#else - else -#endif + if (a == 0) { - *dp = (png_byte)((png_ptr->background.gray >> 8) & 0xff); - *(dp + 1) = (png_byte)(png_ptr->background.gray & 0xff); + *sp = (png_byte)((png_ptr->background.gray >> 8) & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.gray & 0xff); } -#ifdef PNG_READ_GAMMA_SUPPORTED - else + else if (a < 0xffff) { png_uint_16 g, v; g = (png_uint_16)(((*sp) << 8) + *(sp + 1)); png_composite_16(v, g, a, png_ptr->background_1.gray); - *dp = (png_byte)((v >> 8) & 0xff); - *(dp + 1) = (png_byte)(v & 0xff); + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); } -#endif } } } @@ -3304,24 +3764,23 @@ png_do_background(png_row_infop row_info, png_bytep row, png_structp png_ptr) gamma_table != NULL) { sp = row; - dp = row; - for (i = 0; i < row_width; i++, sp += 4, dp += 3) + for (i = 0; i < row_width; i++, sp += 4) { png_byte a = *(sp + 3); if (a == 0xff) { - *dp = gamma_table[*sp]; - *(dp + 1) = gamma_table[*(sp + 1)]; - *(dp + 2) = gamma_table[*(sp + 2)]; + *sp = gamma_table[*sp]; + *(sp + 1) = gamma_table[*(sp + 1)]; + *(sp + 2) = gamma_table[*(sp + 2)]; } else if (a == 0) { /* Background is already in screen gamma */ - *dp = (png_byte)png_ptr->background.red; - *(dp + 1) = (png_byte)png_ptr->background.green; - *(dp + 2) = (png_byte)png_ptr->background.blue; + *sp = (png_byte)png_ptr->background.red; + *(sp + 1) = (png_byte)png_ptr->background.green; + *(sp + 2) = (png_byte)png_ptr->background.blue; } else @@ -3330,15 +3789,18 @@ png_do_background(png_row_infop row_info, png_bytep row, png_structp png_ptr) v = gamma_to_1[*sp]; png_composite(w, v, a, png_ptr->background_1.red); - *dp = gamma_from_1[w]; + if (!optimize) w = gamma_from_1[w]; + *sp = w; v = gamma_to_1[*(sp + 1)]; png_composite(w, v, a, png_ptr->background_1.green); - *(dp + 1) = gamma_from_1[w]; + if (!optimize) w = gamma_from_1[w]; + *(sp + 1) = w; v = gamma_to_1[*(sp + 2)]; png_composite(w, v, a, png_ptr->background_1.blue); - *(dp + 2) = gamma_from_1[w]; + if (!optimize) w = gamma_from_1[w]; + *(sp + 2) = w; } } } @@ -3346,33 +3808,25 @@ png_do_background(png_row_infop row_info, png_bytep row, png_structp png_ptr) #endif { sp = row; - dp = row; - for (i = 0; i < row_width; i++, sp += 4, dp += 3) + for (i = 0; i < row_width; i++, sp += 4) { png_byte a = *(sp + 3); - if (a == 0xff) - { - *dp = *sp; - *(dp + 1) = *(sp + 1); - *(dp + 2) = *(sp + 2); - } - - else if (a == 0) + if (a == 0) { - *dp = (png_byte)png_ptr->background.red; - *(dp + 1) = (png_byte)png_ptr->background.green; - *(dp + 2) = (png_byte)png_ptr->background.blue; + *sp = (png_byte)png_ptr->background.red; + *(sp + 1) = (png_byte)png_ptr->background.green; + *(sp + 2) = (png_byte)png_ptr->background.blue; } - else + else if (a < 0xff) { - png_composite(*dp, *sp, a, png_ptr->background.red); + png_composite(*sp, *sp, a, png_ptr->background.red); - png_composite(*(dp + 1), *(sp + 1), a, + png_composite(*(sp + 1), *(sp + 1), a, png_ptr->background.green); - png_composite(*(dp + 2), *(sp + 2), a, + png_composite(*(sp + 2), *(sp + 2), a, png_ptr->background.blue); } } @@ -3385,8 +3839,7 @@ png_do_background(png_row_infop row_info, png_bytep row, png_structp png_ptr) gamma_16_to_1 != NULL) { sp = row; - dp = row; - for (i = 0; i < row_width; i++, sp += 8, dp += 6) + for (i = 0; i < row_width; i++, sp += 8) { png_uint_16 a = (png_uint_16)(((png_uint_16)(*(sp + 6)) << 8) + (png_uint_16)(*(sp + 7))); @@ -3396,53 +3849,55 @@ png_do_background(png_row_infop row_info, png_bytep row, png_structp png_ptr) png_uint_16 v; v = gamma_16[*(sp + 1) >> gamma_shift][*sp]; - *dp = (png_byte)((v >> 8) & 0xff); - *(dp + 1) = (png_byte)(v & 0xff); + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); v = gamma_16[*(sp + 3) >> gamma_shift][*(sp + 2)]; - *(dp + 2) = (png_byte)((v >> 8) & 0xff); - *(dp + 3) = (png_byte)(v & 0xff); + *(sp + 2) = (png_byte)((v >> 8) & 0xff); + *(sp + 3) = (png_byte)(v & 0xff); v = gamma_16[*(sp + 5) >> gamma_shift][*(sp + 4)]; - *(dp + 4) = (png_byte)((v >> 8) & 0xff); - *(dp + 5) = (png_byte)(v & 0xff); + *(sp + 4) = (png_byte)((v >> 8) & 0xff); + *(sp + 5) = (png_byte)(v & 0xff); } else if (a == 0) { /* Background is already in screen gamma */ - *dp = (png_byte)((png_ptr->background.red >> 8) & 0xff); - *(dp + 1) = (png_byte)(png_ptr->background.red & 0xff); - *(dp + 2) = (png_byte)((png_ptr->background.green >> 8) & 0xff); - *(dp + 3) = (png_byte)(png_ptr->background.green & 0xff); - *(dp + 4) = (png_byte)((png_ptr->background.blue >> 8) & 0xff); - *(dp + 5) = (png_byte)(png_ptr->background.blue & 0xff); + *sp = (png_byte)((png_ptr->background.red >> 8) & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.red & 0xff); + *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) & 0xff); + *(sp + 3) = (png_byte)(png_ptr->background.green & 0xff); + *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) & 0xff); + *(sp + 5) = (png_byte)(png_ptr->background.blue & 0xff); } else { - png_uint_16 v, w, x; + png_uint_16 v, w; v = gamma_16_to_1[*(sp + 1) >> gamma_shift][*sp]; png_composite_16(w, v, a, png_ptr->background_1.red); - - x = gamma_16_from_1[((w&0xff) >> gamma_shift)][w >> 8]; - *dp = (png_byte)((x >> 8) & 0xff); - *(dp + 1) = (png_byte)(x & 0xff); + if (!optimize) + w = gamma_16_from_1[((w&0xff) >> gamma_shift)][w >> 8]; + *sp = (png_byte)((w >> 8) & 0xff); + *(sp + 1) = (png_byte)(w & 0xff); v = gamma_16_to_1[*(sp + 3) >> gamma_shift][*(sp + 2)]; png_composite_16(w, v, a, png_ptr->background_1.green); + if (!optimize) + w = gamma_16_from_1[((w&0xff) >> gamma_shift)][w >> 8]; - x = gamma_16_from_1[((w&0xff) >> gamma_shift)][w >> 8]; - *(dp + 2) = (png_byte)((x >> 8) & 0xff); - *(dp + 3) = (png_byte)(x & 0xff); + *(sp + 2) = (png_byte)((w >> 8) & 0xff); + *(sp + 3) = (png_byte)(w & 0xff); v = gamma_16_to_1[*(sp + 5) >> gamma_shift][*(sp + 4)]; png_composite_16(w, v, a, png_ptr->background_1.blue); + if (!optimize) + w = gamma_16_from_1[((w&0xff) >> gamma_shift)][w >> 8]; - x = gamma_16_from_1[(w & 0xff) >> gamma_shift][w >> 8]; - *(dp + 4) = (png_byte)((x >> 8) & 0xff); - *(dp + 5) = (png_byte)(x & 0xff); + *(sp + 4) = (png_byte)((w >> 8) & 0xff); + *(sp + 5) = (png_byte)(w & 0xff); } } } @@ -3451,28 +3906,22 @@ png_do_background(png_row_infop row_info, png_bytep row, png_structp png_ptr) #endif { sp = row; - dp = row; - for (i = 0; i < row_width; i++, sp += 8, dp += 6) + for (i = 0; i < row_width; i++, sp += 8) { png_uint_16 a = (png_uint_16)(((png_uint_16)(*(sp + 6)) << 8) + (png_uint_16)(*(sp + 7))); - if (a == (png_uint_16)0xffff) + if (a == 0) { - png_memcpy(dp, sp, 6); - } - - else if (a == 0) - { - *dp = (png_byte)((png_ptr->background.red >> 8) & 0xff); - *(dp + 1) = (png_byte)(png_ptr->background.red & 0xff); - *(dp + 2) = (png_byte)((png_ptr->background.green >> 8) & 0xff); - *(dp + 3) = (png_byte)(png_ptr->background.green & 0xff); - *(dp + 4) = (png_byte)((png_ptr->background.blue >> 8) & 0xff); - *(dp + 5) = (png_byte)(png_ptr->background.blue & 0xff); + *sp = (png_byte)((png_ptr->background.red >> 8) & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.red & 0xff); + *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) & 0xff); + *(sp + 3) = (png_byte)(png_ptr->background.green & 0xff); + *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) & 0xff); + *(sp + 5) = (png_byte)(png_ptr->background.blue & 0xff); } - else + else if (a < 0xffff) { png_uint_16 v; @@ -3483,16 +3932,16 @@ png_do_background(png_row_infop row_info, png_bytep row, png_structp png_ptr) + *(sp + 5)); png_composite_16(v, r, a, png_ptr->background.red); - *dp = (png_byte)((v >> 8) & 0xff); - *(dp + 1) = (png_byte)(v & 0xff); + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); png_composite_16(v, g, a, png_ptr->background.green); - *(dp + 2) = (png_byte)((v >> 8) & 0xff); - *(dp + 3) = (png_byte)(v & 0xff); + *(sp + 2) = (png_byte)((v >> 8) & 0xff); + *(sp + 3) = (png_byte)(v & 0xff); png_composite_16(v, b, a, png_ptr->background.blue); - *(dp + 4) = (png_byte)((v >> 8) & 0xff); - *(dp + 5) = (png_byte)(v & 0xff); + *(sp + 4) = (png_byte)((v >> 8) & 0xff); + *(sp + 5) = (png_byte)(v & 0xff); } } } @@ -3503,16 +3952,6 @@ png_do_background(png_row_infop row_info, png_bytep row, png_structp png_ptr) default: break; } - - if (row_info->color_type & PNG_COLOR_MASK_ALPHA) - { - row_info->color_type = (png_byte)(row_info->color_type & - ~PNG_COLOR_MASK_ALPHA); - row_info->channels--; - row_info->pixel_depth = (png_byte)(row_info->channels * - row_info->bit_depth); - row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width); - } } } #endif @@ -3720,6 +4159,73 @@ png_do_gamma(png_row_infop row_info, png_bytep row, png_structp png_ptr) } #endif +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED +/* Encode the alpha channel to the output gamma (the input channel is always + * linear.) Called only with color types that have an alpha channel. Needs the + * from_1 tables. + */ +void /* PRIVATE */ +png_do_encode_alpha(png_row_infop row_info, png_bytep row, png_structp png_ptr) +{ + png_uint_32 row_width = row_info->width; + + png_debug(1, "in png_do_encode_alpha"); + + if (row_info->color_type & PNG_COLOR_MASK_ALPHA) + { + if (row_info->bit_depth == 8) + { + PNG_CONST png_bytep table = png_ptr->gamma_from_1; + + if (table != NULL) + { + PNG_CONST int step = + (row_info->color_type & PNG_COLOR_MASK_COLOR) ? 4 : 2; + + /* The alpha channel is the last component: */ + row += step - 1; + + for (; row_width > 0; --row_width, row += step) + *row = table[*row]; + + return; + } + } + + else if (row_info->bit_depth == 16) + { + PNG_CONST png_uint_16pp table = png_ptr->gamma_16_from_1; + PNG_CONST int gamma_shift = png_ptr->gamma_shift; + + if (table != NULL) + { + PNG_CONST int step = + (row_info->color_type & PNG_COLOR_MASK_COLOR) ? 8 : 4; + + /* The alpha channel is the last component: */ + row += step - 2; + + for (; row_width > 0; --row_width, row += step) + { + png_uint_16 v; + + v = table[*(row + 1) >> gamma_shift][*row]; + *row = (png_byte)((v >> 8) & 0xff); + *(row + 1) = (png_byte)(v & 0xff); + } + + return; + } + } + } + + /* Only get to here if called with a weird row_info; no harm has been done, + * so just issue a warning. + */ + png_warning(png_ptr, "png_do_encode_alpha: unexpected call"); +} +#endif + #ifdef PNG_READ_EXPAND_SUPPORTED /* Expands a palette row to an RGB or RGBA row depending * upon whether you supply trans and num_trans. diff --git a/pngstruct.h b/pngstruct.h index e4683121445c6f2602d00c00571e8d973da81d85..1ae2459a3ab6389dd35f2c17282d4db4600d5e9e 100644 --- a/pngstruct.h +++ b/pngstruct.h @@ -135,7 +135,8 @@ struct png_struct_def png_uint_16 filler; /* filler bytes for pixel expansion */ #endif -#ifdef PNG_bKGD_SUPPORTED +#if defined(PNG_bKGD_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) png_byte background_gamma_type; png_fixed_point background_gamma; png_color_16 background; /* background color in screen gamma space */ diff --git a/pngvalid.c b/pngvalid.c index ccfaccfd32a414eaf13f0b5dbf102db7b964503f..75719f5bc5eff8f73e27c25dd2fd141e09450be4 100644 --- a/pngvalid.c +++ b/pngvalid.c @@ -48,9 +48,13 @@ typedef png_byte *png_const_bytep; #define PNG_COL_IN_INTERLACE_PASS(x, pass) \ ((PNG_PASS_MASK(pass,1) >> ((x)&7)) & 1) -/* These are needed too for the defualt build: */ +/* These are needed too for the default build: */ #define PNG_WRITE_16BIT_SUPPORTED #define PNG_READ_16BIT_SUPPORTED + +/* This comes from pnglibconf.h afer 1.5: */ +#define PNG_GAMMA_THRESHOLD_FIXED\ + ((png_fixed_point)(PNG_GAMMA_THRESHOLD * 100000)) #endif #include "zlib.h" /* For crc32 */ @@ -295,8 +299,9 @@ sample(png_const_bytep row, png_byte colour_type, png_byte bit_depth, if (colour_type & 4) bit_index += x; /* Alpha channel */ + /* Multiple channels; select one: */ if (colour_type & (2+4)) - bit_index += sample_index * bit_depth; /* Multiple channels: select one */ + bit_index += sample_index * bit_depth; } /* Return the sample from the row as an integer. */ @@ -1320,9 +1325,11 @@ typedef struct png_modifier */ double maxout8; /* Maximum output value error */ double maxabs8; /* Absolute sample error 0..1 */ + double maxcalc8; /* Absolute sample error 0..1 */ double maxpc8; /* Percentage sample error 0..100% */ double maxout16; /* Maximum output value error */ double maxabs16; /* Absolute sample error 0..1 */ + double maxcalc16;/* Absolute sample error 0..1 */ double maxpc16; /* Percentage sample error 0..100% */ /* Logged 8 and 16 bit errors ('output' values): */ @@ -1351,11 +1358,17 @@ typedef struct png_modifier unsigned int use_input_precision_sbit :1; unsigned int use_input_precision_16to8 :1; + /* Whether to allow for low bit depth composition errors: */ + unsigned int use_linear_precision :1; + /* Which gamma tests to run: */ 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 test_gamma_background :1; + unsigned int test_gamma_alpha_mode :1; + unsigned int test_gamma_expand16 :1; unsigned int log :1; /* Log max error */ @@ -1385,8 +1398,10 @@ modifier_init(png_modifier *pm) pm->modifications = NULL; pm->state = modifier_start; pm->sbitlow = 1U; - pm->maxout8 = pm->maxpc8 = pm->maxabs8 = 0; - pm->maxout16 = pm->maxpc16 = pm->maxabs16 = 0; + pm->ngammas = 0; + pm->gammas = 0; + pm->maxout8 = pm->maxpc8 = pm->maxabs8 = pm->maxcalc8 = 0; + pm->maxout16 = pm->maxpc16 = pm->maxabs16 = pm->maxcalc16 = 0; pm->error_gray_2 = pm->error_gray_4 = pm->error_gray_8 = 0; pm->error_gray_16 = pm->error_color_8 = pm->error_color_16 = 0; pm->interlace_type = PNG_INTERLACE_NONE; @@ -1396,10 +1411,14 @@ modifier_init(png_modifier *pm) pm->use_input_precision = 0; pm->use_input_precision_sbit = 0; pm->use_input_precision_16to8 = 0; + pm->use_linear_precision = 0; pm->test_gamma_threshold = 0; pm->test_gamma_transform = 0; pm->test_gamma_sbit = 0; pm->test_gamma_strip16 = 0; + pm->test_gamma_background = 0; + pm->test_gamma_alpha_mode = 0; + pm->test_gamma_expand16 = 0; pm->log = 0; /* Rely on the memset for all the other fields - there are no pointers */ @@ -1411,6 +1430,18 @@ static double abserr(png_modifier *pm, png_byte bit_depth) return bit_depth == 16 ? pm->maxabs16 : pm->maxabs8; } +static double calcerr(png_modifier *pm, png_byte bit_depth) +{ + /* This exists because libpng uses linear transformations in the original bit + * depth when performing background composition. This introduces significant + * errors for low values: + */ + if (pm->use_linear_precision) + return bit_depth == 16 ? pm->maxcalc16 : pm->maxcalc8; + else + return bit_depth == 16 ? pm->maxabs16 : pm->maxabs8; +} + static double pcerr(png_modifier *pm, png_byte bit_depth) { return (bit_depth == 16 ? pm->maxpc16 : pm->maxpc8) * .01; @@ -2123,6 +2154,30 @@ make_transform_image(png_store* PNG_CONST ps, png_byte PNG_CONST colour_type, bit_depth, colour_type, interlace_type, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); +#ifdef PNG_TEXT_SUPPORTED + { + static char key[] = "image name"; /* must be writeable */ + size_t pos; + png_text text; + char copy[FILE_NAME_SIZE]; + + /* Use a compressed text string to test the correct interaction of text + * compression and IDAT compression. + */ + text.compression = PNG_TEXT_COMPRESSION_zTXt; + text.key = key; + /* Yuck: the text must be writable! */ + pos = safecat(copy, sizeof copy, 0, ps->wname); + text.text = copy; + text.text_length = pos; + text.itxt_length = 0; + text.lang = 0; + text.lang_key = 0; + + png_set_text(pp, pi, &text, 1); + } +#endif + if (colour_type == 3) /* palette */ { unsigned int i = 0; @@ -2130,7 +2185,7 @@ make_transform_image(png_store* PNG_CONST ps, png_byte PNG_CONST colour_type, do pal[i].red = pal[i].green = pal[i].blue = (png_byte)i; - while(++i < 256U); + while(++i < 256); png_set_PLTE(pp, pi, pal, 256); } @@ -2167,6 +2222,27 @@ make_transform_image(png_store* PNG_CONST ps, png_byte PNG_CONST colour_type, } } +#ifdef PNG_TEXT_SUPPORTED + { + static char key[] = "end marker"; + static char comment[] = "end"; + png_text text; + + /* Use a compressed text string to test the correct interaction of text + * compression and IDAT compression. + */ + text.compression = PNG_TEXT_COMPRESSION_zTXt; + text.key = key; + text.text = comment; + text.text_length = (sizeof comment)-1; + text.itxt_length = 0; + text.lang = 0; + text.lang_key = 0; + + png_set_text(pp, pi, &text, 1); + } +#endif + png_write_end(pp, pi); /* And store this under the appropriate id, then clean up. */ @@ -4819,15 +4895,16 @@ image_transform_png_set_background_set(PNG_CONST image_transform *this, /* Since we don't know the output bit depth at this point we must use the * input values and ask libpng to expand the chunk as required. + * NOTE: the palette case isn't tested yet because there is no tRNS chunk! */ - back.index = 255; /* Should not be used */ + back.index = 128; /* The palette entry with rgb(128,128,128) */ back.gray = back.blue = back.green = back.red = (png_uint_16)((1U << that->this.bit_depth) >> 1); # ifdef PNG_FLOATING_POINT_SUPPORTED - png_set_background(pp, &back, PNG_BACKGROUND_GAMMA_FILE, 1, 0); + png_set_background(pp, &back, PNG_BACKGROUND_GAMMA_FILE, 1/*need expand*/, 0); # else - png_set_background_fixed(pp, &back, PNG_BACKGROUND_GAMMA_FILE, 1, 0); + png_set_background_fixed(pp, &back, PNG_BACKGROUND_GAMMA_FILE, 1/*need expand*/, 0); # endif this->next->set(this->next, that, pp, pi); @@ -5356,12 +5433,15 @@ typedef struct gamma_display png_modifier* pm; double file_gamma; double screen_gamma; + double background_gamma; png_byte sbit; int threshold_test; - PNG_CONST char* name; int speed; int use_input_precision; int strip16; + int expand16; + int do_background; + png_color_16 background_color; /* Local variables */ double maxerrout; @@ -5369,10 +5449,14 @@ typedef struct gamma_display double maxerrabs; } gamma_display; +#define ALPHA_MODE_OFFSET 4 + static void gamma_display_init(gamma_display *dp, png_modifier *pm, png_uint_32 id, double file_gamma, double screen_gamma, png_byte sbit, int threshold_test, - int speed, int use_input_precision, int strip16) + int speed, int use_input_precision, int strip16, int expand16, + int do_background, PNG_CONST png_color_16 *pointer_to_the_background_color, + double background_gamma) { /* Standard fields */ standard_display_init(&dp->this, &pm->this, id, 0/*do_interlace*/); @@ -5381,11 +5465,18 @@ gamma_display_init(gamma_display *dp, png_modifier *pm, png_uint_32 id, dp->pm = pm; dp->file_gamma = file_gamma; dp->screen_gamma = screen_gamma; + dp->background_gamma = background_gamma; dp->sbit = sbit; dp->threshold_test = threshold_test; dp->speed = speed; dp->use_input_precision = use_input_precision; dp->strip16 = strip16; + dp->expand16 = expand16; + dp->do_background = do_background; + if (do_background && pointer_to_the_background_color != 0) + dp->background_color = *pointer_to_the_background_color; + else + memset(&dp->background_color, 0, sizeof dp->background_color); /* Local variable fields */ dp->maxerrout = dp->maxerrpc = dp->maxerrabs = 0; @@ -5409,6 +5500,90 @@ gamma_info_imp(gamma_display *dp, png_structp pp, png_infop pi) png_error(pp, "strip16 (16 to 8 bit conversion) not supported"); # endif + if (dp->expand16) +# ifdef PNG_READ_EXPAND_16_SUPPORTED + png_set_expand_16(pp); +# else + png_error(pp, "expand16 (8 to 16 bit conversion) not supported"); +# endif + + if (dp->do_background >= ALPHA_MODE_OFFSET) + { +# ifdef PNG_READ_ALPHA_MODE_SUPPORTED + { + /* This tests the alpha mode handling, if supported. */ + int mode = dp->do_background - ALPHA_MODE_OFFSET; + + /* The gamma value is the output gamma, and is in the standard, + * non-inverted, represenation. It provides a default for the PNG file + * gamma, but since the file has a gAMA chunk this does not matter. + */ + PNG_CONST double sg = dp->screen_gamma; +# ifndef PNG_FLOATING_POINT_SUPPORTED + PNG_CONST png_fixed_point g = (png_fixed_point)(sg*100000+.5); +# endif + +# ifdef PNG_FLOATING_POINT_SUPPORTED + png_set_alpha_mode(pp, mode, sg); +# else + png_set_alpha_mode_fixed(pp, mode, g); +# endif + + /* However, for the standard Porter-Duff algorithm the output defaults + * to be linear, so if the test requires non-linear output it must be + * corrected here. + */ + if (mode == PNG_ALPHA_STANDARD && sg != 1) + { +# ifdef PNG_FLOATING_POINT_SUPPORTED + png_set_gamma(pp, sg, dp->file_gamma); +# else + png_fixed_point f = (png_fixed_point)(dp->file_gamma*100000+.5); + png_set_gamma_fixed(pp, g, f); +# endif + } + } +# else + png_error(pp, "alpha mode handling not supported"); +# endif + } + + else + { + /* Set up gamma processing. */ +# ifdef PNG_FLOATING_POINT_SUPPORTED + png_set_gamma(pp, dp->screen_gamma, dp->file_gamma); +# else + { + png_fixed_point s = (png_fixed_point)(dp->screen_gamma*100000+.5); + png_fixed_point f = (png_fixed_point)(dp->file_gamma*100000+.5); + png_set_gamma_fixed(pp, s, f); + } +# endif + + if (dp->do_background) + { +# ifdef PNG_READ_BACKGROUND_SUPPORTED + /* NOTE: this assumes the caller provided the correct background gamma! + */ + PNG_CONST double bg = dp->background_gamma; +# ifndef PNG_FLOATING_POINT_SUPPORTED + PNG_CONST png_fixed_point g = (png_fixed_point)(bg*100000+.5); +# endif + +# ifdef PNG_FLOATING_POINT_SUPPORTED + png_set_background(pp, &dp->background_color, dp->do_background, + 0/*need_expand*/, bg); +# else + png_set_background_fixed(pp, &dp->background_color, + dp->do_background, 0/*need_expand*/, g); +# endif +# else + png_error(pp, "png_set_background not supported"); +# endif + } + } + png_read_update_info(pp, pi); /* Now we may get a different cbRow: */ @@ -5421,15 +5596,520 @@ gamma_info(png_structp pp, png_infop pi) gamma_info_imp(png_get_progressive_ptr(pp), pp, pi); } +/* Validate a single component value - the routine gets the input and output + * sample values as unscaled PNG component values along with a cache of all the + * information required to validate the values. + */ +typedef struct validate_info +{ + png_structp pp; + gamma_display *dp; + png_byte sbit; + int use_input_precision; + int do_background; + int strip16; + unsigned int sbit_max; + unsigned int isbit_shift; + unsigned int outmax; + + double gamma_correction; /* Overall correction required. */ + double file_inverse; /* Inverse of file gamma. */ + double screen_gamma; + double screen_inverse; /* Inverse of screen gamma. */ + + double background_red; /* Linear background value, red or gray. */ + double background_green; + double background_blue; + + double maxabs; + double maxcalc; + double maxout; + double maxpc; +} +validate_info; + +static void +init_validate_info(validate_info *vi, gamma_display *dp, png_struct *pp, + PNG_CONST png_byte out_bd) +{ + PNG_CONST unsigned int outmax = (1U<pp = pp; + vi->dp = dp; + vi->sbit = dp->sbit; + vi->sbit_max = (1U << dp->sbit)-1; + vi->isbit_shift = (dp->this.bit_depth - dp->sbit); + + /* This mimics the libpng threshold test, '0' is used to prevent gamma + * correction in the validation test. + */ + vi->screen_gamma = dp->screen_gamma; + if (fabs(vi->screen_gamma-1) < PNG_GAMMA_THRESHOLD) + vi->screen_gamma = vi->screen_inverse = 0; + else + vi->screen_inverse = 1/vi->screen_gamma; + + vi->use_input_precision = dp->use_input_precision; + vi->outmax = outmax; + vi->maxabs = abserr(dp->pm, out_bd); + vi->maxcalc = calcerr(dp->pm, out_bd); + vi->maxout = outerr(dp->pm, out_bd); + vi->maxpc = pcerr(dp->pm, out_bd); + + if ((dp->this.colour_type & PNG_COLOR_MASK_ALPHA) != 0) + { + vi->do_background = dp->do_background; + + if (vi->do_background != 0) + { + PNG_CONST double bg_inverse = 1/dp->background_gamma; + double r, g, b; + + /* Caller must at least put the gray value into the red channel */ + r = dp->background_color.red; r /= outmax; + g = dp->background_color.green; g /= outmax; + b = dp->background_color.blue; b /= outmax; + +# if 0 + /* libpng doesn't do this optimization, if we do pngvalid will fail. + */ + if (fabs(bg_inverse-1) >= PNG_GAMMA_THRESHOLD) +# endif + { + r = pow(r, bg_inverse); + g = pow(g, bg_inverse); + b = pow(b, bg_inverse); + } + + vi->background_red = r; + vi->background_green = g; + vi->background_blue = b; + } + } + else + vi->do_background = 0; + + if (vi->do_background == 0) + vi->background_red = vi->background_green = vi->background_blue = 0; + + vi->gamma_correction = 1/(dp->file_gamma*dp->screen_gamma); + if (fabs(vi->gamma_correction-1) < PNG_GAMMA_THRESHOLD) + vi->gamma_correction = 0; + + vi->file_inverse = 1/dp->file_gamma; + if (fabs(vi->file_inverse-1) < PNG_GAMMA_THRESHOLD) + vi->file_inverse = 0; + + vi->strip16 = dp->strip16; +} + +/* This API returns the encoded *input* component, in the range 0..1 */ +static double +gamma_component_validate(PNG_CONST char *name, PNG_CONST validate_info *vi, + PNG_CONST unsigned int id, PNG_CONST unsigned int od, + PNG_CONST double alpha /* <0 for the alpha channel itself */, + PNG_CONST double background /* component background value */) +{ + PNG_CONST unsigned int isbit = id >> vi->isbit_shift; + PNG_CONST unsigned int sbit_max = vi->sbit_max; + PNG_CONST unsigned int outmax = vi->outmax; + PNG_CONST int do_background = vi->do_background; + + double i; + + /* First check on the 'perfect' result obtained from the digitized input + * value, id, and compare this against the actual digitized result, 'od'. + * 'i' is the input result in the range 0..1: + * + * NOTE: sBIT should be taken into account here but isn't, as described + * below (next function). + */ + i = isbit; i /= sbit_max; + + /* Check for the fast route: if we don't do any background composition or if + * this is the alpha channel ('alpha' < 0) or if the pixel is opaque then + * just use the gamma_correction field to correct to the final output gamma. + */ + if (alpha == 1 /* opaque pixel component */ || !do_background +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED + || do_background == ALPHA_MODE_OFFSET + PNG_ALPHA_PNG +#endif + || (alpha < 0 /* alpha channel */ +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED + && do_background != ALPHA_MODE_OFFSET + PNG_ALPHA_BROKEN +#endif + )) + { + /* Then get the gamma corrected version of 'i' and compare to 'od', any + * error less than .5 is insignificant - just quantization of the output + * value to the nearest digital value (nevertheless the error is still + * recorded - it's interesting ;-) + */ + double encoded_sample = i; + double encoded_error; + + /* alpha less than 0 indicates the alpha channel, which is always linear + */ + if (alpha >= 0 && vi->gamma_correction > 0) + encoded_sample = pow(encoded_sample, vi->gamma_correction); + encoded_sample *= outmax; + + encoded_error = fabs(od-encoded_sample); + + if (encoded_error > vi->dp->maxerrout) + vi->dp->maxerrout = encoded_error; + + if (encoded_error < .5+vi->maxout) + return i; + } + + /* The slow route - attempt to do linear calculations. */ + /* There may be an error, or background processing is required, so calculate + * the actual sample values - unencoded light intensity values. Note that in + * practice these are not completely unencoded because they include a + * 'viewing correction' to decrease or (normally) increase the perceptual + * contrast of the image. There's nothing we can do about this - we don't + * know what it is - so assume the unencoded value is perceptually linear. + */ + { + double input_sample = i; /* In range 0..1 */ + double output, error, encoded_sample; + double es_lo, es_hi; + int compose = 0; /* Set to one if composition done */ + int output_is_encoded = 0; /* Set if encoded to screen gamma */ + int log_max_error = 1; /* Check maximum error values */ + + /* Convert to linear light (with the above caveat.) The alpha channel is + * already linear. + */ + if (alpha >= 0 && vi->file_inverse > 0) + input_sample = pow(input_sample, vi->file_inverse); + + /* And similarly for the output value, but we need to check the background + * handling to linearize it correctly, so do that in the switch below. + */ + output = od; + output /= outmax; + + /* If necessary handle the background processing - note that this does + * not need to do anything to the alpha channel, which starts out linear. + * First the alpha channel case. + */ + if (alpha < 0) switch (do_background) + { +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED + case ALPHA_MODE_OFFSET + PNG_ALPHA_BROKEN: + /* output alpha is gamma encoded */ + if (vi->screen_gamma > 0) + { + output = pow(output, vi->screen_gamma); + output_is_encoded = 1; + } + break; +#endif + + default: + /* In all other cases the output alpha channel is linear already, + * don't log errors here, they are much larger in linear data. + */ + log_max_error = 0; + break; + } + + /* Then the components. */ + else switch (do_background) + { + case PNG_BACKGROUND_GAMMA_SCREEN: + case PNG_BACKGROUND_GAMMA_FILE: + case PNG_BACKGROUND_GAMMA_UNIQUE: + /* Standard PNG background processing. */ + if (alpha < 1) + { + if (alpha > 0) + { + input_sample = input_sample * alpha + background * (1-alpha); + compose = 1; + } + + else + input_sample = background; + } + + /* Output is gamma encoded. */ + if (vi->screen_gamma > 0) + { + output = pow(output, vi->screen_gamma); + output_is_encoded = 1; + } + break; + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED + case ALPHA_MODE_OFFSET + PNG_ALPHA_STANDARD: + case ALPHA_MODE_OFFSET + PNG_ALPHA_BROKEN: + /* The components are premultiplied in either case and the output is + * gamma encoded (to get standard Porter-Duff we expect the output + * gamma to be set to 1.0!) + */ + if (alpha < 1) + { + if (alpha > 0) + { + input_sample *= alpha; + compose = 1; + } + + else + input_sample = 0; + } + + if (vi->screen_gamma > 0) + { + output = pow(output, vi->screen_gamma); + output_is_encoded = 1; + } + break; + + case ALPHA_MODE_OFFSET + PNG_ALPHA_OPTIMIZED: + /* The optimization is that the partial-alpha entries are linear + * while the opaque pixels are gamma encoded. + */ + if (alpha < 1) + { + if (alpha > 0) + { + input_sample *= alpha; + compose = 1; + /* Even though the screen may be non-linear the current + * component value remains linear, don't log errors in this + * case, they are irrelevant to overall perception: + */ + log_max_error = 0; + } + + else + input_sample = 0; + } + + /* Opaque pixel, so gamma encode it: */ + else if (vi->screen_gamma > 0) + { + output = pow(output, vi->screen_gamma); + output_is_encoded = 1; + } + break; +#endif + + default: + /* Standard cases where no compositing is done (so the component + * value is already correct.) The output is still gamma encoded. + */ + if (vi->screen_gamma > 0) + { + output = pow(output, vi->screen_gamma); + output_is_encoded = 1; + } + break; + } + + /* Calculate (or recalculate) the encoded_sample value and repeat the + * check above (unnecessary if we took the fast route, but harmless.) + */ + encoded_sample = input_sample; + if (output_is_encoded) + encoded_sample = pow(encoded_sample, vi->screen_inverse); + encoded_sample *= outmax; + + { + PNG_CONST double encoded_error = fabs(od-encoded_sample); + + /* Don't log errors in the alpha channel, or the 'optimized' case, + * neither are significant to the overall perception. + */ + if (log_max_error && encoded_error > vi->dp->maxerrout) + vi->dp->maxerrout = encoded_error; + + if (encoded_error < .5+vi->maxout) + return i; + } + + /* Now we have the numbers for real errors, both absolute values as as a + * percentage of the correct value (output): + */ + error = fabs(input_sample-output); + + if (log_max_error && error > vi->dp->maxerrabs) + vi->dp->maxerrabs = error; + + /* The following is an attempt to ignore the tendency of quantization to + * dominate the percentage errors for low output sample values: + */ + if (log_max_error && input_sample*vi->maxpc > .5+vi->maxabs) + { + double percentage_error = error/input_sample; + if (percentage_error > vi->dp->maxerrpc) + vi->dp->maxerrpc = percentage_error; + } + + /* Now calculate the digitization limits for 'encoded_sample' using the + * 'max' values. Note that maxout is in the encoded space but maxpc and + * maxabs are in linear light space. + * + * First find the maximum error in linear light space, range 0..1: + */ + { + double tmp = input_sample * vi->maxpc; + if (tmp < vi->maxabs) tmp = vi->maxabs; + if (compose && tmp < vi->maxcalc) tmp = vi->maxcalc; + + /* Low bound - the minimum of the four: */ + es_lo = encoded_sample - vi->maxout; + + if (es_lo > 0 && input_sample-tmp > 0) + { + double low_value = input_sample-tmp; + if (output_is_encoded) + low_value = pow(low_value, vi->screen_inverse); + low_value *= outmax; + if (low_value < es_lo) es_lo = low_value; + } + + else + es_lo = 0; + + es_hi = encoded_sample + vi->maxout; + + if (es_hi < outmax && input_sample+tmp < 1) + { + double high_value = input_sample+tmp; + if (output_is_encoded) + high_value = pow(high_value, vi->screen_inverse); + high_value *= outmax; + if (high_value > es_hi) es_hi = high_value; + } + + else + es_hi = outmax; + } + + /* The primary test is that the final encoded value returned by the + * library should be between the two limits (inclusive) that were + * calculated above. At this point quantization of the output must be + * taken into account. + */ + if (od+.5 < es_lo || od-.5 > es_hi) + { + /* There has been an error in processing. */ + double is_lo, is_hi; + + /* NOTE: 'use_input_precision' is never set in background and alpha + * mode tests, if it ever is the following will be wrong because it + * doesn't do any of the compose handling! + */ + if (vi->use_input_precision) + { + /* Ok, something is wrong - this actually happens in current libpng + * sbit processing. Assume that the input value (id, adjusted for + * sbit) can be anywhere between value-.5 and value+.5 - quite a + * large range if sbit is low. + */ + double tmp = (isbit - .5)/sbit_max; + + if (tmp > 0) + { + if (alpha >= 0 && vi->gamma_correction > 0) + tmp = pow(tmp, vi->gamma_correction); + is_lo = outmax * tmp - vi->maxout; + if (is_lo < 0) is_lo = 0; + } + + else + is_lo = 0; + + tmp = (isbit + .5)/sbit_max; + + if (tmp < 1) + { + if (alpha >= 0 && vi->gamma_correction > 0) + tmp = pow(tmp, vi->gamma_correction); + is_hi = outmax * tmp + vi->maxout; + if (is_hi > outmax) is_hi = outmax; + } + + else + is_hi = outmax; + + if (!(od+.5 < is_lo || od-.5 > is_hi)) + return i; + + /* One last chance. If this is an alpha channel and the 16to8 + * option has been used and 'inaccurate' scaling is used then the + * bit reduction is obtained by simply using the top 8 bits of the + * value. + */ +# ifndef PNG_READ_16_TO_8_ACCURATE_SCALE_SUPPORTED + /* This may be required for other components in the future, but + * at present the presence of gamma correction effectively + * prevents the errors in the component scaling (I don't quite + * understand why, but since it's better this way I care not to + * ask, JB 20110419.) + */ + if (alpha < 0 && vi->strip16 && vi->sbit > 8 && + vi->sbit + vi->isbit_shift == 16) + { + tmp = ((id >> 8) - .5)/255; + + if (tmp > 0) + { + is_lo = outmax * tmp - vi->maxout; + if (is_lo < 0) is_lo = 0; + } + + else + is_lo = 0; + + tmp = ((id >> 8) + .5)/255; + + if (tmp < 1) + { + is_hi = outmax * tmp + vi->maxout; + if (is_hi > outmax) is_hi = outmax; + } + + else + is_hi = outmax; + + if (!(od+.5 < is_lo || od-.5 > is_hi)) + return i; + } +# endif + } + else /* !use_input_precision */ + is_lo = es_lo, is_hi = es_hi; + + { + char msg[256]; + + sprintf(msg, "%s: %.3f; %u*%.3f{%u;%u} -> %u not %.2f (%.1f-%.1f)", + name, od-encoded_sample, id, alpha, vi->sbit, isbit, od, + encoded_sample, is_lo, is_hi); + +# ifdef PNG_WARNINGS_SUPPORTED + png_warning(vi->pp, msg); +# else + store_warning(vi->pp, msg); +# endif + } + } + } + + return i; +} + static void gamma_image_validate(gamma_display *dp, png_structp pp, png_infop pi, png_const_bytep pRow) { /* Get some constants derived from the input and output file formats: */ - PNG_CONST png_byte sbit = dp->sbit; - PNG_CONST double file_gamma = dp->file_gamma; - PNG_CONST double screen_gamma = dp->screen_gamma; - PNG_CONST int use_input_precision = dp->use_input_precision; PNG_CONST int speed = dp->speed; PNG_CONST png_byte in_ct = dp->this.colour_type; PNG_CONST png_byte in_bd = dp->this.bit_depth; @@ -5438,10 +6118,6 @@ gamma_image_validate(gamma_display *dp, png_structp pp, png_infop pi, PNG_CONST size_t cbRow = dp->this.cbRow; PNG_CONST png_byte out_ct = png_get_color_type(pp, pi); PNG_CONST png_byte out_bd = png_get_bit_depth(pp, pi); - PNG_CONST unsigned int outmax = (1U<pm, out_bd); - PNG_CONST double maxout = outerr(dp->pm, out_bd); - PNG_CONST double maxpc = pcerr(dp->pm, out_bd); /* There are three sources of error, firstly the quantization in the * file encoding, determined by sbit and/or the file depth, secondly @@ -5479,201 +6155,76 @@ gamma_image_validate(gamma_display *dp, png_structp pp, png_infop pi, * * Because there is limited precision in the input it is arguable that * an acceptable result is any valid result from input-.5 to input+.5. - * The basic tests below do not do this, however if - * 'use_input_precision' is set a subsequent test is performed below. + * The basic tests below do not do this, however if 'use_input_precision' + * is set a subsequent test is performed below. */ - PNG_CONST int processing = (fabs(screen_gamma*file_gamma-1) >= - PNG_GAMMA_THRESHOLD && !dp->threshold_test && !speed && in_ct != 3) || - in_bd != out_bd; - PNG_CONST unsigned int samples_per_pixel = (out_ct & 2U) ? 3U : 1U; + int processing; + png_uint_32 y; + validate_info vi; - PNG_CONST double gamma_correction = 1/(file_gamma*screen_gamma);/* Overall */ + init_validate_info(&vi, dp, pp, out_bd); - double maxerrout = 0, maxerrabs = 0, maxerrpc = 0; - png_uint_32 y; + processing = (vi.gamma_correction > 0 && !dp->threshold_test + && !speed && in_ct != 3) || in_bd != out_bd || in_ct != out_ct || + vi.do_background; for (y=0; y> (in_bd-sbit); - - double i, input_sample, encoded_sample, output; - double encoded_error, error; - double es_lo, es_hi; - - /* First check on the 'perfect' result obtained from the - * digitized input value, id, and compare this against the - * actual digitized result, 'od'. 'i' is the input result - * in the range 0..1: - * - * NOTE: sBIT should be taken into account here but isn't, - * as described above. - */ - i = isbit; i /= (1U< maxerrout) - maxerrout = encoded_error; - - if (encoded_error < .5+maxout) - continue; - - /* There may be an error, so calculate the actual sample - * values - unencoded light intensity values. Note that - * in practice these are not unencoded because they - * include a 'viewing correction' to decrease or - * (normally) increase the perceptual contrast of the - * image. There's nothing we can do about this - we don't - * know what it is - so assume the unencoded value is - * perceptually linear. - */ - input_sample = pow(i, 1/file_gamma); /* In range 0..1 */ - output = od; - output /= outmax; - output = pow(output, screen_gamma); - - /* Now we have the numbers for real errors, both absolute - * values as as a percentage of the correct value (output): - */ - error = fabs(input_sample-output); + unsigned int x; - if (error > maxerrabs) - maxerrabs = error; - - /* The following is an attempt to ignore the tendency of - * quantization to dominate the percentage errors for low - * output sample values: - */ - if (input_sample*maxpc > .5+maxabs) - { - double percentage_error = error/input_sample; - if (percentage_error > maxerrpc) maxerrpc = percentage_error; - } + for (x=0; x 0 && input_sample-tmp > 0) - { - double low_value = outmax * pow(input_sample-tmp, - 1/screen_gamma); - if (low_value < es_lo) es_lo = low_value; - } + PNG_CONST unsigned int input_alpha = + sample(std, in_ct, in_bd, x, samples_per_pixel); + if ((out_ct & PNG_COLOR_MASK_ALPHA) != 0) + alpha = gamma_component_validate("alpha", &vi, input_alpha, + sample(pRow, out_ct, out_bd, x, samples_per_pixel), + -1/*alpha*/, 0/*background*/); else - es_lo = 0; - - es_hi = encoded_sample + maxout; - - if (es_hi < outmax && input_sample+tmp < 1) { - double high_value = outmax * pow(input_sample+tmp, - 1/screen_gamma); - if (high_value > es_hi) es_hi = high_value; + /* This is a copy of the calculation of 'i' above. */ + alpha = input_alpha >> vi.isbit_shift; + alpha /= vi.sbit_max; } - - else - es_hi = outmax; } - /* The primary test is that the final encoded value - * returned by the library should be between the two limits - * (inclusive) that were calculated above. At this point - * quantization of the output must be taken into account. - */ - if (od+.5 < es_lo || od-.5 > es_hi) + /* Handle greyscale or RGB components. */ + if ((in_ct & PNG_COLOR_MASK_COLOR) == 0) /* greyscale */ + (void)gamma_component_validate("gray", &vi, + sample(std, in_ct, in_bd, x, 0), + sample(pRow, out_ct, out_bd, x, 0), alpha/*component*/, + vi.background_red); + else { - /* There has been an error in processing. */ - double is_lo, is_hi; - - if (use_input_precision) - { - /* Ok, something is wrong - this actually happens in - * current libpng sbit processing. Assume that the - * input value (id, adjusted for sbit) can be - * anywhere between value-.5 and value+.5 - quite a - * large range if sbit is low. - */ - double tmp = (isbit - .5)/((1U< 0) - { - is_lo = outmax * pow(tmp, gamma_correction) - maxout; - if (is_lo < 0) is_lo = 0; - } - - else - is_lo = 0; - - tmp = (isbit + .5)/((1U< outmax) is_hi = outmax; - } - - else - is_hi = outmax; - - if (!(od+.5 < is_lo || od-.5 > is_hi)) - continue; - } - else - is_lo = es_lo, is_hi = es_hi; - - { - char msg[256]; - - sprintf(msg, - "error: %.3f; %u{%u;%u} -> %u not %.2f (%.1f-%.1f)", - od-encoded_sample, id, sbit, isbit, od, - encoded_sample, is_lo, is_hi); - -# ifdef PNG_WARNINGS_SUPPORTED - png_warning(pp, msg); -# else - store_warning(pp, msg); -# endif - } - + (void)gamma_component_validate("red", &vi, + sample(std, in_ct, in_bd, x, 0), + sample(pRow, out_ct, out_bd, x, 0), alpha/*component*/, + vi.background_red); + + (void)gamma_component_validate("green", &vi, + sample(std, in_ct, in_bd, x, 1), + sample(pRow, out_ct, out_bd, x, 1), alpha/*component*/, + vi.background_green); + + (void)gamma_component_validate("blue", &vi, + sample(std, in_ct, in_bd, x, 2), + sample(pRow, out_ct, out_bd, x, 2), alpha/*component*/, + vi.background_blue); } } } @@ -5689,9 +6240,6 @@ gamma_image_validate(gamma_display *dp, png_structp pp, png_infop pi, } } /* row (y) loop */ - dp->maxerrout = maxerrout; - dp->maxerrabs = maxerrabs; - dp->maxerrpc = maxerrpc; dp->this.ps->validated = 1; } @@ -5715,14 +6263,17 @@ gamma_test(png_modifier *pmIn, PNG_CONST png_byte colour_typeIn, PNG_CONST double file_gammaIn, PNG_CONST double screen_gammaIn, PNG_CONST png_byte sbitIn, PNG_CONST int threshold_testIn, PNG_CONST char *name, PNG_CONST int speedIn, - PNG_CONST int use_input_precisionIn, PNG_CONST int strip16In) + PNG_CONST int use_input_precisionIn, PNG_CONST int strip16In, + PNG_CONST int expand16In, PNG_CONST int do_backgroundIn, + PNG_CONST png_color_16 *bkgd_colorIn, double bkgd_gammaIn) { gamma_display d; context(&pmIn->this, fault); gamma_display_init(&d, pmIn, FILEID(colour_typeIn, bit_depthIn, interlace_typeIn, 0, 0, 0), file_gammaIn, screen_gammaIn, sbitIn, - threshold_testIn, speedIn, use_input_precisionIn, strip16In); + threshold_testIn, speedIn, use_input_precisionIn, strip16In, + expand16In, do_backgroundIn, bkgd_colorIn, bkgd_gammaIn); Try { @@ -5745,17 +6296,6 @@ gamma_test(png_modifier *pmIn, PNG_CONST png_byte colour_typeIn, /* Get a png_struct for writing the image. */ pp = set_modifier_for_read(d.pm, &pi, d.this.id, name); - /* Set up gamma processing. */ -#ifdef PNG_FLOATING_POINT_SUPPORTED - png_set_gamma(pp, d.screen_gamma, d.file_gamma); -#else - { - png_fixed_point s = floor(d.screen_gamma*100000+.5); - png_fixed_point f = floor(d.file_gamma*100000+.5); - png_set_gamma_fixed(pp, s, f); - } -#endif - /* Introduce the correct read function. */ if (d.pm->this.progressive) { @@ -5786,7 +6326,7 @@ gamma_test(png_modifier *pmIn, PNG_CONST png_byte colour_typeIn, if (d.pm->log && !d.threshold_test && !d.speed) fprintf(stderr, "%d bit %s %s: max error %f (%.2g, %2g%%)\n", - d.this.bit_depth, colour_types[d.this.colour_type], d.name, + d.this.bit_depth, colour_types[d.this.colour_type], name, d.maxerrout, d.maxerrabs, 100*d.maxerrpc); /* Log the summary values too. */ @@ -5867,7 +6407,8 @@ static void gamma_threshold_test(png_modifier *pm, png_byte colour_type, (void)gamma_test(pm, colour_type, bit_depth, interlace_type, file_gamma, screen_gamma, bit_depth, 1, name, 0 /*speed*/, 0 /*no input precision*/, - 0 /*no strip16*/); + 0 /*no strip16*/, 0 /*no expand16*/, 0 /*no background*/, 0 /*hence*/, + 0 /*no background gamma*/); } static void @@ -5925,7 +6466,8 @@ static void gamma_transform_test(png_modifier *pm, pos = safecatd(name, sizeof name, pos, screen_gamma, 3); gamma_test(pm, colour_type, bit_depth, interlace_type, file_gamma, - screen_gamma, sbit, 0, name, speed, use_input_precision, strip16); + screen_gamma, sbit, 0, name, speed, use_input_precision, strip16, + pm->test_gamma_expand16, 0 , 0, 0); } static void perform_gamma_transform_tests(png_modifier *pm, int speed) @@ -6065,6 +6607,186 @@ static void perform_gamma_strip16_tests(png_modifier *pm, int speed) } #endif /* 16 to 8 bit conversion */ +#if defined PNG_READ_BACKGROUND_SUPPORTED || defined PNG_READ_ALPHA_MODE_SUPPORTED +static void gamma_composition_test(png_modifier *pm, + PNG_CONST png_byte colour_type, PNG_CONST png_byte bit_depth, + PNG_CONST int interlace_type, PNG_CONST double file_gamma, + PNG_CONST double screen_gamma, PNG_CONST int speed, + PNG_CONST int use_input_precision, PNG_CONST int do_background, + PNG_CONST int expand_16) +{ + size_t pos = 0; + png_const_charp base; + double bg; + char name[128]; + png_color_16 background; + + /* Make up a name and get an appropriate background gamma value. */ + switch (do_background) + { + default: + base = "gamma "; + bg = 4; /* should not be used */ + break; + case PNG_BACKGROUND_GAMMA_SCREEN: + base = "background(screen) "; + bg = 1/screen_gamma; + break; + case PNG_BACKGROUND_GAMMA_FILE: + base = "background(file) "; + bg = file_gamma; + break; + case PNG_BACKGROUND_GAMMA_UNIQUE: + base = "background(unique) "; + /* This tests the handling of a unique value, the math is such that the + * value tends to be <1, but is neither screen nor file (even if they + * match!) + */ + bg = (file_gamma + screen_gamma) / 3; + break; +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED + case ALPHA_MODE_OFFSET + PNG_ALPHA_PNG: + base = "alpha mode(PNG) "; + bg = 4; /* should not be used */ + break; + case ALPHA_MODE_OFFSET + PNG_ALPHA_STANDARD: + base = "alpha mode(Porter-Duff) "; + bg = 4; /* should not be used */ + break; + case ALPHA_MODE_OFFSET + PNG_ALPHA_OPTIMIZED: + base = "alpha mode(Optimized) "; + bg = 4; /* should not be used */ + break; + case ALPHA_MODE_OFFSET + PNG_ALPHA_BROKEN: + base = "alpha mode(Broken) "; + bg = 4; /* should not be used */ + break; +#endif + } + + /* Use random background values - the background is always presented in the + * output space (8 or 16 bit components). + */ + if (expand_16 || bit_depth == 16) + { + png_uint_32 r = random_32(); + + background.red = (png_uint_16)r; + background.green = (png_uint_16)(r >> 16); + r = random_32(); + background.blue = (png_uint_16)r; + background.gray = (png_uint_16)(r >> 16); + } + + else /* 8 bit colors */ + { + png_uint_32 r = random_32(); + + background.red = (png_byte)r; + background.green = (png_byte)(r >> 8); + background.blue = (png_byte)(r >> 16); + background.gray = (png_byte)(r >> 24); + } + + background.index = 193; /* rgb(193,193,193) to detect errors */ + if (!(colour_type & PNG_COLOR_MASK_COLOR)) + { + /* Grayscale input, we do not convert to RGB (TBD), so we must set the + * background to gray - else libpng seems to fail. + */ + background.red = background.green = background.blue = background.gray; + } + + pos = safecat(name, sizeof name, pos, base); + pos = safecatd(name, sizeof name, pos, file_gamma, 3); + if (do_background < ALPHA_MODE_OFFSET) + { + /* Include the background color and gamma in the name: */ + pos = safecat(name, sizeof name, pos, ",("); + /* This assumes no expand gray->rgb - the current code won't handle that! + */ + if (colour_type & PNG_COLOR_MASK_COLOR) + { + pos = safecatn(name, sizeof name, pos, background.red); + pos = safecat(name, sizeof name, pos, ","); + pos = safecat(name, sizeof name, pos, ","); + pos = safecatn(name, sizeof name, pos, background.blue); + } + else + pos = safecatn(name, sizeof name, pos, background.gray); + pos = safecat(name, sizeof name, pos, ")^"); + pos = safecatd(name, sizeof name, pos, bg, 3); + } + pos = safecat(name, sizeof name, pos, "->"); + pos = safecatd(name, sizeof name, pos, screen_gamma, 3); + + gamma_test(pm, colour_type, bit_depth, interlace_type, file_gamma, + screen_gamma, bit_depth, 0, name, speed, use_input_precision, + 0/*strip 16*/, expand_16, do_background, &background, bg); +} + + +static void +perform_gamma_composition_tests(png_modifier *pm, int speed, int do_background, + int expand_16) +{ + png_byte colour_type = 0; + png_byte bit_depth = 0; + + /* This should test palette images, but the code isn't here to do it, also + * skip the non-alpha cases - no tRNS chunk is interpolated. + */ + while (next_format(&colour_type, &bit_depth)) + if ((colour_type != 3 && (colour_type & PNG_COLOR_MASK_ALPHA) != 0)) + { + unsigned int i, j; + + /* Don't skip the i==j case here - it's relevant. */ + for (i=0; ingammas; ++i) for (j=0; jngammas; ++j) + { + /* use_input_precision must currently be false here because the checks + * in gamma_component_validate switched on by use_input_precision do + * *not* handle the composition being tested here. + */ + gamma_composition_test(pm, colour_type, bit_depth, pm->interlace_type, + 1/pm->gammas[i], pm->gammas[j], speed, 0/*use_input_precision*/, + do_background, expand_16); + + if (fail(pm)) + return; + } + } +} +#endif /* READ_BACKGROUND || READ_ALPHA_MODE */ + +static void +init_gamma_errors(png_modifier *pm) +{ + pm->error_gray_2 = pm->error_gray_4 = pm->error_gray_8 = pm->error_color_8 = + 0; + pm->error_gray_16 = pm->error_color_16 = 0; +} + +static void +summarize_gamma_errors(png_modifier *pm, png_const_charp who, int low_bit_depth) +{ + if (who) + printf("Gamma correction with %s:\n", who); + + if (low_bit_depth) + { + printf(" 2 bit gray: %.5f\n", pm->error_gray_2); + printf(" 4 bit gray: %.5f\n", pm->error_gray_4); + printf(" 8 bit gray: %.5f\n", pm->error_gray_8); + printf(" 8 bit color: %.5f\n", pm->error_color_8); + } + +#ifdef DO_16BIT + printf(" 16 bit gray: %.5f\n", pm->error_gray_16); + printf(" 16 bit color: %.5f\n", pm->error_color_16); +#endif +} + static void perform_gamma_test(png_modifier *pm, int speed, int summary) { @@ -6080,6 +6802,7 @@ perform_gamma_test(png_modifier *pm, int speed, int summary) /* Now some real transforms. */ if (pm->test_gamma_transform) { + init_gamma_errors(pm); perform_gamma_transform_tests(pm, speed); if (summary) @@ -6097,49 +6820,25 @@ perform_gamma_test(png_modifier *pm, int speed, int summary) printf("For performance reasons the value for 16 bit formats\n"); printf("increases when the image file includes an sBIT chunk.\n\n"); - printf(" 2 bit gray: %.5f\n", pm->error_gray_2); - printf(" 4 bit gray: %.5f\n", pm->error_gray_4); - printf(" 8 bit gray: %.5f\n", pm->error_gray_8); - printf(" 8 bit color: %.5f\n", pm->error_color_8); -#ifdef DO_16BIT - printf(" 16 bit gray: %.5f\n", pm->error_gray_16); - printf(" 16 bit color: %.5f\n", pm->error_color_16); -#endif + summarize_gamma_errors(pm, 0/*who*/, 1); } } /* The sbit tests produce much larger errors: */ 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; + init_gamma_errors(pm); perform_gamma_sbit_tests(pm, speed); if (summary) - { - printf("Gamma correction with sBIT:\n"); - - if (pm->sbitlow < 8U) - { - printf(" 2 bit gray: %.5f\n", pm->error_gray_2); - printf(" 4 bit gray: %.5f\n", pm->error_gray_4); - printf(" 8 bit gray: %.5f\n", pm->error_gray_8); - printf(" 8 bit color: %.5f\n", pm->error_color_8); - } - - #ifdef DO_16BIT - printf(" 16 bit gray: %.5f\n", pm->error_gray_16); - printf(" 16 bit color: %.5f\n", pm->error_color_16); - #endif - } + summarize_gamma_errors(pm, "sBIT", pm->sbitlow < 8U); } #ifdef PNG_READ_16_TO_8_SUPPORTED if (pm->test_gamma_strip16) { /* The 16 to 8 bit strip operations: */ - 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; + init_gamma_errors(pm); perform_gamma_strip16_tests(pm, speed); if (summary) @@ -6150,6 +6849,37 @@ perform_gamma_test(png_modifier *pm, int speed, int summary) } } #endif + +#ifdef PNG_READ_BACKGROUND_SUPPORTED + if (pm->test_gamma_background) + { + init_gamma_errors(pm); + + perform_gamma_composition_tests(pm, speed, PNG_BACKGROUND_GAMMA_UNIQUE, + pm->test_gamma_expand16); + + if (summary) + summarize_gamma_errors(pm, "background", 1); + } +#endif + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED + if (pm->test_gamma_alpha_mode) + { + int do_background; + + init_gamma_errors(pm); + + for (do_background = ALPHA_MODE_OFFSET + PNG_ALPHA_STANDARD; + do_background <= ALPHA_MODE_OFFSET + PNG_ALPHA_BROKEN && !fail(pm); + ++do_background) + perform_gamma_composition_tests(pm, speed, do_background, + pm->test_gamma_expand16); + + if (summary) + summarize_gamma_errors(pm, "alpha mode", 1); + } +#endif } #endif /* PNG_READ_GAMMA_SUPPORTED */ @@ -6552,9 +7282,11 @@ int main(int argc, PNG_CONST char **argv) */ pm.maxout8 = .1; /* Arithmetic error in *encoded* value */ pm.maxabs8 = .00005; /* 1/20000 */ + pm.maxcalc8 = .004; /* 1/250: +/-1 in 8 bits for compose errors */ pm.maxpc8 = .499; /* I.e., .499% fractional error */ pm.maxout16 = .499; /* Error in *encoded* value */ pm.maxabs16 = .00005;/* 1/20000 */ + pm.maxcalc16 =.00005;/* 1/20000: for compose errors */ /* NOTE: this is a reasonable perceptual limit. We assume that humans can * perceive light level differences of 1% over a 100:1 range, so we need to @@ -6627,6 +7359,8 @@ int main(int argc, PNG_CONST char **argv) pm.test_gamma_transform = 1; pm.test_gamma_sbit = 1; pm.test_gamma_strip16 = 1; + pm.test_gamma_background = 1; + pm.test_gamma_alpha_mode = 1; } else if (strcmp(*argv, "--nogamma") == 0) @@ -6656,6 +7390,27 @@ int main(int argc, PNG_CONST char **argv) else if (strcmp(*argv, "--nogamma-16-to-8") == 0) pm.test_gamma_strip16 = 0; + else if (strcmp(*argv, "--gamma-background") == 0) + pm.ngammas = 2U, pm.test_gamma_background = 1; + + else if (strcmp(*argv, "--nogamma-background") == 0) + pm.test_gamma_background = 0; + + else if (strcmp(*argv, "--gamma-alpha-mode") == 0) + pm.ngammas = 2U, pm.test_gamma_alpha_mode = 1; + + else if (strcmp(*argv, "--nogamma-alpha-mode") == 0) + pm.test_gamma_alpha_mode = 0; + + else if (strcmp(*argv, "--expand16") == 0) + pm.test_gamma_expand16 = 1; + + else if (strcmp(*argv, "--noexpand16") == 0) + pm.test_gamma_expand16 = 0; + + else if (strcmp(*argv, "--more-gammas") == 0) + pm.ngammas = 3U; + else if (strcmp(*argv, "--all-gammas") == 0) pm.ngammas = (sizeof gammas)/(sizeof gammas[0]); @@ -6665,32 +7420,44 @@ int main(int argc, PNG_CONST char **argv) else if (strcmp(*argv, "--interlace") == 0) pm.interlace_type = PNG_INTERLACE_ADAM7; + else if (strcmp(*argv, "--use-input-precision") == 0) + pm.use_input_precision = 1; + + else if (strcmp(*argv, "--use-linear-precision") == 0) + pm.use_linear_precision = 1; + else if (argc >= 1 && strcmp(*argv, "--sbitlow") == 0) --argc, pm.sbitlow = (png_byte)atoi(*++argv); else if (argc >= 1 && strcmp(*argv, "--touch") == 0) --argc, touch = *++argv; - else if (argc >= 1 && strncmp(*argv, "--max", 4) == 0) + else if (argc >= 1 && strncmp(*argv, "--max", 5) == 0) { --argc; - if (strcmp(4+*argv, "abs8") == 0) + if (strcmp(5+*argv, "abs8") == 0) pm.maxabs8 = atof(*++argv); - else if (strcmp(4+*argv, "abs16") == 0) + else if (strcmp(5+*argv, "abs16") == 0) pm.maxabs16 = atof(*++argv); - else if (strcmp(4+*argv, "out8") == 0) + else if (strcmp(5+*argv, "calc8") == 0) + pm.maxcalc8 = atof(*++argv); + + else if (strcmp(5+*argv, "calc16") == 0) + pm.maxcalc16 = atof(*++argv); + + else if (strcmp(5+*argv, "out8") == 0) pm.maxout8 = atof(*++argv); - else if (strcmp(4+*argv, "out16") == 0) + else if (strcmp(5+*argv, "out16") == 0) pm.maxout16 = atof(*++argv); - else if (strcmp(4+*argv, "pc8") == 0) + else if (strcmp(5+*argv, "pc8") == 0) pm.maxpc8 = atof(*++argv); - else if (strcmp(4+*argv, "pc16") == 0) + else if (strcmp(5+*argv, "pc16") == 0) pm.maxpc16 = atof(*++argv); else @@ -6713,20 +7480,31 @@ int main(int argc, PNG_CONST char **argv) if (pm.test_standard == 0 && pm.test_size == 0 && pm.test_transform == 0 && pm.ngammas == 0) { + /* Make this do all the tests done in the test shell scripts with the same + * parameters, where possible. The limitation is that all the progressive + * read and interlace stuff has to be done in separate runs, so only the + * basic 'standard' and 'size' tests are done. + */ pm.test_standard = 1; pm.test_size = 1; pm.test_transform = 1; - pm.ngammas = 3U; + pm.ngammas = 2U; } if (pm.ngammas > 0 && pm.test_gamma_threshold == 0 && pm.test_gamma_transform == 0 && - pm.test_gamma_sbit == 0 && pm.test_gamma_strip16 == 0) + pm.test_gamma_sbit == 0 && pm.test_gamma_strip16 == 0 && + pm.test_gamma_background == 0 && pm.test_gamma_alpha_mode == 0) { pm.test_gamma_threshold = 1; pm.test_gamma_transform = 1; pm.test_gamma_sbit = 1; pm.test_gamma_strip16 = 1; + pm.test_gamma_background = 1; + pm.test_gamma_alpha_mode = 1; + /* For the moment the inaccuarcy of the libpng composition requires this: + */ + pm.use_linear_precision = 1; } else if (pm.ngammas == 0) @@ -6736,6 +7514,8 @@ int main(int argc, PNG_CONST char **argv) pm.test_gamma_transform = 0; pm.test_gamma_sbit = 0; pm.test_gamma_strip16 = 0; + pm.test_gamma_background = 0; + pm.test_gamma_alpha_mode = 0; } Try diff --git a/pngwutil.c b/pngwutil.c index 0b48215c6643e89a93b6ba69b286d85b2c5c1c44..e22a307cf3fb63c0ae77015b34935fe60cbfcb0c 100644 --- a/pngwutil.c +++ b/pngwutil.c @@ -315,7 +315,7 @@ png_zlib_release(png_structp png_ptr) break; } - png_warning_parameter_unsigned(p, 1, PNG_NUMBER_FORMAT_d, ret); + png_warning_parameter_signed(p, 1, PNG_NUMBER_FORMAT_d, ret); png_warning_parameter(p, 2, err); if (png_ptr->zstream.msg) @@ -1549,7 +1549,7 @@ png_check_keyword(png_structp png_ptr, png_const_charp key, png_charpp new_key) PNG_WARNING_PARAMETERS(p) png_warning_parameter_unsigned(p, 1, PNG_NUMBER_FORMAT_02x, - *ikp); + (png_byte)*ikp); png_formatted_warning(png_ptr, p, "invalid keyword character 0x@1"); *dp = ' '; } diff --git a/scripts/pnglibconf.dfa b/scripts/pnglibconf.dfa index 3d534a186c26893ca34fa8e49934d0a978d1af5b..79377e3ee44e17c5abde696aba8d2e9baebd995c 100644 --- a/scripts/pnglibconf.dfa +++ b/scripts/pnglibconf.dfa @@ -311,11 +311,13 @@ option READ_BGR requires READ_TRANSFORMS option READ_SWAP requires READ_TRANSFORMS READ_16BIT option READ_PACKSWAP requires READ_TRANSFORMS option READ_INVERT requires READ_TRANSFORMS -option READ_BACKGROUND requires READ_TRANSFORMS +option READ_BACKGROUND requires READ_TRANSFORMS enables READ_STRIP_ALPHA option READ_16_TO_8 requires READ_TRANSFORMS option READ_FILLER requires READ_TRANSFORMS option READ_GAMMA requires READ_TRANSFORMS enables READ_gAMA option READ_GRAY_TO_RGB requires READ_TRANSFORMS + +option READ_ALPHA_MODE requires READ_TRANSFORMS enables READ_GAMMA option READ_SWAP_ALPHA requires READ_TRANSFORMS option READ_INVERT_ALPHA requires READ_TRANSFORMS option READ_STRIP_ALPHA requires READ_TRANSFORMS diff --git a/scripts/pnglibconf.h.prebuilt b/scripts/pnglibconf.h.prebuilt index 663af797224e46b333842a4e53708fd91f89198c..e1046687b70a3add51b1124a2f235aae67515f8f 100644 --- a/scripts/pnglibconf.h.prebuilt +++ b/scripts/pnglibconf.h.prebuilt @@ -69,6 +69,7 @@ #define PNG_PROGRESSIVE_READ_SUPPORTED #define PNG_READ_16BIT_SUPPORTED #define PNG_READ_16_TO_8_SUPPORTED +#define PNG_READ_ALPHA_MODE_SUPPORTED #define PNG_READ_ANCILLARY_CHUNKS_SUPPORTED #define PNG_READ_BACKGROUND_SUPPORTED #define PNG_READ_BGR_SUPPORTED diff --git a/scripts/symbols.def b/scripts/symbols.def index 66a0120a80e7f894af8d4ccc06dc2547024c02ad..ef3b0287cec496275a06dc8d46c2ee5adc702ea8 100644 --- a/scripts/symbols.def +++ b/scripts/symbols.def @@ -232,3 +232,5 @@ EXPORTS png_set_text_compression_strategy @224 png_set_text_compression_window_bits @225 png_set_text_compression_method @226 + png_set_alpha_mode @227 + png_set_alpha_mode_fixed @228 diff --git a/test-pngvalid-full.sh b/test-pngvalid-full.sh index 1ab06f5d1114290809ddb6384f14c83cdd3510b2..4c0cf94f19501664f194ffe6b5556023ce699ae5 100755 --- a/test-pngvalid-full.sh +++ b/test-pngvalid-full.sh @@ -7,9 +7,18 @@ echo >> pngtest-log.txt echo "============ pngvalid-full.sh ==============" >> pngtest-log.txt echo "Running test-pngvalid-full.sh" -for gamma in threshold transform sbit 16-to-8 +for gamma in threshold transform sbit 16-to-8 background alpha-mode do - if ./pngvalid "--gamma-$gamma" >> pngtest-log.txt 2>&1 + # For the moment the composition calculation is performed with minimal + # accuracy, do this to work round the problem: + if test $gamma = background -o $gamma = alpha-mode + then + opts=--use-linear-precision + else + opts= + fi + + if ./pngvalid $opts "--gamma-$gamma" >> pngtest-log.txt 2>&1 then echo " PASS:" pngvalid "--gamma-$gamma" else