From e9d567d9ecf4c3e78348b4f2b4ce368b4527ae07 Mon Sep 17 00:00:00 2001 From: John Bowler Date: Wed, 2 Dec 2015 17:08:17 -0800 Subject: [PATCH] Filter heuristic implementation This implements the heuristic part of filter selction, the methodic testing approach is still not implemented. png_set_option methods are incomplete. Signed-off-by: John Bowler --- png.h | 52 +++++++- pngtest.png | Bin 5711 -> 6000 bytes pngwutil.c | 238 +++++++++++++++++++++++----------- scripts/pnglibconf.dfa | 21 +++ scripts/pnglibconf.h.prebuilt | 5 +- 5 files changed, 234 insertions(+), 82 deletions(-) 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 92cc08e32872643dc26359da9500ca45e3f50c4c..ea61708cb91c57ff086c2396ecc2628ceca8a404 100644 GIT binary patch delta 5477 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 b?@dm9-lH1)kxThsy5jYawUCA0lW`T^XLYR{ delta 5186 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 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