/* pngcp.c * * Copyright (c) 2016,2022,2024 John Cunningham Bowler * * This code is released under the libpng license. * For conditions of distribution and use, see the disclaimer * and license in png.h * * This is an example of copying a PNG without changes using the png_read_png * and png_write_png interfaces. A considerable number of options are provided * to manipulate the compression of the PNG data and other compressed chunks. * * For a more extensive example that uses the transforms see * contrib/libtests/pngimage.c in the libpng distribution. * * This code is not intended for installation in a release system; the command * line options are not documented and most of the behavior is intended for * testing libpng performance, both speed and compression. */ #include "pnglibconf.h" /* To find how libpng was configured. */ #ifdef PNG_PNGCP_TIMING_SUPPORTED /* WARNING: * * This test is here to allow POSIX.1b extensions to be used if enabled in * the compile; specifically the code requires_POSIX_C_SOURCE support of * 199309L or later to enable clock_gettime use. * * IF this causes problems THEN compile with a strict ANSI C compiler and let * this code turn on the POSIX features that it minimally requires. * * IF this does not work there is probably a bug in your ANSI C compiler or * your POSIX implementation. */ # define _POSIX_C_SOURCE 199309L #else /* No timing support required */ # define _POSIX_SOURCE 1 #endif #if defined(HAVE_CONFIG_H) && !defined(PNG_NO_CONFIG_H) # include #endif #include /* Define the following to use this test against your installed libpng, rather * than the one being built here: */ #ifdef PNG_FREESTANDING_TESTS # include #else # include "../../png.h" #endif #if PNG_LIBPNG_VER < 10700 /* READ_PNG and WRITE_PNG were not defined, so: */ # ifdef PNG_INFO_IMAGE_SUPPORTED # ifdef PNG_SEQUENTIAL_READ_SUPPORTED # define PNG_READ_PNG_SUPPORTED # endif /* SEQUENTIAL_READ */ # ifdef PNG_WRITE_SUPPORTED # define PNG_WRITE_PNG_SUPPORTED # endif /* WRITE */ # endif /* INFO_IMAGE */ #endif /* pre 1.7.0 */ #if (defined(PNG_READ_PNG_SUPPORTED)) && (defined(PNG_WRITE_PNG_SUPPORTED)) #include #include #include #include #include #include #include #include #include #ifndef PNG_SETJMP_SUPPORTED # include /* because png.h did *not* include this */ #endif #ifdef __cplusplus # define voidcast(type, value) static_cast(value) #else # define voidcast(type, value) (value) #endif /* __cplusplus */ /* 'CLOCK_PROCESS_CPUTIME_ID' is one of the clock timers for clock_gettime. It * need not be supported even when clock_gettime is available. It returns the * 'CPU' time the process has consumed. 'CPU' time is assumed to include time * when the CPU is actually blocked by a pending cache fill but not time * waiting for page faults. The attempt is to get a measure of the actual time * the implementation takes to read a PNG ignoring the potentially very large IO * overhead. */ #ifdef PNG_PNGCP_TIMING_SUPPORTED # include /* clock_gettime and associated definitions */ # ifndef CLOCK_PROCESS_CPUTIME_ID /* Prevent inclusion of the spurious code: */ # undef PNG_PNGCP_TIMING_SUPPORTED # endif #endif /* PNGCP_TIMING */ /* So if the timing feature has been activated: */ /* This structure is used to control the test of a single file. */ typedef enum { VERBOSE, /* switches on all messages */ INFORMATION, WARNINGS, /* switches on warnings */ LIBPNG_WARNING, APP_WARNING, ERRORS, /* just errors */ APP_FAIL, /* continuable error - no need to longjmp */ LIBPNG_ERROR, /* this and higher cause a longjmp */ LIBPNG_BUG, /* erroneous behavior in libpng */ APP_ERROR, /* such as out-of-memory in a callback */ QUIET, /* no normal messages */ USER_ERROR, /* such as file-not-found */ INTERNAL_ERROR } error_level; #define LEVEL_MASK 0xf /* where the level is in 'options' */ #define STRICT 0x010 /* Fail on warnings as well as errors */ #define LOG 0x020 /* Log pass/fail to stdout */ #define CONTINUE 0x040 /* Continue on APP_FAIL errors */ #define SIZES 0x080 /* Report input and output sizes */ #define SEARCH 0x100 /* Search IDAT compression options */ #define NOWRITE 0x200 /* Do not write an output file */ #ifdef PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED # define IGNORE_INDEX 0x400 /* Ignore out of range palette indices (BAD!) */ # ifdef PNG_GET_PALETTE_MAX_SUPPORTED # define FIX_INDEX 0x800 /* 'Fix' out of range palette indices (OK) */ # endif /* GET_PALETTE_MAX */ #endif /* CHECK_FOR_INVALID_INDEX */ #define OPTION 0x80000000 /* Used for handling options */ #define LIST 0x80000001 /* Used for handling options */ /* Result masks apply to the result bits in the 'results' field below; these * bits are simple 1U<operation = "internal error"; dp->filename = "command line"; dp->output_file = "no output file"; dp->options = WARNINGS; /* default to !verbose, !quiet */ dp->fp = NULL; dp->read_pp = NULL; dp->ip = NULL; dp->write_pp = NULL; dp->min_windowBits = -1; /* this is an OPTIND, so -1 won't match anything */ # if PNG_LIBPNG_VER < 10700 && defined PNG_TEXT_SUPPORTED dp->text_ptr = NULL; dp->num_text = 0; dp->text_stashed = 0; # endif /* pre 1.7 */ } static void display_clean_read(struct display *dp, int freeinfo) { if (dp->read_pp != NULL) png_destroy_read_struct(&dp->read_pp, freeinfo ? &dp->ip : NULL, NULL); if (dp->fp != NULL) { FILE *fp = dp->fp; dp->fp = NULL; (void)fclose(fp); } } static void display_clean_write(struct display *dp, int freeinfo) { if (dp->fp != NULL) { FILE *fp = dp->fp; dp->fp = NULL; (void)fclose(fp); } if (dp->write_pp != NULL) png_destroy_write_struct(&dp->write_pp, freeinfo ? &dp->ip : NULL); } static void display_clean(struct display *dp) { display_clean_read(dp, 1/*freeinfo*/); display_clean_write(dp, 1/*freeinfo*/); dp->output_file = NULL; # if PNG_LIBPNG_VER < 10700 && defined PNG_TEXT_SUPPORTED /* This is actually created and used by the write code, but only * once; it has to be retained for subsequent writes of the same file. */ if (dp->text_stashed) { dp->text_stashed = 0; dp->num_text = 0; free(dp->text_ptr); dp->text_ptr = NULL; } # endif /* pre 1.7 */ /* leave the filename for error detection */ dp->results = 0; /* reset for next time */ } static void display_destroy(struct display *dp) { /* Release any memory held in the display. */ display_clean(dp); } static struct display * get_dp(png_structp pp) /* The display pointer is always stored in the png_struct error pointer */ { struct display *dp = (struct display*)png_get_error_ptr(pp); if (dp == NULL) { fprintf(stderr, "pngcp: internal error (no display)\n"); exit(99); /* prevents a crash */ } return dp; } /* error handling */ #ifdef __GNUC__ # define VGATTR __attribute__((__format__ (__printf__,3,4))) /* Required to quiet GNUC warnings when the compiler sees a stdarg function * that calls one of the stdio v APIs. */ #else # define VGATTR #endif static void VGATTR display_log(struct display *dp, error_level level, const char *fmt, ...) /* 'level' is as above, fmt is a stdio style format string. This routine * does not return if level is above LIBPNG_WARNING */ { dp->results |= 1U << level; if (level > (error_level)(dp->options & LEVEL_MASK)) { const char *lp; va_list ap; switch (level) { case INFORMATION: lp = "information"; break; case LIBPNG_WARNING: lp = "warning(libpng)"; break; case APP_WARNING: lp = "warning(pngcp)"; break; case APP_FAIL: lp = "error(continuable)"; break; case LIBPNG_ERROR: lp = "error(libpng)"; break; case LIBPNG_BUG: lp = "bug(libpng)"; break; case APP_ERROR: lp = "error(pngcp)"; break; case USER_ERROR: lp = "error(user)"; break; case INTERNAL_ERROR: /* anything unexpected is an internal error: */ case VERBOSE: case WARNINGS: case ERRORS: case QUIET: default: lp = "bug(pngcp)"; break; } fprintf(stderr, "%s: %s: %s", dp->filename != NULL ? dp->filename : "", lp, dp->operation); fprintf(stderr, ": "); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fputc('\n', stderr); } /* else do not output any message */ /* Errors cause this routine to exit to the fail code */ if (level > APP_FAIL || (level > ERRORS && !(dp->options & CONTINUE))) { if (dp->errset) { dp->errlevel = level; longjmp(dp->error_return, level); } else exit(99); } } #if PNG_LIBPNG_VER < 10700 && defined PNG_TEXT_SUPPORTED static void text_stash(struct display *dp) { /* libpng 1.6 and earlier fixed a bug whereby text chunks were written * multiple times by png_write_png; the issue was that png_write_png passed * the same png_info to both png_write_info and png_write_end. Rather than * fixing it by recording the information in the png_struct, or by recording * where to write the chunks, the fix made was to change the 'compression' * field of the chunk to invalid values, rendering the png_info somewhat * useless. * * The only fix for this given that we use the png_info more than once is to * make a copy of the text chunks and png_set_text it each time. This adds a * text chunks, so they get replicated, but only the new set gets written * each time. This uses memory like crazy but there is no way to delete the * useless chunks from the png_info. * * To make this slightly more efficient only the top level structure is * copied; since the old strings are actually preserved (in 1.6 and earlier) * this happens to work. */ png_textp chunks = NULL; dp->num_text = png_get_text(dp->write_pp, dp->ip, &chunks, NULL); if (dp->num_text > 0) { dp->text_ptr = voidcast(png_textp, malloc(dp->num_text * sizeof *chunks)); if (dp->text_ptr == NULL) display_log(dp, APP_ERROR, "text chunks: stash malloc failed"); else memcpy(dp->text_ptr, chunks, dp->num_text * sizeof *chunks); } dp->text_stashed = 1; /* regardless of whether there are chunks or not */ } #define text_stash(dp) if (!dp->text_stashed) text_stash(dp) static void text_restore(struct display *dp) { /* libpng makes a copy, so this is fine: */ if (dp->text_ptr != NULL) png_set_text(dp->write_pp, dp->ip, dp->text_ptr, dp->num_text); } #define text_restore(dp) if (dp->text_stashed) text_restore(dp) #else #define text_stash(dp) ((void)0) #define text_restore(dp) ((void)0) #endif /* pre 1.7 */ /* OPTIONS: * * The command handles options of the forms: * * --option * Turn an option on (Option) * --no-option * Turn an option off (Option) * --option=value * Set an option to a value (Value) * --option=val1,val2,val3 * Set an option to a bitmask constructed from the values (List) */ static png_byte option_index(struct display *dp, const char *opt, size_t len) /* Return the index (in options[]) of the given option, outputs an error if * it does not exist. Takes the name of the option and a length (number of * characters in the name). */ { png_byte j; for (j=0; jerrset ? INTERNAL_ERROR : USER_ERROR, "%.*s: unknown option", (int)/*SAFE*/len, opt); abort(); /* NOT REACHED */ } /* This works for an option name (no quotes): */ #define OPTIND(dp, name) option_index(dp, #name, (sizeof #name)-1) static int get_option(struct display *dp, const char *opt, int *value) { png_byte i = option_index(dp, opt, strlen(opt)); if (dp->entry[i]) /* option was set on command line */ { *value = dp->value[i]; return 1; } else return 0; } static int set_opt_string_(struct display *dp, unsigned int sp, png_byte opt, const char *entry_name) /* Add the appropriate option string to dp->curr. */ { int offset, add; if (sp > 0) offset = dp->stack[sp-1].opt_string_end; else offset = dp->opt_string_start; if (entry_name == range_lo) add = sprintf(dp->curr+offset, " --%s=%d", options[opt].name, dp->value[opt]); else add = sprintf(dp->curr+offset, " --%s=%s", options[opt].name, entry_name); if (add < 0) display_log(dp, INTERNAL_ERROR, "sprintf failed"); assert(offset+add < (int)/*SAFE*/sizeof dp->curr); return offset+add; } static void set_opt_string(struct display *dp, unsigned int sp) /* Add the appropriate option string to dp->curr. */ { dp->stack[sp].opt_string_end = set_opt_string_(dp, sp, dp->stack[sp].opt, options[dp->stack[sp].opt].values[dp->stack[sp].entry].name); } static void record_opt(struct display *dp, png_byte opt, const char *entry_name) /* Record this option in dp->curr; called for an option not being searched, * the caller passes in the name of the value, or range_lo to use the * numerical value. */ { unsigned int sp = dp->csp; /* stack entry of next searched option */ if (sp >= dp->tsp) { /* At top of stack; add the opt string for this entry to the previous * searched entry or the start of the dp->curr buffer if there is nothing * on the stack yet (sp == 0). */ int offset = set_opt_string_(dp, sp, opt, entry_name); if (sp > 0) dp->stack[sp-1].opt_string_end = offset; else dp->opt_string_start = offset; } /* else do nothing: option already recorded */ } static int opt_list_end(struct display *dp, png_byte opt, png_byte entry) { if (options[opt].values[entry].name == range_lo) return entry+1U >= options[opt].value_count /* missing range_hi */ || options[opt].values[entry+1U].name != range_hi /* likewise */ || options[opt].values[entry+1U].value <= dp->value[opt] /* range end */; else return entry+1U >= options[opt].value_count /* missing 'all' */ || options[opt].values[entry+1U].name == all /* last entry */; } static void push_opt(struct display *dp, unsigned int sp, png_byte opt, int search) /* Push a new option onto the stack, initializing the new stack entry * appropriately; this does all the work of next_opt (setting end/nsp) for * the first entry in the list. */ { png_byte entry; const char *entry_name; assert(sp == dp->tsp && sp < SL); /* The starting entry is entry 0 unless there is a range in which case it is * the entry corresponding to range_lo: */ entry = options[opt].value_count; assert(entry > 0U); do { entry_name = options[opt].values[--entry].name; if (entry_name == range_lo) break; } while (entry > 0U); dp->tsp = sp+1U; dp->stack[sp].best_size = dp->stack[sp].lo_size = dp->stack[sp].hi_size = MAX_SIZE; if (search && entry_name == range_lo) /* search this range */ { dp->stack[sp].lo = options[opt].values[entry].value; /* check for a mal-formed RANGE above: */ assert(entry+1 < options[opt].value_count && options[opt].values[entry+1].name == range_hi); dp->stack[sp].hi = options[opt].values[entry+1].value; } else { /* next_opt will just iterate over the range. */ dp->stack[sp].lo = INT_MAX; dp->stack[sp].hi = INT_MIN; /* Prevent range chop */ } dp->stack[sp].opt = opt; dp->stack[sp].entry = entry; dp->stack[sp].best_val = dp->value[opt] = options[opt].values[entry].value; set_opt_string(dp, sp); /* This works for the search case too; if the range has only one entry 'end' * will be marked here. */ if (opt_list_end(dp, opt, entry)) { dp->stack[sp].end = 1; /* Skip the warning if pngcp did this itself. See the code in * set_windowBits_hi. */ if (opt != dp->min_windowBits) display_log(dp, APP_WARNING, "%s: only testing one value", options[opt].name); } else { dp->stack[sp].end = 0; dp->nsp = dp->tsp; } /* Do a lazy cache of the text chunks for libpng 1.6 and earlier; this is * because they can only be written once(!) so if we are going to re-use the * png_info we need a copy. */ text_stash(dp); } static void next_opt(struct display *dp, unsigned int sp) /* Return the next value for this option. When called 'sp' is expected to be * the topmost stack entry - only the topmost entry changes each time round - * and there must be a valid entry to return. next_opt will set dp->nsp to * sp+1 if more entries are available, otherwise it will not change it and * set dp->stack[s].end to true. */ { int search = 0; png_byte entry, opt; const char *entry_name; /* dp->stack[sp] must be the top stack entry and it must be active: */ assert(sp+1U == dp->tsp && !dp->stack[sp].end); opt = dp->stack[sp].opt; entry = dp->stack[sp].entry; assert(entry+1U < options[opt].value_count); entry_name = options[opt].values[entry].name; assert(entry_name != NULL); /* For ranges increment the value but don't change the entry, for all other * cases move to the next entry and load its value: */ if (entry_name == range_lo) /* a range */ { /* A range can be iterated over or searched. The default iteration option * is indicated by hi < lo on the stack, otherwise the range being search * is [lo..hi] (inclusive). */ if (dp->stack[sp].lo > dp->stack[sp].hi) dp->value[opt]++; else { /* This is the best size found for this option value: */ png_alloc_size_t best_size = dp->stack[sp].best_size; int lo = dp->stack[sp].lo; int hi = dp->stack[sp].hi; int val = dp->value[opt]; search = 1; /* end is determined here */ assert(best_size < MAX_SIZE); if (val == lo) { /* Finding the best for the low end of the range: */ dp->stack[sp].lo_size = best_size; assert(hi > val); if (hi == val+1) /* only 2 entries */ dp->stack[sp].end = 1; val = hi; } else if (val == hi) { dp->stack[sp].hi_size = best_size; assert(val > lo+1); /* else 'end' set above */ if (val == lo+2) /* only three entries to test */ dp->stack[sp].end = 1; val = (lo + val)/2; } else { png_alloc_size_t lo_size = dp->stack[sp].lo_size; png_alloc_size_t hi_size = dp->stack[sp].hi_size; /* lo and hi should have been tested. */ assert(lo_size < MAX_SIZE && hi_size < MAX_SIZE); /* These cases arise with the 'probe' handling below when there is a * dip or peak in the size curve. */ if (val < lo) /* probing a new lo */ { /* Swap lo and val: */ dp->stack[sp].lo = val; dp->stack[sp].lo_size = best_size; val = lo; best_size = lo_size; lo = dp->stack[sp].lo; lo_size = dp->stack[sp].lo_size; } else if (val > hi) /* probing a new hi */ { /* Swap hi and val: */ dp->stack[sp].hi = val; dp->stack[sp].hi_size = best_size; val = hi; best_size = hi_size; hi = dp->stack[sp].hi; hi_size = dp->stack[sp].hi_size; } /* The following should be true or something got messed up above. */ assert(lo < val && val < hi); /* If there are only four entries (lo, val, hi plus one more) just * test the remaining entry. */ if (hi == lo+3) { /* Because of the 'probe' code val can either be lo+1 or hi-1; we * need to test the other. */ val = lo + ((val == lo+1) ? 2 : 1); assert(lo < val && val < hi); dp->stack[sp].end = 1; } else { /* There are at least 2 entries still untested between lo and hi, * i.e. hi >= lo+4. 'val' is the midpoint +/- 0.5 * * Separate out the four easy cases when lo..val..hi are * monotonically decreased or (more weird) increasing: */ assert(hi > lo+3); if (lo_size <= best_size && best_size <= hi_size) { /* Select the low range; testing this first favours the low * range over the high range when everything comes out equal. * Because of the probing 'val' may be lo+1. In that case end * the search and set 'val' to lo+2. */ if (val == lo+1) { ++val; dp->stack[sp].end = 1; } else { dp->stack[sp].hi = hi = val; dp->stack[sp].hi_size = best_size; val = (lo + val) / 2; } } else if (lo_size >= best_size && best_size >= hi_size) { /* Monotonically decreasing size; this is the expected case. * Select the high end of the range. As above, val may be * hi-1. */ if (val == hi-1) { --val; dp->stack[sp].end = 1; } else { dp->stack[sp].lo = lo = val; dp->stack[sp].lo_size = best_size; val = (val + hi) / 2; } } /* If both those tests failed 'best_size' is either greater than * or less than both lo_size and hi_size. There is a peak or dip * in the curve of sizes from lo to hi and val is on the peak or * dip. * * Because the ranges being searched as so small (level is 1..9, * windowBits 8..15, memLevel 1..9) there will only be at most * three untested values between lo..val and val..hi, so solve * the problem by probing down from hi or up from lo, whichever * is the higher. * * This is the place where 'val' is set to outside the range * lo..hi, described as 'probing', though maybe 'narrowing' would * be more accurate. */ else if (lo_size <= hi_size) /* down from hi */ { dp->stack[sp].hi = val; dp->stack[sp].hi_size = best_size; val = --hi; } else /* up from low */ { dp->stack[sp].lo = val; dp->stack[sp].lo_size = best_size; val = ++lo; } /* lo and hi are still the true range limits, check for the end * condition. */ assert(hi > lo+1); if (hi <= lo+2) dp->stack[sp].end = 1; } } assert(val != dp->stack[sp].best_val); /* should be a new value */ dp->value[opt] = val; dp->stack[sp].best_size = MAX_SIZE; } } else { /* Increment 'entry' */ dp->value[opt] = options[opt].values[++entry].value; dp->stack[sp].entry = entry; } set_opt_string(dp, sp); if (!search && opt_list_end(dp, opt, entry)) /* end of list */ dp->stack[sp].end = 1; else if (!dp->stack[sp].end) /* still active after all these tests */ dp->nsp = dp->tsp; } static int compare_option(const struct display *dp, unsigned int sp) { int opt = dp->stack[sp].opt; /* If the best so far is numerically less than the current value the * current set of options is invariably worse. */ if (dp->stack[sp].best_val < dp->value[opt]) return -1; /* Lists of options are searched out of numerical order (currently only * strategy), so only return +1 here when a range is being searched. */ else if (dp->stack[sp].best_val > dp->value[opt]) { if (dp->stack[sp].lo <= dp->stack[sp].hi /*searching*/) return 1; else return -1; } else return 0; /* match; current value is the best one */ } static int advance_opt(struct display *dp, png_byte opt, int search) { unsigned int sp = dp->csp++; /* my stack entry */ assert(sp >= dp->nsp); /* nsp starts off zero */ /* If the entry was active in the previous run dp->stack[sp] is already * set up and dp->tsp will be greater than sp, otherwise a new entry * needs to be created. * * dp->nsp is handled this way: * * 1) When an option is pushed onto the stack dp->nsp and dp->tsp are * both set (by push_opt) to the next stack entry *unless* there is * only one entry in the new list, in which case dp->stack[sp].end * is set. * * 2) For the top stack entry next_opt is called. The entry must be * active (dp->stack[sp].end is not set) and either 'nsp' or 'end' * will be updated as appropriate. * * 3) For lower stack entries nsp is set unless the stack entry is * already at the end. This means that when all the higher entries * are popped this entry will be too. */ if (sp >= dp->tsp) { push_opt(dp, sp, opt, search); /* This sets tsp to sp+1 */ return 1; /* initialized */ } else { int ret = 0; /* unchanged */ /* An option that is already on the stack; update best_size and best_val * if appropriate. On the first run there are no previous values and * dp->write_size will be MAX_SIZE, however on the first run dp->tsp * starts off as 0. */ assert(dp->write_size > 0U && dp->write_size < MAX_SIZE); if (dp->stack[sp].best_size > dp->write_size || (dp->stack[sp].best_size == dp->write_size && compare_option(dp, sp) > 0)) { dp->stack[sp].best_size = dp->write_size; dp->stack[sp].best_val = dp->value[opt]; } if (sp+1U >= dp->tsp) { next_opt(dp, sp); ret = 1; /* advanced */ } else if (!dp->stack[sp].end) /* Active, not at top of stack */ dp->nsp = sp+1U; return ret; /* advanced || unchanged */ } } static int getallopts_(struct display *dp, png_byte opt, int *value, int record) /* Like getop but iterate over all the values if the option was set to "all". */ { if (dp->entry[opt]) /* option was set on command line */ { /* Simple, single value, entries don't have a stack frame and have a fixed * value (it doesn't change once set on the command line). Otherwise the * value (entry) selected from the command line is 'all': */ const char *entry_name = options[opt].values[dp->entry[opt]-1].name; if (entry_name == all) (void)advance_opt(dp, opt, 0/*do not search; iterate*/); else if (record) record_opt(dp, opt, entry_name); *value = dp->value[opt]; return 1; /* set */ } else return 0; /* not set */ } static int getallopts(struct display *dp, const char *opt_str, int *value) { return getallopts_(dp, option_index(dp, opt_str, strlen(opt_str)), value, 0); } static int getsearchopts(struct display *dp, const char *opt_str, int *value) /* As above except that if the option was not set try a search */ { png_byte istrat; png_byte opt = option_index(dp, opt_str, strlen(opt_str)); int record = options[opt].search; const char *entry_name; /* If it was set on the command line honour the setting, including 'all' * which will override the built in search: */ if (getallopts_(dp, opt, value, record)) return 1; else if (!record) /* not a search option */ return 0; /* unset and not searched */ /* Otherwise decide what to do here. */ istrat = OPTIND(dp, strategy); entry_name = range_lo; /* record the value, not the name */ if (opt == istrat) /* search all strategies */ (void)advance_opt(dp, opt, 0/*iterate*/), record=0; else if (opt == OPTIND(dp, level)) { /* Both RLE and HUFFMAN don't benefit from level increases */ if (dp->value[istrat] == Z_RLE || dp->value[istrat] == Z_HUFFMAN_ONLY) dp->value[opt] = 1; else /* fixed, filtered or default */ (void)advance_opt(dp, opt, 1/*search*/), record=0; } else if (opt == OPTIND(dp, windowBits)) { /* Changing windowBits for strategies that do not search the window is * pointless. Huffman-only does not search, RLE only searches backwards * one byte, so given that the maximum string length is 258, a windowBits * of 9 is always sufficient. */ if (dp->value[istrat] == Z_HUFFMAN_ONLY) dp->value[opt] = 8; else if (dp->value[istrat] == Z_RLE) dp->value[opt] = 9; else /* fixed, filtered or default */ (void)advance_opt(dp, opt, 1/*search*/), record=0; } else if (opt == OPTIND(dp, memLevel)) { # if 0 (void)advance_opt(dp, opt, 0/*all*/), record=0; # else dp->value[opt] = MAX_MEM_LEVEL; # endif } else /* something else */ assert(0=="reached"); if (record) record_opt(dp, opt, entry_name); /* One of the above searched options: */ *value = dp->value[opt]; return 1; } static int find_val(struct display *dp, png_byte opt, const char *str, size_t len) /* Like option_index but sets (index+i) of the entry in options[opt] that * matches str[0..len-1] into dp->entry[opt] as well as returning the actual * value. */ { int rlo = INT_MAX, rhi = INT_MIN; png_byte j, irange = 0; for (j=1U; j<=options[opt].value_count; ++j) { if (strncmp(options[opt].values[j-1U].name, str, len) == 0 && options[opt].values[j-1U].name[len] == 0) { dp->entry[opt] = j; return options[opt].values[j-1U].value; } else if (options[opt].values[j-1U].name == range_lo) rlo = options[opt].values[j-1U].value, irange = j; else if (options[opt].values[j-1U].name == range_hi) rhi = options[opt].values[j-1U].value; } /* No match on the name, but there may be a range. */ if (irange > 0) { char *ep = NULL; long l = strtol(str, &ep, 0); if (ep == str+len && l >= rlo && l <= rhi) { dp->entry[opt] = irange; /* range_lo */ return (int)/*SAFE*/l; } } display_log(dp, dp->errset ? INTERNAL_ERROR : USER_ERROR, "%s: unknown value setting '%.*s'", options[opt].name, (int)/*SAFE*/len, str); abort(); /* NOT REACHED */ } static int opt_check(struct display *dp, const char *arg) { assert(dp->errset == 0); if (arg != NULL && arg[0] == '-' && arg[1] == '-') { int i = 0, negate = (strncmp(arg+2, "no-", 3) == 0), val; png_byte j; if (negate) arg += 5; /* --no- */ else arg += 2; /* -- */ /* Find the length (expect arg\0 or arg=) */ while (arg[i] != 0 && arg[i] != '=') ++i; /* So arg[0..i-1] is the argument name, this does not return if this isn't * a valid option name. */ j = option_index(dp, arg, i); /* It matcheth an option; check the remainder. */ if (arg[i] == 0) /* no specified value, use the default */ { val = options[j].values[negate].value; dp->entry[j] = (png_byte)/*SAFE*/(negate + 1U); } else { const char *list = arg + (i+1); /* Expect a single value here unless this is a list, in which case * multiple values are combined. */ if (options[j].opt != LIST) { /* find_val sets 'dp->entry[j]' to a non-zero value: */ val = find_val(dp, j, list, strlen(list)); if (negate) { if (options[j].opt < OPTION) val = !val; else { display_log(dp, USER_ERROR, "%.*s: option=arg cannot be negated", i, arg); abort(); /* NOT REACHED */ } } } else /* multiple options separated by ',' characters */ { /* --no-option negates list values from the default, which should * therefore be 'all'. Notice that if the option list is empty in * this case nothing will be removed and therefore --no-option= is * the same as --option. */ if (negate) val = options[j].values[0].value; else val = 0; while (*list != 0) /* allows option= which sets 0 */ { /* A value is terminated by the end of the list or a ',' * character. */ int v, iv; iv = 0; /* an index into 'list' */ while (list[++iv] != 0 && list[iv] != ',') {} v = find_val(dp, j, list, iv); if (negate) val &= ~v; else val |= v; list += iv; if (*list != 0) ++list; /* skip the ',' */ } } } /* 'val' is the new value, store it for use later and debugging: */ dp->value[j] = val; if (options[j].opt < LEVEL_MASK) { /* The handling for error levels is to set the level. */ if (val) /* Set this level */ dp->options = (dp->options & ~LEVEL_MASK) | options[j].opt; else display_log(dp, USER_ERROR, "%.*s: messages cannot be turned off individually; set a message level", i, arg); } else if (options[j].opt < OPTION) { if (val) dp->options |= options[j].opt; else dp->options &= ~options[j].opt; } return 1; /* this is an option */ } else return 0; /* not an option */ } #ifdef PNG_PNGCP_TIMING_SUPPORTED static void set_timer(struct display *dp, struct timespec *timer) { /* Do the timing using clock_gettime and the per-process timer. */ if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, timer)) { display_log(dp, APP_ERROR, "CLOCK_PROCESS_CPUTIME_ID: %s: timing disabled\n", strerror(errno)); dp->value[OPTIND(dp,time)] = 0; /* i.e. off */ } } static void start_timer(struct display *dp, int what) { if ((dp->value[OPTIND(dp,time)] & what) != 0) set_timer(dp, what == PNGCP_TIME_READ ? &dp->read_time : &dp->write_time); } static void end_timer(struct display *dp, int what) { if ((dp->value[OPTIND(dp,time)] & what) != 0) { struct timespec t, tmp; set_timer(dp, &t); if (what == PNGCP_TIME_READ) tmp = dp->read_time; else tmp = dp->write_time; t.tv_sec -= tmp.tv_sec; t.tv_nsec -= tmp.tv_nsec; if (t.tv_nsec < 0) { --(t.tv_sec); t.tv_nsec += 1000000000L; } if (what == PNGCP_TIME_READ) dp->read_time = t, tmp = dp->read_time_total; else dp->write_time = t, tmp = dp->write_time_total; tmp.tv_sec += t.tv_sec; tmp.tv_nsec += t.tv_nsec; if (tmp.tv_nsec >= 1000000000L) { ++(tmp.tv_sec); tmp.tv_nsec -= 1000000000L; } if (what == PNGCP_TIME_READ) dp->read_time_total = tmp; else dp->write_time_total = tmp; } } static void print_time(const char *what, struct timespec t) { printf("%s %.2lu.%.9ld", what, (unsigned long)t.tv_sec, t.tv_nsec); } #else /* !PNGCP_TIMING */ #define start_timer(dp, what) ((void)0) #define end_timer(dp, what) ((void)0) #endif /* !PNGCP_TIMING */ /* The following is used in main to verify that the final argument is a * directory: */ static int checkdir(const char *pathname) { struct stat buf; return stat(pathname, &buf) == 0 && S_ISDIR(buf.st_mode); } /* Work out whether a path is valid (if not a display_log occurs), a directory * (1 is returned) or a file *or* non-existent (0 is returned). * * Used for a write path. */ static int isdir(struct display *dp, const char *pathname) { if (pathname == NULL) return 0; /* stdout */ else if (pathname[0] == 0) return 1; /* empty string */ else { struct stat buf; int ret = stat(pathname, &buf); if (ret == 0) /* the entry exists */ { if (S_ISDIR(buf.st_mode)) return 1; /* Else expect an object that exists and can be written: */ if (access(pathname, W_OK) != 0) display_log(dp, USER_ERROR, "%s: cannot be written (%s)", pathname, strerror(errno)); return 0; /* file (exists, can be written) */ } else /* an error */ { /* Non-existence is fine, other errors are not: */ if (errno != ENOENT) display_log(dp, USER_ERROR, "%s: invalid output name (%s)", pathname, strerror(errno)); return 0; /* file (does not exist) */ } } } static void makename(struct display *dp, const char *dir, const char *infile) { /* Make a name for an output file (and check it). */ dp->namebuf[0] = 0; if (dir == NULL || infile == NULL) display_log(dp, INTERNAL_ERROR, "NULL name to makename"); else { size_t dsize = strlen(dir); if (dsize <= (sizeof dp->namebuf)-2) /* Allow for name + '/' + '\0' */ { size_t isize = strlen(infile); size_t istart = isize-1; /* This should fail before here: */ if (infile[istart] == '/') display_log(dp, INTERNAL_ERROR, "infile with trailing /"); memcpy(dp->namebuf, dir, dsize); if (dsize > 0 && dp->namebuf[dsize-1] != '/') dp->namebuf[dsize++] = '/'; /* Find the rightmost non-/ character: */ while (istart > 0 && infile[istart-1] != '/') --istart; isize -= istart; infile += istart; if (dsize+isize < (sizeof dp->namebuf)) /* dsize + infile + '\0' */ { memcpy(dp->namebuf+dsize, infile, isize+1); if (isdir(dp, dp->namebuf)) display_log(dp, USER_ERROR, "%s: output file is a directory", dp->namebuf); } else { dp->namebuf[dsize] = 0; /* allowed for: -2 at start */ display_log(dp, USER_ERROR, "%s%s: output file name too long", dp->namebuf, infile); } } else display_log(dp, USER_ERROR, "%s: output directory name too long", dir); } } /* error handler callbacks for libpng */ static void PNGCBAPI display_warning(png_structp pp, png_const_charp warning) { struct display *dp = get_dp(pp); /* This is used to prevent repeated warnings while searching */ if (!dp->no_warnings) display_log(get_dp(pp), LIBPNG_WARNING, "%s", warning); } static void PNGCBAPI display_error(png_structp pp, png_const_charp error) { struct display *dp = get_dp(pp); display_log(dp, LIBPNG_ERROR, "%s", error); } static void display_start_read(struct display *dp, const char *filename) { if (filename != NULL) { dp->filename = filename; dp->fp = fopen(filename, "rb"); } else { dp->filename = ""; dp->fp = stdin; } dp->w = dp->h = 0U; dp->bpp = 0U; dp->size = 0U; dp->read_size = 0U; if (dp->fp == NULL) display_log(dp, USER_ERROR, "file open failed (%s)", strerror(errno)); } static void PNGCBAPI read_function(png_structp pp, png_bytep data, size_t size) { struct display *dp = get_dp(pp); if (size == 0U || fread(data, size, 1U, dp->fp) == 1U) dp->read_size += size; else { if (feof(dp->fp)) display_log(dp, LIBPNG_ERROR, "PNG file truncated"); else display_log(dp, LIBPNG_ERROR, "PNG file read failed (%s)", strerror(errno)); } } static void read_png(struct display *dp, const char *filename) { /* This is an assumption of the code; it may happen if a previous write fails * and there is a bug in the cleanup handling below (look for setjmp). * Passing freeinfo==1 to display_clean_read below avoids a second error * on dp->ip != NULL below. */ if (dp->read_pp != NULL) { display_log(dp, APP_FAIL, "unexpected png_read_struct"); display_clean_read(dp, 1/*freeinfo*/); /* recovery */ } display_start_read(dp, filename); dp->read_pp = png_create_read_struct(PNG_LIBPNG_VER_STRING, dp, display_error, display_warning); if (dp->read_pp == NULL) display_log(dp, LIBPNG_ERROR, "failed to create read struct"); # ifdef PNG_BENIGN_ERRORS_SUPPORTED png_set_benign_errors(dp->read_pp, 1/*allowed*/); # endif /* BENIGN_ERRORS */ # ifdef FIX_INDEX if ((dp->options & FIX_INDEX) != 0) png_set_check_for_invalid_index(dp->read_pp, 1/*on, no warning*/); # ifdef IGNORE_INDEX else # endif /* IGNORE_INDEX */ # endif /* FIX_INDEX */ # ifdef IGNORE_INDEX if ((dp->options & IGNORE_INDEX) != 0) /* DANGEROUS */ png_set_check_for_invalid_index(dp->read_pp, -1/*off completely*/); # endif /* IGNORE_INDEX */ if (dp->ip != NULL) { /* UNEXPECTED: some problem in the display_clean function calls! */ display_log(dp, APP_FAIL, "read_png: freeing old info struct"); png_destroy_info_struct(dp->read_pp, &dp->ip); } /* The png_read_png API requires us to make the info struct, but it does the * call to png_read_info. */ dp->ip = png_create_info_struct(dp->read_pp); if (dp->ip == NULL) png_error(dp->read_pp, "failed to create info struct"); /* Set the IO handling */ png_set_read_fn(dp->read_pp, dp, read_function); # ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED png_set_keep_unknown_chunks(dp->read_pp, PNG_HANDLE_CHUNK_ALWAYS, NULL, 0); # endif /* HANDLE_AS_UNKNOWN */ # ifdef PNG_SET_USER_LIMITS_SUPPORTED /* Remove the user limits, if any */ png_set_user_limits(dp->read_pp, 0x7fffffff, 0x7fffffff); # endif /* SET_USER_LIMITS */ /* Now read the PNG. */ start_timer(dp, PNGCP_TIME_READ); png_read_png(dp->read_pp, dp->ip, 0U/*transforms*/, NULL/*params*/); end_timer(dp, PNGCP_TIME_READ); dp->w = png_get_image_width(dp->read_pp, dp->ip); dp->h = png_get_image_height(dp->read_pp, dp->ip); dp->ct = png_get_color_type(dp->read_pp, dp->ip); dp->bpp = png_get_bit_depth(dp->read_pp, dp->ip) * png_get_channels(dp->read_pp, dp->ip); { /* png_get_rowbytes should never return 0 because the value is set by the * first call to png_set_IHDR, which should have happened by now, but just * in case: */ png_alloc_size_t rb = png_get_rowbytes(dp->read_pp, dp->ip); if (rb == 0) png_error(dp->read_pp, "invalid row byte count from libpng"); /* The size calc can overflow. */ if ((MAX_SIZE-dp->h)/rb < dp->h) png_error(dp->read_pp, "image too large"); dp->size = rb * dp->h + dp->h/*filter byte*/; } #ifdef FIX_INDEX if (dp->ct == PNG_COLOR_TYPE_PALETTE && (dp->options & FIX_INDEX) != 0) { int max = png_get_palette_max(dp->read_pp, dp->ip); png_colorp palette = NULL; int num = -1; if (png_get_PLTE(dp->read_pp, dp->ip, &palette, &num) != PNG_INFO_PLTE || max < 0 || num <= 0 || palette == NULL) display_log(dp, LIBPNG_ERROR, "invalid png_get_PLTE result"); if (max >= num) { /* 'Fix' the palette. */ int i; png_color newpal[256]; for (i=0; iread_pp, dp->ip, newpal, i); } } #endif /* FIX_INDEX */ /* NOTE: dp->ip is where all the information about the PNG that was just read * is stored. It can be used to write and write again a single PNG file, * however be aware that prior to libpng 1.7 text chunks could only be * written once; this is a bug which would require a significant code rewrite * to fix, it has been there in several versions of libpng (it was introduced * to fix another bug involving duplicate writes of the text chunks.) */ display_clean_read(dp, 0/*freeiinfo*/); dp->operation = "none"; } static void display_start_write(struct display *dp, const char *filename) { assert(dp->fp == NULL); if ((dp->options & NOWRITE) != 0) dp->output_file = ""; else { if (filename != NULL) { dp->output_file = filename; dp->fp = fopen(filename, "wb"); } else { dp->output_file = ""; dp->fp = stdout; } if (dp->fp == NULL) display_log(dp, USER_ERROR, "%s: file open failed (%s)", dp->output_file, strerror(errno)); } } static void PNGCBAPI write_function(png_structp pp, png_bytep data, size_t size) { struct display *dp = get_dp(pp); /* The write fail is classed as a USER_ERROR, so --quiet does not turn it * off, this seems more likely to be correct. */ if (dp->fp == NULL || fwrite(data, size, 1U, dp->fp) == 1U) { dp->write_size += size; if (dp->write_size < size || dp->write_size == MAX_SIZE) png_error(pp, "IDAT size overflow"); } else display_log(dp, USER_ERROR, "%s: PNG file write failed (%s)", dp->output_file, strerror(errno)); } /* Compression option, 'method' is never set: there is no choice. * * IMPORTANT: the order of the entries in this macro determines the preference * order when two different combos of two of these options produce an IDAT of * the same size. The logic here is to put the things that affect the decoding * of the PNG image ahead of those that are relevant only to the encoding. */ #define SET_COMPRESSION\ SET(strategy, strategy);\ SET(windowBits, window_bits);\ SET(level, level);\ SET(memLevel, mem_level); #ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED static void search_compression(struct display *dp) { /* Like set_compression below but use a more restricted search than 'all' */ int val; # define SET(name, func) if (getsearchopts(dp, #name, &val))\ png_set_compression_ ## func(dp->write_pp, val); SET_COMPRESSION # undef SET } static void set_compression(struct display *dp) { int val; # define SET(name, func) if (getallopts(dp, #name, &val))\ png_set_compression_ ## func(dp->write_pp, val); SET_COMPRESSION # undef SET } #ifdef PNG_SW_COMPRESS_level /* 1.7.0+ */ static void set_ICC_profile_compression(struct display *dp) { int val; # define SET(name, func) if (getallopts(dp, "ICC-profile-" #name, &val))\ png_set_ICC_profile_compression_ ## func(dp->write_pp, val); SET_COMPRESSION # undef SET } #else # define set_ICC_profile_compression(dp) ((void)0) #endif #else # define search_compression(dp) ((void)0) # define set_compression(dp) ((void)0) # define set_ICC_profile_compression(dp) ((void)0) #endif /* WRITE_CUSTOMIZE_COMPRESSION */ #ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED static void set_text_compression(struct display *dp) { int val; # define SET(name, func) if (getallopts(dp, "text-" #name, &val))\ png_set_text_compression_ ## func(dp->write_pp, val); SET_COMPRESSION # undef SET } #else # define set_text_compression(dp) ((void)0) #endif /* WRITE_CUSTOMIZE_ZTXT_COMPRESSION */ static void write_png(struct display *dp, const char *destname) { /* If this test fails png_write_png would fail *silently* below; this * is not helpful, so catch the problem now and give up: */ if (dp->ip == NULL) display_log(dp, INTERNAL_ERROR, "missing png_info"); /* This is an assumption of the code; it may happen if a previous * write fails and there is a bug in the cleanup handling below. */ if (dp->write_pp != NULL) { display_log(dp, APP_FAIL, "unexpected png_write_struct"); display_clean_write(dp, 0/*!freeinfo*/); } display_start_write(dp, destname); dp->write_pp = png_create_write_struct(PNG_LIBPNG_VER_STRING, dp, display_error, display_warning); if (dp->write_pp == NULL) display_log(dp, LIBPNG_ERROR, "failed to create write png_struct"); # ifdef PNG_BENIGN_ERRORS_SUPPORTED png_set_benign_errors(dp->write_pp, 1/*allowed*/); # endif /* BENIGN_ERRORS */ png_set_write_fn(dp->write_pp, dp, write_function, NULL/*flush*/); #ifdef IGNORE_INDEX if ((dp->options & IGNORE_INDEX) != 0) /* DANGEROUS */ png_set_check_for_invalid_index(dp->write_pp, -1/*off completely*/); #endif /* IGNORE_INDEX */ /* Restore the text chunks when using libpng 1.6 or less; this is a macro * which expands to nothing in 1.7+ In earlier versions it tests * dp->text_stashed, which is only set (below) *after* the first write. */ text_restore(dp); # ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED png_set_keep_unknown_chunks(dp->write_pp, PNG_HANDLE_CHUNK_ALWAYS, NULL, 0); # endif /* HANDLE_AS_UNKNOWN */ # ifdef PNG_SET_USER_LIMITS_SUPPORTED /* Remove the user limits, if any */ png_set_user_limits(dp->write_pp, 0x7fffffff, 0x7fffffff); # endif /* OPTION HANDLING */ /* compression outputs, IDAT and zTXt/iTXt: */ dp->tsp = dp->nsp; dp->nsp = dp->csp = 0; # ifdef PNG_SW_COMPRESS_png_level { int val; /* This sets everything, but then the following options just override * the specific settings for ICC profiles and text. */ if (getallopts(dp, "compression", &val)) png_set_compression(dp->write_pp, val); if (getallopts(dp, "ICC-profile-compression", &val)) png_set_ICC_profile_compression(dp->write_pp, val); if (getallopts(dp, "text-compression", &val)) png_set_text_compression(dp->write_pp, val); } # endif /* png_level support */ if (dp->options & SEARCH) search_compression(dp); else set_compression(dp); set_ICC_profile_compression(dp); set_text_compression(dp); { int val; /* The permitted range is 1..0x7FFFFFFF, so the cast is safe */ if (get_option(dp, "IDAT-size", &val)) png_set_IDAT_size(dp->write_pp, val); } /* filter handling */ # ifdef PNG_WRITE_FILTER_SUPPORTED { int val; if (get_option(dp, "filter", &val)) png_set_filter(dp->write_pp, PNG_FILTER_TYPE_BASE, val); } # endif /* WRITE_FILTER */ /* This just uses the 'read' info_struct directly, it contains the image. */ dp->write_size = 0U; start_timer(dp, PNGCP_TIME_WRITE); png_write_png(dp->write_pp, dp->ip, 0U/*transforms*/, NULL/*params*/); end_timer(dp, PNGCP_TIME_WRITE); /* Make sure the file was written ok: */ if (dp->fp != NULL) { FILE *fp = dp->fp; dp->fp = NULL; if (fclose(fp)) display_log(dp, APP_ERROR, "%s: write failed (%s)", destname == NULL ? "stdout" : destname, strerror(errno)); } dp->operation = "none"; } static void set_windowBits_hi(struct display *dp) { /* windowBits is in the range 8..15 but zlib maps '8' to '9' so it is only * worth using if the data size is 256 byte or less. */ int wb = MAX_WBITS; /* for large images */ int i = VLSIZE(windowBits_IDAT); while (wb > 8 && dp->size <= 1U<<(wb-1)) --wb; while (--i >= 0) if (VLNAME(windowBits_IDAT)[i].name == range_hi) break; assert(i > 1); /* vl_windowBits_IDAT always has a RANGE() */ VLNAME(windowBits_IDAT)[i].value = wb; assert(VLNAME(windowBits_IDAT)[--i].name == range_lo); VLNAME(windowBits_IDAT)[i].value = wb > 8 ? 9 : 8; /* If wb == 8 then any search has been restricted to just one windowBits * entry. Record that here to avoid producing a spurious app-level warning * above. */ if (wb == 8) dp->min_windowBits = OPTIND(dp, windowBits); } static int better_options(const struct display *dp) { /* Are these options better than the best found so far? Normally the * options are tested in preference order, best first, however when doing a * search operation on a range the range values are tested out of order. In * that case preferable options will get tested later. * * This function looks through the stack from the bottom up looking for an * option that does not match the current best value. When it finds one it * checks to see if it is more or less desirable and returns true or false * as appropriate. * * Notice that this means that the order options are pushed onto the stack * conveys a priority; lower/earlier options are more important than later * ones. */ unsigned int sp; for (sp=0; spcsp; ++sp) { int c = compare_option(dp, sp); if (c < 0) return 0; /* worse */ else if (c > 0) return 1; /* better */ } assert(0 && "unreached"); } static void print_search_results(struct display *dp) { assert(dp->filename != NULL); printf("%s [%ld x %ld %d bpp %s, %lu bytes] %lu -> %lu with '%s'\n", dp->filename, (unsigned long)dp->w, (unsigned long)dp->h, dp->bpp, cts(dp->ct), (unsigned long)dp->size, (unsigned long)dp->read_size, (unsigned long)dp->best_size, dp->best); fflush(stdout); } static void log_search(struct display *dp, unsigned int log_depth) { /* Log, and reset, the search so far: */ if (dp->nsp/*next entry to change*/ <= log_depth) { print_search_results(dp); /* Start again with this entry: */ dp->best_size = MAX_SIZE; } } static void cp_one_file(struct display *dp, const char *filename, const char *destname) { unsigned int log_depth; dp->filename = filename; dp->operation = "read"; dp->no_warnings = 0; /* Read it then write it: */ if (filename != NULL && access(filename, R_OK) != 0) display_log(dp, USER_ERROR, "%s: invalid file name (%s)", filename, strerror(errno)); read_png(dp, filename); /* But 'destname' may be a directory. */ dp->operation = "write"; /* Limit the upper end of the windowBits range for this file */ set_windowBits_hi(dp); /* For logging, depth to log: */ { int val; if (get_option(dp, "log-depth", &val) && val >= 0) log_depth = (unsigned int)/*SAFE*/val; else log_depth = 0U; } if (destname != NULL) /* else stdout */ { if (isdir(dp, destname)) { makename(dp, destname, filename); destname = dp->namebuf; } else if (access(destname, W_OK) != 0 && errno != ENOENT) display_log(dp, USER_ERROR, "%s: invalid output name (%s)", destname, strerror(errno)); } dp->nsp = 0; dp->curr[0] = 0; /* acts as a flag for the caller */ dp->opt_string_start = 0; dp->best[0] = 0; /* safety */ dp->best_size = MAX_SIZE; write_png(dp, destname); /* Initialize the 'best' fields: */ strcpy(dp->best, dp->curr); dp->best_size = dp->write_size; if (dp->nsp > 0) /* iterating over lists */ { char *tmpname, tmpbuf[(sizeof dp->namebuf) + 4]; assert(dp->curr[0] == ' ' && dp->tsp > 0); /* Cancel warnings on subsequent writes */ log_search(dp, log_depth); dp->no_warnings = 1; /* Make a temporary name for the subsequent tests: */ if (destname != NULL) { strcpy(tmpbuf, destname); strcat(tmpbuf, ".tmp"); /* space for .tmp allocated above */ tmpname = tmpbuf; } else tmpname = NULL; /* stdout */ /* Loop to find the best option. */ do { /* Clean before each write_png; this just removes *dp->write_pp which * cannot be reused. */ display_clean_write(dp, 0/*!freeinfo*/); write_png(dp, tmpname); /* And compare the sizes (the write function makes sure write_size * doesn't overflow.) */ assert(dp->csp > 0); if (dp->write_size < dp->best_size || (dp->write_size == dp->best_size && better_options(dp))) { if (destname != NULL && rename(tmpname, destname) != 0) display_log(dp, APP_ERROR, "rename %s %s failed (%s)", tmpname, destname, strerror(errno)); strcpy(dp->best, dp->curr); dp->best_size = dp->write_size; } else if (tmpname != NULL && unlink(tmpname) != 0) display_log(dp, APP_WARNING, "unlink %s failed (%s)", tmpname, strerror(errno)); log_search(dp, log_depth); } while (dp->nsp > 0); /* Do this for the 'sizes' option so that it reports the correct size. */ dp->write_size = dp->best_size; } display_clean_write(dp, 1/*freeinfo*/); } static int cppng(struct display *dp, const char *file, const char *dest) { if (setjmp(dp->error_return) == 0) { dp->errset = 1; cp_one_file(dp, file, dest); dp->errset = 0; return 0; } else { dp->errset = 0; if (dp->errlevel < ERRORS) /* shouldn't longjmp on warnings */ display_log(dp, INTERNAL_ERROR, "unexpected return code %d", dp->errlevel); return dp->errlevel; } } int main(int argc, char **argv) { /* For each file on the command line test it with a range of transforms */ int option_end; struct display d; display_init(&d); d.operation = "options"; for (option_end = 1; option_end < argc && opt_check(&d, argv[option_end]); ++option_end) { } /* Do a quick check on the directory target case; when there are more than * two arguments the last one must be a directory. */ if (!(d.options & NOWRITE) && option_end+2 < argc && !checkdir(argv[argc-1])) { fprintf(stderr, "pngcp: %s: directory required with more than two arguments\n", argv[argc-1]); return 99; } { int errors = 0; int i = option_end; /* Do this at least once; if there are no arguments stdin/stdout are used. */ d.operation = "files"; do { const char *infile = NULL; const char *outfile = NULL; int ret; if (i < argc) { infile = argv[i++]; if (!(d.options & NOWRITE) && i < argc) outfile = argv[argc-1]; } ret = cppng(&d, infile, outfile); if (ret) { if (ret > QUIET) /* abort on user or internal error */ return 99; /* An error: the output is meaningless */ } else if (d.best[0] != 0) { /* This result may already have been output, in which case best_size * has been reset. */ if (d.best_size < MAX_SIZE) print_search_results(&d); } else if (d.options & SIZES) { printf("%s [%ld x %ld %d bpp %s, %lu bytes] %lu -> %lu [0x%lx]\n", infile, (unsigned long)d.w, (unsigned long)d.h, d.bpp, cts(d.ct), (unsigned long)d.size, (unsigned long)d.read_size, (unsigned long)d.write_size, (unsigned long)d.results); fflush(stdout); } /* Here on any return, including failures, except user/internal issues */ { int pass = (d.options & STRICT) ? RESULT_STRICT(d.results) : RESULT_RELAXED(d.results); if (!pass) ++errors; if (d.options & LOG) { int j; printf("%s: pngcp", pass ? "PASS" : "FAIL"); for (j=1; j