libpng/contrib/tools/pngdeflate.c
John Bowler 0c7ac064d3 [libpng16] Added an option to force maximum window size for inflating.
For inflate, reverted previous fixes.
Added fixitxt and pngdeflate to the built programs and removed warnings
from the source code and timepng that are revealed as a result.  Fixed
fixitxt when the chunk length is more than 65535 (untested, no test case).
2013-05-07 21:59:05 -05:00

1148 lines
30 KiB
C

/* pngdeflate.c
*
* Copyright (c) 2013 John Cunningham Bowler
*
* Last changed in libpng 1.6.3 [(PENDING RELEASE)]
*
* This code is released under the libpng license.
* For conditions of distribution and use, see the disclaimer
* and license in png.h
*
* Tool to check and fix the deflate 'too far back' problem, see the usage
* message for more information.
*/
#include <stdlib.h>
#include <stdio.h>
#ifdef PNG_READ_SUPPORTED
#include <string.h>
#include <ctype.h>
#include <png.h>
#include <zlib.h>
static int idat_error = 0;
static int verbose = 0;
static int errors = 0;
static int warnings = 0;
#ifdef PNG_MAXIMUM_INFLATE_WINDOW
static int set_option = 0;
#endif
static const char *name = "stdin";
static uLong crc_IDAT_head; /* CRC32 of "IDAT" */
static uLong crc_IEND;
static z_stream z_idat;
/* Control structure for the temporary file */
typedef struct
{
size_t image_size;
off_t file_size;
fpos_t header_pos;
fpos_t crc_pos;
uLong crc_tail; /* CRC of bytes after header */
png_uint_32 len_tail; /* Count thereof */
png_byte header[2];
/* Image info */
png_uint_32 width;
png_uint_32 height;
png_byte bit_depth;
png_byte color_type;
png_byte compression_method;
png_byte filter_method;
png_byte interlace_method;
} IDAT_info;
static png_uint_32
mult(png_uint_32 n, png_uint_32 m)
{
if ((n + (m-1)) / m > 0xffffffff/m)
{
fprintf(stderr, "%s: overflow (%lu, %u)\n", name, (unsigned long)n, m);
exit(2);
}
return n * m;
}
static size_t
image_size(const IDAT_info *info)
{
unsigned int pd = info->bit_depth;
size_t cb;
switch (info->color_type)
{
case 0: case 3:
break;
case 2: /* rgb */
pd *= 3;
break;
case 4: /* ga */
pd *= 2;
break;
case 6: /* rgba */
pd *= 4;
break;
default:
fprintf(stderr, "%s: invalid color type (%d)\n", name,
info->color_type);
exit(2);
}
switch (info->interlace_method)
{
case PNG_INTERLACE_ADAM7:
/* Interlacing makes the image larger because of the replication of
* both the filter byte and the padding to a byte boundary.
*/
{
int pass;
for (cb=0, pass=0; pass<=6; ++pass)
{
png_uint_32 pw = PNG_PASS_COLS(info->width, pass);
if (pw > 0)
cb += mult(((mult(pd, pw)+7) >> 3)+1,
PNG_PASS_ROWS(info->height, pass));
}
}
break;
case PNG_INTERLACE_NONE:
cb = mult(info->height, 1+((mult(info->width, pd) + 7) >> 3));
break;
default:
fprintf(stderr, "%s: invalid interlace type %d\n", name,
info->interlace_method);
exit(2);
}
return cb;
}
static int
image_windowBits(const IDAT_info *info)
{
size_t cb = image_size(info);
if (cb > 16384) return 15;
if (cb > 8192) return 14;
if (cb > 4096) return 13;
if (cb > 2048) return 12;
if (cb > 1024) return 11;
if (cb > 512) return 10;
if (cb > 256) return 9;
return 8;
}
static void
error_handler(png_structp png_ptr, png_const_charp message)
{
if (strcmp(message, "IDAT: invalid distance too far back") == 0)
idat_error = 1;
else if (errors || verbose)
fprintf(stderr, "%s: %s\n", name, message);
png_longjmp(png_ptr, 1);
}
static void
warning_handler(png_structp png_ptr, png_const_charp message)
{
if (warnings || verbose)
fprintf(stderr, "%s: %s\n", name, message);
(void)png_ptr;
}
static int
read_png(FILE *fp)
{
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,0,
error_handler, warning_handler);
png_infop info_ptr = NULL;
png_bytep row = NULL, display = NULL;
if (png_ptr == NULL)
return 0;
if (setjmp(png_jmpbuf(png_ptr)))
{
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
if (row != NULL) free(row);
if (display != NULL) free(display);
return 0;
}
# ifdef PNG_MAXIMUM_INFLATE_WINDOW
png_set_option(png_ptr, PNG_MAXIMUM_INFLATE_WINDOW, set_option != 0);
# endif
png_init_io(png_ptr, fp);
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL)
png_error(png_ptr, "OOM allocating info structure");
png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, NULL, 0);
png_read_info(png_ptr, info_ptr);
/* Limit the decompression buffer size to 1 - this ensures that overlong
* length codes are always detected.
*/
png_set_compression_buffer_size(png_ptr, 1);
{
png_size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);
row = malloc(rowbytes);
display = malloc(rowbytes);
if (row == NULL || display == NULL)
png_error(png_ptr, "OOM allocating row buffers");
{
png_uint_32 height = png_get_image_height(png_ptr, info_ptr);
int passes = png_set_interlace_handling(png_ptr);
int pass;
png_start_read_image(png_ptr);
for (pass = 0; pass < passes; ++pass)
{
png_uint_32 y = height;
/* NOTE: this trashes the row each time; interlace handling won't
* work, but this avoids memory thrashing for speed testing.
*/
while (y-- > 0)
png_read_row(png_ptr, row, display);
}
}
}
/* Make sure to read to the end of the file: */
png_read_end(png_ptr, info_ptr);
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
free(row);
free(display);
return 1;
}
/* Chunk tags (copied from pngpriv.h) */
#define PNG_32b(b,s) ((png_uint_32)(b) << (s))
#define PNG_CHUNK(b1,b2,b3,b4) \
(PNG_32b(b1,24) | PNG_32b(b2,16) | PNG_32b(b3,8) | PNG_32b(b4,0))
#define png_IHDR PNG_CHUNK( 73, 72, 68, 82)
#define png_IDAT PNG_CHUNK( 73, 68, 65, 84)
#define png_IEND PNG_CHUNK( 73, 69, 78, 68)
#define png_PLTE PNG_CHUNK( 80, 76, 84, 69)
#define png_bKGD PNG_CHUNK( 98, 75, 71, 68)
#define png_cHRM PNG_CHUNK( 99, 72, 82, 77)
#define png_gAMA PNG_CHUNK(103, 65, 77, 65)
#define png_hIST PNG_CHUNK(104, 73, 83, 84)
#define png_iCCP PNG_CHUNK(105, 67, 67, 80)
#define png_iTXt PNG_CHUNK(105, 84, 88, 116)
#define png_oFFs PNG_CHUNK(111, 70, 70, 115)
#define png_pCAL PNG_CHUNK(112, 67, 65, 76)
#define png_sCAL PNG_CHUNK(115, 67, 65, 76)
#define png_pHYs PNG_CHUNK(112, 72, 89, 115)
#define png_sBIT PNG_CHUNK(115, 66, 73, 84)
#define png_sPLT PNG_CHUNK(115, 80, 76, 84)
#define png_sRGB PNG_CHUNK(115, 82, 71, 66)
#define png_sTER PNG_CHUNK(115, 84, 69, 82)
#define png_tEXt PNG_CHUNK(116, 69, 88, 116)
#define png_tIME PNG_CHUNK(116, 73, 77, 69)
#define png_tRNS PNG_CHUNK(116, 82, 78, 83)
#define png_zTXt PNG_CHUNK(122, 84, 88, 116)
static void
rx(FILE *fp, png_bytep buf, off_t cb)
{
if (fread(buf,cb,1,fp) != 1) {
fprintf(stderr, "%s: failed to read %lu bytes\n", name,
(unsigned long)cb);
exit(2);
}
}
static png_uint_32
r32(FILE *fp)
{
png_byte buf[4];
rx(fp, buf, 4);
return ((((((png_uint_32)buf[0] << 8)+buf[1]) << 8)+buf[2]) << 8) + buf[3];
}
static void
wx(FILE *fp, png_const_bytep buf, off_t cb)
{
if (fwrite(buf,cb,1,fp) != 1) {
fprintf(stderr, "%s: failed to write %lu bytes\n", name,
(unsigned long)cb);
exit(3);
}
}
static void
w32(FILE *fp, png_uint_32 val)
{
png_byte buf[4];
buf[0] = (png_byte)(val >> 24);
buf[1] = (png_byte)(val >> 16);
buf[2] = (png_byte)(val >> 8);
buf[3] = (png_byte)(val);
wx(fp, buf, 4);
}
static void
wcrc(FILE *fp, uLong crc)
{
/* Safe cast because a CRC is 32 bits */
w32(fp, (png_uint_32)crc);
}
static void
copy(FILE *fp, FILE *fpIn, off_t cb)
{
png_byte buffer[1024];
while (cb >= 1024)
{
rx(fpIn, buffer, 1024);
wx(fp, buffer, 1024);
cb -= 1024;
}
if (cb > 0)
{
rx(fpIn, buffer, cb);
wx(fp, buffer, cb);
}
}
static void
skip_bytes(FILE *fpIn, png_uint_32 cb)
{
png_byte buffer[1024];
while (cb >= 1024)
{
rx(fpIn, buffer, 1024);
cb -= 1024;
}
if (cb > 0)
rx(fpIn, buffer, cb);
}
static void
safe_getpos(FILE *fp, fpos_t *pos)
{
if (fgetpos(fp, pos))
{
perror("tmpfile");
fprintf(stderr, "%s: tmpfile fgetpos failed\n", name);
exit(3);
}
}
static void
safe_setpos(FILE *fp, fpos_t *pos)
{
if (fflush(fp))
{
perror("tmpfile");
fprintf(stderr, "%s: tmpfile fflush failed\n", name);
exit(3);
}
if (fsetpos(fp, pos))
{
perror("tmpfile");
fprintf(stderr, "%s: tmpfile fsetpos failed\n", name);
exit(3);
}
}
static void
idat_update(FILE *fp, IDAT_info *info)
{
uLong crc;
safe_setpos(fp, &info->header_pos);
wx(fp, info->header, 2);
crc = crc32(crc_IDAT_head, info->header, 2);
crc = crc32_combine(crc, info->crc_tail, info->len_tail);
safe_setpos(fp, &info->crc_pos);
wcrc(fp, crc);
}
static void
set_bits(const char *file, FILE *fp, IDAT_info *info, int bits)
{
int byte1 = (info->header[0] & 0xf) + ((bits-8) << 4);
int byte2 = info->header[1] & 0xe0;
/* The checksum calculation: */
byte2 += 0x1f - ((byte1 << 8) + byte2) % 0x1f;
info->header[0] = (png_byte)byte1;
info->header[1] = (png_byte)byte2;
if (verbose)
fprintf(stderr, "%s: trying windowBits %d (Z_CMF = 0x%x)\n", file, bits,
byte1);
idat_update(fp, info);
}
static void
ptagchar(png_uint_32 ch)
{
ch &= 0xff;
if (isprint(ch))
putc(ch, stderr);
else
fprintf(stderr, "[%02x]", ch);
}
static void
ptag(png_uint_32 tag)
{
if (tag != 0)
{
ptag(tag >> 8);
ptagchar(tag);
}
}
static int
fix_one(FILE *fp, FILE *fpIn, IDAT_info *info, png_uint_32 max_IDAT, int strip)
{
int state = 0;
/* 0: at beginning, before first IDAT
* 1: read first CMF header byte
* 2: read second byte, in first IDAT
* 3: after first IDAT
* +4: saw deflate stream end.
*/
int truncated_idat = 0; /* Count of spurious IDAT bytes */
uLong crc_idat = 0; /* Running CRC of current IDAT */
png_uint_32 len_IDAT = 0; /* Length of current IDAT */
fpos_t pos_IDAT_length; /* fpos_t of length field in current IDAT */
/* The signature: */
{
png_byte buf[8];
rx(fpIn, buf, 8);
wx(fp, buf, 8);
}
info->file_size = 45; /* signature + IHDR + IEND */
for (;;) /* Chunk for loop */
{
png_uint_32 len = r32(fpIn);
png_uint_32 tag = r32(fpIn);
if (tag == png_IHDR)
{
/* Need width, height, color type, bit depth and interlace for the
* file.
*/
info->width = r32(fpIn);
info->height = r32(fpIn);
rx(fpIn, &info->bit_depth, 1);
rx(fpIn, &info->color_type, 1);
rx(fpIn, &info->compression_method, 1);
rx(fpIn, &info->filter_method, 1);
rx(fpIn, &info->interlace_method, 1);
/* And write the information. */
w32(fp, len);
w32(fp, tag);
w32(fp, info->width);
w32(fp, info->height);
wx(fp, &info->bit_depth, 1);
wx(fp, &info->color_type, 1);
wx(fp, &info->compression_method, 1);
wx(fp, &info->filter_method, 1);
wx(fp, &info->interlace_method, 1);
/* Copy the CRC */
copy(fp, fpIn, 4);
}
else if (tag == png_IEND)
{
/* Ok, write an IEND chunk and finish. */
w32(fp, 0);
w32(fp, png_IEND);
wcrc(fp, crc_IEND);
break;
}
else if (tag == png_IDAT && len > 0)
{
/* Write the chunk header now if it hasn't been written yet */
if (len_IDAT == 0)
{
/* The length is set at the end: */
safe_getpos(fp, &pos_IDAT_length);
w32(fp, max_IDAT); /* length, not yet written */
w32(fp, png_IDAT);
if (state == 0) /* Start of first IDAT */
{
safe_getpos(fp, &info->header_pos);
/* This will become info->crc_tail: */
crc_idat = crc32(0L, Z_NULL, 0);
}
else
crc_idat = crc_IDAT_head;
}
/* Do the zlib 2-byte header, it gets written out but not added
* to the CRC (yet):
*/
while (len > 0 && state < 2)
{
rx(fpIn, info->header + state, 1);
wx(fp, info->header + state, 1);
++state;
++len_IDAT;
--len;
if (state == 2)
{
/* The zlib stream is used to validate the compressed IDAT
* data in the most relaxed way possible.
*/
png_byte bdummy;
int ret;
z_idat.next_in = info->header;
z_idat.avail_in = 2;
z_idat.next_out = &bdummy; /* Else Z_STREAM_ERROR! */
z_idat.avail_out = 0;
ret = inflate(&z_idat, Z_NO_FLUSH);
if (ret != Z_OK || z_idat.avail_in != 0)
{
fprintf(stderr,
"%s: unexpected/invalid inflate result %d \"%s\"\n",
name, ret, z_idat.msg);
return 1;
}
}
} /* while in zlib header */
/* Process further bytes in the IDAT chunk */
while (len > 0 && state < 4)
{
png_byte b;
rx(fpIn, &b, 1);
--len;
/* Do this 1 byte at a time to maximize the chance of
* detecting errors (in particular zlib can skip the
* 'too-far-back' error if the output buffer is bigger than
* the window size.)
*/
z_idat.next_in = &b;
z_idat.avail_in = 1;
do
{
int ret;
png_byte bout;
z_idat.next_out = &bout;
z_idat.avail_out = 1;
ret = inflate(&z_idat, Z_SYNC_FLUSH);
if (z_idat.avail_out == 0)
++info->image_size;
switch (ret)
{
case Z_OK:
/* Just keep going */
break;
case Z_BUF_ERROR:
if (z_idat.avail_in > 0)
{
fprintf(stderr,
"%s: unexpected buffer error \"%s\"\n",
name, z_idat.msg);
return 1;
}
goto end_loop;
case Z_STREAM_END:
/* End of stream */
state |= 4;
goto end_loop;
default:
fprintf(stderr, "%s: bad zlib stream %d, \"%s\"\n",
name, ret, z_idat.msg);
return 1;
}
} while (z_idat.avail_in > 0 || z_idat.avail_out == 0);
/* The byte need not be consumed, if, for example, there is a
* spurious byte after the end of the zlib data.
*/
end_loop:
if (z_idat.avail_in == 0)
{
/* Write it and update the length information and running
* CRC.
*/
wx(fp, &b, 1);
crc_idat = crc32(crc_idat, &b, 1);
++len_IDAT;
}
else
++truncated_idat;
if (len_IDAT >= max_IDAT || state >= 4)
{
/* Either the IDAT chunk is full or we've seen the end of
* the deflate stream, or both. Flush the chunk and handle
* the details of the first chunk.
*/
fpos_t save;
if ((state & 3) < 3) /* First IDAT */
{
safe_getpos(fp, &info->crc_pos);
info->crc_tail = crc_idat;
info->len_tail = len_IDAT-2;
}
/* This is not the correct value for the first IDAT! */
wcrc(fp, crc_idat);
state |= 3;
/* Update the length if it is not max_IDAT: */
if (len_IDAT != max_IDAT)
{
safe_getpos(fp, &save);
safe_setpos(fp, &pos_IDAT_length);
w32(fp, len_IDAT);
safe_setpos(fp, &save);
}
/* Add this IDAT to the file size: */
info->file_size += 12 + len_IDAT;
}
} /* while len > 0 && state < 4 */
/* The above loop only exits on 0 bytes left or end of stream, if
* the stream ended with bytes left discard them:
*/
if (len > 0)
{
truncated_idat += len;
/* Skip those bytes and the CRC */
skip_bytes(fpIn, len+4);
}
else
skip_bytes(fpIn, 4); /* The CRC */
} /* IDAT and len > 0 */
else
{
int skip = 0;
if (tag == png_IDAT)
skip = 1;
else if (state == 0)
{
/* Chunk before IDAT */
if (!skip) switch (strip)
{
case 0: /* Don't strip */
break;
case 1: /* Keep gAMA, sRGB */
if (tag == png_gAMA || tag == png_sRGB)
break;
/* Fall trhough */
default: /* Keep only IHDR, PLTE */
if (tag == png_IHDR || tag == png_PLTE)
break;
skip = 1;
break;
}
}
else if (state >= 4)
{
/* Keep nothing after IDAT if stripping: */
skip = strip;
}
else
{
/* This is either an unterminated deflate stream or a spurious
* non-IDAT chunk in the list of IDAT chunks. Both are fatal
* errors.
*/
fprintf(stderr, "%s: tag '", name);
ptag(tag);
fprintf(stderr, "' after unterminated IDAT\n");
break;
}
/* Skip or send? */
if (skip)
{
if (tag != png_IDAT && (tag & 0x20000000) == 0)
{
fprintf(stderr, "%s: unknown critical chunk '", name);
ptag(tag);
fprintf(stderr, "'\n");
return 1;
}
/* Skip this tag */
if (fseek(fpIn, len+4, SEEK_CUR))
{
perror(name);
fprintf(stderr, "%s: seek failed\n", name);
return 1;
}
}
else /* Keep this tag */
{
w32(fp, len);
w32(fp, tag);
copy(fp, fpIn, len+4);
info->file_size += 12+len;
}
} /* Not IDAT or len == 0 */
} /* Chunk for loop */
/* Break out of the loop on error or end */
if (state >= 4)
{
if (truncated_idat)
fprintf(stderr, "%s: removed %d bytes from end of IDAT\n", name,
truncated_idat);
return 0; /* success */
}
/* This is somewhat generic but it works: */
fprintf(stderr, "%s: unterminated/truncated PNG (%d)\n", name, state);
return 1;
}
static FILE *fpIn;
static int
fix_file(FILE *fp, const char *file, png_uint_32 max_IDAT, int inplace,
int strip, int optimize, const char *output)
{
IDAT_info info;
int imageBits, oldBits, bestBits, lowBits, newBits, ok_read;
memset(&info, 0, sizeof info);
name = file;
idat_error = 0;
/* fpIn is closed by the caller if necessary */
fpIn = fopen(file, "rb");
if (fpIn == NULL)
{
perror(file);
fprintf(stderr, "%s: open failed\n", file);
return 1;
}
/* With no arguments just check this file */
if (optimize == 0 && strip == 0 && output == NULL)
return !read_png(fpIn);
/* Otherwise, maybe, fix it */
if (fix_one(fp, fpIn, &info, max_IDAT, strip))
return 1;
/* oldBits may be invalid, imageBits is always OK, newBits always records the
* actual window bits of the temporary file (fp).
*/
bestBits = imageBits = image_windowBits(&info);
newBits = oldBits = 8+(info.header[0] >> 4);
ok_read = 0; /* records a successful read */
/* Find the optimal (lowest) newBits */
if (optimize)
for (lowBits=8; lowBits < bestBits;)
{
/* This will always set 'newBits' to a value lower than 'bestBits' because
* 'lowBits' is less than 'bestBits':
*/
newBits = (bestBits + lowBits) >> 1;
set_bits(file, fp, &info, newBits);
rewind(fp);
idat_error = 0;
if (!read_png(fp))
{
/* If idat_error is *not* set this is some other problem */
if (!idat_error)
return 1;
/* This is the hypothetical case where the IDAT has too much data *and*
* the window size is wrong. In fact this should never happen because
* of the way libpng handles a deflate stream that produces extra data.
*/
if (newBits >= imageBits)
{
fprintf(stderr, "%s: imageBits(%d) too low (%d)\n", file, imageBits,
newBits);
return 1;
}
if (lowBits <= newBits)
lowBits = newBits+1;
}
else
{
bestBits = newBits;
ok_read = 1;
}
}
else if (bestBits > oldBits)
{
/* See if the original value is ok */
rewind(fp);
idat_error = 0;
if (read_png(fp))
{
ok_read = 1;
bestBits = oldBits;
}
else if (!idat_error)
return 1;
/* Otherwise there is an IDAT error and no optimization is being done, so
* just use imageBits (which is already set in bestBits).
*/
}
if (newBits != bestBits)
{
/* Update the header to the required value */
newBits = bestBits;
set_bits(file, fp, &info, newBits);
}
if (!ok_read)
{
/* bestBits has not been checked */
idat_error = 0;
rewind(fp);
ok_read = read_png(fp);
if (idat_error)
{
/* This should never happen */
fprintf(stderr, "%s: imageBits(%d) too low [%d]\n", file, imageBits,
newBits);
return 1;
}
/* This means that the PNG has some other error */
if (!ok_read)
return 1;
}
/* Have a valid newBits */
if (optimize)
printf("%2d %2d %2d %s %s %d %s\n", newBits, oldBits, imageBits,
newBits < imageBits ? "<" : "=",
newBits < oldBits ? "reduce " :
(newBits > oldBits ? "INCREASE" : "ok "),
newBits - oldBits, name);
# ifdef PNG_MAXIMUM_INFLATE_WINDOW
/* Because setting libpng to use the maximum window bits breaks the
* read_png test above.
*/
if (set_option)
return 0;
# endif
if (output != NULL || (inplace && (bestBits != oldBits || strip)))
{
FILE *fpOut;
if (output != NULL)
fpOut = fopen(output, "wb");
else
{
fpOut = freopen(file, "wb", fpIn);
fpIn = NULL;
}
if (fpOut == NULL)
{
perror(output);
fprintf(stderr, "%s: %s: open failed\n", file, output);
exit(3);
}
rewind(fp);
copy(fpOut, fp, info.file_size);
if (fflush(fpOut) || ferror(fpOut) || fclose(fpOut))
{
perror(output != NULL ? output : file);
fprintf(stderr, "%s: %s: close failed\n", file, output);
if (output != NULL)
remove(output);
exit(3);
}
}
return 0;
}
static void
usage(const char *prog, int rc)
{
fprintf(stderr,
"Usage: %s {[options] png-file}\n", prog);
fprintf(stderr,
" Tests, optimizes and fixes the zlib header in PNG files.\n"
" Optionally, when fixing, strips ancilliary chunks from the file.\n");
fprintf(stderr,
"\nOptions:\n"
# ifdef PNG_MAXIMUM_INFLATE_WINDOW
" --test: Test the PNG_MAXIMUM_INFLATE_WINDOW option.\n"
# endif
" --optimize (-o): Find the smallest deflate window size for the file.\n"
" Also outputs a summary for each file.\n"
" --strip (-s): Remove chunks except for IHDR, PLTE, IEND, gAMA, sRGB.\n"
" If given twice remove gAMA and sRGB as well.\n"
" --errors (-e): Output errors from libpng (except too-far-back).\n");
fprintf(stderr,
" --warnings (-w): Output warnings from libpng.\n"
" --verbose (-v): Output more verbose messages.\n"
" --max=<number>: Output IDAT chunks sized <mumber>. If not given the\n"
" the IDAT chunks will be the maximum size permitted\n"
" (2^31-1 bytes.)\n"
" --out=<file>: Save the result for the next PNG to <file>.\n"
" --inplace (-i): Modify the file in place.\n");
fprintf(stderr,
"\nExit codes:\n"
" 0: Success, all files pass the test, all output written ok.\n"
" 1: At least one file had a read error, all files checked.\n"
" 2: A file had an unrecoverable error (integer overflow, bad format),\n"
" the program exited immediately, without processing further files.\n"
" 3: An IO or out of memory error, or a file could not be opened.h\n");
fprintf(stderr,
"\nDescription:\n"
" %s checks each PNG file on the command line for errors.\n"
" By default it is silent and just exits with an error code (as above)\n"
" if any error is detected. With --optimize, --strip or --out,\n"
" however, the zlib \"invalid distance too far back\" error is fixed\n"
" and the program exits with a 0 success code unless some other error\n"
" is encountered.\n"
"\n", prog);
fprintf(stderr,
" Use --errors to display the other errors, use --optimize to test\n"
" different values for the deflate \"window bits\" parameter and find\n"
" the smallest that works.\n"
"\n"
" Notice that some PNG files with the zlib header problem can still be\n"
" read by libpng. This program will still detect the error.\n"
"\n");
fprintf(stderr,
" The output produced with --optimize is as follows:\n"
"\n"
" opt-bits curr-bits image-bits opt-flag opt-type change file\n"
"\n"
" opt-bits: The minimum window bits (8-15) that works, if the file\n"
" is written this is the value that will be stored.\n"
" curr-bits: The value currently stored in the file.\n");
fprintf(stderr,
" image-bits: The window bits value corresponding to the size of the\n"
" uncompressed PNG image data. When --optimize is not\n"
" given but --strip is this value will be used if lower\n"
" than the current value.\n"
" opt-flag: < if the optimized bit value is less than that implied by\n"
" the PNG image size (opt-bits < image-bits)\n"
" = if optimization is not possible (opt-bits = image-bits)\n"
" opt-type: reduce if opts-bits < curr-bits\n");
fprintf(stderr,
" ok if opt-bits = curr-bits (no change required)\n"
" INCREASE if opt-bits > curr-bits (the file has the bug)\n"
" change: opt-bits - curr-bits, so negative if optimization is\n"
" possible, 0 if no change is required, positive if the\n"
" bug is present.\n"
" file: The file name.\n");
exit(rc);
}
int
main(int argc, const char **argv)
{
int err, strip = 0, optimize = 0, inplace = 0, done = 0;
png_uint_32 max_IDAT = 0x7fffffff;
FILE *fp;
const char *outfile = NULL;
const char *prog = *argv;
static const png_byte idat_bytes[4] = { 73, 68, 65, 84 };
static const png_byte iend_bytes[4] = { 73, 69, 78, 68 };
/* Initialize this first, could be stored as a constant: */
crc_IEND = crc_IDAT_head = crc32(0L, Z_NULL, 0);
crc_IDAT_head = crc32(crc_IDAT_head, idat_bytes, 4);
crc_IEND = crc32(crc_IEND, iend_bytes, 4);
z_idat.next_in = Z_NULL;
z_idat.avail_in = 0;
z_idat.zalloc = Z_NULL;
z_idat.zfree = Z_NULL;
z_idat.opaque = Z_NULL;
err = inflateInit(&z_idat);
if (err != Z_OK)
{
fprintf(stderr, "inflateInit failed %d \"%s\"\n", err, z_idat.msg);
inflateEnd(&z_idat);
return 3;
}
fp = tmpfile();
if (fp == NULL)
{
perror("tmpfile");
fprintf(stderr, "could not open a temporary file\n");
return 3;
}
err = 0;
while (--argc > 0)
{
++argv;
if (strcmp(*argv, "--inplace") == 0 || strcmp(*argv, "-i") == 0)
++inplace;
else if (strncmp(*argv, "--max=", 6) == 0)
max_IDAT = (png_uint_32)atol(6+*argv);
else if (strcmp(*argv, "--optimize") == 0 || strcmp(*argv, "-o") == 0)
++optimize;
else if (strncmp(*argv, "--out=", 6) == 0)
outfile = 6+*argv;
else if (strcmp(*argv, "--strip") == 0 || strcmp(*argv, "-s") == 0)
++strip;
else if (strcmp(*argv, "--errors") == 0 || strcmp(*argv, "-e") == 0)
++errors;
else if (strcmp(*argv, "--warnings") == 0 || strcmp(*argv, "-w") == 0)
++warnings;
else if (strcmp(*argv, "--verbose") == 0 || strcmp(*argv, "-v") == 0)
++verbose;
# ifdef PNG_MAXIMUM_INFLATE_WINDOW
else if (strcmp(*argv, "--test") == 0)
++set_option;
# endif
else if ((*argv)[0] == '-')
usage(prog, 3);
else
{
int ret;
err +=
fix_file(fp, *argv, max_IDAT, inplace, strip, optimize, outfile);
if (fpIn != NULL)
{
fclose(fpIn);
fpIn = NULL;
}
z_idat.next_in = z_idat.next_out = Z_NULL;
z_idat.avail_in = z_idat.avail_out = 0;
ret = inflateReset(&z_idat);
if (ret != Z_OK)
{
fprintf(stderr, "inflateReset failed %d \"%s\"\n", ret, z_idat.msg);
inflateEnd(&z_idat);
return 3;
}
rewind(fp);
outfile = NULL;
++done;
}
}
inflateEnd(&z_idat);
if (!done)
usage(prog, 0);
return err != 0;
}
#else /* No read support */
int
main(void)
{
fprintf(stderr, "pngdeflate does not work without read support\n");
return 77;
}
#endif /* PNG_READ_SUPPORTED */