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.c593
1 files changed, 378 insertions, 215 deletions
diff --git a/converter/other/pamtotiff.c b/converter/other/pamtotiff.c
index 1b31c65b..7b645b23 100644
--- a/converter/other/pamtotiff.c
+++ b/converter/other/pamtotiff.c
@@ -27,12 +27,6 @@
 #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
@@ -59,16 +53,19 @@
 #define COMPRESSION_ADOBE_DEFLATE 8
 #endif
 
-struct sizeset {
+typedef struct {
     bool b1, b2, b4, b8;
-};
+} SizeSet;
+
+typedef enum { TMPFILE, DIRECT_CREATE, DIRECT_APPEND } WriteMethod;
 
+typedef enum { MUST_EXIST, MAY_CREATE } CreatePolicy;
 
-struct cmdlineInfo {
+typedef struct {
     /* 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 */
+    const char * inputFileName;
     int compression;
         /* COMPRESSION Tiff tag value, that corresponds to the compression
            option the user specified, or -1 if he didn't specify any.
@@ -84,14 +81,15 @@ struct cmdlineInfo {
     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;
+    int resolutionunit;  /* RESOLUTIONUNIT Tiff tag value */
+    SizeSet indexsizeAllowed;
     /* Which bit widths are allowable in a raster of palette indices */
     unsigned int verbose;
-    unsigned int append;
+    WriteMethod writeMethod;  /* Output mode */
+    const char * output; /* -output option value.  NULL if none. */
     float resolution;  /* X and Y resolution */
     struct optNameValue * taglist;
-};
+} CmdlineInfo;
 
 
 
