diff -urN optipng-0.6.1/lib/pngxtern/pngx.h optipng-0.6.2/lib/pngxtern/pngx.h --- optipng-0.6.1/lib/pngxtern/pngx.h 2008-04-21 10:25:00.000000000 -0300 +++ optipng-0.6.2/lib/pngxtern/pngx.h 2008-11-04 12:09:00.000000000 -0200 @@ -41,6 +41,14 @@ PNGARG((png_structp png_ptr, png_infop info_ptr, int interlace_method)); +#if PNG_LIBPNG_VER >= 10400 +typedef png_alloc_size_t pngx_alloc_size_t; +#else +/* Compatibility backport of png_alloc_size_t */ +typedef png_uint_32 pngx_alloc_size_t; +#endif + +#ifdef PNG_INFO_IMAGE_SUPPORTED /* Allocate memory for the row pointers. * Use filler to initialize the rows if it is non-negative. * On success return the newly-allocated row pointers. @@ -49,6 +57,10 @@ */ extern PNG_EXPORT(png_bytepp, pngx_malloc_rows) PNGARG((png_structp png_ptr, png_infop info_ptr, int filler)); +extern PNG_EXPORT(png_bytepp, pngx_malloc_rows_extended) + PNGARG((png_structp png_ptr, png_infop info_ptr, + pngx_alloc_size_t min_row_size, int filler)); +#endif #if PNG_LIBPNG_VER >= 10400 @@ -77,9 +89,9 @@ #else /* PNG_LIBPNG_VER < 10400 */ /* Compatibility backports of functions added to libpng 1.4 */ -extern PNG_EXPORT(png_uint_32,pngx_get_io_state) +extern PNG_EXPORT(png_uint_32, pngx_get_io_state) PNGARG((png_structp png_ptr)); -extern PNG_EXPORT(png_bytep,pngx_get_io_chunk_name) +extern PNG_EXPORT(png_bytep, pngx_get_io_chunk_name) PNGARG((png_structp png_ptr)); /* Note: although these backports have several limitations in comparison * to the actual libpng 1.4 functions, they work properly in OptiPNG, @@ -87,11 +99,11 @@ */ /* Compatibility wrappers for old libpng functions */ -extern PNG_EXPORT(void,pngx_set_read_fn) PNGARG((png_structp png_ptr, +extern PNG_EXPORT(void, pngx_set_read_fn) PNGARG((png_structp png_ptr, png_voidp io_ptr, png_rw_ptr read_data_fn)); -extern PNG_EXPORT(void,pngx_set_write_fn) PNGARG((png_structp png_ptr, +extern PNG_EXPORT(void, pngx_set_write_fn) PNGARG((png_structp png_ptr, png_voidp io_ptr, png_rw_ptr write_data_fn, png_flush_ptr output_flush_fn)); -extern PNG_EXPORT(void,pngx_write_sig) PNGARG((png_structp png_ptr)); +extern PNG_EXPORT(void, pngx_write_sig) PNGARG((png_structp png_ptr)); /* Flags returned by png_get_io_state() */ #define PNGX_IO_NONE 0x0000 /* no I/O at this moment */ diff -urN optipng-0.6.1/lib/pngxtern/pngxio.c optipng-0.6.2/lib/pngxtern/pngxio.c --- optipng-0.6.1/lib/pngxtern/pngxio.c 2008-06-17 20:42:00.000000000 -0300 +++ optipng-0.6.2/lib/pngxtern/pngxio.c 2008-10-16 21:03:00.000000000 -0300 @@ -218,8 +219,11 @@ void PNGAPI pngx_write_sig(png_structp png_ptr) { -#if (PNG_LIBPNG_BUILD_TYPE & PNG_LIBPNG_BUILD_PRIVATE) +#if 0 /* png_write_sig is not exported from libpng-1.2. */ png_write_sig(png_ptr); + /* TODO: Add png_write_sig to the list of libpng-1.2 exports. + * This would complement well the group png_write_chunk{_start,_data,_end}. + */ #else static png_byte png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10}; pngx_priv_read_write(png_ptr, png_signature, 8); diff -urN optipng-0.6.1/lib/pngxtern/pngxmem.c optipng-0.6.2/lib/pngxtern/pngxmem.c --- optipng-0.6.1/lib/pngxtern/pngxmem.c 2008-04-21 10:14:00.000000000 -0300 +++ optipng-0.6.2/lib/pngxtern/pngxmem.c 2008-11-04 12:18:00.000000000 -0200 @@ -11,36 +11,43 @@ #include "pngx.h" -#if PNG_LIBPNG_VER < 10400 -typedef png_uint_32 png_alloc_size_t; -/* Since libpng-1.4.x, png_alloc_size_t is either png_size_t or png_uint_32, - * whichever is larger. - */ -#endif +#ifdef PNG_INFO_IMAGE_SUPPORTED png_bytepp PNGAPI pngx_malloc_rows(png_structp png_ptr, png_infop info_ptr, int filler) { + return pngx_malloc_rows_extended(png_ptr, info_ptr, 0, filler); +} + + +png_bytepp PNGAPI +pngx_malloc_rows_extended(png_structp png_ptr, png_infop info_ptr, + pngx_alloc_size_t min_row_size, int filler) +{ + pngx_alloc_size_t row_size; png_bytep row; png_bytepp rows; - png_alloc_size_t row_size; png_uint_32 height, i; + /* Calculate the row size. */ + row_size = png_get_rowbytes(png_ptr, info_ptr); + if (row_size == 0) + return NULL; + if (row_size < min_row_size) + row_size = min_row_size; + /* Deallocate the currently-existing rows. */ -#ifdef PNG_FREE_ME_SUPPORTED png_free_data(png_ptr, info_ptr, PNG_FREE_ROWS, 0); -#endif /* Allocate memory for the row index. */ height = png_get_image_height(png_ptr, info_ptr); rows = (png_bytepp)png_malloc(png_ptr, - (png_alloc_size_t)(height * sizeof(png_bytep))); + (pngx_alloc_size_t)(height * sizeof(png_bytep))); if (rows == NULL) return NULL; /* Allocate memory for each row. */ - row_size = png_get_rowbytes(png_ptr, info_ptr); for (i = 0; i < height; ++i) { row = (png_bytep)png_malloc(png_ptr, row_size); @@ -61,3 +68,15 @@ png_set_rows(png_ptr, info_ptr, rows); return rows; } + + +#if 0 /* not necessary */ +void PNGAPI +pngx_free_rows(png_structp png_ptr, png_infop info_ptr) +{ + png_free_data(png_ptr, info_ptr, PNG_FREE_ROWS, 0); +} +#endif + + +#endif /* PNG_INFO_IMAGE_SUPPORTED */ diff -urN optipng-0.6.1/lib/pngxtern/pngxrbmp.c optipng-0.6.2/lib/pngxtern/pngxrbmp.c --- optipng-0.6.1/lib/pngxtern/pngxrbmp.c 2008-05-10 19:21:00.000000000 -0300 +++ optipng-0.6.2/lib/pngxtern/pngxrbmp.c 2008-11-04 12:45:00.000000000 -0200 @@ -13,11 +13,9 @@ #include -/** - * BMP file header macros - * Public domain by MIYASAKA Masaru - * Updated by Cosmin Truta - **/ +/*****************************************************************************/ +/* BMP file header macros */ +/*****************************************************************************/ /* BMP file signature */ #define BMP_SIGNATURE 0x4d42 /* "BM" */ @@ -482,7 +480,7 @@ png_bytep const bih = bfh + FILEHED_SIZE; png_byte rgbq[RGBQUAD_SIZE]; png_uint_32 offbits, bihsize, skip; - png_uint_32 width, height; + png_uint_32 width, height, rowsize; int topdown; unsigned int pixdepth; png_uint_32 compression; @@ -492,7 +490,6 @@ int bit_depth, color_type; png_color palette[256]; png_color_8 sig_bit; - png_size_t rowsize, rowbytes; png_bytepp row_pointers, begin_row, end_row; unsigned int i; png_size_t y; @@ -531,7 +528,7 @@ return 0; skip = offbits - bihsize - FILEHED_SIZE; /* new skip */ topdown = 0; - if (bihsize == COREHED_SIZE) /* OS/2 BMP */ + if (bihsize < INFOHED_SIZE) /* OS/2 BMP */ { width = bmp_get_word(bih + BCH_WWIDTH); height = bmp_get_word(bih + BCH_WHEIGHT); @@ -539,7 +536,7 @@ compression = BI_RGB; palsize = RGBTRIPLE_SIZE; } - else /* bihsize >= INFOHED_SIZE: Windows BMP */ + else /* Windows BMP */ { width = bmp_get_dword(bih + BIH_LWIDTH); height = bmp_get_dword(bih + BIH_LHEIGHT); @@ -551,19 +548,20 @@ height = PNG_UINT_32_MAX - height + 1; topdown = 1; } + if (bihsize == INFOHED_SIZE && compression == BI_BITFIELDS) + { + /* Read the RGB[A] mask. */ + i = (skip <= 16) ? (unsigned int)skip : 16; + if (fread(bih + B4H_DREDMASK, i, 1, stream) != 1) + return 0; + bihsize += i; + skip -= i; + } } png_memset(rgba_mask, 0, sizeof(rgba_mask)); if (pixdepth > 8) { - if (bihsize <= INFOHED_SIZE) - png_memset(bih + B4H_DREDMASK, 0, 16); - if (bihsize == INFOHED_SIZE && skip >= 12) - { - if (fread(bih + B4H_DREDMASK, 12, 1, stream) != 1) - bihsize = 0; - skip -= 12; - } if (compression == BI_RGB) { if (pixdepth == 16) @@ -582,11 +580,17 @@ } else if (compression == BI_BITFIELDS) { - rgba_mask[0] = bmp_get_dword(bih + B4H_DREDMASK); - rgba_mask[1] = bmp_get_dword(bih + B4H_DGREENMASK); - rgba_mask[2] = bmp_get_dword(bih + B4H_DBLUEMASK); + if (bihsize >= INFOHED_SIZE + 12) + { + rgba_mask[0] = bmp_get_dword(bih + B4H_DREDMASK); + rgba_mask[1] = bmp_get_dword(bih + B4H_DGREENMASK); + rgba_mask[2] = bmp_get_dword(bih + B4H_DBLUEMASK); + } + else + png_error(png_ptr, "Missing color mask in BMP file"); } - rgba_mask[3] = bmp_get_dword(bih + B4H_DALPHAMASK); + if (bihsize >= INFOHED_SIZE + 16) + rgba_mask[3] = bmp_get_dword(bih + B4H_DALPHAMASK); } switch (compression) @@ -637,7 +641,8 @@ if (palnum > 256) palnum = 256; skip -= palsize * palnum; - rowsize = rowbytes = (width + (32 / pixdepth) - 1) / (32 / pixdepth) * 4; + rowsize = (width + (32 / pixdepth) - 1) / (32 / pixdepth) * 4; + /* rowsize becomes 0 on overflow. */ bit_depth = pixdepth; color_type = (palnum > 0) ? PNG_COLOR_TYPE_PALETTE : PNG_COLOR_TYPE_GRAY; } @@ -645,28 +650,28 @@ { palnum = 0; bit_depth = 8; - if (width > (png_size_t)(-4) / (pixdepth / 8)) - png_error(png_ptr, "Can't handle exceedingly large BMP dimensions"); - /* Overflow in rowbytes is checked inside png_set_IHDR(). */ switch (pixdepth) { case 16: - rowsize = (png_size_t)((width * 2 + 3) & (~3)); - rowbytes = (width * 3 + 3) & (~3); + rowsize = (width * 2 + 3) & (~3); break; case 24: - rowbytes = rowsize = (png_size_t)((width * 3 + 3) & (~3)); + rowsize = (width * 3 + 3) & (~3); break; case 32: - rowbytes = rowsize = (png_size_t)(width * 4); + rowsize = width * 4; break; default: /* never get here */ bit_depth = 0; - rowbytes = rowsize = 0; + rowsize = 0; } + if (rowsize / width < pixdepth / 8) + rowsize = 0; /* overflow */ color_type = (rgba_mask[3] != 0) ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB; } + if (rowsize == 0) + png_error(png_ptr, "Exceedingly large image dimensions in BMP file"); /* Set the PNG image type. */ png_set_IHDR(png_ptr, info_ptr, @@ -706,7 +711,7 @@ } /* Allocate memory and read the image data. */ - row_pointers = pngx_malloc_rows(png_ptr, info_ptr, -1); + row_pointers = pngx_malloc_rows_extended(png_ptr, info_ptr, rowsize, -1); if (topdown) { begin_row = row_pointers; diff -urN optipng-0.6.1/src/opngoptim.c optipng-0.6.2/src/opngoptim.c --- optipng-0.6.1/src/opngoptim.c 1969-12-31 21:00:00.000000000 -0300 +++ optipng-0.6.2/src/opngoptim.c 2008-11-10 01:48:00.000000000 -0200 @@ -0,0 +1,1777 @@ +/** + ** opngoptim.c + ** The main OptiPNG optimization engine. + ** + ** Copyright (C) 2001-2008 Cosmin Truta. + ** OptiPNG is open-source software, and is distributed under the same + ** licensing and warranty terms as libpng. + **/ + +#include +#include +#include +#include +#include +#include + +#include "proginfo.h" +#include "optipng.h" +#include "png.h" +#include "pngx.h" +#include "pngxtern.h" +#include "opngreduc.h" +#include "cexcept.h" +#include "cbitset.h" +#include "osys.h" + + +/** Program tables, limits and presets **/ +#define OPTIM_LEVEL_MIN 0 +#define OPTIM_LEVEL_MAX 7 +#define OPTIM_LEVEL_DEFAULT 2 + +/* "-" <=> "MIN-MAX" */ + +#define COMPR_LEVEL_MIN 1 +#define COMPR_LEVEL_MAX 9 +static const char *compr_level_presets[OPTIM_LEVEL_MAX + 1] = + { "", "", "9", "9", "9", "9", "-", "-" }; +static const char *compr_level_mask = "1-9"; + +#define MEM_LEVEL_MIN 1 +#define MEM_LEVEL_MAX 9 +static const char *mem_level_presets[OPTIM_LEVEL_MAX + 1] = + { "", "", "8", "8-", "8", "8-", "8", "8-" }; +static const char *mem_level_mask = "1-9"; + +#define STRATEGY_MIN 0 +#define STRATEGY_MAX 3 +static const char *strategy_presets[OPTIM_LEVEL_MAX + 1] = + { "", "", "-", "-", "-", "-", "-", "-" }; +static const char *strategy_mask = "0-3"; + +#define FILTER_MIN 0 +#define FILTER_MAX 5 +static const char *filter_presets[OPTIM_LEVEL_MAX + 1] = + { "", "", "0,5", "0,5", "-", "-", "-", "-" }; +static const char *filter_mask = "0-5"; + + +/** Status flags **/ +#define INPUT_IS_PNG_FILE 0x0001 +#define INPUT_HAS_PNG_DATASTREAM 0x0002 +#define INPUT_HAS_PNG_SIGNATURE 0x0004 +#define INPUT_HAS_DIGITAL_SIGNATURE 0x0008 +#define INPUT_HAS_MULTIPLE_IMAGES 0x0010 +#define INPUT_HAS_APNG 0x0020 +#define INPUT_HAS_JUNK 0x0040 +#define INPUT_HAS_ERRORS 0x0080 +#define OUTPUT_NEEDS_NEW_FILE 0x0100 +#define OUTPUT_NEEDS_NEW_IDAT 0x0200 +#define OUTPUT_RESERVED 0x7c00 +#define OUTPUT_HAS_ERRORS 0x8000U + + +/** The chunks handled by OptiPNG **/ +static const png_byte sig_PLTE[4] = { 0x50, 0x4c, 0x54, 0x45 }; +static const png_byte sig_tRNS[4] = { 0x74, 0x52, 0x4e, 0x53 }; +static const png_byte sig_IDAT[4] = { 0x49, 0x44, 0x41, 0x54 }; +static const png_byte sig_IEND[4] = { 0x49, 0x45, 0x4e, 0x44 }; +static const png_byte sig_bKGD[4] = { 0x62, 0x4b, 0x47, 0x44 }; +static const png_byte sig_hIST[4] = { 0x68, 0x49, 0x53, 0x54 }; +static const png_byte sig_sBIT[4] = { 0x73, 0x42, 0x49, 0x54 }; +static const png_byte sig_dSIG[4] = { 0x64, 0x53, 0x49, 0x47 }; +static const png_byte sig_acTL[4] = { 0x61, 0x63, 0x54, 0x4c }; +static const png_byte sig_fcTL[4] = { 0x66, 0x63, 0x54, 0x4c }; +static const png_byte sig_fdAT[4] = { 0x66, 0x64, 0x41, 0x54 }; + + +/** User exception setup -- see cexcept.h for more info **/ +define_exception_type(const char *); +struct exception_context the_exception_context[1]; + + +/** OptiPNG info **/ +static struct opng_image_struct +{ + png_uint_32 width, height; + int bit_depth, color_type, compression_type, filter_type, interlace_type; + png_bytepp row_pointers; /* IDAT */ + png_colorp palette; /* PLTE */ + int num_palette; + png_color_16p background_ptr; + png_color_16 background; /* bKGD */ + png_uint_16p hist; /* hIST */ + png_color_8p sig_bit_ptr; + png_color_8 sig_bit; /* sBIT */ + png_bytep trans; /* tRNS */ + int num_trans; + png_color_16p trans_values_ptr; + png_color_16 trans_values; + png_unknown_chunkp unknowns; + int num_unknowns; +} opng_image; + +static struct opng_info_struct +{ + unsigned int status; + long in_datastream_offset; + unsigned long in_file_size, out_file_size; + png_uint_32 in_plte_trns_size, out_plte_trns_size; + png_uint_32 in_idat_size, out_idat_size; + png_uint_32 best_idat_size, max_idat_size; + png_uint_32 reductions; + bitset_t compr_level_set, mem_level_set, strategy_set, filter_set; + int best_compr_level, best_mem_level, best_strategy, best_filter; + int num_iterations; +} opng_info; + +static struct opng_summary_struct +{ + unsigned int file_count; + unsigned int err_count; + unsigned int fix_count; + unsigned int snip_count; +} summary; + +static const struct opng_options *options; + + +/** More global variables, for quick access and bonus style points **/ +static png_structp read_ptr, write_ptr; +static png_infop read_info_ptr, write_info_ptr; +static png_infop read_end_info_ptr, write_end_info_ptr; + + +/** Virtual UI calls **/ +static void (*opng_printf)(const char *fmt, ...); +static void (*opng_flush)(void); +static void (*opng_progress)(unsigned long num, unsigned long denom); +static void (*opng_panic)(const char *msg); + + +/** Internal debugging tool **/ +#define OPNG_ENSURE(cond, msg) \ + { if (!(cond)) opng_panic(msg); } /* strong check, no #ifdef's */ + + +/** Bitset utility (find minimum value) **/ +static int +opng_bitset_min(bitset_t set) +{ + unsigned int i; + + for (i = 0; i < BITSET_SIZE; ++i) + if (BITSET_GET(set, i)) + return i; + return -1; /* empty set */ +} + + +/** Ratio display w/ logging **/ +static void +opng_print_ratio(unsigned long num, unsigned long denom, int force_percent) +{ + /* (1) num/denom = 0/0 ==> print "??%" + * (2) num/denom = INFINITY ==> print "INFTY%" + * (3) 0 <= num/denom < 99.995% ==> use the percent format "99.99%" + * if force_percent: + * (4) 0.995 <= num/denom < INFINITY ==> use the percent format "999%" + * else: + * (5) 0.995 <= num/denom < 99.995 ==> use the factor format "9.99x" + * (6) 99.5 <= num/denom < INFINITY ==> use the factor format "999x" + * end if + */ + + unsigned long integral, adj_num, adj_denom; + + /* (1,2): num/denom = 0/0 or num/denom = INFINITY */ + if (denom == 0) + { + opng_printf(num == 0 ? "??%%" : "INFTY%%"); + return; + } + + /* (3): 0 <= num/denom < 99.995% */ + /* num/denom < 99.995% <==> denom/(denom-num) < 20000 */ + if (num < denom && denom / (denom - num) < 20000) + { + /* Round to nearest 0.01% and multiply the result by 10000%. */ + if (denom <= ULONG_MAX / 10000) + { + /* Use the best precision possible. */ + adj_num = num * 10000 + denom / 2; + adj_denom = denom * 100; + assert(adj_num / adj_denom < 100); + } + else + { + /* Reduce the precision to prevent overflow. */ + adj_num = num + denom / 20000; + if (denom <= ULONG_MAX - 5000) + denom = (denom + 5000) / 10000; + else + denom = ULONG_MAX / 10000; + assert(denom > 0); + adj_denom = denom * 100; + if (adj_num / adj_denom >= 100) + adj_num = denom * 9999; /* 100.00% --> 99.99% */ + } + opng_printf("%lu.%02lu%%", + adj_num / adj_denom, adj_num % adj_denom / denom); + return; + } + + /* Extract the integral out of the fraction for the remaining cases. */ + integral = num / denom; + num = num % denom; + /* Round to nearest 0.01 and multiply the result by 100. */ + /* num/denom < 0.995 <==> denom/(denom-num) < 200 */ + if (denom / (denom - num) >= 200) + { + /* Round up, use the best precision possible. */ + ++integral; + adj_num = 0; + adj_denom = denom; + } + else if (denom <= ULONG_MAX / 100) + { + /* Also use the best precision possible. */ + adj_num = num * 100 + denom / 2; + adj_denom = denom; + assert(adj_num / adj_denom < 100); + } + else + { + /* Reduce the precision to prevent overflow. */ + adj_num = num + denom / 200; + if (denom <= ULONG_MAX - 50) + adj_denom = (denom + 50) / 100; + else + adj_denom = ULONG_MAX / 100; + assert(adj_denom > 0); + if (adj_num / adj_denom >= 100) + adj_num = adj_denom * 99; /* N + 100% --> N + 99% */ + } + + /* (4): 0.995 <= num/denom < INFINITY */ + if (force_percent) + { + opng_printf("%lu%02lu%%", integral, adj_num / adj_denom); + return; + } + + /* (5): 0.995 <= num/denom < 99.995 */ + if (integral < 100) + { + opng_printf("%lu.%02lux", integral, adj_num / adj_denom); + return; + } + + /* (6): 99.5 <= num/denom < INFINITY */ + /* Round to nearest integral value, use the best precision possible. */ + if (num % denom >= denom / 2) + ++integral; + opng_printf("%lux", integral); +} + + +/** Size change display w/ logging **/ +static void +opng_print_size_difference(unsigned long init_size, unsigned long final_size, + int show_ratio) +{ + unsigned long difference; + int sign; + + if (init_size <= final_size) + { + sign = 0; + difference = final_size - init_size; + } + else + { + sign = 1; + difference = init_size - final_size; + } + + if (difference == 0) + { + opng_printf("no change"); + return; + } + if (difference == 1) + opng_printf("1 byte"); + else + opng_printf("%lu bytes", difference); + if (show_ratio && init_size > 0) + { + opng_printf(" = "); + opng_print_ratio(difference, init_size, 0); + } + opng_printf(sign == 0 ? " increase" : " decrease"); +} + + +/** Image info display w/ logging **/ +static void +opng_print_image_info(int show_dim, int show_depth, int show_type, + int show_interlaced) +{ + static const int type_channels[8] = {1, 0, 3, 1, 2, 0, 4, 0}; + int channels, printed; + + printed = 0; + if (show_dim) + { + printed = 1; + opng_printf("%lux%lu pixels", + (unsigned long)opng_image.width, (unsigned long)opng_image.height); + } + if (show_depth) + { + if (printed) + opng_printf(", "); + printed = 1; + channels = type_channels[opng_image.color_type & 7]; + if (channels != 1) + opng_printf("%dx%d bits/pixel", channels, opng_image.bit_depth); + else if (opng_image.bit_depth != 1) + opng_printf("%d bits/pixel", opng_image.bit_depth); + else + opng_printf("1 bit/pixel"); + } + if (show_type) + { + if (printed) + opng_printf(", "); + printed = 1; + if (opng_image.color_type & PNG_COLOR_MASK_PALETTE) + { + if (opng_image.num_palette == 1) + opng_printf("1 color"); + else + opng_printf("%d colors", opng_image.num_palette); + if (opng_image.num_trans > 0) + opng_printf(" (%d transparent)", opng_image.num_trans); + opng_printf(" in palette"); + } + else + { + opng_printf((opng_image.color_type & PNG_COLOR_MASK_COLOR) ? + "RGB" : "grayscale"); + if (opng_image.color_type & PNG_COLOR_MASK_ALPHA) + opng_printf("+alpha"); + else if (opng_image.trans_values_ptr != NULL) + opng_printf("+transparency"); + } + } + if (show_interlaced) + { + if (opng_image.interlace_type != PNG_INTERLACE_NONE) + { + if (printed) + opng_printf(", "); + opng_printf("interlaced"); + } + /* Displaying "non-interlaced" is not really necessary for PNG images, + * and is almost meaningless for non-PNG images. + */ + } +} + + +/** Warning display **/ +static void +opng_print_warning(const char *msg) +{ + opng_printf("Warning: %s\n", msg); +} + + +/** Warning display **/ +static void +opng_print_error(const char *msg) +{ + opng_printf("Error: %s\n", msg); +} + + +/** Warning handler **/ +static void +opng_warning(png_structp png_ptr, png_const_charp msg) +{ + /* Error in input or output file; processing may continue. */ + /* Recovery requires (re)compression of IDAT. */ + if (png_ptr == read_ptr) + opng_info.status |= (INPUT_HAS_ERRORS | OUTPUT_NEEDS_NEW_IDAT); + opng_print_warning(msg); +} + + +/** Error handler **/ +static void +opng_error(png_structp png_ptr, png_const_charp msg) +{ + /* Error in input or output file; processing must stop. */ + /* Recovery requires (re)compression of IDAT. */ + if (png_ptr == read_ptr) + opng_info.status |= (INPUT_HAS_ERRORS | OUTPUT_NEEDS_NEW_IDAT); + Throw msg; +} + + +/** Chunk categorization **/ +static int +opng_is_critical_chunk(png_bytep chunk_type) +{ + if ((chunk_type[0] & 0x20) == 0) + return 1; + /* In strict terms of the PNG specification, tRNS is ancillary. + * However, the tRNS data defines the actual alpha samples, which + * is critical information. OptiPNG cannot operate losslessly + * unless it treats tRNS as a critical chunk. + * (Image animations constitute yet another area of applications + * in which transparency is critical, and cannot be ignored unless + * explicitly stated on a case-by-case basis.) + */ + if (memcmp(chunk_type, sig_tRNS, 4) == 0) + return 1; + return 0; +} + + +/** Chunk categorization **/ +static int +opng_is_apng_chunk(png_bytep chunk_type) +{ + if (memcmp(chunk_type, sig_acTL, 4) == 0 + || memcmp(chunk_type, sig_fcTL, 4) == 0 + || memcmp(chunk_type, sig_fdAT, 4) == 0) + return 1; + return 0; +} + + +/** Chunk filter **/ +static int +opng_allow_chunk(png_bytep chunk_type) +{ + /* Always block the digital signature chunks. */ + if (memcmp(chunk_type, sig_dSIG, 4) == 0) + return 0; + /* Block the APNG chunks when snipping. */ + if (options->snip && opng_is_apng_chunk(chunk_type)) + return 0; + /* Allow everything else. */ + return 1; +} + + +/** Chunk handler **/ +static void +opng_handle_chunk(png_structp png_ptr, png_bytep chunk_type) +{ + png_byte chunk_name[5]; + int keep; + + if (opng_is_critical_chunk(chunk_type) + || memcmp(chunk_type, sig_bKGD, 4) == 0 + || memcmp(chunk_type, sig_hIST, 4) == 0 + || memcmp(chunk_type, sig_sBIT, 4) == 0) + return; /* let libpng handle it */ + + /* Everything else is handled as unknown by libpng. */ + keep = PNG_HANDLE_CHUNK_ALWAYS; + if (memcmp(chunk_type, sig_dSIG, 4) == 0) /* digital signature? */ + opng_info.status |= INPUT_HAS_DIGITAL_SIGNATURE; + else if (opng_is_apng_chunk(chunk_type)) /* APNG? */ + { + opng_info.status |= INPUT_HAS_APNG; + if (memcmp(chunk_type, sig_fdAT, 4) == 0) + opng_info.status |= INPUT_HAS_MULTIPLE_IMAGES; + if (options->snip) + { + opng_info.status |= INPUT_HAS_JUNK; + keep = PNG_HANDLE_CHUNK_NEVER; + } + } + memcpy(chunk_name, chunk_type, 4); + chunk_name[4] = 0; + if (!png_handle_as_unknown(png_ptr, chunk_name)) + png_set_keep_unknown_chunks(png_ptr, keep, chunk_name, 1); +} + + +/** Initialization for input handler **/ +static void +opng_init_read_data(void) +{ + /* The relevant fields inside opng_info are set to zero, + * and nothing else needs to be done at this moment. + */ +} + + +/** Initialization for output handler **/ +static void +opng_init_write_data(void) +{ + opng_info.out_file_size = 0; + opng_info.out_plte_trns_size = 0; + opng_info.out_idat_size = 0; +} + + +/** Input handler **/ +static void +opng_read_data(png_structp png_ptr, png_bytep data, png_size_t length) +{ + FILE *stream = (FILE *)png_get_io_ptr(png_ptr); + int io_state = pngx_get_io_state(png_ptr); + int io_state_loc = io_state & PNGX_IO_MASK_LOC; + png_bytep chunk_sig; + + /* Read the data. */ + if (fread(data, 1, length, stream) != length) + png_error(png_ptr, + "Can't read the input file or unexpected end of file"); + + if (opng_info.in_file_size == 0) /* first piece of PNG data */ + { + OPNG_ENSURE(length == 8, "PNG I/O must start with the first 8 bytes"); + opng_info.in_datastream_offset = ftell(stream) - 8; + opng_info.status |= INPUT_HAS_PNG_DATASTREAM; + if (io_state_loc == PNGX_IO_SIGNATURE) + opng_info.status |= INPUT_HAS_PNG_SIGNATURE; + if (opng_info.in_datastream_offset == 0) + opng_info.status |= INPUT_IS_PNG_FILE; + else if (opng_info.in_datastream_offset < 0) + png_error(png_ptr, + "Can't get the file-position indicator in input file"); + opng_info.in_file_size = (unsigned long)opng_info.in_datastream_offset; + } + opng_info.in_file_size += length; + + /* Handle the OptiPNG-specific events. */ + OPNG_ENSURE((io_state & PNGX_IO_READING) && (io_state_loc != 0), + "Incorrect info in png_ptr->io_state"); + if (io_state_loc == PNGX_IO_CHUNK_HDR) + { + /* In libpng 1.4.x and later, the chunk length and the chunk name + * are serialized in a single operation. This is also ensured by + * the opngio add-on for libpng 1.2.x and earlier. + */ + OPNG_ENSURE(length == 8, "Reading chunk header, expecting 8 bytes"); + chunk_sig = data + 4; + + if (memcmp(chunk_sig, sig_IDAT, 4) == 0) + { + if (opng_info.in_idat_size == 0) /* first IDAT */ + { + /* Allocate the rows here, bypassing libpng. + * This allows to initialize the contents and perform recovery + * in case of a premature EOF. + */ + OPNG_ENSURE(png_ptr == read_ptr, "Incorrect I/O handler setup"); + if (png_get_image_height(read_ptr, read_info_ptr) == 0) + return; /* premature IDAT; an error will be triggered later */ + OPNG_ENSURE(png_get_rows(read_ptr, read_info_ptr) == NULL, + "Image rows have been allocated too early"); + OPNG_ENSURE(pngx_malloc_rows(read_ptr, read_info_ptr, 0) != NULL, + "Failed allocation of image rows; check the safe allocator"); + png_data_freer(read_ptr, read_info_ptr, + PNG_USER_WILL_FREE_DATA, PNG_FREE_ROWS); + } + else + opng_info.status |= INPUT_HAS_JUNK; /* collapse multiple IDAT's */ + opng_info.in_idat_size += png_get_uint_32(data); + } + else if (memcmp(chunk_sig, sig_PLTE, 4) == 0 || + memcmp(chunk_sig, sig_tRNS, 4) == 0) + { + /* Add the chunk overhead (header + CRC) besides the data size. */ + opng_info.in_plte_trns_size += png_get_uint_32(data) + 12; + } + else + opng_handle_chunk(png_ptr, chunk_sig); + } +} + + +/** Output handler **/ +static void +opng_write_data(png_structp png_ptr, png_bytep data, png_size_t length) +{ + static int allow_crt_chunk; + static int crt_chunk_is_idat; + static long crt_idat_offset; + static png_uint_32 crt_idat_size, crt_idat_crc; + FILE *stream = (FILE *)png_get_io_ptr(png_ptr); + int io_state = pngx_get_io_state(png_ptr); + int io_state_loc = io_state & PNGX_IO_MASK_LOC; + png_bytep chunk_sig; + png_byte buf[4]; + + OPNG_ENSURE((io_state & PNGX_IO_WRITING) && (io_state_loc != 0), + "Incorrect info in png_ptr->io_state"); + + /* Handle the OptiPNG-specific events. */ + if (io_state_loc == PNGX_IO_CHUNK_HDR) + { + OPNG_ENSURE(length == 8, "Writing chunk header, expecting 8 bytes"); + chunk_sig = data + 4; + allow_crt_chunk = opng_allow_chunk(chunk_sig); + if (memcmp(chunk_sig, sig_IDAT, 4) == 0) + { + crt_chunk_is_idat = 1; + opng_info.out_idat_size += png_get_uint_32(data); + /* Abandon the trial if IDAT is bigger than the maximum allowed. */ + if (stream == NULL) + { + if (opng_info.out_idat_size > opng_info.max_idat_size) + Throw NULL; /* early interruption, not an error */ + } + } + else /* not IDAT */ + { + crt_chunk_is_idat = 0; + if (memcmp(chunk_sig, sig_PLTE, 4) == 0 || + memcmp(chunk_sig, sig_tRNS, 4) == 0) + { + /* Add the chunk overhead (header + CRC) besides the data size. */ + opng_info.out_plte_trns_size += png_get_uint_32(data) + 12; + } + } + } + + /* Exit early if this is only a trial. */ + if (stream == NULL) + return; + + /* Continue only if the current chunk type is allowed. */ + if (io_state_loc != PNGX_IO_SIGNATURE && !allow_crt_chunk) + return; + + /* Here comes an elaborate way of writing the data, in which + * multiple IDATs are collapsed in a single chunk. + * Normally, the user-supplied I/O routines are not so complicated. + */ + switch (io_state_loc) + { + case PNGX_IO_CHUNK_HDR: + { + if (crt_chunk_is_idat) + { + if (crt_idat_offset == 0) /* this is the first IDAT */ + { + crt_idat_offset = ftell(stream); + /* Try guessing the concatenated IDAT's length. */ + if (opng_info.best_idat_size > 0) + crt_idat_size = opng_info.best_idat_size; + else + crt_idat_size = length; + png_save_uint_32(data, crt_idat_size); + /* Start computing the concatenated IDAT's CRC. */ + crt_idat_crc = crc32(0, sig_IDAT, 4); + } + else /* this is not the first IDAT, so do not write its header */ + return; + } + else + { + if (crt_idat_offset != 0) + { + /* This is the header of the first chunk after IDAT. */ + /* IDAT must be finalized. */ + png_save_uint_32(buf, crt_idat_crc); + if (fwrite(buf, 1, 4, stream) != 4) + io_state = 0; /* error */ + opng_info.out_file_size += 4; + if (opng_info.out_idat_size != crt_idat_size) + { + /* The IDAT chunk size has not been correctly anticipated. + * It must be corrected in a non-streamable way. + */ + OPNG_ENSURE(opng_info.best_idat_size == 0, + "Incorrect calculation of IDAT size"); + OPNG_ENSURE(opng_info.out_idat_size <= PNG_UINT_31_MAX, + "Exceedingly large IDAT in output"); + png_save_uint_32(buf, opng_info.out_idat_size); + if (osys_fwrite_at(stream, crt_idat_offset, SEEK_SET, + buf, 4) != 4) + io_state = 0; /* error */ + } + if (io_state == 0) + png_error(png_ptr, "Can't finalize IDAT"); + crt_idat_offset = 0; + } + } + break; + } + case PNGX_IO_CHUNK_DATA: + { + if (crt_chunk_is_idat) + crt_idat_crc = crc32(crt_idat_crc, data, length); + break; + } + case PNGX_IO_CHUNK_CRC: + { + if (crt_chunk_is_idat) + return; /* defer writing until the first non-IDAT occurs */ + break; + } + } + + /* Write the data. */ + if (fwrite(data, 1, length, stream) != length) + png_error(png_ptr, "Can't write the output file"); + opng_info.out_file_size += length; +} + + +/** Image info initialization **/ +static void +opng_clear_image_info(void) +{ + png_debug(0, "Clearing opng_image"); + memset(&opng_image, 0, sizeof(opng_image)); +} + + +/** Image info transfer **/ +static void +opng_load_image_info(png_structp png_ptr, png_infop info_ptr, + png_infop end_info_ptr, int load_metadata) +{ + png_debug(0, "Loading opng_image from info struct\n"); + memset(&opng_image, 0, sizeof(opng_image)); + + png_get_IHDR(png_ptr, info_ptr, + &opng_image.width, &opng_image.height, &opng_image.bit_depth, + &opng_image.color_type, &opng_image.interlace_type, + &opng_image.compression_type, &opng_image.filter_type); + opng_image.row_pointers = png_get_rows(png_ptr, info_ptr); + png_get_PLTE(png_ptr, info_ptr, + &opng_image.palette, &opng_image.num_palette); + /* Transparency is not considered metadata, although tRNS is ancillary. + * See the comment in opng_is_critical_chunk() above. + */ + if (png_get_tRNS(png_ptr, info_ptr, + &opng_image.trans, &opng_image.num_trans, + &opng_image.trans_values_ptr)) + { + /* Double copying (pointer + value) is necessary here + * due to an inconsistency in the libpng design. + */ + if (opng_image.trans_values_ptr != NULL) + { + opng_image.trans_values = *opng_image.trans_values_ptr; + opng_image.trans_values_ptr = &opng_image.trans_values; + } + } + + if (!load_metadata) + return; + + if (png_get_bKGD(png_ptr, info_ptr, &opng_image.background_ptr)) + { + /* Same problem as in tRNS. */ + opng_image.background = *opng_image.background_ptr; + opng_image.background_ptr = &opng_image.background; + } + png_get_hIST(png_ptr, info_ptr, &opng_image.hist); + if (png_get_sBIT(png_ptr, info_ptr, &opng_image.sig_bit_ptr)) + { + /* Same problem as in tRNS. */ + opng_image.sig_bit = *opng_image.sig_bit_ptr; + opng_image.sig_bit_ptr = &opng_image.sig_bit; + } + opng_image.num_unknowns = + png_get_unknown_chunks(png_ptr, info_ptr, &opng_image.unknowns); + + if (end_info_ptr == NULL) /* dummy, keep compilers happy */ + return; +} + + +/** Image info transfer **/ +static void +opng_store_image_info(png_structp png_ptr, png_infop info_ptr, + png_infop end_info_ptr, int store_metadata) +{ + png_debug(0, "Storing opng_image to info struct\n"); + OPNG_ENSURE(opng_image.row_pointers != NULL, "No info in opng_image"); + + png_set_IHDR(png_ptr, info_ptr, + opng_image.width, opng_image.height, opng_image.bit_depth, + opng_image.color_type, opng_image.interlace_type, + opng_image.compression_type, opng_image.filter_type); + png_set_rows(write_ptr, write_info_ptr, opng_image.row_pointers); + if (opng_image.palette != NULL) + png_set_PLTE(png_ptr, info_ptr, + opng_image.palette, opng_image.num_palette); + /* Transparency is not considered metadata, although tRNS is ancillary. + * See the comment in opng_is_critical_chunk() above. + */ + if (opng_image.trans != NULL || opng_image.trans_values_ptr != NULL) + png_set_tRNS(png_ptr, info_ptr, + opng_image.trans, opng_image.num_trans, + opng_image.trans_values_ptr); + + if (!store_metadata) + return; + + if (opng_image.background_ptr != NULL) + png_set_bKGD(png_ptr, info_ptr, opng_image.background_ptr); + if (opng_image.hist != NULL) + png_set_hIST(png_ptr, info_ptr, opng_image.hist); + if (opng_image.sig_bit_ptr != NULL) + png_set_sBIT(png_ptr, info_ptr, opng_image.sig_bit_ptr); + if (opng_image.num_unknowns != 0) + { + int i; + png_set_unknown_chunks(png_ptr, info_ptr, + opng_image.unknowns, opng_image.num_unknowns); + /* Is this really necessary? Should it not be implemented in libpng? */ + for (i = 0; i < opng_image.num_unknowns; ++i) + png_set_unknown_chunk_location(png_ptr, info_ptr, + i, opng_image.unknowns[i].location); + } + + if (end_info_ptr == NULL) /* dummy, keep compilers happy */ + return; +} + + +/** Image info destruction **/ +static void +opng_destroy_image_info(void) +{ + png_uint_32 i; + int j; + + png_debug(0, "Destroying opng_image\n"); + if (opng_image.row_pointers == NULL) + return; /* nothing to clean up */ + + for (i = 0; i < opng_image.height; ++i) + osys_free(opng_image.row_pointers[i]); + osys_free(opng_image.row_pointers); + osys_free(opng_image.palette); + osys_free(opng_image.trans); + osys_free(opng_image.hist); + for (j = 0; j < opng_image.num_unknowns; ++j) + osys_free(opng_image.unknowns[j].data); + osys_free(opng_image.unknowns); + /* DO NOT deallocate background_ptr, sig_bit_ptr, trans_values_ptr. + * See the comments regarding double copying inside opng_load_image_info(). + */ + + /* Clear the space here and do not worry about double-deallocation issues + * that might arise later on. + */ + memset(&opng_image, 0, sizeof(opng_image)); +} + + +/** Image file reading **/ +static void +opng_read_file(FILE *infile) +{ + char fmt_name[16]; + int num_img; + png_uint_32 reductions; + const char * volatile err_msg; /* volatile is required by cexcept */ + + png_debug(0, "Reading opng_image\n"); + assert(infile != NULL); + + Try + { + read_info_ptr = read_end_info_ptr = NULL; + read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, + NULL, opng_error, opng_warning); + if (read_ptr != NULL) + { + read_info_ptr = png_create_info_struct(read_ptr); + if (read_info_ptr != NULL) + read_end_info_ptr = png_create_info_struct(read_ptr); + } + if (read_end_info_ptr == NULL) /* something went wrong on the way */ + Throw "Out of memory"; + + png_set_keep_unknown_chunks(read_ptr, PNG_HANDLE_CHUNK_ALWAYS, NULL, 0); + + png_debug(0, "Reading input stream\n"); + opng_init_read_data(); + pngx_set_read_fn(read_ptr, infile, opng_read_data); + fmt_name[0] = '\0'; + num_img = pngx_read_image(read_ptr, read_info_ptr, + fmt_name, sizeof(fmt_name), NULL, 0); + if (num_img > 1) + opng_info.status |= INPUT_HAS_MULTIPLE_IMAGES; + if ((opng_info.status & INPUT_IS_PNG_FILE) && + (opng_info.status & INPUT_HAS_MULTIPLE_IMAGES)) + { + /* pngxtern can't distinguish between APNG and proper PNG. */ + strcpy(fmt_name, (opng_info.status & INPUT_HAS_PNG_SIGNATURE) ? + "APNG" : "APNG datastream"); + } + OPNG_ENSURE(num_img >= 0, "Format name buffer too small for pngxtern"); + OPNG_ENSURE(fmt_name[0] != 0, "No format name from pngxtern"); + + if (opng_info.in_file_size == 0) + { + if (fseek(infile, 0, SEEK_END) == 0) + { + opng_info.in_file_size = (unsigned long)ftell(infile); + if (opng_info.in_file_size > LONG_MAX) + opng_info.in_file_size = 0; + } + if (opng_info.in_file_size == 0) + opng_print_warning("Unable to get the correct file size"); + } + + err_msg = NULL; /* everything is ok */ + } + Catch (err_msg) + { + /* If the critical info has been loaded, treat all errors as warnings. + * This enables a more advanced data recovery. + */ + if (opng_validate_image(read_ptr, read_info_ptr)) + { + png_warning(read_ptr, err_msg); + err_msg = NULL; + } + } + + Try + { + if (err_msg != NULL) + Throw err_msg; + + /* Display format and image information. */ + if (strcmp(fmt_name, "PNG") != 0) + { + opng_printf("Importing %s", fmt_name); + if (opng_info.status & INPUT_HAS_MULTIPLE_IMAGES) + { + if (!(opng_info.status & INPUT_IS_PNG_FILE)) + opng_printf(" (multi-image or animation)"); + if (options->snip) + opng_printf("; snipping..."); + } + opng_printf("\n"); + } + opng_load_image_info(read_ptr, read_info_ptr, read_end_info_ptr, 1); + opng_print_image_info(1, 1, 1, 1); + opng_printf("\n"); + + /* Choose the applicable image reductions. */ + reductions = OPNG_REDUCE_ALL; + if (options->nb) + reductions &= ~OPNG_REDUCE_BIT_DEPTH; + if (options->nc) + reductions &= ~OPNG_REDUCE_COLOR_TYPE; + if (options->np) + reductions &= ~OPNG_REDUCE_PALETTE_ALL; + if (opng_info.status & INPUT_HAS_DIGITAL_SIGNATURE) + { + /* Do not reduce signed files. */ + reductions = OPNG_REDUCE_NONE; + } + if ((opng_info.status & INPUT_IS_PNG_FILE) && + (opng_info.status & INPUT_HAS_MULTIPLE_IMAGES) && + (reductions != OPNG_REDUCE_NONE) && !options->snip) + { + opng_printf( + "Can't reliably reduce APNG file; disabling reductions.\n" + "(Rerun " PROGRAM_NAME " with the -snip option " + "to convert APNG to optimized PNG.)\n"); + reductions = OPNG_REDUCE_NONE; + } + + /* Try to reduce the image. */ + opng_info.reductions = + opng_reduce_image(read_ptr, read_info_ptr, reductions); + + /* If the image is reduced, enforce full compression. */ + if (opng_info.reductions != OPNG_REDUCE_NONE) + { + opng_load_image_info(read_ptr, read_info_ptr, read_end_info_ptr, 1); + opng_printf("Reducing image to "); + opng_print_image_info(0, 1, 1, 0); + opng_printf("\n"); + } + + /* Change the interlace type if required. */ + if (options->interlace >= 0 && + opng_image.interlace_type != options->interlace) + { + opng_image.interlace_type = options->interlace; + /* A change in interlacing requires IDAT recompression. */ + opng_info.status |= OUTPUT_NEEDS_NEW_IDAT; + } + } + Catch (err_msg) + { + /* Do the cleanup, then rethrow the exception. */ + png_data_freer(read_ptr, read_info_ptr, + PNG_DESTROY_WILL_FREE_DATA, PNG_FREE_ALL); + png_data_freer(read_ptr, read_end_info_ptr, + PNG_DESTROY_WILL_FREE_DATA, PNG_FREE_ALL); + png_destroy_read_struct(&read_ptr, &read_info_ptr, + &read_end_info_ptr); + Throw err_msg; + } + + png_debug(0, "Destroying data structs\n"); + /* Leave the data for upcoming processing. */ + png_data_freer(read_ptr, read_info_ptr, PNG_USER_WILL_FREE_DATA, + PNG_FREE_ALL); + png_data_freer(read_ptr, read_end_info_ptr, PNG_USER_WILL_FREE_DATA, + PNG_FREE_ALL); + png_destroy_read_struct(&read_ptr, &read_info_ptr, &read_end_info_ptr); +} + + +/** PNG file writing **/ +/** If the output file is NULL, PNG encoding is still done, + but no file is written. **/ +static void +opng_write_file(FILE *outfile, + int compression_level, int memory_level, + int compression_strategy, int filter) +{ + const char * volatile err_msg; /* volatile is required by cexcept */ + + static int filter_table[FILTER_MAX + 1] = + { + PNG_FILTER_NONE, PNG_FILTER_SUB, PNG_FILTER_UP, + PNG_FILTER_AVG, PNG_FILTER_PAETH, PNG_ALL_FILTERS + }; + + png_debug(0, "Encoding opng_image\n"); + OPNG_ENSURE( + compression_level >= COMPR_LEVEL_MIN && + compression_level <= COMPR_LEVEL_MAX && + memory_level >= MEM_LEVEL_MIN && + memory_level <= MEM_LEVEL_MAX && + compression_strategy >= STRATEGY_MIN && + compression_strategy <= STRATEGY_MAX && + filter >= FILTER_MIN && + filter <= FILTER_MAX, + "Invalid encoding parameters"); + + Try + { + write_info_ptr = write_end_info_ptr = NULL; + write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, opng_error, opng_warning); + if (write_ptr != NULL) + { + write_info_ptr = png_create_info_struct(write_ptr); + if (write_info_ptr != NULL) + write_end_info_ptr = png_create_info_struct(write_ptr); + } + if (write_end_info_ptr == NULL) /* something went wrong on the way */ + Throw "Out of memory"; + + png_set_compression_level(write_ptr, compression_level); + png_set_compression_mem_level(write_ptr, memory_level); + png_set_compression_strategy(write_ptr, compression_strategy); + png_set_filter(write_ptr, PNG_FILTER_TYPE_BASE, filter_table[filter]); + if (compression_strategy != Z_HUFFMAN_ONLY && + compression_strategy != Z_RLE) + { + if (options->window_bits > 0) + png_set_compression_window_bits(write_ptr, options->window_bits); + } + else + { +#ifdef WBITS_8_OK + png_set_compression_window_bits(write_ptr, 8); +#else + png_set_compression_window_bits(write_ptr, 9); +#endif + } + png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, NULL, 0); + opng_store_image_info(write_ptr, write_info_ptr, write_end_info_ptr, + (outfile != NULL ? 1 : 0)); + + png_debug(0, "Writing PNG stream\n"); + opng_init_write_data(); + pngx_set_write_fn(write_ptr, outfile, opng_write_data, NULL); + png_write_png(write_ptr, write_info_ptr, 0, NULL); + + err_msg = NULL; /* everything is ok */ + } + Catch (err_msg) + { + /* Set IDAT size to invalid. */ + opng_info.out_idat_size = PNG_UINT_31_MAX + 1; + } + + png_debug(0, "Destroying data structs\n"); + png_destroy_info_struct(write_ptr, &write_end_info_ptr); + png_destroy_write_struct(&write_ptr, &write_info_ptr); + + if (err_msg != NULL) + Throw err_msg; +} + + +/** PNG file copying **/ +static void +opng_copy_file(FILE *infile, FILE *outfile) +{ + volatile png_bytep buf; /* volatile is required by cexcept */ + const png_uint_32 buf_size_incr = 0x1000; + png_uint_32 buf_size, length; + png_byte chunk_hdr[8]; + const char * volatile err_msg; + + png_debug(0, "Copying PNG stream\n"); + assert(infile != NULL && outfile != NULL); + + write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, opng_error, opng_warning); + if (write_ptr == NULL) + Throw "Out of memory"; + opng_init_write_data(); + pngx_set_write_fn(write_ptr, outfile, opng_write_data, NULL); + + Try + { + buf = NULL; + buf_size = 0; + + /* Write the signature in the output file. */ + pngx_write_sig(write_ptr); + + /* Copy all chunks until IEND. */ + /* Error checking is done only at a very basic level. */ + do + { + if (fread(chunk_hdr, 8, 1, infile) != 1) /* length + name */ + Throw "Read error"; + length = png_get_uint_32(chunk_hdr); + if (length > PNG_UINT_31_MAX) + { + if (buf == NULL && length == 0x89504e47) /* "\x89PNG" */ + continue; /* skip the signature */ + Throw "Data error"; + } + if (length + 4 > buf_size) + { + png_free(write_ptr, buf); + buf_size = (((length + 4) + (buf_size_incr - 1)) + / buf_size_incr) * buf_size_incr; + buf = (png_bytep)png_malloc(write_ptr, buf_size); + /* Do not use realloc() here, it's slower. */ + } + if (fread(buf, length + 4, 1, infile) != 1) /* data + crc */ + Throw "Read error"; + png_write_chunk(write_ptr, chunk_hdr + 4, buf, length); + } while (memcmp(chunk_hdr + 4, sig_IEND, 4) != 0); + + err_msg = NULL; /* everything is ok */ + } + Catch (err_msg) + { + } + + png_free(write_ptr, buf); + png_destroy_write_struct(&write_ptr, NULL); + + if (err_msg != NULL) + Throw err_msg; +} + + +/** Iteration initialization **/ +static void +opng_init_iteration(int cmdline_set, const char *preset, const char *mask, + int *output_set) +{ + bitset_t set; + + *output_set = cmdline_set; + if (*output_set == BITSET_EMPTY || options->optim_level >= 0) + { + OPNG_ENSURE(bitset_parse(preset, &set) == 0, "Invalid iteration preset"); + *output_set |= set; + } + OPNG_ENSURE(bitset_parse(mask, &set) == 0, "Invalid iteration mask"); + *output_set &= set; +} + + +/** Iteration initialization **/ +static void +opng_init_iterations(void) +{ + bitset_t compr_level_set, mem_level_set, strategy_set, filter_set; + int preset_index; + int t1, t2; + + /* Set the IDAT size limit. The trials that pass this limit will be + * abandoned, as there will be no need to wait until their completion. + * This limit may further decrease as iterations go on. + */ + if ((opng_info.status & OUTPUT_NEEDS_NEW_IDAT) || options->full) + opng_info.max_idat_size = PNG_UINT_31_MAX; + else + { + OPNG_ENSURE(opng_info.in_idat_size > 0, "No IDAT in input"); + /* Add the input PLTE and tRNS sizes to the initial max IDAT size, + * to account for the changes that may occur during reduction. + * This incurs a negligible overhead on processing only: the final + * IDAT size will not be affected, because a precise check will be + * performed at the end, inside opng_finish_iterations(). + */ + opng_info.max_idat_size = + opng_info.in_idat_size + opng_info.in_plte_trns_size; + } + + /* Get preset_index from options->optim_level, but leave the latter intact, + * because the effect of "optipng -o2 -z... -f..." is slightly different + * than the effect of "optipng -z... -f..." (without "-o"). + */ + preset_index = options->optim_level; + if (preset_index < 0) + preset_index = OPTIM_LEVEL_DEFAULT; + else if (preset_index > OPTIM_LEVEL_MAX) + preset_index = OPTIM_LEVEL_MAX; + + /* Load the iteration sets from the implicit (preset) values, + * and also from the explicit (user-specified) values. + */ + opng_init_iteration(options->compr_level_set, + compr_level_presets[preset_index], compr_level_mask, &compr_level_set); + opng_init_iteration(options->mem_level_set, + mem_level_presets[preset_index], mem_level_mask, &mem_level_set); + opng_init_iteration(options->strategy_set, + strategy_presets[preset_index], strategy_mask, &strategy_set); + opng_init_iteration(options->filter_set, + filter_presets[preset_index], filter_mask, &filter_set); + + /* Replace the empty sets with the libpng's "best guess" heuristics. */ + if (compr_level_set == BITSET_EMPTY) + BITSET_SET(compr_level_set, Z_BEST_COMPRESSION); /* -zc9 */ + if (mem_level_set == BITSET_EMPTY) + BITSET_SET(mem_level_set, 8); + if (opng_image.bit_depth < 8 || opng_image.palette != NULL) + { + if (strategy_set == BITSET_EMPTY) + BITSET_SET(strategy_set, Z_DEFAULT_STRATEGY); /* -zs0 */ + if (filter_set == BITSET_EMPTY) + BITSET_SET(filter_set, 0); /* -f0 */ + } + else + { + if (strategy_set == BITSET_EMPTY) + BITSET_SET(strategy_set, Z_FILTERED); /* -zs1 */ + if (filter_set == BITSET_EMPTY) + BITSET_SET(filter_set, 5); /* -f0 */ + } + + /* Store the results into opng_info. */ + opng_info.compr_level_set = compr_level_set; + opng_info.mem_level_set = mem_level_set; + opng_info.strategy_set = strategy_set; + opng_info.filter_set = filter_set; + t1 = bitset_count(compr_level_set) * + bitset_count(strategy_set & ~((1 << Z_HUFFMAN_ONLY) | (1 << Z_RLE))); + t2 = bitset_count(strategy_set & ((1 << Z_HUFFMAN_ONLY) | (1 << Z_RLE))); + opng_info.num_iterations = (t1 + t2) * + bitset_count(mem_level_set) * bitset_count(filter_set); + + if (opng_info.num_iterations <= 0) + Throw "Invalid iteration parameters (-zc, -zm, -zs, -f)"; +} + + +/** Iteration **/ +static void +opng_iterate(void) +{ + bitset_t compr_level_set, mem_level_set, strategy_set, filter_set; + int compr_level, mem_level, strategy, filter; + int counter; + int carriage_returned; + + OPNG_ENSURE(opng_info.num_iterations > 0, "Iterations not initialized"); + if ((opng_info.num_iterations == 1) && + (opng_info.status & OUTPUT_NEEDS_NEW_IDAT)) + { + /* We already know this combination is going to be selected. + * Do not waste time running it twice. + */ + opng_info.best_idat_size = 0; + opng_info.best_compr_level = opng_bitset_min(opng_info.compr_level_set); + opng_info.best_mem_level = opng_bitset_min(opng_info.mem_level_set); + opng_info.best_strategy = opng_bitset_min(opng_info.strategy_set); + opng_info.best_filter = opng_bitset_min(opng_info.filter_set); + return; + } + + /* Prepare for the big iteration. */ + compr_level_set = opng_info.compr_level_set; + mem_level_set = opng_info.mem_level_set; + strategy_set = opng_info.strategy_set; + filter_set = opng_info.filter_set; + opng_info.best_idat_size = PNG_UINT_31_MAX + 1; + opng_info.best_compr_level = -1; + opng_info.best_mem_level = -1; + opng_info.best_strategy = -1; + opng_info.best_filter = -1; + + /* Iterate through the "hyper-rectangle" (zc, zm, zs, f). */ + opng_printf("Trying:\n"); + carriage_returned = 0; + counter = 0; + for (filter = FILTER_MIN; filter <= FILTER_MAX; ++filter) + if (BITSET_GET(filter_set, filter)) + for (strategy = STRATEGY_MIN; strategy <= STRATEGY_MAX; ++strategy) + if (BITSET_GET(strategy_set, strategy)) + { + /* The compression level has no significance under + Z_HUFFMAN_ONLY or Z_RLE. */ + bitset_t saved_level_set = compr_level_set; + if (strategy == Z_HUFFMAN_ONLY) + { + compr_level_set = BITSET_EMPTY; + BITSET_SET(compr_level_set, 1); + } + else if (strategy == Z_RLE) + { + compr_level_set = BITSET_EMPTY; + BITSET_SET(compr_level_set, 9); /* use deflate_slow */ + } + for (compr_level = COMPR_LEVEL_MAX; + compr_level >= COMPR_LEVEL_MIN; --compr_level) + if (BITSET_GET(compr_level_set, compr_level)) + { + for (mem_level = MEM_LEVEL_MAX; + mem_level >= MEM_LEVEL_MIN; --mem_level) + if (BITSET_GET(mem_level_set, mem_level)) + { + ++counter; + opng_printf( + " zc = %d zm = %d zs = %d f = %d\t\t", + compr_level, mem_level, strategy, filter); + opng_write_file(NULL, + compr_level, mem_level, strategy, filter); + if (opng_info.out_idat_size > PNG_UINT_31_MAX) + { + if (options->ver) + { + opng_printf("IDAT too big\n"); + carriage_returned = 0; + } + else + { + opng_printf("\r"); + carriage_returned = 1; + } + continue; + } + opng_printf("IDAT size = %lu\n", + (unsigned long)opng_info.out_idat_size); + carriage_returned = 0; + if (opng_info.best_idat_size < opng_info.out_idat_size) + continue; + if (opng_info.best_idat_size == opng_info.out_idat_size + && opng_info.best_strategy >= Z_HUFFMAN_ONLY) + continue; /* it's neither smaller nor faster */ + opng_info.best_compr_level = compr_level; + opng_info.best_mem_level = mem_level; + opng_info.best_strategy = strategy; + opng_info.best_filter = filter; + opng_info.best_idat_size = opng_info.out_idat_size; + if (!options->full) + opng_info.max_idat_size = opng_info.out_idat_size; + } + } + compr_level_set = saved_level_set; + } + if (carriage_returned) /* wipe out last line */ + opng_printf(" \t\t\r"); + + OPNG_ENSURE(counter == opng_info.num_iterations, + "Inconsistent iteration counter"); +} + + +/** Iteration finalization **/ +static void +opng_finish_iterations(void) +{ + if (opng_info.best_idat_size + opng_info.out_plte_trns_size + < opng_info.in_idat_size + opng_info.in_plte_trns_size) + opng_info.status |= OUTPUT_NEEDS_NEW_IDAT; + if (opng_info.status & OUTPUT_NEEDS_NEW_IDAT) + { + opng_printf("\nSelecting parameters:\n" + " zc = %d zm = %d zs = %d f = %d", + opng_info.best_compr_level, opng_info.best_mem_level, + opng_info.best_strategy, opng_info.best_filter); + if (opng_info.best_idat_size != 0) /* trials have been run */ + opng_printf("\t\tIDAT size = %lu", + (unsigned long)opng_info.best_idat_size); + opng_printf("\n"); + } +} + + +/** Image file optimization **/ +static void +opng_optimize_impl(const char *infile_name) +{ + static FILE *infile, *outfile; /* static or volatile is required */ + static const char *outfile_name, *bakfile_name; /* by cexcept */ + static int new_outfile; + char name_buf[FILENAME_MAX], tmp_buf[FILENAME_MAX]; + const char * volatile err_msg; + + png_debug1(0, "Optimizing file: %s\n", infile_name); + memset(&opng_info, 0, sizeof(opng_info)); + if (options->force) + opng_info.status |= OUTPUT_NEEDS_NEW_IDAT; + + err_msg = NULL; /* prepare for error handling */ + + if ((infile = fopen(infile_name, "rb")) == NULL) + Throw "Can't open the input file"; + Try + { + opng_read_file(infile); + } + Catch (err_msg) + { + /* assert(err_msg != NULL); */ + } + fclose(infile); /* finally */ + if (err_msg != NULL) + Throw err_msg; /* rethrow */ + + /* Check the PNG datastream and signature flags. */ + if (!(opng_info.status & INPUT_HAS_PNG_DATASTREAM)) + opng_info.status |= OUTPUT_NEEDS_NEW_IDAT; + if (!(opng_info.status & INPUT_HAS_PNG_SIGNATURE)) + opng_info.status |= OUTPUT_NEEDS_NEW_FILE; + + /* Check the digital signature flag. */ + if (opng_info.status & INPUT_HAS_DIGITAL_SIGNATURE) + { + opng_printf("Digital signature found in input."); + if (options->force) + { + opng_printf(" Erasing...\n"); + opng_info.status |= OUTPUT_NEEDS_NEW_FILE; + } + else + { + opng_printf(" Rerun " PROGRAM_NAME " with the -force option.\n"); + Throw "Can't optimize digitally-signed files"; + } + } + + /* Check the multi-image flag. */ + if (opng_info.status & INPUT_HAS_MULTIPLE_IMAGES) + { + if (options->snip) + ++summary.snip_count; + else if (!(opng_info.status & INPUT_IS_PNG_FILE)) + { + opng_printf("Conversion to PNG requires snipping. " + "Rerun " PROGRAM_NAME " with the -snip option.\n"); + Throw "Incompatible input format"; + } + } + if ((opng_info.status & INPUT_HAS_APNG) && options->snip) + opng_info.status |= OUTPUT_NEEDS_NEW_FILE; + + /* Check the junk flag. */ + if (opng_info.status & INPUT_HAS_JUNK) + opng_info.status |= OUTPUT_NEEDS_NEW_FILE; + + /* Check the error flag. */ + if (opng_info.status & INPUT_HAS_ERRORS) + { + opng_printf("Recoverable errors found in input."); + if (options->fix) + { + opng_printf(" Fixing...\n"); + opng_info.status |= OUTPUT_NEEDS_NEW_FILE; + ++summary.err_count; + ++summary.fix_count; + } + else + { + opng_printf(" Rerun " PROGRAM_NAME " with the -fix option.\n"); + Throw "Previous error(s) not fixed"; + } + } + + /* Initialize the output file name. */ + outfile_name = NULL; + if (!(opng_info.status & INPUT_IS_PNG_FILE)) + { + if (osys_fname_chext(name_buf, sizeof(name_buf), infile_name, + ".png") == NULL) + Throw "Can't create the output file (name too long)"; + outfile_name = name_buf; + } + if (options->out_name != NULL) + outfile_name = options->out_name; /* override the old name */ + if (options->dir_name != NULL) + { + const char *tmp_name; + if (outfile_name != NULL) + { + strcpy(tmp_buf, outfile_name); + tmp_name = tmp_buf; + } + else + tmp_name = infile_name; + if (osys_fname_chdir(name_buf, sizeof(name_buf), tmp_name, + options->dir_name) == NULL) + Throw "Can't create the output file (name too long)"; + outfile_name = name_buf; + } + if (outfile_name == NULL) + { + outfile_name = infile_name; + new_outfile = 0; + } + else + new_outfile = (osys_fname_cmp(infile_name, outfile_name) != 0) ? 1 : 0; + + /* Initialize the backup file name. */ + bakfile_name = tmp_buf; + if (new_outfile) + { + if (osys_fname_mkbak(tmp_buf, sizeof(tmp_buf), outfile_name) == NULL) + bakfile_name = NULL; + } + else + { + if (osys_fname_mkbak(tmp_buf, sizeof(tmp_buf), infile_name) == NULL) + bakfile_name = NULL; + } + /* Check the name even in simulation mode, to ensure a uniform behavior. */ + if (bakfile_name == NULL) + Throw "Can't create backup file (name too long)"; + /* Check the backup file before engaging into lengthy trials. */ + if (!options->simulate && osys_ftest(outfile_name, "e") == 0) + { + if (new_outfile && !options->keep) + Throw "The output file exists, try backing it up (use -keep)"; + if (osys_ftest(outfile_name, "fw") != 0 || + osys_ftest(bakfile_name, "e") == 0) + Throw "Can't back up the existing output file"; + } + + /* Display the input IDAT/file sizes. */ + if (opng_info.status & INPUT_HAS_PNG_DATASTREAM) + opng_printf("Input IDAT size = %lu bytes\n", + (unsigned long)opng_info.in_idat_size); + opng_printf("Input file size = %lu bytes\n", opng_info.in_file_size); + + if (options->nz + && (opng_info.status & INPUT_HAS_PNG_DATASTREAM) + && (opng_info.status & OUTPUT_NEEDS_NEW_IDAT)) + opng_print_warning( + "IDAT recompression is required; ignoring the -o0/-nz option"); + + /* Find the best parameters and see if it's worth recompressing. */ + if (!options->nz || (opng_info.status & OUTPUT_NEEDS_NEW_IDAT)) + { + opng_init_iterations(); + opng_iterate(); + opng_finish_iterations(); + } + if (opng_info.status & OUTPUT_NEEDS_NEW_IDAT) + opng_info.status |= OUTPUT_NEEDS_NEW_FILE; + if (!(opng_info.status & OUTPUT_NEEDS_NEW_FILE)) + { + opng_printf("\n%s is already optimized.\n", infile_name); + if (!new_outfile) + return; + } + + if (options->simulate) + { + if (new_outfile) + opng_printf("\nSimulation mode: %s not created.\n", outfile_name); + else + opng_printf("\nSimulation mode: %s not changed.\n", infile_name); + return; + } + + /* Make room for the output file. */ + if (new_outfile) + { + opng_printf("\nOutput file: %s\n", outfile_name); + if (options->dir_name != NULL) + osys_dir_make(options->dir_name); + if (osys_ftest(outfile_name, "e") == 0) + if (rename(outfile_name, bakfile_name) != 0) + Throw "Can't back up the output file"; + } + else + { + if (rename(infile_name, bakfile_name) != 0) + Throw "Can't back up the input file"; + } + + outfile = fopen(outfile_name, "wb"); + Try + { + if (outfile == NULL) + Throw "Can't open the output file"; + if (opng_info.status & OUTPUT_NEEDS_NEW_IDAT) + { + /* Write a brand new PNG datastream to the output. */ + opng_write_file(outfile, + opng_info.best_compr_level, opng_info.best_mem_level, + opng_info.best_strategy, opng_info.best_filter); + } + else + { + /* Copy the input PNG datastream to the output. */ + infile = osys_fopen_at((new_outfile ? infile_name : bakfile_name), + "rb", opng_info.in_datastream_offset, SEEK_SET); + if (infile == NULL) + Throw "Can't reopen the input file"; + Try + { + opng_info.best_idat_size = opng_info.in_idat_size; + opng_copy_file(infile, outfile); + } + Catch (err_msg) + { + /* assert(err_msg != NULL); */ + } + fclose(infile); /* finally */ + if (err_msg != NULL) + Throw err_msg; /* rethrow */ + } + } + Catch (err_msg) + { + if (outfile != NULL) + fclose(outfile); + /* Restore the original input file and rethrow the exception. */ + if (remove(outfile_name) != 0 || + rename(bakfile_name, (new_outfile ? outfile_name : infile_name)) != 0) + opng_print_warning( + "The original file could not be recovered from the backup"); + Throw err_msg; /* rethrow */ + } + /* assert(err_msg == NULL); */ + fclose(outfile); + + /* Preserve file attributes (e.g. ownership, access rights, time stamps) + * on request, if possible. + */ + if (options->preserve) + osys_fattr_copy(outfile_name, (new_outfile ? infile_name : bakfile_name)); + + /* Remove the backup file if it is not needed. */ + if (!new_outfile && !options->keep) + { + if (remove(bakfile_name) != 0) + Throw "Can't remove the backup file"; + } + + /* Display the output IDAT/file sizes. */ + opng_printf("\nOutput IDAT size = %lu bytes", + (unsigned long)opng_info.out_idat_size); + if (opng_info.status & INPUT_HAS_PNG_DATASTREAM) + { + opng_printf(" ("); + opng_print_size_difference(opng_info.in_idat_size, + opng_info.out_idat_size, 0); + opng_printf(")"); + } + opng_printf("\nOutput file size = %lu bytes (", opng_info.out_file_size); + opng_print_size_difference(opng_info.in_file_size, + opng_info.out_file_size, 1); + opng_printf(")\n"); +} + + +/** Engine initialization **/ +int +opng_initialize(const struct opng_options *init_options, + const struct opng_ui *init_ui) +{ + memset(&summary, 0, sizeof(summary)); + options = init_options; + + opng_printf = init_ui->printf_fn; + opng_flush = init_ui->flush_fn; + opng_progress = init_ui->progress_fn; + opng_panic = init_ui->panic_fn; + + return 0; +} + + +/** Engine execution **/ +int +opng_optimize(const char *infile_name) +{ + const char *err_msg; + volatile int result; /* needs not be volatile, but keeps compilers happy */ + + opng_printf("** Processing: %s\n", infile_name); + ++summary.file_count; + opng_clear_image_info(); + Try + { + opng_optimize_impl(infile_name); + result = 0; + } + Catch (err_msg) + { + ++summary.err_count; + opng_print_error(err_msg); + result = -1; + } + opng_destroy_image_info(); + opng_printf("\n"); + return result; +} + + +/** Engine finalization **/ +int +opng_finalize(void) +{ + if (options->ver || summary.snip_count > 0 || summary.err_count > 0) + { + opng_printf("** Status report\n"); + opng_printf("%u file(s) have been processed.\n", summary.file_count); + if (summary.snip_count > 0) + { + opng_printf("%u multi-image file(s) have been snipped.\n", + summary.snip_count); + } + if (summary.err_count > 0) + { + opng_printf("%u error(s) have been encountered.\n", + summary.err_count); + if (summary.fix_count > 0) + opng_printf("%u erroneous file(s) have been fixed.\n", + summary.fix_count); + } + } + + return 0; +} diff -urN optipng-0.6.1/src/optipng.c optipng-0.6.2/src/optipng.c --- optipng-0.6.1/src/optipng.c 2008-07-20 00:14:00.000000000 -0300 +++ optipng-0.6.2/src/optipng.c 2008-11-10 02:56:00.000000000 -0200 @@ -6,44 +6,39 @@ ** OptiPNG is open-source software, and is distributed under the same ** licensing and warranty terms as libpng. ** - ** This program functions as follows: - ** For each input image, it reduces the bit depth, color type and - ** palette without losing any information; combines several methods - ** and strategies of compression; and reencodes the IDAT data using - ** the best method found. If none of these methods yield a smaller - ** IDAT, then the original IDAT is preserved. - ** The output file will have all the IDAT data in a single chunk. + ** PNG optimization is described in detail in the PNG-Tech article + ** "A guide to PNG optimization" + ** http://www.cs.toronto.edu/~cosmin/pngtech/optipng.html ** - ** The idea of running multiple trials with different PNG filters - ** and zlib parameters is inspired from the pngcrush program by - ** Glenn Randers-Pehrson. + ** The idea of running multiple compression trials with different + ** PNG filters and zlib parameters is inspired from the pngcrush + ** program by Glenn Randers-Pehrson. + ** The idea of performing lossless image reductions is inspired from + ** the pngrewrite program by Jason Summers. ** ** Requirements: ** ANSI C or ISO C compiler and library. ** POSIX library for enhanced functionality. ** zlib version 1.2.1 or newer (version 1.2.3 is bundled). - ** libpng version 1.2.9 or newer (version 1.2.29 is bundled). - ** pngxtern (version 0.6 is bundled). + ** libpng version 1.2.9 or newer (version 1.2.33 is bundled). + ** pngxtern (version 0.6.2 is bundled). ** cexcept (version 2.0.1 is bundled). **/ #include -#include +#include #include -#include #include #include #include #include "proginfo.h" -#include "png.h" -#include "pngx.h" -#include "pngxtern.h" -#include "opngreduc.h" -#include "cexcept.h" +#include "optipng.h" #include "cbitset.h" #include "osys.h" #include "strutil.h" +#include "png.h" +#include "zlib.h" static const char *msg_intro = @@ -63,7 +58,7 @@ static const char *msg_short_help = "Type \"optipng -h\" for advanced help.\n" "\n" - "Usage:\n" + "Synopsis:\n" " optipng [options] files ...\n" "Files:\n" " Image files of type: PNG, BMP, GIF, PNM or TIFF\n" @@ -80,7 +75,7 @@ " optipng -o7 file.png\t\t(very slow)\n"; static const char *msg_help = - "Usage:\n" + "Synopsis:\n" " optipng [options] files ...\n" "Files:\n" " Image files of type: PNG, BMP, GIF, PNM or TIFF\n" @@ -115,6 +110,9 @@ " -fix\t\tenable error recovery\n" " -force\t\tenforce writing of a new output file\n" " -full\t\tproduce a full report on IDAT (might reduce speed)\n" +#if 0 /* parallel processing is not implemented */ + " -jobs \tallow parallel jobs\n" +#endif " -preserve\t\tpreserve file attributes if possible\n" " -simulate\t\trun in simulation mode, do not create output files\n" " -snip\t\tcut one image out of multi-image or animation files\n" @@ -125,25 +123,21 @@ " -dir \twrite output file(s) to \n" " -log \t\tlog messages to \n" " --\t\t\tstop option switch parsing\n" - "Optimization level presets:\n" - " -o0 <=> -nz\n" - " -o1 <=> [apply libpng heuristics]\t\t(1 trial)\n" - " -o2 <=> -zc9 -zm8 -zs0-3 -f0,5\t\t(8 trials)\n" - " -o3 <=> -zc9 -zm8-9 -zs0-3 -f0,5\t\t(16 trials)\n" - " -o4 <=> -zc9 -zm8 -zs0-3 -f0-5\t\t(24 trials)\n" - " -o5 <=> -zc9 -zm8-9 -zs0-3 -f0-5\t\t(48 trials)\n" - " -o6 <=> -zc1-9 -zm8 -zs0-3 -f0-5\t\t(120 trials)\n" - " -o7 <=> -zc1-9 -zm8-9 -zs0-3 -f0-5\t(240 trials)\n" - "Notes:\n" - " - The option names are case-insensitive and can be abbreviated.\n" - " - Range arguments are cumulative; e.g.\n" - " -f0 -f3-5 <=> -f0,3-5\n" - " -zs0 -zs1 -zs2-3 <=> -zs0,1,2,3 <=> -zs0-3\n" - " - The libpng heuristics consist of:\n" - " -o1 <=> -zc9 -zm8 -zs0 -f0\t\t(if PLTE is present)\n" - " -o1 <=> -zc9 -zm8 -zs1 -f5\t\t(if PLTE is not present)\n" - " - The most exhaustive search -zc1-9 -zm1-9 -zs0-3 -f0-5 (1080 trials)\n" - " is offered only as an advanced option, and it is not recomended.\n" + "Optimization parameters:\n" + " The optimization level presets\n" + " -o0 <=> -nz\n" + " -o1 <=> [use the libpng heuristics]\t(1 trial)\n" + " -o2 <=> -zc9 -zm8 -zs0-3 -f0,5\t(8 trials)\n" + " -o3 <=> -zc9 -zm8-9 -zs0-3 -f0,5\t(16 trials)\n" + " -o4 <=> -zc9 -zm8 -zs0-3 -f0-5\t(24 trials)\n" + " -o5 <=> -zc9 -zm8-9 -zs0-3 -f0-5\t(48 trials)\n" + " -o6 <=> -zc1-9 -zm8 -zs0-3 -f0-5\t(120 trials)\n" + " -o7 <=> -zc1-9 -zm8-9 -zs0-3 -f0-5\t(240 trials)\n" + " The libpng heuristics\n" + " -o1 <=> -zc9 -zm8 -zs0 -f0\t\t(if PLTE is present)\n" + " -o1 <=> -zc9 -zm8 -zs1 -f5\t\t(if PLTE is not present)\n" + " The most exhaustive search (not generally recommended)\n" + " [no preset] -zc1-9 -zm1-9 -zs0-3 -f0-5\t(1080 trials)\n" "Examples:\n" " optipng file.png\t\t\t\t(default speed)\n" " optipng -o5 file.png\t\t\t(moderately slow)\n" @@ -151,1676 +145,152 @@ " optipng -i1 -o7 -v -full -sim experiment.png -log experiment.log\n"; -/** Program tables, limits and presets **/ -#define OPTIM_LEVEL_MIN 0 -#define OPTIM_LEVEL_MAX 7 -#define OPTIM_LEVEL_DEFAULT 2 - -/* "-" <=> "MIN-MAX" */ - -#define COMPR_LEVEL_MIN 1 -#define COMPR_LEVEL_MAX 9 -static const char *compr_level_presets[OPTIM_LEVEL_MAX + 1] = - { "", "", "9", "9", "9", "9", "-", "-" }; -static const char *compr_level_mask = "1-9"; - -#define MEM_LEVEL_MIN 1 -#define MEM_LEVEL_MAX 9 -static const char *mem_level_presets[OPTIM_LEVEL_MAX + 1] = - { "", "", "8", "8-", "8", "8-", "8", "8-" }; -static const char *mem_level_mask = "1-9"; - -#define STRATEGY_MIN 0 -#define STRATEGY_MAX 3 -static const char *strategy_presets[OPTIM_LEVEL_MAX + 1] = - { "", "", "-", "-", "-", "-", "-", "-" }; -static const char *strategy_mask = "0-3"; - -#define FILTER_MIN 0 -#define FILTER_MAX 5 -static const char *filter_presets[OPTIM_LEVEL_MAX + 1] = - { "", "", "0,5", "0,5", "-", "-", "-", "-" }; -static const char *filter_mask = "0-5"; - - -/** Status flags **/ -#define INPUT_IS_PNG_FILE 0x0001 -#define INPUT_HAS_PNG_DATASTREAM 0x0002 -#define INPUT_HAS_PNG_SIGNATURE 0x0004 -#define INPUT_HAS_DIGITAL_SIGNATURE 0x0008 -#define INPUT_HAS_MULTIPLE_IMAGES 0x0010 -#define INPUT_HAS_NONCONFORMING_PNG 0x0020 -#define INPUT_HAS_JUNK 0x0040 -#define INPUT_HAS_ERRORS 0x0080 -#define OUTPUT_NEEDS_NEW_FILE 0x0100 -#define OUTPUT_NEEDS_NEW_IDAT 0x0200 -#define OUTPUT_RESERVED 0x7c00 -#define OUTPUT_HAS_ERRORS 0x8000U - - -/** The chunks handled by OptiPNG **/ -static const png_byte sig_PLTE[4] = { 0x50, 0x4c, 0x54, 0x45 }; -static const png_byte sig_tRNS[4] = { 0x74, 0x52, 0x4e, 0x53 }; -static const png_byte sig_IDAT[4] = { 0x49, 0x44, 0x41, 0x54 }; -static const png_byte sig_IEND[4] = { 0x49, 0x45, 0x4e, 0x44 }; -static const png_byte sig_bKGD[4] = { 0x62, 0x4b, 0x47, 0x44 }; -static const png_byte sig_hIST[4] = { 0x68, 0x49, 0x53, 0x54 }; -static const png_byte sig_sBIT[4] = { 0x73, 0x42, 0x49, 0x54 }; -static const png_byte sig_dSIG[4] = { 0x64, 0x53, 0x49, 0x47 }; -static const png_byte sig_fdAT[4] = { 0x66, 0x64, 0x41, 0x54 }; - - -/** User exception setup -- see cexcept.h for more info **/ -define_exception_type(const char *); -struct exception_context the_exception_context[1]; - - -/** OptiPNG info **/ -static struct opng_image_struct -{ - png_uint_32 width, height; - int bit_depth, color_type, compression_type, filter_type, interlace_type; - png_bytepp row_pointers; /* IDAT */ - png_colorp palette; /* PLTE */ - int num_palette; - png_color_16p background_ptr; - png_color_16 background; /* bKGD */ - png_uint_16p hist; /* hIST */ - png_color_8p sig_bit_ptr; - png_color_8 sig_bit; /* sBIT */ - png_bytep trans; /* tRNS */ - int num_trans; - png_color_16p trans_values_ptr; - png_color_16 trans_values; - png_unknown_chunkp unknowns; - int num_unknowns; -} opng_image; - -static struct opng_info_struct -{ - unsigned int status; - long in_datastream_offset; - unsigned long in_file_size, out_file_size; - png_uint_32 in_plte_trns_size, out_plte_trns_size; - png_uint_32 in_idat_size, out_idat_size; - png_uint_32 best_idat_size, max_idat_size; - png_uint_32 reductions; - bitset_t compr_level_set, mem_level_set, strategy_set, filter_set; - int best_compr_level, best_mem_level, best_strategy, best_filter; - int num_iterations; -} opng_info; - -static struct cmdline_struct -{ - unsigned int file_count; - int help, ver; - int optim_level; - int interlace; - int keep, quiet; - int nb, nc, np, nz; - int fix, force, full; - int preserve, simulate, snip; - bitset_t compr_level_set, mem_level_set, strategy_set, filter_set; - int window_bits; - char *out_name, *dir_name, *log_name; -} cmdline; - -static struct global_struct -{ - FILE *logfile; - unsigned int err_count, fix_count, snip_count; -} global; - - -/** Global variables, for quick access and bonus style points **/ -static png_structp read_ptr, write_ptr; -static png_infop read_info_ptr, write_info_ptr; -static png_infop read_end_info_ptr, write_end_info_ptr; - - -/** Internal debugging tool **/ -#define OPNG_ENSURE(cond, msg) \ - { if (!(cond)) opng_internal_error(msg); } /* strong check, no #ifdef's */ - - -/** Bitset utility (find minimum value) **/ -static int -opng_bitset_min(bitset_t set) -{ - unsigned int i; - - for (i = 0; i < BITSET_SIZE; ++i) - if (BITSET_GET(set, i)) - return i; - return -1; /* empty set */ -} - - -/** Message display w/ logging **/ -static void -opng_printf(const char *fmt, ...) -{ - va_list arg_ptr; - - if (!cmdline.quiet) - { - va_start(arg_ptr, fmt); - vfprintf(stdout, fmt, arg_ptr); - va_end(arg_ptr); - } - if (global.logfile != NULL) - { - va_start(arg_ptr, fmt); - vfprintf(global.logfile, fmt, arg_ptr); - va_end(arg_ptr); - fflush(global.logfile); - } -} - - -/** Ratio display w/ logging **/ -static void -opng_print_ratio(unsigned long num, unsigned long denom, int force_percent) -{ - /* (1) num/denom = 0/0 ==> print "??%" - * (2) num/denom = INFINITY ==> print "INFTY%" - * (3) 0 <= num/denom < 99.995% ==> use the percent format "99.99%" - * if force_percent: - * (4) 0.995 <= num/denom < INFINITY ==> use the percent format "999%" - * else: - * (5) 0.995 <= num/denom < 99.995 ==> use the factor format "9.99x" - * (6) 99.5 <= num/denom < INFINITY ==> use the factor format "999x" - * end if - */ - - unsigned long integral, adj_num, adj_denom; - - /* (1,2): num/denom = 0/0 or num/denom = INFINITY */ - if (denom == 0) - { - opng_printf(num == 0 ? "??%%" : "INFTY%%"); - return; - } - - /* (3): 0 <= num/denom < 99.995% */ - /* num/denom < 99.995% <==> denom/(denom-num) < 20000 */ - if (num < denom && denom / (denom - num) < 20000) - { - /* Round to nearest 0.01% and multiply the result by 10000%. */ - if (denom <= ULONG_MAX / 10000) - { - /* Use the best precision possible. */ - adj_num = num * 10000 + denom / 2; - adj_denom = denom * 100; - assert(adj_num / adj_denom < 100); - } - else - { - /* Reduce the precision to prevent overflow. */ - adj_num = num + denom / 20000; - if (denom <= ULONG_MAX - 5000) - denom = (denom + 5000) / 10000; - else - denom = ULONG_MAX / 10000; - assert(denom > 0); - adj_denom = denom * 100; - if (adj_num / adj_denom >= 100) - adj_num = denom * 9999; /* 100.00% --> 99.99% */ - } - opng_printf("%lu.%02lu%%", - adj_num / adj_denom, adj_num % adj_denom / denom); - return; - } - - /* Extract the integral out of the fraction for the remaining cases. */ - integral = num / denom; - num = num % denom; - /* Round to nearest 0.01 and multiply the result by 100. */ - /* num/denom < 0.995 <==> denom/(denom-num) < 200 */ - if (denom / (denom - num) >= 200) - { - /* Round up, use the best precision possible. */ - ++integral; - adj_num = 0; - adj_denom = denom; - } - else if (denom <= ULONG_MAX / 100) - { - /* Also use the best precision possible. */ - adj_num = num * 100 + denom / 2; - adj_denom = denom; - assert(adj_num / adj_denom < 100); - } - else - { - /* Reduce the precision to prevent overflow. */ - adj_num = num + denom / 200; - if (denom <= ULONG_MAX - 50) - adj_denom = (denom + 50) / 100; - else - adj_denom = ULONG_MAX / 100; - assert(adj_denom > 0); - if (adj_num / adj_denom >= 100) - adj_num = adj_denom * 99; /* N + 100% --> N + 99% */ - } - - /* (4): 0.995 <= num/denom < INFINITY */ - if (force_percent) - { - opng_printf("%lu%02lu%%", integral, adj_num / adj_denom); - return; - } - - /* (5): 0.995 <= num/denom < 99.995 */ - if (integral < 100) - { - opng_printf("%lu.%02lux", integral, adj_num / adj_denom); - return; - } - - /* (6): 99.5 <= num/denom < INFINITY */ - /* Round to nearest integral value, use the best precision possible. */ - if (num % denom >= denom / 2) - ++integral; - opng_printf("%lux", integral); -} +static enum { OP_NONE, OP_HELP, OP_RUN } operation; +static struct opng_options options; +static FILE *logfile; -/** Size change display w/ logging **/ +/** Error handling **/ static void -opng_print_size_difference(unsigned long init_size, unsigned long final_size, - int show_ratio) +error(const char *fmt, ...) { - unsigned long difference; - int sign; + va_list arg_ptr; - if (init_size <= final_size) - { - sign = 0; - difference = final_size - init_size; - } - else - { - sign = 1; - difference = init_size - final_size; - } - - if (difference == 0) - { - opng_printf("no change"); - return; - } - if (difference == 1) - opng_printf("1 byte"); - else - opng_printf("%lu bytes", difference); - if (show_ratio && init_size > 0) - { - opng_printf(" = "); - opng_print_ratio(difference, init_size, 0); - } - opng_printf(sign == 0 ? " increase" : " decrease"); + /* Print the error message to stderr and exit. */ + fprintf(stderr, "** Error: "); + va_start(arg_ptr, fmt); + vfprintf(stderr, fmt, arg_ptr); + va_end(arg_ptr); + fprintf(stderr, "\n"); + exit(EXIT_FAILURE); } -/** Image info display w/ logging **/ +/** Panic handling **/ static void -opng_print_image_info(int show_dim, int show_depth, int show_type, - int show_interlaced) +panic(const char *msg) { - static const int type_channels[8] = {1, 0, 3, 1, 2, 0, 4, 0}; - int channels, printed; - - printed = 0; - if (show_dim) - { - printed = 1; - opng_printf("%lux%lu pixels", - (unsigned long)opng_image.width, (unsigned long)opng_image.height); - } - if (show_depth) - { - if (printed) - opng_printf(", "); - printed = 1; - channels = type_channels[opng_image.color_type & 7]; - if (channels != 1) - opng_printf("%dx%d bits/pixel", channels, opng_image.bit_depth); - else if (opng_image.bit_depth != 1) - opng_printf("%d bits/pixel", opng_image.bit_depth); - else - opng_printf("1 bit/pixel"); - } - if (show_type) - { - if (printed) - opng_printf(", "); - printed = 1; - if (opng_image.color_type & PNG_COLOR_MASK_PALETTE) - { - if (opng_image.num_palette == 1) - opng_printf("1 color"); - else - opng_printf("%d colors", opng_image.num_palette); - if (opng_image.num_trans > 0) - opng_printf(" (%d transparent)", opng_image.num_trans); - opng_printf(" in palette"); - } - else - { - opng_printf((opng_image.color_type & PNG_COLOR_MASK_COLOR) ? - "RGB" : "grayscale"); - if (opng_image.color_type & PNG_COLOR_MASK_ALPHA) - opng_printf("+alpha"); - else if (opng_image.trans_values_ptr != NULL) - opng_printf("+transparency"); - } - } - if (show_interlaced) - { - if (opng_image.interlace_type != PNG_INTERLACE_NONE) - { - if (printed) - opng_printf(", "); - opng_printf("interlaced"); - } - /* Displaying "non-interlaced" is not really necessary for PNG images, - * and is almost meaningless for non-PNG images. - */ - } + /* Print the panic message to stderr and terminate abnormally. */ + fprintf(stderr, "\n** INTERNAL ERROR: %s\n", msg); + fprintf(stderr, "Please submit a defect report.\n"); + fprintf(stderr, PROGRAM_URI "\n\n"); + fflush(stderr); + osys_terminate(); } -/** Warning display **/ -static void -opng_print_warning(const char *msg) +/** String-to-integer conversion **/ +static long +str2long(const char *str, long *value) { - opng_printf("Warning: %s\n", msg); -} + char *endptr; + /* Extract the value from the string. */ + *value = strtol(str, &endptr, 10); + if (endptr == NULL || endptr == str) + { + errno = EINVAL; /* matching failure */ + return -1; + } -/** Warning handler **/ -static void -opng_warning(png_structp png_ptr, png_const_charp msg) -{ - /* Error in input or output file; processing may continue. */ - /* Recovery requires (re)compression of IDAT. */ - if (png_ptr == read_ptr) - opng_info.status |= (INPUT_HAS_ERRORS | OUTPUT_NEEDS_NEW_IDAT); - opng_print_warning(msg); -} + /* Check for the 'kilo' suffix. */ + if (*endptr == 'k' || *endptr == 'K') + { + ++endptr; + if (*value > LONG_MAX / 1024) + { + errno = ERANGE; /* overflow */ + *value = LONG_MAX; + } + else if (*value < LONG_MIN / 1024) + { + errno = ERANGE; /* overflow */ + *value = LONG_MIN; + } + else + *value *= 1024; + } + /* Check for trailing garbage. */ + while (isspace(*endptr)) + ++endptr; /* skip whitespace */ + if (*endptr != 0) + { + errno = EINVAL; /* garbage in input */ + return -1; + } -/** Error handler **/ -static void -opng_error(png_structp png_ptr, png_const_charp msg) -{ - /* Error in input or output file; processing must stop. */ - /* Recovery requires (re)compression of IDAT. */ - if (png_ptr == read_ptr) - opng_info.status |= (INPUT_HAS_ERRORS | OUTPUT_NEEDS_NEW_IDAT); - Throw msg; + return 0; } -/** Internal error handler -- this should never get executed! **/ +/** Command line error handling **/ static void -opng_internal_error(png_const_charp msg) -{ - fprintf(stderr, "\n[internal error] %s\n", msg); - fflush(stderr); - osys_terminate(); -} - - -/** Chunk categorization **/ -static int -opng_is_critical_chunk(png_bytep chunk_type) +error_option(const char *opt_desc, const char *opt_arg) { - if ((chunk_type[0] & 0x20) == 0) - return 1; - /* In strict terms of the PNG specification, tRNS is ancillary. - * However, the tRNS data defines the actual alpha samples, which - * is critical information. OptiPNG cannot operate losslessly - * unless it treats tRNS as a critical chunk. - * (Image animations constitute yet another area of applications - * in which transparency is critical, and cannot be ignored unless - * explicitly stated on a case-by-case basis.) - */ - if (memcmp(chunk_type, sig_tRNS, 4) == 0) - return 1; - return 0; + /* Issue an error regarding the incorrect use of the option, and exit. */ + if (opt_arg != NULL && opt_arg[0] != 0) + error("Invalid %s: %s", opt_desc, opt_arg); + else + error("Missing %s", opt_desc); } -/** Chunk filter **/ +/** Command line parsing **/ static int -opng_allow_chunk(png_bytep chunk_type) -{ - if (memcmp(chunk_type, sig_dSIG, 4) == 0) /* digital signature? */ - return 0; /* ... never allow */ - if ((chunk_type[1] & 0x20) != 0) /* non-compliant extension? */ - return cmdline.snip ? 0 : 1; /* ... allow if not snipping */ - return 1; /* allow everything else */ -} - - -/** Chunk handler **/ -static void -opng_handle_chunk(png_structp png_ptr, png_bytep chunk_type) -{ - png_byte chunk_name[5]; - int keep; - - if (opng_is_critical_chunk(chunk_type) - || memcmp(chunk_type, sig_bKGD, 4) == 0 - || memcmp(chunk_type, sig_hIST, 4) == 0 - || memcmp(chunk_type, sig_sBIT, 4) == 0) - return; /* let libpng handle it */ - - /* Everything else is handled as unknown by libpng. */ - keep = PNG_HANDLE_CHUNK_ALWAYS; - if (memcmp(chunk_type, sig_dSIG, 4) == 0) /* digital signature? */ - opng_info.status |= INPUT_HAS_DIGITAL_SIGNATURE; - else if ((chunk_type[1] & 0x20) != 0) /* non-compliant extension? */ - { - opng_info.status |= INPUT_HAS_NONCONFORMING_PNG; - if (memcmp(chunk_type, sig_fdAT, 4) == 0) - opng_info.status |= INPUT_HAS_MULTIPLE_IMAGES; - if (cmdline.snip) - { - opng_info.status |= INPUT_HAS_JUNK; - keep = PNG_HANDLE_CHUNK_NEVER; - } - } - memcpy(chunk_name, chunk_type, 4); - chunk_name[4] = 0; - if (!png_handle_as_unknown(png_ptr, chunk_name)) - png_set_keep_unknown_chunks(png_ptr, keep, chunk_name, 1); -} - - -/** Initialization for input handler **/ -static void -opng_init_read_data(void) -{ - /* The relevant fields inside opng_info are set to zero, - * and nothing else needs to be done at this moment. - */ -} - - -/** Initialization for output handler **/ -static void -opng_init_write_data(void) -{ - opng_info.out_file_size = 0; - opng_info.out_plte_trns_size = 0; - opng_info.out_idat_size = 0; -} - - -/** Input handler **/ -static void -opng_read_data(png_structp png_ptr, png_bytep data, png_size_t length) -{ - FILE *stream = (FILE *)png_get_io_ptr(png_ptr); - int io_state = pngx_get_io_state(png_ptr); - int io_state_loc = io_state & PNGX_IO_MASK_LOC; - png_bytep chunk_sig; - - /* Read the data. */ - if (fread(data, 1, length, stream) != length) - png_error(png_ptr, - "Can't read the input file or unexpected end of file"); - - if (opng_info.in_file_size == 0) /* first piece of PNG data */ - { - OPNG_ENSURE(length == 8, "PNG I/O must start with the first 8 bytes"); - opng_info.in_datastream_offset = ftell(stream) - 8; - opng_info.status |= INPUT_HAS_PNG_DATASTREAM; - if (io_state_loc == PNGX_IO_SIGNATURE) - opng_info.status |= INPUT_HAS_PNG_SIGNATURE; - if (opng_info.in_datastream_offset == 0) - opng_info.status |= INPUT_IS_PNG_FILE; - else if (opng_info.in_datastream_offset < 0) - png_error(png_ptr, - "Can't get the file-position indicator in input file"); - opng_info.in_file_size = (unsigned long)opng_info.in_datastream_offset; - } - opng_info.in_file_size += length; - - /* Handle the OptiPNG-specific events. */ - OPNG_ENSURE((io_state & PNGX_IO_READING) && (io_state_loc != 0), - "Incorrect info in png_ptr->io_state"); - if (io_state_loc == PNGX_IO_CHUNK_HDR) - { - /* In libpng 1.4.x and later, the chunk length and the chunk name - * are serialized in a single operation. This is also ensured by - * the opngio add-on for libpng 1.2.x and earlier. - */ - OPNG_ENSURE(length == 8, "Reading chunk header, expecting 8 bytes"); - chunk_sig = data + 4; - - if (memcmp(chunk_sig, sig_IDAT, 4) == 0) - { - if (opng_info.in_idat_size == 0) /* first IDAT */ - { - /* Allocate the rows here, bypassing libpng. - * This allows to initialize the contents and perform recovery - * in case of a premature EOF. - */ - OPNG_ENSURE(png_ptr == read_ptr, "Incorrect I/O handler setup"); - if (png_get_image_height(read_ptr, read_info_ptr) == 0) - return; /* premature IDAT; an error will be triggered later */ - OPNG_ENSURE(png_get_rows(read_ptr, read_info_ptr) == NULL, - "Image rows have been allocated too early"); - OPNG_ENSURE(pngx_malloc_rows(read_ptr, read_info_ptr, 0) != NULL, - "Failed allocation of image rows; check the safe allocator"); - png_data_freer(read_ptr, read_info_ptr, - PNG_USER_WILL_FREE_DATA, PNG_FREE_ROWS); - } - else - opng_info.status |= INPUT_HAS_JUNK; /* collapse multiple IDAT's */ - opng_info.in_idat_size += png_get_uint_32(data); - } - else if (memcmp(chunk_sig, sig_PLTE, 4) == 0 || - memcmp(chunk_sig, sig_tRNS, 4) == 0) - { - /* Add the chunk overhead (header + CRC) besides the data size. */ - opng_info.in_plte_trns_size += png_get_uint_32(data) + 12; - } - else - opng_handle_chunk(png_ptr, chunk_sig); - } -} - - -/** Output handler **/ -static void -opng_write_data(png_structp png_ptr, png_bytep data, png_size_t length) -{ - static int allow_crt_chunk; - static int crt_chunk_is_idat; - static long crt_idat_offset; - static png_uint_32 crt_idat_size, crt_idat_crc; - FILE *stream = (FILE *)png_get_io_ptr(png_ptr); - int io_state = pngx_get_io_state(png_ptr); - int io_state_loc = io_state & PNGX_IO_MASK_LOC; - png_bytep chunk_sig; - png_byte buf[4]; - - OPNG_ENSURE((io_state & PNGX_IO_WRITING) && (io_state_loc != 0), - "Incorrect info in png_ptr->io_state"); - - /* Handle the OptiPNG-specific events. */ - if (io_state_loc == PNGX_IO_CHUNK_HDR) - { - OPNG_ENSURE(length == 8, "Writing chunk header, expecting 8 bytes"); - chunk_sig = data + 4; - allow_crt_chunk = opng_allow_chunk(chunk_sig); - if (memcmp(chunk_sig, sig_IDAT, 4) == 0) - { - crt_chunk_is_idat = 1; - opng_info.out_idat_size += png_get_uint_32(data); - /* Abandon the trial if IDAT is bigger than the maximum allowed. */ - if (stream == NULL) - { - if (opng_info.out_idat_size > opng_info.max_idat_size) - Throw NULL; /* early interruption, not an error */ - } - } - else /* not IDAT */ - { - crt_chunk_is_idat = 0; - if (memcmp(chunk_sig, sig_PLTE, 4) == 0 || - memcmp(chunk_sig, sig_tRNS, 4) == 0) - { - /* Add the chunk overhead (header + CRC) besides the data size. */ - opng_info.out_plte_trns_size += png_get_uint_32(data) + 12; - } - } - } - - /* Exit early if this is only a trial. */ - if (stream == NULL) - return; - - /* Continue only if the current chunk type is allowed. */ - if (io_state_loc != PNGX_IO_SIGNATURE && !allow_crt_chunk) - return; - - /* Here comes an elaborate way of writing the data, in which - * multiple IDATs are collapsed in a single chunk. - * Normally, the user-supplied I/O routines are not so complicated. - */ - switch (io_state_loc) - { - case PNGX_IO_CHUNK_HDR: - { - if (crt_chunk_is_idat) - { - if (crt_idat_offset == 0) /* this is the first IDAT */ - { - crt_idat_offset = ftell(stream); - /* Try guessing the concatenated IDAT's length. */ - if (opng_info.best_idat_size > 0) - crt_idat_size = opng_info.best_idat_size; - else - crt_idat_size = length; - png_save_uint_32(data, crt_idat_size); - /* Start computing the concatenated IDAT's CRC. */ - crt_idat_crc = crc32(0, sig_IDAT, 4); - } - else /* this is not the first IDAT, so do not write its header */ - return; - } - else - { - if (crt_idat_offset != 0) - { - /* This is the header of the first chunk after IDAT. */ - /* IDAT must be finalized. */ - png_save_uint_32(buf, crt_idat_crc); - if (fwrite(buf, 1, 4, stream) != 4) - io_state = 0; /* error */ - opng_info.out_file_size += 4; - if (opng_info.out_idat_size != crt_idat_size) - { - /* The IDAT chunk size has not been correctly anticipated. - * It must be corrected in a non-streamable way. - */ - OPNG_ENSURE(opng_info.best_idat_size == 0, - "Incorrect calculation of IDAT size"); - OPNG_ENSURE(opng_info.out_idat_size <= PNG_UINT_31_MAX, - "Exceedingly large IDAT in output"); - png_save_uint_32(buf, opng_info.out_idat_size); - if (osys_fwrite_at(stream, crt_idat_offset, SEEK_SET, - buf, 4) != 4) - io_state = 0; /* error */ - } - if (io_state == 0) - png_error(png_ptr, "Can't finalize IDAT"); - crt_idat_offset = 0; - } - } - break; - } - case PNGX_IO_CHUNK_DATA: - { - if (crt_chunk_is_idat) - crt_idat_crc = crc32(crt_idat_crc, data, length); - break; - } - case PNGX_IO_CHUNK_CRC: - { - if (crt_chunk_is_idat) - return; /* defer writing until the first non-IDAT occurs */ - break; - } - } - - /* Write the data. */ - if (fwrite(data, 1, length, stream) != length) - png_error(png_ptr, "Can't write the output file"); - opng_info.out_file_size += length; -} - - -/** Image info initialization **/ -static void -opng_clear_image_info(void) -{ - png_debug(0, "Clearing opng_image"); - memset(&opng_image, 0, sizeof(opng_image)); -} - - -/** Image info transfer **/ -static void -opng_load_image_info(png_structp png_ptr, png_infop info_ptr, - png_infop end_info_ptr, int load_metadata) -{ - png_debug(0, "Loading opng_image from info struct\n"); - memset(&opng_image, 0, sizeof(opng_image)); - - png_get_IHDR(png_ptr, info_ptr, - &opng_image.width, &opng_image.height, &opng_image.bit_depth, - &opng_image.color_type, &opng_image.interlace_type, - &opng_image.compression_type, &opng_image.filter_type); - opng_image.row_pointers = png_get_rows(png_ptr, info_ptr); - png_get_PLTE(png_ptr, info_ptr, - &opng_image.palette, &opng_image.num_palette); - /* Transparency is not considered metadata, although tRNS is ancillary. - * See the comment in opng_is_critical_chunk() above. - */ - if (png_get_tRNS(png_ptr, info_ptr, - &opng_image.trans, &opng_image.num_trans, - &opng_image.trans_values_ptr)) - { - /* Double copying (pointer + value) is necessary here - * due to an inconsistency in the libpng design. - */ - if (opng_image.trans_values_ptr != NULL) - { - opng_image.trans_values = *opng_image.trans_values_ptr; - opng_image.trans_values_ptr = &opng_image.trans_values; - } - } - - if (!load_metadata) - return; - - if (png_get_bKGD(png_ptr, info_ptr, &opng_image.background_ptr)) - { - /* Same problem as in tRNS. */ - opng_image.background = *opng_image.background_ptr; - opng_image.background_ptr = &opng_image.background; - } - png_get_hIST(png_ptr, info_ptr, &opng_image.hist); - if (png_get_sBIT(png_ptr, info_ptr, &opng_image.sig_bit_ptr)) - { - /* Same problem as in tRNS. */ - opng_image.sig_bit = *opng_image.sig_bit_ptr; - opng_image.sig_bit_ptr = &opng_image.sig_bit; - } - opng_image.num_unknowns = - png_get_unknown_chunks(png_ptr, info_ptr, &opng_image.unknowns); - - if (end_info_ptr == NULL) /* dummy, keep compilers happy */ - return; -} - - -/** Image info transfer **/ -static void -opng_store_image_info(png_structp png_ptr, png_infop info_ptr, - png_infop end_info_ptr, int store_metadata) -{ - png_debug(0, "Storing opng_image to info struct\n"); - OPNG_ENSURE(opng_image.row_pointers != NULL, "No info in opng_image"); - - png_set_IHDR(png_ptr, info_ptr, - opng_image.width, opng_image.height, opng_image.bit_depth, - opng_image.color_type, opng_image.interlace_type, - opng_image.compression_type, opng_image.filter_type); - png_set_rows(write_ptr, write_info_ptr, opng_image.row_pointers); - if (opng_image.palette != NULL) - png_set_PLTE(png_ptr, info_ptr, - opng_image.palette, opng_image.num_palette); - /* Transparency is not considered metadata, although tRNS is ancillary. - * See the comment in opng_is_critical_chunk() above. - */ - if (opng_image.trans != NULL || opng_image.trans_values_ptr != NULL) - png_set_tRNS(png_ptr, info_ptr, - opng_image.trans, opng_image.num_trans, - opng_image.trans_values_ptr); - - if (!store_metadata) - return; - - if (opng_image.background_ptr != NULL) - png_set_bKGD(png_ptr, info_ptr, opng_image.background_ptr); - if (opng_image.hist != NULL) - png_set_hIST(png_ptr, info_ptr, opng_image.hist); - if (opng_image.sig_bit_ptr != NULL) - png_set_sBIT(png_ptr, info_ptr, opng_image.sig_bit_ptr); - if (opng_image.num_unknowns != 0) - { - int i; - png_set_unknown_chunks(png_ptr, info_ptr, - opng_image.unknowns, opng_image.num_unknowns); - /* Is this really necessary? Should it not be implemented in libpng? */ - for (i = 0; i < opng_image.num_unknowns; ++i) - png_set_unknown_chunk_location(png_ptr, info_ptr, - i, opng_image.unknowns[i].location); - } - - if (end_info_ptr == NULL) /* dummy, keep compilers happy */ - return; -} - - -/** Image info destruction **/ -static void -opng_destroy_image_info(void) -{ - png_uint_32 i; - int j; - - png_debug(0, "Destroying opng_image\n"); - if (opng_image.row_pointers == NULL) - return; /* nothing to clean up */ - - for (i = 0; i < opng_image.height; ++i) - osys_free(opng_image.row_pointers[i]); - osys_free(opng_image.row_pointers); - osys_free(opng_image.palette); - osys_free(opng_image.trans); - osys_free(opng_image.hist); - for (j = 0; j < opng_image.num_unknowns; ++j) - osys_free(opng_image.unknowns[j].data); - osys_free(opng_image.unknowns); - /* DO NOT deallocate background_ptr, sig_bit_ptr, trans_values_ptr. - * See the comments regarding double copying inside opng_load_image_info(). - */ - - /* Clear the space here and do not worry about double-deallocation issues - * that might arise later on. - */ - memset(&opng_image, 0, sizeof(opng_image)); -} - - -/** Image file reading **/ -static void -opng_read_file(FILE *infile) -{ - char fmt_name[16]; - int num_img; - png_uint_32 reductions; - const char * volatile err_msg; /* volatile is required by cexcept */ - - png_debug(0, "Reading opng_image\n"); - assert(infile != NULL); - - Try - { - read_info_ptr = read_end_info_ptr = NULL; - read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, - NULL, opng_error, opng_warning); - if (read_ptr != NULL) - { - read_info_ptr = png_create_info_struct(read_ptr); - if (read_info_ptr != NULL) - read_end_info_ptr = png_create_info_struct(read_ptr); - } - if (read_end_info_ptr == NULL) /* something went wrong on the way */ - Throw "Out of memory"; - - png_set_keep_unknown_chunks(read_ptr, PNG_HANDLE_CHUNK_ALWAYS, NULL, 0); - - png_debug(0, "Reading input stream\n"); - opng_init_read_data(); - pngx_set_read_fn(read_ptr, infile, opng_read_data); - fmt_name[0] = '\0'; - num_img = pngx_read_image(read_ptr, read_info_ptr, - fmt_name, sizeof(fmt_name), NULL, 0); - if (num_img > 1) - opng_info.status |= INPUT_HAS_MULTIPLE_IMAGES; - if ((opng_info.status & INPUT_IS_PNG_FILE) && - (opng_info.status & INPUT_HAS_MULTIPLE_IMAGES)) - { - /* pngxtern can't distinguish between APNG and proper PNG. */ - strcpy(fmt_name, (opng_info.status & INPUT_HAS_PNG_SIGNATURE) ? - "APNG" : "APNG datastream"); - } - OPNG_ENSURE(num_img >= 0, "Format name buffer too small for pngxtern"); - OPNG_ENSURE(fmt_name[0] != 0, "No format name from pngxtern"); - - if (opng_info.in_file_size == 0) - { - if (fseek(infile, 0, SEEK_END) == 0) - { - opng_info.in_file_size = (unsigned long)ftell(infile); - if (opng_info.in_file_size > LONG_MAX) - opng_info.in_file_size = 0; - } - if (opng_info.in_file_size == 0) - opng_print_warning("Unable to get the correct file size"); - } - - err_msg = NULL; /* everything is ok */ - } - Catch (err_msg) - { - /* If the critical info has been loaded, treat all errors as warnings. - * This enables a more advanced data recovery. - */ - if (opng_validate_image(read_ptr, read_info_ptr)) - { - png_warning(read_ptr, err_msg); - err_msg = NULL; - } - } - - Try - { - if (err_msg != NULL) - Throw err_msg; - - /* Display format and image information. */ - if (strcmp(fmt_name, "PNG") != 0) - { - opng_printf("Importing %s", fmt_name); - if (opng_info.status & INPUT_HAS_MULTIPLE_IMAGES) - { - if (!(opng_info.status & INPUT_IS_PNG_FILE)) - opng_printf(" (multi-image or animation)"); - if (cmdline.snip) - opng_printf("; snipping..."); - } - opng_printf("\n"); - } - opng_load_image_info(read_ptr, read_info_ptr, read_end_info_ptr, 1); - opng_print_image_info(1, 1, 1, 1); - opng_printf("\n"); - - /* Choose the applicable image reductions. */ - reductions = OPNG_REDUCE_ALL; - if (cmdline.nb) - reductions &= ~OPNG_REDUCE_BIT_DEPTH; - if (cmdline.nc) - reductions &= ~OPNG_REDUCE_COLOR_TYPE; - if (cmdline.np) - reductions &= ~OPNG_REDUCE_PALETTE_ALL; - if (opng_info.status & INPUT_HAS_DIGITAL_SIGNATURE) - { - /* Do not reduce signed files. */ - reductions = OPNG_REDUCE_NONE; - } - if ((opng_info.status & INPUT_IS_PNG_FILE) && - (opng_info.status & INPUT_HAS_MULTIPLE_IMAGES) && - (reductions != OPNG_REDUCE_NONE) && !cmdline.snip) - { - opng_printf( - "Can't reliably reduce APNG file; disabling reductions.\n" - "(Rerun " PROGRAM_NAME " with the -snip option " - "to convert APNG to optimized PNG.)\n"); - reductions = OPNG_REDUCE_NONE; - } - - /* Try to reduce the image. */ - opng_info.reductions = - opng_reduce_image(read_ptr, read_info_ptr, reductions); - - /* If the image is reduced, enforce full compression. */ - if (opng_info.reductions != OPNG_REDUCE_NONE) - { - opng_load_image_info(read_ptr, read_info_ptr, read_end_info_ptr, 1); - opng_printf("Reducing image to "); - opng_print_image_info(0, 1, 1, 0); - opng_printf("\n"); - } - - /* Change the interlace type if required. */ - if (cmdline.interlace >= 0 && - opng_image.interlace_type != cmdline.interlace) - { - opng_image.interlace_type = cmdline.interlace; - /* A change in interlacing requires IDAT recompression. */ - opng_info.status |= OUTPUT_NEEDS_NEW_IDAT; - } - } - Catch (err_msg) - { - /* Do the cleanup, then rethrow the exception. */ - png_data_freer(read_ptr, read_info_ptr, - PNG_DESTROY_WILL_FREE_DATA, PNG_FREE_ALL); - png_data_freer(read_ptr, read_end_info_ptr, - PNG_DESTROY_WILL_FREE_DATA, PNG_FREE_ALL); - png_destroy_read_struct(&read_ptr, &read_info_ptr, - &read_end_info_ptr); - Throw err_msg; - } - - png_debug(0, "Destroying data structs\n"); - /* Leave the data for upcoming processing. */ - png_data_freer(read_ptr, read_info_ptr, PNG_USER_WILL_FREE_DATA, - PNG_FREE_ALL); - png_data_freer(read_ptr, read_end_info_ptr, PNG_USER_WILL_FREE_DATA, - PNG_FREE_ALL); - png_destroy_read_struct(&read_ptr, &read_info_ptr, &read_end_info_ptr); -} - - -/** PNG file writing **/ -/** If the output file is NULL, PNG encoding is still done, - but no file is written. **/ -static void -opng_write_file(FILE *outfile, - int compression_level, int memory_level, - int compression_strategy, int filter) -{ - const char * volatile err_msg; /* volatile is required by cexcept */ - - static int filter_table[FILTER_MAX + 1] = - { - PNG_FILTER_NONE, PNG_FILTER_SUB, PNG_FILTER_UP, - PNG_FILTER_AVG, PNG_FILTER_PAETH, PNG_ALL_FILTERS - }; - - png_debug(0, "Encoding opng_image\n"); - OPNG_ENSURE( - compression_level >= COMPR_LEVEL_MIN && - compression_level <= COMPR_LEVEL_MAX && - memory_level >= MEM_LEVEL_MIN && - memory_level <= MEM_LEVEL_MAX && - compression_strategy >= STRATEGY_MIN && - compression_strategy <= STRATEGY_MAX && - filter >= FILTER_MIN && - filter <= FILTER_MAX, - "Invalid encoding parameters"); - - Try - { - write_info_ptr = write_end_info_ptr = NULL; - write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, - NULL, opng_error, opng_warning); - if (write_ptr != NULL) - { - write_info_ptr = png_create_info_struct(write_ptr); - if (write_info_ptr != NULL) - write_end_info_ptr = png_create_info_struct(write_ptr); - } - if (write_end_info_ptr == NULL) /* something went wrong on the way */ - Throw "Out of memory"; - - png_set_compression_level(write_ptr, compression_level); - png_set_compression_mem_level(write_ptr, memory_level); - png_set_compression_strategy(write_ptr, compression_strategy); - png_set_filter(write_ptr, PNG_FILTER_TYPE_BASE, filter_table[filter]); - if (compression_strategy != Z_HUFFMAN_ONLY && - compression_strategy != Z_RLE) - { - if (cmdline.window_bits > 0) - png_set_compression_window_bits(write_ptr, cmdline.window_bits); - } - else - { -#ifdef WBITS_8_OK - png_set_compression_window_bits(write_ptr, 8); -#else - png_set_compression_window_bits(write_ptr, 9); -#endif - } - png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, NULL, 0); - opng_store_image_info(write_ptr, write_info_ptr, write_end_info_ptr, - (outfile != NULL ? 1 : 0)); - - png_debug(0, "Writing PNG stream\n"); - opng_init_write_data(); - pngx_set_write_fn(write_ptr, outfile, opng_write_data, NULL); - png_write_png(write_ptr, write_info_ptr, 0, NULL); - - err_msg = NULL; /* everything is ok */ - } - Catch (err_msg) - { - /* Set IDAT size to invalid. */ - opng_info.out_idat_size = PNG_UINT_31_MAX + 1; - } - - png_debug(0, "Destroying data structs\n"); - png_destroy_info_struct(write_ptr, &write_end_info_ptr); - png_destroy_write_struct(&write_ptr, &write_info_ptr); - - if (err_msg != NULL) - Throw err_msg; -} - - -/** PNG file copying **/ -static void -opng_copy_file(FILE *infile, FILE *outfile) -{ - volatile png_bytep buf; /* volatile is required by cexcept */ - const png_uint_32 buf_size_incr = 0x1000; - png_uint_32 buf_size, length; - png_byte chunk_hdr[8]; - const char * volatile err_msg; - - png_debug(0, "Copying PNG stream\n"); - assert(infile != NULL && outfile != NULL); - - write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, - NULL, opng_error, opng_warning); - if (write_ptr == NULL) - Throw "Out of memory"; - opng_init_write_data(); - pngx_set_write_fn(write_ptr, outfile, opng_write_data, NULL); - - Try - { - buf = NULL; - buf_size = 0; - - /* Write the signature in the output file. */ - pngx_write_sig(write_ptr); - - /* Copy all chunks until IEND. */ - /* Error checking is done only at a very basic level. */ - do - { - if (fread(chunk_hdr, 8, 1, infile) != 1) /* length + name */ - Throw "Read error"; - length = png_get_uint_32(chunk_hdr); - if (length > PNG_UINT_31_MAX) - { - if (buf == NULL && length == 0x89504e47) /* "\x89PNG" */ - continue; /* skip the signature */ - Throw "Data error"; - } - if (length + 4 > buf_size) - { - png_free(write_ptr, buf); - buf_size = (((length + 4) + (buf_size_incr - 1)) - / buf_size_incr) * buf_size_incr; - buf = (png_bytep)png_malloc(write_ptr, buf_size); - /* Do not use realloc() here, it's slower. */ - } - if (fread(buf, length + 4, 1, infile) != 1) /* data + crc */ - Throw "Read error"; - png_write_chunk(write_ptr, chunk_hdr + 4, buf, length); - } while (memcmp(chunk_hdr + 4, sig_IEND, 4) != 0); - - err_msg = NULL; /* everything is ok */ - } - Catch (err_msg) - { - } - - png_free(write_ptr, buf); - png_destroy_write_struct(&write_ptr, NULL); - - if (err_msg != NULL) - Throw err_msg; -} - - -/** Iteration initialization **/ -static void -opng_init_iteration(int cmdline_set, const char *preset, const char *mask, - int *output_set) -{ - bitset_t set; - - *output_set = cmdline_set; - if (*output_set == BITSET_EMPTY || cmdline.optim_level >= 0) - { - OPNG_ENSURE(bitset_parse(preset, &set) == 0, "Invalid iteration preset"); - *output_set |= set; - } - OPNG_ENSURE(bitset_parse(mask, &set) == 0, "Invalid iteration mask"); - *output_set &= set; -} - - -/** Iteration initialization **/ -static void -opng_init_iterations(void) -{ - bitset_t compr_level_set, mem_level_set, strategy_set, filter_set; - int preset_index; - int t1, t2; - - /* Set the IDAT size limit. The trials that pass this limit will be - * abandoned, as there will be no need to wait until their completion. - * This limit may further decrease as iterations go on. - */ - if ((opng_info.status & OUTPUT_NEEDS_NEW_IDAT) || cmdline.full) - opng_info.max_idat_size = PNG_UINT_31_MAX; - else - { - OPNG_ENSURE(opng_info.in_idat_size > 0, "No IDAT in input"); - /* Add the input PLTE and tRNS sizes to the initial max IDAT size, - * to account for the changes that may occur during reduction. - * This incurs a negligible overhead on processing only: the final - * IDAT size will not be affected, because a precise check will be - * performed at the end, inside opng_finish_iterations(). - */ - opng_info.max_idat_size = - opng_info.in_idat_size + opng_info.in_plte_trns_size; - } - - /* Get preset_index from cmdline.optim_level, but leave the latter intact, - * because the effect of "optipng -o2 -z... -f..." is slightly different - * than the effect of "optipng -z... -f..." (without "-o"). - */ - preset_index = cmdline.optim_level; - if (preset_index < 0) - preset_index = OPTIM_LEVEL_DEFAULT; - else if (preset_index > OPTIM_LEVEL_MAX) - preset_index = OPTIM_LEVEL_MAX; - - /* Load the iteration sets from the implicit (preset) values, - * and also from the explicit (user-specified) values. - */ - opng_init_iteration(cmdline.compr_level_set, - compr_level_presets[preset_index], compr_level_mask, &compr_level_set); - opng_init_iteration(cmdline.mem_level_set, - mem_level_presets[preset_index], mem_level_mask, &mem_level_set); - opng_init_iteration(cmdline.strategy_set, - strategy_presets[preset_index], strategy_mask, &strategy_set); - opng_init_iteration(cmdline.filter_set, - filter_presets[preset_index], filter_mask, &filter_set); - - /* Replace the empty sets with the libpng's "best guess" heuristics. */ - if (compr_level_set == BITSET_EMPTY) - BITSET_SET(compr_level_set, Z_BEST_COMPRESSION); /* -zc9 */ - if (mem_level_set == BITSET_EMPTY) - BITSET_SET(mem_level_set, 8); - if (opng_image.bit_depth < 8 || opng_image.palette != NULL) - { - if (strategy_set == BITSET_EMPTY) - BITSET_SET(strategy_set, Z_DEFAULT_STRATEGY); /* -zs0 */ - if (filter_set == BITSET_EMPTY) - BITSET_SET(filter_set, 0); /* -f0 */ - } - else - { - if (strategy_set == BITSET_EMPTY) - BITSET_SET(strategy_set, Z_FILTERED); /* -zs1 */ - if (filter_set == BITSET_EMPTY) - BITSET_SET(filter_set, 5); /* -f0 */ - } - - /* Store the results into opng_info. */ - opng_info.compr_level_set = compr_level_set; - opng_info.mem_level_set = mem_level_set; - opng_info.strategy_set = strategy_set; - opng_info.filter_set = filter_set; - t1 = bitset_count(compr_level_set) * - bitset_count(strategy_set & ~((1 << Z_HUFFMAN_ONLY) | (1 << Z_RLE))); - t2 = bitset_count(strategy_set & ((1 << Z_HUFFMAN_ONLY) | (1 << Z_RLE))); - opng_info.num_iterations = (t1 + t2) * - bitset_count(mem_level_set) * bitset_count(filter_set); - - if (opng_info.num_iterations <= 0) - Throw "Invalid iteration parameters (-zc, -zm, -zs, -f)"; -} - - -/** Iteration **/ -static void -opng_iterate(void) -{ - bitset_t compr_level_set, mem_level_set, strategy_set, filter_set; - int compr_level, mem_level, strategy, filter; - int counter; - - OPNG_ENSURE(opng_info.num_iterations > 0, "Iterations not initialized"); - if ((opng_info.num_iterations == 1) && - (opng_info.status & OUTPUT_NEEDS_NEW_FILE) && - (opng_info.status & OUTPUT_NEEDS_NEW_IDAT)) - { - /* If there is one single trial, it is unnecessary to run it. */ - opng_info.best_idat_size = 0; - opng_info.best_compr_level = opng_bitset_min(opng_info.compr_level_set); - opng_info.best_mem_level = opng_bitset_min(opng_info.mem_level_set); - opng_info.best_strategy = opng_bitset_min(opng_info.strategy_set); - opng_info.best_filter = opng_bitset_min(opng_info.filter_set); - return; - } - - /* Prepare for the big iteration. */ - compr_level_set = opng_info.compr_level_set; - mem_level_set = opng_info.mem_level_set; - strategy_set = opng_info.strategy_set; - filter_set = opng_info.filter_set; - opng_info.best_idat_size = PNG_UINT_31_MAX + 1; - opng_info.best_compr_level = -1; - opng_info.best_mem_level = -1; - opng_info.best_strategy = -1; - opng_info.best_filter = -1; - - /* Iterate through the "hyper-rectangle" (zc, zm, zs, f). */ - opng_printf("Trying:\n"); - counter = 0; - for (filter = FILTER_MIN; filter <= FILTER_MAX; ++filter) - if (BITSET_GET(filter_set, filter)) - for (strategy = STRATEGY_MIN; strategy <= STRATEGY_MAX; ++strategy) - if (BITSET_GET(strategy_set, strategy)) - { - /* The compression level has no significance under - Z_HUFFMAN_ONLY or Z_RLE. */ - bitset_t saved_level_set = compr_level_set; - if (strategy == Z_HUFFMAN_ONLY) - { - compr_level_set = BITSET_EMPTY; - BITSET_SET(compr_level_set, 1); - } - else if (strategy == Z_RLE) - { - compr_level_set = BITSET_EMPTY; - BITSET_SET(compr_level_set, 9); /* use deflate_slow */ - } - for (compr_level = COMPR_LEVEL_MAX; - compr_level >= COMPR_LEVEL_MIN; --compr_level) - if (BITSET_GET(compr_level_set, compr_level)) - { - for (mem_level = MEM_LEVEL_MAX; - mem_level >= MEM_LEVEL_MIN; --mem_level) - if (BITSET_GET(mem_level_set, mem_level)) - { - ++counter; - opng_printf( - " zc = %d zm = %d zs = %d f = %d\t\t", - compr_level, mem_level, strategy, filter); - opng_write_file(NULL, - compr_level, mem_level, strategy, filter); - if (opng_info.out_idat_size > PNG_UINT_31_MAX) - { - opng_printf("IDAT too big\n"); - continue; - } - opng_printf("IDAT size = %lu\n", - (unsigned long)opng_info.out_idat_size); - if (opng_info.best_idat_size < opng_info.out_idat_size) - continue; - if (opng_info.best_idat_size == opng_info.out_idat_size - && opng_info.best_strategy >= Z_HUFFMAN_ONLY) - continue; /* it's neither smaller nor faster */ - opng_info.best_compr_level = compr_level; - opng_info.best_mem_level = mem_level; - opng_info.best_strategy = strategy; - opng_info.best_filter = filter; - opng_info.best_idat_size = opng_info.out_idat_size; - if (!cmdline.full) - opng_info.max_idat_size = opng_info.out_idat_size; - } - } - compr_level_set = saved_level_set; - } - - OPNG_ENSURE(counter == opng_info.num_iterations, - "Inconsistent iteration counter"); -} - - -/** Iteration finalization **/ -static void -opng_finish_iterations(void) +scan_option(char *str, char opt_buf[], size_t opt_buf_size, char **opt_arg_ptr) { - if (opng_info.best_idat_size + opng_info.out_plte_trns_size - < opng_info.in_idat_size + opng_info.in_plte_trns_size) - opng_info.status |= OUTPUT_NEEDS_NEW_IDAT; - if (opng_info.status & OUTPUT_NEEDS_NEW_IDAT) - { - opng_printf("\nSelecting parameters:\n" - " zc = %d zm = %d zs = %d f = %d", - opng_info.best_compr_level, opng_info.best_mem_level, - opng_info.best_strategy, opng_info.best_filter); - if (opng_info.best_idat_size != 0) /* trials have been run */ - opng_printf("\t\tIDAT size = %lu", - (unsigned long)opng_info.best_idat_size); - opng_printf("\n"); - } -} + char *ptr; + unsigned int opt_len; - -/** Image file optimization **/ -static void -opng_optimize(const char *infile_name) -{ - static FILE *infile, *outfile; /* static or volatile is required */ - static const char *outfile_name, *bakfile_name; /* by cexcept */ - static int new_outfile; - char name_buf[FILENAME_MAX], tmp_buf[FILENAME_MAX]; - const char * volatile err_msg; - - png_debug1(0, "Optimizing file: %s\n", infile_name); - memset(&opng_info, 0, sizeof(opng_info)); - if (cmdline.force) - opng_info.status |= OUTPUT_NEEDS_NEW_IDAT; - - err_msg = NULL; /* prepare for error handling */ - - if ((infile = fopen(infile_name, "rb")) == NULL) - Throw "Can't open the input file"; - Try - { - opng_read_file(infile); - } - Catch (err_msg) - { - /* assert(err_msg != NULL); */ - } - fclose(infile); /* finally */ - if (err_msg != NULL) - Throw err_msg; /* rethrow */ - - /* Check the digital signature flag. */ - if (opng_info.status & INPUT_HAS_DIGITAL_SIGNATURE) - { - opng_printf("Digital signature found in input."); - if (cmdline.force) - { - opng_printf(" Erasing...\n"); - opng_info.status |= OUTPUT_NEEDS_NEW_FILE; - } - else - { - opng_printf(" Rerun " PROGRAM_NAME " with the -force option.\n"); - Throw "Can't optimize digitally-signed files"; - } - } - - /* Check the multi-image flag. */ - if (opng_info.status & INPUT_HAS_MULTIPLE_IMAGES) - { - if (cmdline.snip) - ++global.snip_count; - else if (!(opng_info.status & INPUT_IS_PNG_FILE)) - { - opng_printf("Conversion to PNG requires snipping. " - "Rerun " PROGRAM_NAME " with the -snip option.\n"); - Throw "Incompatible input format"; - } - } - if ((opng_info.status & INPUT_HAS_NONCONFORMING_PNG) && cmdline.snip) - opng_info.status |= OUTPUT_NEEDS_NEW_FILE; - - /* Check the junk flag. */ - if (opng_info.status & INPUT_HAS_JUNK) - opng_info.status |= OUTPUT_NEEDS_NEW_FILE; - - /* Check the error flag. */ - if (opng_info.status & INPUT_HAS_ERRORS) - { - opng_printf("Recoverable errors found in input."); - if (cmdline.fix) - { - opng_printf(" Fixing...\n"); - opng_info.status |= OUTPUT_NEEDS_NEW_FILE; - ++global.err_count; - ++global.fix_count; - } - else - { - opng_printf(" Rerun " PROGRAM_NAME " with the -fix option.\n"); - Throw "Previous error(s) not fixed"; - } - } - - /* Initialize the output file name. */ - outfile_name = NULL; - if (!(opng_info.status & INPUT_IS_PNG_FILE)) - { - if (osys_fname_chext(name_buf, sizeof(name_buf), infile_name, - ".png") == NULL) - Throw "Can't create the output file (name too long)"; - outfile_name = name_buf; - } - if (cmdline.out_name != NULL) - outfile_name = cmdline.out_name; /* override the old name */ - if (cmdline.dir_name != NULL) - { - const char *tmp_name; - if (outfile_name != NULL) - { - strcpy(tmp_buf, outfile_name); - tmp_name = tmp_buf; - } - else - tmp_name = infile_name; - if (osys_fname_chdir(name_buf, sizeof(name_buf), tmp_name, - cmdline.dir_name) == NULL) - Throw "Can't create the output file (name too long)"; - outfile_name = name_buf; - } - if (outfile_name == NULL) - { - outfile_name = infile_name; - new_outfile = 0; - } - else - new_outfile = (osys_fname_cmp(infile_name, outfile_name) != 0) ? 1 : 0; - - /* Initialize the backup file name. */ - bakfile_name = tmp_buf; - if (new_outfile) - { - if (osys_fname_mkbak(tmp_buf, sizeof(tmp_buf), outfile_name) == NULL) - bakfile_name = NULL; - } - else - { - if (osys_fname_mkbak(tmp_buf, sizeof(tmp_buf), infile_name) == NULL) - bakfile_name = NULL; - } - /* Check the name even in simulation mode, to ensure a uniform behavior. */ - if (bakfile_name == NULL) - Throw "Can't create backup file (name too long)"; - /* Check the backup file before engaging into lengthy trials. */ - if (!cmdline.simulate && osys_ftest(outfile_name, "e") == 0) - { - if (new_outfile && !cmdline.keep) - Throw "The output file exists, try backing it up (use -keep)"; - if (osys_ftest(outfile_name, "fw") != 0 || - osys_ftest(bakfile_name, "e") == 0) - Throw "Can't back up the existing output file"; - } - - /* Display the input IDAT/file sizes. */ - if (opng_info.status & INPUT_HAS_PNG_DATASTREAM) - opng_printf("Input IDAT size = %lu bytes\n", - (unsigned long)opng_info.in_idat_size); - else - opng_info.status |= OUTPUT_NEEDS_NEW_IDAT; - opng_printf("Input file size = %lu bytes\n", opng_info.in_file_size); - - if (cmdline.nz - && (opng_info.status & INPUT_HAS_PNG_DATASTREAM) - && (opng_info.status & OUTPUT_NEEDS_NEW_IDAT)) - opng_print_warning( - "IDAT recompression is necessary; ignoring the -nz option"); - - /* Find the best parameters and see if it's worth recompressing. */ - if (!cmdline.nz || (opng_info.status & OUTPUT_NEEDS_NEW_IDAT)) - { - opng_init_iterations(); - opng_iterate(); - opng_finish_iterations(); - } - if (opng_info.status & OUTPUT_NEEDS_NEW_IDAT) - opng_info.status |= OUTPUT_NEEDS_NEW_FILE; - if (!(opng_info.status & OUTPUT_NEEDS_NEW_FILE)) - { - opng_printf("\n%s is already optimized.\n", infile_name); - if (!new_outfile) - return; - } - - if (cmdline.simulate) - { - if (new_outfile) - opng_printf("\nSimulation mode: %s not created.\n", outfile_name); - else - opng_printf("\nSimulation mode: %s not changed.\n", infile_name); - return; - } - - /* Make room for the output file. */ - if (new_outfile) - { - opng_printf("\nOutput file: %s\n", outfile_name); - if (cmdline.dir_name != NULL) - osys_dir_make(cmdline.dir_name); - if (osys_ftest(outfile_name, "e") == 0) - if (rename(outfile_name, bakfile_name) != 0) - Throw "Can't back up the output file"; - } - else - { - if (rename(infile_name, bakfile_name) != 0) - Throw "Can't back up the input file"; - } - - outfile = fopen(outfile_name, "wb"); - Try - { - if (outfile == NULL) - Throw "Can't open the output file"; - if (opng_info.status & OUTPUT_NEEDS_NEW_IDAT) - { - /* Write a brand new PNG datastream to the output. */ - opng_write_file(outfile, - opng_info.best_compr_level, opng_info.best_mem_level, - opng_info.best_strategy, opng_info.best_filter); - } - else - { - /* Copy the input PNG datastream to the output. */ - infile = osys_fopen_at((new_outfile ? infile_name : bakfile_name), - "rb", opng_info.in_datastream_offset, SEEK_SET); - if (infile == NULL) - Throw "Can't reopen the input file"; - Try - { - opng_info.best_idat_size = opng_info.in_idat_size; - opng_copy_file(infile, outfile); - } - Catch (err_msg) - { - /* assert(err_msg != NULL); */ - } - fclose(infile); /* finally */ - if (err_msg != NULL) - Throw err_msg; /* rethrow */ - } - } - Catch (err_msg) - { - if (outfile != NULL) - fclose(outfile); - /* Restore the original input file and rethrow the exception. */ - if (remove(outfile_name) != 0 || - rename(bakfile_name, (new_outfile ? outfile_name : infile_name)) != 0) - opng_print_warning( - "The original file could not be recovered from the backup"); - Throw err_msg; /* rethrow */ - } - /* assert(err_msg == NULL); */ - fclose(outfile); - - /* Preserve file attributes (e.g. ownership, access rights, time stamps) - * on request, if possible. - */ - if (cmdline.preserve) - osys_fattr_copy(outfile_name, (new_outfile ? infile_name : bakfile_name)); - - /* Remove the backup file if it is not needed. */ - if (!new_outfile && !cmdline.keep) - { - if (remove(bakfile_name) != 0) - Throw "Can't remove the backup file"; - } - - /* Display the output IDAT/file sizes. */ - opng_printf("\nOutput IDAT size = %lu bytes", - (unsigned long)opng_info.out_idat_size); - if (opng_info.status & INPUT_HAS_PNG_DATASTREAM) - { - opng_printf(" ("); - opng_print_size_difference(opng_info.in_idat_size, - opng_info.out_idat_size, 0); - opng_printf(")"); - } - opng_printf("\nOutput file size = %lu bytes (", opng_info.out_file_size); - opng_print_size_difference(opng_info.in_file_size, - opng_info.out_file_size, 1); - opng_printf(")\n"); + /* Check if arg is an "-option". */ + if (str[0] != '-' || str[1] == 0) /* no "-option", or just "-" */ + return 0; + + /* Extract the normalized option, and possibly the option argument. */ + opt_len = 0; + ptr = str + 1; + while (*ptr == '-') /* "--option", "---option", etc. */ + ++ptr; + if (*ptr == 0) /* "--" */ + --ptr; + if (isalpha(*ptr)) /* "-option" */ + { + do + { + if (opt_buf_size > opt_len) /* truncate "-verylongoption" */ + opt_buf[opt_len] = (char)tolower(*ptr); + ++opt_len; + ++ptr; + } while (isalpha(*ptr) || (*ptr == '-')); /* "-option", "-option-x" */ + while (*(ptr - 1) == '-') /* put back trailing '-' in "-option-" */ + { + --opt_len; + --ptr; + } + } + else /* "--", "-@", etc. */ + { + if (opt_buf_size > 1) + opt_buf[0] = *ptr; + opt_len = 1; + ++ptr; + } + + /* Finalize the normalized option. */ + if (opt_buf_size > 0) + { + if (opt_len < opt_buf_size) + opt_buf[opt_len] = '\0'; + else + opt_buf[opt_buf_size - 1] = '\0'; + } + if (*ptr == '=') /* "-option=arg" */ + ++ptr; + else while (isspace(*ptr)) /* "-option arg" */ + ++ptr; + *opt_arg_ptr = (*ptr != 0) ? ptr : NULL; + return 1; } @@ -1828,309 +298,353 @@ static void parse_args(int argc, char *argv[]) { - char *arg, *dash_arg; - /* char */ int cmd; - int stop_switch, i; - bitset_t set, interlace_set, optim_level_set; - - /* Initialize. */ - memset(&cmdline, 0, sizeof(cmdline)); - cmdline.optim_level = cmdline.interlace = -1; - interlace_set = optim_level_set = BITSET_EMPTY; - - /* Parse the args. */ - stop_switch = 0; - for (i = 1; i < argc; ++i) - { - arg = dash_arg = argv[i]; - if (arg[0] != '-' || stop_switch) - { - ++cmdline.file_count; - continue; - } - - argv[i] = NULL; /* allow process_args() to skip it */ - do ++arg; /* multiple dashes are as good as one */ - while (arg[0] == '-'); - if (arg[0] == 0 && dash_arg[0] == '-') /* -- */ - { - stop_switch = 1; - continue; - } - - string_lower(arg); /* options are case-insensitive */ - cmd = 0; - - if (strcmp(arg, "?") == 0 || - string_prefix_min_cmp("help", arg, 1) == 0) - { - cmdline.help = 1; - } - else if (strcmp(arg, "v") == 0) - { - cmdline.ver = 1; - } - else if (string_prefix_min_cmp("keep", arg, 1) == 0) - { - cmdline.keep = 1; - } - else if (string_prefix_min_cmp("quiet", arg, 1) == 0) - { - cmdline.quiet = 1; - } - else if (string_prefix_min_cmp("fix", arg, 2) == 0) - { - cmdline.fix = 1; - } - else if (string_prefix_min_cmp("force", arg, 2) == 0) - { - cmdline.force = 1; - } - else if (string_prefix_min_cmp("full", arg, 2) == 0) - { - cmdline.full = 1; - } - else if (string_prefix_min_cmp("preserve", arg, 2) == 0) - { - cmdline.preserve = 1; - } - else if (string_prefix_min_cmp("simulate", arg, 2) == 0) - { - cmdline.simulate = 1; - } - else if (string_prefix_min_cmp("snip", arg, 2) == 0) - { - cmdline.snip = 1; - } - else if (string_prefix_min_cmp("out", arg, 2) == 0) - { - if (cmdline.out_name != NULL) - Throw "duplicate output file name"; - if (++i >= argc) - Throw "missing output file name"; - cmdline.out_name = argv[i]; - argv[i] = NULL; /* allow process_args() to skip it */ - } - else if (string_prefix_min_cmp("dir", arg, 2) == 0) - { - if (cmdline.dir_name != NULL) - Throw "duplicate output dir name"; - if (++i >= argc) - Throw "missing output dir name"; - cmdline.dir_name = argv[i]; - argv[i] = NULL; /* allow process_args() to skip it */ - } - else if (string_prefix_min_cmp("log", arg, 2) == 0) - { - if (cmdline.log_name != NULL) - Throw "duplicate log file name"; - if (++i >= argc) - Throw "missing log file name"; - cmdline.log_name = argv[i]; - argv[i] = NULL; /* allow process_args() to skip it */ - } - else if (strcmp(arg, "nb") == 0) - { - cmdline.nb = 1; - } - else if (strcmp(arg, "nc") == 0) - { - cmdline.nc = 1; - } - else if (strcmp(arg, "np") == 0) - { - cmdline.np = 1; - } - else if (strcmp(arg, "nz") == 0) - { - cmdline.nz = 1; - } - else /* -i, -o, -zX, -f, or unrecognized option */ - { - /* Parse the numeric or bitset parameters. */ - cmd = arg[0]; - if (cmd == 'z') - cmd = toupper((++arg)[0]); - if ((arg[1] < 'a' || arg[1] > 'z') && cmd != 0 && - strchr("fioCMSW", cmd)) - { - ++arg; - if (arg[0] == 0) - { - if (++i < argc) - { - arg = argv[i]; - argv[i] = NULL; /* allow process_args() to skip it */ - } - else - arg = "[NULL]"; /* trigger an error later */ - } - } - else /* unrecognized option */ - Throw dash_arg; - } - - /* The numeric/bitset parameter is now in arg. */ - switch (cmd) - { - case 0: - continue; - case 'f': /* -f: PNG filter */ - { - if (bitset_parse(arg, &set) != 0) - Throw /* invalid */ "filter(s)"; - cmdline.filter_set |= set; - break; - } - case 'i': /* -i: PNG interlace type */ - { - if (bitset_parse(arg, &set) != 0 || - sscanf(arg, "%d", &cmdline.interlace) < 1 || - (cmdline.interlace & ~1) != 0) - Throw /* invalid */ "interlace type"; - if (bitset_count(interlace_set |= set) != 1) - Throw "multiple interlace types are not permitted"; - break; - } - case 'o': /* -o: optimization level */ - { - if (bitset_parse(arg, &set) != 0 || - sscanf(arg, "%d", &cmdline.optim_level) < 1) - Throw /* invalid */ "optimization level"; - if (bitset_count(optim_level_set |= set) != 1) - Throw "multiple optimization levels are not permitted"; - break; - } -#if 0 /* not implemented */ - case 'b': /* -b: bit depth */ - { - /* cmdline.bit_depth ... */ - break; - } - case 'c': /* -c: color type */ - { - /* cmdline.color_type ... */ - break; - } + char opt[16]; + char *arg, *xopt; + unsigned int file_count; + int stop_switch, i; + int val; + long lval; + bitset_t set; + + /* Initialize. */ + memset(&options, 0, sizeof(options)); + options.optim_level = -1; + options.interlace = -1; + file_count = 0; + + /* Iterate over args. */ + stop_switch = 0; + for (i = 1; i < argc; ++i) + { + arg = argv[i]; + if (stop_switch || scan_option(arg, opt, sizeof(opt), &xopt) < 1) + { + ++file_count; + continue; /* leave file names for process_files() */ + } + + /* Prevent process_files() from seeing this arg. */ + argv[i] = NULL; + + /* Check the simple options (without option arguments). */ + if (strcmp(opt, "-") == 0) /* "--" */ + { + stop_switch = 1; + } + else if (strcmp(opt, "?") == 0 || + string_prefix_min_cmp("help", opt, 1) == 0) + { + options.help = 1; + } + else if (strcmp(opt, "v") == 0) + { + options.ver = 1; + } + else if (string_prefix_min_cmp("keep", opt, 1) == 0) + { + options.keep = 1; + } + else if (string_prefix_min_cmp("quiet", opt, 1) == 0) + { + options.quiet = 1; + } + else if (string_prefix_min_cmp("fix", opt, 2) == 0) + { + options.fix = 1; + } + else if (string_prefix_min_cmp("force", opt, 2) == 0) + { + options.force = 1; + } + else if (string_prefix_min_cmp("full", opt, 2) == 0) + { + options.full = 1; + } + else if (string_prefix_min_cmp("preserve", opt, 2) == 0) + { + options.preserve = 1; + } + else if (string_prefix_min_cmp("simulate", opt, 2) == 0) + { + options.simulate = 1; + } + else if (string_prefix_min_cmp("snip", opt, 2) == 0) + { + options.snip = 1; + } + else if (strcmp(opt, "nb") == 0) + { + options.nb = 1; + } + else if (strcmp(opt, "nc") == 0) + { + options.nc = 1; + } + else if (strcmp(opt, "nm") == 0) + { +#if 0 + options.nm = 1; #endif - case 'C': /* -zc: zlib compression level */ - { - if (bitset_parse(arg, &set) != 0) - Throw /* invalid */ "compression level(s)"; - cmdline.compr_level_set |= set; - break; - } - case 'M': /* -zm: zlib memory level */ - { - if (bitset_parse(arg, &set) != 0) - Throw /* invalid */ "memory level(s)"; - cmdline.mem_level_set |= set; - break; - } - case 'S': /* -zs: zlib strategy */ - { - if (bitset_parse(arg, &set) != 0) - Throw /* invalid */ "strategy"; - cmdline.strategy_set |= set; - break; - } - case 'W': /* -zw: zlib window size */ - { - unsigned int wsize; - int wbits; - char wk; - int nscanf = sscanf(arg, "%u%c", &wsize, &wk); - if (nscanf == 0) - wsize = 0; - else if (nscanf == 2) + } + else if (strcmp(opt, "np") == 0) + { + options.np = 1; + } + else if (strcmp(opt, "nz") == 0) + { + options.nz = 1; + } + else /* possibly an option with an argument */ + { + if (xopt == NULL) { - if (tolower(wk) == 'k' && wsize <= 32) - wsize *= 1024; - else - wsize = 0; + if (++i < argc) + { + xopt = argv[i]; + /* Prevent process_files() from seeing this xopt. */ + argv[i] = NULL; + } + else + xopt = ""; } - for (wbits = 15; wbits >= 8; --wbits) - if ((1U << wbits) == wsize) - break; - if (wbits < 8) - Throw /* invalid */ "window size"; -#ifndef WBITS_8_OK - else if (wbits == 8) - wbits = 9; -#endif - if (cmdline.window_bits > 0 && cmdline.window_bits != wbits) - Throw "multiple window sizes are not permitted"; - else - cmdline.window_bits = wbits; - break; - } - default: /* never get here */ - { - /* If cmd is none of the above, it should be 0. */ - OPNG_ENSURE(cmd == 0, "Error in command-line parsing"); - } - } - } - - /* Finalize. */ - if (cmdline.out_name != NULL) - { - if (cmdline.file_count > 1) - Throw "-out requires a single input file"; - if (cmdline.dir_name != NULL) - Throw "-out and -dir are mutually exclusive"; - } - if (cmdline.optim_level == OPTIM_LEVEL_MIN) - cmdline.nz = 1; - if (cmdline.nz) - cmdline.nb = cmdline.nc = cmdline.np = 1; + } + + /* Check the options that have option arguments. */ + if (xopt == NULL) + { + /* Do nothing, an option without argument is already recognized. */ + } + else if (strcmp(opt, "o") == 0) + { + if (str2long(xopt, &lval) != 0 || lval < 0 || lval > 99) + error_option("optimization level", xopt); + val = (int)lval; + if (options.optim_level < 0) + options.optim_level = val; + else if (options.optim_level != val) + error("Multiple optimization levels are not permitted"); + } + else if (strcmp(opt, "i") == 0) + { + if (str2long(xopt, &lval) != 0 || lval < 0 || lval > 1) + error_option("interlace type", xopt); + val = (int)lval; + if (options.interlace < 0) + options.interlace = (int)val; + else if (options.interlace != (int)val) + error("Multiple interlace types are not permitted"); + } + else if (strcmp(opt, "b") == 0) + { + /* options.bit_depth = ... */ + error("Selection of bit depth is not implemented"); + } + else if (strcmp(opt, "c") == 0) + { + /* options.color_type = ... */ + error("Selection of color type is not implemented"); + } + else if (strcmp(opt, "f") == 0) + { + if (bitset_parse(xopt, &set) != 0) + error_option("filter(s)", xopt); + options.filter_set |= set; + } + else if (strcmp(opt, "zc") == 0) + { + if (bitset_parse(xopt, &set) != 0) + error_option("zlib compression level(s)", xopt); + options.compr_level_set |= set; + } + else if (strcmp(opt, "zm") == 0) + { + if (bitset_parse(xopt, &set) != 0) + error_option("zlib memory level(s)", xopt); + options.mem_level_set |= set; + } + else if (strcmp(opt, "zs") == 0) + { + if (bitset_parse(xopt, &set) != 0) + error_option("zlib compression strategy", xopt); + options.strategy_set |= set; + } + else if (strcmp(opt, "zw") == 0) + { + if (str2long(xopt, &lval) != 0) + lval = 0; + for (val = 15; val >= 8; --val) + if ((1L << val) == lval) + break; + if (val < 8) + error_option("zlib window size", xopt); + if (options.window_bits == 0) + options.window_bits = val; + else if (options.window_bits != val) + error("Multiple window sizes are not permitted"); + } + else if (string_prefix_min_cmp("out", opt, 2) == 0) + { + if (options.out_name != NULL) + error("Duplicate output file name"); + if (xopt[0] == 0) + error_option("output file name", xopt); + options.out_name = xopt; + } + else if (string_prefix_min_cmp("dir", opt, 2) == 0) + { + if (options.dir_name != NULL) + error("Duplicate output dir name"); + if (xopt[0] == 0) + error_option("output dir name", xopt); + options.dir_name = xopt; + } + else if (string_prefix_min_cmp("log", opt, 2) == 0) + { + if (options.log_name != NULL) + error("Duplicate log file name"); + if (xopt[0] == 0) + error_option("log file name", xopt); + options.log_name = xopt; + } + else if (string_prefix_min_cmp("jobs", opt, 2) == 0) + { + error("Parallel processing is not implemented"); + } + else + { + error("Unrecognized option: %s", arg); + } + } + + /* Finalize. */ + if (options.out_name != NULL) + { + if (file_count > 1) + error("-out requires one input file"); + if (options.dir_name != NULL) + error("-out and -dir are mutually exclusive"); + } + if (options.log_name != NULL) + { + if (string_suffix_case_cmp(options.log_name, ".log") != 0) + error("To prevent accidental data corruption," + " the log file name must end with \".log\""); + } + if (options.optim_level == 0) + options.nz = 1; + if (options.nz) + options.nb = options.nc = options.np = 1; + operation = (options.help || file_count == 0) ? OP_HELP : OP_RUN; +} + + +/** Initialization **/ +static void +app_init(void) +{ + if (options.log_name != NULL) + { + /* Open the log file, line-buffered. */ + if ((logfile = fopen(options.log_name, "a")) == NULL) + error("Can't open log file: %s\n", options.log_name); + setvbuf(logfile, NULL, _IOLBF, BUFSIZ); + } +} + + +/** Finalization **/ +static void +app_finish(void) +{ + if (logfile != NULL) + { + /* Close the log file. */ + fclose(logfile); + } +} + + +/** Application-defined printf callback **/ +static void +app_printf(const char *fmt, ...) +{ + va_list arg_ptr; + + /* Print w/ logging. */ + if (!options.quiet) + { + va_start(arg_ptr, fmt); + vfprintf(stdout, fmt, arg_ptr); + va_end(arg_ptr); + } + if (logfile != NULL) + { + /* "\r" --> reset line in console, new line in log */ + /* "[:blank:]*\r" --> wipe line in console, leave log intact */ + if (strcmp(fmt, "\r") == 0) + fmt = "\n"; + else if (fmt[0] != '\0' && fmt[strlen(fmt) - 1] == '\r') + return; + va_start(arg_ptr, fmt); + vfprintf(logfile, fmt, arg_ptr); + va_end(arg_ptr); + } +} + + +/** Application-defined flush callback **/ +static void +app_flush(void) +{ + if (!options.quiet) + fflush(stdout); + if (logfile != NULL) + fflush(logfile); +} + + +/** Application-defined progress-bar callback **/ +static void +app_progress(unsigned long num, unsigned long denom) +{ + /* A GUI application would normally update a progress bar. */ + /* We do nothing in this console program. */ + if (num && denom) + return; } -/** Command line processing **/ -static void -process_args(int argc, char *argv[]) +/** File list processing **/ +static int +process_files(int argc, char *argv[]) { - const char *err_msg; - volatile int i; /* no need to be volatile, but it keeps compilers happy */ + int result; + struct opng_ui ui; + int i; + + /* Initialize the optimization engine. */ + ui.printf_fn = app_printf; + ui.flush_fn = app_flush; + ui.progress_fn = app_progress; + ui.panic_fn = panic; + if (opng_initialize(&options, &ui) != 0) + panic("Can't initialize optimization engine"); + + /* Iterate over file names. */ + result = EXIT_SUCCESS; + for (i = 1; i < argc; ++i) + { + if (argv[i] == NULL || argv[i][0] == 0) + continue; /* this was an "-option" */ + if (opng_optimize(argv[i]) != 0) + result = EXIT_FAILURE; + } + + /* Finalize the optimization engine. */ + if (opng_finalize() != 0) + panic("Can't finalize optimization engine"); - for (i = 1; i < argc; ++i) - { - if (argv[i] == NULL || argv[i][0] == 0) - continue; - opng_clear_image_info(); - Try - { - opng_printf("** Processing: %s\n", argv[i]); - opng_optimize(argv[i]); - opng_printf("\n"); - } - Catch (err_msg) - { - opng_printf("\nError: %s\n\n", err_msg); - ++global.err_count; - } - opng_destroy_image_info(); - } - - if (cmdline.ver || global.snip_count > 0 || global.err_count > 0) - { - opng_printf("** Status report\n"); - opng_printf("%u file(s) have been processed.\n", cmdline.file_count); - if (global.snip_count > 0) - { - opng_printf("%u multi-image file(s) have been snipped.\n", - global.snip_count); - } - if (global.err_count > 0) - { - opng_printf("%u error(s) have been encountered.\n", - global.err_count); - if (global.fix_count > 0) - opng_printf("%u erroneous file(s) have been fixed.\n", - global.fix_count); - } - } + return result; } @@ -2138,68 +652,40 @@ int main(int argc, char *argv[]) { - const char *err_msg; - int result; - - memset(&global, 0, sizeof(global)); + int result; - Try - { - parse_args(argc, argv); - } - Catch (err_msg) - { - fprintf(stderr, "Invalid option: %s\n", err_msg); - return EXIT_FAILURE; - } - - if (cmdline.log_name != NULL) - { - if (string_suffix_case_cmp(cmdline.log_name, ".log") != 0) - { - fprintf(stderr, "To prevent accidental data corruption," - " the log file name must end with \".log\"\n"); - return EXIT_FAILURE; - } - if ((global.logfile = fopen(cmdline.log_name, "a")) == NULL) - { - fprintf(stderr, "Can't open log file: %s\n", cmdline.log_name); - return EXIT_FAILURE; - } - /* logfile is open, so use opng_printf() from now on. */ - } - - result = EXIT_SUCCESS; - - opng_printf(msg_intro); - if (cmdline.ver) - { - opng_printf(msg_license); - opng_printf("Compiled with libpng version %s and zlib version %s\n\n", - png_get_libpng_ver(NULL), zlibVersion()); - } - if (cmdline.help) - { - opng_printf(msg_help); - if (cmdline.file_count > 0) - { - opng_print_warning("No files processed"); - cmdline.file_count = 0; - result = EXIT_FAILURE; - } - } - else if (!cmdline.ver && cmdline.file_count == 0) - opng_printf(msg_short_help); - - if (cmdline.file_count > 0) - { - process_args(argc, argv); - if (global.err_count != global.fix_count) - result = EXIT_FAILURE; - } + /* Parse the command options. */ + parse_args(argc, argv); - if (global.logfile != NULL) - fclose(global.logfile); + /* Initialize the application. */ + app_init(); - return result; + /* Print the program/library version, copyright and licensing info. */ + app_printf("%s", msg_intro); + if (options.ver) + { + app_printf(msg_license); + app_printf("Compiled with libpng version %s and zlib version %s\n\n", + png_get_libpng_ver(NULL), zlibVersion()); + /* Print the help text only if explicitly requested. */ + if (operation == OP_HELP && !options.help) + operation = OP_NONE; + } + + /* Print the help text or run the application. */ + switch (operation) + { + case OP_RUN: + result = process_files(argc, argv); + break; + case OP_HELP: + app_printf("%s", options.help ? msg_help : msg_short_help); + /* Fall through. */ + default: + result = EXIT_SUCCESS; + } + + /* Finalize the application. */ + app_finish(); + return result; } diff -urN optipng-0.6.1/src/optipng.h optipng-0.6.2/src/optipng.h --- optipng-0.6.1/src/optipng.h 1969-12-31 21:00:00.000000000 -0300 +++ optipng-0.6.2/src/optipng.h 2008-07-29 15:01:00.000000000 -0300 @@ -0,0 +1,86 @@ +/** + ** optipng.h + ** OptiPNG programming interface. + ** + ** Copyright (C) 2001-2008 Cosmin Truta. + ** OptiPNG is open-source software, and is distributed under the same + ** licensing and warranty terms as libpng. + **/ + + +#ifndef OPTIPNG_H +#define OPTIPNG_H + +#include "cbitset.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * Program options (e.g. extracted from the command line) + */ +struct opng_options +{ + int help; + int ver; + int optim_level; + int interlace; + int keep, quiet; + int nb, nc, np, nz; + int fix; + int force; + int full; + int preserve; + int simulate; + int snip; + bitset_t compr_level_set; + bitset_t mem_level_set; + bitset_t strategy_set; + bitset_t filter_set; + int window_bits; + char *out_name; + char *dir_name; + char *log_name; +}; + + +/* + * Program UI callbacks + */ +struct opng_ui +{ + void (*printf_fn)(const char *fmt, ...); + void (*flush_fn)(void); + void (*progress_fn)(unsigned long num, unsigned long denom); + void (*panic_fn)(const char *msg); +}; + + +/* + * Engine initialization + */ +int opng_initialize(const struct opng_options *options, + const struct opng_ui *ui); + + +/* + * Engine execution + */ +int opng_optimize(const char *infile_name); + + +/* + * Engine finalization + */ +int opng_finalize(void); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + + +#endif /* OPTIPNG_H */