about summary refs log tree commit diff
path: root/converter/other/pamtopng.c
diff options
context:
space:
mode:
Diffstat (limited to 'converter/other/pamtopng.c')
-rw-r--r--converter/other/pamtopng.c825
1 files changed, 825 insertions, 0 deletions
diff --git a/converter/other/pamtopng.c b/converter/other/pamtopng.c
new file mode 100644
index 00000000..fdeb6582
--- /dev/null
+++ b/converter/other/pamtopng.c
@@ -0,0 +1,825 @@
+/*
+** read a PNM/PAM image and produce a Portable Network Graphics (PNG) file
+**
+** derived from pnmtorast.c by Jef Poskanzer and pamrgbatopng.c by Bryan
+** Henderson <bryanh@giraffe-data.com> and probably some other sources
+**
+** 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>
+** Copyright (C) 2015 by Willem van Schaik <willem@schaik.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 program pamtopng was derived in 2015 from the Netpbm program
+  Pnmtopng. This was a nearly complete rewrite with the following goals:
+
+  - Add ability to create a PNG alpha channel from the alpha channel in a
+    PAM (format P7) file.
+
+  - Simplify the 20 year old pnmtopng code. Because of the many, many features
+    that program implements and its need for backward compatibility, the code
+    had become rather complex.  This program is roughly 1/3 the size of
+    pnmtopng.c that it replaces.
+
+  - In 1995 bandwith was limited and therefore filesize had to be kept
+    small. The original program tried to optimize for that by applying
+    many "clever tricks". Today that isn't an issue anymore, so gone 
+    are filters, palettes, etc. Also, image conversions were removed,
+    because those should be done with other NetPBM tools.
+
+  - Add ability to create iTXt (international language) chunks.
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <png.h>
+/* setjmp.h needs to be included after png.h */
+#include <setjmp.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pam.h"
+#include "pngx.h"
+#include "pngtxt.h"
+
+
+/* global variable */
+static bool verbose;
+
+
+struct CmdlineInfo {
+    const char * inputFileName;
+    unsigned int verbose;
+    unsigned int transparencySpec;
+    const char * transparency;
+    unsigned int chromaSpec;
+    struct pngx_chroma chroma;
+    unsigned int gammaSpec;
+    float gamma;
+    unsigned int srgbintentSpec;
+    pngx_srgbIntent srgbintent;
+    unsigned int textSpec;
+    const char * text;
+    unsigned int ztxtSpec;
+    const char * ztxt;
+    unsigned int itxtSpec;
+    const char * itxt;
+    unsigned int backgroundSpec;
+    const char * background;
+    unsigned int timeSpec;
+    time_t time;
+};
+
+
+
+static void
+parseChromaOpt(const char *         const chromaOpt,
+               struct pngx_chroma * const chromaP) {
+
+    int count;
+    
+    count = sscanf(chromaOpt, "%f %f %f %f %f %f %f %f",
+                   &chromaP->wx, &chromaP->wy,
+                   &chromaP->rx, &chromaP->ry,
+                   &chromaP->gx, &chromaP->gy,
+                   &chromaP->bx, &chromaP->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",
+                 chromaOpt);
+}
+
+
+
+static void
+parseSrgbintentOpt(const char *      const srgbintentOpt,
+                   pngx_srgbIntent * const srgbintentP) {
+    
+    if (streq(srgbintentOpt, "perceptual"))
+        *srgbintentP = PNGX_PERCEPTUAL;
+    else if (streq(srgbintentOpt, "relativecolorimetric"))
+        *srgbintentP = PNGX_RELATIVE_COLORIMETRIC;
+    else if (streq(srgbintentOpt, "saturation"))
+        *srgbintentP = PNGX_SATURATION;
+    else if (streq(srgbintentOpt, "absolutecolorimetric"))
+        *srgbintentP = PNGX_ABSOLUTE_COLORIMETRIC;
+    else
+        pm_error("Unrecognized sRGB intent value '%s'.  We understand "
+                 "only 'perceptual', 'relativecolorimetric', "
+                 "'saturation', and 'absolutecolorimetric'",
+                 srgbintentOpt);
+}
+
+
+
+static void
+parseTimeOpt(const char * const timeOpt,
+             time_t *     const timeP) {
+
+    struct tm brokenTime;
+    int year;
+    int month;
+    int count;
+
+    count = sscanf(timeOpt, "%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 -time '%s'.   It should have "
+                 "the form [yy]yy-mm-dd hh:mm:ss.", timeOpt);
+    
+    if (year < 0)
+        pm_error("Year is negative in -time value '%s'", timeOpt);
+    if (year > 9999)
+        pm_error("Year is more than 4 digits in -time value '%s'",
+                 timeOpt);
+    if (month < 0)
+        pm_error("Month is negative in -time value '%s'", timeOpt);
+    if (month > 12)
+        pm_error("Month is >12 in -time value '%s'", timeOpt);
+    if (brokenTime.tm_mday < 0)
+        pm_error("Day of month is negative in -time value '%s'",
+                 timeOpt);
+    if (brokenTime.tm_mday > 31)
+        pm_error("Day of month is >31 in -time value '%s'", timeOpt);
+    if (brokenTime.tm_hour < 0)
+        pm_error("Hour is negative in -time value '%s'", timeOpt);
+    if (brokenTime.tm_hour > 23)
+        pm_error("Hour is >23 in -time value '%s'", timeOpt);
+    if (brokenTime.tm_min < 0)
+        pm_error("Minute is negative in -time value '%s'", timeOpt);
+    if (brokenTime.tm_min > 59)
+        pm_error("Minute is >59 in -time value '%s'", timeOpt);
+    if (brokenTime.tm_sec < 0)
+        pm_error("Second is negative in -time value '%s'", timeOpt);
+    if (brokenTime.tm_sec > 59)
+        pm_error("Second is >59 in -time value '%s'", timeOpt);
+
+    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.
+    */
+    *timeP = mktime(&brokenTime);
+}
+
+
+
+static void
+parseCommandLine (int                  argc,
+                  const char **        argv,
+                  struct CmdlineInfo * const cmdlineP) {
+    
+    optEntry * option_def;
+    optStruct3 opt;
+    unsigned int option_def_index = 0;  /* incremented by OPTENT3 */
+
+    const char * srgbintent;
+    const char * chroma;
+    const char * time;
+
+    MALLOCARRAY(option_def, 100);
+
+    OPTENT3(0,  "verbose",      OPT_FLAG,       NULL,
+            &cmdlineP->verbose,        0);
+    OPTENT3(0,  "transparency", OPT_STRING,     &cmdlineP->transparency,
+            &cmdlineP->transparencySpec, 0);
+    OPTENT3(0,  "chroma",       OPT_STRING,     &chroma,
+            &cmdlineP->chromaSpec,     0);
+    OPTENT3(0,  "gamma",        OPT_FLOAT,      &cmdlineP->gamma,
+            &cmdlineP->gammaSpec,      0);
+    OPTENT3(0,  "srgbintent",   OPT_STRING,     &srgbintent,
+            &cmdlineP->srgbintentSpec, 0);
+    OPTENT3(0,  "text",         OPT_STRING,     &cmdlineP->text,
+            &cmdlineP->textSpec,       0);
+    OPTENT3(0,  "ztxt",         OPT_STRING,     &cmdlineP->ztxt,
+            &cmdlineP->ztxtSpec,       0);
+    OPTENT3(0,  "itxt",         OPT_STRING,     &cmdlineP->itxt,
+            &cmdlineP->itxtSpec,       0);
+    OPTENT3(0,  "background",   OPT_STRING,     &cmdlineP->background,
+            &cmdlineP->backgroundSpec, 0);
+    OPTENT3(0,  "time",         OPT_STRING,        &time,
+            &cmdlineP->timeSpec,       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 */
+
+    /* uses and sets argc, argv, and some of *cmdlineP and others */
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+
+    if (cmdlineP->chromaSpec)
+        parseChromaOpt(chroma, &cmdlineP->chroma);
+
+    if (cmdlineP->srgbintentSpec)
+        parseSrgbintentOpt(srgbintent, &cmdlineP->srgbintent);
+
+    if (cmdlineP->timeSpec)
+        parseTimeOpt(time, &cmdlineP->time);
+    
+    /* get the input-file or stdin pipe */
+    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.");
+
+    free(option_def);
+}
+
+
+
+static png_byte
+colorTypeFromInputType(const struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+  Analyse the Netpbm image for color-type and bit-depth
+-----------------------------------------------------------------------------*/
+    png_byte retval;
+
+    if (pamP->depth < 1 && pamP->depth > 4)
+        pm_error ("Number of color planes must be between 1 and 4 inclusive");
+
+    if (pamP->maxval != 1 && pamP->maxval != 3 && pamP->maxval != 15 &&
+        pamP->maxval != 255 && pamP->maxval != 65535)
+        pm_error("The maxval of the input image is %u; "
+                 "it must be 1, 3, 15, 255 or 65535", (unsigned)pamP->maxval);
+
+    if (strneq(pamP->tuple_type, "RGB_ALPHA", 9)) {
+        if (pamP->depth == 4)
+            retval = PNG_COLOR_TYPE_RGB_ALPHA;
+        else
+            pm_error("Input tuple type is RGB_ALPHA, "
+                     "but number of planes is %u instead of 4",
+                pamP->depth);
+    } else if (strneq(pamP->tuple_type, "RGB", 3)) {
+        if (pamP->depth == 3)
+            retval = PNG_COLOR_TYPE_RGB;
+        else
+            pm_error("Input tuple type is RGB, "
+                     "but number of planes is %u instead of 3",
+                     pamP->depth);
+    } else if (strneq(pamP->tuple_type, "GRAYSCALE_ALPHA", 15)) {
+        if (pamP->depth == 2)
+            retval = PNG_COLOR_TYPE_GRAY_ALPHA;
+        else
+            pm_error("Input tupel type is GRAYSCALE_ALPHA, "
+                     "but number of planes is %u instread of 2",
+                     pamP->depth);
+    } else if (strneq(pamP->tuple_type, "GRAYSCALE", 9)) {
+        if (pamP->depth == 1)
+            retval = PNG_COLOR_TYPE_GRAY;
+        else
+            pm_error("Input tuple type is GRAYSCALE, "
+                     "but number of planes is %u instead of 1",
+                     pamP->depth);
+    } else if (strneq(pamP->tuple_type, "BLACKANDWHITE", 3)) {
+        if (pamP->depth != 1)
+            pm_error("Input tuple type is BLACKANDWHITE, "
+                     "but number of planes is %u instead of 1",
+                     pamP->depth);
+        if (pamP->maxval != 1)
+            pm_error("Input tuple type is BLACKANDWHITE, "
+                     "but maxval is %u instead of 1", (unsigned)pamP->maxval);
+
+        retval = PNG_COLOR_TYPE_GRAY;
+    } else
+        pm_error("Unrecognized tuple type: '%s'", pamP->tuple_type);
+
+    return retval;
+}
+
+
+
+/*****************************************************************************
+*  Subroutines that create all the (ancillary) chunks
+*****************************************************************************/
+
+
+
+static png_color_16
+parseAndScaleColor(const char * const colorString,
+                   xelval       const pngMaxval) {
+
+    png_color_16 pngColor;
+
+    if (colorString) {
+        xel const inputColor = ppm_parsecolor(colorString, PNM_OVERALLMAXVAL);
+
+        xel scaledColor;
+
+        /* Scale the color down to the PNG bit depth */
+        PPM_DEPTH(scaledColor, inputColor, PNM_OVERALLMAXVAL, pngMaxval);
+
+        pngColor.red   = PPM_GETR(scaledColor);
+        pngColor.green = PPM_GETG(scaledColor);
+        pngColor.blue  = PPM_GETB(scaledColor);
+        pngColor.gray  = PNM_GET1(scaledColor);
+    }
+
+    return pngColor;
+}
+
+
+
+static png_color_8
+sigBitsFmImgType(unsigned int const pnmBitDepth,
+                 int          const pngColorType) {
+/*----------------------------------------------------------------------------
+   A representation used in PNG of color resolutions in an original image.
+-----------------------------------------------------------------------------*/
+    png_color_8 retval;
+
+    /* Initial values */
+    if (pnmBitDepth < 8) {
+        switch (pngColorType) {
+        case PNG_COLOR_TYPE_RGB:
+            retval.red   = pnmBitDepth;
+            retval.green = pnmBitDepth;
+            retval.blue  = pnmBitDepth;
+            retval.gray  = 0;
+            retval.alpha = 0;
+            break;
+        case PNG_COLOR_TYPE_RGB_ALPHA:
+            retval.red   = pnmBitDepth;
+            retval.green = pnmBitDepth;
+            retval.blue  = pnmBitDepth;
+            retval.gray  = 0;
+            retval.alpha = pnmBitDepth;
+            break;
+        case PNG_COLOR_TYPE_GRAY:
+            /* PNG can (so presumably will) use original bit depth */
+            retval.red   = 0;
+            retval.green = 0;
+            retval.blue  = 0;
+            retval.gray  = 0;
+            retval.alpha = 0;
+            break;
+        case PNG_COLOR_TYPE_GRAY_ALPHA:
+            retval.red   = 0;
+            retval.green = 0;
+            retval.blue  = 0;
+            retval.gray  = pnmBitDepth;
+            retval.alpha = pnmBitDepth;
+            break;
+        }
+    } else {
+        /* PNG can (so presumably will) use original bit depth */
+        retval.red   = 0;
+        retval.green = 0;
+        retval.blue  = 0;
+        retval.gray  = 0;
+        retval.alpha = 0;
+    }
+    return retval;
+}
+
+
+
+static void
+doTrnsChunk(const struct pam * const pamP,
+            struct pngx *      const pngxP,
+            const char *       const trans) {
+
+    if (pngx_colorType(pngxP) == PNG_COLOR_TYPE_GRAY_ALPHA || 
+        pngx_colorType(pngxP) == PNG_COLOR_TYPE_RGB_ALPHA)
+        pm_error("Both alpha channel and transparency chunk not allowed.");
+    else {
+        xelval const pngMaxval = pm_bitstomaxval(pngx_bitDepth(pngxP));
+        png_color_16 const pngColor = parseAndScaleColor(trans, pngMaxval);
+            /* Transparency color from text format scaled from 16-bit to
+               maxval.
+            */
+
+        pngx_setTrnsValue(pngxP, pngColor);
+
+        if (verbose) {
+            if (pngx_colorType(pngxP) == PNG_COLOR_TYPE_GRAY) {
+                pm_message("writing tRNS chunk with color {gray} = {%u}",
+                           pngColor.gray );
+            } else if (pngx_colorType(pngxP) == PNG_COLOR_TYPE_RGB) {
+                pm_message("writing tRNS chunk with color "
+                           "{red, green, blue} = {%u, %u, %u}",
+                           pngColor.red, pngColor.green, pngColor.blue);
+            }
+        }
+    }
+}
+
+
+
+static void
+doChrmChunk(struct pngx *      const pngxP,
+            struct pngx_chroma const chroma) {
+    
+    pngx_setChrm(pngxP, chroma);
+
+    if (verbose) {
+        pm_message("writing cHRM chunk { wx, wy, rx, ry, gx, gy, bx, by } = "
+                   "{ %4.2f, %4.2f, %4.2f, %4.2f, "
+                   "%4.2f, %4.2f, %4.2f, %4.2f }", 
+                   chroma.wx, chroma.wy,
+                   chroma.rx, chroma.ry,
+                   chroma.gx, chroma.gy,
+                   chroma.bx, chroma.by);
+    }
+}
+
+
+
+static void 
+doGamaChunk(struct pngx *  const pngxP,
+            float          const gamma) {
+
+    pngx_setGama(pngxP, gamma);
+
+    if (verbose) {
+        pm_message("writing gAMA chunk with image gamma value %4.2f", gamma);
+    }
+}
+
+
+
+static void
+doSbitChunk(const struct pam * const pamP,
+            struct pngx *      const pngxP,
+            png_color_8        const sigBits) {
+
+    if (sigBits.red + sigBits.green + sigBits.blue +
+        sigBits.gray + sigBits.alpha > 0) {
+        pngx_setSbit(pngxP, sigBits);
+    }
+}
+
+
+
+static void
+doSrgbChunk(struct pngx *   const pngxP,
+            pngx_srgbIntent const srgbIntent) {
+
+    pngx_setSrgb(pngxP, srgbIntent);
+
+    if (verbose) {
+        pm_message("writing sRGB chunk with intent value %s",
+                   pngx_srgbIntentDesc(srgbIntent));
+    }
+}
+
+
+
+static void
+doTextChunkSet(struct pngx * const pngxP,
+               const char *  const textFileName) {
+
+    bool const ztxt = true;
+    bool const itxt = false;
+
+    FILE * tfP;
+
+    tfP = pm_openr(textFileName);
+    
+    pngtxt_addChunk(pngxP, tfP, ztxt, itxt, verbose);
+    
+    pm_close(tfP);
+}
+
+
+
+static void
+doZtxtChunkSet(struct pngx * const pngxP,
+               const char *  const textFileName) {
+
+    bool const ztxt = true;
+    bool const itxt = false;
+
+    FILE * tfP;
+
+    tfP = pm_openr(textFileName);
+    
+    pngtxt_addChunk(pngxP, tfP, ztxt, itxt, verbose);
+    
+    pm_close(tfP);
+}
+
+
+
+
+static void
+doItxtChunkSet(struct pngx * const pngxP,
+               const char *  const textFileName) {
+
+    bool const ztxt = true;
+    bool const itxt = true;
+
+    FILE * tfP;
+
+    tfP = pm_openr(textFileName);
+
+    pngtxt_addChunk(pngxP, tfP, ztxt, itxt, verbose);
+}
+
+
+
+static void
+doBkgdChunk (const struct pam * const pamP,
+             struct pngx *      const pngxP,
+             const char *       const colorName)
+{
+    xelval const pngMaxval = pm_bitstomaxval(pngx_bitDepth(pngxP));
+
+    png_color_16 const pngColor = parseAndScaleColor(colorName, pngMaxval);
+        /* Background color from text format, scaled from 16-bit to maxval */
+
+    pngx_setBkgdRgb(pngxP, pngColor);
+
+    if (verbose) {
+        if (pngx_colorType(pngxP) == PNG_COLOR_TYPE_GRAY || 
+            pngx_colorType(pngxP) == PNG_COLOR_TYPE_GRAY_ALPHA) {
+            pm_message("writing bKGD chunk with gray level = %u",
+                       pngColor.gray);
+        } else if (pngx_colorType(pngxP) == PNG_COLOR_TYPE_RGB || 
+                   pngx_colorType(pngxP) == PNG_COLOR_TYPE_RGB_ALPHA) {
+            pm_message("writing bKGD chunk with color {red, green, blue} = "
+                       "{%u, %u, %u}",
+                       pngColor.red, pngColor.green, pngColor.blue);
+        }
+    }
+}
+
+
+
+static void
+doTimeChunk(struct pngx * const pngxP,
+            time_t        const time) {
+
+    pngx_setTime(pngxP, time);
+
+    if (verbose) {
+        struct tm * const brokenTimeP = gmtime(&time);
+
+        char buffer[100];
+
+        strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", brokenTimeP);
+
+        pm_message("Writing tIME chunk specifying datetime %s", buffer);
+    }
+}
+
+
+
+static void
+setShift(struct pngx * const pngxP,
+         png_color_8   const sigBits) {
+
+    if (sigBits.red + sigBits.green + sigBits.blue +
+        sigBits.gray + sigBits.alpha > 0) {
+
+        /* Move the 1, 2, 4 bits to most significant bits */
+        pngx_setShift(pngxP, sigBits);
+    }
+}
+
+
+
+static void
+convertRaster(const struct pam * const pamP,
+              const tuple *      const tuplerow,
+              png_byte *         const pngRow,
+              unsigned int       const bitDepth) {
+
+    unsigned int col;
+
+    /* An image row consists of columns x planes like gray or rgb(a) x 8 or 16
+       bits.
+    */
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane) {
+            if (bitDepth > 8) {
+                /* Copy 2 bytes = 16 bits for one pixel */
+                pngRow[2 * (pamP->depth * col + plane)] =
+                    (tuplerow[col][plane] >> 8) & 0xff ;
+                pngRow[2 * (pamP->depth * col + plane) + 1] =
+                    tuplerow[col][plane] & 0xff ;
+            } else {
+                /* Copy 1 byte for one pixel. Later, a packing of 2, 4 or 8
+                   pixels into a single byte can still happen.
+                */
+                pngRow[pamP->depth * col + plane] = tuplerow[col][plane];
+            }
+        }
+    }
+}
+
+
+
+static void
+writeRaster(const struct pam * const pamP,
+            struct pngx *      const pngxP,
+            int                const bitDepth) {
+
+    tuple * tupleRow;
+    png_byte * pngRow;
+    unsigned int row;
+
+    /* We process row-by-row and do not read the complete image into memory */
+
+    tupleRow = pnm_allocpamrow(pamP);
+
+    MALLOCARRAY(pngRow, pamP->width * 8);
+        /* sufficient to store a 16-bit RGB+A row */
+
+    if (pngRow == NULL)
+        pm_error("Unable to allocate space for PNG pixel row for "
+                 "%u columns", pamP->width);
+    else {
+        for (row = 0; row < pamP->height; ++row) {
+            pnm_readpamrow(pamP, tupleRow);
+
+            convertRaster(pamP, tupleRow, pngRow, bitDepth);
+
+            png_write_row(pngxP->png_ptr, pngRow);
+        }
+        free(pngRow);
+    }
+    pnm_freepamrow(tupleRow);
+}
+
+
+
+static void
+writePng(const struct pam * const pamP,
+         FILE *             const ofP,
+         struct CmdlineInfo const cmdline) {
+
+    unsigned int const pnmBitDepth = pm_maxvaltobits(pamP->maxval);
+    int const pngColorType = colorTypeFromInputType(pamP);
+
+    struct pngx * pngxP;
+    unsigned int pngBitDepth;
+    png_color_8 sBit;
+
+    pngx_create(&pngxP, PNGX_WRITE, NULL);
+
+
+
+    if ((pngColorType == PNG_COLOR_TYPE_RGB ||
+         pngColorType == PNG_COLOR_TYPE_RGB_ALPHA) &&
+        pnmBitDepth < 8) {
+
+        pngBitDepth = 8;
+    } else
+        pngBitDepth = pnmBitDepth;
+
+    png_init_io(pngxP->png_ptr, ofP);
+
+    pngx_setIhdr(pngxP, pamP->width, pamP->height,
+                 pngBitDepth, pngColorType,
+                 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
+                 PNG_FILTER_TYPE_BASE);
+
+    sBit = sigBitsFmImgType(pnmBitDepth, pngColorType);
+
+    /* Where requested, add ancillary chunks */
+    if (cmdline.transparencySpec)
+        doTrnsChunk(pamP, pngxP,cmdline.transparency);
+
+    if (cmdline.chromaSpec)
+        doChrmChunk(pngxP, cmdline.chroma);
+
+    if (cmdline.gammaSpec)
+        doGamaChunk(pngxP, cmdline.gamma);
+
+    /* no iccp */
+
+    doSbitChunk(pamP, pngxP, sBit);
+
+    if (cmdline.srgbintentSpec)
+        doSrgbChunk(pngxP, cmdline.srgbintent);
+
+    if (cmdline.textSpec)
+        doTextChunkSet(pngxP, cmdline.text);
+
+    if (cmdline.ztxtSpec)
+        doZtxtChunkSet(pngxP, cmdline.ztxt);
+
+    if (cmdline.itxtSpec)
+        doItxtChunkSet(pngxP, cmdline.itxt);
+
+    if (cmdline.backgroundSpec)
+        doBkgdChunk(pamP, pngxP, cmdline.background);
+
+    /* no hist */
+
+    /* no phys */
+
+    /* no splt */
+
+    if (cmdline.timeSpec)
+        doTimeChunk(pngxP, cmdline.time);
+
+    setShift(pngxP, sBit);
+
+    /* Write the ancillary chunks to PNG file */
+    pngx_writeInfo(pngxP);
+
+    if (pngColorType != PNG_COLOR_TYPE_GRAY && pnmBitDepth < 8) {
+        /* Move the 1, 2, 4 bits to most significant bits */
+        pngx_setShift(pngxP, sBit);
+    }
+    if ((pngColorType == PNG_COLOR_TYPE_GRAY) && (pnmBitDepth < 8)) {
+        /* Pack multiple pixels in a byte */
+        pngx_setPacking(pngxP);
+    }
+
+    writeRaster(pamP, pngxP, pnmBitDepth);
+
+    pngx_writeEnd(pngxP);
+    pngx_destroy(pngxP);
+}
+
+
+
+
+static void
+reportInputFormat(const struct pam * const pamP) {
+
+    const char * formatDesc;
+
+    if (pamP->format == PBM_FORMAT || pamP->format == RPBM_FORMAT)
+        formatDesc = "PBM";
+    else if (pamP->format == PGM_FORMAT || pamP->format == RPGM_FORMAT)
+        formatDesc = "PGM";
+    else if (pamP->format == PPM_FORMAT || pamP->format == RPPM_FORMAT)
+        formatDesc = "PPM";
+    else if (pamP->format == PAM_FORMAT)
+        formatDesc = "PAM";
+    else
+        formatDesc = NULL;
+
+    if (formatDesc)
+        pm_message("Input format = %s", formatDesc);
+    else
+        pm_message("Unrecognized input format, format code = 0x%x",
+                   pamP->format);
+
+    pm_message("Input tuple type = '%s'", pamP->tuple_type);
+    pm_message("Input depth = %u", pamP->depth);
+    pm_message("Input maxval = %u", (unsigned int) pamP->maxval);
+}
+
+
+
+int
+main(int           argc,
+     const char ** argv) {
+
+    FILE * ifP;
+    struct CmdlineInfo cmdline;
+    struct pam pam;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    verbose = cmdline.verbose;
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pnm_readpaminit(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
+
+    if (verbose)
+        reportInputFormat(&pam);
+
+    writePng(&pam, stdout, cmdline);
+
+    pm_close(ifP);
+
+    return 0;
+}
+
+
+