@@ -102,7 +100,7 @@ validateTagList(struct optNameValue const taglist[]) {
     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 {
@@ -137,9 +135,9 @@ validateTagList(struct optNameValue const taglist[]) {
 
 
 static void
-parseCommandLine(int                        argc,
-                 char **              const argv,
-                 struct cmdlineInfo * const cmdlineP) {
+parseCommandLine(int                 argc,
+                 const char ** const argv,
+                 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.
@@ -151,10 +149,11 @@ parseCommandLine(int                        argc,
     unsigned int none, packbits, lzw, g3, g4, msb2lsb, lsb2msb, opt_2d, fill;
     unsigned int flate, adobeflate;
     char * indexbits;
-    char * resolutionUnit;
+    char * resolutionunit;
 
-    unsigned int predictorSpec, rowsperstripSpec, xresolutionSpec,
-        yresolutionSpec, indexbitsSpec, resolutionUnitSpec, tagSpec;
+    unsigned int appendSpec, outputSpec, predictorSpec, rowsperstripSpec,
+                 xresolutionSpec, yresolutionSpec, indexbitsSpec,
+      resolutionunitSpec, tagSpec;
 
     unsigned int option_def_index;
 
@@ -162,7 +161,6 @@ parseCommandLine(int                        argc,
 
     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);
@@ -180,17 +178,20 @@ parseCommandLine(int                        argc,
     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,    
+    OPTENT3(0, "append",       OPT_FLAG,   NULL, &appendSpec,       0);
+    OPTENT3(0, "output",       OPT_STRING, &cmdlineP->output,
+            &outputSpec,       0);
+    OPTENT3(0, "predictor",    OPT_UINT,   &cmdlineP->predictor,
             &predictorSpec,    0);
-    OPTENT3(0, "rowsperstrip", OPT_UINT,   &cmdlineP->rowsperstrip, 
+    OPTENT3(0, "rowsperstrip", OPT_UINT,   &cmdlineP->rowsperstrip,
             &rowsperstripSpec, 0);
-    OPTENT3(0, "xresolution",  OPT_FLOAT,  &cmdlineP->xresolution,  
+    OPTENT3(0, "xresolution",  OPT_FLOAT,  &cmdlineP->xresolution,
             &xresolutionSpec,  0);
-    OPTENT3(0, "yresolution",  OPT_FLOAT,  &cmdlineP->yresolution,  
+    OPTENT3(0, "yresolution",  OPT_FLOAT,  &cmdlineP->yresolution,
             &yresolutionSpec,  0);
-    OPTENT3(0, "resolutionunit", OPT_STRING, &resolutionUnit,
-            &resolutionUnitSpec,    0);
-    OPTENT3(0, "indexbits",    OPT_STRING,   &indexbits, 
+    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);
 
@@ -198,14 +199,14 @@ parseCommandLine(int                        argc,
     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);
+    pm_optParseOptions3(&argc, (char**)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)
@@ -222,7 +223,7 @@ parseCommandLine(int                        argc,
         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.");
@@ -231,9 +232,9 @@ parseCommandLine(int                        argc,
         cmdlineP->fillorder = FILLORDER_MSB2LSB;
     else if (lsb2msb)
         cmdlineP->fillorder = FILLORDER_LSB2MSB;
-    else 
+    else
         cmdlineP->fillorder = FILLORDER_MSB2LSB;
-    
+
 
     if (cmdlineP->miniswhite && cmdlineP->minisblack)
         pm_error("You cannot specify both -miniswhite and -minisblack");
@@ -244,9 +245,17 @@ parseCommandLine(int                        argc,
     if (fill)
         cmdlineP->g3options |= GROUP3OPT_FILLBITS;
 
+    if (outputSpec) {
+        if (appendSpec)
+            cmdlineP->writeMethod = DIRECT_APPEND;
+        else
+            cmdlineP->writeMethod = DIRECT_CREATE;
+    } else
+        cmdlineP->writeMethod = TMPFILE;
+
     if (predictorSpec) {
         if (cmdlineP->predictor != 1 && cmdlineP->predictor != 2)
-            pm_error("-predictor may be only 1 or 2.  You specified %d.", 
+            pm_error("-predictor may be only 1 or 2.  You specified %d.",
                      cmdlineP->predictor);
     } else
         cmdlineP->predictor = -1;
@@ -272,25 +281,25 @@ parseCommandLine(int                        argc,
     } 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;
+    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);
+                     "You specified '%s'.", resolutionunit);
     } else
-        cmdlineP->resolutionUnit = RESUNIT_INCH;
+        cmdlineP->resolutionunit = RESUNIT_INCH;
 
     if (indexbitsSpec) {
         if (strstr(indexbits, "1"))
@@ -324,46 +333,18 @@ parseCommandLine(int                        argc,
         cmdlineP->taglist[0].value = NULL;
     }
 
-    if (argc-1 == 0) 
-        cmdlineP->input_filespec = "-";
+    if (argc-1 == 0)
+        cmdlineP->inputFileName = "-";
     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];
+        cmdlineP->inputFileName = 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)) = s2;
-        (*tPP) += sizeof(short);
-    } else
-        *(*tPP)++ = s2 & 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,
@@ -380,9 +361,9 @@ fillRowOfSubBytePixels(struct pam *    const pamP,
     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.  
+           the output buffer.
         */
-    int const firstbitshift = 
+    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).
@@ -397,7 +378,7 @@ fillRowOfSubBytePixels(struct pam *    const pamP,
         /* 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) {
@@ -406,7 +387,7 @@ fillRowOfSubBytePixels(struct pam *    const pamP,
             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 {
@@ -440,6 +421,36 @@ fillRowOfSubBytePixels(struct pam *    const pamP,
 
 
 static void
+putSample(sample           const s,
+          sample           const maxval,
+          sample           const tiffMaxval,
+          unsigned int     const bitspersample,
+          bool             const minIsWhite,
+          unsigned char ** const tPP) {
+
+    /* Until release 10.48 (September 2009), we ignored the min-is-white
+       photometric (i.e. treated it like min-is-black).  Nobody has ever
+       complained, but it seems clear to me that that was wrong, so I
+       changed it.  We have always respected it for sub-byte samples,
+       and have always respected it going the other direction, in
+       Tifftopnm.
+       - Bryan.
+    */
+
+    xelval const s2 = maxval == tiffMaxval ? s : s * tiffMaxval / maxval;
+
+    xelval const s3 = minIsWhite ? tiffMaxval - s2 : s2;
+
+    if (bitspersample > 8) {
+        *((unsigned short *)(*tPP)) = s3;
+        (*tPP) += sizeof(short);
+    } else
+        *(*tPP)++ = s3 & 0xff;
+}
+
+
+
+static void
 fillRowOfWholeBytePixels(struct pam *    const pamP,
                          tuple *         const tuplerow,
                          unsigned char * const buf,
@@ -447,10 +458,12 @@ fillRowOfWholeBytePixels(struct pam *    const pamP,
                          unsigned short  const tiffMaxval,
                          unsigned int    const bitsPerSample) {
 
+    bool const minIsWhite = (photometric == PHOTOMETRIC_MINISWHITE);
+
     unsigned int col;
     unsigned char * tP;
     unsigned int planes;
-    
+
     if (photometric == PHOTOMETRIC_RGB)
         planes = pamP->depth;
     else
@@ -463,22 +476,22 @@ fillRowOfWholeBytePixels(struct pam *    const pamP,
         unsigned int plane;
         for (plane = 0; plane < planes; ++plane) {
             putSample(tuplerow[col][plane], pamP->maxval,
-                      tiffMaxval, bitsPerSample, &tP);
+                      tiffMaxval, bitsPerSample, minIsWhite, &tP);
             /* Advances tP */
         }
     }
-} 
+}
 
 
 
 static void
 writeScanLines(struct pam *   const pamP,
-               TIFF *         const tif, 
+               TIFF *         const tif,
                tuplehash      const cht,
                unsigned short const tiffMaxval,
-               unsigned short const bitspersample, 
+               unsigned short const bitspersample,
                unsigned short const photometric,
-               int            const bytesperrow, 
+               int            const bytesperrow,
                int            const fillorder) {
 /*----------------------------------------------------------------------------
    Write out the raster for the input image described by 'pamP', whose
@@ -492,15 +505,19 @@ writeScanLines(struct pam *   const pamP,
        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
+       it's 8, 'buf' is an array of 8 bit unsigned integers, one
+       per pixel sample.  If it's less than 8, it's an array of bytes,
+       each of which represents 1-8 pixel samples, 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.
+       whole samples.
 
        In all cases, the array elements are in order left to right going
        from low array indices to high array indices.
+
+       The samples form pixel values according to the pixel format indicated
+       by the TIFF photometric.  E.g. if it is MINISWHITE, then a pixel is
+       one sample and a value of 0 for that sample means white.
     */
     MALLOCARRAY(buf, bytesperrow);
 
@@ -508,7 +525,7 @@ writeScanLines(struct pam *   const pamP,
         pm_error("can't allocate memory for row buffer");
 
     tuplerow = pnm_allocpamrow(pamP);
-    
+
     for (row = 0; row < pamP->height; ++row) {
         int col;
 
@@ -530,9 +547,9 @@ writeScanLines(struct pam *   const pamP,
                 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!  "
@@ -556,12 +573,12 @@ writeScanLines(struct pam *   const pamP,
 
 
 static void
-analyzeColorsInRgbInput(struct pam *        const pamP,
-                        struct cmdlineInfo  const cmdline,
-                        int                 const maxcolors, 
-                        tupletable *        const chvP, 
-                        unsigned int *      const colorsP, 
-                        bool *              const grayscaleP) {
+analyzeColorsInRgbInput(struct pam *   const pamP,
+                        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.
 -----------------------------------------------------------------------------*/
@@ -580,7 +597,7 @@ analyzeColorsInRgbInput(struct pam *        const pamP,
             grayscale = FALSE;
         } else {
             unsigned int i;
-            pm_message("%u color%s found", 
+            pm_message("%u color%s found",
                        *colorsP, *colorsP == 1 ? "" : "s");
             grayscale = TRUE;  /* initial assumption */
             for (i = 0; i < *colorsP && grayscale; ++i) {
@@ -613,15 +630,15 @@ analyzeColorsInRgbInput(struct pam *        const pamP,
 
 
 static void
-analyzeColors(struct pam *        const pamP,
-              struct cmdlineInfo  const cmdline,
-              int                 const maxcolors, 
-              tupletable *        const chvP, 
-              unsigned int *      const colorsP, 
-              bool *              const grayscaleP) {
+analyzeColors(struct pam *   const pamP,
+              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. 
+   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
@@ -650,13 +667,13 @@ analyzeColors(struct pam *        const pamP,
 
 static void
 computeRasterParm(struct pam *     const pamP,
-                  tupletable       const chv, 
-                  int              const colors, 
+                  tupletable       const chv,
+                  int              const colors,
                   bool             const grayscale,
                   int              const compression,
                   bool             const minisblack,
                   bool             const miniswhite,
-                  struct sizeset   const indexsizeAllowed,
+                  SizeSet          const indexsizeAllowed,
                   unsigned short * const samplesperpixelP,
                   unsigned short * const bitspersampleP,
                   unsigned short * const photometricP,
@@ -677,7 +694,7 @@ computeRasterParm(struct pam *     const pamP,
        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.  
+       backward compatibility requirements.
     */
 
     if (pamP->depth == 1 && pamP->maxval == 1) {
@@ -691,7 +708,7 @@ computeRasterParm(struct pam *     const pamP,
     } else {
         if (chv) {
             *samplesperpixelP = 1;  /* Pixel is just the one index value */
-            *bitspersampleP = 
+            *bitspersampleP =
                 colors <=   2 && indexsizeAllowed.b1 ? 1 :
                 colors <=   4 && indexsizeAllowed.b2 ? 2 :
                 colors <=  16 && indexsizeAllowed.b4 ? 4 :
@@ -744,18 +761,53 @@ computeRasterParm(struct pam *     const pamP,
 
 
 
-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.
+  WRITE MODES
+  -----------
+  
+  The Tiff library does all output.  There are several issues:
+  
+    1) The manner of output is opaque to the library client.  I.e.  we cannot
+       see or control it.
+
+    2) The output file must be random-access.
+
+    3) The output file must be writable and readable for multiple-image
+       streams.  (This includes append operations.)
+
+    4) The Tiff library produces unhelpful error messages when the above
+       conditions are not met.
+  
+  We provide two modes for output:
+  
+  1. Tmpfile mode (default)
+  
+     We have the Tiff library direct output to an unnamed temporary file we
+     create which is seekable and readable.  When output is complete, we copy
+     the file's contents to Standard Output.
+  
+  2. Direct mode (specified with -output)
+  
+     We have the Tiff library write output to the specified file.  As the Tiff
+     library requires taht it be be seekable and readable, we fail the program
+     rather than ask the Tiff library to use the file if it does not meet
+     these requirements.
+  
+     Direct mode is further divided into append and create.  They are the same
+     except that in append mode, we insist that the file already exist,
+     whereas with create mode, we create it if necessary.  In either case, if
+     the file already exists, he Tiff library appends the output to it.
+-----------------------------------------------------------------------------*/
+
 
-   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.
+static bool
+fileIsSeekable(int          const ofd,
+               const char * const outFileName) {
+/*----------------------------------------------------------------------------
+  The file represented by 'ofd' iscapable of seeking.
+
+  As a side effect, we position the file to the beginning.
 -----------------------------------------------------------------------------*/
     int rc;
 
@@ -769,44 +821,170 @@ validateSeekableOutputFile(int          const ofd,
     */
     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));
+
+    return rc >= 0;
+
+}
+
+
+
+static void
+validateReadableOutputFile(int const ofd) {
+/*----------------------------------------------------------------------------
+  Validate that file 'ofd' 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.
+-----------------------------------------------------------------------------*/
+#if !MSVCRT
+
+    int flags;
+
+    flags = fcntl(ofd, 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("Output is not opened for reading.  "
+                     "In order to create a multi-image TIFF stream, "
+                     "output must be both readable and writable.");
+    }
+#endif
 }
 
 
 
 static void
-createTiffGenerator(int          const ofd, 
-                    const char * const outFileName,
-                    bool         const append,
-                    TIFF **      const tifPP) {
+createTiffGeneratorDirect(const char * const outputFileName,
+                          CreatePolicy const createPolicy,
+                          TIFF **      const tifPP,
+                          int  *       const ofdP) {
+/*----------------------------------------------------------------------------
+  Create a TIFF generator that writes its output to the specified file.
 
-    const char * option;
+  If the file doesn't already exist and 'createPolicy' is MayCreate,
+  create the file; otherwise fail the program.
 
-    validateSeekableOutputFile(ofd, outFileName);
+  Fail the program if the specified file is not seekable and readable.
 
-    if (append)
-        option = "a";
+  Return the handle of the TIFF generator as *tifPP.  Also return the
+  file descriptor for the output file as *ofdP.
+-----------------------------------------------------------------------------*/
+    int fd;
+
+    if (createPolicy == MUST_EXIST)
+        fd = open(outputFileName, O_RDWR);
     else
-        option = "w";
+        fd = open(outputFileName, (O_RDWR | O_CREAT), 00644);
+
+    if (fd == -1) {
+        if (errno == ENOENT) /* Possible only if MustExist */
+            pm_error ("Cannot open file : '%s'.  File does not exist.",
+                      outputFileName);
+        else
+            pm_error ("Cannot open file : '%s'.  open() failed with "
+                      "errno %d (%s).  ",
+                      outputFileName, errno, strerror(errno));
+    }
 
-    *tifPP = TIFFFdOpen(ofd, outFileName, option);
+    if (!fileIsSeekable(fd, outputFileName))
+        pm_error("Output file (%s) is not seekable.  "
+                 "lseek() returned errno %d (%s).  "
+                 "The TIFF library can write only to "
+                 "a seekable file.",
+                 outputFileName, errno, strerror(errno));
+
+    *tifPP = TIFFFdOpen(fd, outputFileName, "a");
     if (*tifPP == NULL)
-        pm_error("error opening standard output as TIFF file.  "
+        pm_error("error opening file %s as TIFF file.  "
+                 "TIFFFdOpen() failed.", outputFileName);
+
+    *ofdP = fd;
+}
+
+
+
+static void
+createTiffGeneratorTmpfile(TIFF ** const tifPP,
+                            int  * const ofdP) {
+/*----------------------------------------------------------------------------
+  Create a TIFF generator that writes its output to an unnnamed temporary file
+  we create.
+
+  Return the handle of the TIFF generator as *tifPP.  Also return the file
+  descriptor for the temporary file as *ofdP.
+
+  The TIFF generator has a file name attribute, but it is just for messages;
+  it is not the name of a file.  We use "Internal Temporary File".
+-----------------------------------------------------------------------------*/
+    int fd;
+
+    fd = pm_tmpfile_fd();
+
+    *tifPP = TIFFFdOpen(fd, "Internal Temporary File", "w");
+
+    if (*tifPP == NULL)
+        pm_error("error opening temporary file as TIFF file.  "
                  "TIFFFdOpen() failed.");
+
+    *ofdP = fd;
+}
+
+
+
+static void
+copyBufferToStdout(int const tmpfileFd) {
+
+    FILE * tmpfileP;
+
+    tmpfileP = fdopen(tmpfileFd, "rb");
+
+    fseek(tmpfileP, 0, SEEK_SET);
+
+    while (!feof(tmpfileP) && !ferror(tmpfileP) && !ferror(stdout)) {
+        char buffer[4096];
+        size_t bytesReadCt;
+
+        bytesReadCt = fread(buffer, 1, sizeof(buffer), tmpfileP);
+
+        if (ferror(tmpfileP))
+            pm_error("Error reading from temporary file.  "
+                     "Incomplete output.  "
+                     "Errno = %s (%d)", strerror(errno), errno);
+        else
+            fwrite(buffer, 1, bytesReadCt, stdout);
+    }
+
+    /* POSIX lets us create a FILE from an existing file descriptor, but
+       does not provide a way to destroy the FILE and keep the file
+       descriptor.  The following fclose() closes the file.  Caller
+       must not access the file again, and if he attempts to close it,
+       must ignore the failure of close
+    */
+    fclose(tmpfileP);
 }
 
 
 
 static void
-destroyTiffGenerator(TIFF * const tifP) {
+destroyTiffGenerator(WriteMethod const writeMethod,
+                     TIFF *      const tifP,
+                     int         const ofd) {
 
     TIFFFlushData(tifP);
+
+    if (writeMethod == TMPFILE)
+        copyBufferToStdout(ofd);
+
+    /* If we copied the buffer above, the buffer file is already closed
+       (copyBufferToStdout closes it), TIFFClose appears to tolerate that -
+       all it does is a close() and doesn't mind that it fails.
+    */
     TIFFClose(tifP);
 }
 
@@ -823,11 +1001,11 @@ createTiffColorMap(struct pam *       const pamP,
     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) {
@@ -840,7 +1018,7 @@ createTiffColorMap(struct pam *       const pamP,
     }
     *tiffColorMapP = tiffColorMap;
 }
-        
+
 
 
 static void
@@ -865,7 +1043,7 @@ setTagListFields(const struct optNameValue * const taglist,
 
     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);
@@ -876,7 +1054,7 @@ setTagListFields(const struct optNameValue * const taglist,
 
 static void
 setTiffFields(TIFF *              const tifP,
-              struct cmdlineInfo  const cmdline,
+              CmdlineInfo         const cmdline,
               struct pam *        const pamP,
               unsigned short      const bitspersample,
               unsigned short      const photometric,
@@ -911,12 +1089,17 @@ setTiffFields(TIFF *              const tifP,
     else
         TIFFSetField(tifP, TIFFTAG_ROWSPERSTRIP,
                      TIFFDefaultStripSize(tifP, 0));
+    /* Since Netpbm 10.31, we prefer that the user use -tags to specify
+       RESOLUTIONUNIT, XRESOLUTION, and YRESOLUTION, but retain
+       -xresolution, -yresolution, and -resolutionunit for backward
+       compatibility
+    */
     if (cmdline.xresolution != -1 ||
         cmdline.yresolution != -1 ||
-        cmdline.resolutionUnit != -1) {
+        cmdline.resolutionunit != -1) {
         TIFFSetField(tifP, TIFFTAG_RESOLUTIONUNIT,
-                     cmdline.resolutionUnit != -1 ?
-                     cmdline.resolutionUnit : RESUNIT_NONE);
+                     cmdline.resolutionunit != -1 ?
+                     cmdline.resolutionunit : RESUNIT_NONE);
     }
     if (cmdline.xresolution > 0)
         TIFFSetField(tifP, TIFFTAG_XRESOLUTION, cmdline.xresolution);
@@ -933,7 +1116,7 @@ setTiffFields(TIFF *              const tifP,
 
     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.
@@ -944,10 +1127,10 @@ setTiffFields(TIFF *              const tifP,
 
 
 static void
-convertImage(FILE *             const ifP,
-             TIFF *             const tifP,
-             const char *       const inputFileDescription,
-             struct cmdlineInfo const cmdline) {
+convertImage(FILE *       const ifP,
+             TIFF *       const tifP,
+             const char * const inputFileDescription,
+             CmdlineInfo  const cmdline) {
 
     tupletable chv;
     tuplehash cht;
@@ -959,7 +1142,7 @@ convertImage(FILE *             const ifP,
     unsigned short samplesperpixel;
     unsigned short bitspersample;
     unsigned short tiff_maxval;
-    /* This is the maxval of the samples in the tiff file.  It is 
+    /* This is the maxval of the samples in the tiff file.  It is
        determined solely by the bits per sample ('bitspersample').
        */
     int bytesperrow;
@@ -972,11 +1155,11 @@ convertImage(FILE *             const ifP,
     analyzeColors(&pam, cmdline, MAXCOLORS, &chv, &colors, &grayscale);
 
     /* Go back to beginning of raster */
-    pm_seek2(ifP, &rasterPos, sizeof(rasterPos));  
+    pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
 
     /* Figure out TIFF parameters. */
 
-    computeRasterParm(&pam, chv, colors, grayscale, 
+    computeRasterParm(&pam, chv, colors, grayscale,
                       cmdline.compression,
                       cmdline.minisblack, cmdline.miniswhite,
                       cmdline.indexsizeAllowed,
@@ -1001,7 +1184,7 @@ convertImage(FILE *             const ifP,
                   cmdline.taglist);
 
     writeScanLines(&pam, tifP, cht,
-                   tiff_maxval, bitspersample, photometric, bytesperrow, 
+                   tiff_maxval, bitspersample, photometric, bytesperrow,
                    cmdline.fillorder);
 
     if (tiffColorMap)
@@ -1010,63 +1193,41 @@ convertImage(FILE *             const ifP,
 
 
 
-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.
------------------------------------------------------------------------------*/
-#if !defined(WIN32) || defined(__CYGWIN__)
-
-    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.");
-    }
-#endif
-}
 
 
 
 int
-main(int argc, char *argv[]) {
-    struct cmdlineInfo cmdline;
+main(int argc, const char *argv[]) {
+    CmdlineInfo cmdline;
     const char * inputFileDescription;
-    FILE* ifP;
-    TIFF* tifP;
-    bool eof;
+    FILE * ifP;
+    TIFF * tifP;
+    int ofd;
+    int eof;
     unsigned int imageSeq;
-
-    pnm_init(&argc, argv);
-
-    parseCommandLine(argc, argv, &cmdline);
     
-    ifP = pm_openr_seekable(cmdline.input_filespec);
+    pm_proginit(&argc, argv);
 
-    if (streq(cmdline.input_filespec, "-"))
-        inputFileDescription = "Standard Input";
-    else 
-        inputFileDescription = cmdline.input_filespec;
+    parseCommandLine(argc, argv, &cmdline);
 
-    if (cmdline.append)
-        validateReadableStdout();
+    ifP = pm_openr_seekable(cmdline.inputFileName);
 
-    createTiffGenerator(STDOUT_FILENO, "Standard Output", cmdline.append,
-                        &tifP);
+    if (streq(cmdline.inputFileName, "-"))
+        inputFileDescription = "Standard Input";
+    else
+        inputFileDescription = cmdline.inputFileName;
+
+    switch (cmdline.writeMethod) {
+    case DIRECT_APPEND:
+        createTiffGeneratorDirect(cmdline.output, MUST_EXIST,  &tifP, &ofd);
+        break;
+    case DIRECT_CREATE:
+        createTiffGeneratorDirect(cmdline.output, MAY_CREATE,  &tifP, &ofd);
+        break;
+    case TMPFILE:
+        createTiffGeneratorTmpfile(&tifP, &ofd);
+        break;
+    }
 
     eof = FALSE;  /* initial assumption */
     imageSeq = 0;
@@ -1074,17 +1235,17 @@ main(int argc, char *argv[]) {
     while (!eof) {
         bool success;
 
-        if (cmdline.verbose)
-            pm_message("Converting Image %u", imageSeq);
-
         pnm_nextimage(ifP, &eof);
 
         if (!eof) {
             if (imageSeq > 0)
-                validateReadableStdout();
+                validateReadableOutputFile(ofd);
+
+            if (cmdline.verbose)
+                pm_message("Converting Image %u", imageSeq);
 
             convertImage(ifP, tifP, inputFileDescription, cmdline);
-            
+
             success = TIFFWriteDirectory(tifP);
             if (!success)
                 pm_error("Unable to write TIFF image %u to file.  "
@@ -1093,8 +1254,10 @@ main(int argc, char *argv[]) {
         }
     }
 
-    destroyTiffGenerator(tifP);
+    destroyTiffGenerator(cmdline.writeMethod, tifP, ofd);
     pm_close(ifP);
 
     return 0;
 }
+
+