| /* |
| * OptiPNG: Advanced PNG optimization program. |
| * http://optipng.sourceforge.net/ |
| * |
| * Copyright (C) 2001-2012 Cosmin Truta and the Contributing Authors. |
| * |
| * This software is distributed under the zlib license. |
| * Please see the attached LICENSE for more information. |
| * |
| * PNG optimization is described in detail in the PNG-Tech article |
| * "A guide to PNG optimization" |
| * http://optipng.sourceforge.net/pngtech/png_optimization.html |
| * |
| * 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. |
| */ |
| |
| #include <ctype.h> |
| #include <errno.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "optipng.h" |
| #include "proginfo.h" |
| |
| #include "cbitset.h" |
| #include "osys.h" |
| #include "png.h" |
| #include "pngxutil.h" |
| #include "zlib.h" |
| |
| |
| static const char *msg_intro = |
| PROGRAM_NAME " version " PROGRAM_VERSION "\n" |
| PROGRAM_COPYRIGHT ".\n"; |
| |
| static const char *msg_license = |
| "This program is open-source software. See LICENSE for more details.\n" |
| "\n" |
| "Portions of this software are based in part on the work of:\n" |
| " Jean-loup Gailly and Mark Adler (zlib)\n" |
| " Glenn Randers-Pehrson and the PNG Development Group (libpng)\n" |
| " Miyasaka Masaru (BMP support)\n" |
| " David Koblas (GIF support)\n"; |
| |
| static const char *msg_help_synopsis = |
| "Synopsis:\n" |
| " optipng [options] files ...\n" |
| "Files:\n" |
| " Image files of type: PNG, BMP, GIF, PNM or TIFF\n"; |
| |
| static const char *msg_help_basic_options = |
| "Basic options:\n" |
| " -?, -h, -help\tshow the extended help\n" |
| " -o <level>\t\toptimization level (0-7)\t\t[default: 2]\n" |
| " -v\t\t\trun in verbose mode / show copyright and version info\n"; |
| |
| static const char *msg_help_options = |
| "Basic options:\n" |
| " -?, -h, -help\tshow this help\n" |
| " -o <level>\t\toptimization level (0-7)\t\t[default: 2]\n" |
| " -v\t\t\trun in verbose mode / show copyright and version info\n" |
| "General options:\n" |
| " -backup, -keep\tkeep a backup of the modified files\n" |
| " -clobber\t\toverwrite existing files\n" |
| #if 0 /* internal */ |
| " -debug\t\tenable debug features\n" |
| #endif |
| " -fix\t\tenable error recovery\n" |
| " -force\t\tenforce writing of a new output file\n" |
| " -preserve\t\tpreserve file attributes if possible\n" |
| " -quiet, -silent\trun in quiet mode\n" |
| " -simulate\t\trun in simulation mode\n" |
| " -out <file>\t\twrite output file to <file>\n" |
| " -dir <directory>\twrite output file(s) to <directory>\n" |
| " -log <file>\t\tlog messages to <file>\n" |
| " --\t\t\tstop option switch parsing\n" |
| "Optimization options:\n" |
| " -f <filters>\tPNG delta filters (0-5)\t\t\t[default: 0,5]\n" |
| " -i <type>\t\tPNG interlace type (0-1)\n" |
| " -zc <levels>\tzlib compression levels (1-9)\t\t[default: 9]\n" |
| " -zm <levels>\tzlib memory levels (1-9)\t\t[default: 8]\n" |
| " -zs <strategies>\tzlib compression strategies (0-3)\t[default: 0-3]\n" |
| " -zw <size>\t\tzlib window size (256,512,1k,2k,4k,8k,16k,32k)\n" |
| " -full\t\tproduce a full report on IDAT (might reduce speed)\n" |
| " -nb\t\t\tno bit depth reduction\n" |
| " -nc\t\t\tno color type reduction\n" |
| " -np\t\t\tno palette reduction\n" |
| " -nx\t\t\tno reductions\n" |
| " -nz\t\t\tno IDAT recoding\n" |
| "Editing options:\n" |
| " -snip\t\tcut one image out of multi-image or animation files\n" |
| " -strip <objects>\tstrip metadata objects (e.g. \"all\")\n" |
| "Optimization levels:\n" |
| " -o0\t\t<=>\t-o1 -nx -nz\t\t\t\t(0 or 1 trials)\n" |
| " -o1\t\t<=>\t-zc9 -zm8 -zs0 -f0\t\t\t(1 trial)\n" |
| " \t\t(or...)\t-zc9 -zm8 -zs1 -f5\t\t\t(1 trial)\n" |
| " -o2\t\t<=>\t-zc9 -zm8 -zs0-3 -f0,5\t\t\t(8 trials)\n" |
| " -o3\t\t<=>\t-zc9 -zm8-9 -zs0-3 -f0,5\t\t(16 trials)\n" |
| " -o4\t\t<=>\t-zc9 -zm8 -zs0-3 -f0-5\t\t\t(24 trials)\n" |
| " -o5\t\t<=>\t-zc9 -zm8-9 -zs0-3 -f0-5\t\t(48 trials)\n" |
| " -o6\t\t<=>\t-zc1-9 -zm8 -zs0-3 -f0-5\t\t(120 trials)\n" |
| " -o7\t\t<=>\t-zc1-9 -zm8-9 -zs0-3 -f0-5\t\t(240 trials)\n" |
| " -o7 -zm1-9\t<=>\t-zc1-9 -zm1-9 -zs0-3 -f0-5\t\t(1080 trials)\n" |
| "Notes:\n" |
| " The combination for -o1 is chosen heuristically.\n" |
| " Exhaustive combinations such as \"-o7 -zm1-9\" are not generally recommended.\n"; |
| |
| static const char *msg_help_examples = |
| "Examples:\n" |
| " optipng file.png\t\t\t\t\t\t(default speed)\n" |
| " optipng -o5 file.png\t\t\t\t\t(slow)\n" |
| " optipng -o7 file.png\t\t\t\t\t(very slow)\n"; |
| |
| static const char *msg_help_more = |
| "Type \"optipng -h\" for extended help.\n"; |
| |
| |
| static enum |
| { |
| OP_RUN, |
| OP_SHOW_HELP, |
| OP_SHOW_VERSION |
| } operation; |
| |
| static struct |
| { |
| int help; |
| int version; |
| } local_options; |
| |
| static struct opng_options options; |
| |
| static FILE *con_file; |
| static FILE *log_file; |
| |
| static int start_of_line; |
| |
| |
| /* |
| * Error handling |
| */ |
| static void |
| error(const char *fmt, ...) |
| { |
| va_list arg_ptr; |
| |
| /* 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); |
| } |
| |
| /* |
| * Panic handling |
| */ |
| static void |
| panic(const char *msg) |
| { |
| /* 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" PROGRAM_URI "\n\n"); |
| fflush(stderr); |
| if (options.debug) |
| { |
| /* Terminate abnormally, possibly with a stack trace or a core dump. */ |
| abort(); |
| } |
| else |
| { |
| /* Terminate abnormally, cleanly. */ |
| osys_terminate(); |
| } |
| } |
| |
| /* |
| * String utility |
| */ |
| static int |
| opng_strcasecmp(const char *str1, const char *str2) |
| { |
| int ch1, ch2; |
| |
| /* Perform a case-insensitive string comparison. */ |
| for ( ; ; ) |
| { |
| ch1 = tolower(*str1++); |
| ch2 = tolower(*str2++); |
| if (ch1 != ch2) |
| return ch1 - ch2; |
| if (ch1 == 0) |
| return 0; |
| } |
| /* FIXME: This function is not MBCS-aware. */ |
| } |
| |
| /* |
| * String utility |
| */ |
| static char * |
| opng_strltrim(const char *str) |
| { |
| /* Skip the leading whitespace characters. */ |
| while (isspace(*str)) |
| ++str; |
| return (char *)str; |
| } |
| |
| /* |
| * String utility |
| */ |
| static char * |
| opng_strtail(const char *str, size_t num) |
| { |
| size_t len; |
| |
| /* Return up to num rightmost characters. */ |
| len = strlen(str); |
| if (len <= num) |
| return (char *)str; |
| return (char *)str + len - num; |
| } |
| |
| /* |
| * String utility |
| */ |
| static char * |
| opng_strpbrk_digit(const char *str) |
| { |
| for ( ; ; ) |
| { |
| if (*str == 0) |
| return NULL; |
| if (isdigit(*str)) |
| return (char *)str; |
| ++str; |
| } |
| } |
| |
| /* |
| * String conversion utility |
| */ |
| static int |
| opng_str2ulong(unsigned long *out_val, const char *in_str, |
| int allow_multiplier) |
| { |
| const char *begin_ptr; |
| char *end_ptr; |
| unsigned long multiplier; |
| |
| /* Extract the value from the string. */ |
| /* Do not allow the minus sign, not even for -0. */ |
| begin_ptr = end_ptr = opng_strltrim(in_str); |
| if (*begin_ptr >= '0' && *begin_ptr <= '9') |
| *out_val = strtoul(begin_ptr, &end_ptr, 10); |
| if (begin_ptr == end_ptr) |
| { |
| errno = EINVAL; /* matching failure */ |
| *out_val = 0; |
| return -1; |
| } |
| |
| if (allow_multiplier) |
| { |
| /* Check for the following SI suffixes: |
| * 'K' or 'k': kibi (1024) |
| * 'M': mebi (1024 * 1024) |
| * 'G': gibi (1024 * 1024 * 1024) |
| */ |
| if (*end_ptr == 'k' || *end_ptr == 'K') |
| { |
| ++end_ptr; |
| multiplier = 1024UL; |
| } |
| else if (*end_ptr == 'M') |
| { |
| ++end_ptr; |
| multiplier = 1024UL * 1024UL; |
| } |
| else if (*end_ptr == 'G') |
| { |
| ++end_ptr; |
| multiplier = 1024UL * 1024UL * 1024UL; |
| } |
| else |
| multiplier = 1; |
| if (multiplier > 1) |
| { |
| if (*out_val > ULONG_MAX / multiplier) |
| { |
| errno = ERANGE; /* overflow */ |
| *out_val = ULONG_MAX; |
| } |
| else |
| *out_val *= multiplier; |
| } |
| } |
| |
| /* Check for trailing garbage. */ |
| if (*opng_strltrim(end_ptr) != 0) |
| { |
| errno = EINVAL; /* garbage in input */ |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* |
| * String conversion utility |
| */ |
| static int |
| opng_rangeset2bitset(bitset_t *out_val, const char *in_str) |
| { |
| size_t end_idx; |
| |
| /* Extract the bitset value from the rangeset string. */ |
| *out_val = rangeset_string_to_bitset(in_str, &end_idx); |
| if (end_idx == 0 || *opng_strltrim(in_str + end_idx) != 0) |
| { |
| errno = EINVAL; |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* |
| * Command line utility |
| */ |
| static void |
| err_option_arg(const char *opt, const char *opt_arg) |
| { |
| /* Issue an error regarding the incorrect value of the option argument. */ |
| if (opt_arg == NULL || *opng_strltrim(opt_arg) == 0) |
| error("Missing argument for option %s", opt); |
| else |
| error("Invalid argument for option %s: %s", opt, opt_arg); |
| } |
| |
| /* |
| * Command line utility |
| */ |
| static int |
| check_num_option(const char *opt, const char *opt_arg, |
| int lowest, int highest) |
| { |
| unsigned long value; |
| |
| /* Extract the numeric value from the option argument. */ |
| if (opng_str2ulong(&value, opt_arg, 0) != 0 || |
| value > INT_MAX || (int)value < lowest || (int)value > highest) |
| err_option_arg(opt, opt_arg); |
| return (int)value; |
| } |
| |
| /* |
| * Command line utility |
| */ |
| static int |
| check_power2_option(const char *opt, const char *opt_arg, |
| int lowest, int highest) |
| { |
| unsigned long value; |
| int result; |
| |
| /* Extract the exact log2 of the numeric value from the option argument. */ |
| /* Allow the 'k', 'M', 'G' suffixes. */ |
| if (opng_str2ulong(&value, opt_arg, 1) == 0) |
| { |
| if (lowest < 0) |
| lowest = 0; |
| if (highest > (int)(CHAR_BIT * sizeof(long) - 2)) |
| highest = (int)(CHAR_BIT * sizeof(long) - 2); |
| for (result = lowest; result <= highest; ++result) |
| { |
| if ((1UL << result) == value) |
| return result; |
| } |
| } |
| err_option_arg(opt, opt_arg); |
| return -1; |
| } |
| |
| /* |
| * Command line utility |
| */ |
| static bitset_t |
| check_rangeset_option(const char *opt, const char *opt_arg, |
| bitset_t result_mask) |
| { |
| bitset_t result; |
| |
| /* Extract the rangeset from the option argument. */ |
| if (opng_rangeset2bitset(&result, opt_arg) == 0) |
| result &= result_mask; |
| else |
| result = BITSET_EMPTY; |
| if (result == BITSET_EMPTY) |
| err_option_arg(opt, opt_arg); |
| return result; |
| } |
| |
| /* |
| * Command line utility |
| */ |
| static void |
| check_obj_option(const char *opt, const char *opt_arg) |
| { |
| unsigned int i; |
| |
| if (strcmp("all", opt_arg) == 0) |
| return; |
| |
| /* Issue an error about the unrecognized option argument. */ |
| /* Make it specific on whether this argument is a chunk name. */ |
| for (i = 0; i < 4; ++i) |
| { |
| /* Do not use isalpha(), because it is locale-dependent. */ |
| if (!((opt_arg[i] >= 'A' && opt_arg[i] <= 'Z') || |
| (opt_arg[i] >= 'a' && opt_arg[i] <= 'z'))) |
| break; |
| } |
| if (i == 4 && opt_arg[i] == 0) |
| error("Manipulation of individual chunks is not implemented"); |
| else |
| err_option_arg(opt, opt_arg); |
| } |
| |
| /* |
| * Command line parsing |
| */ |
| static int |
| scan_option(const char *str, |
| char opt_buf[], size_t opt_buf_size, const char **opt_arg_ptr) |
| { |
| const char *ptr; |
| unsigned int opt_len; |
| |
| /* 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; |
| for ( ; ; ) |
| { |
| if (opt_len < opt_buf_size) /* truncate "-verylongoption" */ |
| opt_buf[opt_len] = (char)tolower(*ptr); |
| ++opt_len; |
| ++ptr; |
| if (*ptr == 0 || isspace(*ptr)) /* "-option" or "-option arg" */ |
| { |
| while (isspace(*ptr)) |
| ++ptr; |
| *opt_arg_ptr = (*ptr != 0) ? ptr : NULL; |
| break; |
| } |
| if (*ptr == '=') /* "-option=arg" */ |
| { |
| ++ptr; |
| *opt_arg_ptr = ptr; |
| break; |
| } |
| } |
| |
| /* 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'; |
| } |
| return 1; |
| } |
| |
| /* |
| * Command line parsing |
| */ |
| static void |
| parse_args(int argc, char *argv[]) |
| { |
| char *arg; |
| char opt[16]; |
| size_t opt_len; |
| const char *xopt; |
| int simple_opt, stop_switch; |
| bitset_t set; |
| int val; |
| unsigned int file_count; |
| int i; |
| |
| /* 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() */ |
| } |
| opt_len = strlen(opt); |
| |
| /* Prevent process_files() from seeing this arg. */ |
| argv[i] = NULL; |
| |
| /* Normalize the options that allow juxtaposed arguments. */ |
| if ((strchr("fio", opt[0]) != NULL && isdigit(opt[1])) || |
| (opt[0] == 'z' && isalpha(opt[1]) && isdigit(opt[2]))) |
| { |
| /* -f0-5 <=> -f=0-5; -i1 <=> -i=1; -o3 <=> -o=3; |
| * -zc3-9 <=> -zc=3-9; etc. |
| */ |
| opt_len = (size_t)(opng_strpbrk_digit(opt) - opt); |
| opt[opt_len] = '\0'; |
| xopt = opng_strpbrk_digit(arg); |
| } |
| |
| /* Check the simple options (without option arguments). */ |
| simple_opt = 1; |
| if (strcmp("-", opt) == 0) |
| { |
| /* -- */ |
| stop_switch = 1; |
| } |
| else if (strcmp("?", opt) == 0 || |
| strncmp("help", opt, opt_len) == 0) |
| { |
| /* -? | -h | ... | -help */ |
| local_options.help = 1; |
| } |
| else if ((strncmp("backup", opt, opt_len) == 0) || |
| (strncmp("keep", opt, opt_len) == 0)) |
| { |
| /* -b | ... | -backup | -k | ... | -keep */ |
| options.backup = 1; |
| } |
| else if (strncmp("clobber", opt, opt_len) == 0) |
| { |
| /* -c | ... | -clobber */ |
| options.clobber = 1; |
| } |
| else if (strcmp("debug", opt) == 0) |
| { |
| /* -debug */ |
| /* Do not abbreviate this internal option. */ |
| options.debug = 1; |
| } |
| else if (strncmp("fix", opt, opt_len) == 0 && opt_len >= 2) |
| { |
| /* -fi | -fix */ |
| options.fix = 1; |
| } |
| else if (strncmp("force", opt, opt_len) == 0 && opt_len >= 2) |
| { |
| /* -fo | ... | -force */ |
| options.force = 1; |
| } |
| else if (strncmp("full", opt, opt_len) == 0 && opt_len >= 2) |
| { |
| /* -fu | ... | -full */ |
| options.full = 1; |
| } |
| else if (strcmp("nb", opt) == 0) |
| { |
| /* -nb */ |
| options.nb = 1; |
| } |
| else if (strcmp("nc", opt) == 0) |
| { |
| /* -nc */ |
| options.nc = 1; |
| } |
| else if (strcmp("np", opt) == 0) |
| { |
| /* -np */ |
| options.np = 1; |
| } |
| else if (strcmp("nx", opt) == 0) |
| { |
| /* -nx */ |
| options.nb = options.nc = options.np = 1; |
| /* options.nm = 1; */ |
| } |
| else if (strcmp("nz", opt) == 0) |
| { |
| /* -nz */ |
| options.nz = 1; |
| } |
| else if (strncmp("preserve", opt, opt_len) == 0) |
| { |
| /* -p | ... | -preserve */ |
| options.preserve = 1; |
| } |
| else if ((strncmp("quiet", opt, opt_len) == 0) || |
| (strncmp("silent", opt, opt_len) == 0 && opt_len >= 3)) |
| { |
| /* -q | ... | -quiet | -sil | ... | -silent */ |
| options.quiet = 1; |
| } |
| else if (strncmp("simulate", opt, opt_len) == 0 && opt_len >= 3) |
| { |
| /* -sim | ... | -simulate */ |
| options.simulate = 1; |
| } |
| else if (strncmp("snip", opt, opt_len) == 0 && opt_len >= 2) |
| { |
| /* -sn | ... | -snip */ |
| options.snip = 1; |
| } |
| else if (strcmp("v", opt) == 0) |
| { |
| /* -v */ |
| options.verbose = 1; |
| local_options.version = 1; |
| } |
| else if (strncmp("verbose", opt, opt_len) == 0 && opt_len >= 4) |
| { |
| /* -verb | ... | -verbose */ |
| options.verbose = 1; |
| } |
| else if (strncmp("version", opt, opt_len) == 0 && opt_len >= 4) |
| { |
| /* -vers | ... | -version */ |
| local_options.version = 1; |
| } |
| else /* possibly an option with an argument */ |
| { |
| simple_opt = 0; |
| if (xopt == NULL) |
| { |
| if (++i < argc) |
| { |
| xopt = argv[i]; |
| /* Prevent process_files() from seeing this xopt. */ |
| argv[i] = NULL; |
| } |
| else |
| { |
| /* Last option in command line; assume an empty xopt. */ |
| xopt = ""; |
| } |
| } |
| } |
| |
| /* Check the options that have option arguments. */ |
| if (simple_opt) |
| { |
| if (xopt != NULL) |
| error("No argument allowed for option: %s", arg); |
| } |
| else if (strcmp("o", opt) == 0) |
| { |
| /* -o NUM */ |
| val = check_num_option("-o", xopt, 0, INT_MAX); |
| 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("i", opt) == 0) |
| { |
| /* -i NUM */ |
| val = check_num_option("-i", xopt, 0, 1); |
| if (options.interlace < 0) |
| options.interlace = val; |
| else if (options.interlace != val) |
| error("Multiple interlace types are not permitted"); |
| } |
| else if (strcmp("f", opt) == 0) |
| { |
| /* -f SET */ |
| set = check_rangeset_option("-f", xopt, OPNG_FILTER_SET_MASK); |
| options.filter_set |= set; |
| } |
| else if (strcmp("zc", opt) == 0) |
| { |
| /* -zc SET */ |
| set = check_rangeset_option("-zc", xopt, OPNG_COMPR_LEVEL_SET_MASK); |
| options.compr_level_set |= set; |
| } |
| else if (strcmp("zm", opt) == 0) |
| { |
| /* -zm SET */ |
| set = check_rangeset_option("-zm", xopt, OPNG_MEM_LEVEL_SET_MASK); |
| options.mem_level_set |= set; |
| } |
| else if (strcmp("zs", opt) == 0) |
| { |
| /* -zs SET */ |
| set = check_rangeset_option("-zs", xopt, OPNG_STRATEGY_SET_MASK); |
| options.strategy_set |= set; |
| } |
| else if (strcmp("zw", opt) == 0) |
| { |
| /* -zw NUM */ |
| val = check_power2_option("-zw", xopt, 8, 15); |
| if (options.window_bits == 0) |
| options.window_bits = val; |
| else if (options.window_bits != val) |
| error("Multiple window sizes are not permitted"); |
| } |
| else if (strncmp("strip", opt, opt_len) == 0 && opt_len >= 2) |
| { |
| /* -st OBJ | ... | -strip OBJ */ |
| check_obj_option("-strip", xopt); |
| options.strip_all = 1; |
| } |
| else if (strncmp("out", opt, opt_len) == 0 && opt_len >= 2) |
| { |
| /* -ou PATH | -out PATH */ |
| if (options.out_name != NULL) |
| error("Multiple output file names are not permitted"); |
| if (xopt[0] == 0) |
| err_option_arg("-out", NULL); |
| options.out_name = xopt; |
| } |
| else if (strncmp("dir", opt, opt_len) == 0) |
| { |
| /* -d PATH | ... | -dir PATH */ |
| if (options.dir_name != NULL) |
| error("Multiple output dir names are not permitted"); |
| if (xopt[0] == 0) |
| err_option_arg("-dir", NULL); |
| options.dir_name = xopt; |
| } |
| else if (strncmp("log", opt, opt_len) == 0) |
| { |
| /* -l PATH | ... | -log PATH */ |
| if (options.log_name != NULL) |
| error("Multiple log file names are not permitted"); |
| if (xopt[0] == 0) |
| err_option_arg("-log", NULL); |
| options.log_name = xopt; |
| } |
| else |
| { |
| error("Unrecognized option: %s", arg); |
| } |
| } |
| |
| /* Finalize. */ |
| if (options.out_name != NULL) |
| { |
| if (file_count > 1) |
| error("The option -out requires one input file"); |
| if (options.dir_name != NULL) |
| error("The options -out and -dir are mutually exclusive"); |
| } |
| if (options.log_name != NULL) |
| { |
| if (opng_strcasecmp(".log", opng_strtail(options.log_name, 4)) != 0) |
| error("To prevent accidental data corruption, " |
| "the log file name must end with \".log\""); |
| } |
| if (local_options.help) |
| operation = OP_SHOW_HELP; |
| else if (file_count != 0) |
| operation = OP_RUN; |
| else if (local_options.version) |
| operation = OP_SHOW_VERSION; |
| else |
| operation = OP_SHOW_HELP; |
| } |
| |
| /* |
| * Application-defined printf callback |
| */ |
| static void |
| app_printf(const char *fmt, ...) |
| { |
| va_list arg_ptr; |
| |
| if (fmt[0] == 0) |
| return; |
| start_of_line = (fmt[strlen(fmt) - 1] == '\n') ? 1 : 0; |
| |
| if (con_file != NULL) |
| { |
| va_start(arg_ptr, fmt); |
| vfprintf(con_file, fmt, arg_ptr); |
| va_end(arg_ptr); |
| } |
| if (log_file != NULL) |
| { |
| va_start(arg_ptr, fmt); |
| vfprintf(log_file, fmt, arg_ptr); |
| va_end(arg_ptr); |
| } |
| } |
| |
| /* |
| * Application-defined control print callback |
| */ |
| static void |
| app_print_cntrl(int cntrl_code) |
| { |
| const char *con_str, *log_str; |
| int i; |
| |
| if (cntrl_code == '\r') |
| { |
| /* CR: reset line in console, new line in log file. */ |
| con_str = "\r"; |
| log_str = "\n"; |
| start_of_line = 1; |
| } |
| else if (cntrl_code == '\v') |
| { |
| /* VT: new line if current line is not empty, nothing otherwise. */ |
| if (!start_of_line) |
| { |
| con_str = log_str = "\n"; |
| start_of_line = 1; |
| } |
| else |
| con_str = log_str = ""; |
| } |
| else if (cntrl_code < 0 && cntrl_code > -80 && start_of_line) |
| { |
| /* Minus N: erase first N characters from line, in console only. */ |
| if (con_file != NULL) |
| { |
| for (i = 0; i > cntrl_code; --i) |
| fputc(' ', con_file); |
| } |
| con_str = "\r"; |
| log_str = ""; |
| } |
| else |
| { |
| /* Unhandled control code (due to internal error): show err marker. */ |
| con_str = log_str = "<?>"; |
| } |
| |
| if (con_file != NULL) |
| fputs(con_str, con_file); |
| if (log_file != NULL) |
| fputs(log_str, log_file); |
| } |
| |
| /* |
| * Application-defined progress update callback |
| */ |
| static void |
| app_progress(unsigned long current_step, unsigned long total_steps) |
| { |
| /* There will be a potentially long wait, so flush the console output. */ |
| if (con_file != NULL) |
| fflush(con_file); |
| /* An eager flush of the line-buffered log file is not very important. */ |
| |
| /* A GUI application would normally update a progress bar. */ |
| /* Here we ignore the progress info. */ |
| if (current_step && total_steps) |
| return; |
| } |
| |
| /* |
| * Application initialization |
| */ |
| static void |
| app_init(void) |
| { |
| start_of_line = 1; |
| |
| if (operation == OP_SHOW_HELP || operation == OP_SHOW_VERSION) |
| con_file = stdout; |
| else if (!options.quiet) |
| con_file = stderr; |
| else |
| con_file = NULL; |
| |
| if (options.log_name != NULL) |
| { |
| /* Open the log file, line-buffered. */ |
| if ((log_file = fopen(options.log_name, "a")) == NULL) |
| error("Can't open log file: %s\n", options.log_name); |
| setvbuf(log_file, NULL, _IOLBF, BUFSIZ); |
| app_printf("** Warning: %s\n\n", |
| "The option -log is deprecated; use shell redirection"); |
| } |
| } |
| |
| /* |
| * Application finalization |
| */ |
| static void |
| app_finish(void) |
| { |
| if (log_file != NULL) |
| { |
| /* Close the log file. */ |
| fclose(log_file); |
| } |
| } |
| |
| /* |
| * File list processing |
| */ |
| static int |
| process_files(int argc, char *argv[]) |
| { |
| int result; |
| struct opng_ui ui; |
| int i; |
| |
| /* Initialize the optimization engine. */ |
| ui.printf_fn = app_printf; |
| ui.print_cntrl_fn = app_print_cntrl; |
| 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"); |
| |
| return result; |
| } |
| |
| /* |
| * main |
| */ |
| int |
| main(int argc, char *argv[]) |
| { |
| int result; |
| |
| /* Parse the user options and initialize the application. */ |
| parse_args(argc, argv); |
| app_init(); |
| result = EXIT_SUCCESS; |
| |
| if (local_options.version) |
| { |
| /* Print the copyright and version info. */ |
| app_printf("%s\n", msg_intro); |
| } |
| |
| switch (operation) |
| { |
| case OP_RUN: |
| /* Run the application. */ |
| result = process_files(argc, argv); |
| break; |
| case OP_SHOW_HELP: |
| if (local_options.help) |
| { |
| /* Print the extended help text. */ |
| app_printf("%s%s%s", |
| msg_help_synopsis, |
| msg_help_options, |
| msg_help_examples); |
| } |
| else |
| { |
| /* Print the basic help text. */ |
| app_printf("%s%s%s%s", |
| msg_help_synopsis, |
| msg_help_basic_options, |
| msg_help_examples, |
| msg_help_more); |
| } |
| break; |
| case OP_SHOW_VERSION: |
| /* Print the licensing terms and the extended version info. */ |
| app_printf("%s\n", msg_license); |
| app_printf("Using libpng version %s and zlib version %s\n", |
| png_get_libpng_ver(NULL), zlibVersion()); |
| break; |
| default: |
| result = -1; |
| } |
| |
| /* Finalize the application. */ |
| app_finish(); |
| return result; |
| } |