mirror of
				https://git.code.sf.net/p/libpng/code.git
				synced 2025-07-10 18:04:09 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			2497 lines
		
	
	
		
			74 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2497 lines
		
	
	
		
			74 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* pngcp.c
 | |
|  *
 | |
|  * Copyright (c) 2016,2022 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 <config.h>
 | |
| #endif
 | |
| 
 | |
| #include <stdio.h>
 | |
| 
 | |
| /* Define the following to use this test against your installed libpng, rather
 | |
|  * than the one being built here:
 | |
|  */
 | |
| #ifdef PNG_FREESTANDING_TESTS
 | |
| #  include <png.h>
 | |
| #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 <stdarg.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <errno.h>
 | |
| #include <limits.h>
 | |
| #include <assert.h>
 | |
| 
 | |
| #include <unistd.h>
 | |
| #include <sys/stat.h>
 | |
| 
 | |
| #include <zlib.h>
 | |
| 
 | |
| #ifndef PNG_SETJMP_SUPPORTED
 | |
| #  include <setjmp.h> /* because png.h did *not* include this */
 | |
| #endif
 | |
| 
 | |
| #ifdef __cplusplus
 | |
| #  define voidcast(type, value) static_cast<type>(value)
 | |
| #else
 | |
| #  define voidcast(type, value) (value)
 | |
| #endif /* __cplusplus */
 | |
| 
 | |
| #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
 | |
| 
 | |
| /* '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 <time.h>   /* 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<<error_level.  A pass requires either nothing worse than
 | |
|  * warnings (--relaxes) or nothing worse than information (--strict)
 | |
|  */
 | |
| #define RESULT_STRICT(r)   (((r) & ~((1U<<WARNINGS)-1)) == 0)
 | |
| #define RESULT_RELAXED(r)  (((r) & ~((1U<<ERRORS)-1)) == 0)
 | |
| 
 | |
| /* OPTION DEFINITIONS */
 | |
| static const char range_lo[] = "low";
 | |
| static const char range_hi[] = "high";
 | |
| static const char all[] = "all";
 | |
| #define RANGE(lo,hi) { range_lo, lo }, { range_hi, hi }
 | |
| typedef struct value_list
 | |
| {
 | |
|    const char *name;  /* the command line name of the value */
 | |
|    int         value; /* the actual value to use */
 | |
| }  value_list;
 | |
| 
 | |
| static const value_list
 | |
| #ifdef PNG_SW_COMPRESS_png_level
 | |
| vl_compression[] =
 | |
| {
 | |
|    /* Overall compression control.  The order controls the search order for
 | |
|     * 'all'.  Since the search is for the smallest the order used is low memory
 | |
|     * then high speed.
 | |
|     */
 | |
|    { "low-memory",      PNG_COMPRESSION_LOW_MEMORY },
 | |
|    { "high-speed",      PNG_COMPRESSION_HIGH_SPEED },
 | |
|    { "high-read-speed", PNG_COMPRESSION_HIGH_READ_SPEED },
 | |
|    { "low",             PNG_COMPRESSION_LOW },
 | |
|    { "medium",          PNG_COMPRESSION_MEDIUM },
 | |
|    { "old",             PNG_COMPRESSION_COMPAT },
 | |
|    { "high",            PNG_COMPRESSION_HIGH },
 | |
|    { all, 0 }
 | |
| },
 | |
| #endif /* SW_COMPRESS_png_level */
 | |
| 
 | |
| #if defined(PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED) ||\
 | |
|     defined(PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED)
 | |
| vl_strategy[] =
 | |
| {
 | |
|    /* This controls the order of search. */
 | |
|    { "huffman", Z_HUFFMAN_ONLY },
 | |
|    { "RLE", Z_RLE },
 | |
|    { "fixed", Z_FIXED }, /* the remainder do window searches */
 | |
|    { "filtered", Z_FILTERED },
 | |
|    { "default", Z_DEFAULT_STRATEGY },
 | |
|    { all, 0 }
 | |
| },
 | |
| #ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED
 | |
| vl_windowBits_text[] =
 | |
| {
 | |
|    { "default", MAX_WBITS/*from zlib*/ },
 | |
|    { "minimum", 8 },
 | |
|    RANGE(8, MAX_WBITS/*from zlib*/),
 | |
|    { all, 0 }
 | |
| },
 | |
| #endif /* text compression */
 | |
| vl_level[] =
 | |
