diff options
author | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2014-04-17 02:51:18 +0000 |
---|---|---|
committer | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2014-04-17 02:51:18 +0000 |
commit | b9e9b5c053a824da0c1c978a19bf2c9a50c07224 (patch) | |
tree | f2ae4590031856979b0766f7a9f23f3993f4da7a /converter | |
parent | d5134d10177ce64bc0d0c666d33dd1d4d54d72a9 (diff) | |
download | netpbm-mirror-b9e9b5c053a824da0c1c978a19bf2c9a50c07224.tar.gz netpbm-mirror-b9e9b5c053a824da0c1c978a19bf2c9a50c07224.tar.xz netpbm-mirror-b9e9b5c053a824da0c1c978a19bf2c9a50c07224.zip |
Add -output, make Standard Output work normally, thanks to a temporary file
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@2189 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'converter')
-rw-r--r-- | converter/other/pamtotiff.c | 379 |
1 files changed, 260 insertions, 119 deletions
diff --git a/converter/other/pamtotiff.c b/converter/other/pamtotiff.c index 0206678d..277b25d8 100644 --- a/converter/other/pamtotiff.c +++ b/converter/other/pamtotiff.c @@ -57,6 +57,9 @@ struct sizeset { bool b1, b2, b4, b8; }; +enum writeMethod {Tmpfile, DirectCreate, DirectAppend}; + +enum createPolicy {MustExist, MayCreate}; struct cmdlineInfo { /* All the information the user supplied in the command line, @@ -82,7 +85,8 @@ struct cmdlineInfo { struct sizeset indexsizeAllowed; /* Which bit widths are allowable in a raster of palette indices */ unsigned int verbose; - unsigned int append; + enum writeMethod writeMethod; /* Output mode */ + const char *output; /* -output option value. NULL if none. */ float resolution; /* X and Y resolution */ struct optNameValue * taglist; }; @@ -96,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 { @@ -147,8 +151,9 @@ parseCommandLine(int argc, char * indexbits; 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; @@ -156,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); @@ -174,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, "indexbits", OPT_STRING, &indexbits, &indexbitsSpec, 0); OPTENT3(0, "tag", OPT_NAMELIST, &cmdlineP->taglist, &tagSpec, 0); @@ -199,7 +206,7 @@ parseCommandLine(int argc, 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) @@ -216,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."); @@ -225,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"); @@ -238,9 +245,17 @@ parseCommandLine(int argc, if (fill) cmdlineP->g3options |= GROUP3OPT_FILLBITS; + if (outputSpec) { + if (appendSpec) + cmdlineP->writeMethod = DirectAppend; + else + cmdlineP->writeMethod = DirectCreate; + } 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; @@ -318,7 +333,7 @@ parseCommandLine(int argc, cmdlineP->taglist[0].value = NULL; } - if (argc-1 == 0) + if (argc-1 == 0) cmdlineP->input_filespec = "-"; else if (argc-1 != 1) pm_error("Program takes zero or one argument (filename). You " @@ -346,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). @@ -363,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) { @@ -372,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 { @@ -448,7 +463,7 @@ fillRowOfWholeBytePixels(struct pam * const pamP, unsigned int col; unsigned char * tP; unsigned int planes; - + if (photometric == PHOTOMETRIC_RGB) planes = pamP->depth; else @@ -465,18 +480,18 @@ fillRowOfWholeBytePixels(struct pam * const pamP, /* 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 @@ -510,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; @@ -532,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! " @@ -560,9 +575,9 @@ 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, + 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. @@ -582,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) { @@ -617,13 +632,13 @@ 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, + 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 @@ -652,8 +667,8 @@ 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, @@ -679,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) { @@ -693,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 : @@ -746,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; @@ -771,44 +821,160 @@ 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 -createTiffGenerator(int const ofd, - const char * const outFileName, - bool const append, - TIFF ** const tifPP) { +validateReadableOutputFile(int const ofd) { +/*---------------------------------------------------------------------------- + Validate that file 'ofd' is readable and fail the program if it isn't. - const char * option; + 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 - validateSeekableOutputFile(ofd, outFileName); + int flags; + + flags = fcntl(ofd, F_GETFL); - if (append) - option = "a"; + 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 +createTiffGeneratorDirect(const char * const outputFileName, + enum createPolicy const createPolicy, + TIFF ** const tifPP, + int * const ofdP) { +/*---------------------------------------------------------------------------- + Create a TIFF generator that writes its output to the specified file. + + If the file doesn't already exist and 'createPolicy' is MayCreate, + create the file; otherwise fail the program. + + Fail the program if the specified file is not seekable and readable. + + Return the handle of the TIFF generator as *tifPP. Also return the + file descriptor for the output file as *ofdP. +-----------------------------------------------------------------------------*/ + int fd; + + if (createPolicy == MustExist) + 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)); + } + + 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 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"); - *tifPP = TIFFFdOpen(ofd, outFileName, option); if (*tifPP == NULL) - pm_error("error opening standard output as TIFF file. " + 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); + } + + fclose(tmpfileP); } static void -destroyTiffGenerator(TIFF * const tifP) { +destroyTiffGenerator(enum writeMethod const writeMethod, + TIFF * const tifP, + int const ofd) { TIFFFlushData(tifP); + + if (writeMethod == Tmpfile) + copyBufferToStdout(ofd); + TIFFClose(tifP); } @@ -825,11 +991,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) { @@ -842,7 +1008,7 @@ createTiffColorMap(struct pam * const pamP, } *tiffColorMapP = tiffColorMap; } - + static void @@ -867,7 +1033,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); @@ -940,7 +1106,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. @@ -966,7 +1132,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; @@ -979,11 +1145,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, @@ -1008,7 +1174,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) @@ -1017,35 +1183,6 @@ 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 !MSVCRT - - 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 -} @@ -1055,25 +1192,27 @@ main(int argc, char *argv[]) { const char * inputFileDescription; 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); if (streq(cmdline.input_filespec, "-")) inputFileDescription = "Standard Input"; - else + else inputFileDescription = cmdline.input_filespec; - if (cmdline.append) - validateReadableStdout(); - - createTiffGenerator(STDOUT_FILENO, "Standard Output", cmdline.append, - &tifP); + if (cmdline.writeMethod == DirectAppend) + createTiffGeneratorDirect(cmdline.output, MustExist, &tifP, &ofd); + else if (cmdline.writeMethod == DirectCreate) + createTiffGeneratorDirect(cmdline.output, MayCreate, &tifP, &ofd); + else + createTiffGeneratorTmpfile(&tifP, &ofd); eof = FALSE; /* initial assumption */ imageSeq = 0; @@ -1081,17 +1220,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. " @@ -1100,8 +1239,10 @@ main(int argc, char *argv[]) { } } - destroyTiffGenerator(tifP); + destroyTiffGenerator(cmdline.writeMethod, tifP, ofd); pm_close(ifP); return 0; } + + |