mirror of
https://git.code.sf.net/p/libpng/code.git
synced 2025-07-10 18:04:09 +02:00
LOW_MEMORY and COMPAT bug fixes
The LOW_MEMORY PNG_COMPRESSION option should not be setting HUFFMAN_ONLY or using a low deflate 'level'; according to the comments in zconf.h only windowBits and memLevel affect the memory. pngwutil.c has been changed to use the same values as HIGH compression. The COMPAT option turned on the old optimize_cmf code (now in fix_cinfo), however there was a serious bug in that code; it put the wrong value in z_cmf. The setting was also not handled correctly in pz_compression_settings. pngtest now verifies the operation of COMPAT and, as a result, pngtest.png has been reverted to the libpng 1.6 (etc) version. IDAT size handling has been improved; if not explicitly set values appropriate to png_level are now chosen (in addition to the handling for the COMPAT setting). HIGH and HIGH_READ_SPEED now create unlimited size IDAT chunks, which requires buffering the whole of the IDAT data in memory but reflects what other programs and optimizers do. Signed-off-by: John Bowler <jbowler@acm.org>
This commit is contained in:
parent
2e68f96511
commit
2b711a751c
13
png.h
13
png.h
@ -1708,7 +1708,7 @@ PNG_REMOVED(66, void, png_set_crc_action, (png_structrp png_ptr,
|
||||
* usage.
|
||||
* 2) IDAT size: What size to make the IDAT chunks in the PNG.
|
||||
* 3) PNG row filters to consider when writing the PNG.
|
||||
* 4) Very low level control over the deflate compression (useful mainly for
|
||||
* 4) Very low level control over the deflate compression (useful mainly for
|
||||
* programs that want to try every option to find which gives the smallest
|
||||
* PNG.)
|
||||
*/
|
||||
@ -1732,16 +1732,17 @@ PNG_REMOVED(66, void, png_set_crc_action, (png_structrp png_ptr,
|
||||
/* Minimize the memory required both when reading (4) and writing (2) the
|
||||
* PNG. This results in a significantly larger PNG (which may itself have
|
||||
* the opposite effect of slowing down either read or write) however the
|
||||
* memory overhead is reduced and, apart from the extra time to read or
|
||||
* write the data, the read and write time is likely to be reduced too.
|
||||
* memory overhead is reduced and, apart from the extra time to read the
|
||||
* data, the read time is likely to be reduced too.
|
||||
*
|
||||
* Use this when both read and write will happen on a memory starved
|
||||
* (really, very low memory) system.
|
||||
* (really, very low memory) system. Note that this sets a high deflate
|
||||
* compression setting because that does not affect zlib memory usage.
|
||||
*/
|
||||
# define PNG_COMPRESSION_HIGH_SPEED (2)
|
||||
/* Minimize the time to both read (5) and write (3) the PNG. This uses
|
||||
* slightly more memory on read and potentially significantly more on
|
||||
* write but is optimized for maximum speed in both cases.
|
||||
* write but is optimized for maximum speed in both cases.
|
||||
*
|
||||
* Use this when both read and write need to be fast and PNG size is not
|
||||
* likely to be an issue. An example would be if you are using PNG to
|
||||
@ -4002,7 +4003,7 @@ PNG_EXPORT(249, png_int_32, png_setting, (png_structrp png_ptr,
|
||||
* PNG_EBADF will be returned.
|
||||
*/
|
||||
#define PNG_SF_WRITE (0x10000000U)
|
||||
/* The setting may be applied to a write png_struct. If this is not set
|
||||
/* The setting may be applied to a write png_struct. If this is not set
|
||||
* and an attempt is made to apply the setting to a write struct
|
||||
* PNG_EBADF will be returned.
|
||||
*/
|
||||
|
@ -1386,6 +1386,11 @@ test_one_file(PNG_CONST char *inname, PNG_CONST char *outname)
|
||||
png_write_info(write_ptr, write_info_ptr);
|
||||
|
||||
write_chunks(write_ptr, before_IDAT); /* after PLTE */
|
||||
|
||||
#ifdef PNG_COMPRESSION_COMPAT
|
||||
/* Test the 'compatibility' setting here, if it is available. */
|
||||
png_set_compression(write_ptr, PNG_COMPRESSION_COMPAT);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef SINGLE_ROWBUF_ALLOC
|
||||
|
BIN
pngtest.png
BIN
pngtest.png
Binary file not shown.
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 8.5 KiB |
121
pngwutil.c
121
pngwutil.c
@ -1036,7 +1036,7 @@ png_deflate_destroy(png_structrp png_ptr)
|
||||
|
||||
static png_int_32
|
||||
pz_compression_setting(png_structrp png_ptr, png_uint_32 owner,
|
||||
int min, int max, int shift, png_int_32 value, int only_get)
|
||||
int min, int max, int shift, png_int_32 value, int only_get, int unset)
|
||||
/* This is a support function for png_write_setting below. */
|
||||
{
|
||||
png_zlib_statep ps;
|
||||
@ -1079,33 +1079,32 @@ pz_compression_setting(png_structrp png_ptr, png_uint_32 owner,
|
||||
* software engineers never made mistakes.
|
||||
*/
|
||||
res = pz_compression_setting(png_ptr, png_IDAT, min, max, shift,
|
||||
value, 0/*set*/);
|
||||
value, 0/*set*/, 1/*iff unset*/);
|
||||
|
||||
if (PNG_FAILED(res))
|
||||
return res;
|
||||
|
||||
res = pz_compression_setting(png_ptr, png_iCCP, min, max, shift,
|
||||
value, 0/*set*/);
|
||||
value, 0/*set*/, 1/*iff unset*/);
|
||||
|
||||
if (PNG_FAILED(res))
|
||||
return res;
|
||||
|
||||
/* The text settings are only changed if the system builder didn't
|
||||
* disable the API to change them. Perhaps this API shouldn't exist?
|
||||
/* The text settings are changed regardless of the customize support
|
||||
* because if WRITE_CUSTOMIZE_ZTXT_COMPRESSION is not supported the old
|
||||
* behavior was to use the WRITE_CUSTOMIZE_COMPRESSION setting.
|
||||
*
|
||||
* However, when we get png_zTXt directly (from png_write_setting) and
|
||||
* the support is not compiled in return PNG_ENOSYS.
|
||||
*/
|
||||
res = pz_compression_setting(png_ptr, png_iCCP, min, max, shift,
|
||||
value, 0/*set*/);
|
||||
|
||||
if (PNG_FAILED(res))
|
||||
return res;
|
||||
|
||||
return 0;
|
||||
|
||||
unset = 1; /* i.e. only if not already set */
|
||||
|
||||
# ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED
|
||||
case png_zTXt:
|
||||
case png_iTXt:
|
||||
if (ps != NULL) psettings = &ps->pz_text;
|
||||
break;
|
||||
# endif /* WRITE_CUSTOMIZE_ZTXT_COMPRESSION */
|
||||
if (ps != NULL) psettings = &ps->pz_text;
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Return PNG_ENOSYS, not PNG_EINVAL, to support future addition of new
|
||||
@ -1122,7 +1121,10 @@ pz_compression_setting(png_structrp png_ptr, png_uint_32 owner,
|
||||
png_uint_32 settings = *psettings;
|
||||
png_uint_32 mask = 0xFU << shift;
|
||||
|
||||
if (!only_get)
|
||||
/* Do not set it if 'only_get' was passed in or if 'unset' is true and the
|
||||
* setting is not currently set:
|
||||
*/
|
||||
if (!only_get && ((settings & mask) == 0U || !unset))
|
||||
*psettings = (settings & ~mask) +
|
||||
((png_uint_32)/*SAFE*/(value-min+1) << shift);
|
||||
|
||||
@ -1138,7 +1140,7 @@ pz_compression_setting(png_structrp png_ptr, png_uint_32 owner,
|
||||
|
||||
#define compression_setting(pp, owner, setting, value, get)\
|
||||
pz_compression_setting(pp, owner, pz_min(setting), pz_max(setting),\
|
||||
pz_shift(setting), value, get)
|
||||
pz_shift(setting), value, get, 0/*always*/)
|
||||
|
||||
/* There is (as of zlib 1.2.8) a bug in the implementation of compression with a
|
||||
* window size of 256 which zlib works round by resetting windowBits from 8 to 9
|
||||
@ -1186,28 +1188,27 @@ fix_cinfo(png_zlib_statep ps, png_bytep data, png_alloc_size_t data_size)
|
||||
|
||||
else if (data_size > 0U)
|
||||
{
|
||||
int windowBits = 8+(data[0] >> 4);
|
||||
unsigned int half_window_size = 1U << (windowBits-1);
|
||||
/* Prior to 1.7.0 libpng would shrink the windowBits even if the
|
||||
* application requested a particular value, so:
|
||||
*/
|
||||
unsigned int z_cinfo = data[0] >> 4;
|
||||
unsigned int half_z_window_size = 1U << (z_cinfo + 7);
|
||||
|
||||
debug(pz_get(ps, current, windowBits, 0) == windowBits);
|
||||
|
||||
if (data_size <= half_window_size /* Can shrink */ &&
|
||||
pz_get(ps, IDAT, png_level, PNG_DEFAULT_COMPRESSION_LEVEL) ==
|
||||
PNG_COMPRESSION_COMPAT)
|
||||
if (data_size <= half_z_window_size && z_cinfo > 0)
|
||||
{
|
||||
unsigned int d1;
|
||||
unsigned int tmp;
|
||||
|
||||
/* Before 1.7.0 libpng overrode a user-supplied windowBits if the data
|
||||
* was smaller.
|
||||
*/
|
||||
do
|
||||
--windowBits, half_window_size >>= 1;
|
||||
while (data_size <= half_window_size);
|
||||
{
|
||||
half_z_window_size >>= 1;
|
||||
--z_cinfo;
|
||||
}
|
||||
while (z_cinfo > 0 && data_size <= half_z_window_size);
|
||||
|
||||
data[0] = PNG_BYTE((windowBits << 4) + 0x8U);
|
||||
d1 = data[1] & 0xE0U; /* top three bits */
|
||||
d1 += 31U - ((data[0]<<8) + d1) % 31U;
|
||||
data[1] = PNG_BYTE(d1);
|
||||
data[0] = PNG_BYTE((z_cinfo << 4) + 0x8U);
|
||||
tmp = data[1] & 0xE0U; /* top three bits */
|
||||
tmp += 31U - ((data[0] << 8) + tmp) % 31U;
|
||||
data[1] = PNG_BYTE(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1272,11 +1273,6 @@ pz_default_settings(png_uint_32 settings, const png_uint_32 owner,
|
||||
strategy = Z_FILTERED;
|
||||
break;
|
||||
|
||||
case PNG_COMPRESSION_LOW_MEMORY:
|
||||
/* Reduce memory at all costs: */
|
||||
strategy = Z_HUFFMAN_ONLY;
|
||||
break;
|
||||
|
||||
case PNG_COMPRESSION_HIGH_SPEED:
|
||||
/* RLE is as fast as HUFFMAN_ONLY and can reduce size a lot in a few
|
||||
* cases.
|
||||
@ -1314,10 +1310,13 @@ pz_default_settings(png_uint_32 settings, const png_uint_32 owner,
|
||||
else if (owner == png_iCCP)
|
||||
strategy = Z_DEFAULT_STRATEGY;
|
||||
|
||||
/* TODO: investigate this, the observed behavior is suspicious: */
|
||||
else /* text chunk */
|
||||
strategy = Z_FILTERED; /* Always better for some reason */
|
||||
break;
|
||||
|
||||
case PNG_COMPRESSION_LOW_MEMORY:
|
||||
/* Reduce memory at all costs, speed doesn't matter. */
|
||||
case PNG_COMPRESSION_HIGH_READ_SPEED:
|
||||
case PNG_COMPRESSION_HIGH:
|
||||
if (owner == png_IDAT || owner == png_iCCP)
|
||||
@ -1393,7 +1392,6 @@ pz_default_settings(png_uint_32 settings, const png_uint_32 owner,
|
||||
zlib_level = Z_DEFAULT_COMPRESSION; /* NOTE: -1 */
|
||||
break;
|
||||
|
||||
case PNG_COMPRESSION_LOW_MEMORY:
|
||||
case PNG_COMPRESSION_HIGH_SPEED:
|
||||
zlib_level = 1;
|
||||
break;
|
||||
@ -1407,6 +1405,7 @@ pz_default_settings(png_uint_32 settings, const png_uint_32 owner,
|
||||
zlib_level = 6; /* Old default! */
|
||||
break;
|
||||
|
||||
case PNG_COMPRESSION_LOW_MEMORY:
|
||||
case PNG_COMPRESSION_HIGH_READ_SPEED:
|
||||
case PNG_COMPRESSION_HIGH:
|
||||
zlib_level = 9;
|
||||
@ -1445,6 +1444,7 @@ pz_default_settings(png_uint_32 settings, const png_uint_32 owner,
|
||||
*/
|
||||
windowBits = 15;
|
||||
|
||||
if (data_size <= 16384U)
|
||||
{
|
||||
unsigned int half_window_size = 1U << (windowBits-1);
|
||||
|
||||
@ -3058,12 +3058,39 @@ png_write_IDAT(png_structrp png_ptr, int flush)
|
||||
IDAT_size = png_ptr->IDAT_size;
|
||||
if (IDAT_size == 0U)
|
||||
{
|
||||
if (pz_get(ps, IDAT, png_level, PNG_DEFAULT_COMPRESSION_LEVEL) !=
|
||||
PNG_COMPRESSION_COMPAT/*legacy*/)
|
||||
IDAT_size = PNG_ZBUF_SIZE;
|
||||
switch (pz_get(ps, IDAT, png_level, PNG_DEFAULT_COMPRESSION_LEVEL))
|
||||
{
|
||||
case PNG_COMPRESSION_COMPAT: /* Legacy */
|
||||
IDAT_size = 8192U;
|
||||
break;
|
||||
|
||||
else
|
||||
IDAT_size = 8192U;
|
||||
case PNG_COMPRESSION_LOW_MEMORY:
|
||||
case PNG_COMPRESSION_HIGH_SPEED:
|
||||
case PNG_COMPRESSION_LOW:
|
||||
/* png_compress uses PNG_ROW_BUFFER_SIZE buffers for the compressed
|
||||
* data. Optimize to allocate only one of these:
|
||||
*/
|
||||
IDAT_size = PNG_ROW_BUFFER_SIZE;
|
||||
break;
|
||||
|
||||
default:
|
||||
case PNG_COMPRESSION_MEDIUM:
|
||||
IDAT_size = PNG_ZBUF_SIZE;
|
||||
|
||||
case PNG_COMPRESSION_HIGH_READ_SPEED:
|
||||
/* Assume the reader reads partial IDAT chunks (pretty much a
|
||||
* requirement given that some PNG encoders produce just one IDAT)
|
||||
*/
|
||||
case PNG_COMPRESSION_HIGH:
|
||||
/* This doesn't control the amount of memory allocated unless the
|
||||
* PNG IDAT data really is this big.
|
||||
*
|
||||
* TODO: review handling out-of-memory from png_compress() by
|
||||
* flushing an IDAT.
|
||||
*/
|
||||
IDAT_size = PNG_UINT_31_MAX;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write IDAT chunks while either 'flush' is true or there are at
|
||||
@ -3985,7 +4012,7 @@ png_start_filter_select(png_zlib_statep ps, unsigned int bpp)
|
||||
ps->filter_select_threshold2 = 50U; /* TODO: experiment! */
|
||||
break;
|
||||
|
||||
case -1: /* Legacy */
|
||||
case PNG_COMPRESSION_COMPAT: /* Legacy */
|
||||
memset(fs->sum_bias, 0U, sizeof fs->sum_bias);
|
||||
ps->filter_select_threshold = 1U; /* disabled */
|
||||
ps->filter_select_threshold2 = 1U;
|
||||
@ -4900,7 +4927,7 @@ png_write_setting(png_structrp png_ptr, png_uint_32 setting,
|
||||
case PNG_SW_COMPRESS_png_level:
|
||||
return compression_setting(png_ptr, parameter, png_level, value,
|
||||
only_get);
|
||||
|
||||
|
||||
# ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED
|
||||
case PNG_SW_COMPRESS_zlib_level:
|
||||
return compression_setting(png_ptr, parameter, level, value,
|
||||
|
@ -779,8 +779,12 @@ setting sCAL_PRECISION default 5
|
||||
# written IDAT chunks. It can be any (zlib) uInt value, however this amount of
|
||||
# data has to be buffered on write so it is recommended that a smaller size be
|
||||
# used unless saving the 12-byte chunk overhead is necessary.
|
||||
#
|
||||
# If not set libpng uses the DEFAULT_COMPRESSION_LEVEL setting to determine
|
||||
# something appropriate. The value below is only used for 'MEDIUM' compression
|
||||
# (which is the default: see below.)
|
||||
|
||||
setting ZBUF_SIZE default 4096
|
||||
setting ZBUF_SIZE default 8192
|
||||
|
||||
# This is the size of the decompression buffer used when counting or checking
|
||||
# the decompressed size of an LZ stream from a compressed ancilliary chunk; the
|
||||
|
Loading…
x
Reference in New Issue
Block a user