| {
 | |
|    { "default", Z_DEFAULT_COMPRESSION /* this is -1 */ },
 | |
|    { "none", Z_NO_COMPRESSION },
 | |
|    { "speed", Z_BEST_SPEED },
 | |
|    { "best", Z_BEST_COMPRESSION },
 | |
|    { "0", Z_NO_COMPRESSION },
 | |
|    RANGE(1, 9), /* this deliberately excludes '0' */
 | |
|    { all, 0 }
 | |
| },
 | |
| vl_memLevel[] =
 | |
| {
 | |
|    { "max", MAX_MEM_LEVEL }, /* zlib maximum */
 | |
|    { "1", 1 }, /* zlib minimum */
 | |
|    { "default", 8 }, /* zlib default */
 | |
|    { "2", 2 },
 | |
|    { "3", 3 },
 | |
|    { "4", 4 },
 | |
|    { "5", 5 }, /* for explicit testing */
 | |
|    RANGE(6, MAX_MEM_LEVEL/*zlib*/), /* exclude 5 and below: zlib bugs */
 | |
|    { all, 0 }
 | |
| },
 | |
| #endif /* WRITE_CUSTOMIZE_*COMPRESSION */
 | |
| #ifdef PNG_WRITE_FILTER_SUPPORTED
 | |
| vl_filter[] =
 | |
| {
 | |
|    { all,      PNG_ALL_FILTERS   },
 | |
|    { "off",    PNG_NO_FILTERS    },
 | |
|    { "none",   PNG_FILTER_NONE   },
 | |
|    { "sub",    PNG_FILTER_SUB    },
 | |
|    { "up",     PNG_FILTER_UP     },
 | |
|    { "avg",    PNG_FILTER_AVG    },
 | |
|    { "paeth",  PNG_FILTER_PAETH  }
 | |
| },
 | |
| #endif /* WRITE_FILTER */
 | |
| #ifdef PNG_PNGCP_TIMING_SUPPORTED
 | |
| #  define PNGCP_TIME_READ  1
 | |
| #  define PNGCP_TIME_WRITE 2
 | |
| vl_time[] =
 | |
| {
 | |
|    { "both",  PNGCP_TIME_READ+PNGCP_TIME_WRITE },
 | |
|    { "off",   0 },
 | |
|    { "read",  PNGCP_TIME_READ },
 | |
|    { "write", PNGCP_TIME_WRITE }
 | |
| },
 | |
| #endif /* PNGCP_TIMING */
 | |
| vl_IDAT_size[] = /* for png_set_IDAT_size */
 | |
| {
 | |
|    { "default", 0x7FFFFFFF },
 | |
|    { "minimal", 1 },
 | |
|    RANGE(1, 0x7FFFFFFF)
 | |
| },
 | |
| #ifndef PNG_SW_IDAT_size
 | |
|    /* Pre 1.7 API: */
 | |
| #  define png_set_IDAT_size(p,v) png_set_compression_buffer_size(p, v)
 | |
| #endif /* !SW_IDAT_size */
 | |
| #define SL 8 /* stack limit in display, below */
 | |
| vl_log_depth[] = { { "on", 1 }, { "off", 0 }, RANGE(0, SL) },
 | |
| vl_on_off[] = { { "on", 1 }, { "off", 0 } };
 | |
| 
 | |
| #ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED
 | |
| static value_list
 | |
| vl_windowBits_IDAT[] =
 | |
| {
 | |
|    { "default", MAX_WBITS },
 | |
|    { "small", 9 },
 | |
|    RANGE(8, MAX_WBITS), /* modified by set_windowBits_hi */
 | |
|    { all, 0 }
 | |
| };
 | |
| #endif /* IDAT compression */
 | |
| 
 | |
| typedef struct option
 | |
| {
 | |
|    const char       *name;         /* name of the option */
 | |
|    png_uint_32       opt;          /* an option, or OPTION or LIST */
 | |
|    png_byte          search;       /* Search on --search */
 | |
|    png_byte          value_count;  /* length of the list of values: */
 | |
|    const value_list *values;       /* values for OPTION or LIST */
 | |
| }  option;
 | |
| 
 | |
| static const option options[] =
 | |
