1 /*
2 ** tifftopnm.c - converts a Tagged Image File to a portable anymap
3 **
4 ** Derived by Jef Poskanzer from tif2ras.c, which is:
5 **
6 ** Copyright (c) 1990 by Sun Microsystems, Inc.
7 **
8 ** Author: Patrick J. Naughton
9 ** naughton@wind.sun.com
10 **
11 ** Permission to use, copy, modify, and distribute this software and its
12 ** documentation for any purpose and without fee is hereby granted,
13 ** provided that the above copyright notice appear in all copies and that
14 ** both that copyright notice and this permission notice appear in
15 ** supporting documentation.
16 **
17 ** This file is provided AS IS with no warranties of any kind.  The author
18 ** shall have no liability with respect to the infringement of copyrights,
19 ** trade secrets or any patents by this file or any part thereof.  In no
20 ** event will the author be liable for any lost revenue or profits or
21 ** other special, indirect and consequential damages.
22 */
23 
24 /* Design note:
25 
26    We have two different ways of converting from Tiff, as provided by the
27    Tiff library:
28 
29    1) decode the entire image into memory at once, using
30       TIFFRGBAImageGet(), then convert to PNM and output row by row.
31 
32    2) read, convert, and output one row at a time using TIFFReadScanline().
33 
34    (1) is preferable because the Tiff library does more of the work, which
35    means it understands more of the Tiff format possibilities now and in
36    the future.  Also, some compressed TIFF formats don't allow you to
37    extract an individual row.
38 
39    (2) uses far less memory, and because our code does more of the work,
40    it's possible that it can be more flexible or at least give better
41    diagnostic information if there's something wrong with the TIFF.
42 
43    In Netpbm, we stress function over performance, so by default we
44    try (1) first, and if we can't get enough memory for the decoded
45    image or TIFFRGBAImageGet() fails, we fall back to (2).  But we
46    give the user the -byrow option to order (2) only.
47 */
48 
49 #define _DEFAULT_SOURCE 1  /* New name for SVID & BSD source defines */
50 #define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
51 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
52 
53 #include <assert.h>
54 #include <string.h>
55 #include <stdio.h>
56 #include <sys/wait.h>  /* WIFSIGNALED, etc. */
57 
58 #include "pm_c_util.h"
59 #include "shhopt.h"
60 #include "mallocvar.h"
61 #include "nstring.h"
62 #include "pnm.h"
63 
64 /* See warning about tiffio.h in pamtotiff.c */
65 #include <tiffio.h>
66 
67 /* The following are in current tiff.h, but so that we can compile against
68    older tiff libraries, we define them here.
69 */
70 
71 #ifndef PHOTOMETRIC_LOGL
72 #define PHOTOMETRIC_LOGL 32844
73 #endif
74 #ifndef PHOTOMETRIC_LOGLUV
75 #define PHOTOMETRIC_LOGLUV 32845
76 #endif
77 
78 #define MAXCOLORS 1024
79 #ifndef PHOTOMETRIC_DEPTH
80 #define PHOTOMETRIC_DEPTH 32768
81 #endif
82 
83 struct CmdlineInfo {
84     /* All the information the user supplied in the command line,
85        in a form easy for the program to use.
86     */
87     char * inputFilename;
88     unsigned int headerdump;
89     char * alphaFilename;
90     bool alphaStdout;
91     unsigned int respectfillorder;   /* -respectfillorder option */
92     unsigned int byrow;
93     unsigned int orientraw;
94     unsigned int verbose;
95 };
96 
97 
98 
99 static void
parseCommandLine(int argc,const char ** const argv,struct CmdlineInfo * const cmdlineP)100 parseCommandLine(int argc, const char ** const argv,
101                  struct CmdlineInfo * const cmdlineP) {
102 /*----------------------------------------------------------------------------
103    Note that many of the strings that this function returns in the
104    *cmdlineP structure are actually in the supplied argv array.  And
105    sometimes, one of these strings is actually just a suffix of an entry
106    in argv!
107 -----------------------------------------------------------------------------*/
108     optStruct3 opt;
109     optEntry *option_def;
110     unsigned int option_def_index;
111     unsigned int alphaSpec;
112 
113     MALLOCARRAY_NOFAIL(option_def, 100);
114 
115     opt.opt_table = option_def;
116     opt.short_allowed = FALSE;
117     opt.allowNegNum = FALSE;
118 
119     option_def_index = 0;   /* incremented by OPTENT3 */
120     OPTENT3(0, "verbose",
121             OPT_FLAG,   NULL, &cmdlineP->verbose,              0);
122     OPTENT3(0, "respectfillorder",
123             OPT_FLAG,   NULL, &cmdlineP->respectfillorder,     0);
124     OPTENT3(0,   "byrow",
125             OPT_FLAG,   NULL, &cmdlineP->byrow,                0);
126     OPTENT3(0,   "orientraw",
127             OPT_FLAG,   NULL, &cmdlineP->orientraw,            0);
128     OPTENT3('h', "headerdump",
129             OPT_FLAG,   NULL, &cmdlineP->headerdump,           0);
130     OPTENT3(0,   "alphaout",
131             OPT_STRING, &cmdlineP->alphaFilename, &alphaSpec,  0);
132 
133     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
134 
135     if (argc - 1 == 0)
136         cmdlineP->inputFilename = strdup("-");  /* he wants stdin */
137     else if (argc - 1 == 1)
138         cmdlineP->inputFilename = strdup(argv[1]);
139     else
140         pm_error("Too many arguments.  The only argument accepted "
141                  "is the input file name");
142 
143     if (alphaSpec) {
144         if (streq(cmdlineP->alphaFilename, "-"))
145             cmdlineP->alphaStdout = TRUE;
146         else
147             cmdlineP->alphaStdout = FALSE;
148     } else {
149         cmdlineP->alphaFilename = NULL;
150         cmdlineP->alphaStdout = FALSE;
151     }
152 }
153 
154 
155 
156 static TIFF *
newTiffImageObject(const char * const inputFileName)157 newTiffImageObject(const char * const inputFileName) {
158 /*----------------------------------------------------------------------------
159    Create a TIFF library object for accessing the TIFF input in file
160    named 'inputFileName'.  If 'inputFileName' is "-", that means
161    Standard Input.
162 -----------------------------------------------------------------------------*/
163     const char * const tiffSourceName =
164         streq(inputFileName, "-") ? "Standard Input" : inputFileName;
165 
166     TIFF * retval;
167 
168     retval = TIFFFdOpen(fileno(pm_openr_seekable(inputFileName)),
169                         tiffSourceName,
170                         "r");
171 
172     if (retval == NULL)
173         pm_error("Failed to access input file.  The OS opened the file fine, "
174                  "but the TIFF library's TIFFFdOpen rejected the open file.");
175 
176     return retval;
177 }
178 
179 
180 
181 static void
getBps(TIFF * const tif,unsigned short * const bpsP)182 getBps(TIFF *           const tif,
183        unsigned short * const bpsP) {
184 
185     unsigned short tiffBps;
186     unsigned short bps;
187     int fldPresent;
188 
189     fldPresent = TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &tiffBps);
190     bps = fldPresent ? tiffBps : 1;
191 
192     if (bps < 1 || (bps > 8 && bps != 16 && bps != 32))
193         pm_error("This program can process Tiff images with only "
194                  "1-8 or 16 or 32 bits per sample.  The input Tiff image "
195                  "has %hu bits per sample.", bps);
196     else
197         *bpsP = bps;
198 }
199 
200 
201 
202 struct tiffDirInfo {
203     /* 'width' and 'height' are the dimensions of the raster matrix in
204        the TIFF stream -- what the TIFF spec calls the image.  The
205        dimensions of the actual visual image represented may be the
206        reverse, because the raster can represent the visual image in
207        various orientations, as described by 'orientation'.
208     */
209     unsigned int   width;
210     unsigned int   height;
211     unsigned short bps;
212     unsigned short spp;
213     unsigned short photomet;
214     unsigned short planarconfig;
215     unsigned short fillorder;
216     unsigned short orientation;
217 };
218 
219 
220 
221 static void
tiffToImageDim(unsigned int const tiffWidth,unsigned int const tiffHeight,unsigned short const orientation,unsigned int * const imageColsP,unsigned int * const imageRowsP)222 tiffToImageDim(unsigned int   const tiffWidth,
223                unsigned int   const tiffHeight,
224                unsigned short const orientation,
225                unsigned int * const imageColsP,
226                unsigned int * const imageRowsP) {
227 /*----------------------------------------------------------------------------
228    Determine the image dimensions (as *imageColsP and *imageRowsP) from the
229    width, height, and orientation of the TIFF raster ('tiffWidth',
230    'tiffHeight', and 'orientation', respectively.
231 -----------------------------------------------------------------------------*/
232     switch (orientation) {
233     case ORIENTATION_TOPLEFT:
234     case ORIENTATION_TOPRIGHT:
235     case ORIENTATION_BOTRIGHT:
236     case ORIENTATION_BOTLEFT:
237         *imageColsP = tiffWidth;
238         *imageRowsP = tiffHeight;
239         break;
240     case ORIENTATION_LEFTTOP:
241     case ORIENTATION_RIGHTTOP:
242     case ORIENTATION_RIGHTBOT:
243     case ORIENTATION_LEFTBOT:
244         *imageColsP = tiffHeight;
245         *imageRowsP = tiffWidth;
246         break;
247     default:
248         pm_error("Invalid value for orientation tag in TIFF directory: %u",
249                  orientation);
250     }
251 }
252 
253 
254 
255 static void
getTiffDimensions(TIFF * const tiffP,unsigned int * const colsP,unsigned int * const rowsP)256 getTiffDimensions(TIFF *         const tiffP,
257                   unsigned int * const colsP,
258                   unsigned int * const rowsP) {
259 /*----------------------------------------------------------------------------
260    Return the dimensions of the image represented by *tiffP.  Not the
261    dimensions of the internal raster matrix -- the dimensions of the
262    actual visual image.
263 -----------------------------------------------------------------------------*/
264     int fldPresent;
265 
266     unsigned int width, length;
267     unsigned short tiffOrientation;
268     unsigned short orientation;
269 
270     fldPresent = TIFFGetField(tiffP, TIFFTAG_IMAGEWIDTH, &width);
271     if (!fldPresent)
272         pm_error("Input Tiff file is invalid.  It has no IMAGEWIDTH tag.");
273     fldPresent = TIFFGetField(tiffP, TIFFTAG_IMAGELENGTH, &length);
274     if (!fldPresent)
275         pm_error("Input Tiff file is invalid.  It has no IMAGELENGTH tag.");
276 
277     fldPresent = TIFFGetField(tiffP, TIFFTAG_ORIENTATION, &tiffOrientation);
278     orientation = fldPresent ? tiffOrientation : ORIENTATION_TOPLEFT;
279 
280     tiffToImageDim(width, length, orientation, colsP, rowsP);
281 }
282 
283 
284 
285 static unsigned short
planarConfigFmTiff(TIFF * const tiffP)286 planarConfigFmTiff(TIFF * const tiffP) {
287 
288     int fldPresent;
289     unsigned short retval;
290 
291     fldPresent = TIFFGetField(tiffP, TIFFTAG_PLANARCONFIG, &retval);
292 
293     if (!fldPresent)
294         pm_error("PLANARCONFIG tag is not in Tiff file, though it "
295                  "has more than one sample per pixel.  "
296                  "TIFFGetField() of it failed.  This means the input "
297                  "is not valid Tiff.");
298 
299     return retval;
300 }
301 
302 
303 
304 static void
validatePlanarConfig(unsigned short const planarconfig,unsigned short const photomet)305 validatePlanarConfig(unsigned short const planarconfig,
306                      unsigned short const photomet) {
307 
308     switch (planarconfig) {
309     case PLANARCONFIG_CONTIG:
310         break;
311     case PLANARCONFIG_SEPARATE:
312         if (photomet != PHOTOMETRIC_RGB &&
313             photomet != PHOTOMETRIC_SEPARATED)
314             pm_error("This program can handle separate planes only "
315                      "with RGB (PHOTOMETRIC tag = %u) or SEPARATED "
316                      "(PHOTOMETRIC tag = %u) data.  The input Tiff file "
317                      "has PHOTOMETRIC tag = %hu.",
318                      PHOTOMETRIC_RGB, PHOTOMETRIC_SEPARATED,
319                      photomet);
320         break;
321     default:
322         pm_error("Unrecognized PLANARCONFIG tag value in Tiff input: %u",
323                  planarconfig);
324     }
325 }
326 
327 
328 
329 static unsigned short
orientationFmTiff(TIFF * const tiffP)330 orientationFmTiff(TIFF * const tiffP) {
331 
332     unsigned short tiffOrientation;
333     int fldPresent;
334 
335     fldPresent = TIFFGetField(tiffP, TIFFTAG_ORIENTATION, &tiffOrientation);
336 
337     return fldPresent ? tiffOrientation : ORIENTATION_TOPLEFT;
338 }
339 
340 
341 
342 static void
dumpHeader(const struct tiffDirInfo * const headerP)343 dumpHeader(const struct tiffDirInfo * const headerP) {
344 
345     pm_message("%ux%ux%u raster matrix, oriented %u",
346                headerP->width, headerP->height,
347                headerP->bps * headerP->spp, headerP->orientation);
348     pm_message("%hu bits/sample, %hu samples/pixel",
349                headerP->bps, headerP->spp);
350 }
351 
352 
353 
354 static void
readDirectory(TIFF * const tiffP,bool const headerdump,struct tiffDirInfo * const headerP)355 readDirectory(TIFF *               const tiffP,
356               bool                 const headerdump,
357               struct tiffDirInfo * const headerP) {
358 /*----------------------------------------------------------------------------
359    Read various values of TIFF tags from the TIFF directory, and
360    default them if not in there and make guesses where values are
361    invalid.  Exit program with error message if required tags aren't
362    there or values are inconsistent or beyond our capabilities.  If
363    'headerdump' is true, issue informational messages about what we
364    find.
365 
366    The TIFF library is capable of returning invalid values (if the
367    input file contains invalid values).  We generally return those
368    invalid values to our caller.
369 -----------------------------------------------------------------------------*/
370     int fldPresent;
371     unsigned short tiffSpp;
372 
373     if (headerdump)
374         TIFFPrintDirectory(tiffP, stderr, TIFFPRINT_NONE);
375 
376     getBps(tiffP, &headerP->bps);
377 
378     fldPresent =
379         TIFFGetFieldDefaulted(tiffP, TIFFTAG_FILLORDER, &headerP->fillorder);
380     fldPresent = TIFFGetField(tiffP, TIFFTAG_SAMPLESPERPIXEL, &tiffSpp);
381     headerP->spp = fldPresent ? tiffSpp: 1;
382 
383     fldPresent = TIFFGetField(tiffP, TIFFTAG_PHOTOMETRIC, &headerP->photomet);
384     if (!fldPresent)
385         pm_error("PHOTOMETRIC tag is not in Tiff file.  "
386                  "TIFFGetField() of it failed.\n"
387                  "This means the input is not valid Tiff.");
388 
389     if (headerP->spp > 1)
390         headerP->planarconfig = planarConfigFmTiff(tiffP);
391     else
392         headerP->planarconfig = PLANARCONFIG_CONTIG;
393 
394     validatePlanarConfig(headerP->planarconfig, headerP->photomet);
395 
396     fldPresent = TIFFGetField(tiffP, TIFFTAG_IMAGEWIDTH, &headerP->width);
397     if (!fldPresent)
398         pm_error("Input Tiff file is invalid.  It has no IMAGEWIDTH tag.");
399     fldPresent = TIFFGetField(tiffP, TIFFTAG_IMAGELENGTH, &headerP->height);
400     if (!fldPresent)
401         pm_error("Input Tiff file is invalid.  It has no IMAGELENGTH tag.");
402 
403     headerP->orientation = orientationFmTiff(tiffP);
404 
405     if (headerdump)
406         dumpHeader(headerP);
407 }
408 
409 
410 
411 static void
readscanline(TIFF * const tif,unsigned char * const scanbuf,int const row,int const plane,unsigned int const cols,unsigned short const bps,unsigned short const spp,unsigned short const fillorder,unsigned int * const samplebuf)412 readscanline(TIFF *          const tif,
413              unsigned char * const scanbuf,
414              int             const row,
415              int             const plane,
416              unsigned int    const cols,
417              unsigned short  const bps,
418              unsigned short  const spp,
419              unsigned short  const fillorder,
420              unsigned int *  const samplebuf) {
421 /*----------------------------------------------------------------------------
422    Read one scanline out of the Tiff input and store it into samplebuf[].
423    Unlike the scanline returned by the Tiff library function, samplebuf[]
424    is composed of one sample per array element, which makes it easier for
425    our caller to process.
426 
427    scanbuf[] is a scratch array for our use, which is big enough to hold
428    a Tiff scanline.
429 -----------------------------------------------------------------------------*/
430     int rc;
431     unsigned int const bpsmask = (1 << bps) - 1;
432       /* A mask for taking the lowest 'bps' bits of a number */
433 
434     /* The TIFFReadScanline man page doesn't tell the format of its
435        'buf' return value, but it is exactly the same format as the 'buf'
436        input to TIFFWriteScanline.  The man page for that doesn't say
437        anything either, but the source code for Pamtotiff contains a
438        specification.
439     */
440 
441     rc = TIFFReadScanline(tif, scanbuf, row, plane);
442 
443     if (rc < 0)
444         pm_error( "Unable to read row %d, plane %d of input Tiff image.  "
445                   "TIFFReadScanline() failed.",
446                   row, plane);
447     else if (bps == 8) {
448         unsigned int sample;
449         for (sample = 0; sample < cols * spp; ++sample)
450             samplebuf[sample] = scanbuf[sample];
451     } else if (bps < 8) {
452         /* Note that in this format, samples do not span bytes.  Rather,
453            each byte may have don't-care bits in the right-end positions.
454            At least that's how I infer the format from reading pnmtotiff.c
455            -Bryan 00.11.18
456            */
457         unsigned int sample;
458         unsigned int bitsleft;
459         unsigned char * inP;
460 
461         for (sample = 0, bitsleft = 8, inP = scanbuf;
462              sample < cols * spp;
463              ++sample) {
464             if (bitsleft == 0) {
465                 ++inP;
466                 bitsleft = 8;
467             }
468             switch (fillorder) {
469             case FILLORDER_MSB2LSB:
470                 samplebuf[sample] = (*inP >> (bitsleft-bps)) & bpsmask;
471                 break;
472             case FILLORDER_LSB2MSB:
473                 samplebuf[sample] = (*inP >> (8-bitsleft)) & bpsmask;
474                 break;
475             default:
476                 pm_error("Internal error: invalid value for fillorder: %u",
477                          fillorder);
478             }
479             assert(bitsleft >= bps);
480             bitsleft -= bps;
481             if (bitsleft < bps)
482                 /* Don't count dregs at end of byte */
483                 bitsleft = 0;
484        }
485     } else if (bps == 16) {
486         /* Before Netpbm 9.17, this program assumed that scanbuf[]
487            contained an array of bytes as read from the Tiff file.  In
488            fact, in this bps == 16 case, it's an array of "shorts",
489            each stored in whatever format this platform uses (which is
490            none of our concern).  The pre-9.17 code also presumed that
491            the TIFF "FILLORDER" tag determined the order in which the
492            bytes of each sample appear in a TIFF file, which is
493            contrary to the TIFF spec.
494         */
495         const uint16 * const scanbuf16 = (const uint16 *) scanbuf;
496         unsigned int sample;
497 
498         for (sample = 0; sample < cols*spp; ++sample)
499             samplebuf[sample] = scanbuf16[sample];
500     } else if (bps == 32) {
501         const uint32 * const scanbuf32 = (const uint32 *) scanbuf;
502         unsigned int sample;
503 
504         for (sample = 0; sample < cols * spp; ++sample)
505             samplebuf[sample] = scanbuf32[sample];
506     } else
507         pm_error("Internal error: invalid bits per sample passed to "
508                  "readscanline()");
509 }
510 
511 
512 
513 static void
pick_cmyk_pixel(unsigned int const samplebuf[],int const sampleCursor,xelval * const redP,xelval * const bluP,xelval * const grnP)514 pick_cmyk_pixel(unsigned int const samplebuf[],
515                 int          const sampleCursor,
516                 xelval *     const redP,
517                 xelval *     const bluP,
518                 xelval *     const grnP) {
519 
520     /* Note that the TIFF spec does not say which of the 4 samples is
521        which, but common sense says they're in order C,M,Y,K.  Before
522        Netpbm 10.21 (March 2004), we assumed C,Y,M,K for some reason.
523        But combined with a compensating error in the CMYK->RGB
524        calculation, it had the same effect as C,M,Y,K.
525     */
526     unsigned int const c = samplebuf[sampleCursor + 0];
527     unsigned int const m = samplebuf[sampleCursor + 1];
528     unsigned int const y = samplebuf[sampleCursor + 2];
529     unsigned int const k = samplebuf[sampleCursor + 3];
530 
531     /* The CMYK->RGB formula used by TIFFRGBAImageGet() in the TIFF
532        library is the following, (with some apparent confusion with
533        the names of the yellow and magenta pigments being reversed).
534 
535        R = (1-K)*(1-C)     (with C,Y,M,K normalized to 0..1)
536        G = (1-K)*(1-M)
537        B = (1-K)*(1-Y)
538 
539        We used that too before Netpbm 10.21 (March 2004).
540 
541        Now we use the inverse of what Pnmtotiffcmyk has always used, which
542        makes sense as follows:  A microliter of black ink is simply a
543        substitute for a microliter each of cyan, magenta, and yellow ink.
544        Yellow ink removes blue light from what the white paper reflects.
545     */
546 
547     *redP = 255 - MIN(255, c + k);
548     *grnP = 255 - MIN(255, m + k);
549     *bluP = 255 - MIN(255, y + k);
550 }
551 
552 
553 
554 static void
computeFillorder(unsigned short const fillorderTag,unsigned short * const fillorderP,bool const respectfillorder)555 computeFillorder(unsigned short   const fillorderTag,
556                  unsigned short * const fillorderP,
557                  bool             const respectfillorder) {
558 
559     if (respectfillorder) {
560         if (fillorderTag != FILLORDER_MSB2LSB &&
561             fillorderTag != FILLORDER_LSB2MSB)
562             pm_error("Invalid value in Tiff input for the FILLORDER tag: %u.  "
563                      "Valid values are %u and %u.  Try omitting the "
564                      "-respectfillorder option.",
565                      fillorderTag, FILLORDER_MSB2LSB, FILLORDER_LSB2MSB);
566         else
567             *fillorderP = fillorderTag;
568     } else {
569         *fillorderP = FILLORDER_MSB2LSB;
570         if (fillorderTag != *fillorderP)
571             pm_message("Warning: overriding FILLORDER tag in the tiff input "
572                        "and assuming msb-to-lsb.  Consider the "
573                        "-respectfillorder option.");
574     }
575 }
576 
577 
578 
579 static void
analyzeImageType(TIFF * const tiffP,unsigned short const bps,unsigned short const spp,unsigned short const photomet,xelval * const maxvalP,int * const formatP,xel * const colormap,bool const headerdump,struct CmdlineInfo const cmdline)580 analyzeImageType(TIFF *             const tiffP,
581                  unsigned short     const bps,
582                  unsigned short     const spp,
583                  unsigned short     const photomet,
584                  xelval *           const maxvalP,
585                  int *              const formatP,
586                  xel *              const colormap,
587                  bool               const headerdump,
588                  struct CmdlineInfo const cmdline) {
589 /*----------------------------------------------------------------------------
590    Determine from the TIFF header in *tif certain properties of the image
591    as well as the proper format of PNM image for the conversion.
592 
593    *formatP and *maxvalP are the basic PNM parameters.
594 -----------------------------------------------------------------------------*/
595     switch (photomet) {
596     case PHOTOMETRIC_MINISBLACK:
597     case PHOTOMETRIC_MINISWHITE:
598         if (spp != 1)
599             pm_error("This grayscale image has %d samples per pixel.  "
600                      "We understand only 1.", spp);
601 
602         *formatP = bps == 1 ? PBM_TYPE : PGM_TYPE;
603 
604         *maxvalP = pm_bitstomaxval(MIN(bps, 16));
605 
606         if (headerdump)
607             pm_message("grayscale image, (min=%s) output maxval %u ",
608                        photomet == PHOTOMETRIC_MINISBLACK ?
609                        "black" : "white",
610                        *maxvalP
611                 );
612         break;
613 
614     case PHOTOMETRIC_PALETTE: {
615         int fldPresent;
616         int i;
617         int numcolors;
618         unsigned short* redcolormap;
619         unsigned short* greencolormap;
620         unsigned short* bluecolormap;
621 
622         if (headerdump)
623             pm_message("colormapped");
624 
625         if (spp != 1)
626             pm_error("This paletted image has %d samples per pixel.  "
627                      "We understand only 1.", spp);
628 
629         fldPresent = TIFFGetField(
630             tiffP, TIFFTAG_COLORMAP,
631             &redcolormap, &greencolormap, &bluecolormap);
632 
633         if (!fldPresent)
634             pm_error("error getting colormaps");
635 
636         numcolors = 1 << bps;
637         if (numcolors > MAXCOLORS)
638             pm_error("too many colors");
639 
640         *formatP = PPM_TYPE;
641 
642         *maxvalP = PNM_MAXMAXVAL;
643 
644         for (i = 0; i < numcolors; ++i) {
645             xelval r, g, b;
646             r = (long) redcolormap[i] * PNM_MAXMAXVAL / 65535L;
647             g = (long) greencolormap[i] * PNM_MAXMAXVAL / 65535L;
648             b = (long) bluecolormap[i] * PNM_MAXMAXVAL / 65535L;
649             PPM_ASSIGN(colormap[i], r, g, b);
650         }
651     }
652         break;
653 
654     case PHOTOMETRIC_SEPARATED: {
655         unsigned short inkset;
656         int fldPresent;
657 
658         if (headerdump)
659             pm_message("color separation");
660 
661         fldPresent = TIFFGetField(tiffP, TIFFTAG_INKNAMES, &inkset);
662         if (fldPresent && inkset != INKSET_CMYK)
663             pm_error("This color separation file uses an inkset (%d) "
664                      "we can't handle.  We handle only CMYK.", inkset);
665         if (spp != 4)
666             pm_error("This CMYK color separation file is %d samples per "
667                      "pixel.  "
668                      "We need 4 samples, though: C, M, Y, and K.  ",
669                      spp);
670 
671         *formatP = PPM_TYPE;
672 
673         *maxvalP = (1 << bps) - 1;
674     }
675         break;
676 
677     case PHOTOMETRIC_RGB:
678         if (headerdump)
679             pm_message("RGB truecolor");
680 
681         if (spp != 3 && spp != 4)
682             pm_error("This RGB image has %d samples per pixel.  "
683                      "We understand only 3 or 4.", spp);
684 
685         *formatP = PPM_TYPE;
686 
687         *maxvalP = (1 << bps) - 1;
688         break;
689 
690     case PHOTOMETRIC_MASK:
691         pm_error("don't know how to handle PHOTOMETRIC_MASK");
692 
693     case PHOTOMETRIC_DEPTH:
694         pm_error("don't know how to handle PHOTOMETRIC_DEPTH");
695 
696     case PHOTOMETRIC_YCBCR:
697         pm_error("don't know how to handle PHOTOMETRIC_YCBCR");
698 
699     case PHOTOMETRIC_CIELAB:
700         pm_error("don't know how to handle PHOTOMETRIC_CIELAB");
701 
702     case PHOTOMETRIC_LOGL:
703         pm_error("don't know how to handle PHOTOMETRIC_LOGL");
704 
705     case PHOTOMETRIC_LOGLUV:
706         pm_error("don't know how to handle PHOTOMETRIC_LOGLUV");
707 
708     default:
709         pm_error("unknown photometric: %d", photomet);
710     }
711     if (*maxvalP > PNM_OVERALLMAXVAL)
712         pm_error("bits/sample (%u) in the input image is too large.", bps);
713 }
714 
715 
716 
717 static void
reportOutputFormat(int const format)718 reportOutputFormat(int const format) {
719 
720     const char * formatDesc;
721 
722     switch (format) {
723     case PBM_TYPE: formatDesc = "PBM"; break;
724     case PGM_TYPE: formatDesc = "PGM"; break;
725     case PPM_TYPE: formatDesc = "PPM"; break;
726     default: assert(false);
727     }
728 
729     pm_message("writing %s file", formatDesc);
730 }
731 
732 
733 
734 typedef struct {
735     FILE *       imageoutFileP;
736         /* The stream to which we write the PNM image.  Null for none. */
737     FILE *       alphaFileP;
738         /* The stream to which we write the alpha channel.  Null for none. */
739     unsigned int inCols;
740         /* Width of each row that gets passed to this object */
741     unsigned int inRows;
742         /* Number of rows that get passed to this object */
743     unsigned int outCols;
744         /* Width of each row this object writes out to the file */
745     unsigned int outRows;
746         /* Number of rows this object writes out */
747     xelval       maxval;
748         /* Maxval of the output image */
749     int          format;
750         /* Format of the output image */
751     gray         alphaMaxval;
752         /* Maxval of the alpha channel */
753     bool         flipping;
754         /* We're passing rows through a Pamflip process, rather than writing
755            them directly to *imageoutFileP, *alphaFileP.
756         */
757     FILE *       imagePipeP;
758         /* Stream hooked up to pipe that goes to a Pamflip process.
759            Meaningful only when 'flipping' is true.
760         */
761     FILE *       alphaPipeP;
762         /* Stream hooked up to pipe that goes to a Pamflip process.
763            Meaningful only when 'flipping' is true.
764         */
765     pid_t        imageFlipPid;
766         /* Process ID of the Pamflip process.
767            Meaningful only when 'flipping' is true.
768         */
769     pid_t        alphaFlipPid;
770         /* Process ID of the Pamflip process.
771            Meaningful only when 'flipping' is true.
772         */
773 } pnmOut;
774 
775 
776 
777 static const char *
xformNeeded(unsigned short const tiffOrientation)778 xformNeeded(unsigned short const tiffOrientation) {
779 /*----------------------------------------------------------------------------
780    Return the value of the Pamflip -xform option that causes Pamflip
781    to change a raster from orientation 'tiffOrientation' to Row 0 top,
782    Column 0 left.
783 -----------------------------------------------------------------------------*/
784     switch (tiffOrientation) {
785     case ORIENTATION_TOPLEFT:  return "";
786     case ORIENTATION_TOPRIGHT: return "leftright";
787     case ORIENTATION_BOTRIGHT: return "topbottom,leftright";
788     case ORIENTATION_BOTLEFT:  return "topbottom";
789     case ORIENTATION_LEFTTOP:  return "transpose";
790     case ORIENTATION_RIGHTTOP: return "transpose,leftright";
791     case ORIENTATION_RIGHTBOT: return "transpose,topbottom,leftright";
792     case ORIENTATION_LEFTBOT:  return "transpose,topbottom";
793     default:
794         pm_error("Invalid value for orientation tag in TIFF directory: %u",
795                  tiffOrientation);
796         return "";
797     }
798 }
799 
800 
801 
802 /* File descriptors array indices for use with pipe() */
803 #define PIPE_READ 0
804 #define PIPE_WRITE 1
805 
806 static void
spawnWithInputPipe(const char * const shellCmd,FILE ** const pipePP,pid_t * const pidP,const char ** const errorP)807 spawnWithInputPipe(const char *  const shellCmd,
808                    FILE **       const pipePP,
809                    pid_t *       const pidP,
810                    const char ** const errorP) {
811 
812     int fd[2];
813     int rc;
814 
815     rc = pm_pipe(fd);
816 
817     if (rc != 0)
818         pm_asprintf(errorP, "Failed to create pipe for process input.  "
819                     "Errno=%d (%s)", errno, strerror(errno));
820     else {
821         int iAmParent;
822         pid_t childPid;
823 
824         pm_fork(&iAmParent, &childPid, errorP);
825 
826         if (!*errorP) {
827             if (iAmParent) {
828                 close(fd[PIPE_READ]);
829 
830                 *pidP   = childPid;
831                 *pipePP = fdopen(fd[PIPE_WRITE], "w");
832 
833                 if (*pipePP == NULL)
834                     pm_asprintf(errorP,"Unable to create stream from pipe.  "
835                                 "fdopen() fails with errno=%d (%s)",
836                                 errno, strerror(errno));
837                 else
838                     *errorP = NULL;
839             } else {
840                 int terminationStatus;
841                 close(fd[PIPE_WRITE]);
842                 close(STDIN_FILENO);
843                 dup2(fd[PIPE_READ], STDIN_FILENO);
844 
845                 terminationStatus = system(shellCmd);
846 
847                 if (WIFSIGNALED(terminationStatus))
848                     pm_error("Shell process was killed "
849                              "by a Class %u signal.",
850                              WTERMSIG(terminationStatus));
851                 else if (!WIFEXITED(terminationStatus))
852                     pm_error("Shell process died, but its termination status "
853                              "0x%x doesn't make sense", terminationStatus);
854                 else
855                     exit(WEXITSTATUS(terminationStatus));
856             }
857         }
858     }
859 }
860 
861 
862 
863 static void
createFlipProcess(FILE * const outFileP,unsigned short const orientation,bool const verbose,FILE ** const inPipePP,pid_t * const pidP)864 createFlipProcess(FILE *         const outFileP,
865                   unsigned short const orientation,
866                   bool           const verbose,
867                   FILE **        const inPipePP,
868                   pid_t *        const pidP) {
869 /*----------------------------------------------------------------------------
870    Create a process that runs the program Pamflip and writes its output
871    to *imageoutFileP.
872 
873    The process takes its input from a pipe that we create.  We return as
874    *inPipePP a file stream connected to the other end of that pipe.
875 
876    I.e. Caller will write a Netpbm file stream to **inPipePP and a flipped
877    version of it will go to *outFileP.
878 
879    The flipping it does turns the input from orientation 'orientation'
880    to Netpbm orientation, i.e. raster row 0 top, raster column 0 left,
881    where the raster stream is divided into rows, with row 0 being first
882    in the stream, and column 0 being first within each row.
883 
884    Caller must close *inPipePP when he is done.
885 -----------------------------------------------------------------------------*/
886     const char * pamflipCmd;
887     const char * error;
888 
889     /* Hooking up the process to the output stream is kind of tricky
890        because the stream (FILE *) is an entity local to this process.
891        We just assume that nothing in this process actually touches
892        the stream so that having the process write to the underlying
893        file descriptor is equivalent to writing to the stream.
894     */
895 
896     pm_asprintf(&pamflipCmd, "pamflip -xform=%s >&%u",
897                 xformNeeded(orientation), fileno(outFileP));
898 
899     if (verbose)
900         pm_message("Reorienting raster with shell command '%s'", pamflipCmd);
901 
902     spawnWithInputPipe(pamflipCmd, inPipePP, pidP, &error);
903 
904     if (error) {
905         pm_error("Shell command '%s', to reorient the TIFF "
906                  "raster, failed.  %s.  To work around this, you can use "
907                  "the -orientraw option.", pamflipCmd, error);
908 
909         pm_strfree(error);
910     }
911 }
912 
913 
914 
915 static void
setupFlipper(pnmOut * const pnmOutP,unsigned short const orientation,bool const flipIfNeeded,bool const orientraw,bool const verbose,bool * const flipOkP,bool * const noflipOkP)916 setupFlipper(pnmOut *       const pnmOutP,
917              unsigned short const orientation,
918              bool           const flipIfNeeded,
919              bool           const orientraw,
920              bool           const verbose,
921              bool *         const flipOkP,
922              bool *         const noflipOkP) {
923 /*----------------------------------------------------------------------------
924    Set up the Pamflip processes to flip the raster, where needed.
925 
926    Whether we need a Pamflip process is a complex decision.  For
927    reasons of efficiency and robustness, we don't want one unless
928    flipping is actually required.  Flipping is not required if the
929    TIFF image is already oriented like a PNM.  It also isn't required
930    if the user explicitly asks for raw orientation.  Finally, it's not
931    required when the user is using the TIFF library whole-image
932    conversion services, because they do the flipping and thus the
933    'pnmOut' object will see properly oriented pixels as input.
934 
935    'flipIfNeeded' says to set up the flipping pipe if 'orientation'
936    indicates something other than standard PNM orientation.
937 
938    'orientation' is the orientation of the TIFF raster.
939 
940    'orientraw' says the final output should be in the same
941    orientation, 'orientation', not the proper PNM orientation.
942 
943    *flipOkP means that as we set up the pnmOut object,
944    it is OK if Caller flips the raster on his own.  *noflipOk means
945    is is OK if Caller does not flip the raster on his own.  Note
946    that they are both true if the raster is already in the PNM
947    orientation, because then flipping is idempotent.
948 -----------------------------------------------------------------------------*/
949 
950     if (orientation == ORIENTATION_TOPLEFT) {
951         /* Ah, the easy case.  Flipping and not flipping are identical,
952            so none of the other parameters matter.  Just write directly
953            to the output file and let Caller flip or not flip as he
954            wishes.
955         */
956         pnmOutP->flipping = FALSE;
957         *flipOkP   = TRUE;
958         *noflipOkP = TRUE;
959     } else {
960         if (orientraw) {
961             /* Raster is not to be flipped, so go directly to file,
962                and tell Caller not to flip it either.
963             */
964             pnmOutP->flipping = FALSE;
965             *flipOkP   = FALSE;
966             *noflipOkP = TRUE;
967         } else {
968             if (flipIfNeeded) {
969                 if (verbose)
970                     pm_message("Transforming raster with Pamflip");
971 
972                 if (pnmOutP->alphaFileP)
973                     createFlipProcess(pnmOutP->alphaFileP, orientation,
974                                       verbose,
975                                       &pnmOutP->alphaPipeP,
976                                       &pnmOutP->alphaFlipPid);
977                 if (pnmOutP->imageoutFileP)
978                     createFlipProcess(pnmOutP->imageoutFileP, orientation,
979                                       verbose,
980                                       &pnmOutP->imagePipeP,
981                                       &pnmOutP->imageFlipPid);
982 
983                 /* The stream will flip it, so Caller must not: */
984                 pnmOutP->flipping = TRUE;
985                 *flipOkP   = FALSE;
986                 *noflipOkP = TRUE;
987             } else {
988                 /* It needs flipping, but Caller doesn't want us to do it.
989                    So Caller must do it:
990                 */
991                 pnmOutP->flipping = FALSE;
992                 *flipOkP   = TRUE;
993                 *noflipOkP = FALSE;
994             }
995         }
996     }
997 }
998 
999 
1000 
1001 static void
computeOutputDimensions(unsigned int const tiffCols,unsigned int const tiffRows,unsigned short const orientation,bool const orientraw,unsigned int * const colsP,unsigned int * const rowsP,bool const verbose)1002 computeOutputDimensions(unsigned int       const tiffCols,
1003                         unsigned int       const tiffRows,
1004                         unsigned short     const orientation,
1005                         bool               const orientraw,
1006                         unsigned int *     const colsP,
1007                         unsigned int *     const rowsP,
1008                         bool               const verbose) {
1009 /*----------------------------------------------------------------------------
1010    Compute the dimensions of the image.  We're talking about the
1011    actual image, not what the TIFF spec calls the image.  What the
1012    TIFF spec calls the image is matrix of pixels within the TIFF file.
1013    That matrix can represent the actual image in various ways.
1014    E.g. the first row of the matrix might be the right edge of the
1015    image.
1016 
1017    'tiffCols' and 'tiffRows' are the images of the TIFF matrix and
1018    'orientation' is the orientation of that matrix.
1019 
1020    'orientraw' says we want to output the matrix in its natural
1021    orientation, not the true image.
1022 -----------------------------------------------------------------------------*/
1023     if (orientraw) {
1024         *colsP = tiffCols;
1025         *rowsP = tiffRows;
1026     } else
1027         tiffToImageDim(tiffCols, tiffRows, orientation, colsP, rowsP);
1028 
1029     if (verbose)
1030         pm_message("Generating %uw x %uh PNM image", *colsP, *rowsP);
1031 }
1032 
1033 
1034 
1035 static void
pnmOut_init(FILE * const imageoutFileP,FILE * const alphaFileP,unsigned int const cols,unsigned int const rows,unsigned short const orientation,xelval const maxval,int const format,gray const alphaMaxval,bool const flipIfNeeded,bool const orientraw,bool const verbose,bool * const flipOkP,bool * const noflipOkP,pnmOut * const pnmOutP)1036 pnmOut_init(FILE *         const imageoutFileP,
1037             FILE *         const alphaFileP,
1038             unsigned int   const cols,
1039             unsigned int   const rows,
1040             unsigned short const orientation,
1041             xelval         const maxval,
1042             int            const format,
1043             gray           const alphaMaxval,
1044             bool           const flipIfNeeded,
1045             bool           const orientraw,
1046             bool           const verbose,
1047             bool *         const flipOkP,
1048             bool *         const noflipOkP,
1049             pnmOut *       const pnmOutP) {
1050 /*----------------------------------------------------------------------------
1051    'cols' and 'rows' are the dimensions of the raster matrix which is
1052    oriented according to 'orientation' with respect to the image.
1053 
1054    If the user flips the data before giving it to the pnmOut object,
1055    pnmOut may see the inverse dimensions; if the pnmOut object flips the
1056    data, pnmOut get 'cols' x 'rows' data, but its output file may be
1057    'rows x cols'.
1058 
1059    Because we must set up the pnmOut object either to receive flipped or not
1060    flipped input, we have *flipOkP and *noflipOkP outputs that tell Caller
1061    whether he has to flip or not.  Note that Caller also influences which way
1062    we set up pnmOut, with his 'flipIfNeeded' argument.  In the unique case
1063    that the TIFF matrix is already oriented the way the output PNM file needs
1064    to be, flipping is idempotent, so both *flipOkP and *noflipOkP are true.
1065 -----------------------------------------------------------------------------*/
1066     pnmOutP->imageoutFileP = imageoutFileP;
1067     pnmOutP->alphaFileP    = alphaFileP;
1068     pnmOutP->maxval        = maxval;
1069     pnmOutP->format        = format;
1070     pnmOutP->alphaMaxval   = alphaMaxval;
1071 
1072     setupFlipper(pnmOutP, orientation, flipIfNeeded, orientraw, verbose,
1073                  flipOkP, noflipOkP);
1074 
1075     computeOutputDimensions(cols, rows, orientation, orientraw,
1076                             &pnmOutP->outCols, &pnmOutP->outRows, verbose);
1077 
1078     if (pnmOutP->flipping) {
1079         pnmOutP->inCols = cols;         /* Caller won't flip */
1080         pnmOutP->inRows = rows;
1081     } else {
1082         pnmOutP->inCols = pnmOutP->outCols;  /* Caller will flip */
1083         pnmOutP->inRows = pnmOutP->outRows;
1084     }
1085     if (pnmOutP->flipping) {
1086         if (pnmOutP->imagePipeP != NULL)
1087             pnm_writepnminit(pnmOutP->imagePipeP,
1088                              pnmOutP->inCols, pnmOutP->inRows,
1089                              pnmOutP->maxval, pnmOutP->format, 0);
1090         if (pnmOutP->alphaPipeP != NULL)
1091             pgm_writepgminit(pnmOutP->alphaPipeP,
1092                              pnmOutP->inCols, pnmOutP->inRows,
1093                              pnmOutP->alphaMaxval, 0);
1094     } else {
1095         if (imageoutFileP != NULL)
1096             pnm_writepnminit(pnmOutP->imageoutFileP,
1097                              pnmOutP->outCols, pnmOutP->outRows,
1098                              pnmOutP->maxval, pnmOutP->format, 0);
1099         if (alphaFileP != NULL)
1100             pgm_writepgminit(pnmOutP->alphaFileP,
1101                              pnmOutP->outCols, pnmOutP->outRows,
1102                              pnmOutP->alphaMaxval, 0);
1103     }
1104 }
1105 
1106 
1107 
1108 static void
pnmOut_term(pnmOut * const pnmOutP,bool const verbose)1109 pnmOut_term(pnmOut * const pnmOutP,
1110             bool     const verbose) {
1111 
1112     if (pnmOutP->flipping) {
1113         /* Closing the pipes also causes the Pamflip processes to terminate
1114            and they consequently flush their output to pnmOutP->imageoutFileP
1115            and pnmOutP->alphaFileP and close those file descriptors.
1116 
1117            We wait for the processes to exit before returning so that we
1118            know everything is flushed, so the invoker of Tifftopnm is free
1119            to use its output.
1120         */
1121         if (verbose)
1122             pm_message("Flushing data through Pamflip process, "
1123                        "waiting for Pamflip to terminate");
1124 
1125         if (pnmOutP->imagePipeP) {
1126             fclose(pnmOutP->imagePipeP);
1127             pm_waitpidSimple(pnmOutP->imageFlipPid);
1128         }
1129         if (pnmOutP->alphaPipeP) {
1130             fclose(pnmOutP->alphaPipeP);
1131             pm_waitpidSimple(pnmOutP->alphaFlipPid);
1132         }
1133     } else {
1134         if (pnmOutP->imageoutFileP)
1135             fflush(pnmOutP->imageoutFileP);
1136         if (pnmOutP->alphaFileP)
1137             fflush(pnmOutP->alphaFileP);
1138     }
1139 }
1140 
1141 
1142 
1143 static void
pnmOut_writeRow(pnmOut * const pnmOutP,unsigned int const cols,const xel * const imageRow,const gray * const alphaRow)1144 pnmOut_writeRow(pnmOut *     const pnmOutP,
1145                 unsigned int const cols,
1146                 const xel *  const imageRow,
1147                 const gray * const alphaRow) {
1148 
1149     assert(cols == pnmOutP->inCols);
1150 
1151     if (pnmOutP->flipping) {
1152         if (pnmOutP->imagePipeP != NULL)
1153             pnm_writepnmrow(pnmOutP->imagePipeP, (xel *)imageRow,
1154                             pnmOutP->inCols, pnmOutP->maxval,
1155                             pnmOutP->format, 0);
1156         if (pnmOutP->alphaPipeP != NULL)
1157             pgm_writepgmrow(pnmOutP->alphaPipeP, alphaRow,
1158                             pnmOutP->inCols, pnmOutP->alphaMaxval, 0);
1159     } else {
1160         if (pnmOutP->imageoutFileP != NULL)
1161             pnm_writepnmrow(pnmOutP->imageoutFileP, (xel *)imageRow,
1162                             pnmOutP->outCols, pnmOutP->maxval,
1163                             pnmOutP->format, 0);
1164         if (pnmOutP->alphaFileP != NULL)
1165             pgm_writepgmrow(pnmOutP->alphaFileP, alphaRow,
1166                             pnmOutP->outCols, pnmOutP->alphaMaxval, 0);
1167     }
1168 }
1169 
1170 
1171 
1172 static void
convertRow(unsigned int const samplebuf[],xel * const xelrow,gray * const alpharow,int const cols,xelval const maxval,unsigned short const photomet,unsigned short const spp,xel const colormap[])1173 convertRow(unsigned int   const samplebuf[],
1174            xel *          const xelrow,
1175            gray *         const alpharow,
1176            int            const cols,
1177            xelval         const maxval,
1178            unsigned short const photomet,
1179            unsigned short const spp,
1180            xel            const colormap[]) {
1181 /*----------------------------------------------------------------------------
1182    Assuming samplebuf[] is an array of raster values as returned by the Tiff
1183    library, convert it to a libnetpbm row in xelrow[] and alpharow[].
1184 -----------------------------------------------------------------------------*/
1185     switch (photomet) {
1186     case PHOTOMETRIC_MINISBLACK: {
1187         int col;
1188         for (col = 0; col < cols; ++col) {
1189             PNM_ASSIGN1(xelrow[col], samplebuf[col]);
1190             alpharow[col] = 0;
1191         }
1192     }
1193     break;
1194 
1195     case PHOTOMETRIC_MINISWHITE: {
1196         int col;
1197         for (col = 0; col < cols; ++col) {
1198             PNM_ASSIGN1(xelrow[col], maxval - samplebuf[col]);
1199             alpharow[col] = 0;
1200         }
1201     }
1202     break;
1203 
1204     case PHOTOMETRIC_PALETTE: {
1205         int col;
1206         for ( col = 0; col < cols; ++col ) {
1207             /* We know the following array index is in bounds because
1208                we filled samplebuf with samples of 'bps' bits each and
1209                we verified that the largest number that fits in 'bps'
1210                bits is less than MAXCOLORS, the dimension of the array.
1211             */
1212             xelrow[col] = colormap[samplebuf[col]];
1213             alpharow[col] = 0;
1214         }
1215     }
1216     break;
1217 
1218     case PHOTOMETRIC_SEPARATED: {
1219         int col, sample;
1220         for (col = 0, sample = 0; col < cols; ++col, sample+=spp) {
1221             xelval r, g, b;
1222             pick_cmyk_pixel(samplebuf, sample, &r, &b, &g);
1223 
1224             PPM_ASSIGN(xelrow[col], r, g, b);
1225             alpharow[col] = 0;
1226         }
1227     }
1228     break;
1229 
1230     case PHOTOMETRIC_RGB: {
1231         int col, sample;
1232         for (col = 0, sample = 0; col < cols; ++col, sample+=spp) {
1233             PPM_ASSIGN(xelrow[col], samplebuf[sample+0],
1234                        samplebuf[sample+1], samplebuf[sample+2]);
1235             if (spp >= 4)
1236                 alpharow[col] = samplebuf[sample+3];
1237             else
1238                 alpharow[col] = 0;
1239         }
1240         break;
1241     }
1242     default:
1243         pm_error("internal error:  unknown photometric in the picking "
1244                  "routine: %d", photomet);
1245     }
1246 }
1247 
1248 
1249 
1250 static void
scale32to16(unsigned int * const samplebuf,unsigned int const cols,unsigned int const spp)1251 scale32to16(unsigned int * const samplebuf,
1252             unsigned int   const cols,
1253             unsigned int   const spp) {
1254 /*----------------------------------------------------------------------------
1255   Convert every sample in samplebuf[] to something that can be expressed
1256   in 16 bits, assuming it takes 32 bits now.
1257 -----------------------------------------------------------------------------*/
1258     unsigned int i;
1259     for (i = 0; i < cols * spp; ++i)
1260         samplebuf[i] >>= 16;
1261 }
1262 
1263 
1264 
1265 static void
convertMultiPlaneRow(TIFF * const tif,xel * const xelrow,gray * const alpharow,int const cols,xelval const maxval,int const row,unsigned short const photomet,unsigned short const bps,unsigned short const spp,unsigned short const fillorder,unsigned char * const scanbuf,unsigned int * const samplebuf)1266 convertMultiPlaneRow(TIFF *          const tif,
1267                      xel *           const xelrow,
1268                      gray *          const alpharow,
1269                      int             const cols,
1270                      xelval          const maxval,
1271                      int             const row,
1272                      unsigned short  const photomet,
1273                      unsigned short  const bps,
1274                      unsigned short  const spp,
1275                      unsigned short  const fillorder,
1276                      unsigned char * const scanbuf,
1277                      unsigned int *  const samplebuf) {
1278 
1279     /* The input is in separate planes, so we need to read one
1280        scanline for the reds, another for the greens, then another
1281        for the blues.
1282     */
1283 
1284     if (photomet != PHOTOMETRIC_RGB)
1285         pm_error("This is a multiple-plane file, but is not an RGB "
1286                  "file.  This program does not know how to handle that.");
1287     else {
1288         unsigned int col;
1289 
1290         /* First, clear the buffer so we can add red, green,
1291            and blue one at a time.
1292         */
1293         for (col = 0; col < cols; ++col)
1294             PPM_ASSIGN(xelrow[col], 0, 0, 0);
1295 
1296         /* Read the reds */
1297         readscanline(tif, scanbuf, row, 0, cols, bps, spp, fillorder,
1298                      samplebuf);
1299         if (bps == 32)
1300             scale32to16(samplebuf, cols, spp);
1301         for (col = 0; col < cols; ++col)
1302             PPM_PUTR(xelrow[col], samplebuf[col]);
1303 
1304         /* Next the greens */
1305         readscanline(tif, scanbuf, row, 1, cols, bps, spp, fillorder,
1306                      samplebuf);
1307         if (bps == 32)
1308             scale32to16(samplebuf, cols, spp);
1309         for (col = 0; col < cols; ++col)
1310             PPM_PUTG( xelrow[col], samplebuf[col] );
1311 
1312         /* And finally the blues */
1313         readscanline(tif, scanbuf, row, 2, cols, bps, spp, fillorder,
1314                      samplebuf);
1315         if (bps == 32)
1316             scale32to16(samplebuf, cols, spp);
1317         for (col = 0; col < cols; ++col)
1318             PPM_PUTB(xelrow[col], samplebuf[col]);
1319 
1320         /* Could there be an alpha plane?  (We assume no.  But if so,
1321            here is where to read it)
1322         */
1323         for (col = 0; col < cols; ++col)
1324             alpharow[col] = 0;
1325     }
1326 }
1327 
1328 
1329 
1330 static void
convertRasterByRows(pnmOut * const pnmOutP,unsigned int const cols,unsigned int const rows,xelval const maxval,TIFF * const tif,unsigned short const photomet,unsigned short const planarconfig,unsigned short const bps,unsigned short const spp,unsigned short const fillorder,xel const colormap[],bool const verbose)1331 convertRasterByRows(pnmOut *       const pnmOutP,
1332                     unsigned int   const cols,
1333                     unsigned int   const rows,
1334                     xelval         const maxval,
1335                     TIFF *         const tif,
1336                     unsigned short const photomet,
1337                     unsigned short const planarconfig,
1338                     unsigned short const bps,
1339                     unsigned short const spp,
1340                     unsigned short const fillorder,
1341                     xel            const colormap[],
1342                     bool           const verbose) {
1343 /*----------------------------------------------------------------------------
1344    With the TIFF header all processed (and relevant information from it in
1345    our arguments), write out the TIFF raster to the Netpbm output files
1346    as described by *pnmOutP.
1347 
1348    Do this one row at a time, employing the TIFF library's
1349    TIFFReadScanline.
1350 -----------------------------------------------------------------------------*/
1351     unsigned char * scanbuf;
1352         /* Buffer for a raster line in the format returned by TIFF library's
1353            TIFFReadScanline
1354         */
1355     unsigned int * samplebuf;
1356         /* Same info as 'scanbuf' above, but with each raster column (sample)
1357            represented as single array element, so it's easy to work with.
1358         */
1359     xel * xelrow;
1360         /* The ppm-format row of the image row we are currently converting */
1361     gray * alpharow;
1362         /* The pgm-format row representing the alpha values for the image
1363            row we are currently converting.
1364         */
1365 
1366     unsigned int row;
1367 
1368     if (verbose)
1369         pm_message("Converting row by row ...");
1370 
1371     MALLOCARRAY(scanbuf, TIFFScanlineSize(tif));
1372     if (scanbuf == NULL)
1373         pm_error("can't allocate memory for scanline buffer");
1374 
1375     MALLOCARRAY(samplebuf, cols * spp);
1376     if (samplebuf == NULL)
1377         pm_error("can't allocate memory for row buffer");
1378 
1379     xelrow = pnm_allocrow(cols);
1380     alpharow = pgm_allocrow(cols);
1381 
1382     for (row = 0; row < rows; ++row) {
1383         /* Read one row of samples into samplebuf[] */
1384 
1385         if (planarconfig == PLANARCONFIG_CONTIG) {
1386             readscanline(tif, scanbuf, row, 0, cols, bps, spp, fillorder,
1387                          samplebuf);
1388             if (bps == 32)
1389                 scale32to16(samplebuf, cols, spp);
1390             convertRow(samplebuf, xelrow, alpharow, cols, maxval,
1391                        photomet, spp, colormap);
1392         } else
1393             convertMultiPlaneRow(tif, xelrow, alpharow, cols, maxval, row,
1394                                  photomet, bps, spp, fillorder,
1395                                  scanbuf, samplebuf);
1396 
1397         pnmOut_writeRow(pnmOutP, cols, xelrow, alpharow);
1398     }
1399     pgm_freerow(alpharow);
1400     pnm_freerow(xelrow);
1401 
1402     free(samplebuf);
1403     free(scanbuf);
1404 }
1405 
1406 
1407 
1408 static void
warnBrokenTiffLibrary(TIFF * const tiffP)1409 warnBrokenTiffLibrary(TIFF * const tiffP) {
1410 
1411 /* TIFF library bug:
1412 
1413    In every version of the TIFF library we've seen, TIFFRGBAImageGet()
1414    fails when the raster orientation (per the TIFF_ORIENTATION tag)
1415    requires a transposition, e.g. ORIENTATION_LEFTBOT.  It simply omits
1416    the transposition part, so e.g. it treats ORIENTATION_LEFTBOT as
1417    ORIENTATION_BOTLEFT.  And because we provide a raster buffer dimensioned
1418    for the properly transposed image, the result is somewhat of a mess.
1419 
1420    We have found no documentation of the TIFF library that suggests
1421    this behavior is as designed, so it's probably not a good idea to
1422    work around it; it might be fixed somewhere.
1423 
1424    The user can of course work around just by using -byrow and therefore
1425    not using TIFFRGBAImageGet().
1426 
1427    There is some evidence of an interface in the TIFF library that
1428    lets you request that TIFFRGBAImageGet() produce a raster in the
1429    same orientation as the one in the TIFF image.
1430    (tiff.req_orientation).  We could conceivably use that and then do
1431    a Pamflip to get the proper orientation, but that somewhat defeats
1432    the philosophy of using TIFFRGBAImageGet(), so I would like to wait
1433    until there's a good practical reason to do it.
1434 */
1435 
1436     unsigned short tiffOrientation;
1437     int fldPresent;
1438     fldPresent = TIFFGetField(tiffP, TIFFTAG_ORIENTATION, &tiffOrientation);
1439     if (fldPresent) {
1440         switch (tiffOrientation) {
1441         case ORIENTATION_LEFTTOP:
1442         case ORIENTATION_RIGHTTOP:
1443         case ORIENTATION_RIGHTBOT:
1444         case ORIENTATION_LEFTBOT:
1445             pm_message("WARNING: This TIFF image has an orientation that "
1446                        "most TIFF libraries convert incorrectly.  "
1447                        "Use -byrow to circumvent.");
1448             break;
1449         }
1450     }
1451 }
1452 
1453 
1454 
1455 static void
convertTiffRaster(uint32 * const raster,unsigned int const cols,unsigned int const rows,xelval const maxval,pnmOut * const pnmOutP)1456 convertTiffRaster(uint32 *        const raster,
1457                   unsigned int    const cols,
1458                   unsigned int    const rows,
1459                   xelval          const maxval,
1460                   pnmOut *        const pnmOutP) {
1461 /*----------------------------------------------------------------------------
1462    Convert the raster 'raster' from the format generated by the TIFF library
1463    to PPM (plus PGM alpha mask where applicable) and output it to
1464    the object *pnmOutP.  The raster is 'cols' wide by 'rows' high.
1465 -----------------------------------------------------------------------------*/
1466     xel * xelrow;
1467         /* The ppm-format row of the image row we are
1468            currently converting
1469         */
1470     gray * alpharow;
1471         /* The pgm-format row representing the alpha values
1472            for the image row we are currently converting.
1473         */
1474     unsigned int row;
1475 
1476     xelrow = pnm_allocrow(cols);
1477     alpharow = pgm_allocrow(cols);
1478 
1479     for (row = 0; row < rows; ++row) {
1480         uint32 * rp;
1481             /* Address of pixel in 'raster' we are currently converting */
1482         unsigned int col;
1483 
1484         /* Start at beginning of row: */
1485         rp = raster + (rows - row - 1) * cols;
1486 
1487         for (col = 0; col < cols; ++col) {
1488             uint32 const tiffPixel = *rp++;
1489 
1490             PPM_ASSIGN(xelrow[col],
1491                        TIFFGetR(tiffPixel) * maxval / 255,
1492                        TIFFGetG(tiffPixel) * maxval / 255,
1493                        TIFFGetB(tiffPixel) * maxval / 255);
1494             alpharow[col] = TIFFGetA(tiffPixel) * maxval / 255 ;
1495         }
1496         pnmOut_writeRow(pnmOutP, cols, xelrow, alpharow);
1497     }
1498 
1499     pgm_freerow(alpharow);
1500     pnm_freerow(xelrow);
1501 }
1502 
1503 
1504 
1505 enum convertDisp {CONV_DONE,
1506                   CONV_OOM,
1507                   CONV_UNABLE,
1508                   CONV_FAILED,
1509                   CONV_NOTATTEMPTED};
1510 
1511 
1512 static void
convertRasterIntoProvidedMemory(pnmOut * const pnmOutP,unsigned int const cols,unsigned int const rows,xelval const maxval,TIFF * const tif,bool const verbose,uint32 * const raster,enum convertDisp * const statusP)1513 convertRasterIntoProvidedMemory(pnmOut *           const pnmOutP,
1514                                 unsigned int       const cols,
1515                                 unsigned int       const rows,
1516                                 xelval             const maxval,
1517                                 TIFF *             const tif,
1518                                 bool               const verbose,
1519                                 uint32 *           const raster,
1520                                 enum convertDisp * const statusP) {
1521 
1522     int const stopOnErrorFalse = false;
1523 
1524     TIFFRGBAImage img;
1525     char emsg[1024];
1526     int ok;
1527 
1528     ok = TIFFRGBAImageBegin(&img, tif, stopOnErrorFalse, emsg);
1529     if (!ok) {
1530         pm_message("%s", emsg);
1531         *statusP = CONV_FAILED;
1532     } else {
1533         int ok;
1534         ok = TIFFRGBAImageGet(&img, raster, cols, rows);
1535         TIFFRGBAImageEnd(&img) ;
1536         if (!ok) {
1537             pm_message("%s", emsg);
1538             *statusP = CONV_FAILED;
1539         } else {
1540             *statusP = CONV_DONE;
1541             convertTiffRaster(raster, cols, rows, maxval, pnmOutP);
1542         }
1543     }
1544 }
1545 
1546 
1547 
1548 static void
convertRasterInMemory(pnmOut * const pnmOutP,xelval const maxval,TIFF * const tif,bool const verbose,enum convertDisp * const statusP)1549 convertRasterInMemory(pnmOut *           const pnmOutP,
1550                       xelval             const maxval,
1551                       TIFF *             const tif,
1552                       bool               const verbose,
1553                       enum convertDisp * const statusP) {
1554 /*----------------------------------------------------------------------------
1555    With the TIFF header all processed (and relevant information from
1556    it in our arguments), write out the TIFF raster to the file images
1557    *imageoutFileP and *alphaFileP.
1558 
1559    Do this by reading the entire TIFF image into memory at once and formatting
1560    it with the TIFF library's TIFFRGBAImageGet().
1561 
1562    'cols' and 'rows' are the dimensions of the actual image, not of the
1563    TIFF raster matrix; ('tif' knows the TIFF raster matrix dimensions).
1564 
1565    Return *statusP == CONV_OOM iff we are unable to proceed because we cannot
1566    get memory to store the entire raster.  This means Caller may still be able
1567    to do the conversion using a row-by-row strategy.  Like typical Netpbm
1568    programs, we simply abort the program if we are unable to allocate
1569    memory for other things.
1570 -----------------------------------------------------------------------------*/
1571     char emsg[1024];
1572     int ok;
1573 
1574     if (verbose)
1575         pm_message("Converting in memory ...");
1576 
1577     warnBrokenTiffLibrary(tif);
1578 
1579     ok = TIFFRGBAImageOK(tif, emsg);
1580     if (!ok) {
1581         pm_message("%s", emsg);
1582         *statusP = CONV_UNABLE;
1583     } else {
1584         unsigned int cols, rows;  /* Dimensions of output image */
1585         getTiffDimensions(tif, &cols, &rows);
1586 
1587         if (rows == 0 || cols == 0)
1588             *statusP = CONV_DONE;
1589         else {
1590             if (cols > UINT_MAX/rows) {
1591                 pm_message("%u rows of %u columns is too large to compute",
1592                            rows, cols);
1593                 *statusP = CONV_OOM;
1594             } else {
1595                 unsigned int const pixelCt = rows * cols;
1596 
1597                 uint32 * raster;
1598 
1599                 /* Note that TIFFRGBAImageGet() converts any bits per sample
1600                    to 8.  Maxval of the raster it returns is always 255.
1601                 */
1602                 MALLOCARRAY(raster, pixelCt);
1603                 if (raster == NULL) {
1604                     pm_message("Unable to allocate space for a raster of %u "
1605                                "pixels.", pixelCt);
1606                     *statusP = CONV_OOM;
1607                 } else {
1608                     convertRasterIntoProvidedMemory(
1609                         pnmOutP, cols, rows, maxval, tif, verbose,
1610                         raster, statusP);
1611 
1612                     free(raster);
1613                 }
1614             }
1615         }
1616     }
1617 }
1618 
1619 
1620 
1621 static void
convertRaster(pnmOut * const pnmOutP,TIFF * const tifP,struct tiffDirInfo const tiffDir,xelval const maxval,unsigned short const fillorder,const xel * const colormap,bool const byrow,bool const flipOk,bool const noflipOk,bool const verbose)1622 convertRaster(pnmOut *           const pnmOutP,
1623               TIFF *             const tifP,
1624               struct tiffDirInfo const tiffDir,
1625               xelval             const maxval,
1626               unsigned short     const fillorder,
1627               const xel *        const colormap,
1628               bool               const byrow,
1629               bool               const flipOk,
1630               bool               const noflipOk,
1631               bool               const verbose) {
1632 
1633     enum convertDisp status;
1634 
1635     if (byrow || !flipOk)
1636         status = CONV_NOTATTEMPTED;
1637     else {
1638         convertRasterInMemory(pnmOutP, maxval, tifP, verbose, &status);
1639     }
1640     if (status == CONV_DONE) {
1641         if (tiffDir.bps > 8)
1642             pm_message("actual resolution has been reduced to 24 bits "
1643                        "per pixel in the conversion.  You can get the "
1644                        "full %u bits that are in the TIFF with the "
1645                        "-byrow option.", tiffDir.bps);
1646     } else {
1647         if (status != CONV_NOTATTEMPTED) {
1648             pm_message("In-memory conversion failed; "
1649                        "using more primitive row-by-row conversion.");
1650 
1651             if (!noflipOk)
1652                 pm_error("TIFF raster is in nonstandard orientation, "
1653                          "and we already committed to in-memory "
1654                          "conversion.  To avoid this failure, "
1655                          "use -byrow .");
1656         }
1657         convertRasterByRows(
1658             pnmOutP, tiffDir.width, tiffDir.height, maxval,
1659             tifP, tiffDir.photomet, tiffDir.planarconfig,
1660             tiffDir.bps, tiffDir.spp, fillorder, colormap, verbose);
1661     }
1662 }
1663 
1664 
1665 
1666 static void
convertImage(TIFF * const tifP,FILE * const alphaFileP,FILE * const imageoutFileP,struct CmdlineInfo const cmdline)1667 convertImage(TIFF *             const tifP,
1668              FILE *             const alphaFileP,
1669              FILE *             const imageoutFileP,
1670              struct CmdlineInfo const cmdline) {
1671 
1672     struct tiffDirInfo tiffDir;
1673     int format;
1674     xelval maxval;
1675     xel colormap[MAXCOLORS];
1676     unsigned short fillorder;
1677     bool flipOk, noflipOk;
1678     pnmOut pnmOut;
1679 
1680     readDirectory(tifP, cmdline.headerdump, &tiffDir);
1681 
1682     computeFillorder(tiffDir.fillorder, &fillorder, cmdline.respectfillorder);
1683 
1684     analyzeImageType(tifP, tiffDir.bps, tiffDir.spp, tiffDir.photomet,
1685                      &maxval, &format, colormap, cmdline.headerdump, cmdline);
1686 
1687     reportOutputFormat(format);
1688 
1689     pnmOut_init(imageoutFileP, alphaFileP, tiffDir.width, tiffDir.height,
1690                 tiffDir.orientation, maxval, format, maxval,
1691                 cmdline.byrow, cmdline.orientraw,
1692                 cmdline.verbose,
1693                 &flipOk, &noflipOk,
1694                 &pnmOut);
1695 
1696     convertRaster(&pnmOut, tifP, tiffDir, maxval,
1697                   fillorder, colormap, cmdline.byrow, flipOk, noflipOk,
1698                   cmdline.verbose);
1699 
1700     pnmOut_term(&pnmOut, cmdline.verbose);
1701 }
1702 
1703 
1704 
1705 static void
convertIt(TIFF * const tifP,FILE * const alphaFile,FILE * const imageoutFile,struct CmdlineInfo const cmdline)1706 convertIt(TIFF *             const tifP,
1707           FILE *             const alphaFile,
1708           FILE *             const imageoutFile,
1709           struct CmdlineInfo const cmdline) {
1710 
1711     unsigned int imageSeq;
1712     bool eof;
1713 
1714     imageSeq = 0;
1715     eof = FALSE;
1716 
1717     while (!eof) {
1718         bool success;
1719 
1720         if (cmdline.verbose)
1721             pm_message("Converting Image %u", imageSeq);
1722         convertImage(tifP, alphaFile, imageoutFile, cmdline);
1723         success = TIFFReadDirectory(tifP);
1724         eof = !success;
1725         ++imageSeq;
1726     }
1727 }
1728 
1729 
1730 
1731 int
main(int argc,const char * argv[])1732 main(int argc, const char * argv[]) {
1733 
1734     struct CmdlineInfo cmdline;
1735     TIFF * tiffP;
1736     FILE * alphaFile;
1737     FILE * imageoutFile;
1738 
1739     pm_proginit(&argc, argv);
1740 
1741     parseCommandLine(argc, argv, &cmdline);
1742 
1743     tiffP = newTiffImageObject(cmdline.inputFilename);
1744 
1745     if (cmdline.alphaStdout)
1746         alphaFile = stdout;
1747     else if (cmdline.alphaFilename == NULL)
1748         alphaFile = NULL;
1749     else
1750         alphaFile = pm_openw(cmdline.alphaFilename);
1751 
1752     if (cmdline.alphaStdout)
1753         imageoutFile = NULL;
1754     else
1755         imageoutFile = stdout;
1756 
1757     convertIt(tiffP, alphaFile, imageoutFile, cmdline);
1758 
1759     if (imageoutFile != NULL)
1760         pm_close( imageoutFile );
1761     if (alphaFile != NULL)
1762         pm_close( alphaFile );
1763 
1764     TIFFClose(tiffP);
1765 
1766     pm_strfree(cmdline.inputFilename);
1767 
1768     /* If the program failed, it previously aborted with nonzero completion
1769        code, via various function calls.
1770     */
1771     return 0;
1772 }
1773 
1774 
1775 
1776