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 <jbowler@acm.org>
This commit is contained in:
John Bowler 2015-12-02 17:08:17 -08:00
parent aacda27449
commit e9d567d9ec
5 changed files with 234 additions and 82 deletions

52
png.h
View File

@ -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 */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -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

View File

@ -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

View File

@ -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