| {
 | |
|    /* struct display options, these are set when the command line is read */
 | |
| #  define S(n,v) { #n, v, 0, 2, vl_on_off },
 | |
|    S(verbose,  VERBOSE)
 | |
|    S(warnings, WARNINGS)
 | |
|    S(errors,   ERRORS)
 | |
|    S(quiet,    QUIET)
 | |
|    S(strict,   STRICT)
 | |
|    S(log,      LOG)
 | |
|    S(continue, CONTINUE)
 | |
|    S(sizes,    SIZES)
 | |
|    S(search,   SEARCH)
 | |
|    S(nowrite,  NOWRITE)
 | |
| #  ifdef IGNORE_INDEX
 | |
|       S(ignore-palette-index, IGNORE_INDEX)
 | |
| #  endif /* IGNORE_INDEX */
 | |
| #  ifdef FIX_INDEX
 | |
|       S(fix-palette-index, FIX_INDEX)
 | |
| #  endif /* FIX_INDEX */
 | |
| #  undef S
 | |
| 
 | |
|    /* OPTION settings, these and LIST settings are read on demand */
 | |
| #  define VLNAME(name) vl_ ## name
 | |
| #  define VLSIZE(name) voidcast(png_byte,\
 | |
|                            (sizeof VLNAME(name))/(sizeof VLNAME(name)[0]))
 | |
| #  define VL(oname, name, type, search)\
 | |
|    { oname, type, search, VLSIZE(name), VLNAME(name) },
 | |
| #  define VLO(oname, name, search) VL(oname, name, OPTION, search)
 | |
| 
 | |
| #  ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED
 | |
| #     define VLCIDAT(name) VLO(#name, name, 1/*search*/)
 | |
| #     ifdef PNG_SW_COMPRESS_level
 | |
| #        define VLCiCCP(name) VLO("ICC-profile-" #name, name, 0/*search*/)
 | |
| #     else
 | |
| #        define VLCiCCP(name)
 | |
| #     endif
 | |
| #  else
 | |
| #     define VLCIDAT(name)
 | |
| #     define VLCiCCP(name)
 | |
| #  endif /* WRITE_CUSTOMIZE_COMPRESSION */
 | |
| 
 | |
| #  ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED
 | |
| #     define VLCzTXt(name) VLO("text-" #name, name, 0/*search*/)
 | |
| #  else
 | |
| #     define VLCzTXt(name)
 | |
| #  endif /* WRITE_CUSTOMIZE_ZTXT_COMPRESSION */
 | |
| 
 | |
| #  define VLC(name) VLCIDAT(name) VLCiCCP(name) VLCzTXt(name)
 | |
| 
 | |
| #  ifdef PNG_SW_COMPRESS_png_level
 | |
|       /* The libpng compression level isn't searched because it just sets the
 | |
|        * other things that are searched!
 | |
|        */
 | |
|       VLO("compression", compression, 0)
 | |
|       VLO("text-compression", compression, 0)
 | |
|       VLO("ICC-profile-compression", compression, 0)
 | |
| #  endif /* SW_COMPRESS_png_level */
 | |
|    VLC(strategy)
 | |
|    VLO("windowBits", windowBits_IDAT, 1)
 | |
| #  ifdef PNG_SW_COMPRESS_windowBits
 | |
|       VLO("ICC-profile-windowBits", windowBits_text/*sic*/, 0)
 | |
| #  endif
 | |
|    VLO("text-windowBits", windowBits_text, 0)
 | |
|    VLC(level)
 | |
|    VLC(memLevel)
 | |
|    VLO("IDAT-size", IDAT_size, 0)
 | |
|    VLO("log-depth", log_depth, 0)
 | |
| 
 | |
| #  undef VLO
 | |
| 
 | |
|    /* LIST settings */
 | |
| #  define VLL(name, search) VL(#name, name, LIST, search)
 | |
| #ifdef PNG_WRITE_FILTER_SUPPORTED
 | |
|    VLL(filter, 0)
 | |
| #endif /* WRITE_FILTER */
 | |
| #ifdef PNG_PNGCP_TIMING_SUPPORTED
 | |
|    VLL(time, 0)
 | |
| #endif /* PNGCP_TIMING */
 | |
| #  undef VLL
 | |
| #  undef VL
 | |
| };
 | |
| 
 | |
| #ifdef __cplusplus
 | |
|    static const size_t option_count((sizeof options)/(sizeof options[0]));
 | |
| #else /* !__cplusplus */
 | |
| #  define option_count ((sizeof options)/(sizeof options[0]))
 | |
| #endif /* !__cplusplus */
 | |
| 
 | |
| static const char *
 | |
| cts(int ct)
 | |
| {
 | |
|    switch (ct)
 | |
|    {
 | |
|       case PNG_COLOR_TYPE_PALETTE:     return "P";
 | |
|       case PNG_COLOR_TYPE_GRAY:        return "G";
 | |
|       case PNG_COLOR_TYPE_GRAY_ALPHA:  return "GA";
 | |
|       case PNG_COLOR_TYPE_RGB:         return "RGB";
 | |
|       case PNG_COLOR_TYPE_RGB_ALPHA:   return "RGBA";
 | |
|       default:                         return "INVALID";
 | |
|    }
 | |
| }
 | |
