diff --git a/png.h b/png.h index 2c403cb65..e714f7262 100644 --- a/png.h +++ b/png.h @@ -1646,14 +1646,50 @@ PNG_EXPORT(66, void, png_set_crc_action, (png_structrp png_ptr, int crit_action, * The set of filters may be changed at any time, the new values will affect the * next row written. * - * Prior to 1.7.0 it was only possible to add the filters that use the previous - * row if at least one of them was selected at the start of the write. - * - * In 1.7.0 if a filter is added which causes the previous row to be required - * (anywhere in the interlace pass after row 0) the use of the filter will be - * delayed until the row after the next one. + * The first time a filter is selected which requires the previous row (UP, AVG + * or PAETH) retention of the previous row is switched on. This means that if + * this is done after the first row in a pass the previous-row filter will not + * be considered until the row after the png_set_filter call. libpng issues a + * warning (via png_warning) in this case. * * The 'method' must match that passed to png_set_IHDR; it cannot be changed. + * + * If multiple filters are enabled libpng will select one according to the + * following rules: + * + * 1) On the first row of a pass UP is ignored if NONE is set and PAETH is + * ignored if SUB is set; this is because these filter pairs are equivalent + * when there is no previous row. + * + * PNG_WRITE_OPTIMIZE_FITLER_SUPPORTED: + * 2) If WRITE_OPTIMIZE_FILTER is supported and it has not been disabled by + * png_set_option(PNG_DISABLE_OPTIMIZE_FILTER, PNG_OPTION_ON) libpng tries + * all the filters in the list and selects the one which gives the shortest + * compressed row, favoring earlier filters. + * + * PNG_WRITE_HEURISTIC_FITLER_SUPPORTED: + * 3) If not (2) an WRITE_HEURISTIC_FILTER is supported and has not been + * disabled by png_set_option(PNG_DISABLE_HEURISTIC_FILTER, PNG_OPTION_ON) + * libpng tests the start of each row (a few thousand bytes at most) to see + * which filter is likely to produce best compression. + * + * 4) If neither (2) nor (3) libpng selects the first filter in the list (there + * is no warning that this will happen - check the #defines if you need to + * know.) + * + * If you intend to use 'previous row' filters in an image set either the UP or + * PAETH filter before the first call to png_write_row, depending on whether you + * want to use NONE or SUB on the first row. + * + * You can also select AVG on the first row; it uses half the value of the + * preceding byte as a predictor and is not likely to have very good + * performance. + * + * The WRITE_OPTIMIZE_FILTER option is slow and memory intensive, but it is + * likely to produce the smallest PNG file. Depending on the image data the + * HEURISTIC option may improve results and has little overall effect on + * compression speed, however it can sometimes produce larger files than not + * using any filtering. */ PNG_EXPORT(67, void, png_set_filter, (png_structrp png_ptr, int method, int filters)); @@ -3455,7 +3491,9 @@ PNG_EXPORT(240, int, png_image_write_to_stdio, (png_imagep image, FILE *file, #define PNG_EXTENSIONS 0 /* BOTH: enable or disable extensions */ #define PNG_MAXIMUM_INFLATE_WINDOW 2 /* SOFTWARE: force maximum window */ #define PNG_SKIP_sRGB_CHECK_PROFILE 4 /* SOFTWARE: Check ICC profile for sRGB */ -#define PNG_OPTION_NEXT 6 /* Next option - numbers must be even */ +#define PNG_DISABLE_HEURISTIC_FILTER 6 /* SOFTWARE: see png_set_filter */ +#define PNG_DISABLE_OPTIMIZE_FILTER 8 /* SOFTWARE: see png_set_filter */ +#define PNG_OPTION_NEXT 10 /* Next option - numbers must be even */ /* Return values: NOTE: there are four values and 'off' is *not* zero */ #define PNG_OPTION_UNSET 0 /* Unset - defaults to off */ diff --git a/pngtest.png b/pngtest.png index 92cc08e32..ea61708cb 100644 Binary files a/pngtest.png and b/pngtest.png differ diff --git a/pngwutil.c b/pngwutil.c index d6998e32f..03aa6a270 100644 --- a/pngwutil.c +++ b/pngwutil.c @@ -2035,8 +2035,9 @@ typedef struct } png_IDAT_compression_state; static int -png_compress_IDAT_test(png_structrp png_ptr, png_IDAT_compression_state *state, - z_stream *zstream, png_const_voidp input, uInt input_len, int flush) +png_compress_IDAT_test(png_structp png_ptr, z_stream *zstream, + png_compression_bufferp **ep, png_uint_32p output_lenp, + png_const_voidp input, uInt input_len, int flush) { png_uint_32 output_len = 0U; int ret; @@ -2049,8 +2050,7 @@ png_compress_IDAT_test(png_structrp png_ptr, png_IDAT_compression_state *state, * parameter: */ zstream->next_in = PNGZ_INPUT_CAST(png_voidcast(const Bytef*, input)); - ret = png_compress(png_ptr, zstream, &state->zbuffer_end, input_len, - &output_len, flush); + ret = png_compress(png_ptr, zstream, ep, input_len, &output_len, flush); implies(ret == Z_OK || ret == Z_FINISH, zstream->avail_in == 0U); zstream->next_in = NULL; zstream->avail_in = 0U; /* safety */ @@ -2058,7 +2058,7 @@ png_compress_IDAT_test(png_structrp png_ptr, png_IDAT_compression_state *state, /* If IDAT_size is set to PNG_UINT_31_MAX the length will be larger, but * not enough to overflow a png_uint_32. */ - state->zbuffer_len += output_len; + *output_lenp += output_len; return ret; } @@ -2067,15 +2067,8 @@ static void png_compress_IDAT(png_structp png_ptr, png_const_voidp input, uInt input_len, int flush) { - png_IDAT_compression_state state; - int ret; - - state.zbuffer_end = png_ptr->zbuffer_end; - state.zbuffer_len = png_ptr->zbuffer_len; - ret = png_compress_IDAT_test(png_ptr, &state, &png_ptr->zstream, input, - input_len, flush); - png_ptr->zbuffer_end = state.zbuffer_end; - png_ptr->zbuffer_len = state.zbuffer_len; + int ret = png_compress_IDAT_test(png_ptr, &png_ptr->zstream, + &png_ptr->zbuffer_end, &png_ptr->zbuffer_len, input, input_len, flush); /* Check the return code. */ if (ret == Z_OK || ret == Z_STREAM_END) @@ -2300,39 +2293,19 @@ filter_block_multibyte(unsigned int row_bytes, } static void -filter_row(png_structrp png_ptr, png_const_bytep prev_row, - png_bytep prev_pixels, png_const_bytep unfiltered_row, - unsigned int row_bits, unsigned int bpp, unsigned int filters_to_try, - int start_of_row, int end_of_image) +filter_block(png_const_bytep prev_row, png_bytep prev_pixels, + png_const_bytep unfiltered_row, unsigned int row_bits, + const unsigned int bpp, png_bytep sub_row, png_bytep up_row, + png_bytep avg_row, png_bytep paeth_row) { - /* filters_to_try identifies a single filter and it is not PNG_FILTER_NONE. - */ - unsigned int row_bytes = row_bits >> 3; /* complete bytes */ - png_byte filter = PNG_FILTER_VALUE_LAST /* not at start */; - png_byte filtered_row[PNG_ROW_BUFFER_SIZE]; - - debug((row_bits % bpp) == 0U); - - if (start_of_row) switch (filters_to_try) - { - case PNG_FILTER_SUB: filter = PNG_FILTER_VALUE_SUB; break; - case PNG_FILTER_UP: filter = PNG_FILTER_VALUE_UP; break; - case PNG_FILTER_AVG: filter = PNG_FILTER_VALUE_AVG; break; - case PNG_FILTER_PAETH: filter = PNG_FILTER_VALUE_PAETH; break; - default: - impossible("filter list"); - } + const unsigned int row_bytes = row_bits >> 3; /* complete bytes */ if (bpp <= 8U) { /* There may be a partial byte at the end. */ if (row_bytes > 0) - filter_block_singlebyte(row_bytes, - filters_to_try & PNG_FILTER_SUB ? filtered_row : NULL, - filters_to_try & PNG_FILTER_UP ? filtered_row : NULL, - filters_to_try & PNG_FILTER_AVG ? filtered_row : NULL, - filters_to_try & PNG_FILTER_PAETH ? filtered_row : NULL, - unfiltered_row, prev_row, prev_pixels); + filter_block_singlebyte(row_bytes, sub_row, up_row, avg_row, paeth_row, + unfiltered_row, prev_row, prev_pixels); /* The partial byte must be handled correctly here; both the previous row * value and the current value need to have non-present bits cleared. @@ -2351,48 +2324,136 @@ filter_row(png_structrp png_ptr, png_const_bytep prev_row, buffer[1U] = 0U; filter_block_singlebyte(1U, - filters_to_try & PNG_FILTER_SUB ? filtered_row+row_bytes : NULL, - filters_to_try & PNG_FILTER_UP ? filtered_row+row_bytes : NULL, - filters_to_try & PNG_FILTER_AVG ? filtered_row+row_bytes : NULL, - filters_to_try & PNG_FILTER_PAETH ? filtered_row+row_bytes : NULL, - buffer, buffer+1U, prev_pixels); - - ++row_bytes; /* for write_filtered_row below */ + sub_row == NULL ? NULL : sub_row+row_bytes, + up_row == NULL ? NULL : up_row+row_bytes, + avg_row == NULL ? NULL : avg_row+row_bytes, + paeth_row == NULL ? NULL : paeth_row+row_bytes, + buffer, buffer+1U, prev_pixels); } } else - { - debug((bpp & 7U) == 0U && row_bits == (row_bytes << 3)); filter_block_multibyte(row_bytes, bpp >> 3, - filters_to_try & PNG_FILTER_SUB ? filtered_row : NULL, - filters_to_try & PNG_FILTER_UP ? filtered_row : NULL, - filters_to_try & PNG_FILTER_AVG ? filtered_row : NULL, - filters_to_try & PNG_FILTER_PAETH ? filtered_row : NULL, + sub_row, up_row, avg_row, paeth_row, unfiltered_row, prev_row, prev_pixels); - } - - write_filtered_row(png_ptr, filtered_row, row_bytes, filter, end_of_image); } static void -find_filter(png_structrp png_ptr, png_const_bytep prev_row, +filter_row(png_structrp png_ptr, png_const_bytep prev_row, + png_bytep prev_pixels, png_const_bytep unfiltered_row, + unsigned int row_bits, unsigned int bpp, unsigned int filters_to_try, + int start_of_row, int end_of_image) +{ + /* filters_to_try identifies a single filter and it is not PNG_FILTER_NONE. + */ + png_byte filter = PNG_FILTER_VALUE_LAST /* not at start */; + png_byte filtered_row[PNG_ROW_BUFFER_SIZE]; + + affirm((row_bits+7U) >> 3 <= PNG_ROW_BUFFER_SIZE); + debug((row_bits % bpp) == 0U); + + if (start_of_row) switch (filters_to_try) + { + case PNG_FILTER_SUB: filter = PNG_FILTER_VALUE_SUB; break; + case PNG_FILTER_UP: filter = PNG_FILTER_VALUE_UP; break; + case PNG_FILTER_AVG: filter = PNG_FILTER_VALUE_AVG; break; + case PNG_FILTER_PAETH: filter = PNG_FILTER_VALUE_PAETH; break; + default: + impossible("filter list"); + } + + filter_block(prev_row, prev_pixels, unfiltered_row, row_bits, bpp, + filters_to_try & PNG_FILTER_SUB ? filtered_row : NULL, + filters_to_try & PNG_FILTER_UP ? filtered_row : NULL, + filters_to_try & PNG_FILTER_AVG ? filtered_row : NULL, + filters_to_try & PNG_FILTER_PAETH ? filtered_row : NULL); + + write_filtered_row(png_ptr, filtered_row, (row_bits+7U)>>3, filter, + end_of_image); +} + +/* These two #defines simplify writing code that depends on one or the other of + * the options being both supported and on: + */ +#ifdef PNG_WRITE_OPTIMIZE_FILTER_SUPPORTED +# define optimize_filters\ + (((png_ptr->options >> PNG_DISABLE_OPTIMIZE_FILTER)&3) != PNG_OPTION_ON) +#else +# define optimize_filters 0 +#endif + +#ifdef PNG_WRITE_HEURISTIC_FILTER_SUPPORTED +# define heuristic_filters\ + (((png_ptr->options >> PNG_DISABLE_HEURISTIC_FILTER)&3) != PNG_OPTION_ON) + +static unsigned int +select_filter_heuristically(png_structrp png_ptr, png_const_bytep prev_row, png_bytep prev_pixels, png_const_bytep unfiltered_row, unsigned int row_bits, unsigned int bpp, unsigned int filters_to_try, - int start_of_row, int end_of_image) + int end_of_image) { - /* filters_to_try identifies multiple filters, up to all five. */ - /* TODO: reimplement this, currently this just selects the first filter */ - filters_to_try &= -filters_to_try; - if (filters_to_try == PNG_FILTER_NONE) - write_unfiltered_rowbits(png_ptr, unfiltered_row, row_bits, - start_of_row ? PNG_FILTER_VALUE_NONE : PNG_FILTER_VALUE_LAST, - end_of_image); + const unsigned int row_bytes = (row_bits+7U) >> 3; + png_byte test_buffers[4][PNG_ROW_BUFFER_SIZE]; /* for each filter */ - else - filter_row(png_ptr, prev_row, prev_pixels, unfiltered_row, row_bits, bpp, - filters_to_try & -filters_to_try, start_of_row, end_of_image); + affirm(row_bytes <= PNG_ROW_BUFFER_SIZE); + debug((row_bits % bpp) == 0U); + + filter_block(prev_row, prev_pixels, unfiltered_row, row_bits, bpp, + test_buffers[PNG_FILTER_VALUE_SUB-1U], + test_buffers[PNG_FILTER_VALUE_UP-1U], + test_buffers[PNG_FILTER_VALUE_AVG-1U], + test_buffers[PNG_FILTER_VALUE_PAETH-1U]); + + /* Now check each buffer and the original row to see which is best; this is + * the heuristic. The test is on the number of separate code values in the + * buffer. Since the buffer is either the full row or PNG_ROW_BUFFER_SIZE + * bytes (or slightly less for RGB) we either find the true number of codes + * generated or we expect a count of average 8 per code. + */ + { + unsigned int filter_max = 257U; + png_byte best_filter, test_filter; + png_const_bytep best_row, test_row; + + for (best_filter = test_filter = PNG_FILTER_VALUE_NONE, + best_row = test_row = unfiltered_row; + test_filter < PNG_FILTER_VALUE_LAST; + test_row = test_buffers[test_filter], ++test_filter) + if ((filters_to_try & PNG_FILTER_MASK(test_filter)) != 0U) + { + unsigned int count = 1U, x; + png_byte code[256]; + + memset(code, 0, sizeof code); + code[test_filter] = 1U; + + for (x=0U; x < row_bytes; ++x) + { + const png_byte b = test_row[x]; + if (code[b] == 0) code[b] = 1U, ++count; + } + + if (count < filter_max) + filter_max = count, best_filter = test_filter, best_row = test_row; + } + + /* Calling write_unfiltered_rowbits is necessary here to deal with the + * clearly of a partial byte at the end. + */ + if (best_filter == PNG_FILTER_VALUE_NONE) + write_unfiltered_rowbits(png_ptr, unfiltered_row, row_bits, + PNG_FILTER_VALUE_NONE, end_of_image); + + else + write_filtered_row(png_ptr, best_row, row_bytes, best_filter, + end_of_image); + + return PNG_FILTER_MASK(best_filter); + } } +#else /* !WRITE_HEURISTIC_FILTER */ +# define heuristic_filters 0 +#endif /* !WRITE_HEURISTIC_FILTER */ /* This filters the row, chooses which filter to use, if it has not already * been specified by the application, and then writes the row out with the @@ -2420,7 +2481,8 @@ png_write_filter_row(png_structrp png_ptr, png_bytep prev_pixels, { /* Delaying initialization of the filter stuff. */ if (png_ptr->filter_mask == 0U) - png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_ALL_FILTERS); + png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, (optimize_filters || + heuristic_filters) ? PNG_ALL_FILTERS : PNG_NO_FILTERS); /* Now work out the filters to try for this row: */ filters_to_try = png_ptr->filter_mask; /* else caller must preserve */ @@ -2476,6 +2538,21 @@ png_write_filter_row(png_structrp png_ptr, png_bytep prev_pixels, filters_to_try &= PNG_BIC_MASK(PNG_FILTER_UP); # undef match } + + /* Is this a list of filters which can be simplified to a single filter? + * If there is no selection algorithm enabled do so now: + * + * (Errors in the logic here trigger the 'impossible' else below.) + */ +# ifdef PNG_WRITE_OPTIMIZE_FILTER_SUPPORTED + if (((png_ptr->options >> PNG_DISABLE_OPTIMIZE_FILTER) & 3) == + PNG_OPTION_ON) /* optimize supported but disabled */ +# endif /* WRITE_OPTIMIZE_FILTER */ +# ifdef PNG_WRITE_HEURISTIC_FILTER_SUPPORTED + if (((png_ptr->options >> PNG_DISABLE_HEURISTIC_FILTER) & 3) == + PNG_OPTION_ON) /* heuristic supported but disabled */ +# endif /* WRITE_HEURISTIC_FILTER */ + filters_to_try &= -filters_to_try; } /* start of row */ else if (prev_row != NULL) @@ -2502,10 +2579,25 @@ png_write_filter_row(png_structrp png_ptr, png_bytep prev_pixels, prev_pixels, unfiltered_row, row_bits, bpp, filters_to_try, x == 0, end_of_image); +# ifdef PNG_WRITE_OPTIMIZE_FILTER_SUPPORTED +#if 0 + else if (((png_ptr->options >> PNG_DISABLE_OPTIMIZE_FILTER) & 3) != + PNG_OPTION_ON) /* optimize supported and not disabled */ + impossible("optimize filters NYI"); +#endif +# endif /* WRITE_OPTIMIZE_FITLER */ +# ifdef PNG_WRITE_HEURISTIC_FILTER_SUPPORTED + /* The heuristic must select a single filter based on the first block of + * pixels: + */ + else if (((png_ptr->options >> PNG_DISABLE_HEURISTIC_FILTER) & 3) != + PNG_OPTION_ON) + filters_to_try = select_filter_heuristically(png_ptr, + first_row_in_pass ? NULL : prev_row, prev_pixels, unfiltered_row, + row_bits, bpp, filters_to_try, end_of_image); +# endif /* WRITE_HEURISTIC_FITLER */ else - find_filter(png_ptr, first_row_in_pass ? NULL : prev_row, - prev_pixels, unfiltered_row, row_bits, bpp, filters_to_try, x == 0, - end_of_image); + impossible("bad filter select logic"); /* Copy the current row into the previous row buffer, if available, unless * this is the last row in the pass, when there is no point. Note that diff --git a/scripts/pnglibconf.dfa b/scripts/pnglibconf.dfa index f01535457..bf903f3ea 100644 --- a/scripts/pnglibconf.dfa +++ b/scripts/pnglibconf.dfa @@ -921,7 +921,28 @@ option CONVERT_tIME requires WRITE_ANCILLARY_CHUNKS @# define PNG_NO_CONVERT_tIME @#endif +# Write filter options: +# +# WRITE_FILTER +# Enables code to do PNG row filtering on write. If not enabled rows will +# be written without filtering, the 'NONE' filter. This enables the +# png_set_filter interface allowing the application to select the filter +# used for each row. +# +# WRITE_HEURISTIC_FILTER +# Enables code to cause libpng to choose a filter from a set passed to +# png_set_filter. Without this code libpng just chooses the first filter in +# the list if multiple are given. +# +# WRITE_OPTIMIZE_FILTER +# Enables code to try all the filters in the list passed to png_set_filter +# and choose the one which results in the least number of compressed bytes +# added by the current row. +# +# See png.h for more description of these options. option WRITE_FILTER requires WRITE +option WRITE_HEURISTIC_FILTER requires WRITE_FILTER enables SET_OPTION +option WRITE_OPTIMIZE_FILTER requires WRITE_FILTER enables SET_OPTION # added at libpng-1.5.4 diff --git a/scripts/pnglibconf.h.prebuilt b/scripts/pnglibconf.h.prebuilt index d39a89ae3..f0b784717 100644 --- a/scripts/pnglibconf.h.prebuilt +++ b/scripts/pnglibconf.h.prebuilt @@ -1,8 +1,7 @@ /* libpng 1.7.0beta70 STANDARD API DEFINITION */ - /* pnglibconf.h - library build configuration */ -/* Libpng version 1.7.0beta70 - November 30, 2015 */ +/* libpng version 1.7.0beta70, November 24, 2015 */ /* Copyright (c) 1998-2015 Glenn Randers-Pehrson */ @@ -135,11 +134,13 @@ #define PNG_WRITE_FILTER_SUPPORTED #define PNG_WRITE_FLUSH_SUPPORTED #define PNG_WRITE_GET_PALETTE_MAX_SUPPORTED +#define PNG_WRITE_HEURISTIC_FILTER_SUPPORTED #define PNG_WRITE_INTERLACING_SUPPORTED #define PNG_WRITE_INT_FUNCTIONS_SUPPORTED #define PNG_WRITE_INVERT_ALPHA_SUPPORTED #define PNG_WRITE_INVERT_SUPPORTED #define PNG_WRITE_OPTIMIZE_CMF_SUPPORTED +#define PNG_WRITE_OPTIMIZE_FILTER_SUPPORTED #define PNG_WRITE_PACKSWAP_SUPPORTED #define PNG_WRITE_PACK_SUPPORTED #define PNG_WRITE_PNG_SUPPORTED