about summary refs log tree commit diff
path: root/converter/other/pamtotiff.c
diff options
context:
space:
mode:
Diffstat (limited to 'converter/other/pamtotiff.c')
-rw-r--r--converter/other/pamtotiff.c1110
1 files changed, 1110 insertions, 0 deletions
diff --git a/converter/other/pamtotiff.c b/converter/other/pamtotiff.c
new file mode 100644
index 00000000..4174a431
--- /dev/null
+++ b/converter/other/pamtotiff.c
@@ -0,0 +1,1110 @@
+/*
+** pnmtotiff.c - converts a PNM image to a TIFF (Tagged Image File) image
+**
+** Derived by Jef Poskanzer from ras2tif.c, which is:
+**
+** Copyright (c) 1990 by Sun Microsystems, Inc.
+**
+** Author: Patrick J. Naughton
+** naughton@wind.sun.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 file is provided AS IS with no warranties of any kind.  The author
+** shall have no liability with respect to the infringement of copyrights,
+** trade secrets or any patents by this file or any part thereof.  In no
+** event will the author be liable for any lost revenue or profits or
+** other special, indirect and consequential damages.
+*/
+
+#define _XOPEN_SOURCE    /* Make sure stdio.h contains fileno() */
+#define _BSD_SOURCE      /* Make sure string.h contains strcasecmp() */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#ifdef VMS
+#ifdef SYSV
+#undef SYSV
+#endif
+#include <tiffioP.h>
+#endif
+/* tiffio.h has a weird problem on AIX.  tiffio.h wrongly typedefs
+   "int32".  That's wrong because such a name is likely to be used in
+   other parts of the program that includes tiffio.h.  And in fact, on
+   AIX, <inttypes.h> defines it in some cases.  One case in which <inttypes.h>
+   does _not_ define int32 is where _XOPEN_SOURCE is declared.  So we always
+   do that when including tiffio.h.  06.01.05
+*/
+#include <tiffio.h>
+
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pam.h"
+#include "pammap.h"
+#include "pm_tiff.h"
+
+#define MAXCOLORS 256
+
+/* 2001.09.03: Old tiff.h doesn't contain these values: */
+#ifndef COMPRESSION_ADOBE_DEFLATE
+#define COMPRESSION_ADOBE_DEFLATE 8
+#endif
+
+struct sizeset {
+    bool b1, b2, b4, b8;
+};
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespecs of input files */
+    int compression;
+        /* COMPRESSION Tiff tag value, that corresponds to the compression
+           option the user specified, or -1 if he didn't specify any.
+           The -tag option doesn't count.
+        */
+    int fillorder;     /* FILLORDER Tiff tag value */
+    int g3options;     /* G3OPTIONS Tiff tag value or 0 for none */
+    int predictor;     /* PREDICTOR Tiff tag value or -1 for none */
+    int rowsperstrip;  /* -1 = unspecified */
+    unsigned int minisblack;   /* logical: User wants MINISBLACK photometric */
+    unsigned int miniswhite;   /* logical: User wants MINISWHITE photometric */
+    unsigned int truecolor;    /* logical: User wants truecolor, not cmap */
+    unsigned int color;        /* logical: assume not grayscale */
+    float xresolution; /* XRESOLUTION Tiff tag value or -1 for none */
+    float yresolution; /* YRESOLUTION Tiff tag value or -1 for none */
+    int resolutionUnit;  /* RESOLUTIONUNIT Tiff tag value */
+    struct sizeset indexsizeAllowed;
+    /* Which bit widths are allowable in a raster of palette indices */
+    unsigned int verbose;
+    unsigned int append;
+    float resolution;  /* X and Y resolution */
+    struct optNameValue * taglist;
+};
+
+
+
+static void
+validateTagList(struct optNameValue const taglist[]) {
+
+    unsigned int i;
+    for (i = 0; taglist[i].name; ++i) {
+        const char * const tagName = taglist[i].name;
+        const tagDefinition * tagDefP = tagDefFind(tagName);
+        
+        if (!tagDefP)
+            pm_error("Unknown tag name '%s'", tagName);
+        else {
+            /* We don't allow the user to set directly any tag that we
+               control explicitly.  These are normally tags that have to be
+               consistent with other stuff we put in the image.  But in some
+               cases, they're just tags we had special options for before
+               -tag existed.  The latter should probably be converted
+               eventually to prefer -tag.
+            */
+            switch (tagDefP->tagnum) {
+            case TIFFTAG_IMAGEWIDTH:
+            case TIFFTAG_IMAGELENGTH:
+            case TIFFTAG_BITSPERSAMPLE:
+            case TIFFTAG_COMPRESSION:
+            case TIFFTAG_GROUP3OPTIONS:
+            case TIFFTAG_PREDICTOR:
+            case TIFFTAG_PHOTOMETRIC:
+            case TIFFTAG_FILLORDER:
+            case TIFFTAG_SAMPLESPERPIXEL:
+            case TIFFTAG_ROWSPERSTRIP:
+            case TIFFTAG_PLANARCONFIG:
+            case TIFFTAG_COLORMAP:
+            case TIFFTAG_RESOLUTIONUNIT:
+            case TIFFTAG_XRESOLUTION:
+            case TIFFTAG_YRESOLUTION:
+                pm_error("You cannot specify a '%s' tag with -tag.  "
+                         "Pamtotiff controls that internally or via other "
+                         "options.", tagName);
+            }
+        }
+    }
+}
+
+
+
+static void
+parseCommandLine(int                        argc,
+                 char **              const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+
+    optStruct3 opt;
+
+    unsigned int none, packbits, lzw, g3, g4, msb2lsb, lsb2msb, opt_2d, fill;
+    unsigned int flate, adobeflate;
+    char * indexbits;
+    char * resolutionUnit;
+
+    unsigned int predictorSpec, rowsperstripSpec, xresolutionSpec,
+        yresolutionSpec, indexbitsSpec, resolutionUnitSpec, tagSpec;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "verbose",      OPT_FLAG,   NULL, &cmdlineP->verbose,       0);
+    OPTENT3(0, "append",       OPT_FLAG,   NULL, &cmdlineP->append,        0);
+    OPTENT3(0, "none",         OPT_FLAG,   NULL, &none,                    0);
+    OPTENT3(0, "packbits",     OPT_FLAG,   NULL, &packbits,                0);
+    OPTENT3(0, "lzw",          OPT_FLAG,   NULL, &lzw,                     0);
+    OPTENT3(0, "g3",           OPT_FLAG,   NULL, &g3,                      0);
+    OPTENT3(0, "g4",           OPT_FLAG,   NULL, &g4,                      0);
+    OPTENT3(0, "flate",        OPT_FLAG,   NULL, &flate,                   0);
+    OPTENT3(0, "adobeflate",   OPT_FLAG,   NULL, &adobeflate,              0);
+    OPTENT3(0, "msb2lsb",      OPT_FLAG,   NULL, &msb2lsb,                 0);
+    OPTENT3(0, "lsb2msb",      OPT_FLAG,   NULL, &lsb2msb,                 0);
+    OPTENT3(0, "2d",           OPT_FLAG,   NULL, &opt_2d,                  0);
+    OPTENT3(0, "fill",         OPT_FLAG,   NULL, &fill,                    0);
+    OPTENT3(0, "minisblack",   OPT_FLAG,   NULL, &cmdlineP->minisblack,    0);
+    OPTENT3(0, "mb",           OPT_FLAG,   NULL, &cmdlineP->minisblack,    0);
+    OPTENT3(0, "miniswhite",   OPT_FLAG,   NULL, &cmdlineP->miniswhite,    0);
+    OPTENT3(0, "mw",           OPT_FLAG,   NULL, &cmdlineP->miniswhite,    0);
+    OPTENT3(0, "truecolor",    OPT_FLAG,   NULL, &cmdlineP->truecolor,     0);
+    OPTENT3(0, "color",        OPT_FLAG,   NULL, &cmdlineP->color,         0);
+    OPTENT3(0, "predictor",    OPT_UINT,   &cmdlineP->predictor,    
+            &predictorSpec,    0);
+    OPTENT3(0, "rowsperstrip", OPT_UINT,   &cmdlineP->rowsperstrip, 
+            &rowsperstripSpec, 0);
+    OPTENT3(0, "xresolution",  OPT_FLOAT,  &cmdlineP->xresolution,  
+            &xresolutionSpec,  0);
+    OPTENT3(0, "yresolution",  OPT_FLOAT,  &cmdlineP->yresolution,  
+            &yresolutionSpec,  0);
+    OPTENT3(0, "resolutionunit", OPT_STRING, &resolutionUnit,
+            &resolutionUnitSpec,    0);
+    OPTENT3(0, "indexbits",    OPT_STRING,   &indexbits, 
+            &indexbitsSpec,    0);
+    OPTENT3(0, "tag",          OPT_NAMELIST, &cmdlineP->taglist, &tagSpec, 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 (none + packbits + lzw + g3 + g4 + flate + adobeflate > 1)
+        pm_error("You specified more than one compression option.  "
+                 "Only one of -none, -packbits, -lze, -g3, and -g4 "
+                 "is allowed.");
+    
+    if (none)
+        cmdlineP->compression = COMPRESSION_NONE;
+    else if (packbits)
+        cmdlineP->compression = COMPRESSION_PACKBITS;
+    else if (lzw)
+        cmdlineP->compression = COMPRESSION_LZW;
+    else if (g3)
+        cmdlineP->compression = COMPRESSION_CCITTFAX3;
+    else if (g4)
+        cmdlineP->compression = COMPRESSION_CCITTFAX4;
+    else if (adobeflate)
+        cmdlineP->compression = COMPRESSION_ADOBE_DEFLATE;
+    else if (flate)
+        cmdlineP->compression = COMPRESSION_DEFLATE;
+    else
+        cmdlineP->compression = COMPRESSION_NONE;
+    
+    if (msb2lsb + lsb2msb > 1)
+        pm_error("You specified both -msb2lsb and -lsb2msb.  "
+                 "These are conflicting options.");
+
+    if (msb2lsb)
+        cmdlineP->fillorder = FILLORDER_MSB2LSB;
+    else if (lsb2msb)
+        cmdlineP->fillorder = FILLORDER_LSB2MSB;
+    else 
+        cmdlineP->fillorder = FILLORDER_MSB2LSB;
+    
+
+    if (cmdlineP->miniswhite && cmdlineP->minisblack)
+        pm_error("You cannot specify both -miniswhite and -minisblack");
+
+    cmdlineP->g3options = 0;  /* initial value */
+    if (opt_2d)
+        cmdlineP->g3options |= GROUP3OPT_2DENCODING;
+    if (fill)
+        cmdlineP->g3options |= GROUP3OPT_FILLBITS;
+
+    if (predictorSpec) {
+        if (cmdlineP->predictor != 1 && cmdlineP->predictor != 2)
+            pm_error("-predictor may be only 1 or 2.  You specified %d.", 
+                     cmdlineP->predictor);
+    } else
+        cmdlineP->predictor = -1;
+
+    if (rowsperstripSpec) {
+        if (cmdlineP->rowsperstrip < 1)
+            pm_error("-rowsperstrip must be positive.  You specified %d.",
+                     cmdlineP->rowsperstrip);
+    } else
+        cmdlineP->rowsperstrip = -1;
+
+    if (xresolutionSpec) {
+        if (cmdlineP->xresolution < 1)
+            pm_error("-xresolution must be positive.  You specified %f.",
+                     cmdlineP->xresolution);
+    } else
+        cmdlineP->xresolution = -1;
+
+    if (yresolutionSpec) {
+        if (cmdlineP->yresolution < 1)
+            pm_error("-yresolution must be positive.  You specified %f.",
+                     cmdlineP->yresolution);
+    } else
+        cmdlineP->yresolution = -1;
+
+    if (resolutionUnitSpec) {
+        if (streq(resolutionUnit, "inch"))
+            cmdlineP->resolutionUnit = RESUNIT_INCH;
+        else if (streq(resolutionUnit, "in"))
+            cmdlineP->resolutionUnit = RESUNIT_INCH;
+        else if (streq(resolutionUnit, "centimeter"))
+            cmdlineP->resolutionUnit = RESUNIT_CENTIMETER;
+        else if (streq(resolutionUnit, "cm"))
+            cmdlineP->resolutionUnit = RESUNIT_CENTIMETER;
+        else if (streq(resolutionUnit, "none"))
+            cmdlineP->resolutionUnit = RESUNIT_NONE;
+        else if (streq(resolutionUnit, "no"))
+            cmdlineP->resolutionUnit = RESUNIT_NONE;
+        else
+            pm_error("The only acceptable values for -resolutionunit are "
+                     "inch, centimeter, none, in, cm, and no.  "
+                     "You specified '%s'.", resolutionUnit);
+    } else
+        cmdlineP->resolutionUnit = RESUNIT_INCH;
+
+    if (indexbitsSpec) {
+        if (strstr(indexbits, "1"))
+            cmdlineP->indexsizeAllowed.b1 = TRUE;
+        else
+            cmdlineP->indexsizeAllowed.b1 = FALSE;
+        if (strstr(indexbits, "2"))
+            cmdlineP->indexsizeAllowed.b2 = TRUE;
+        else
+            cmdlineP->indexsizeAllowed.b2 = FALSE;
+        if (strstr(indexbits, "4"))
+            cmdlineP->indexsizeAllowed.b4 = TRUE;
+        else
+            cmdlineP->indexsizeAllowed.b4 = FALSE;
+        if (strstr(indexbits, "8"))
+            cmdlineP->indexsizeAllowed.b8 = TRUE;
+        else
+            cmdlineP->indexsizeAllowed.b8 = FALSE;
+    } else {
+        cmdlineP->indexsizeAllowed.b1 = FALSE;
+        cmdlineP->indexsizeAllowed.b2 = FALSE;
+        cmdlineP->indexsizeAllowed.b4 = FALSE;
+        cmdlineP->indexsizeAllowed.b8 = TRUE;
+    }
+
+    if (tagSpec)
+        validateTagList(cmdlineP->taglist);
+    else {
+        MALLOCARRAY_NOFAIL(cmdlineP->taglist, 1);
+        cmdlineP->taglist[0].name = NULL;
+        cmdlineP->taglist[0].value = NULL;
+    }
+
+    if (argc-1 == 0) 
+        cmdlineP->input_filespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->input_filespec = argv[1];
+}
+
+
+
+static void
+putSample(sample           const s,
+          sample           const maxval,
+          sample           const tiff_maxval,
+          unsigned int     const bitspersample,
+          unsigned char ** const tPP) {
+
+    xelval s2;
+
+    s2 = s;
+    if (maxval != tiff_maxval)
+        s2 = s * tiff_maxval / maxval;
+    if (bitspersample > 8) {
+        *((unsigned short *)(*tPP)) = s;
+        (*tPP) += sizeof(short);
+    } else
+        *(*tPP)++ = s & 0xff;
+}
+
+
+
+
+/* Note: PUTSAMPLE doesn't work if bitspersample is 1-4. */
+
+#define PUTSAMPLE putSample(s, maxval, tiff_maxval, bitspersample, &tP);
+
+
+
+static void
+fillRowOfSubBytePixels(struct pam *    const pamP,
+                       const tuple *   const tuplerow,
+                       unsigned char * const buf,
+                       unsigned short  const tiff_maxval,
+                       unsigned short  const photometric,
+                       int             const fillorder,
+                       int             const bitspersample,
+                       tuplehash       const cht) {
+/*----------------------------------------------------------------------------
+  This subroutine deals with cases where multiple pixels are packed into
+  a single byte of the Tiff output image, i.e. bits per pixel < 8.
+-----------------------------------------------------------------------------*/
+    int col;
+    int bitshift;
+        /* The number of bits we have to shift a pixel value left to line
+           it up with where the current pixel goes in the current byte of
+           the output buffer.  
+        */
+    int const firstbitshift = 
+        (fillorder == FILLORDER_MSB2LSB) ? 8 - bitspersample : 0;
+        /* The value of 'bitshift' for the first pixel into a
+           byte of the output buffer.  (MSB2LSB is normal).
+        */
+    sample s;
+        /* The value of the current sample */
+    unsigned char* tP;
+        /* The address of the byte in the output buffer in which
+           the current pixel goes.
+        */
+    unsigned char byte;
+        /* The under-construction value of the byte pointed to by
+           tP, above.
+        */
+                
+    bitshift = firstbitshift;
+    byte = 0;
+    for (col = 0, tP = buf; col < pamP->width; ++col) {
+        if (cht == NULL) {
+            /* It's grayscale */
+            s = tuplerow[col][0];
+            if (pamP->maxval != tiff_maxval )
+                s = (long) s * tiff_maxval / pamP->maxval;
+ 
+            if (photometric == PHOTOMETRIC_MINISWHITE)
+                s = tiff_maxval - s;
+        } else {
+            /* It's a colormapped raster */
+            int si;
+            int found;
+            pnm_lookuptuple(pamP, cht, tuplerow[col], &found, &si);
+            if (!found)
+                pm_error("INTERNAL ERROR.  We made a color map, and a "
+                         "color map we need is not in it!.  "
+                         "col=%d  r=%lu g=%lu b=%lu",
+                         col, tuplerow[col][0], tuplerow[col][1],
+                         tuplerow[col][2]);
+            s = (unsigned char) si;
+        }
+        byte |= s << bitshift;
+        if (fillorder == FILLORDER_MSB2LSB)
+            bitshift -= bitspersample;  /* normal case */
+        else
+            bitshift += bitspersample;
+        if (bitshift < 0 || bitshift > 7) {
+            *tP++ = byte;
+            bitshift = firstbitshift;
+            byte = 0;
+        }
+    }
+    if (bitshift != firstbitshift)
+        *tP++ = byte;
+}
+
+
+
+static void
+fillRowOfWholeBytePixels(struct pam *    const pamP,
+                         tuple *         const tuplerow,
+                         unsigned char * const buf,
+                         unsigned short  const photometric,
+                         unsigned short  const tiffMaxval,
+                         unsigned int    const bitsPerSample) {
+
+    unsigned int col;
+    unsigned char * tP;
+    unsigned int planes;
+    
+    if (photometric == PHOTOMETRIC_RGB)
+        planes = pamP->depth;
+    else
+        /* Write only the first plane from the PAM, even if there
+           are more.
+        */
+        planes = 1;
+
+    for (col = 0, tP = buf; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < planes; ++plane) {
+            putSample(tuplerow[col][plane], pamP->maxval,
+                      tiffMaxval, bitsPerSample, &tP);
+            /* Advances tP */
+        }
+    }
+} 
+
+
+
+static void
+writeScanLines(struct pam *   const pamP,
+               TIFF *         const tif, 
+               tuplehash      const cht,
+               unsigned short const tiffMaxval,
+               unsigned short const bitspersample, 
+               unsigned short const photometric,
+               int            const bytesperrow, 
+               int            const fillorder) {
+/*----------------------------------------------------------------------------
+   Write out the raster for the input image described by 'pamP', whose
+   file is positioned to the raster of the image.
+-----------------------------------------------------------------------------*/
+    tuple * tuplerow;  /* malloc'ed */
+    unsigned char * buf; /* malloc'ed */
+    int row;
+
+    /* The man page for TIFFWriteScanLine doesn't tell the format of
+       it's 'buf' parameter, but here it is: Its format depends on the
+       bits per pixel of the TIFF image.  If it's 16, 'buf' is an
+       array of short (16 bit) integers, one per raster column.  If
+       it's 8, 'buf' is an array of characters (8 bit integers), one
+       per image column.  If it's less than 8, it's an array of characters,
+       each of which represents 1-8 raster columns, packed
+       into it in the order specified by the TIFF image's fill order,
+       with don't-care bits on the right such that each byte contains only
+       whole pixels.
+
+       In all cases, the array elements are in order left to right going
+       from low array indices to high array indices.
+    */
+    MALLOCARRAY(buf, bytesperrow);
+
+    if (buf == NULL)
+        pm_error("can't allocate memory for row buffer");
+
+    tuplerow = pnm_allocpamrow(pamP);
+    
+    for (row = 0; row < pamP->height; ++row) {
+        int col;
+
+        pnm_readpamrow(pamP, tuplerow);
+
+        if (cht == NULL) {
+            /* It's a direct, non-colormapped raster */
+
+            if (bitspersample == 8 || bitspersample == 16)
+                fillRowOfWholeBytePixels(pamP, tuplerow, buf, photometric,
+                                         tiffMaxval, bitspersample);
+            else
+                fillRowOfSubBytePixels(pamP, tuplerow, buf,
+                                       tiffMaxval, photometric,
+                                       fillorder, bitspersample, NULL);
+        } else {
+            /* It's a colormapped raster */
+            if (bitspersample == 8) {
+                for (col = 0; col < pamP->width; ++col) {
+                    int si;
+                    int found;
+                    
+                    pnm_lookuptuple(pamP, cht, tuplerow[col], &found, &si);
+                    
+                    if (!found)
+                        pm_error("INTERNAL ERROR.  We made a color map, and a "
+                                 "color map we need is not in it!  "
+                                 "col=%d  r=%lu g=%lu b=%lu",
+                                 col, tuplerow[col][0], tuplerow[col][1],
+                                 tuplerow[col][2]);
+                    buf[col] = (unsigned char) si;
+                }
+            } else {
+                fillRowOfSubBytePixels(pamP, tuplerow, buf, 0, 0, fillorder,
+                                       bitspersample, cht);
+            }
+        }
+        if (TIFFWriteScanline(tif, buf, row, 0) < 0)
+            pm_error("failed a scanline write on row %d", row);
+    }
+    pnm_freepamrow(tuplerow);
+    free(buf);
+}
+
+
+
+static void
+analyzeColorsInRgbInput(struct pam *        const pamP,
+                        struct cmdlineInfo  const cmdline,
+                        int                 const maxcolors, 
+                        tupletable *        const chvP, 
+                        unsigned int *      const colorsP, 
+                        bool *              const grayscaleP) {
+/*----------------------------------------------------------------------------
+   Same as analyzeColors(), except assuming input image has R/G/B tuples.
+-----------------------------------------------------------------------------*/
+    if (cmdline.color && cmdline.truecolor) {
+        *chvP = NULL;
+        *grayscaleP = FALSE;
+    } else {
+        tupletable chv;
+        bool grayscale;
+
+        pm_message("computing colormap...");
+        chv = pnm_computetuplefreqtable2(pamP, NULL, maxcolors,
+                                         pamP->maxval,
+                                         colorsP);
+        if (chv == NULL) {
+            grayscale = FALSE;
+        } else {
+            unsigned int i;
+            pm_message("%u color%s found", 
+                       *colorsP, *colorsP == 1 ? "" : "s");
+            grayscale = TRUE;  /* initial assumption */
+            for (i = 0; i < *colorsP && grayscale; ++i) {
+                if (!pnm_rgbtupleisgray(chv[i]->tuple))
+                    grayscale = FALSE;
+            }
+        }
+        *grayscaleP = grayscale;
+
+        if (grayscale || cmdline.truecolor) {
+            *chvP = NULL;
+            if (chv)
+                pnm_freetupletable(pamP, chv);
+        } else {
+            /* He wants a colormap.  But can we give it to him? */
+            if (chv)
+                *chvP = chv;
+            else {
+                pm_message("Too many colors - "
+                           "proceeding to write a 24-bit RGB file.");
+                pm_message("If you want an 8-bit palette file, "
+                           "try doing a 'pnmquant %d'.",
+                           maxcolors);
+                *chvP = NULL;
+            }
+        }
+    }
+}
+
+
+
+static void
+analyzeColors(struct pam *        const pamP,
+              struct cmdlineInfo  const cmdline,
+              int                 const maxcolors, 
+              tupletable *        const chvP, 
+              unsigned int *      const colorsP, 
+              bool *              const grayscaleP) {
+/*----------------------------------------------------------------------------
+   Analyze the colors in the input image described by 'pamP', whose file
+   is positioned to the raster. 
+
+   If the colors, combined with command line options 'cmdline', indicate
+   a colormapped TIFF should be generated, return as *chvP the address
+   of a color map (in newly malloc'ed space).  If a colormapped TIFF is
+   not indicated, return *chvP == NULL.
+
+   Return *grayscaleP == true iff the image should be stored as a grayscale
+   image (which means the image is monochromatic and the user doesn't
+   insist on color format).
+
+   Leave the file position undefined.
+-----------------------------------------------------------------------------*/
+    if (pamP->depth >= 3)
+        /* Assume it's a color-capable input image
+           (tuple type RGB or RGB_ALPHA)
+        */
+        analyzeColorsInRgbInput(pamP, cmdline, maxcolors,
+                                chvP, colorsP, grayscaleP);
+    else {
+        *chvP = NULL;
+        *grayscaleP = TRUE;
+    }
+}
+
+
+
+static void
+computeRasterParm(struct pam *     const pamP,
+                  tupletable       const chv, 
+                  int              const colors, 
+                  bool             const grayscale,
+                  int              const compression,
+                  bool             const minisblack,
+                  bool             const miniswhite,
+                  struct sizeset   const indexsizeAllowed,
+                  unsigned short * const samplesperpixelP,
+                  unsigned short * const bitspersampleP,
+                  unsigned short * const photometricP,
+                  int            * const bytesperrowP) {
+/*----------------------------------------------------------------------------
+   Compute the parameters of the raster portion of the TIFF image.
+
+   'minisblack' and 'miniswhite' mean the user requests the corresponding
+   photometric.  Both FALSE means user has no explicit requirement.
+-----------------------------------------------------------------------------*/
+    unsigned short defaultPhotometric;
+    /* The photometric we use if the user specified no preference */
+
+    /* We determine the bits per sample by the maxval, except that if
+       it would be more than 8, we just use 16.  I don't know if bits
+       per sample between 8 and 16 are legal, but they aren't very
+       nice in any case.  If users want them, we should provide an
+       option.  It is not clear why we don't use bits per pixel < 8
+       for RGB images.  Note that code to handle maxvals <= 255 was
+       written long before maxval > 255 was possible and there are
+       backward compatibility requirements.  
+    */
+
+    if (pamP->depth == 1 && pamP->maxval == 1) {
+        *samplesperpixelP = 1;
+        *bitspersampleP = 1;
+        if ((compression == COMPRESSION_CCITTFAX3) ||
+            (compression == COMPRESSION_CCITTFAX4))
+            defaultPhotometric = PHOTOMETRIC_MINISWHITE;
+        else
+            defaultPhotometric = PHOTOMETRIC_MINISBLACK;
+    } else {
+        if (chv) {
+            *samplesperpixelP = 1;  /* Pixel is just the one index value */
+            *bitspersampleP = 
+                colors <=   2 && indexsizeAllowed.b1 ? 1 :
+                colors <=   4 && indexsizeAllowed.b2 ? 2 :
+                colors <=  16 && indexsizeAllowed.b4 ? 4 :
+                colors <= 256 && indexsizeAllowed.b8 ? 8 :
+                0;
+            if (*bitspersampleP == 0)
+                pm_error("Your -indexbits option is insufficient for the "
+                         "%d colors in this image.", colors);
+
+            defaultPhotometric = PHOTOMETRIC_PALETTE;
+        } else {
+            if (grayscale) {
+                /* We'll write just the first plane regardless of how many are
+                   in the input PAM.
+                */
+                *samplesperpixelP = 1;
+                *bitspersampleP =
+                    pamP->maxval > 255 ? 16 : pm_maxvaltobits(pamP->maxval);
+                defaultPhotometric = PHOTOMETRIC_MINISBLACK;
+            } else {
+                *samplesperpixelP = pamP->depth;
+                *bitspersampleP = pamP->maxval > 255 ? 16 : 8;
+                defaultPhotometric = PHOTOMETRIC_RGB;
+            }
+        }
+    }
+
+    if (miniswhite)
+        *photometricP = PHOTOMETRIC_MINISWHITE;
+    else if (minisblack)
+        *photometricP = PHOTOMETRIC_MINISBLACK;
+    else
+        *photometricP = defaultPhotometric;
+
+    {
+        unsigned int const samplesPerRow = pamP->width * *samplesperpixelP;
+
+        if (*bitspersampleP < 8) {
+            int samplesperbyte;
+            samplesperbyte = 8 / *bitspersampleP;
+            *bytesperrowP =
+                (samplesPerRow + samplesperbyte-1) / samplesperbyte;
+        } else {
+            if (UINT_MAX / *bitspersampleP < samplesPerRow)
+                pm_error("Too many bytes per row to compute");
+            *bytesperrowP = (samplesPerRow * *bitspersampleP) / 8;
+        }
+    }
+}
+
+
+
+static void
+validateSeekableOutputFile(int          const ofd,
+                           const char * const outFileName) {
+/*----------------------------------------------------------------------------
+   Validate that the file attached to file descriptor 'ofd' is capable
+   of seeking.  If not, fail the program.
+
+   This is useful because the TIFF library requires seekable output and
+   fails with an unhelpful error message about a file I/O error if it is
+   not.  We, on the other hand, give a helpful error message.
+
+   We leave the file positioned to the beginning.
+-----------------------------------------------------------------------------*/
+    int rc;
+
+    /* We'd like to get the current position and leave the file positioned
+       where we found it.  But that entails the mess with some systems
+       having 32 bit file offsets and some having 64 bit file offsets.
+    */
+
+    /* Some files can seek ahead, but not back.  So we test for the
+       more difficult backward seek.
+    */
+    lseek(ofd, 1, SEEK_SET);
+    rc = lseek(ofd, 0, SEEK_SET);
+            
+    if (rc < 0)
+        pm_error("Output file (%s) is not seekable.  lseek() returned "
+                 "errno %d (%s).  "
+                 "The TIFF library can write only to "
+                 "a seekable file.", 
+                 outFileName, errno, strerror(errno));
+}
+
+
+
+static void
+createTiffGenerator(int          const ofd, 
+                    const char * const outFileName,
+                    bool         const append,
+                    TIFF **      const tifPP) {
+
+    const char * option;
+
+    /* Before 10.12 (November 2002), we set O_NONBLOCK here:
+
+       fcntl( 1, F_SETFL, O_NONBLOCK ) ; 
+   
+       I have no idea why.  The comment attached said, 
+
+         acooke dec99 - otherwise blocks on read inside 
+         next line (Linux i386) 
+    */
+
+    validateSeekableOutputFile(ofd, outFileName);
+
+    if (append)
+        option = "a";
+    else
+        option = "w";
+
+    *tifPP = TIFFFdOpen(ofd, outFileName, option);
+    if (*tifPP == NULL)
+        pm_error("error opening standard output as TIFF file.  "
+                 "TIFFFdOpen() failed.");
+}
+
+
+
+static void
+destroyTiffGenerator(TIFF * const tifP) {
+
+    TIFFFlushData(tifP);
+    TIFFClose(tifP);
+}
+
+
+
+static void
+createTiffColorMap(struct pam *       const pamP,
+                   unsigned int       const bitspersample,
+                   tupletable         const chv,
+                   unsigned int       const colors,
+                   unsigned short *** const tiffColorMapP) {
+
+    unsigned int const colorMapSize = 1 << bitspersample;
+    unsigned short ** tiffColorMap;
+    unsigned int plane;
+    unsigned int i;
+    
+    MALLOCARRAY_NOFAIL(tiffColorMap, pamP->depth);
+    for (plane = 0; plane < pamP->depth; ++plane)
+        MALLOCARRAY_NOFAIL(tiffColorMap[plane], colorMapSize);
+    
+    for (i = 0; i < colorMapSize; ++i) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane) {
+            if (i < colors)
+                tiffColorMap[plane][i] =
+                    chv[i]->tuple[plane] * 65535L / pamP->maxval;
+            else
+                tiffColorMap[plane][i] = 0;
+        }
+    }
+    *tiffColorMapP = tiffColorMap;
+}
+        
+
+
+static void
+destroyTiffColorMap(struct pam *      const pamP,
+                    unsigned short ** const tiffColorMap) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane)
+        free(tiffColorMap[plane]);
+
+    free(tiffColorMap);
+}
+
+
+
+static void
+setTagListFields(const struct optNameValue * const taglist,
+                 TIFF *                      const tifP) {
+
+    unsigned int i;
+
+    for (i = 0; taglist[i].name; ++i) {
+        const tagDefinition * const tagDefP = tagDefFind(taglist[i].name);
+        
+        if (tagDefP->put)
+            tagDefP->put(tifP, tagDefP->tagnum, taglist[i].value,
+                         tagDefP->choices);
+    }
+}
+
+
+
+static void
+setTiffFields(TIFF *              const tifP,
+              struct cmdlineInfo  const cmdline,
+              struct pam *        const pamP,
+              unsigned short      const bitspersample,
+              unsigned short      const photometric,
+              unsigned short      const samplesperpixel,
+              unsigned short **   const tiffColorMap,
+              const char *        const inputFileDescription,
+              struct optNameValue const taglist[]) {
+
+    /* TODO: It would be cleaner to set all tags in a tag list and
+       then call setTagListFields() on that list.  That way,
+       TIFFSetField() is in only one place and all tags get set the
+       same way and we don't have this icky setting of default and
+       then resetting with user choice (e.g. DOCUMENTNAME).
+    */
+
+    /* Set TIFF parameters. */
+    TIFFSetField(tifP, TIFFTAG_IMAGEWIDTH,    pamP->width);
+    TIFFSetField(tifP, TIFFTAG_IMAGELENGTH,   pamP->height);
+    TIFFSetField(tifP, TIFFTAG_BITSPERSAMPLE, bitspersample);
+    if (cmdline.compression != -1)
+        TIFFSetField(tifP, TIFFTAG_COMPRESSION, cmdline.compression);
+    if (cmdline.compression == COMPRESSION_CCITTFAX3 && cmdline.g3options != 0)
+        TIFFSetField( tifP, TIFFTAG_GROUP3OPTIONS, cmdline.g3options);
+    if (cmdline.compression == COMPRESSION_LZW && cmdline.predictor != -1)
+        TIFFSetField( tifP, TIFFTAG_PREDICTOR, cmdline.predictor);
+    TIFFSetField(tifP, TIFFTAG_PHOTOMETRIC, photometric);
+    if (cmdline.fillorder != -1)
+        TIFFSetField(tifP, TIFFTAG_FILLORDER, cmdline.fillorder);
+    TIFFSetField(tifP, TIFFTAG_SAMPLESPERPIXEL, samplesperpixel);
+    if (cmdline.rowsperstrip != -1)
+        TIFFSetField(tifP, TIFFTAG_ROWSPERSTRIP, cmdline.rowsperstrip);
+    else
+        TIFFSetField(tifP, TIFFTAG_ROWSPERSTRIP,
+                     TIFFDefaultStripSize(tifP, 0));
+    if (cmdline.xresolution != -1 ||
+        cmdline.yresolution != -1 ||
+        cmdline.resolutionUnit != -1) {
+        TIFFSetField(tifP, TIFFTAG_RESOLUTIONUNIT,
+                     cmdline.resolutionUnit != -1 ?
+                     cmdline.resolutionUnit : RESUNIT_NONE);
+    }
+    if (cmdline.xresolution > 0)
+        TIFFSetField(tifP, TIFFTAG_XRESOLUTION, cmdline.xresolution);
+    if ( cmdline.yresolution > 0)
+        TIFFSetField(tifP, TIFFTAG_YRESOLUTION, cmdline.yresolution);
+    TIFFSetField(tifP, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
+
+    if (tiffColorMap)
+        TIFFSetField(tifP, TIFFTAG_COLORMAP,
+                     tiffColorMap[PAM_RED_PLANE],
+                     tiffColorMap[PAM_GRN_PLANE],
+                     tiffColorMap[PAM_BLU_PLANE]);
+
+
+    TIFFSetField(tifP, TIFFTAG_DOCUMENTNAME,     inputFileDescription);
+    TIFFSetField(tifP, TIFFTAG_IMAGEDESCRIPTION, "converted PNM file");
+ 
+    /* Some of taglist[] overrides defaults we set above.  But taglist[]
+       is defined not to specify any tag types that are not purely user
+       choice.
+    */
+    setTagListFields(taglist, tifP);
+}
+
+
+
+static void
+convertImage(FILE *             const ifP,
+             TIFF *             const tifP,
+             const char *       const inputFileDescription,
+             struct cmdlineInfo const cmdline) {
+
+    tupletable chv;
+    tuplehash cht;
+    unsigned short ** tiffColorMap;  /* malloc'ed */
+    struct pam pam;
+    unsigned int colors;
+    bool grayscale;
+    unsigned short photometric;
+    unsigned short samplesperpixel;
+    unsigned short bitspersample;
+    unsigned short tiff_maxval;
+    /* This is the maxval of the samples in the tiff file.  It is 
+       determined solely by the bits per sample ('bitspersample').
+       */
+    int bytesperrow;
+    pm_filepos rasterPos;
+
+    pnm_readpaminit(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
+
+    pm_tell2(ifP, &rasterPos, sizeof(rasterPos));
+
+    analyzeColors(&pam, cmdline, MAXCOLORS, &chv, &colors, &grayscale);
+
+    /* Go back to beginning of raster */
+    pm_seek2(ifP, &rasterPos, sizeof(rasterPos));  
+
+    /* Figure out TIFF parameters. */
+
+    computeRasterParm(&pam, chv, colors, grayscale, 
+                      cmdline.compression,
+                      cmdline.minisblack, cmdline.miniswhite,
+                      cmdline.indexsizeAllowed,
+                      &samplesperpixel, &bitspersample, &photometric,
+                      &bytesperrow);
+
+    tiff_maxval = pm_bitstomaxval(bitspersample);
+
+    if (!chv) {
+        cht = NULL;
+        tiffColorMap = NULL;
+    } else {
+        createTiffColorMap(&pam, bitspersample, chv, colors, &tiffColorMap);
+
+        /* Convert color vector to color hash table, for fast lookup. */
+        cht = pnm_computetupletablehash(&pam, chv, colors);
+        pnm_freetupletable(&pam, chv);
+    }
+
+    setTiffFields(tifP, cmdline, &pam, bitspersample, photometric,
+                  samplesperpixel, tiffColorMap, inputFileDescription,
+                  cmdline.taglist);
+
+    writeScanLines(&pam, tifP, cht,
+                   tiff_maxval, bitspersample, photometric, bytesperrow, 
+                   cmdline.fillorder);
+
+    if (tiffColorMap)
+        destroyTiffColorMap(&pam, tiffColorMap);
+}
+
+
+
+static void
+validateReadableStdout(void) {
+/*----------------------------------------------------------------------------
+  We validate that Standard Output is readable and fail the program if
+  it isn't.
+
+  This is useful because there are situations in which the TIFF library
+  must read the output file and if it can't, it fails with an unhelpful
+  error message about a file I/O error.  We, on the other hand, produce
+  a helpful error message.
+-----------------------------------------------------------------------------*/
+    int flags;
+
+    flags = fcntl(STDOUT_FILENO, F_GETFL);
+
+    if (flags < 0) {
+        /* We couldn't get the flags.  So just assume the file's OK */
+    } else {
+        if ((flags & O_RDONLY) || (flags & O_RDWR)) {
+            /* File is readable.  All is well. */
+        } else
+            pm_error("Standard Output is not opened for reading.  "
+                     "In order to create a multi-image TIFF stream, "
+                     "Standard Output must be both readable and writable.");
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+    struct cmdlineInfo cmdline;
+    const char * inputFileDescription;
+    FILE* ifP;
+    TIFF* tifP;
+    bool eof;
+    unsigned int imageSeq;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    ifP = pm_openr_seekable(cmdline.input_filespec);
+
+    if (streq(cmdline.input_filespec, "-"))
+        inputFileDescription = "Standard Input";
+    else 
+        inputFileDescription = cmdline.input_filespec;
+
+    if (cmdline.append)
+        validateReadableStdout();
+
+    createTiffGenerator(STDOUT_FILENO, "Standard Output", cmdline.append,
+                        &tifP);
+
+    eof = FALSE;  /* initial assumption */
+    imageSeq = 0;
+
+    while (!eof) {
+        bool success;
+
+        if (cmdline.verbose)
+            pm_message("Converting Image %u", imageSeq);
+
+        pnm_nextimage(ifP, &eof);
+
+        if (!eof) {
+            if (imageSeq > 0)
+                validateReadableStdout();
+
+            convertImage(ifP, tifP, inputFileDescription, cmdline);
+            
+            success = TIFFWriteDirectory(tifP);
+            if (!success)
+                pm_error("Unable to write TIFF image %u to file.  "
+                         "tifWriteDirectory() failed.", imageSeq);
+            ++imageSeq;
+        }
+    }
+
+    destroyTiffGenerator(tifP);
+    pm_close(ifP);
+
+    return 0;
+}