| 
 | |
| struct display
 | |
| {
 | |
|    jmp_buf          error_return;      /* Where to go to on error */
 | |
|    unsigned int     errset;            /* error_return is set */
 | |
| 
 | |
|    const char      *operation;         /* What is happening */
 | |
|    const char      *filename;          /* The name of the original file */
 | |
|    const char      *output_file;       /* The name of the output file */
 | |
| 
 | |
|    /* Used on both read and write: */
 | |
|    FILE            *fp;
 | |
| 
 | |
|    /* Used on a read, both the original read and when validating a written
 | |
|     * image.
 | |
|     */
 | |
|    png_alloc_size_t read_size;
 | |
|    png_structp      read_pp;
 | |
|    png_infop        ip;
 | |
| #  if PNG_LIBPNG_VER < 10700 && defined PNG_TEXT_SUPPORTED
 | |
|       png_textp     text_ptr; /* stash of text chunks */
 | |
|       int           num_text;
 | |
|       int           text_stashed;
 | |
| #  endif /* pre 1.7 */
 | |
| 
 | |
| #  ifdef PNG_PNGCP_TIMING_SUPPORTED
 | |
|       struct timespec   read_time;
 | |
|       struct timespec   read_time_total;
 | |
|       struct timespec   write_time;
 | |
|       struct timespec   write_time_total;
 | |
| #  endif /* PNGCP_TIMING */
 | |
| 
 | |
|    /* Used to write a new image (the original info_ptr is used) */
 | |
| #  define MAX_SIZE ((png_alloc_size_t)(-1))
 | |
|    png_alloc_size_t write_size;
 | |
|    png_alloc_size_t best_size;
 | |
|    png_structp      write_pp;
 | |
| 
 | |
|    /* Base file information */
 | |
|    png_alloc_size_t size;
 | |
|    png_uint_32      w;
 | |
|    png_uint_32      h;
 | |
|    int              bpp;
 | |
|    png_byte         ct;
 | |
|    int              no_warnings;       /* Do not output libpng warnings */
 | |
|    int              min_windowBits;    /* The windowBits range is 8..8 */
 | |
| 
 | |
|    /* Options handling */
 | |
|    png_uint_32      results;             /* A mask of errors seen */
 | |
|    png_uint_32      options;             /* See display_log below */
 | |
|    png_byte         entry[option_count]; /* The selected entry+1 of an option
 | |
|                                           * that appears on the command line, or
 | |
|                                           * 0 if it was not given. */
 | |
|    int              value[option_count]; /* Corresponding value */
 | |
| 
 | |
|    /* Compression exhaustive testing */
 | |
|    /* Temporary variables used only while testing a single collection of
 | |
|     * settings:
 | |
|     */
 | |
|    unsigned int     csp;               /* next stack entry to use */
 | |
|    unsigned int     nsp;               /* highest active entry+1 found so far */
 | |
| 
 | |
|    /* Values used while iterating through all the combinations of settings for a
 | |
|     * single file:
 | |
|     */
 | |
|    unsigned int     tsp;               /* nsp from the last run; this is the
 | |
|                                         * index+1 of the highest active entry on
 | |
|                                         * this run; this entry will be advanced.
 | |
|                                         */
 | |
|    int              opt_string_start;  /* Position in buffer for the first
 | |
|                                         * searched option; non-zero if earlier
 | |
|                                         * options were set on the command line.
 | |
|                                         */
 | |
|    struct stack
 | |
|    {
 | |
|       png_alloc_size_t best_size;      /* Best so far for this option */
 | |
|       png_alloc_size_t lo_size;
 | |
|       png_alloc_size_t hi_size;
 | |
|       int              lo, hi;         /* For binary chop of a range */
 | |
|       int              best_val;       /* Best value found so far */
 | |
|       int              opt_string_end; /* End of the option string in 'curr' */
 | |
|       png_byte         opt;            /* The option being tested */
 | |
|       png_byte         entry;          /* The next value entry to be tested */
 | |
|       png_byte         end;            /* This is the last entry */
 | |
|    }                stack[SL];         /* Stack of entries being tested */
 | |
|    char             curr[32*SL];       /* current options being tested */
 | |
|    char             best[32*SL];       /* best options */
 | |
| 
 | |
|    char             namebuf[FILENAME_MAX]; /* output file name */
 | |
| };
 | |
| 
 | |
| static void
 | |
| display_init(struct display *dp)
 | |
