/* pngcp.c
 *
 * Copyright (c) 2016,2022,2024 John Cunningham Bowler
 *
 * This code is released under the libpng license.
 * For conditions of distribution and use, see the disclaimer
 * and license in png.h
 *
 * This is an example of copying a PNG without changes using the png_read_png
 * and png_write_png interfaces.  A considerable number of options are provided
 * to manipulate the compression of the PNG data and other compressed chunks.
 *
 * For a more extensive example that uses the transforms see
 * contrib/libtests/pngimage.c in the libpng distribution.
 *
 * This code is not intended for installation in a release system; the command
 * line options are not documented and most of the behavior is intended for
 * testing libpng performance, both speed and compression.
 */

#include "pnglibconf.h" /* To find how libpng was configured. */

#ifdef PNG_PNGCP_TIMING_SUPPORTED
   /* WARNING:
    *
    * This test is here to allow POSIX.1b extensions to be used if enabled in
    * the compile; specifically the code requires_POSIX_C_SOURCE support of
    * 199309L or later to enable clock_gettime use.
    *
    * IF this causes problems THEN compile with a strict ANSI C compiler and let
    * this code turn on the POSIX features that it minimally requires.
    *
    * IF this does not work there is probably a bug in your ANSI C compiler or
    * your POSIX implementation.
    */
#  define _POSIX_C_SOURCE 199309L
#else /* No timing support required */
#  define _POSIX_SOURCE 1
#endif

#if defined(HAVE_CONFIG_H) && !defined(PNG_NO_CONFIG_H)
#  include <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 */

/* '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 */
   int              errlevel;          /* error level from longjmp */

   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)
      {
         dp->errlevel = level;
         longjmp(dp->error_return, level);
      }

      else
         exit(99);
   }
}

#if PNG_LIBPNG_VER < 10700 && defined PNG_TEXT_SUPPORTED
static void
text_stash(struct display *dp)
{
   /* libpng 1.6 and earlier fixed a bug whereby text chunks were written
    * multiple times by png_write_png; the issue was that png_write_png passed
    * the same png_info to both png_write_info and png_write_end.  Rather than
    * fixing it by recording the information in the png_struct, or by recording
    * where to write the chunks, the fix made was to change the 'compression'
    * field of the chunk to invalid values, rendering the png_info somewhat
    * useless.
    *
    * The only fix for this given that we use the png_info more than once is to
    * make a copy of the text chunks and png_set_text it each time.  This adds a
    * text chunks, so they get replicated, but only the new set gets written
    * each time.  This uses memory like crazy but there is no way to delete the
    * useless chunks from the png_info.
    *
    * To make this slightly more efficient only the top level structure is
    * copied; since the old strings are actually preserved (in 1.6 and earlier)
    * this happens to work.
    */
   png_textp chunks = NULL;

   dp->num_text = png_get_text(dp->write_pp, dp->ip, &chunks, NULL);

   if (dp->num_text > 0)
   {
      dp->text_ptr = voidcast(png_textp, malloc(dp->num_text * sizeof *chunks));

      if (dp->text_ptr == NULL)
         display_log(dp, APP_ERROR, "text chunks: stash malloc failed");

      else
         memcpy(dp->text_ptr, chunks, dp->num_text * sizeof *chunks);
   }

   dp->text_stashed = 1; /* regardless of whether there are chunks or not */
}

#define text_stash(dp) if (!dp->text_stashed) text_stash(dp)

static void
text_restore(struct display *dp)
{
   /* libpng makes a copy, so this is fine: */
   if (dp->text_ptr != NULL)
      png_set_text(dp->write_pp, dp->ip, dp->text_ptr, dp->num_text);
}

#define text_restore(dp) if (dp->text_stashed) text_restore(dp)

#else
#define text_stash(dp) ((void)0)
#define text_restore(dp) ((void)0)
#endif /* pre 1.7 */

/* OPTIONS:
 *
 * The command handles options of the forms:
 *
 *    --option
 *       Turn an option on (Option)
 *    --no-option
 *       Turn an option off (Option)
 *    --option=value
 *       Set an option to a value (Value)
 *    --option=val1,val2,val3
 *       Set an option to a bitmask constructed from the values (List)
 */
static png_byte
option_index(struct display *dp, const char *opt, size_t len)
   /* Return the index (in options[]) of the given option, outputs an error if
    * it does not exist.  Takes the name of the option and a length (number of
    * characters in the name).
    */
{
   png_byte j;

   for (j=0; 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 *dest)
{
   if (setjmp(dp->error_return) == 0)
   {
      dp->errset = 1;
      cp_one_file(dp, file, dest);
      dp->errset = 0;
      return 0;
   }

   else
   {
      dp->errset = 0;

      if (dp->errlevel < ERRORS) /* shouldn't longjmp on warnings */
         display_log(dp, INTERNAL_ERROR, "unexpected return code %d",
               dp->errlevel);

      return dp->errlevel;
   }
}

int
main(int argc, char **argv)
{
   /* For each file on the command line test it with a range of transforms */
   int option_end;
   struct display d;

   display_init(&d);

   d.operation = "options";
   for (option_end = 1;
        option_end < argc && opt_check(&d, argv[option_end]);
        ++option_end)
   {
   }

   /* Do a quick check on the directory target case; when there are more than
    * two arguments the last one must be a directory.
    */
   if (!(d.options & NOWRITE) && option_end+2 < argc && !checkdir(argv[argc-1]))
   {
      fprintf(stderr,
            "pngcp: %s: directory required with more than two arguments\n",
            argv[argc-1]);
      return 99;
   }

   {
      int errors = 0;
      int i = option_end;

      /* Do this at least once; if there are no arguments stdin/stdout are used.
       */
      d.operation = "files";
      do
      {
         const char *infile = NULL;
         const char *outfile = NULL;
         int ret;

         if (i < argc)
         {
            infile = argv[i++];
            if (!(d.options & NOWRITE) && i < argc)
               outfile = argv[argc-1];
         }

         ret = cppng(&d, infile, outfile);

         if (ret)
         {
            if (ret > QUIET) /* abort on user or internal error */
               return 99;

            /* An error: the output is meaningless */
         }

         else if (d.best[0] != 0)
         {
            /* This result may already have been output, in which case best_size
             * has been reset.
             */
            if (d.best_size < MAX_SIZE)
               print_search_results(&d);
         }

         else if (d.options & SIZES)
         {
            printf("%s [%ld x %ld %d bpp %s, %lu bytes] %lu -> %lu [0x%lx]\n",
                  infile, (unsigned long)d.w, (unsigned long)d.h, d.bpp,
                  cts(d.ct), (unsigned long)d.size, (unsigned long)d.read_size,
                  (unsigned long)d.write_size, (unsigned long)d.results);
            fflush(stdout);
         }

         /* Here on any return, including failures, except user/internal issues
          */
         {
            int pass = (d.options & STRICT) ?
               RESULT_STRICT(d.results) : RESULT_RELAXED(d.results);

            if (!pass)
               ++errors;

            if (d.options & LOG)
            {
               int j;

               printf("%s: pngcp", pass ? "PASS" : "FAIL");

               for (j=1; j<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 */
