diff --git a/contrib/examples/pngcp.c b/contrib/examples/pngcp.c index 34e847ac9..2bbec1d6b 100644 --- a/contrib/examples/pngcp.c +++ b/contrib/examples/pngcp.c @@ -14,6 +14,7 @@ * For a more extensive example that uses the transforms see * contrib/libtests/pngimage.c in the libpng distribution. */ +#define _POSIX_SOURCE 1 #include #include #include @@ -21,6 +22,9 @@ #include #include +#include +#include + #if defined(HAVE_CONFIG_H) && !defined(PNG_NO_CONFIG_H) # include #endif @@ -34,10 +38,34 @@ # include "../../png.h" #endif +#include + #ifndef PNG_SETJMP_SUPPORTED # include /* because png.h did *not* include this */ #endif +#ifdef __GNUC__ + /* Many versions of GCC erroneously report that local variables unmodified + * within the scope of a setjmp may be clobbered. This hacks round the + * problem (sometimes) without harming other compilers. + */ +# define gv volatile +#else +# define gv +#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)) /* This structure is used to control the test of a single file. */ typedef enum @@ -61,6 +89,9 @@ typedef enum #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 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->filename = NULL; dp->fp = NULL; dp->read_pp = NULL; dp->ip = NULL; - dp->output_file = NULL; dp->write_pp = NULL; } @@ -134,7 +349,7 @@ display_clean_write(struct display *dp) } if (dp->write_pp != NULL) - png_destroy_write_struct(&dp->write_pp, &dp->ip); + png_destroy_write_struct(&dp->write_pp, dp->tsp > 0 ? NULL : &dp->ip); } static void @@ -223,7 +438,526 @@ display_log(struct display *dp, error_level level, const char *fmt, ...) /* Errors cause this routine to exit to the fail code */ if (level > APP_FAIL || (level > ERRORS && !(dp->options & CONTINUE))) - longjmp(dp->error_return, level); + { + if (dp->errset) + longjmp(dp->error_return, level); + + else + exit(99); + } +} + +/* 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 +optind(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 */ +} + +static int +getopt(struct display *dp, const char *opt, int *value) +{ + const png_byte i = optind(dp, opt, strlen(opt)); + + if (dp->entry[i]) /* option was set on command line */ + { + *value = dp->value[i]; + return 1; + } + + else + return 0; +} + +static void +set_opt_string(struct display *dp, unsigned int sp) + /* Add the appropriate option string to dp->curr. */ +{ + int offset, add; + png_byte opt = dp->stack[sp].opt; + const char *entry_name = options[opt].values[dp->stack[sp].entry].name; + + if (sp > 0) + offset = dp->stack[sp-1].opt_string_end; + + else + offset = 0; + + 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); + dp->stack[sp].opt_string_end = offset+add; +} + +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) + /* 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].opt = opt; + dp->stack[sp].entry = entry; + dp->value[opt] = options[opt].values[entry].value; + + set_opt_string(dp, sp); + + if (opt_list_end(dp, opt, entry)) + { + dp->stack[sp].end = 1; + display_log(dp, APP_WARNING, "%s: only testing one value", + options[opt].name); + } + + else + { + dp->stack[sp].end = 0; + dp->nsp = dp->tsp; + } +} + +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. + */ +{ + 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 */ + dp->value[opt]++; + + else + { + /* Increment 'entry' */ + dp->value[opt] = options[opt].values[++entry].value; + dp->stack[sp].entry = entry; + } + + set_opt_string(dp, sp); + + if (opt_list_end(dp, opt, entry)) /* end of list */ + dp->stack[sp].end = 1; + + else /* still active after all these tests */ + dp->nsp = dp->tsp; +} + +static int +getallopts(struct display *dp, const char *opt_str, int *value) + /* Like getop but iterate over all the values if the option was set to "all". + */ +{ + const png_byte opt = optind(dp, opt_str, strlen(opt_str)); + + 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': + */ + if (options[opt].values[dp->entry[opt]-1].name == all) + { + 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); /* This sets tsp to sp+1 */ + + else if (sp+1U >= dp->tsp) + next_opt(dp, sp); + + else if (!dp->stack[sp].end) /* Active, not at top of stack */ + dp->nsp = sp+1U; + } + + *value = dp->value[opt]; + return 1; /* set */ + } + + else + return 0; /* not set */ +} + +static int +find_val(struct display *dp, png_byte opt, const char *str, size_t len) + /* Like optind 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 = optind(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 */ +} + +/* 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 < FILENAME_MAX+2) + { + 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 <= FILENAME_MAX) + { + 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; + 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 */ @@ -256,8 +990,13 @@ display_start_read(struct display *dp, const char *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, APP_ERROR, "file open failed (%s)", strerror(errno)); + display_log(dp, USER_ERROR, "file open failed (%s)", strerror(errno)); } static void PNGCBAPI @@ -265,12 +1004,15 @@ read_function(png_structp pp, png_bytep data, png_size_t size) { struct display *dp = get_dp(pp); - if (fread(data, size, 1U, dp->fp) != 1U) + if (fread(data, size, 1U, dp->fp) == 1U) + dp->read_size += size; + + else { if (feof(dp->fp)) display_log(dp, USER_ERROR, "PNG file truncated"); else - display_log(dp, APP_ERROR, "PNG file read failed (%s)", + display_log(dp, USER_ERROR, "PNG file read failed (%s)", strerror(errno)); } } @@ -278,7 +1020,6 @@ read_function(png_structp pp, png_bytep data, png_size_t size) static void read_png(struct display *dp, const char *filename) { - dp->operation = "read"; display_clean_read(dp); /* safety */ display_start_read(dp, filename); @@ -309,6 +1050,11 @@ read_png(struct display *dp, const char *filename) /* Now read the PNG. */ png_read_png(dp->read_pp, dp->ip, 0U/*transforms*/, NULL/*params*/); + dp->w = png_get_image_width(dp->read_pp, dp->ip); + dp->h = png_get_image_height(dp->read_pp, dp->ip); + dp->bpp = png_get_bit_depth(dp->read_pp, dp->ip) * + png_get_channels(dp->read_pp, dp->ip); + dp->size = png_get_rowbytes(dp->read_pp, dp->ip) * dp->h; /* can overflow */ display_clean_read(dp); dp->operation = "none"; } @@ -328,8 +1074,10 @@ display_start_write(struct display *dp, const char *filename) dp->fp = stdout; } + dp->write_size = 0U; + if (dp->fp == NULL) - display_log(dp, APP_ERROR, "%s: file open failed (%s)", dp->output_file, + display_log(dp, USER_ERROR, "%s: file open failed (%s)", dp->output_file, strerror(errno)); } @@ -338,15 +1086,54 @@ write_function(png_structp pp, png_bytep data, png_size_t size) { struct display *dp = get_dp(pp); - if (fwrite(data, size, 1U, dp->fp) != 1U) - display_log(dp, APP_ERROR, "%s: PNG file write failed (%s)", + if (fwrite(data, size, 1U, dp->fp) == 1U) + dp->write_size += size; + + 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 */ +#define SET_COMPRESSION\ + SET(strategy, strategy);\ + SET(level, level);\ + SET(windowBits, window_bits);\ + SET(memLevel, mem_level); + +#ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED +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 +} +#else +# define set_compression(dp, pp) ((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, pp) ((void)0) +#endif /* WRITE_CUSTOMIZE_ZTXT_COMPRESSION */ + static void write_png(struct display *dp, const char *destname) { - dp->operation = "write"; display_clean_write(dp); /* safety */ display_start_write(dp, destname); @@ -368,6 +1155,42 @@ write_png(struct display *dp, const char *destname) 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; + set_compression(dp); + set_text_compression(dp); + + /* filter handling */ +#if defined(PNG_SELECT_FILTER_HEURISTICALLY_SUPPORTED) ||\ + defined(PNG_SELECT_FILTER_METHODICALLY_SUPPORTED) + { + int val; + + if (getopt(dp, "select", &val)) + { +# ifdef PNG_SELECT_FILTER_HEURISTICALLY_SUPPORTED + png_set_option(dp->write_pp, PNG_SELECT_FILTER_HEURISTICALLY, + (val & SELECT_HEURISTICALLY) != 0); +# endif /* SELECT_FILTER_HEURISTICALLY */ +# ifdef PNG_SELECT_FILTER_METHODICALLY_SUPPORTED + png_set_option(dp->write_pp, PNG_SELECT_FILTER_METHODICALLY, + (val & SELECT_METHODICALLY) != 0); +# endif /* SELECT_FILTER_METHODICALLY */ + } + } +#endif /* SELECT_FILTER_HEURISTICALLY || SELECT_FILTER_METHODICALLY */ + +# ifdef PNG_WRITE_FILTER_SUPPORTED + { + int val; + + if (getopt(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. */ png_write_png(dp->write_pp, dp->ip, 0U/*transforms*/, NULL/*params*/); @@ -390,89 +1213,138 @@ write_png(struct display *dp, const char *destname) static void cp_one_file(struct display *dp, const char *filename, const char *destname) { + dp->filename = filename; + dp->operation = "read"; + /* 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"; + + 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; + dp->best[0] = 0; /* acts as a flag for the caller */ write_png(dp, destname); + + if (dp->nsp > 0) /* interating over lists */ + { + char tmpname[(sizeof dp->namebuf) + 4]; + assert(dp->curr[0] == ' ' && dp->tsp > 0); + + /* Loop to find the best option, first initialize the 'best' fields: */ + strcpy(dp->best, dp->curr); + dp->best_size = dp->write_size; + strcpy(tmpname, destname); + strcat(tmpname, ".tmp"); /* space for .tmp allocated above */ + + do + { + write_png(dp, tmpname); + + /* And compare the sizes (theoretically this could overflow, in which + * case this program will need to be rewritten perhaps considerably). + */ + if (dp->write_size < dp->best_size) + { + if (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 (unlink(tmpname) != 0) + display_log(dp, APP_WARNING, "unlink %s failed (%s)", tmpname, + strerror(errno)); + } + while (dp->nsp > 0); + + /* Do this for the 'sizes' option so that it reports the correct size. */ + dp->write_size = dp->best_size; + } } static int -cppng(struct display *dp, const char *file, const char *dest) - /* Exists solely to isolate the setjmp clobbers */ +cppng(struct display *dp, const char *file, const char *gv dest) + /* Exists solely to isolate the setjmp clobbers which some versions of GCC + * erroneously generate. + */ { int ret = setjmp(dp->error_return); if (ret == 0) { + dp->errset = 1; cp_one_file(dp, file, dest); + dp->errset = 0; return 0; } - else if (ret < ERRORS) /* shouldn't longjmp on warnings */ - display_log(dp, INTERNAL_ERROR, "unexpected return code %d", ret); + else + { + dp->errset = 0; - return ret; + if (ret < ERRORS) /* shouldn't longjmp on warnings */ + display_log(dp, INTERNAL_ERROR, "unexpected return code %d", ret); + + return ret; + } } int main(const int argc, const char * const * const argv) { /* For each file on the command line test it with a range of transforms */ - int option_end, ilog = 0; + int option_end; struct display d; display_init(&d); - for (option_end=1; option_end %lu\n", + infile, (unsigned long)d.w, (unsigned long)d.h, d.bpp, + (unsigned long)d.size, (unsigned long)d.results, + (unsigned long)d.read_size, (unsigned long)d.write_size); + + /* This somewhat replicates the above information, but it seems better + * to do both. + */ + if (d.best[0] != 0 && (error_level)(d.options & LEVEL_MASK) < QUIET) + printf("%s [%ld x %ld %d bpp %lu bytes] %lu -> %lu with '%s'\n", + infile, (unsigned long)d.w, (unsigned long)d.h, d.bpp, + (unsigned long)d.size, (unsigned long)d.read_size, + (unsigned long)d.best_size, d.best); + /* Here on any return, including failures, except user/internal issues */ { @@ -506,7 +1393,7 @@ main(const int argc, const char * const * const argv) printf("%s: pngcp", pass ? "PASS" : "FAIL"); - for (j=1; j