|    /* Call this only once right at the start to initialize the control
 | |
|     * structure, the (struct buffer) lists are maintained across calls - the
 | |
|     * memory is not freed.
 | |
|     */
 | |
| {
 | |
|    memset(dp, 0, sizeof *dp);
 | |
|    dp->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 : "<stdin>", 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)
 | |
|          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; j<option_count; ++j)
 | |
|       if (strncmp(options[j].name, opt, len) == 0 && options[j].name[len] == 0)
 | |
|          return j;
 | |
| 
 | |
|    /* If the setjmp buffer is set the code is asking for an option index; this
 | |
|     * is bad.  Otherwise this is the command line option parsing.
 | |
|     */
 | |
|    display_log(dp, dp->errset ? 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 = "<stdin>";
 | |
|       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; i<num; ++i)
 | |
|             newpal[i] = palette[i];
 | |
| 
 | |
|          /* Fill in any remainder with a warning color: */
 | |
|          for (; i<=max; ++i)
 | |
|          {
 | |
|             newpal[i].red = 0xbe;
 | |
|             newpal[i].green = 0xad;
 | |
|             newpal[i].blue = 0xed;
 | |
|          }
 | |
| 
 | |
|          png_set_PLTE(dp->read_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 = "<no write>";
 | |
| 
 | |
|    else
 | |
|    {
 | |
|       if (filename != NULL)
 | |
|       {
 | |
|          dp->output_file = filename;
 | |
|          dp->fp = fopen(filename, "wb");
 | |
|       }
 | |
| 
 | |
|       else
 | |
|       {
 | |
|          dp->output_file = "<stdout>";
 | |
|          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; sp<dp->csp; ++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 *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
 | |
|    {
 | |
|       dp->errset = 0;
 | |
| 
 | |
|       if (ret < ERRORS) /* shouldn't longjmp on warnings */
 | |
|          display_log(dp, INTERNAL_ERROR, "unexpected return code %d", ret);
 | |
| 
 | |
|       return ret;
 | |
|    }
 | |
| }
 | |
| 
 | |
| 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<option_end; ++j)
 | |
|                   printf(" %s", argv[j]);
 | |
| 
 | |
|                if (infile != NULL)
 | |
|                   printf(" %s", infile);
 | |
| 
 | |
| #              ifdef PNG_PNGCP_TIMING_SUPPORTED
 | |
|                   /* When logging output the files for each file, if enabled. */
 | |
|                   if ((d.value[OPTIND(&d,time)] & PNGCP_TIME_READ) != 0)
 | |
|                      print_time(" read", d.read_time);
 | |
| 
 | |
|                   if ((d.value[OPTIND(&d,time)] & PNGCP_TIME_WRITE) != 0)
 | |
|                      print_time(" write", d.write_time);
 | |
| #              endif /* PNGCP_TIMING */
 | |
| 
 | |
|                printf("\n");
 | |
|                fflush(stdout);
 | |
|             }
 | |
|          }
 | |
| 
 | |
|          display_clean(&d);
 | |
|       }
 | |
|       while (i+!(d.options & NOWRITE) < argc);
 | |
|          /* I.e. for write cases after the first time through the loop require
 | |
|           * there to be at least two arguments left and for the last one to be a
 | |
|           * directory (this was checked above).
 | |
|           */
 | |
| 
 | |
|       /* Release allocated memory */
 | |
|       display_destroy(&d);
 | |
| 
 | |
| #     ifdef PNG_PNGCP_TIMING_SUPPORTED
 | |
|          {
 | |
|             int output = 0;
 | |
| 
 | |
|             if ((d.value[OPTIND(&d,time)] & PNGCP_TIME_READ) != 0)
 | |
|                print_time("read", d.read_time_total), output = 1;
 | |
| 
 | |
|             if ((d.value[OPTIND(&d,time)] & PNGCP_TIME_WRITE) != 0)
 | |
|             {
 | |
|                if (output) putchar(' ');
 | |
|                print_time("write", d.write_time_total);
 | |
|                output = 1;
 | |
|             }
 | |
| 
 | |
|             if (output) putchar('\n');
 | |
|          }
 | |
| #     endif /* PNGCP_TIMING */
 | |
| 
 | |
|       return errors != 0;
 | |
|    }
 | |
| }
 | |
| #else /* !READ_PNG || !WRITE_PNG */
 | |
| int
 | |
| main(void)
 | |
| {
 | |
|    fprintf(stderr, "pngcp: no support for png_read/write_image\n");
 | |
|    return 77;
 | |
| }
 | |
| #endif /* !READ_PNG || !WRITE_PNG */
 | 
