From 8c7c2cf01900dae9d0ec14c43e5003d864a90346 Mon Sep 17 00:00:00 2001 From: John Bowler Date: Fri, 3 Jan 2025 22:43:00 -0800 Subject: [PATCH] PNG v3: Support for mDCV and cLLI This adds APIs to get/set the two remaining new PNG-v3 colour space chunks. The mDCV API matches that of cHRM. Both chunks support floating point APIs (all values in the two chunks are real numbers). Both chunk have a new encoded type, a 4dp fixed point number, which cannot be represented in the existing png_fixed_point type so a png_uint_32 is used. Test examples for cICP, cLLI and mDCV are now in 'pngtest.png' and a necessary change to the pngunknown test has been made to accomodate the additions. Signed-off-by: John Bowler --- contrib/libtests/pngunknown.c | 24 ++++++ png.c | 20 +++++ png.h | 101 +++++++++++++++++++++- pngget.c | 109 +++++++++++++++++++++++- pnginfo.h | 18 ++++ pngpread.c | 16 ++++ pngpriv.h | 38 ++++++++- pngread.c | 20 +++++ pngrutil.c | 105 +++++++++++++++++++++++ pngset.c | 154 ++++++++++++++++++++++++++++++++++ pngtest.c | 47 +++++++++++ pngtest.png | Bin 8759 -> 8831 bytes pngwrite.c | 23 +++++ pngwutil.c | 44 ++++++++++ scripts/pnglibconf.dfa | 2 + 15 files changed, 716 insertions(+), 5 deletions(-) diff --git a/contrib/libtests/pngunknown.c b/contrib/libtests/pngunknown.c index 88133defbe..47a84d984d 100644 --- a/contrib/libtests/pngunknown.c +++ b/contrib/libtests/pngunknown.c @@ -113,6 +113,8 @@ typedef png_byte *png_const_bytep; #define png_PLTE PNG_U32( 80, 76, 84, 69) #define png_bKGD PNG_U32( 98, 75, 71, 68) #define png_cHRM PNG_U32( 99, 72, 82, 77) +#define png_cICP PNG_U32( 99, 73, 67, 80) /* PNGv3 */ +#define png_cLLI PNG_U32( 99, 76, 76, 73) /* PNGv3 */ #define png_eXIf PNG_U32(101, 88, 73, 102) /* registered July 2017 */ #define png_fRAc PNG_U32(102, 82, 65, 99) /* registered, not defined */ #define png_gAMA PNG_U32(103, 65, 77, 65) @@ -122,6 +124,7 @@ typedef png_byte *png_const_bytep; #define png_hIST PNG_U32(104, 73, 83, 84) #define png_iCCP PNG_U32(105, 67, 67, 80) #define png_iTXt PNG_U32(105, 84, 88, 116) +#define png_mDCV PNG_U32(109, 68, 67, 86) /* PNGv3 */ #define png_oFFs PNG_U32(111, 70, 70, 115) #define png_pCAL PNG_U32(112, 67, 65, 76) #define png_pHYs PNG_U32(112, 72, 89, 115) @@ -208,6 +211,20 @@ static struct 0, # else 1, +# endif + 1, START, 0 }, + { "cICP", PNG_INFO_cICP, png_cICP, +# ifdef PNG_READ_cICP_SUPPORTED + 0, +# else + 1, +# endif + 1, START, 0 }, + { "cLLI", PNG_INFO_cLLI, png_cLLI, +# ifdef PNG_READ_cLLI_SUPPORTED + 0, +# else + 1, # endif 1, START, 0 }, { "eXIf", PNG_INFO_eXIf, png_eXIf, @@ -245,6 +262,13 @@ static struct 1, # endif 1, ABSENT, 0 }, + { "mDCV", PNG_INFO_mDCV, png_mDCV, +# ifdef PNG_READ_mDCV_SUPPORTED + 0, +# else + 1, +# endif + 1, START, 0 }, { "oFFs", PNG_INFO_oFFs, png_oFFs, # ifdef PNG_READ_oFFs_SUPPORTED 0, diff --git a/png.c b/png.c index 36d3dd947c..ef49bd5a85 100644 --- a/png.c +++ b/png.c @@ -3390,6 +3390,26 @@ png_fixed(png_const_structrp png_ptr, double fp, png_const_charp text) } #endif +#if defined(PNG_FLOATING_POINT_SUPPORTED) && \ + !defined(PNG_FIXED_POINT_MACRO_SUPPORTED) && \ + (defined(PNG_cLLI_SUPPORTED) || defined(PNG_mDCV_SUPPORTED)) +png_uint_32 +png_fixed_ITU(png_const_structrp png_ptr, double fp, png_const_charp text) +{ + double r = floor(10000 * fp + .5); + + if (r > 2147483647. || r < 0) + png_fixed_error(png_ptr, text); + +# ifndef PNG_ERROR_TEXT_SUPPORTED + PNG_UNUSED(text) +# endif + + return (png_uint_32)r; +} +#endif + + #if defined(PNG_GAMMA_SUPPORTED) || defined(PNG_COLORSPACE_SUPPORTED) ||\ defined(PNG_INCH_CONVERSIONS_SUPPORTED) || defined(PNG_READ_pHYs_SUPPORTED) /* muldiv functions */ diff --git a/png.h b/png.h index 3e130a9987..3056c7b2e1 100644 --- a/png.h +++ b/png.h @@ -744,7 +744,21 @@ typedef png_unknown_chunk * * png_unknown_chunkpp; #define PNG_INFO_sCAL 0x4000U /* ESR, 1.0.6 */ #define PNG_INFO_IDAT 0x8000U /* ESR, 1.0.6 */ #define PNG_INFO_eXIf 0x10000U /* GR-P, 1.6.31 */ -#define PNG_INFO_cICP 0x20000U +#define PNG_INFO_cICP 0x20000U /* PNGv3: 1.6.45 */ +#define PNG_INFO_cLLI 0x40000U /* PNGv3: 1.6.45 */ +#define PNG_INFO_mDCV 0x80000U /* PNGv3: 1.6.45 */ +/* APNG: these chunks are stored as unknown, these flags are never set + * however they are provided as a convenience for implementors of APNG and + * avoids any merge conflicts. + * + * Private chunks: these chunk names violate the chunk name recommendations + * because the chunk definitions have no signature and because the private + * chunks with these names have been reserved. Private definitions should + * avoid them. + */ +#define PNG_INFO_acTL 0x100000U /* PNGv3: 1.6.45: unknown */ +#define PNG_INFO_fcTL 0x200000U /* PNGv3: 1.6.45: unknown */ +#define PNG_INFO_fdAT 0x400000U /* PNGv3: 1.6.45: unknown */ /* This is used for the transformation routines, as some of them * change these values for the row. It also should enable using @@ -1976,15 +1990,44 @@ PNG_FIXED_EXPORT(233, void, png_set_cHRM_XYZ_fixed, (png_const_structrp png_ptr, #ifdef PNG_cICP_SUPPORTED PNG_EXPORT(250, png_uint_32, png_get_cICP, (png_const_structrp png_ptr, - png_inforp info_ptr, png_bytep colour_primaries, + png_const_inforp info_ptr, png_bytep colour_primaries, png_bytep transfer_function, png_bytep matrix_coefficients, png_bytep video_full_range_flag)); +#endif + +#ifdef PNG_cICP_SUPPORTED PNG_EXPORT(251, void, png_set_cICP, (png_const_structrp png_ptr, png_inforp info_ptr, png_byte colour_primaries, png_byte transfer_function, png_byte matrix_coefficients, png_byte video_full_range_flag)); #endif +#ifdef PNG_cLLI_SUPPORTED +PNG_FP_EXPORT(252, png_uint_32, png_get_cLLI, (png_const_structrp png_ptr, + png_const_inforp info_ptr, double *maximum_content_light_level, + double *maximum_frame_average_light_level)); +PNG_FIXED_EXPORT(253, png_uint_32, png_get_cLLI_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr, + /* The values below are in cd/m2 (nits) and are scaled by 10,000; not + * 100,000 as in the case of png_fixed_point. + */ + png_uint_32p maximum_content_light_level_scaled_by_10000, + png_uint_32p maximum_frame_average_light_level_scaled_by_10000)); +#endif + +#ifdef PNG_cLLI_SUPPORTED +PNG_FP_EXPORT(254, void, png_set_cLLI, (png_const_structrp png_ptr, + png_inforp info_ptr, double maximum_content_light_level, + double maximum_frame_average_light_level)); +PNG_FIXED_EXPORT(255, void, png_set_cLLI_fixed, (png_const_structrp png_ptr, + png_inforp info_ptr, + /* The values below are in cd/m2 (nits) and are scaled by 10,000; not + * 100,000 as in the case of png_fixed_point. + */ + png_uint_32 maximum_content_light_level_scaled_by_10000, + png_uint_32 maximum_frame_average_light_level_scaled_by_10000)); +#endif + #ifdef PNG_eXIf_SUPPORTED PNG_EXPORT(246, png_uint_32, png_get_eXIf, (png_const_structrp png_ptr, png_inforp info_ptr, png_bytep *exif)); @@ -2029,6 +2072,60 @@ PNG_EXPORT(144, void, png_set_IHDR, (png_const_structrp png_ptr, int color_type, int interlace_method, int compression_method, int filter_method)); +#ifdef PNG_mDCV_SUPPORTED +PNG_FP_EXPORT(256, png_uint_32, png_get_mDCV, (png_const_structrp png_ptr, + png_const_inforp info_ptr, + /* The chromaticities of the mastering display. As cHRM, but independent of + * the encoding endpoints in cHRM, or cICP, or iCCP. These values will + * always be in the range 0 to 1.3107. + */ + double *white_x, double *white_y, double *red_x, double *red_y, + double *green_x, double *green_y, double *blue_x, double *blue_y, + /* Mastering display luminance in cd/m2 (nits). */ + double *mastering_display_maximum_luminance, + double *mastering_display_minimum_luminance)); + +PNG_FIXED_EXPORT(257, png_uint_32, png_get_mDCV_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr, + png_fixed_point *int_white_x, png_fixed_point *int_white_y, + png_fixed_point *int_red_x, png_fixed_point *int_red_y, + png_fixed_point *int_green_x, png_fixed_point *int_green_y, + png_fixed_point *int_blue_x, png_fixed_point *int_blue_y, + /* Mastering display luminance in cd/m2 (nits) multiplied (scaled) by + * 10,000. + */ + png_uint_32p mastering_display_maximum_luminance_scaled_by_10000, + png_uint_32p mastering_display_minimum_luminance_scaled_by_10000)); +#endif + +#ifdef PNG_mDCV_SUPPORTED +PNG_FP_EXPORT(258, void, png_set_mDCV, (png_const_structrp png_ptr, + png_inforp info_ptr, + /* The chromaticities of the mastering display. As cHRM, but independent of + * the encoding endpoints in cHRM, or cICP, or iCCP. + */ + double white_x, double white_y, double red_x, double red_y, double green_x, + double green_y, double blue_x, double blue_y, + /* Mastering display luminance in cd/m2 (nits). */ + double mastering_display_maximum_luminance, + double mastering_display_minimum_luminance)); + +PNG_FIXED_EXPORT(259, void, png_set_mDCV_fixed, (png_const_structrp png_ptr, + png_inforp info_ptr, + /* The admissible range of these values is not the full range of a PNG + * fixed point value. Negative values cannot be encoded and the maximum + * value is about 1.3 */ + png_fixed_point int_white_x, png_fixed_point int_white_y, + png_fixed_point int_red_x, png_fixed_point int_red_y, + png_fixed_point int_green_x, png_fixed_point int_green_y, + png_fixed_point int_blue_x, png_fixed_point int_blue_y, + /* These are PNG unsigned 4 byte values: 31-bit unsigned values. The MSB + * must be zero. + */ + png_uint_32 mastering_display_maximum_luminance_scaled_by_10000, + png_uint_32 mastering_display_minimum_luminance_scaled_by_10000)); +#endif + #ifdef PNG_oFFs_SUPPORTED PNG_EXPORT(145, png_uint_32, png_get_oFFs, (png_const_structrp png_ptr, png_const_inforp info_ptr, png_int_32 *offset_x, png_int_32 *offset_y, diff --git a/pngget.c b/pngget.c index 9be8814322..2c646aee1f 100644 --- a/pngget.c +++ b/pngget.c @@ -787,7 +787,7 @@ png_get_sPLT(png_const_structrp png_ptr, png_inforp info_ptr, #ifdef PNG_cICP_SUPPORTED png_uint_32 PNGAPI png_get_cICP(png_const_structrp png_ptr, - png_inforp info_ptr, png_bytep colour_primaries, + png_const_inforp info_ptr, png_bytep colour_primaries, png_bytep transfer_function, png_bytep matrix_coefficients, png_bytep video_full_range_flag) { @@ -805,10 +805,115 @@ png_get_cICP(png_const_structrp png_ptr, return (PNG_INFO_cICP); } - return (0); + return 0; } #endif +#ifdef PNG_cLLI_SUPPORTED +# ifdef PNG_FIXED_POINT_SUPPORTED +png_uint_32 PNGAPI +png_get_cLLI_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_uint_32p maxCLL, + png_uint_32p maxFALL) +{ + png_debug1(1, "in %s retrieval function", "cLLI"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_cLLI) != 0) + { + if (maxCLL != NULL) *maxCLL = info_ptr->maxCLL; + if (maxFALL != NULL) *maxFALL = info_ptr->maxFALL; + return PNG_INFO_cLLI; + } + + return 0; +} +# endif + +# ifdef PNG_FLOATING_POINT_SUPPORTED +png_uint_32 PNGAPI +png_get_cLLI(png_const_structrp png_ptr, png_const_inforp info_ptr, + double *maxCLL, double *maxFALL) +{ + png_debug1(1, "in %s retrieval function", "cLLI(float)"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_cLLI) != 0) + { + if (maxCLL != NULL) *maxCLL = info_ptr->maxCLL * .0001; + if (maxFALL != NULL) *maxFALL = info_ptr->maxFALL * .0001; + return PNG_INFO_cLLI; + } + + return 0; +} +# endif +#endif /* cLLI */ + +#ifdef PNG_mDCV_SUPPORTED +# ifdef PNG_FIXED_POINT_SUPPORTED +png_uint_32 PNGAPI +png_get_mDCV_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_fixed_point *white_x, png_fixed_point *white_y, + png_fixed_point *red_x, png_fixed_point *red_y, + png_fixed_point *green_x, png_fixed_point *green_y, + png_fixed_point *blue_x, png_fixed_point *blue_y, + png_uint_32p mastering_maxDL, png_uint_32p mastering_minDL) +{ + png_debug1(1, "in %s retrieval function", "mDCV"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_mDCV) != 0) + { + if (white_x != NULL) *white_x = info_ptr->mastering_white_x * 2; + if (white_y != NULL) *white_y = info_ptr->mastering_white_y * 2; + if (red_x != NULL) *red_x = info_ptr->mastering_red_x * 2; + if (red_y != NULL) *red_y = info_ptr->mastering_red_y * 2; + if (green_x != NULL) *green_x = info_ptr->mastering_green_x * 2; + if (green_y != NULL) *green_y = info_ptr->mastering_green_y * 2; + if (blue_x != NULL) *blue_x = info_ptr->mastering_blue_x * 2; + if (blue_y != NULL) *blue_y = info_ptr->mastering_blue_y * 2; + if (mastering_maxDL != NULL) *mastering_maxDL = info_ptr->mastering_maxDL; + if (mastering_minDL != NULL) *mastering_minDL = info_ptr->mastering_minDL; + return PNG_INFO_mDCV; + } + + return 0; +} +# endif + +# ifdef PNG_FLOATING_POINT_SUPPORTED +png_uint_32 PNGAPI +png_get_mDCV(png_const_structrp png_ptr, png_const_inforp info_ptr, + double *white_x, double *white_y, double *red_x, double *red_y, + double *green_x, double *green_y, double *blue_x, double *blue_y, + double *mastering_maxDL, double *mastering_minDL) +{ + png_debug1(1, "in %s retrieval function", "mDCV(float)"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_mDCV) != 0) + { + if (white_x != NULL) *white_x = info_ptr->mastering_white_x * .00002; + if (white_y != NULL) *white_y = info_ptr->mastering_white_y * .00002; + if (red_x != NULL) *red_x = info_ptr->mastering_red_x * .00002; + if (red_y != NULL) *red_y = info_ptr->mastering_red_y * .00002; + if (green_x != NULL) *green_x = info_ptr->mastering_green_x * .00002; + if (green_y != NULL) *green_y = info_ptr->mastering_green_y * .00002; + if (blue_x != NULL) *blue_x = info_ptr->mastering_blue_x * .00002; + if (blue_y != NULL) *blue_y = info_ptr->mastering_blue_y * .00002; + if (mastering_maxDL != NULL) + *mastering_maxDL = info_ptr->mastering_maxDL * .0001; + if (mastering_minDL != NULL) + *mastering_minDL = info_ptr->mastering_minDL * .0001; + return PNG_INFO_mDCV; + } + + return 0; +} +# endif /* FLOATING_POINT */ +#endif /* mDCV */ + #ifdef PNG_eXIf_SUPPORTED png_uint_32 PNGAPI png_get_eXIf(png_const_structrp png_ptr, png_inforp info_ptr, diff --git a/pnginfo.h b/pnginfo.h index e85420c1ad..4e55da3975 100644 --- a/pnginfo.h +++ b/pnginfo.h @@ -115,6 +115,24 @@ struct png_info_def png_uint_32 iccp_proflen; /* ICC profile data length */ #endif +#ifdef PNG_cLLI_SUPPORTED + png_uint_32 maxCLL; /* cd/m2 (nits) * 10,000 */ + png_uint_32 maxFALL; +#endif + +#ifdef PNG_mDCV_SUPPORTED + png_uint_16 mastering_red_x; /* CIE (xy) x * 50,000 */ + png_uint_16 mastering_red_y; + png_uint_16 mastering_green_x; + png_uint_16 mastering_green_y; + png_uint_16 mastering_blue_x; + png_uint_16 mastering_blue_y; + png_uint_16 mastering_white_x; + png_uint_16 mastering_white_y; + png_uint_32 mastering_maxDL; /* cd/m2 (nits) * 10,000 */ + png_uint_32 mastering_minDL; +#endif + #ifdef PNG_TEXT_SUPPORTED /* The tEXt, and zTXt chunks contain human-readable textual data in * uncompressed, compressed, and optionally compressed forms, respectively. diff --git a/pngpread.c b/pngpread.c index 1bf880eabb..d67a5676fc 100644 --- a/pngpread.c +++ b/pngpread.c @@ -316,6 +316,22 @@ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) png_handle_cICP(png_ptr, info_ptr, png_ptr->push_length); } +#endif +#ifdef PNG_READ_cLLI_SUPPORTED + else if (png_ptr->chunk_name == png_cLLI) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_cLLI(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_mDCV_SUPPORTED + else if (png_ptr->chunk_name == png_mDCV) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_mDCV(png_ptr, info_ptr, png_ptr->push_length); + } + #endif #ifdef PNG_READ_eXIf_SUPPORTED else if (png_ptr->chunk_name == png_eXIf) diff --git a/pngpriv.h b/pngpriv.h index 84f77c3508..aeda39f0ae 100644 --- a/pngpriv.h +++ b/pngpriv.h @@ -782,6 +782,8 @@ #ifdef PNG_FIXED_POINT_MACRO_SUPPORTED #define png_fixed(png_ptr, fp, s) ((fp) <= 21474 && (fp) >= -21474 ?\ ((png_fixed_point)(100000 * (fp))) : (png_fixed_error(png_ptr, s),0)) +#define png_fixed_ITU(png_ptr, fp, s) ((fp) <= 214748 && (fp) >= 0 ?\ + ((png_uint_32)(10000 * (fp))) : (png_fixed_error(png_ptr, s),0)) #endif /* else the corresponding function is defined below, inside the scope of the * cplusplus test. @@ -834,7 +836,8 @@ #define png_PLTE PNG_U32( 80, 76, 84, 69) #define png_bKGD PNG_U32( 98, 75, 71, 68) #define png_cHRM PNG_U32( 99, 72, 82, 77) -#define png_cICP PNG_U32( 99, 73, 67, 80) +#define png_cICP PNG_U32( 99, 73, 67, 80) /* PNGv3 */ +#define png_cLLI PNG_U32( 99, 76, 76, 73) /* PNGv3 */ #define png_eXIf PNG_U32(101, 88, 73, 102) /* registered July 2017 */ #define png_fRAc PNG_U32(102, 82, 65, 99) /* registered, not defined */ #define png_gAMA PNG_U32(103, 65, 77, 65) @@ -844,6 +847,7 @@ #define png_hIST PNG_U32(104, 73, 83, 84) #define png_iCCP PNG_U32(105, 67, 67, 80) #define png_iTXt PNG_U32(105, 84, 88, 116) +#define png_mDCV PNG_U32(109, 68, 67, 86) /* PNGv3 */ #define png_oFFs PNG_U32(111, 70, 70, 115) #define png_pCAL PNG_U32(112, 67, 65, 76) #define png_pHYs PNG_U32(112, 72, 89, 115) @@ -971,6 +975,7 @@ PNG_INTERNAL_FUNCTION(void,png_free_buffer_list,(png_structrp png_ptr, !defined(PNG_FIXED_POINT_MACRO_SUPPORTED) && \ (defined(PNG_gAMA_SUPPORTED) || defined(PNG_cHRM_SUPPORTED) || \ defined(PNG_sCAL_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) || \ + defined(PNG_mDCV_SUPPORTED) || \ defined(PNG_READ_RGB_TO_GRAY_SUPPORTED)) || \ (defined(PNG_sCAL_SUPPORTED) && \ defined(PNG_FLOATING_ARITHMETIC_SUPPORTED)) @@ -978,6 +983,13 @@ PNG_INTERNAL_FUNCTION(png_fixed_point,png_fixed,(png_const_structrp png_ptr, double fp, png_const_charp text),PNG_EMPTY); #endif +#if defined(PNG_FLOATING_POINT_SUPPORTED) && \ + !defined(PNG_FIXED_POINT_MACRO_SUPPORTED) && \ + (defined(PNG_cLLI_SUPPORTED) || defined(PNG_mDCV_SUPPORTED)) +PNG_INTERNAL_FUNCTION(png_uint_32,png_fixed_ITU,(png_const_structrp png_ptr, + double fp, png_const_charp text),PNG_EMPTY); +#endif + /* Check the user version string for compatibility, returns false if the version * numbers aren't compatible. */ @@ -1137,6 +1149,20 @@ PNG_INTERNAL_FUNCTION(void,png_write_cICP,(png_structrp png_ptr, png_byte matrix_coefficients, png_byte video_full_range_flag), PNG_EMPTY); #endif +#ifdef PNG_WRITE_cLLI_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_cLLI_fixed,(png_structrp png_ptr, + png_uint_32 maxCLL, png_uint_32 maxFALL), PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_mDCV_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_mDCV_fixed,(png_structrp png_ptr, + png_uint_16 red_x, png_uint_16 red_y, + png_uint_16 green_x, png_uint_16 green_y, + png_uint_16 blue_x, png_uint_16 blue_y, + png_uint_16 white_x, png_uint_16 white_y, + png_uint_32 maxDL, png_uint_32 minDL), PNG_EMPTY); +#endif + #ifdef PNG_WRITE_sRGB_SUPPORTED PNG_INTERNAL_FUNCTION(void,png_write_sRGB,(png_structrp png_ptr, int intent),PNG_EMPTY); @@ -1485,6 +1511,11 @@ PNG_INTERNAL_FUNCTION(void,png_handle_cICP,(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); #endif +#ifdef PNG_READ_cLLI_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_cLLI,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + #ifdef PNG_READ_eXIf_SUPPORTED PNG_INTERNAL_FUNCTION(void,png_handle_eXIf,(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); @@ -1510,6 +1541,11 @@ PNG_INTERNAL_FUNCTION(void,png_handle_iTXt,(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); #endif +#ifdef PNG_READ_mDCV_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_mDCV,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + #ifdef PNG_READ_oFFs_SUPPORTED PNG_INTERNAL_FUNCTION(void,png_handle_oFFs,(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); diff --git a/pngread.c b/pngread.c index 49e19a4960..73192f75e1 100644 --- a/pngread.c +++ b/pngread.c @@ -179,6 +179,11 @@ png_read_info(png_structrp png_ptr, png_inforp info_ptr) png_handle_cICP(png_ptr, info_ptr, length); #endif +#ifdef PNG_READ_cLLI_SUPPORTED + else if (chunk_name == png_cLLI) + png_handle_cLLI(png_ptr, info_ptr, length); +#endif + #ifdef PNG_READ_eXIf_SUPPORTED else if (chunk_name == png_eXIf) png_handle_eXIf(png_ptr, info_ptr, length); @@ -194,6 +199,11 @@ png_read_info(png_structrp png_ptr, png_inforp info_ptr) png_handle_hIST(png_ptr, info_ptr, length); #endif +#ifdef PNG_READ_mDCV_SUPPORTED + else if (chunk_name == png_mDCV) + png_handle_mDCV(png_ptr, info_ptr, length); +#endif + #ifdef PNG_READ_oFFs_SUPPORTED else if (chunk_name == png_oFFs) png_handle_oFFs(png_ptr, info_ptr, length); @@ -861,6 +871,11 @@ png_read_end(png_structrp png_ptr, png_inforp info_ptr) png_handle_cICP(png_ptr, info_ptr, length); #endif +#ifdef PNG_READ_cLLI_SUPPORTED + else if (chunk_name == png_cLLI) + png_handle_cLLI(png_ptr, info_ptr, length); +#endif + #ifdef PNG_READ_eXIf_SUPPORTED else if (chunk_name == png_eXIf) png_handle_eXIf(png_ptr, info_ptr, length); @@ -876,6 +891,11 @@ png_read_end(png_structrp png_ptr, png_inforp info_ptr) png_handle_hIST(png_ptr, info_ptr, length); #endif +#ifdef PNG_READ_mDCV_SUPPORTED + else if (chunk_name == png_mDCV) + png_handle_mDCV(png_ptr, info_ptr, length); +#endif + #ifdef PNG_READ_oFFs_SUPPORTED else if (chunk_name == png_oFFs) png_handle_oFFs(png_ptr, info_ptr, length); diff --git a/pngrutil.c b/pngrutil.c index 7c609b4b48..a8867cb299 100644 --- a/pngrutil.c +++ b/pngrutil.c @@ -2087,6 +2087,111 @@ png_handle_cICP(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) } #endif +#ifdef PNG_READ_cLLI_SUPPORTED +void /* PRIVATE */ +png_handle_cLLI(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte buf[8]; + + png_debug(1, "in png_handle_cLLI"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & (PNG_HAVE_IDAT|PNG_HAVE_PLTE)) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_cLLI) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + else if (length != 8) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, 8); + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + /* The error checking happens here, this puts it in just one place: */ + png_set_cLLI_fixed(png_ptr, info_ptr, png_get_uint_32(buf), + png_get_uint_32(buf+4)); +} +#endif + +#ifdef PNG_READ_mDCV_SUPPORTED +void /* PRIVATE */ +png_handle_mDCV(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte buf[24]; + + png_debug(1, "in png_handle_mDCV"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & (PNG_HAVE_IDAT|PNG_HAVE_PLTE)) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_mDCV) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + else if (length != 24) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, 24); + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + /* The error checking happens here, this puts it in just one place. The + * odd /50000 scaling factor makes it more difficult but the (x.y) values are + * only two bytes so a <<1 is safe. + * + * WARNING: the PNG specification defines the cHRM chunk to **start** with + * the white point (x,y). The W3C PNG v3 specification puts the white point + * **after* R,G,B. The x,y values in mDCV are also scaled by 50,000 and + * stored in just two bytes, whereas those in cHRM are scaled by 100,000 and + * stored in four bytes. This is very, very confusing. These APIs remove + * the confusion by copying the existing, well established, API. + */ + png_set_mDCV_fixed(png_ptr, info_ptr, + png_get_uint_16(buf+12U) << 1U, /* white x */ + png_get_uint_16(buf+14U) << 1U, /* white y */ + png_get_uint_16(buf+ 0U) << 1U, /* red x */ + png_get_uint_16(buf+ 2U) << 1U, /* red y */ + png_get_uint_16(buf+ 4U) << 1U, /* green x */ + png_get_uint_16(buf+ 6U) << 1U, /* green y */ + png_get_uint_16(buf+ 8U) << 1U, /* blue x */ + png_get_uint_16(buf+10U) << 1U, /* blue y */ + png_get_uint_32(buf+16U), /* peak luminance */ + png_get_uint_32(buf+20U));/* minimum perceivable luminance */ +} +#endif + #ifdef PNG_READ_eXIf_SUPPORTED void /* PRIVATE */ png_handle_eXIf(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) diff --git a/pngset.c b/pngset.c index 462b50cf2a..8e2b869d62 100644 --- a/pngset.c +++ b/pngset.c @@ -159,6 +159,158 @@ png_set_cICP(png_const_structrp png_ptr, png_inforp info_ptr, } #endif /* cICP */ +#ifdef PNG_cLLI_SUPPORTED +void PNGFAPI +png_set_cLLI_fixed(png_const_structrp png_ptr, png_inforp info_ptr, + /* The values below are in cd/m2 (nits) and are scaled by 10,000; not + * 100,000 as in the case of png_fixed_point. + */ + png_uint_32 maxCLL, png_uint_32 maxFALL) +{ + png_debug1(1, "in %s storage function", "cLLI"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + /* Check the light level range: */ + if (maxCLL > 0x7FFFFFFFU || maxFALL > 0x7FFFFFFFU) + { + /* The limit is 200kcd/m2; somewhat bright but not inconceivable because + * human vision is said to run up to 100Mcd/m2. The sun is about 2Gcd/m2. + * + * The reference sRGB monitor is 80cd/m2 and the limit of PQ encoding is + * 2kcd/m2. + */ + png_chunk_report(png_ptr, "cLLI light level exceeds PNG limit", + PNG_CHUNK_WRITE_ERROR); + return; + } + + info_ptr->maxCLL = maxCLL; + info_ptr->maxFALL = maxFALL; + info_ptr->valid |= PNG_INFO_cLLI; +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_cLLI(png_const_structrp png_ptr, png_inforp info_ptr, + double maxCLL, double maxFALL) +{ + png_set_cLLI_fixed(png_ptr, info_ptr, + png_fixed_ITU(png_ptr, maxCLL, "png_set_cLLI(maxCLL)"), + png_fixed_ITU(png_ptr, maxFALL, "png_set_cLLI(maxFALL)")); +} +# endif /* FLOATING_POINT */ +#endif /* cLLI */ + +#ifdef PNG_mDCV_SUPPORTED +static png_uint_16 +png_ITU_fixed_16(png_const_structrp png_ptr, png_fixed_point v, + png_const_charp text) +{ + /* Return a safe uint16_t value scaled according to the ITU H273 rules for + * 16-bit display chromaticities. Functions like the corresponding + * png_fixed() internal function with regard to errors: it's an error on + * write, a chunk_benign_error on read: See the definition of + * png_chunk_report in pngpriv.h. + */ + v /= 2; /* rounds to 0 in C: avoids insignificant arithmetic errors */ + if (v > 65535 || v < 0) + png_fixed_error(png_ptr, text); + +# ifndef PNG_ERROR_TEXT_SUPPORTED + PNG_UNUSED(text) +# endif + + return (png_uint_16)/*SAFE*/v; +} + +void PNGAPI +png_set_mDCV_fixed(png_const_structrp png_ptr, png_inforp info_ptr, + png_fixed_point white_x, png_fixed_point white_y, + png_fixed_point red_x, png_fixed_point red_y, + png_fixed_point green_x, png_fixed_point green_y, + png_fixed_point blue_x, png_fixed_point blue_y, + png_uint_32 maxDL, + png_uint_32 minDL) +{ + png_uint_16 rx, ry, gx, gy, bx, by, wx, wy; + + png_debug1(1, "in %s storage function", "mDCV"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + /* Check the input values to ensure they are in the expected range: */ + rx = png_ITU_fixed_16(png_ptr, red_x, "png_set_mDCV(red(x))"); + ry = png_ITU_fixed_16(png_ptr, red_y, "png_set_mDCV(red(y))"); + gx = png_ITU_fixed_16(png_ptr, green_x, "png_set_mDCV(green(x))"); + gy = png_ITU_fixed_16(png_ptr, green_y, "png_set_mDCV(green(y))"); + bx = png_ITU_fixed_16(png_ptr, blue_x, "png_set_mDCV(blue(x))"); + by = png_ITU_fixed_16(png_ptr, blue_y, "png_set_mDCV(blue(y))"); + wx = png_ITU_fixed_16(png_ptr, white_x, "png_set_mDCV(white(x))"); + wy = png_ITU_fixed_16(png_ptr, white_y, "png_set_mDCV(white(y))"); + + /* Check the light level range: */ + if (maxDL > 0x7FFFFFFFU || minDL > 0x7FFFFFFFU) + { + /* The limit is 200kcd/m2; somewhat bright but not inconceivable because + * human vision is said to run up to 100Mcd/m2. The sun is about 2Gcd/m2. + * + * The reference sRGB monitor is 80cd/m2 and the limit of PQ encoding is + * 2kcd/m2. + */ + png_chunk_report(png_ptr, "mDCV display light level exceeds PNG limit", + PNG_CHUNK_WRITE_ERROR); + return; + } + + /* All values are safe, the settings are accepted. + * + * IMPLEMENTATION NOTE: in practice the values can be checked and assigned + * but the result is confusing if a writing app calls png_set_mDCV more than + * once, the second time with an invalid value. This approach is more + * obviously correct at the cost of typing and a very slight machine + * overhead. + */ + info_ptr->mastering_red_x = rx; + info_ptr->mastering_red_y = ry; + info_ptr->mastering_green_x = gx; + info_ptr->mastering_green_y = gy; + info_ptr->mastering_blue_x = bx; + info_ptr->mastering_blue_y = by; + info_ptr->mastering_white_x = wx; + info_ptr->mastering_white_y = wy; + info_ptr->mastering_maxDL = maxDL; + info_ptr->mastering_minDL = minDL; + info_ptr->valid |= PNG_INFO_mDCV; +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_mDCV(png_const_structrp png_ptr, png_inforp info_ptr, + double white_x, double white_y, double red_x, double red_y, double green_x, + double green_y, double blue_x, double blue_y, + double maxDL, double minDL) +{ + png_set_mDCV_fixed(png_ptr, info_ptr, + /* The ITU approach is to scale by 50,000, not 100,000 so just divide + * the input values by 2 and use png_fixed: + */ + png_fixed(png_ptr, white_x / 2, "png_set_mDCV(white(x))"), + png_fixed(png_ptr, white_y / 2, "png_set_mDCV(white(y))"), + png_fixed(png_ptr, red_x / 2, "png_set_mDCV(red(x))"), + png_fixed(png_ptr, red_y / 2, "png_set_mDCV(red(y))"), + png_fixed(png_ptr, green_x / 2, "png_set_mDCV(green(x))"), + png_fixed(png_ptr, green_y / 2, "png_set_mDCV(green(y))"), + png_fixed(png_ptr, blue_x / 2, "png_set_mDCV(blue(x))"), + png_fixed(png_ptr, blue_y / 2, "png_set_mDCV(blue(y))"), + png_fixed_ITU(png_ptr, maxDL, "png_set_mDCV(maxDL)"), + png_fixed_ITU(png_ptr, minDL, "png_set_mDCV(minDL)")); +} +# endif /* FLOATING_POINT */ +#endif /* mDCV */ + #ifdef PNG_eXIf_SUPPORTED void PNGAPI png_set_eXIf(png_const_structrp png_ptr, png_inforp info_ptr, @@ -1421,11 +1573,13 @@ png_set_keep_unknown_chunks(png_structrp png_ptr, int keep, 98, 75, 71, 68, '\0', /* bKGD */ 99, 72, 82, 77, '\0', /* cHRM */ 99, 73, 67, 80, '\0', /* cICP */ + 99, 76, 76, 73, '\0', /* cLLI */ 101, 88, 73, 102, '\0', /* eXIf */ 103, 65, 77, 65, '\0', /* gAMA */ 104, 73, 83, 84, '\0', /* hIST */ 105, 67, 67, 80, '\0', /* iCCP */ 105, 84, 88, 116, '\0', /* iTXt */ + 109, 68, 67, 86, '\0', /* mDCV */ 111, 70, 70, 115, '\0', /* oFFs */ 112, 67, 65, 76, '\0', /* pCAL */ 112, 72, 89, 115, '\0', /* pHYs */ diff --git a/pngtest.c b/pngtest.c index c02926693e..1e16b4ccdd 100644 --- a/pngtest.c +++ b/pngtest.c @@ -1142,6 +1142,30 @@ test_one_file(const char *inname, const char *outname) png_set_gAMA_fixed(write_ptr, write_info_ptr, gamma); } #endif +#ifdef PNG_cLLI_SUPPORTED + { + png_uint_32 maxCLL; + png_uint_32 maxFALL; + + if (png_get_cLLI_fixed(read_ptr, read_info_ptr, &maxCLL, &maxFALL) != 0) + png_set_cLLI_fixed(write_ptr, write_info_ptr, maxCLL, maxFALL); + } +#endif +#ifdef PNG_mDCV_SUPPORTED + { + png_fixed_point white_x, white_y, red_x, red_y, green_x, green_y, blue_x, + blue_y; + png_uint_32 maxDL; + png_uint_32 minDL; + + if (png_get_mDCV_fixed(read_ptr, read_info_ptr, &white_x, &white_y, + &red_x, &red_y, &green_x, &green_y, &blue_x, &blue_y, + &maxDL, &minDL) != 0) + png_set_mDCV_fixed(write_ptr, write_info_ptr, white_x, white_y, + red_x, red_y, green_x, green_y, blue_x, blue_y, + maxDL, minDL); + } +#endif #else /* Use floating point versions */ #ifdef PNG_FLOATING_POINT_SUPPORTED #ifdef PNG_cHRM_SUPPORTED @@ -1165,6 +1189,29 @@ test_one_file(const char *inname, const char *outname) png_set_gAMA(write_ptr, write_info_ptr, gamma); } #endif +#ifdef PNG_cLLI_SUPPORTED + { + double maxCLL; + double maxFALL; + + if (png_get_cLLI(read_ptr, read_info_ptr, &maxCLL, &maxFALL) != 0) + png_set_cLLI(write_ptr, write_info_ptr, maxCLL, maxFALL); + } +#endif +#ifdef PNG_mDCV_SUPPORTED + { + double white_x, white_y, red_x, red_y, green_x, green_y, blue_x, blue_y; + double maxDL; + double minDL; + + if (png_get_mDCV(read_ptr, read_info_ptr, &white_x, &white_y, + &red_x, &red_y, &green_x, &green_y, &blue_x, &blue_y, + &maxDL, &minDL) != 0) + png_set_mDCV(write_ptr, write_info_ptr, white_x, white_y, + red_x, red_y, green_x, green_y, blue_x, blue_y, + maxDL, minDL); + } +#endif #endif /* Floating point */ #endif /* Fixed point */ #ifdef PNG_cICP_SUPPORTED diff --git a/pngtest.png b/pngtest.png index cf620eb32637a6e8b36fc60e83cd54c96c765ee1..7dc251c1fed0f2e5edde96e32fe7df71cf0206bd 100644 GIT binary patch delta 80 zcmdn)^512Gi7Q94kB=vV?y&<5%)Ac%Y`N(S3=9&vF3w@@`z2rSPYV)ap3K0ccu1L< ifyWdmr7n;=W5;(OgC*J1Ie?LufpJczwb4dHHYEU+&=k!8 delta 10 RcmezGvfX8Z$;KLHB>)_}1XTb4 diff --git a/pngwrite.c b/pngwrite.c index 8b1b06c20c..f6d9d816c6 100644 --- a/pngwrite.c +++ b/pngwrite.c @@ -157,7 +157,30 @@ png_write_info_before_PLTE(png_structrp png_ptr, png_const_inforp info_ptr) * space priority. As above it therefore behooves libpng to write the colour * space chunks in the priority order so that a streaming app need not buffer * them. + * + * PNG v3: Chunks mDCV and cLLI provide ancillary information for the + * interpretation of the colourspace chunkgs but do not require support for + * those chunks so are outside the "COLORSPACE" check but before the write of + * the colourspace chunks themselves. */ +#ifdef PNG_WRITE_cLLI_SUPPORTED + if ((info_ptr->valid & PNG_INFO_cLLI) != 0) + { + png_write_cLLI_fixed(png_ptr, info_ptr->maxCLL, info_ptr->maxFALL); + } +#endif +#ifdef PNG_WRITE_mDCV_SUPPORTED + if ((info_ptr->valid & PNG_INFO_mDCV) != 0) + { + png_write_mDCV_fixed(png_ptr, + info_ptr->mastering_red_x, info_ptr->mastering_red_y, + info_ptr->mastering_green_x, info_ptr->mastering_green_y, + info_ptr->mastering_blue_x, info_ptr->mastering_blue_y, + info_ptr->mastering_white_x, info_ptr->mastering_white_y, + info_ptr->mastering_maxDL, info_ptr->mastering_minDL); + } +#endif + #ifdef PNG_COLORSPACE_SUPPORTED # ifdef PNG_WRITE_cICP_SUPPORTED /* Priority 4 */ if ((info_ptr->valid & PNG_INFO_cICP) != 0) diff --git a/pngwutil.c b/pngwutil.c index 8b2bf4e6d6..c98475a082 100644 --- a/pngwutil.c +++ b/pngwutil.c @@ -1511,6 +1511,50 @@ png_write_cICP(png_structrp png_ptr, } #endif +#ifdef PNG_WRITE_cLLI_SUPPORTED +void /* PRIVATE */ +png_write_cLLI_fixed(png_structrp png_ptr, png_uint_32 maxCLL, + png_uint_32 maxFALL) +{ + png_byte buf[8]; + + png_debug(1, "in png_write_cLLI_fixed"); + + png_save_uint_32(buf, maxCLL); + png_save_uint_32(buf + 4, maxFALL); + + png_write_complete_chunk(png_ptr, png_cLLI, buf, 8); +} +#endif + +#ifdef PNG_WRITE_mDCV_SUPPORTED +void /* PRIVATE */ +png_write_mDCV_fixed(png_structrp png_ptr, + png_uint_16 red_x, png_uint_16 red_y, + png_uint_16 green_x, png_uint_16 green_y, + png_uint_16 blue_x, png_uint_16 blue_y, + png_uint_16 white_x, png_uint_16 white_y, + png_uint_32 maxDL, png_uint_32 minDL) +{ + png_byte buf[24]; + + png_debug(1, "in png_write_mDCV_fixed"); + + png_save_uint_16(buf + 0, red_x); + png_save_uint_16(buf + 2, red_y); + png_save_uint_16(buf + 4, green_x); + png_save_uint_16(buf + 6, green_y); + png_save_uint_16(buf + 8, blue_x); + png_save_uint_16(buf + 10, blue_y); + png_save_uint_16(buf + 12, white_x); + png_save_uint_16(buf + 14, white_y); + png_save_uint_32(buf + 16, maxDL); + png_save_uint_32(buf + 20, minDL); + + png_write_complete_chunk(png_ptr, png_mDCV, buf, 24); +} +#endif + #ifdef PNG_WRITE_eXIf_SUPPORTED /* Write the Exif data */ void /* PRIVATE */ diff --git a/scripts/pnglibconf.dfa b/scripts/pnglibconf.dfa index 83000d618c..11b2e3a8e7 100644 --- a/scripts/pnglibconf.dfa +++ b/scripts/pnglibconf.dfa @@ -847,11 +847,13 @@ setting IDAT_READ_SIZE default PNG_ZBUF_SIZE chunk bKGD chunk cHRM enables COLORSPACE chunk cICP enables COLORSPACE, GAMMA +chunk cLLI chunk eXIf chunk gAMA enables GAMMA chunk hIST chunk iCCP enables COLORSPACE, GAMMA chunk iTXt enables TEXT +chunk mDCV chunk oFFs chunk pCAL chunk pHYs