about summary refs log tree commit diff
path: root/converter/other/pnmtopng.c
diff options
context:
space:
mode:
Diffstat (limited to 'converter/other/pnmtopng.c')
-rw-r--r--converter/other/pnmtopng.c2745
1 files changed, 2745 insertions, 0 deletions
diff --git a/converter/other/pnmtopng.c b/converter/other/pnmtopng.c
new file mode 100644
index 00000000..9287d0ee
--- /dev/null
+++ b/converter/other/pnmtopng.c
@@ -0,0 +1,2745 @@
+/*
+** pnmtopng.c -
+** read a portable anymap and produce a Portable Network Graphics file
+**
+** derived from pnmtorast.c (c) 1990,1991 by Jef Poskanzer and some
+** parts derived from ppmtogif.c by Marcel Wijkstra <wijkstra@fwi.uva.nl>
+**
+** Copyright (C) 1995-1998 by Alexander Lehmann <alex@hal.rhein-main.de>
+**                        and Willem van Schaik <willem@schaik.com>
+** Copyright (C) 1999,2001 by Greg Roelofs <newt@pobox.com>
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* This Netpbm version of Pnmtopng was derived from the independently
+   distributed program of the same name, Version 2.37.6 (21 July 2001).
+*/
+
+/* A performance note: This program reads one row at a time because
+   the whole image won't fit in memory always.  When you realize that
+   in a Netpbm xel array a one bit pixel can take 96 bits of memory,
+   it's easy to see that an ordinary fax could deplete your virtual
+   memory and even if it didn't, it might deplete your real memory and
+   iterating through the array would cause thrashing.  This program
+   iterates through the image multiple times.  
+
+   So instead, we read the image into memory one row at a time, into a
+   single row buffer.  We use Netpbm's pm_openr_seekable() facility to
+   access the file.  That facility copies the file into a temporary
+   file if it isn't seekable, so we always end up with a file that we
+   can rewind and reread multiple times.
+
+   This shouldn't cause I/O delays because the entire image ought to fit
+   in the system's I/O cache (remember that the file is a lot smaller than
+   the xel array you'd get by doing a pnm_readpnm() of it).
+
+   However, it does introduce some delay because of all the system calls 
+   required to read the file.  A future enhancement might read the entire
+   file into an xel array in some cases, and read one row at a time in 
+   others, depending on the needs of the particular use.
+
+   We do still read the entire alpha mask (if there is one) into a
+   'gray' array, rather than access it one row at a time.  
+
+   Before May 2001, we did in fact read the whole image into an xel array,
+   and we got complaints.  Before April 2000, it wasn't as big a problem
+   because xels were only 24 bits.  Now they're 96.
+*/
+   
+#define GRR_GRAY_PALETTE_FIX
+
+#ifndef PNMTOPNG_WARNING_LEVEL
+#  define PNMTOPNG_WARNING_LEVEL 0   /* use 0 for backward compatibility, */
+#endif                               /*  2 for warnings (1 == error) */
+
+#include <assert.h>
+#include <string.h> /* strcat() */
+#include <limits.h>
+#include <png.h>    /* includes zlib.h and setjmp.h */
+#include "pnm.h"
+#include "pngtxt.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "version.h"
+
+struct zlibCompression {
+    /* These are parameters that describe a form of zlib compression.
+       Values have the same meaning as the similarly named arguments to
+       zlib's deflateInit2().  See zlib.h.
+    */
+    unsigned int levelSpec;
+    unsigned int level;
+    unsigned int memLevelSpec;
+    unsigned int mem_level;
+    unsigned int strategySpec;
+    unsigned int strategy;
+    unsigned int windowBitsSpec;
+    unsigned int window_bits;
+    unsigned int methodSpec;
+    unsigned int method;
+    unsigned int bufferSizeSpec;
+    unsigned int buffer_size;
+};
+
+struct chroma {
+    float wx;
+    float wy;
+    float rx;
+    float ry;
+    float gx;
+    float gy;
+    float bx;
+    float by;
+};
+
+struct phys {
+    int x;
+    int y;
+    int unit;
+};
+
+typedef struct cahitem {
+    xel color;
+    gray alpha;
+    int value;
+    struct cahitem * next;
+} cahitem;
+
+typedef cahitem ** coloralphahash_table;
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *  inputFilename;  /* '-' if stdin */
+    const char *  alpha;
+    unsigned int  verbose;
+    unsigned int  downscale;
+    unsigned int  interlace;
+    const char *  transparent;  /* NULL if none */
+    const char *  background;   /* NULL if none */
+    unsigned int  gammaSpec;
+    float         gamma;        /* Meaningless if !gammaSpec */
+    unsigned int  hist;
+    unsigned int  rgbSpec;
+    struct chroma rgb;          /* Meaningless if !rgbSpec */
+    unsigned int  sizeSpec;
+    struct phys   size;         /* Meaningless if !sizeSpec */
+    const char *  text;         /* NULL if none */
+    const char *  ztxt;         /* NULL if none */
+    unsigned int  modtimeSpec;
+    time_t        modtime;      /* Meaningless if !modtimeSpec */
+    const char *  palette;      /* NULL if none */
+    int           filterSet;
+    unsigned int  force;
+    unsigned int  libversion;
+    unsigned int  compressionSpec;
+    struct zlibCompression zlibCompression;
+};
+
+
+
+typedef struct _jmpbuf_wrapper {
+  jmp_buf jmpbuf;
+} jmpbuf_wrapper;
+
+#ifndef TRUE
+#  define TRUE 1
+#endif
+#ifndef FALSE
+#  define FALSE 0
+#endif
+#ifndef NONE
+#  define NONE 0
+#endif
+#define MAXCOLORS 256
+#define MAXPALETTEENTRIES 256
+
+/* PALETTEMAXVAL is the maxval used in a PNG palette */
+#define PALETTEMAXVAL 255
+
+#define PALETTEOPAQUE 255
+#define PALETTETRANSPARENT 0
+
+static bool verbose;
+
+static jmpbuf_wrapper pnmtopng_jmpbuf_struct;
+static int errorlevel;
+
+
+
+static void
+parseSizeOpt(const char *  const sizeOpt,
+             struct phys * const sizeP) {
+
+    int count;
+    
+    count = sscanf(sizeOpt, "%d %d %d", &sizeP->x, &sizeP->y, &sizeP->unit);
+
+    if (count != 3)
+        pm_error("Invalid syntax for the -size option value '%s'.  "
+                 "Should be 3 integers: x, y, and unit code", sizeOpt);
+}
+
+
+
+static void
+parseRgbOpt(const char *    const rgbOpt,
+            struct chroma * const rgbP) {
+
+    int count;
+    
+    count = sscanf(rgbOpt, "%f %f %f %f %f %f %f %f",
+                   &rgbP->wx, &rgbP->wy,
+                   &rgbP->rx, &rgbP->ry,
+                   &rgbP->gx, &rgbP->gy,
+                   &rgbP->bx, &rgbP->by);
+
+    if (count != 6)
+        pm_error("Invalid syntax for the -rgb option value '%s'.  "
+                 "Should be 6 floating point number: "
+                 "x and y for each of white, red, green, and blue",
+                 rgbOpt);
+}
+
+
+
+static void
+parseModtimeOpt(const char * const modtimeOpt,
+                time_t *     const modtimeP) {
+
+    struct tm brokenTime;
+    int year;
+    int month;
+    int count;
+
+    count = sscanf(modtimeOpt, "%d-%d-%d %d:%d:%d",
+                   &year,
+                   &month,
+                   &brokenTime.tm_mday,
+                   &brokenTime.tm_hour,
+                   &brokenTime.tm_min,
+                   &brokenTime.tm_sec);
+
+    if (count != 6)
+        pm_error("Invalid value for -modtime '%s'.   It should have "
+                 "the form [yy]yy-mm-dd hh:mm:ss.", modtimeOpt);
+    
+    if (year < 0)
+        pm_error("Year is negative in -modtime value '%s'", modtimeOpt);
+    if (year > 9999)
+        pm_error("Year is more than 4 digits in -modtime value '%s'",
+                 modtimeOpt);
+    if (month < 0)
+        pm_error("Month is negative in -modtime value '%s'", modtimeOpt);
+    if (month > 12)
+        pm_error("Month is >12 in -modtime value '%s'", modtimeOpt);
+    if (brokenTime.tm_mday < 0)
+        pm_error("Day of month is negative in -modtime value '%s'",
+                 modtimeOpt);
+    if (brokenTime.tm_mday > 31)
+        pm_error("Day of month is >31 in -modtime value '%s'", modtimeOpt);
+    if (brokenTime.tm_hour < 0)
+        pm_error("Hour is negative in -modtime value '%s'", modtimeOpt);
+    if (brokenTime.tm_hour > 23)
+        pm_error("Hour is >23 in -modtime value '%s'", modtimeOpt);
+    if (brokenTime.tm_min < 0)
+        pm_error("Minute is negative in -modtime value '%s'", modtimeOpt);
+    if (brokenTime.tm_min > 59)
+        pm_error("Minute is >59 in -modtime value '%s'", modtimeOpt);
+    if (brokenTime.tm_sec < 0)
+        pm_error("Second is negative in -modtime value '%s'", modtimeOpt);
+    if (brokenTime.tm_sec > 59)
+        pm_error("Second is >59 in -modtime value '%s'", modtimeOpt);
+
+    brokenTime.tm_mon = month - 1;
+    if (year >= 1900)
+        brokenTime.tm_year = year - 1900;
+    else
+        brokenTime.tm_year = year;
+
+    /* Note that mktime() considers brokeTime to be in local time.
+       This is what we want, since we got it from a user.  User should
+       set his local time zone to UTC if he wants absolute time.
+    */
+    *modtimeP = mktime(&brokenTime);
+}
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int alphaSpec, transparentSpec, backgroundSpec;
+    unsigned int textSpec, ztxtSpec, paletteSpec;
+    unsigned int filterSpec;
+
+    unsigned int nofilter, sub, up, avg, paeth, filter;
+    unsigned int chroma, phys, time;
+    const char * size;
+    const char * rgb;
+    const char * modtime;
+    const char * compMethod;
+    const char * compStrategy;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "alpha",            OPT_STRING,    &cmdlineP->alpha,
+            &alphaSpec,            0);
+    OPTENT3(0, "transparent",      OPT_STRING,    &cmdlineP->transparent,
+            &transparentSpec,      0);
+    OPTENT3(0, "background",       OPT_STRING,    &cmdlineP->background,
+            &backgroundSpec,       0);
+    OPTENT3(0, "rgb",              OPT_STRING,    &rgb,
+            &cmdlineP->rgbSpec,    0);
+    OPTENT3(0, "size",             OPT_STRING,    &size,
+            &cmdlineP->sizeSpec,   0);
+    OPTENT3(0, "text",             OPT_STRING,    &cmdlineP->text,
+            &textSpec,             0);
+    OPTENT3(0, "ztxt",             OPT_STRING,    &cmdlineP->ztxt,
+            &ztxtSpec,             0);
+    OPTENT3(0, "modtime",          OPT_STRING,    &modtime,
+            &cmdlineP->modtimeSpec,0);
+    OPTENT3(0, "palette",          OPT_STRING,    &cmdlineP->palette,
+            &paletteSpec,          0);
+    OPTENT3(0, "compression",      OPT_UINT,
+            &cmdlineP->zlibCompression.level,
+            &cmdlineP->zlibCompression.levelSpec,            0);
+    OPTENT3(0, "comp_mem_level",   OPT_UINT,
+            &cmdlineP->zlibCompression.mem_level,
+            &cmdlineP->zlibCompression.memLevelSpec,         0);
+    OPTENT3(0, "comp_strategy",    OPT_STRING,    &compStrategy,
+            &cmdlineP->zlibCompression.strategySpec,         0);
+    OPTENT3(0, "comp_window_bits", OPT_UINT,
+            &cmdlineP->zlibCompression.window_bits,
+            &cmdlineP->zlibCompression.windowBitsSpec,       0);
+    OPTENT3(0, "comp_method",      OPT_STRING,    &compMethod,
+            &cmdlineP->zlibCompression.methodSpec,           0);
+    OPTENT3(0, "comp_buffer_size", OPT_UINT,
+            &cmdlineP->zlibCompression.buffer_size,
+            &cmdlineP->zlibCompression.bufferSizeSpec,       0);
+    OPTENT3(0, "gamma",            OPT_FLOAT,     &cmdlineP->gamma,
+            &cmdlineP->gammaSpec,  0);
+    OPTENT3(0, "hist",             OPT_FLAG,      NULL,
+            &cmdlineP->hist,       0);
+    OPTENT3(0, "downscale",        OPT_FLAG,      NULL,
+            &cmdlineP->downscale,  0);
+    OPTENT3(0, "interlace",        OPT_FLAG,      NULL,
+            &cmdlineP->interlace,  0);
+    OPTENT3(0, "force",            OPT_FLAG,      NULL,
+            &cmdlineP->force,      0);
+    OPTENT3(0, "libversion",       OPT_FLAG,      NULL,
+            &cmdlineP->libversion, 0);
+    OPTENT3(0, "verbose",          OPT_FLAG,      NULL,
+            &cmdlineP->verbose,    0);
+    OPTENT3(0, "nofilter",         OPT_FLAG,      NULL,
+            &nofilter,             0);
+    OPTENT3(0, "sub",              OPT_FLAG,      NULL,
+            &sub,                  0);
+    OPTENT3(0, "up",               OPT_FLAG,      NULL,
+            &up,                   0);
+    OPTENT3(0, "avg",              OPT_FLAG,      NULL,
+            &avg,                  0);
+    OPTENT3(0, "paeth",            OPT_FLAG,      NULL,
+            &paeth,                0);
+    OPTENT3(0, "filter",           OPT_INT,       &filter,
+            &filterSpec,           0);
+    OPTENT3(0, "verbose",          OPT_FLAG,      NULL,
+            &cmdlineP->verbose,    0);
+    OPTENT3(0, "chroma",           OPT_FLAG,      NULL,
+            &chroma,               0);
+    OPTENT3(0, "phys",             OPT_FLAG,      NULL,
+            &phys,                 0);
+    OPTENT3(0, "time",             OPT_FLAG,      NULL,
+            &time,                 0);
+
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+
+    if (chroma)
+        pm_error("The -chroma option no longer exists.  Use -rgb instead.");
+    if (phys)
+        pm_error("The -phys option no longer exists.  Use -size instead.");
+    if (time)
+        pm_error("The -time option no longer exists.  Use -modtime instead.");
+
+    if (alphaSpec + transparentSpec > 1)
+        pm_error("You may not specify both -alpha and -transparent");
+    if (!alphaSpec)
+        cmdlineP->alpha = NULL;
+    if (!transparentSpec)
+        cmdlineP->transparent = NULL;
+    if (!backgroundSpec)
+        cmdlineP->background = NULL;
+    if (!textSpec)
+        cmdlineP->text = NULL;
+    if (!ztxtSpec)
+        cmdlineP->ztxt = NULL;
+    if (!paletteSpec)
+        cmdlineP->palette = NULL;
+    
+    if (filterSpec + nofilter + sub + up + avg + paeth > 1)
+        pm_error("You may specify at most one of "
+                 "-nofilter, -sub, -up, -avg, -paeth, and -filter");
+    
+    if (filterSpec) {
+        if (filter < 0 || filter > 4)
+            pm_error("-filter is obsolete.  Use -nofilter, -sub, -up, -avg, "
+                     "and -paeth options instead.");
+        else
+            switch (filter) {
+            case 0: cmdlineP->filterSet = PNG_FILTER_NONE;  break;
+            case 1: cmdlineP->filterSet = PNG_FILTER_SUB;   break;
+            case 2: cmdlineP->filterSet = PNG_FILTER_UP;    break;
+            case 3: cmdlineP->filterSet = PNG_FILTER_AVG;   break;
+            case 4: cmdlineP->filterSet = PNG_FILTER_PAETH; break;
+            }
+    } else {
+        if (nofilter)
+            cmdlineP->filterSet = PNG_FILTER_NONE;
+        else if (sub)
+            cmdlineP->filterSet = PNG_FILTER_SUB;
+        else if (up)
+            cmdlineP->filterSet = PNG_FILTER_UP;
+        else if (avg)
+            cmdlineP->filterSet = PNG_FILTER_AVG;
+        else if (paeth)
+            cmdlineP->filterSet = PNG_FILTER_PAETH;
+        else
+            cmdlineP->filterSet = PNG_FILTER_NONE;
+    }
+    
+    if (cmdlineP->sizeSpec)
+        parseSizeOpt(size, &cmdlineP->size);
+
+    if (cmdlineP->rgbSpec)
+        parseRgbOpt(rgb, &cmdlineP->rgb);
+    
+    if (cmdlineP->modtimeSpec)
+        parseModtimeOpt(modtime, &cmdlineP->modtime);
+
+    if (cmdlineP->zlibCompression.levelSpec &&
+        cmdlineP->zlibCompression.level > 9)
+        pm_error("-compression value must be from 0 (no compression) "
+                 "to 9 (maximum compression).  You specified %u",
+                 cmdlineP->zlibCompression.level);
+
+    if (cmdlineP->zlibCompression.memLevelSpec) {
+        if (cmdlineP->zlibCompression.mem_level  < 1 ||
+            cmdlineP->zlibCompression.mem_level > 9)
+        pm_error("-comp_mem_level value must be from 1 (minimum memory usage) "
+                 "to 9 (maximum memory usage).  You specified %u",
+                 cmdlineP->zlibCompression.mem_level);
+    }
+
+    if (cmdlineP->zlibCompression.methodSpec) {
+        if (STREQ(compMethod, "deflated"))
+            cmdlineP->zlibCompression.method = Z_DEFLATED;
+        else
+            pm_error("The only valid value for -method is 'deflated'.  "
+                     "You specified '%s'", compMethod);
+    }
+
+    if (cmdlineP->zlibCompression.strategySpec) {
+        if (STREQ(compStrategy, "huffman_only"))
+            cmdlineP->zlibCompression.strategy = Z_HUFFMAN_ONLY;
+        else if (STREQ(compStrategy, "filtered"))
+            cmdlineP->zlibCompression.strategy = Z_FILTERED;
+        else
+            pm_error("Valid values for -strategy are 'huffman_only' and "
+                     "filtered.  You specified '%s'", compStrategy);
+    }
+
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilename = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilename = argv[1];
+    else
+        pm_error("Program takes at most one argument:  input file name");
+}
+
+
+
+static png_color_16
+xelToPngColor_16(xel const input, 
+                 xelval const maxval, 
+                 xelval const pngMaxval) {
+    png_color_16 retval;
+
+    xel scaled;
+    
+    PPM_DEPTH(scaled, input, maxval, pngMaxval);
+
+    retval.red   = PPM_GETR(scaled);
+    retval.green = PPM_GETG(scaled);
+    retval.blue  = PPM_GETB(scaled);
+    retval.gray  = PNM_GET1(scaled);
+
+    return retval;
+}
+
+
+
+static void
+closestColorInPalette(pixel          const targetColor, 
+                      pixel                palette_pnm[],
+                      unsigned int   const paletteSize,
+                      unsigned int * const bestIndexP,
+                      unsigned int * const bestMatchP) {
+    
+    unsigned int paletteIndex;
+    unsigned int bestIndex;
+    unsigned int bestMatch;
+
+    assert(paletteSize > 0);
+
+    bestMatch = UINT_MAX;
+    for (paletteIndex = 0; paletteIndex < paletteSize; ++paletteIndex) {
+        unsigned int const dist = 
+            PPM_DISTANCE(palette_pnm[paletteIndex], targetColor);
+
+        if (dist < bestMatch) {
+            bestMatch = dist;
+            bestIndex = paletteIndex;
+        }
+    }
+    if (bestIndexP != NULL)
+        *bestIndexP = bestIndex;
+    if (bestMatchP != NULL)
+        *bestMatchP = bestMatch;
+}
+
+
+
+/* We really ought to make this hash function actually depend upon
+   the "a" argument; we just don't know a decent prime number off-hand.
+*/
+#define HASH_SIZE 20023
+#define hashpixelalpha(p,a) ((((long) PPM_GETR(p) * 33023 + \
+                               (long) PPM_GETG(p) * 30013 + \
+                               (long) PPM_GETB(p) * 27011 ) \
+                              & 0x7fffffff ) % HASH_SIZE )
+
+static coloralphahash_table
+alloccoloralphahash(void)  {
+    coloralphahash_table caht;
+    int i;
+
+    MALLOCARRAY(caht,HASH_SIZE);
+    if (caht == NULL)
+        pm_error( "out of memory allocating hash table" );
+
+    for (i = 0; i < HASH_SIZE; ++i)
+        caht[i] = NULL;
+
+    return caht;
+}
+
+
+static void
+freecoloralphahash(coloralphahash_table const caht) {
+    int i;
+
+    for (i = 0; i < HASH_SIZE; ++i) {
+        cahitem * p;
+        cahitem * next;
+        for (p = caht[i]; p; p = next) {
+            next = p->next;
+            free(p);
+        }
+    }
+    free(caht);
+}
+
+
+
+static void
+addtocoloralphahash(coloralphahash_table const caht,
+                    pixel *              const colorP,
+                    gray *               const alphaP,
+                    int                  const value) {
+
+    int hash;
+    cahitem * itemP;
+
+    MALLOCVAR(itemP);
+    if (itemP == NULL)
+        pm_error("Out of memory building hash table");
+    hash = hashpixelalpha(*colorP, *alphaP);
+    itemP->color = *colorP;
+    itemP->alpha = *alphaP;
+    itemP->value = value;
+    itemP->next = caht[hash];
+    caht[hash] = itemP;
+}
+
+
+
+static int
+lookupColorAlpha(coloralphahash_table const caht,
+                 const pixel *        const colorP,
+                 const gray *         const alphaP) {
+
+    int hash;
+    cahitem * p;
+
+    hash = hashpixelalpha(*colorP, *alphaP);
+    for (p = caht[hash]; p; p = p->next)
+        if (PPM_EQUAL(p->color, *colorP) && p->alpha == *alphaP)
+            return p->value;
+
+    return -1;
+}
+
+
+
+static void
+pnmtopng_error_handler(png_structp     const png_ptr,
+                       png_const_charp const msg) {
+
+  jmpbuf_wrapper  *jmpbuf_ptr;
+
+  /* this function, aside from the extra step of retrieving the "error
+   * pointer" (below) and the fact that it exists within the application
+   * rather than within libpng, is essentially identical to libpng's
+   * default error handler.  The second point is critical:  since both
+   * setjmp() and longjmp() are called from the same code, they are
+   * guaranteed to have compatible notions of how big a jmp_buf is,
+   * regardless of whether _BSD_SOURCE or anything else has (or has not)
+   * been defined. */
+
+  fprintf(stderr, "pnmtopng:  fatal libpng error: %s\n", msg);
+  fflush(stderr);
+
+  jmpbuf_ptr = png_get_error_ptr(png_ptr);
+  if (jmpbuf_ptr == NULL) {         /* we are completely hosed now */
+    fprintf(stderr,
+      "pnmtopng:  EXTREMELY fatal error: jmpbuf unrecoverable; terminating.\n");
+    fflush(stderr);
+    exit(99);
+  }
+
+  longjmp(jmpbuf_ptr->jmpbuf, 1);
+}
+
+
+/* The following variables belong to getChv() and freeChv() */
+static bool getChv_computed = FALSE;
+static colorhist_vector getChv_chv;
+
+
+
+static void
+getChv(FILE *             const ifP, 
+       pm_filepos         const rasterPos,
+       int                const cols, 
+       int                const rows, 
+       xelval             const maxval,
+       int                const format, 
+       int                const maxColors, 
+       colorhist_vector * const chvP,
+       unsigned int *     const colorsP) {
+/*----------------------------------------------------------------------------
+   Return a list of all the colors in a libnetpbm image and the number of
+   times they occur.  The image is in the seekable file 'ifP', whose
+   raster starts at position 'rasterPos' of the file.  The image's properties
+   are 'cols', 'rows', 'maxval', and 'format'.
+
+   Return the number of colors as *colorsP.  Return the details of the 
+   colors in newly malloc'ed storage, and its address as *chvP.  If
+   there are more than 'maxColors' colors, though, just return NULL as
+   *chvP and leave *colorsP undefined.
+
+   Don't spend the time to read the file if this subroutine has been called
+   before.  In that case, just assume the inputs are all the same and return
+   the previously computed information.  Ick.
+
+   *chvP is in static program storage.
+-----------------------------------------------------------------------------*/
+    static unsigned int getChv_colors;
+
+    if (!getChv_computed) {
+        int colorCount;
+        if (verbose) 
+            pm_message ("Finding colors in input image...");
+
+        pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
+        getChv_chv = ppm_computecolorhist2(ifP, cols, rows, maxval, format, 
+                                           maxColors, &colorCount);
+        
+        getChv_colors = colorCount;
+
+        if (verbose) {
+            if (getChv_chv)
+                pm_message("%u colors found", getChv_colors);
+            else
+                pm_message("Too many colors (more than %u) found", maxColors);
+        }
+        getChv_computed = TRUE;
+    }
+    *chvP = getChv_chv;
+    *colorsP = getChv_colors;
+}
+
+
+
+static void freeChv(void) {
+
+    if (getChv_computed)
+        if (getChv_chv)
+            ppm_freecolorhist(getChv_chv);
+
+    getChv_computed = FALSE;
+}
+
+
+
+static bool
+pgmBitsAreRepeated(unsigned int const repeatedSize,
+                   FILE *       const ifP,
+                   pm_filepos   const rasterPos, 
+                   int          const cols,
+                   int          const rows,
+                   xelval       const maxval,
+                   int          const format) {
+/*----------------------------------------------------------------------------
+   Return TRUE iff all the samples in the image in file 'ifP',
+   described by 'cols', 'rows', 'maxval', and 'format', consist in the
+   rightmost 'repeatedSize' * 2 bits of two identical sets of
+   'repeatedSize' bits.
+
+   The file has arbitrary position, but the raster is at file position
+   'rasterPos'.
+
+   E.g. for repeatedSize = 2, a sample value of 0xaa would qualify.
+   So would 0x0a.
+
+   Leave the file positioned where we found it.
+-----------------------------------------------------------------------------*/
+    unsigned int const mask2 = (1 << repeatedSize*2) - 1;
+    unsigned int const mask1 = (1 << repeatedSize) - 1;
+
+    bool mayscale;
+    unsigned int row;
+    xel * xelrow;
+
+    xelrow = pnm_allocrow(cols);
+    
+    pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
+
+    mayscale = TRUE;  /* initial assumption */
+
+    for (row = 0; row < rows && mayscale; ++row) {
+        unsigned int col;
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+        for (col = 0; col < cols && mayscale; ++col) {
+            xelval const testbits2 = PNM_GET1(xelrow[col]) & mask2;
+                /* The bits of interest in the sample */
+            xelval const testbits1 = testbits2 & mask1;
+                /* The lower half of the bits of interest in the sample */
+            if (((testbits1 << repeatedSize) | testbits1) != testbits2)
+                mayscale = FALSE;
+        }
+    }
+    pnm_freerow(xelrow);
+
+    return mayscale;
+}
+
+
+
+static void
+meaningful_bits_pgm(FILE *         const ifP, 
+                    pm_filepos     const rasterPos, 
+                    int            const cols,
+                    int            const rows,
+                    xelval         const maxval,
+                    int            const format,
+                    unsigned int * const retvalP) {
+/*----------------------------------------------------------------------------
+   In the PGM raster with maxval 'maxval' at file offset 'rasterPos'
+   in file 'ifp', the samples may be composed of groups of 1, 2, 4, or 8
+   bits repeated.  This would be the case if the image were converted
+   at some point from a 2 bits-per-pixel image to an 8-bits-per-pixel
+   image, for example.
+
+   If this is the case, we find out and find out how small these repeated
+   groups of bits are and return the number of bits.
+-----------------------------------------------------------------------------*/
+    unsigned int maxMeaningfulBits;
+        /* progressive estimate of the maximum number of meaningful
+           (nonrepeated) bits in the samples.
+        */
+
+    maxMeaningfulBits = pm_maxvaltobits(maxval);  /* initial value */
+
+    if (maxval == 0xffff || maxval == 0xff || maxval == 0xf || maxval == 0x3) {
+        if (maxMeaningfulBits == 16) {
+            if (pgmBitsAreRepeated(8,
+                                   ifP, rasterPos, cols, rows, maxval, format))
+                maxMeaningfulBits = 8;
+        }
+        if (maxMeaningfulBits == 8) {
+            if (pgmBitsAreRepeated(4,
+                                   ifP, rasterPos, cols, rows, maxval, format))
+                maxMeaningfulBits = 4;
+        }
+        if (maxMeaningfulBits == 4) {
+            if (pgmBitsAreRepeated(2,
+                                   ifP, rasterPos, cols, rows, maxval, format))
+                maxMeaningfulBits = 2;
+        }
+        if (maxMeaningfulBits == 2) {
+            if (pgmBitsAreRepeated(1,
+                                   ifP, rasterPos, cols, rows, maxval, format))
+                maxMeaningfulBits = 1;
+        }
+    }
+    *retvalP = maxMeaningfulBits;
+}
+
+
+
+static void
+meaningful_bits_ppm(FILE *         const ifp, 
+                    pm_filepos     const rasterPos, 
+                    int            const cols,
+                    int            const rows,
+                    xelval         const maxval,
+                    int            const format,
+                    unsigned int * const retvalP) {
+/*----------------------------------------------------------------------------
+   In the PPM raster with maxval 'maxval' at file offset 'rasterPos'
+   in file 'ifp', the samples may be composed of groups of 8
+   bits repeated twice.  This would be the case if the image were converted
+   at some point from a 8 bits-per-pixel image to an 16-bits-per-pixel
+   image, for example.
+
+   We return the smallest number of bits we can take from the right of
+   a sample without losing information (8 or all).
+-----------------------------------------------------------------------------*/
+    int mayscale;
+    unsigned int row;
+    xel * xelrow;
+    unsigned int maxMeaningfulBits;
+        /* progressive estimate of the maximum number of meaningful
+           (nonrepeated) bits in the samples.
+        */
+
+    xelrow = pnm_allocrow(cols);
+
+    maxMeaningfulBits = pm_maxvaltobits(maxval);
+
+    if (maxval == 65535) {
+        mayscale = TRUE;   /* initial assumption */
+        pm_seek2(ifp, &rasterPos, sizeof(rasterPos));
+        for (row = 0; row < rows && mayscale; ++row) {
+            unsigned int col;
+            pnm_readpnmrow(ifp, xelrow, cols, maxval, format);
+            for (col = 0; col < cols && mayscale; ++col) {
+                xel const p = xelrow[col];
+                if ((PPM_GETR(p) & 0xff) * 0x101 != PPM_GETR(p) ||
+                    (PPM_GETG(p) & 0xff) * 0x101 != PPM_GETG(p) ||
+                    (PPM_GETB(p) & 0xff) * 0x101 != PPM_GETB(p))
+                    mayscale = FALSE;
+            }
+        }
+        if (mayscale)
+            maxMeaningfulBits = 8;
+    }
+    pnm_freerow(xelrow);
+
+    *retvalP = maxMeaningfulBits;
+}
+
+
+
+static void
+tryTransparentColor(FILE *     const ifp, 
+                    pm_filepos const rasterPos, 
+                    int        const cols, 
+                    int        const rows, 
+                    xelval     const maxval,
+                    int        const format, 
+                    gray **    const alphaMask,
+                    gray       const alphaMaxval,
+                    pixel      const transcolor,
+                    bool *     const singleColorIsTransP) {
+
+    int const pnm_type = PNM_FORMAT_TYPE(format);
+
+    xel * xelrow;
+    bool singleColorIsTrans;
+        /* So far, it looks like a single color is uniquely transparent */
+    int row;
+
+    xelrow = pnm_allocrow(cols);
+
+    pm_seek2(ifp, &rasterPos, sizeof(rasterPos));
+
+    singleColorIsTrans = TRUE;  /* initial assumption */
+        
+    for (row = 0; row < rows && singleColorIsTrans; ++row) {
+        int col;
+        pnm_readpnmrow(ifp, xelrow, cols, maxval, format);
+        for (col = 0 ; col < cols && singleColorIsTrans; ++col) {
+            if (alphaMask[row][col] == 0) { /* transparent */
+                /* If we have a second transparent color, we're
+                   disqualified
+                */
+                if (pnm_type == PPM_TYPE) {
+                    if (!PPM_EQUAL(xelrow[col], transcolor))
+                        singleColorIsTrans = FALSE;
+                } else {
+                    if (PNM_GET1(xelrow[col]) != PNM_GET1(transcolor))
+                        singleColorIsTrans = FALSE;
+                }
+            } else if (alphaMask[row][col] != alphaMaxval) {
+                /* Here's an area of the mask that is translucent.  That
+                   disqualified us.
+                */
+                singleColorIsTrans = FALSE;
+            } else {
+                /* Here's an area of the mask that is opaque.  If it's
+                   the same color as our candidate transparent color,
+                   that disqualifies us.
+                */
+                if (pnm_type == PPM_TYPE) {
+                    if (PPM_EQUAL(xelrow[col], transcolor))
+                        singleColorIsTrans = FALSE;
+                } else {
+                    if (PNM_GET1(xelrow[col]) == PNM_GET1(transcolor))
+                        singleColorIsTrans = FALSE;
+                }
+            }
+        }
+    }  
+    pnm_freerow(xelrow);
+}
+
+
+
+static void
+analyzeAlpha(FILE *     const ifp, 
+             pm_filepos const rasterPos, 
+             int        const cols, 
+             int        const rows, 
+             xelval     const maxval,
+             int        const format, 
+             gray **    const alphaMask,
+             gray       const alphaMaxval,
+             bool *     const allOpaqueP,
+             bool *     const singleColorIsTransP, 
+             pixel*     const alphaTranscolorP) {
+/*----------------------------------------------------------------------------
+  Get information about the alpha mask, in combination with the masked
+  image, that Caller can use to choose the most efficient way to
+  represent the information in the alpha mask in a PNG.  Simply
+  putting the alpha mask in the PNG is a last resort.  But if the mask
+  says all opaque, we can simply omit any mention of transparency
+  instead -- default is opaque.  And if the mask makes all the pixels
+  of a certain color fully transparent and every other pixel opaque,
+  we can simply identify that color in the PNG.
+
+  We have to do this before any scaling occurs, since alpha is only
+  possible with 8 and 16-bit.
+-----------------------------------------------------------------------------*/
+    xel * xelrow;
+    bool foundTransparentPixel;
+        /* We found a pixel in the image where the alpha mask says it is
+           transparent.
+        */
+    pixel transcolor;
+        /* Color of the transparent pixel mentioned above. */
+    
+    xelrow = pnm_allocrow(cols);
+
+    {
+        int row;
+        /* Find a candidate transparent color -- the color of any pixel in the
+           image that the alpha mask says should be transparent.
+        */
+        foundTransparentPixel = FALSE;  /* initial assumption */
+        pm_seek2(ifp, &rasterPos, sizeof(rasterPos));
+        for (row = 0 ; row < rows && !foundTransparentPixel ; ++row) {
+            int col;
+            pnm_readpnmrow(ifp, xelrow, cols, maxval, format);
+            for (col = 0; col < cols && !foundTransparentPixel; ++col) {
+                if (alphaMask[row][col] == 0) {
+                    foundTransparentPixel = TRUE;
+                    transcolor = xeltopixel(xelrow[col]);
+                }
+            }
+        }
+    }
+
+    pnm_freerow(xelrow);
+
+    if (foundTransparentPixel) {
+        *allOpaqueP = FALSE;
+        tryTransparentColor(ifp, rasterPos, cols, rows, maxval, format,
+                            alphaMask, alphaMaxval, transcolor,
+                            singleColorIsTransP);
+        *alphaTranscolorP = transcolor;
+    } else {
+        *allOpaqueP   = TRUE;
+        *singleColorIsTransP = FALSE;
+    }
+}
+
+
+
+static void
+findRedundantBits(FILE *         const ifp, 
+                  int            const rasterPos, 
+                  int            const cols,
+                  int            const rows,
+                  xelval         const maxval,
+                  int            const format,
+                  bool           const alpha,
+                  bool           const force,
+                  unsigned int * const meaningfulBitsP) {
+/*----------------------------------------------------------------------------
+   Find out if we can use just a subset of the bits from each input
+   sample.  Often, people create an image with e.g. 8 bit samples from
+   one that has e.g. only 4 bit samples by scaling by 256/16, which is
+   the same as repeating the bits.  E.g.  1011 becomes 10111011.  We
+   detect this case.  We return as *meaningfulBitsP the minimum number
+   of bits, starting from the least significant end, that contain
+   original information.
+-----------------------------------------------------------------------------*/
+  if (!alpha && PNM_FORMAT_TYPE(format) == PGM_TYPE && !force) 
+      meaningful_bits_pgm(ifp, rasterPos, cols, rows, maxval, format,
+                          meaningfulBitsP);
+  else if (PNM_FORMAT_TYPE(format) == PPM_TYPE && !force)
+      meaningful_bits_ppm(ifp, rasterPos, cols, rows, maxval, format,
+                          meaningfulBitsP);
+  else 
+      *meaningfulBitsP = pm_maxvaltobits(maxval);
+
+  if (verbose && *meaningfulBitsP != pm_maxvaltobits(maxval))
+      pm_message("Using only %d rightmost bits of input samples.  The "
+                 "rest are redundant.", *meaningfulBitsP);
+}
+
+
+
+static void
+readOrderedPalette(FILE *         const pfp,
+                   xel                  ordered_palette[], 
+                   unsigned int * const ordered_palette_size_p) {
+
+    xel ** xels;
+    int cols, rows;
+    xelval maxval;
+    int format;
+    
+    if (verbose)
+        pm_message("reading ordered palette (colormap)...");
+
+    xels = pnm_readpnm(pfp, &cols, &rows, &maxval, &format);
+    
+    if (PNM_FORMAT_TYPE(format) != PPM_TYPE) 
+        pm_error("ordered palette must be a PPM file, not type %d", format);
+
+    *ordered_palette_size_p = rows * cols;
+    if (*ordered_palette_size_p > MAXCOLORS) 
+        pm_error("ordered-palette image contains %d pixels.  Maximum is %d",
+                 *ordered_palette_size_p, MAXCOLORS);
+    if (verbose)
+        pm_message("%u colors found", *ordered_palette_size_p);
+
+    {
+        unsigned int j;
+        unsigned int row;
+        j = 0;  /* initial value */
+        for (row = 0; row < rows; ++row) {
+            int col;
+            for (col = 0; col < cols; ++col) 
+                ordered_palette[j++] = xels[row][col];
+        }
+    }
+    pnm_freearray(xels, rows);
+}        
+
+
+
+static void
+compute_nonalpha_palette(colorhist_vector const chv,
+                         int              const colors,
+                         pixval           const maxval,
+                         FILE *           const pfp,
+                         pixel                  palette_pnm[],
+                         unsigned int *   const paletteSizeP,
+                         gray                   trans_pnm[],
+                         unsigned int *   const transSizeP) {
+/*----------------------------------------------------------------------------
+   Compute the palette corresponding to the color set 'chv'
+   (consisting of 'colors' distinct colors) assuming a pure-color (no
+   transparency) palette.
+
+   If 'pfp' is non-null, assume it's a PPM file and read the palette
+   from that.  Make sure it contains the same colors as the palette
+   we computed ourself would have.  Caller supplied the file because he
+   wants the colors in a particular order in the palette.
+-----------------------------------------------------------------------------*/
+    unsigned int colorIndex;
+    
+    xel ordered_palette[MAXCOLORS];
+    unsigned int ordered_palette_size;
+
+    if (pfp) {
+        readOrderedPalette(pfp, ordered_palette, &ordered_palette_size);
+
+        if (colors != ordered_palette_size) 
+            pm_error("sizes of ordered palette (%d) "
+                     "and existing palette (%d) differ",
+                     ordered_palette_size, colors);
+        
+        /* Make sure the ordered palette contains all the colors in
+           the image 
+        */
+        for (colorIndex = 0; colorIndex < colors; colorIndex++) {
+            int j;
+            bool found;
+            
+            found = FALSE;
+            for (j = 0; j < ordered_palette_size && !found; ++j) {
+                if (PNM_EQUAL(ordered_palette[j], chv[colorIndex].color)) 
+                    found = TRUE;
+            }
+            if (!found) 
+                pm_error("failed to find color (%d, %d, %d), which is in the "
+                         "input image, in the ordered palette",
+                         PPM_GETR(chv[colorIndex].color),
+                         PPM_GETG(chv[colorIndex].color),
+                         PPM_GETB(chv[colorIndex].color));
+        }
+        /* OK, the ordered palette passes muster as a palette; go ahead
+           and return it as the palette.
+        */
+        for (colorIndex = 0; colorIndex < colors; ++colorIndex)
+            palette_pnm[colorIndex] = ordered_palette[colorIndex];
+    } else {
+        for (colorIndex = 0; colorIndex < colors; ++colorIndex) 
+            palette_pnm[colorIndex] = chv[colorIndex].color;
+    }
+    *paletteSizeP = colors;
+    *transSizeP = 0;
+}
+
+
+
+static void
+computeUnsortedAlphaPalette(FILE *           const ifP,
+                            int              const cols,
+                            int              const rows,
+                            xelval           const maxval,
+                            int              const format,
+                            pm_filepos       const rasterPos,
+                            gray **          const alpha_mask,
+                            unsigned int     const maxPaletteEntries,
+                            colorhist_vector const chv,
+                            int              const colors,
+                            gray *                 alphas_of_color[],
+                            unsigned int           alphas_first_index[],
+                            unsigned int           alphas_of_color_cnt[]) {
+/*----------------------------------------------------------------------------
+   Read the image at position 'rasterPos' in file *ifP, which is a PNM
+   described by 'cols', 'rows', 'maxval', and 'format'.
+
+   Using the alpha mask 'alpha_mask' and color map 'chv' (of size 'colors')
+   for the image, construct a palette of (color index, alpha) ordered pairs 
+   for the image, as follows.
+
+   The alpha/color palette is the set of all ordered pairs of
+   (color,alpha) in the PNG, including the background color.  The
+   actual palette is an array with up to 'maxPaletteEntries elements.  Each
+   array element contains a color index from the color palette and
+   an alpha value.  All the elements with the same color index are
+   contiguous.  alphas_first_index[x] is the index in the
+   alpha/color palette of the first element that has color index x.
+   alphas_of_color_cnt[x] is the number of elements that have color
+   index x.  alphas_of_color[x][y] is the yth alpha value that
+   appears with color index x (in order of appearance).
+   alpha_color_pair_count is the total number of elements, i.e. the
+   total number of combinations color and alpha.
+-----------------------------------------------------------------------------*/
+    colorhash_table cht;
+    int color_index;
+    int row;
+    xel * xelrow;
+
+    cht = ppm_colorhisttocolorhash (chv, colors);
+
+    for (color_index = 0 ; color_index < colors + 1 ; ++color_index) {
+        /* TODO: It sure would be nice if we didn't have to allocate
+           256 words here for what is normally only 0 or 1 different
+           alpha values!  Maybe we should do some sophisticated reallocation.
+        */
+        MALLOCARRAY(alphas_of_color[color_index], maxPaletteEntries);
+        if (alphas_of_color[color_index] == NULL)
+            pm_error ("out of memory allocating alpha/palette entries");
+        alphas_of_color_cnt[color_index] = 0;
+    }
+ 
+    pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
+
+    xelrow = pnm_allocrow(cols);
+
+    for (row = 0 ; row < rows ; ++row) {
+        int col;
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+        pnm_promoteformatrow(xelrow, cols, maxval, format, maxval, PPM_TYPE);
+        for (col = 0 ; col < cols ; ++col) {
+            int i;
+            int const color = ppm_lookupcolor(cht, &xelrow[col]);
+            for (i = 0 ; i < alphas_of_color_cnt[color] ; ++i) {
+                if (alpha_mask[row][col] == alphas_of_color[color][i])
+                    break;
+            }
+            if (i == alphas_of_color_cnt[color]) {
+                alphas_of_color[color][i] = alpha_mask[row][col];
+                alphas_of_color_cnt[color]++;
+            }
+        }
+    }
+    {
+        int i;
+        alphas_first_index[0] = 0;
+        for (i = 1 ; i < colors ; i++)
+            alphas_first_index[i] = alphas_first_index[i-1] +
+                alphas_of_color_cnt[i-1];
+    }
+    pnm_freerow(xelrow);
+    ppm_freecolorhash(cht);
+}
+
+
+
+static void
+sortAlphaPalette(gray *alphas_of_color[],
+                 unsigned int alphas_first_index[],
+                 unsigned int alphas_of_color_cnt[],
+                 unsigned int const colors,
+                 unsigned int mapping[],
+                 unsigned int * const transSizeP) {
+/*----------------------------------------------------------------------------
+   Remap the palette indices so opaque entries are last.
+
+   alphas_of_color[], alphas_first_index[], and alphas_of_color_cnt[]
+   describe an unsorted PNG (alpha/color) palette.  We generate
+   mapping[] such that mapping[x] is the index into the sorted PNG
+   palette of the alpha/color pair whose index is x in the unsorted
+   PNG palette.  This mapping sorts the palette so that opaque entries
+   are last.
+-----------------------------------------------------------------------------*/
+    unsigned int bot_idx;
+    unsigned int top_idx;
+    unsigned int colorIndex;
+    
+    /* We start one index at the bottom of the palette index range
+       and another at the top.  We run through the unsorted palette,
+       and when we see an opaque entry, we map it to the current top
+       cursor and bump it down.  When we see a non-opaque entry, we map 
+       it to the current bottom cursor and bump it up.  Because the input
+       and output palettes are the same size, the two cursors should meet
+       right when we process the last entry of the unsorted palette.
+    */    
+    bot_idx = 0;
+    top_idx = alphas_first_index[colors-1] + alphas_of_color_cnt[colors-1] - 1;
+    
+    for (colorIndex = 0;  colorIndex < colors;  ++colorIndex) {
+        unsigned int j;
+        for (j = 0; j < alphas_of_color_cnt[colorIndex]; ++j) {
+            unsigned int const paletteIndex = 
+                alphas_first_index[colorIndex] + j;
+            if (alphas_of_color[colorIndex][j] == PALETTEOPAQUE)
+                mapping[paletteIndex] = top_idx--;
+                else
+                    mapping[paletteIndex] = bot_idx++;
+        }
+    }
+    /* indices should have just crossed paths */
+    if (bot_idx != top_idx + 1) {
+        pm_error ("internal inconsistency: "
+                  "remapped bot_idx = %u, top_idx = %u",
+                  bot_idx, top_idx);
+    }
+    *transSizeP = bot_idx;
+}
+
+
+
+static void
+compute_alpha_palette(FILE *         const ifP, 
+                      int            const cols,
+                      int            const rows,
+                      xelval         const maxval,
+                      int            const format,
+                      pm_filepos     const rasterPos,
+                      gray **        const alpha_mask,
+                      pixel                palette_pnm[],
+                      gray                 trans_pnm[],
+                      unsigned int * const paletteSizeP,
+                      unsigned int * const transSizeP,
+                      bool *         const tooBigP) {
+/*----------------------------------------------------------------------------
+   Return the palette of color/alpha pairs for the image indicated by
+   'ifP', 'cols', 'rows', 'maxval', 'format', and 'rasterPos'.
+   alpha_mask[] is the Netpbm-style alpha mask for the image.
+
+   Return the palette as the arrays palette_pnm[] and trans_pnm[].
+   The ith entry in the palette is the combination of palette[i],
+   which defines the color, and trans[i], which defines the
+   transparency.
+
+   Return the number of entries in the palette as *paletteSizeP.
+
+   The palette is sorted so that the opaque entries are last, and we return
+   *transSizeP as the number of non-opaque entries.
+
+   palette[] and trans[] are allocated by the caller to at least 
+   MAXPALETTEENTRIES elements.
+
+   If there are more than MAXPALETTEENTRIES color/alpha pairs in the image, 
+   don't return any palette information -- just return *tooBigP == TRUE.
+-----------------------------------------------------------------------------*/
+    colorhist_vector chv;
+    unsigned int colors;
+
+    gray *alphas_of_color[MAXPALETTEENTRIES];
+    unsigned int alphas_first_index[MAXPALETTEENTRIES];
+    unsigned int alphas_of_color_cnt[MAXPALETTEENTRIES];
+ 
+    getChv(ifP, rasterPos, cols, rows, maxval, format, MAXCOLORS, 
+           &chv, &colors);
+
+    computeUnsortedAlphaPalette(ifP, cols, rows, maxval, format, rasterPos,
+                                alpha_mask, MAXPALETTEENTRIES, chv, colors,
+                                alphas_of_color,
+                                alphas_first_index,
+                                alphas_of_color_cnt);
+
+    *paletteSizeP = 
+        alphas_first_index[colors-1] + alphas_of_color_cnt[colors-1];
+    if (*paletteSizeP > MAXPALETTEENTRIES) {
+        *tooBigP = TRUE;
+    } else {
+        unsigned int mapping[MAXPALETTEENTRIES];
+            /* Sorting of the alpha/color palette.  mapping[x] is the
+               index into the sorted PNG palette of the alpha/color
+               pair whose index is x in the unsorted PNG palette.
+               This mapping sorts the palette so that opaque entries
+               are last.  
+            */
+
+        *tooBigP = FALSE;
+
+        /* Make the opaque palette entries last */
+        sortAlphaPalette(alphas_of_color, alphas_first_index,
+                         alphas_of_color_cnt, colors,
+                         mapping, transSizeP);
+
+        {
+            unsigned int colorIndex;
+
+            for (colorIndex = 0; colorIndex < colors; ++colorIndex) {
+                unsigned int j;
+                for (j = 0; j < alphas_of_color_cnt[colorIndex]; ++j) {
+                    unsigned int const paletteIndex = 
+                        alphas_first_index[colorIndex] + j;
+                    palette_pnm[mapping[paletteIndex]] = chv[colorIndex].color;
+                    trans_pnm[mapping[paletteIndex]] = 
+                    alphas_of_color[colorIndex][j];
+                }
+            }
+        }
+    }
+    { 
+        unsigned int colorIndex;
+        for (colorIndex = 0; colorIndex < colors + 1; ++colorIndex)
+            free(alphas_of_color[colorIndex]);
+    }
+} 
+
+
+
+static void
+makeOneColorTransparentInPalette(xel            const transColor, 
+                                 bool           const exact,
+                                 pixel                palette_pnm[],
+                                 unsigned int   const paletteSize,
+                                 gray                 trans_pnm[],
+                                 unsigned int * const transSizeP) {
+/*----------------------------------------------------------------------------
+   Find the color 'transColor' in the color/alpha palette defined by
+   palette_pnm[], paletteSize, trans_pnm[] and *transSizeP.  
+
+   Make that entry fully transparent.
+
+   Rearrange the palette so that that entry is first.  (The PNG compressor
+   can do a better job when the opaque entries are all last in the 
+   color/alpha palette).
+
+   If the specified color is not there and exact == TRUE, return
+   without changing anything, but issue a warning message.  If it's
+   not there and exact == FALSE, just find the closest color.
+
+   We assume every entry in the palette is opaque upon entry.
+
+   A valid palette has at least one color.
+-----------------------------------------------------------------------------*/
+    unsigned int transparentIndex;
+    unsigned int distance;
+
+    assert(paletteSize > 0);
+    
+    if (*transSizeP != 0)
+        pm_error("Internal error: trying to make a color in the palette "
+                 "transparent where there already is one.");
+
+    closestColorInPalette(transColor, palette_pnm, paletteSize, 
+                          &transparentIndex, &distance);
+
+    if (distance != 0 && exact) {
+        pm_message("specified transparent color not present in palette; "
+                   "ignoring -transparent");
+        errorlevel = PNMTOPNG_WARNING_LEVEL;
+    } else {        
+        /* Swap this with the first entry in the palette */
+        pixel tmp;
+    
+        tmp = palette_pnm[transparentIndex];
+        palette_pnm[transparentIndex] = palette_pnm[0];
+        palette_pnm[0] = tmp;
+        
+        /* Make it transparent */
+        trans_pnm[0] = PGM_TRANSPARENT;
+        *transSizeP = 1;
+        if (verbose) {
+            pixel const p = palette_pnm[0];
+            pm_message("Making all occurences of color (%u, %u, %u) "
+                       "transparent.",
+                       PPM_GETR(p), PPM_GETG(p), PPM_GETB(p));
+        }
+    }
+}
+
+
+
+static void
+findOrAddBackgroundInPalette(pixel          const backColor, 
+                             pixel                palette_pnm[], 
+                             unsigned int * const paletteSizeP,
+                             unsigned int * const backgroundIndexP) {
+/*----------------------------------------------------------------------------
+  Add the background color 'backColor' to the palette, unless
+  it's already in there.  If it's not present and there's no room to
+  add it, choose a background color that's already in the palette,
+  as close to 'backColor' as possible.
+
+  If we add an entry to the palette, make it opaque.  But in searching the 
+  existing palette, ignore transparency.
+
+  Note that PNG specs say that transparency of the background is meaningless;
+  i.e. a viewer must ignore the transparency of the palette entry when 
+  using the background color.
+
+  Return the palette index of the background color as *backgroundIndexP.
+-----------------------------------------------------------------------------*/
+    int backgroundIndex;  /* negative means not found */
+    unsigned int paletteIndex;
+
+    backgroundIndex = -1;
+    for (paletteIndex = 0; 
+         paletteIndex < *paletteSizeP; 
+         ++paletteIndex) 
+        if (PPM_EQUAL(palette_pnm[paletteIndex], backColor))
+            backgroundIndex = paletteIndex;
+
+    if (backgroundIndex >= 0) {
+        /* The background color is already in the palette. */
+        *backgroundIndexP = backgroundIndex;
+        if (verbose) {
+            pixel const p = palette_pnm[*backgroundIndexP];
+            pm_message("background color (%u, %u, %u) appears in image.",
+                       PPM_GETR(p), PPM_GETG(p), PPM_GETB(p));
+        }
+    } else {
+        /* Try to add the background color, opaque, to the palette. */
+        if (*paletteSizeP < MAXCOLORS) {
+            /* There's room, so just add it to the end of the palette */
+
+            /* Because we're not expanding the transparency palette, this
+               entry is not in it, and is thus opaque.
+            */
+            *backgroundIndexP = (*paletteSizeP)++;
+            palette_pnm[*backgroundIndexP] = backColor;
+            if (verbose) {
+                pixel const p = palette_pnm[*backgroundIndexP];
+                pm_message("added background color (%u, %u, %u) to palette.",
+                           PPM_GETR(p), PPM_GETG(p), PPM_GETB(p));
+            }
+        } else {
+            closestColorInPalette(backColor, palette_pnm, *paletteSizeP,
+                                  backgroundIndexP, NULL);
+            errorlevel = PNMTOPNG_WARNING_LEVEL;
+            {
+                pixel const p = palette_pnm[*backgroundIndexP];
+                pm_message("no room in palette for background color; "
+                           "using closest match (%u, %u, %u) instead",
+                           PPM_GETR(p), PPM_GETG(p), PPM_GETB(p));
+            }
+        }
+    }
+}
+
+
+
+static void 
+buildColorLookup(pixel                   palette_pnm[], 
+                 unsigned int      const paletteSize,
+                 colorhash_table * const chtP) {
+/*----------------------------------------------------------------------------
+   Create a colorhash_table out of the palette described by
+   palette_pnm[] (which has 'paletteSize' entries) so one can look up
+   the palette index of a given color.
+
+   Where the same color appears twice in the palette, the lookup table
+   finds an arbitrary one of them.  We don't consider transparency of
+   palette entries, so if the same color appears in the palette once
+   transparent and once opaque, the lookup table finds an arbitrary one
+   of those two.
+-----------------------------------------------------------------------------*/
+    colorhash_table const cht = ppm_alloccolorhash();
+    unsigned int paletteIndex;
+
+    for (paletteIndex = 0; paletteIndex < paletteSize; ++paletteIndex) {
+        ppm_addtocolorhash(cht, &palette_pnm[paletteIndex], paletteIndex);
+    }
+    *chtP = cht;
+}
+
+
+static void 
+buildColorAlphaLookup(pixel              palette_pnm[], 
+                      unsigned int const paletteSize,
+                      gray               trans_pnm[], 
+                      unsigned int const transSize,
+                      gray         const alphaMaxval,
+                      coloralphahash_table * const cahtP) {
+    
+    coloralphahash_table const caht = alloccoloralphahash();
+
+    unsigned int paletteIndex;
+
+    for (paletteIndex = 0; paletteIndex < paletteSize; ++paletteIndex) {
+        gray paletteTrans;
+
+        if (paletteIndex < transSize)
+            paletteTrans = alphaMaxval;
+        else
+            paletteTrans = trans_pnm[paletteIndex];
+
+
+        addtocoloralphahash(caht, &palette_pnm[paletteIndex],
+                            &trans_pnm[paletteIndex], paletteIndex);
+    }
+    *cahtP = caht;
+}
+
+
+
+static void
+tryAlphaPalette(FILE *         const ifP,
+                int            const cols,
+                int            const rows,
+                xelval         const maxval,
+                int            const format,
+                pm_filepos     const rasterPos,
+                gray **        const alpha_mask,
+                FILE *         const pfP,
+                pixel *        const palette_pnm,
+                unsigned int * const paletteSizeP,
+                gray *         const trans_pnm,
+                unsigned int * const transSizeP,
+                const char **  const impossibleReasonP) {
+/*----------------------------------------------------------------------------
+   Try to make an alpha palette as 'trans_pnm', size *transSizeP.
+
+   If it's impossible, return as *impossibleReasonP newly malloced storage
+   containing text that tells why.  But if we succeed, return
+   *impossibleReasonP == NULL.
+-----------------------------------------------------------------------------*/
+    bool tooBig;
+    if (pfP)
+        pm_error("This program is not capable of generating "
+                 "a PNG with transparency when you specify "
+                 "the palette with -palette.");
+
+    compute_alpha_palette(ifP, cols, rows, maxval, format, 
+                          rasterPos,  alpha_mask, palette_pnm, trans_pnm, 
+                          paletteSizeP, transSizeP, &tooBig);
+    if (tooBig) {
+        asprintfN(impossibleReasonP,
+                  "too many color/transparency pairs "
+                  "(more than the PNG maximum of %u", 
+                  MAXPALETTEENTRIES);
+    } else
+        *impossibleReasonP = NULL;
+} 
+
+
+
+static void
+computePixelWidth(int            const pnm_type,
+                  unsigned int   const pnm_meaningful_bits,
+                  bool           const alpha,
+                  unsigned int * const bitsPerSampleP,
+                  unsigned int * const bitsPerPixelP) {
+
+    unsigned int bitsPerSample, bitsPerPixel;
+
+    if (pnm_type == PPM_TYPE || alpha) {
+        /* PNG allows only depths of 8 and 16 for a truecolor image 
+           and for a grayscale image with an alpha channel.
+          */
+        if (pnm_meaningful_bits > 8)
+            bitsPerSample = 16;
+        else 
+            bitsPerSample = 8;
+    } else {
+        /* A grayscale, non-colormapped, no-alpha PNG may have any 
+             bit depth from 1 to 16
+          */
+        if (pnm_meaningful_bits > 8)
+            bitsPerSample = 16;
+        else if (pnm_meaningful_bits > 4)
+            bitsPerSample = 8;
+        else if (pnm_meaningful_bits > 2)
+            bitsPerSample = 4;
+        else if (pnm_meaningful_bits > 1)
+            bitsPerSample = 2;
+        else
+            bitsPerSample = 1;
+    }
+    if (alpha) {
+        if (pnm_type == PPM_TYPE)
+            bitsPerPixel = 4 * bitsPerSample;
+        else
+            bitsPerPixel = 2 * bitsPerSample;
+    } else {
+        if (pnm_type == PPM_TYPE)
+            bitsPerPixel = 3 * bitsPerSample;
+        else
+            bitsPerPixel = bitsPerSample;
+    }
+    if (bitsPerPixelP)
+        *bitsPerPixelP = bitsPerPixel;
+    if (bitsPerSampleP)
+        *bitsPerSampleP = bitsPerSample;
+}
+
+
+
+static unsigned int
+paletteIndexBits(unsigned int const nColors) {
+/*----------------------------------------------------------------------------
+  Return the number of bits that a palette index in the PNG will
+  occupy given that the palette has 'nColors' colors in it.  It is 1,
+  2, 4, or 8 bits.
+  
+  If 'nColors' is not a valid PNG palette size, return 0.
+-----------------------------------------------------------------------------*/
+    unsigned int retval;
+
+    if (nColors < 1)
+        retval = 0;
+    else if (nColors <= 2)
+        retval = 1;
+    else if (nColors <= 4)
+        retval = 2;
+    else if (nColors <= 16)
+        retval = 4;
+    else if (nColors <= 256)
+        retval = 8;
+    else
+        retval = 0;
+
+    return retval;
+}
+
+
+
+static void
+computeColorMap(FILE *         const ifP,
+                pm_filepos     const rasterPos,
+                int            const cols,
+                int            const rows,
+                xelval         const maxval,
+                int            const format,
+                bool           const force,
+                FILE *         const pfP,
+                bool           const alpha,
+                bool           const transparent,
+                pixel          const transcolor,
+                bool           const transexact,
+                bool           const background,
+                pixel          const backcolor,
+                gray **        const alpha_mask,
+                unsigned int   const pnm_meaningful_bits,
+                /* Outputs */
+                pixel *        const palette_pnm,
+                unsigned int * const paletteSizeP,
+                gray *         const trans_pnm,
+                unsigned int * const transSizeP,
+                unsigned int * const backgroundIndexP,
+                const char **  const noColormapReasonP) {
+/*---------------------------------------------------------------------------
+  Determine whether to do a colormapped or truecolor PNG and if
+  colormapped, compute the full PNG palette -- both color and
+  transparency.
+
+  If we decide to do truecolor, we return as *noColormapReasonP a text
+  description of why, in newly malloced memory.  If we decide to go
+  with colormapped, we return *noColormapReasonP == NULL.
+
+  In the colormapped case, we return the palette as arrays
+  palette_pnm[] and trans_pnm[], allocated by Caller, with sizes
+  *paletteSizeP and *transSizeP.
+
+  'background' means the image is to have a background color, and that
+  color is 'backcolor'.  'backcolor' is meaningless when 'background'
+  is false.
+
+  If the image is to have a background color, we return the palette index
+  of that color as *backgroundIndexP.
+-------------------------------------------------------------------------- */
+    if (force)
+        asprintfN(noColormapReasonP, "You requested no color map");
+    else if (maxval > PALETTEMAXVAL)
+        asprintfN(noColormapReasonP, "The maxval of the input image (%u) "
+                  "exceeds the PNG palette maxval (%u)", 
+                  maxval, PALETTEMAXVAL);
+    else {
+        unsigned int bitsPerPixel;
+        computePixelWidth(PNM_FORMAT_TYPE(format), pnm_meaningful_bits, alpha,
+                          NULL, &bitsPerPixel);
+
+        if (!pfP && bitsPerPixel == 1)
+            /* No palette can beat 1 bit per pixel -- no need to waste time
+               counting the colors.
+            */
+            asprintfN(noColormapReasonP, "pixel is already only 1 bit");
+        else {
+            /* We'll have to count the colors ('colors') to know if a
+               palette is possible and desirable.  Along the way, we'll
+               compute the actual set of colors (chv) too, and then create
+               the palette itself if we decide we want one.
+            */
+            colorhist_vector chv;
+            unsigned int colors;
+            
+            getChv(ifP, rasterPos, cols, rows, maxval, format, MAXCOLORS, 
+                   &chv, &colors);
+
+            if (chv == NULL) {
+                asprintfN(noColormapReasonP, 
+                          "More than %u colors found -- too many for a "
+                          "colormapped PNG", MAXCOLORS);
+            } else {
+                /* There are few enough colors that a palette is possible */
+                if (bitsPerPixel <= paletteIndexBits(colors) && !pfP)
+                    asprintfN(noColormapReasonP, 
+                              "palette index for %u colors would be "
+                              "no smaller than the indexed value (%u bits)", 
+                              colors, bitsPerPixel);
+                else {
+                    unsigned int paletteSize;
+                    unsigned int transSize;
+                    if (alpha)
+                        tryAlphaPalette(ifP, cols, rows, maxval, format,
+                                        rasterPos, alpha_mask, pfP,
+                                        palette_pnm, &paletteSize, 
+                                        trans_pnm, &transSize,
+                                        noColormapReasonP);
+
+                    else {
+                        *noColormapReasonP = NULL;
+
+                        compute_nonalpha_palette(chv, colors, maxval, pfP,
+                                                 palette_pnm, &paletteSize, 
+                                                 trans_pnm, &transSize);
+    
+                        if (transparent)
+                            makeOneColorTransparentInPalette(
+                                transcolor, transexact, 
+                                palette_pnm, paletteSize, trans_pnm, 
+                                &transSize);
+                    }
+                    if (!*noColormapReasonP) {
+                        if (background)
+                            findOrAddBackgroundInPalette(
+                                backcolor, palette_pnm, &paletteSize,
+                                backgroundIndexP);
+                        *paletteSizeP = paletteSize;
+                        *transSizeP   = transSize;
+                    }
+                }
+            }
+            freeChv();
+        }
+    }
+}
+
+
+
+static void computeColorMapLookupTable(
+    bool                   const colorMapped,
+    pixel                        palette_pnm[],
+    unsigned int           const palette_size,
+    gray                         trans_pnm[],
+    unsigned int           const trans_size,
+    bool                   const alpha,
+    xelval                 const alpha_maxval,
+    colorhash_table *      const chtP,
+    coloralphahash_table * const cahtP) {
+/*----------------------------------------------------------------------------
+   Compute applicable lookup tables for the palette index.  If there's no
+   alpha mask, this is just a standard Netpbm colorhash_table.  If there's
+   an alpha mask, it is the slower Pnmtopng-specific 
+   coloralphahash_table.
+
+   If a lookup table is not applicable to the image, return NULL as
+   its address.  (If the image is not colormapped, both will be NULL).
+-----------------------------------------------------------------------------*/
+    if (colorMapped) {
+        if (alpha) {
+            buildColorAlphaLookup(palette_pnm, palette_size, 
+                                  trans_pnm, trans_size, alpha_maxval, cahtP);
+            *chtP = NULL;
+        } else { 
+            buildColorLookup(palette_pnm, palette_size, chtP);
+            *cahtP = NULL;
+        }
+        if (verbose)
+            pm_message("PNG palette has %u entries, %u of them non-opaque",
+                       palette_size, trans_size);
+    } else {
+        *chtP = NULL;
+        *cahtP = NULL;
+    }
+}
+
+
+
+static void
+computeRasterWidth(bool           const colorMapped,
+                   unsigned int   const palette_size,
+                   int            const pnm_type,
+                   unsigned int   const pnm_meaningful_bits,
+                   bool           const alpha,
+                   unsigned int * const bitsPerSampleP,
+                   unsigned int * const bitsPerPixelP) {
+/*----------------------------------------------------------------------------
+   Compute the number of bits per raster sample and per raster pixel:
+   *bitsPerSampleP and *bitsPerPixelP.  Note that a raster element may be a
+   palette index, or a gray value or color with or without alpha mask.
+-----------------------------------------------------------------------------*/
+    if (colorMapped) {
+        /* The raster element is a palette index */
+        if (palette_size <= 2)
+            *bitsPerSampleP = 1;
+        else if (palette_size <= 4)
+            *bitsPerSampleP = 2;
+        else if (palette_size <= 16)
+            *bitsPerSampleP = 4;
+        else
+            *bitsPerSampleP = 8;
+        *bitsPerPixelP = *bitsPerSampleP;
+        if (verbose)
+            pm_message("Writing %d-bit color indexes", *bitsPerSampleP);
+    } else {
+        /* The raster element is an explicit pixel -- color and transparency */
+        computePixelWidth(pnm_type, pnm_meaningful_bits, alpha,
+                          bitsPerSampleP, bitsPerPixelP);
+
+        if (verbose)
+            pm_message("Writing %d bits per component per pixel", 
+                       *bitsPerSampleP);
+    }
+}
+
+
+static void
+createPngPalette(pixel              palette_pnm[], 
+                 unsigned int const paletteSize, 
+                 pixval       const maxval,
+                 gray               trans_pnm[],
+                 unsigned int const transSize,
+                 gray               alpha_maxval,
+                 png_color          palette[],
+                 png_byte           trans[]) {
+/*----------------------------------------------------------------------------
+   Create the data structure to be passed to the PNG compressor to represent
+   the palette -- the whole palette, color + transparency.
+
+   This is basically just a maxval conversion from the Netpbm-format
+   equivalents we get as input.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+
+    for (i = 0; i < paletteSize; ++i) {
+        pixel p;
+        PPM_DEPTH(p, palette_pnm[i], maxval, PALETTEMAXVAL);
+        palette[i].red   = PPM_GETR(p);
+        palette[i].green = PPM_GETG(p);
+        palette[i].blue  = PPM_GETB(p);
+    }
+
+    for (i = 0; i < transSize; ++i) {
+        unsigned int const newmv = PALETTEMAXVAL;
+        unsigned int const oldmv = alpha_maxval;
+        trans[i] = (trans_pnm[i] * newmv + (oldmv/2)) / oldmv;
+    }
+}
+
+
+
+static void
+setCompressionSize(png_struct * const png_ptr,
+                   int const    buffer_size) {
+
+#if PNG_LIBPNG_VER >= 10009
+    png_set_compression_buffer_size(png_ptr, buffer_size);
+#else
+    pm_error("Your PNG library cannot set the compression buffer size.  "
+             "You need at least Version 1.0.9 of Libpng; you have Version %s",
+             PNG_LIBPNG_VER_STRING);
+#endif
+}
+
+
+
+static void
+setZlibCompression(png_struct *           const png_ptr,
+                   struct zlibCompression const zlibCompression) {
+
+    if (zlibCompression.levelSpec)
+        png_set_compression_level(png_ptr, zlibCompression.level);
+
+    if (zlibCompression.memLevelSpec)
+        png_set_compression_mem_level(png_ptr, zlibCompression.mem_level);
+
+    if (zlibCompression.strategySpec)
+        png_set_compression_strategy(png_ptr, zlibCompression.strategy);
+
+    if (zlibCompression.windowBitsSpec)
+        png_set_compression_window_bits(png_ptr, zlibCompression.window_bits);
+
+    if (zlibCompression.methodSpec)
+        png_set_compression_method(png_ptr, zlibCompression.method);
+
+    if (zlibCompression.bufferSizeSpec) {
+        setCompressionSize(png_ptr, zlibCompression.buffer_size);
+    }
+}
+                  
+
+
+static void
+makePngLine(png_byte *           const line,
+            const xel *          const xelrow,
+            unsigned int         const cols,
+            xelval               const maxval,
+            bool                 const alpha,
+            gray *               const alpha_mask,
+            colorhash_table      const cht,
+            coloralphahash_table const caht,
+            png_info *           const info_ptr,
+            xelval               const png_maxval,
+            unsigned int         const depth) {
+            
+    unsigned int col;
+    png_byte *pp;
+
+    pp = line;  /* start at beginning of line */
+    for (col = 0; col < cols; ++col) {
+        xel p_png;
+        xel const p = xelrow[col];
+        PPM_DEPTH(p_png, p, maxval, png_maxval);
+        if (info_ptr->color_type == PNG_COLOR_TYPE_GRAY ||
+            info_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+            if (depth == 16)
+                *pp++ = PNM_GET1(p_png) >> 8;
+            *pp++ = PNM_GET1(p_png) & 0xff;
+        } else if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) {
+            unsigned int paletteIndex;
+            if (alpha)
+                paletteIndex = lookupColorAlpha(caht, &p, &alpha_mask[col]);
+            else
+                paletteIndex = ppm_lookupcolor(cht, &p);
+            *pp++ = paletteIndex;
+        } else if (info_ptr->color_type == PNG_COLOR_TYPE_RGB ||
+                   info_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
+            if (depth == 16)
+                *pp++ = PPM_GETR(p_png) >> 8;
+            *pp++ = PPM_GETR(p_png) & 0xff;
+            if (depth == 16)
+                *pp++ = PPM_GETG(p_png) >> 8;
+            *pp++ = PPM_GETG(p_png) & 0xff;
+            if (depth == 16)
+                *pp++ = PPM_GETB(p_png) >> 8;
+            *pp++ = PPM_GETB(p_png) & 0xff;
+        } else
+            pm_error("INTERNAL ERROR: undefined color_type");
+                
+        if (info_ptr->color_type & PNG_COLOR_MASK_ALPHA) {
+            int const png_alphaval = (int)
+                alpha_mask[col] * (float) png_maxval / maxval + 0.5;
+            if (depth == 16)
+                *pp++ = png_alphaval >> 8;
+            *pp++ = png_alphaval & 0xff;
+        }
+    }
+}
+
+
+
+static void
+writeRaster(png_struct *         const png_ptr,
+            png_info *           const info_ptr,
+            FILE *               const ifP,
+            pm_filepos           const rasterPos,
+            unsigned int         const cols,
+            unsigned int         const rows,
+            xelval               const maxval,
+            int                  const format,
+            xelval               const png_maxval,
+            unsigned             const int depth,
+            bool                 const alpha,
+            gray **              const alpha_mask,
+            colorhash_table      const cht,
+            coloralphahash_table const caht
+            ) {
+/*----------------------------------------------------------------------------
+   Write the PNG raster via compressor *png_ptr, reading the PNM raster
+   from file *ifP, position 'rasterPos'.
+
+   The PNG raster consists of IDAT chunks.
+
+   'alpha_mask' is defined only if 'alpha' is true.
+-----------------------------------------------------------------------------*/
+    xel * xelrow;
+    png_byte * line;
+    unsigned int pass;
+
+    xelrow = pnm_allocrow(cols);
+
+    /* max: 3 color channels, one alpha channel, 16-bit */
+    MALLOCARRAY(line, cols * 8);
+    if (line == NULL)
+        pm_error("out of memory allocating PNG row buffer");
+
+    for (pass = 0; pass < png_set_interlace_handling(png_ptr); ++pass) {
+        unsigned int row;
+        pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
+        for (row = 0; row < rows; ++row) {
+            pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+            pnm_promoteformatrow(xelrow, cols, maxval, format, maxval,
+                                 PPM_TYPE);
+            
+            makePngLine(line, xelrow, cols, maxval,
+                        alpha, alpha ? alpha_mask[row] : NULL,
+                        cht, caht, info_ptr, png_maxval, depth);
+
+            png_write_row(png_ptr, line);
+        }
+    }
+    pnm_freerow(xelrow);
+}
+
+
+
+static void
+doGamaChunk(struct cmdlineInfo const cmdline,
+            png_info *         const info_ptr) {
+            
+    if (cmdline.gammaSpec) {
+        /* gAMA chunk */
+        info_ptr->valid |= PNG_INFO_gAMA;
+        info_ptr->gamma = cmdline.gamma;
+    }
+}
+
+
+
+static void
+doChrmChunk(struct cmdlineInfo const cmdline,
+            png_info *         const info_ptr) {
+
+    if (cmdline.rgbSpec) {
+        /* cHRM chunk */
+        info_ptr->valid |= PNG_INFO_cHRM;
+
+        info_ptr->x_white = cmdline.rgb.wx;
+        info_ptr->y_white = cmdline.rgb.wy;
+        info_ptr->x_red   = cmdline.rgb.rx;
+        info_ptr->y_red   = cmdline.rgb.ry;
+        info_ptr->x_green = cmdline.rgb.gx;
+        info_ptr->y_green = cmdline.rgb.gy;
+        info_ptr->x_blue  = cmdline.rgb.bx;
+        info_ptr->y_blue  = cmdline.rgb.by;
+    }
+}
+
+
+
+static void
+doPhysChunk(struct cmdlineInfo const cmdline,
+            png_info *         const info_ptr) {
+
+    if (cmdline.sizeSpec) {
+        /* pHYS chunk */
+        info_ptr->valid |= PNG_INFO_pHYs;
+
+        info_ptr->x_pixels_per_unit = cmdline.size.x;
+        info_ptr->y_pixels_per_unit = cmdline.size.y;
+        info_ptr->phys_unit_type    = cmdline.size.unit;
+    }
+}
+
+
+
+
+static void
+doTimeChunk(struct cmdlineInfo const cmdline,
+            png_info *         const info_ptr) {
+
+    if (cmdline.modtimeSpec) {
+        /* tIME chunk */
+        info_ptr->valid |= PNG_INFO_tIME;
+
+        png_convert_from_time_t(&info_ptr->mod_time, cmdline.modtime);
+    }
+}
+
+
+
+static void
+doSbitChunk(png_info * const pngInfoP,
+            xelval     const pngMaxval,
+            xelval     const maxval,
+            bool       const alpha,
+            xelval     const alphaMaxval) {
+
+    if (pngInfoP->color_type != PNG_COLOR_TYPE_PALETTE &&
+        (pngMaxval > maxval || (alpha && pngMaxval > alphaMaxval))) {
+
+        /* We're writing in a bit depth that doesn't match the maxval
+           of the input image and the alpha mask.  So we write an sBIT
+           chunk to tell what the original image's maxval was.  The
+           sBit chunk doesn't let us specify any maxval -- only powers
+           of two minus one.  So we pick the power of two minus one
+           which is greater than or equal to the actual input maxval.
+           
+           PNG also doesn't let an sBIT chunk indicate a maxval
+           _greater_ than the the PNG maxval.  The designers probably
+           did not conceive of the case where that would happen.  The
+           case is this: We detected redundancy in the bits so were
+           able to store fewer bits than the user provided.  But since
+           PNG doesn't allow it, we don't attempt to create such an
+           sBIT chunk.
+        */
+
+        pngInfoP->valid |= PNG_INFO_sBIT;
+
+        {
+            int const sbitval = pm_maxvaltobits(MIN(maxval, pngMaxval));
+
+            if (pngInfoP->color_type & PNG_COLOR_MASK_COLOR) {
+                pngInfoP->sig_bit.red   = sbitval;
+                pngInfoP->sig_bit.green = sbitval;
+                pngInfoP->sig_bit.blue  = sbitval;
+            } else
+                pngInfoP->sig_bit.gray = sbitval;
+            
+            if (verbose)
+                pm_message("Writing sBIT chunk with bits = %d", sbitval);
+        }
+        if (pngInfoP->color_type & PNG_COLOR_MASK_ALPHA) {
+            pngInfoP->sig_bit.alpha =
+                pm_maxvaltobits(MIN(alphaMaxval, pngMaxval));
+            if (verbose)
+                pm_message("  alpha bits = %d", pngInfoP->sig_bit.alpha);
+        }
+    }
+}
+
+
+
+static void 
+convertpnm(struct cmdlineInfo const cmdline,
+           FILE *             const ifp,
+           FILE *             const afp,
+           FILE *             const pfp,
+           FILE *             const tfp,
+           int *              const errorLevelP
+    ) {
+/*----------------------------------------------------------------------------
+   Design note:  It's is really a modularity violation that we have
+   all the command line parameters as an argument.  We do it because we're
+   lazy -- it takes a great deal of work to carry all that information as
+   separate arguments -- and it's only a very small violation.
+-----------------------------------------------------------------------------*/
+  xel p;
+  int rows, cols, format;
+  xelval maxval;
+      /* The maxval of the input image */
+  xelval png_maxval;
+      /* The maxval of the samples in the PNG output 
+         (must be 1, 3, 7, 15, 255, or 65535)
+      */
+  pixel transcolor;
+      /* The color that is to be transparent, with maxval equal to that
+         of the input image.
+      */
+  int transexact;  
+    /* boolean: the user wants only the exact color he specified to be
+       transparent; not just something close to it.
+    */
+  int transparent;
+  bool alpha;
+    /* There will be an alpha mask */
+  unsigned int pnm_meaningful_bits;
+  pixel backcolor;
+      /* The background color, with maxval equal to that of the input
+         image.
+      */
+  png_struct *png_ptr;
+  png_info *info_ptr;
+
+  bool colorMapped;
+  pixel palette_pnm[MAXCOLORS];
+  png_color palette[MAXCOLORS];
+      /* The color part of the color/alpha palette passed to the PNG
+         compressor 
+      */
+  unsigned int palette_size;
+
+  gray trans_pnm[MAXCOLORS];
+  png_byte  trans[MAXCOLORS];
+      /* The alpha part of the color/alpha palette passed to the PNG
+         compressor 
+      */
+  unsigned int trans_size;
+
+  colorhash_table cht;
+  coloralphahash_table caht;
+
+  unsigned int background_index;
+      /* Index into palette[] of the background color. */
+
+  png_uint_16 histogram[MAXCOLORS];
+  gray alpha_maxval;
+  int alpha_rows;
+  int alpha_cols;
+  const char * noColormapReason;
+      /* The reason that we shouldn't make a colormapped PNG, or NULL if
+         we should.  malloc'ed null-terminated string.
+      */
+  unsigned int depth;
+      /* The number of bits per sample in the (uncompressed) png 
+         raster -- if the raster contains palette indices, this is the
+         number of bits in the index.
+      */
+  unsigned int fulldepth;
+      /* The total number of bits per pixel in the (uncompressed) png
+         raster, including all channels 
+      */
+  pm_filepos rasterPos;  
+      /* file position in input image file of start of image (i.e. after
+         the header)
+      */
+  xel *xelrow;    /* malloc'ed */
+      /* The row of the input image currently being processed */
+
+  int pnm_type;
+  xelval maxmaxval;
+  gray ** alpha_mask;
+
+  /* these guys are initialized to quiet compiler warnings: */
+  maxmaxval = 255;
+  alpha_mask = NULL;
+  depth = 0;
+  errorlevel = 0;
+
+  png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
+    &pnmtopng_jmpbuf_struct, pnmtopng_error_handler, NULL);
+  if (png_ptr == NULL) {
+    pm_closer (ifp);
+    pm_error ("cannot allocate main libpng structure (png_ptr)");
+  }
+
+  info_ptr = png_create_info_struct (png_ptr);
+  if (info_ptr == NULL) {
+    png_destroy_write_struct (&png_ptr, (png_infopp)NULL);
+    pm_closer (ifp);
+    pm_error ("cannot allocate libpng info structure (info_ptr)");
+  }
+
+  if (setjmp (pnmtopng_jmpbuf_struct.jmpbuf)) {
+    png_destroy_write_struct (&png_ptr, &info_ptr);
+    pm_closer (ifp);
+    pm_error ("setjmp returns error condition (1)");
+  }
+
+  pnm_readpnminit (ifp, &cols, &rows, &maxval, &format);
+  pm_tell2(ifp, &rasterPos, sizeof(rasterPos));
+  pnm_type = PNM_FORMAT_TYPE (format);
+
+  xelrow = pnm_allocrow(cols);
+
+  if (verbose) {
+    if (pnm_type == PBM_TYPE)    
+      pm_message ("reading a PBM file (maxval=%d)", maxval);
+    else if (pnm_type == PGM_TYPE)    
+      pm_message ("reading a PGM file (maxval=%d)", maxval);
+    else if (pnm_type == PPM_TYPE)    
+      pm_message ("reading a PPM file (maxval=%d)", maxval);
+  }
+
+  if (pnm_type == PGM_TYPE)
+    maxmaxval = PGM_OVERALLMAXVAL;
+  else if (pnm_type == PPM_TYPE)
+    maxmaxval = PPM_OVERALLMAXVAL;
+
+  if (cmdline.transparent) {
+      const char * transstring2;  
+          /* The -transparent value, but with possible leading '=' removed */
+      if (cmdline.transparent[0] == '=') {
+          transexact = 1;
+          transstring2 = &cmdline.transparent[1];
+      } else {
+          transexact = 0;
+          transstring2 = cmdline.transparent;
+      }  
+      /* We do this funny PPM_DEPTH thing instead of just passing 'maxval'
+         to ppm_parsecolor() because ppm_parsecolor() does a cheap maxval
+         scaling, and this is more precise.
+      */
+      PPM_DEPTH (transcolor, ppm_parsecolor(transstring2, maxmaxval),
+                 maxmaxval, maxval);
+  }
+  if (cmdline.alpha) {
+    pixel alpha_transcolor;
+    bool alpha_can_be_transparency_index;
+    bool all_opaque;
+
+    if (verbose)
+      pm_message ("reading alpha-channel image...");
+    alpha_mask = pgm_readpgm (afp, &alpha_cols, &alpha_rows, &alpha_maxval);
+
+    if (alpha_cols != cols || alpha_rows != rows) {
+      png_destroy_write_struct (&png_ptr, &info_ptr);
+      pm_closer (ifp);
+      pm_error ("dimensions for image and alpha mask do not agree");
+    }
+    analyzeAlpha(ifp, rasterPos, cols, rows, maxval, format, 
+                 alpha_mask, alpha_maxval, &all_opaque,
+                 &alpha_can_be_transparency_index, &alpha_transcolor);
+
+    if (alpha_can_be_transparency_index && !cmdline.force) {
+      if (verbose)
+        pm_message ("converting alpha mask to transparency index");
+      alpha = FALSE;
+      transparent = 2;
+      transcolor = alpha_transcolor;
+    } else if (all_opaque) {
+        alpha = FALSE;
+        transparent = -1;
+    } else {
+      alpha = TRUE;
+      transparent = -1;
+    }
+  } else {
+      /* Though there's no alpha_mask, we still need an alpha_maxval for
+         use with trans[], which can have stuff in it if the user specified
+         a transparent color.
+      */
+      alpha = FALSE;
+      alpha_maxval = 255;
+      transparent = cmdline.transparent ? 1 : -1;
+  }
+  if (cmdline.background) 
+      PPM_DEPTH(backcolor, ppm_parsecolor(cmdline.background, maxmaxval), 
+                maxmaxval, maxval);;
+
+  /* first of all, check if we have a grayscale image written as PPM */
+
+  if (pnm_type == PPM_TYPE && !cmdline.force) {
+      unsigned int row;
+      bool isgray;
+
+      isgray = TRUE;  /* initial assumption */
+      pm_seek2(ifp, &rasterPos, sizeof(rasterPos));
+      for (row = 0; row < rows && isgray; ++row) {
+          unsigned int col;
+          pnm_readpnmrow(ifp, xelrow, cols, maxval, format);
+          for (col = 0; col < cols && isgray; ++col) {
+              p = xelrow[col];
+              if (PPM_GETR(p) != PPM_GETG(p) || PPM_GETG(p) != PPM_GETB(p))
+                  isgray = FALSE;
+          }
+      }
+      if (isgray)
+          pnm_type = PGM_TYPE;
+  }
+
+  /* handle `odd' maxvalues */
+
+  if (maxval > 65535 && !cmdline.downscale) {
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      pm_closer(ifp);
+      pm_error("can only handle files up to 16-bit "
+               "(use -downscale to override");
+  }
+
+  findRedundantBits(ifp, rasterPos, cols, rows, maxval, format, alpha,
+                    cmdline.force, &pnm_meaningful_bits);
+  
+  computeColorMap(ifp, rasterPos, cols, rows, maxval, format,
+                  cmdline.force, pfp,
+                  alpha, transparent >= 0, transcolor, transexact, 
+                  !!cmdline.background, backcolor,
+                  alpha_mask, pnm_meaningful_bits,
+                  palette_pnm, &palette_size, trans_pnm, &trans_size,
+                  &background_index, &noColormapReason);
+
+  if (noColormapReason) {
+      if (pfp)
+          pm_error("You specified a particular palette, but this image "
+                   "cannot be represented by any palette.  %s",
+                   noColormapReason);
+      if (verbose)
+          pm_message("Not using color map.  %s", noColormapReason);
+      strfree(noColormapReason);
+      colorMapped = FALSE;
+  } else
+      colorMapped = TRUE;
+  
+  computeColorMapLookupTable(colorMapped, palette_pnm, palette_size,
+                             trans_pnm, trans_size, alpha, alpha_maxval,
+                             &cht, &caht);
+
+  computeRasterWidth(colorMapped, palette_size, pnm_type, 
+                     pnm_meaningful_bits, alpha,
+                     &depth, &fulldepth);
+  if (verbose)
+    pm_message ("writing a%s %d-bit %s%s file%s",
+                fulldepth == 8 ? "n" : "", fulldepth,
+                colorMapped ? "palette": 
+                (pnm_type == PPM_TYPE ? "RGB" : "gray"),
+                alpha ? (colorMapped ? "+transparency" : "+alpha") : "",
+                cmdline.interlace ? " (interlaced)" : "");
+
+  /* now write the file */
+
+  png_maxval = pm_bitstomaxval(depth);
+
+  if (setjmp (pnmtopng_jmpbuf_struct.jmpbuf)) {
+    png_destroy_write_struct (&png_ptr, &info_ptr);
+    pm_closer (ifp);
+    pm_error ("setjmp returns error condition (2)");
+  }
+
+  png_init_io (png_ptr, stdout);
+  info_ptr->width = cols;
+  info_ptr->height = rows;
+  info_ptr->bit_depth = depth;
+
+  if (colorMapped)
+    info_ptr->color_type = PNG_COLOR_TYPE_PALETTE;
+  else if (pnm_type == PPM_TYPE)
+    info_ptr->color_type = PNG_COLOR_TYPE_RGB;
+  else
+    info_ptr->color_type = PNG_COLOR_TYPE_GRAY;
+
+  if (alpha && info_ptr->color_type != PNG_COLOR_TYPE_PALETTE)
+    info_ptr->color_type |= PNG_COLOR_MASK_ALPHA;
+
+  info_ptr->interlace_type = cmdline.interlace;
+
+  doGamaChunk(cmdline, info_ptr);
+
+  doChrmChunk(cmdline, info_ptr);
+
+  doPhysChunk(cmdline, info_ptr);
+
+  if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) {
+
+    /* creating PNG palette  (PLTE and tRNS chunks) */
+
+    createPngPalette(palette_pnm, palette_size, maxval,
+                     trans_pnm, trans_size, alpha_maxval, 
+                     palette, trans);
+    info_ptr->valid |= PNG_INFO_PLTE;
+    info_ptr->palette = palette;
+    info_ptr->num_palette = palette_size;
+    if (trans_size > 0) {
+        info_ptr->valid |= PNG_INFO_tRNS;
+        info_ptr->trans = trans;
+        info_ptr->num_trans = trans_size;   /* omit opaque values */
+    }
+    /* creating hIST chunk */
+    if (cmdline.hist) {
+        colorhist_vector chv;
+        unsigned int colors;
+        colorhash_table cht;
+        
+        getChv(ifp, rasterPos, cols, rows, maxval, format, MAXCOLORS, 
+               &chv, &colors);
+
+        cht = ppm_colorhisttocolorhash (chv, colors);
+                
+        { 
+            unsigned int i;
+            for (i = 0 ; i < MAXCOLORS; ++i) {
+                int const chvIndex = ppm_lookupcolor(cht, &palette_pnm[i]);
+                if (chvIndex == -1)
+                    histogram[i] = 0;
+                else
+                    histogram[i] = chv[chvIndex].value;
+            }
+        }
+
+        ppm_freecolorhash(cht);
+
+        info_ptr->valid |= PNG_INFO_hIST;
+        info_ptr->hist = histogram;
+        if (verbose)
+            pm_message("histogram created");
+    }
+  } else { /* color_type != PNG_COLOR_TYPE_PALETTE */
+    if (info_ptr->color_type == PNG_COLOR_TYPE_GRAY ||
+        info_ptr->color_type == PNG_COLOR_TYPE_RGB) {
+        if (transparent > 0) {
+            info_ptr->valid |= PNG_INFO_tRNS;
+            info_ptr->trans_values = 
+                xelToPngColor_16(transcolor, maxval, png_maxval);
+        }
+    } else {
+        /* This is PNG_COLOR_MASK_ALPHA.  Transparency will be handled
+           by the alpha channel, not a transparency color.
+        */
+    }
+    if (verbose) {
+        if (info_ptr->valid && PNG_INFO_tRNS) 
+            pm_message("Transparent color {gray, red, green, blue} = "
+                       "{%d, %d, %d, %d}",
+                       info_ptr->trans_values.gray,
+                       info_ptr->trans_values.red,
+                       info_ptr->trans_values.green,
+                       info_ptr->trans_values.blue);
+        else
+            pm_message("No transparent color");
+    }
+  }
+
+  /* bKGD chunk */
+  if (cmdline.background) {
+      info_ptr->valid |= PNG_INFO_bKGD;
+      if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) {
+          info_ptr->background.index = background_index;
+      } else {
+          info_ptr->background = 
+              xelToPngColor_16(backcolor, maxval, png_maxval);
+          if (verbose)
+              pm_message("Writing bKGD chunk with background color "
+                         " {gray, red, green, blue} = {%d, %d, %d, %d}",
+                         info_ptr->background.gray, 
+                         info_ptr->background.red, 
+                         info_ptr->background.green, 
+                         info_ptr->background.blue ); 
+      }
+  }
+
+  doSbitChunk(info_ptr, png_maxval, maxval, alpha, alpha_maxval);
+
+  /* tEXT and zTXT chunks */
+  if (cmdline.text || cmdline.ztxt)
+      pnmpng_read_text(info_ptr, tfp, !!cmdline.ztxt, cmdline.verbose);
+
+  doTimeChunk(cmdline, info_ptr);
+
+  if (cmdline.filterSet != 0)
+      png_set_filter(png_ptr, 0, cmdline.filterSet);
+
+  setZlibCompression(png_ptr, cmdline.zlibCompression);
+
+  /* write the png-info struct */
+  png_write_info(png_ptr, info_ptr);
+
+  if (cmdline.text || cmdline.ztxt)
+      /* prevent from being written twice with png_write_end */
+      info_ptr->num_text = 0;
+
+  if (cmdline.modtime)
+      /* prevent from being written twice with png_write_end */
+      info_ptr->valid &= ~PNG_INFO_tIME;
+
+  /* let libpng take care of, e.g., bit-depth conversions */
+  png_set_packing (png_ptr);
+
+  writeRaster(png_ptr, info_ptr, ifp, rasterPos, cols, rows, maxval, format,
+              png_maxval, depth, alpha, alpha_mask, cht, caht);
+
+  png_write_end (png_ptr, info_ptr);
+
+
+#if 0
+  /* The following code may be intended to solve some segfault problem
+     that arises with png_destroy_write_struct().  The latter is the
+     method recommended in the libpng documentation and this program 
+     will not compile under Cygwin because the Windows DLL for libpng
+     does not contain png_write_destroy() at all.  Since the author's
+     comment below does not make it clear what the segfault issue is,
+     we cannot consider it.  -Bryan 00.09.15
+*/
+
+  png_write_destroy (png_ptr);
+  /* flush first because free(png_ptr) can segfault due to jmpbuf problems
+     in png_write_destroy */
+  fflush (stdout);
+  free (png_ptr);
+  free (info_ptr);
+#else
+  png_destroy_write_struct(&png_ptr, &info_ptr);
+#endif
+
+  pnm_freerow(xelrow);
+
+  if (cht)
+      ppm_freecolorhash(cht);
+  if (caht)
+      freecoloralphahash(caht);
+
+  *errorLevelP = errorlevel;
+}
+
+
+
+static void
+displayVersion() {
+
+    fprintf(stderr,"Pnmtopng version %s.\n", NETPBM_VERSION);
+
+    /* We'd like to display the version of libpng with which we're
+       linked, as we do for zlib, but it isn't practical.
+       While libpng is capable of telling you what it's level
+       is, different versions of it do it two different ways: with
+       png_libpng_ver or with png_get_header_ver.  So we have to be
+       compiled for a particular version just to find out what
+       version it is! It's not worth having a link failure, much
+       less a compile failure, if we choose wrong.
+       png_get_header_ver is not in anything older than libpng 1.0.2a
+       (Dec 1998).  png_libpng_ver is not there in libraries built
+       without USE_GLOBAL_ARRAYS.  Cygwin versions are normally built
+       without USE_GLOBAL_ARRAYS.  -bjh 2002.06.17.
+    */
+    fprintf(stderr, "   Compiled with libpng %s.\n",
+            PNG_LIBPNG_VER_STRING);
+    fprintf(stderr, "   Compiled with zlib %s; using zlib %s.\n",
+            ZLIB_VERSION, zlib_version);
+    fprintf(stderr, "\n");
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    FILE * afP;
+    FILE * pfP;
+    FILE * tfP;
+
+    int errorlevel;
+    
+    pnm_init (&argc, argv);
+    
+    parseCommandLine(argc, argv, &cmdline);
+    
+    if (cmdline.libversion) {
+        displayVersion();
+        return 0;
+    }
+    verbose = cmdline.verbose;
+    
+    ifP = pm_openr_seekable(cmdline.inputFilename);
+    
+    if (cmdline.alpha)
+        afP = pm_openr(cmdline.alpha);
+    else
+        afP = NULL;
+    
+    if (cmdline.palette)
+        pfP = pm_openr(cmdline.palette);
+    else
+        pfP = NULL;
+    
+    if (cmdline.text)
+        tfP = pm_openr(cmdline.text);
+    else if (cmdline.ztxt)
+        tfP = pm_openr(cmdline.ztxt);
+    else
+        tfP = NULL;
+
+    convertpnm(cmdline, ifP, afP, pfP, tfP, &errorlevel);
+    
+    if (afP)
+        pm_close(afP);
+    if (pfP)
+        pm_close(pfP);
+    if (tfP)
+        pm_close(tfP);
+
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return errorlevel;
+}