/***************************************************************************** jpegtopnm ****************************************************************************** This program is part of the Netpbm package. This program converts from the JFIF format, which is based on JPEG, to the fundamental ppm or pgm format (depending on whether the JFIF image is gray scale or color). This program is by Bryan Henderson on 2000.03.20, but is derived with permission from the program djpeg, which is in the Independent Jpeg Group's JPEG library package. Under the terms of that permission, redistribution of this software is restricted as described in the file README.JPEG. Copyright (C) 1991-1998, Thomas G. Lane. TODO: For CMYK and YCCK JPEG input, optionally produce a 4-deep PAM output containing CMYK values. Define a tupletype for this. Extend pamtoppm to convert this to ppm using the standard transformation. See if additional decompressor options effects significant speedup. grayscale output of color image, downscaling, color quantization, and dithering are possibilities. Djpeg's man page says they make it faster. IMPLEMENTATION NOTE - PRECISION There are two versions of the JPEG library. One handles only JPEG files with 8 bit samples; the other handles only 12 bit files. This program may be compiled and linked against either, and run dynamically linked to either at runtime independently. It uses the precision information from the file header. Note that when the input has 12 bit precision, this program generates PPM files with two-byte samples, but when the input has 8 bit precision, it generates PPM files with one-byte samples. One should use Pnmdepth to reduce precision to 8 bits if one-byte-sample output is essential. IMPLEMENTATION NOTE - EXIF See http://exif.org. See the programs Exifdump (http://topo.math.u-psud.fr/~bousch/exifdump.py) and Jhead (http://www.sentex.net/~mwandel/jhead). *****************************************************************************/ #define _DEFAULT_SOURCE 1 /* New name for SVID & BSD source defines */ #define _BSD_SOURCE 1 /* Make sure strdup() is in string.h */ #define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */ #include #include /* to declare isprint() */ #include #include #include #include #include /* Note: jpeglib.h prerequires stdlib.h and ctype.h. It should include them itself, but doesn't. */ #include #include "pm_c_util.h" #include "pnm.h" #include "shhopt.h" #include "mallocvar.h" #include "nstring.h" #include "exif.h" #include "jpegdatasource.h" #define EXIT_WARNING 2 /* Goes with EXIT_FAILURE, EXIT_SUCCESS in stdlib.h */ enum Inklevel {NORMAL, ADOBE, GUESS}; /* This describes image samples that represent ink levels. NORMAL means 0 is no ink; ADOBE means 0 is maximum ink. GUESS means we don't know what 0 means, so we have to guess from information in the image. */ enum Colorspace { /* These are the color spaces in which we can get pixels from the JPEG decompressor. We include only those that are possible given our particular inputs to the decompressor. The decompressor is theoretically capable of other, e.g. YCCK. Unlike the JPEG library, this type distinguishes between the Adobe and non-Adobe style of CMYK samples. */ GRAYSCALE_COLORSPACE, RGB_COLORSPACE, CMYK_NORMAL_COLORSPACE, CMYK_ADOBE_COLORSPACE }; struct CmdlineInfo { /* All the information the user supplied in the command line, in a form easy for the program to use. */ char * inputFileName; char * exifFileName; /* Name of file in which to save EXIF information. NULL means don't save. "-" means standard output */ unsigned int verbose; unsigned int nosmooth; J_DCT_METHOD dctMethod; long int maxMemoryToUse; unsigned int traceLevel; enum Inklevel inklevel; unsigned int comments; unsigned int dumpexif; unsigned int traceexif; unsigned int multiple; unsigned int repair; }; static bool displayComments; /* User wants comments from the JPEG to be displayed */ static void interpretMaxmemory(bool const maxmemorySpec, const char * const maxmemory, long int * const maxMemoryToUseP) { /*---------------------------------------------------------------------------- Interpret the "maxmemory" command line option. -----------------------------------------------------------------------------*/ long int lval; char ch; if (!maxmemorySpec) { *maxMemoryToUseP = -1; /* unspecified */ } else if (sscanf(maxmemory, "%ld%c", &lval, &ch) < 1) { pm_error("Invalid value for --maxmemory option: '%s'.", maxmemory); } else { if (ch == 'm' || ch == 'M') lval *= 1000L; *maxMemoryToUseP = lval * 1000L; } } static void interpretAdobe(int const adobe, int const notadobe, enum Inklevel * const inklevelP) { /*---------------------------------------------------------------------------- Interpret the adobe/notadobe command line options -----------------------------------------------------------------------------*/ if (adobe && notadobe) pm_error("You cannot specify both -adobe and -notadobe options."); else { if (adobe) *inklevelP = ADOBE; else if (notadobe) *inklevelP = NORMAL; else *inklevelP = GUESS; } } static void parseCommandLine(int const argc, char ** const argv, struct CmdlineInfo * const cmdlineP) { /*---------------------------------------------------------------------------- Note that many of the strings that this function returns in the *cmdlineP structure are actually in the supplied argv array. And sometimes, one of these strings is actually just a suffix of an entry in argv! On the other hand, unlike other option processing functions, we do not change argv at all. -----------------------------------------------------------------------------*/ optEntry * option_def; /* Instructions to pm_optParseOptions3 on how to parse our options. */ optStruct3 opt; unsigned int i; /* local loop variable */ const char * maxmemory; const char * dctval; unsigned int adobe, notadobe; unsigned int tracelevelSpec, exifSpec, dctvalSpec, maxmemorySpec; unsigned int option_def_index; int argc_parse; /* argc, except we modify it as we parse */ char ** argv_parse; MALLOCARRAY_NOFAIL(option_def, 100); MALLOCARRAY_NOFAIL(argv_parse, argc); /* argv, except we modify it as we parse */ option_def_index = 0; /* incremented by OPTENTRY */ OPTENT3(0, "verbose", OPT_FLAG, NULL, &cmdlineP->verbose, 0); OPTENT3(0, "dct", OPT_STRING, &dctval, &dctvalSpec, 0); OPTENT3(0, "maxmemory", OPT_STRING, &maxmemory, &maxmemorySpec, 0); OPTENT3(0, "nosmooth", OPT_FLAG, NULL, &cmdlineP->nosmooth, 0); OPTENT3(0, "tracelevel", OPT_UINT, &cmdlineP->traceLevel, &tracelevelSpec, 0); OPTENT3(0, "adobe", OPT_FLAG, NULL, &adobe, 0); OPTENT3(0, "notadobe", OPT_FLAG, NULL, ¬adobe, 0); OPTENT3(0, "comments", OPT_FLAG, NULL, &cmdlineP->comments, 0); OPTENT3(0, "exif", OPT_STRING, &cmdlineP->exifFileName, &exifSpec, 0); OPTENT3(0, "dumpexif", OPT_FLAG, NULL, &cmdlineP->dumpexif, 0); OPTENT3(0, "multiple", OPT_FLAG, NULL, &cmdlineP->multiple, 0); OPTENT3(0, "repair", OPT_FLAG, NULL, &cmdlineP->repair, 0); OPTENT3(0, "traceexif", OPT_FLAG, NULL, &cmdlineP->traceexif, 0); opt.opt_table = option_def; opt.short_allowed = false; /* We have no short (old-fashioned) options */ opt.allowNegNum = false; /* We may have parms that are negative numbers */ /* Make private copy of arguments for pm_optParseOptions to corrupt */ argc_parse = argc; for (i=0; i < argc; ++i) argv_parse[i] = argv[i]; pm_optParseOptions3(&argc_parse, argv_parse, opt, sizeof(opt), 0); /* Uses and sets argc_parse, argv_parse, and some of *cmdlineP and others. */ if (!tracelevelSpec) cmdlineP->traceLevel = 0; if (!exifSpec) cmdlineP->exifFileName = NULL; if (argc_parse - 1 == 0) cmdlineP->inputFileName = strdup("-"); /* he wants stdin */ else if (argc_parse - 1 == 1) cmdlineP->inputFileName = strdup(argv_parse[1]); else pm_error("Too many arguments. The only argument accepted " "is the input file specification"); if (!dctvalSpec) cmdlineP->dctMethod = JDCT_DEFAULT; else { if (streq(dctval, "int")) cmdlineP->dctMethod = JDCT_ISLOW; else if (streq(dctval, "fast")) cmdlineP->dctMethod = JDCT_IFAST; else if (streq(dctval, "float")) cmdlineP->dctMethod = JDCT_FLOAT; else pm_error("Invalid value for the --dct option: '%s'.", dctval); } interpretMaxmemory(maxmemorySpec, maxmemory, &cmdlineP->maxMemoryToUse); interpretAdobe(adobe, notadobe, &cmdlineP->inklevel); free(argv_parse); } /* * Marker processor for COM and interesting APPn markers. * This replaces the library's built-in processor, which just skips the marker. * We want to print out the marker as text, to the extent possible. * Note this code relies on a non-suspending data source. */ #if 0 static unsigned int jpegGetc (j_decompress_ptr const cinfoP) { /* Read next byte */ struct jpeg_source_mgr * datasrcP = cinfoP->src; if (datasrcP->bytes_in_buffer == 0) { if (! (*datasrcP->fill_input_buffer) (cinfoP)) pm_error("Can't suspend here."); } datasrcP->bytes_in_buffer--; return GETJOCTET(*datasrcP->next_input_byte++); } static bool printTextMarker(j_decompress_ptr const cinfoP) { /*---------------------------------------------------------------------------- This is a routine that you can register with the Jpeg decompressor with e.g. jpeg_set_marker_processor(cinfoP, JPEG_APP0 + appType, printTextMarker); The decompressor then calls it when it encounters a miscellaneous marker of the specified type (e.g. APP1). At that time, the jpeg input stream is positioned to the marker contents -- first 2 bytes of length information, MSB first, where the length includes those two bytes, then the data. We just get and print the contents of the marker. This routine is no longer used; it is kept as an example in case we want to use it in the future. Instead, we use jpeg_save_markers() and have the Jpeg library store all the markers in memory for our later access. -----------------------------------------------------------------------------*/ const bool traceit = (cinfoP->err->trace_level >= 1); const bool display_value = traceit || (cinfoP->unread_marker == JPEG_COM && displayComments); INT32 length; unsigned int ch; unsigned int lastch; lastch = 0; /* initial value */ length = jpeg_getc(cinfoP) << 8; length += jpeg_getc(cinfoP); length -= 2; /* discount the length word itself */ if (traceit) { if (cinfoP->unread_marker == JPEG_COM) fprintf(stderr, "Comment, length %ld:\n", (long) length); else /* assume it is an APPn otherwise */ fprintf(stderr, "APP%d, length %ld:\n", cinfoP->unread_marker - JPEG_APP0, (long) length); } if (cinfoP->unread_marker == JPEG_COM && displayComments) fprintf(stderr, "COMMENT: "); while (--length >= 0) { ch = jpeg_getc(cinfoP); if (display_value) { /* Emit the character in a readable form. * Nonprintables are converted to \nnn form, * while \ is converted to \\. * Newlines in CR, CR/LF, or LF form will be printed as one * newline. */ if (ch == '\r') { fprintf(stderr, "\n"); } else if (ch == '\n') { if (lastch != '\r') fprintf(stderr, "\n"); } else if (ch == '\\') { fprintf(stderr, "\\\\"); } else if (isprint(ch)) { putc(ch, stderr); } else { fprintf(stderr, "\\%03o", ch); } lastch = ch; } } if (display_value) fprintf(stderr, "\n"); return true; } #endif static void printMarker(struct jpeg_marker_struct const marker) { if (marker.original_length != marker.data_length) { /* This should be impossible, because we asked for up to 65535 bytes, and the jpeg spec doesn't allow anything bigger than that. */ pm_message("INTERNAL ERROR: %d of %d bytes of marker were " "saved.", marker.data_length, marker.original_length); } { int i; JOCTET lastch; lastch = 0; /* initial value */ for (i = 0; i < marker.data_length; i++) { /* Emit the character in a readable form. * Nonprintables are converted to \nnn form, * while \ is converted to \\. * Newlines in CR, CR/LF, or LF form will be printed as one * newline. */ if (marker.data[i] == '\r') { fprintf(stderr, "\n"); } else if (marker.data[i] == '\n') { if (lastch != '\r') fprintf(stderr, "\n"); } else if (marker.data[i] == '\\') { fprintf(stderr, "\\\\"); } else if (isprint(marker.data[i])) { putc(marker.data[i], stderr); } else { fprintf(stderr, "\\%03o", marker.data[i]); } lastch = marker.data[i]; } fprintf(stderr, "\n"); } } typedef struct rgb {unsigned int r; unsigned int g; unsigned int b;} rgb_type; static rgb_type * read_rgb(JSAMPLE * const ptr, enum Colorspace const colorspace, unsigned int const maxval) { /*---------------------------------------------------------------------------- Return the RGB triple corresponding to the color of the JPEG pixel at 'ptr', which is in color space 'color_space'. Assume 'maxval' is the maximum sample value in the input pixel, and also use it for the maximum sample value in the return value. -----------------------------------------------------------------------------*/ static rgb_type rgb; /* Our return value */ switch (colorspace) { case RGB_COLORSPACE: { rgb.r = GETJSAMPLE(*(ptr+0)); rgb.g = GETJSAMPLE(*(ptr+1)); rgb.b = GETJSAMPLE(*(ptr+2)); } break; case CMYK_NORMAL_COLORSPACE: { int const c = GETJSAMPLE(*(ptr+0)); int const m = GETJSAMPLE(*(ptr+1)); int const y = GETJSAMPLE(*(ptr+2)); int const k = GETJSAMPLE(*(ptr+3)); /* I swapped m and y below, because they looked wrong. -Bryan 2000.08.20 */ rgb.r = ((maxval-k)*(maxval-c))/maxval; rgb.g = ((maxval-k)*(maxval-m))/maxval; rgb.b = ((maxval-k)*(maxval-y))/maxval; } break; case CMYK_ADOBE_COLORSPACE: { int const c = GETJSAMPLE(*(ptr+0)); int const m = GETJSAMPLE(*(ptr+1)); int const y = GETJSAMPLE(*(ptr+2)); int const k = GETJSAMPLE(*(ptr+3)); rgb.r = (k*c)/maxval; rgb.g = (k*m)/maxval; rgb.b = (k*y)/maxval; } break; default: pm_error("Internal error: unknown color space %d passed to " "read_rgb().", (int) colorspace); } return &rgb; } static void convertPixelRow(JSAMPROW const jpegbuffer, unsigned int const width, unsigned int const samplesPerPixel, enum Colorspace const colorSpace, int const format, xelval const maxval, xel * const pnmbuffer) { /*---------------------------------------------------------------------------- Convert the pixels in 'jpegbuffer' from libjpeg format to libnetpbm format in 'pnmbuffer'. The row has 'width' pixels and the data in 'jpegbuffer' is formatted with 'samplesPerPixel' samples per pixel with colorspace 'colorSpace'. The output Netpbm data is in format 'format' with maxval 'maxval'. -----------------------------------------------------------------------------*/ JSAMPLE * ptr; unsigned int outputCursor; /* Cursor into output buffer 'pnmbuffer' */ ptr = &jpegbuffer[0]; /* Start at beginning of input row */ for (outputCursor = 0; outputCursor < width; ++outputCursor) { xel currentPixel; if (samplesPerPixel >= 3) { const rgb_type * const rgbP = read_rgb(ptr, colorSpace, maxval); PPM_ASSIGN(currentPixel, rgbP->r, rgbP->g, rgbP->b); } else { PNM_ASSIGN1(currentPixel, GETJSAMPLE(*ptr)); } ptr += samplesPerPixel; /* move to next pixel of input */ pnmbuffer[outputCursor] = currentPixel; } } static void setColorSpaces(J_COLOR_SPACE const jpegColorSpace, int * const outputTypeP, J_COLOR_SPACE * const outColorSpaceP) { /*---------------------------------------------------------------------------- Decide what type of output (PPM or PGM) we shall generate and what color space we must request from the JPEG decompressor, based on the color space of the input JPEG image, 'jpegColorSpace'. Write to stderr a message telling which type we picked. Exit the process with EXIT_FAILURE completion code and a message to stderr if the input color space is beyond our capability. -----------------------------------------------------------------------------*/ /* Note that the JPEG decompressor is not capable of translating CMYK or YCCK to RGB, but can translate YCCK to CMYK. */ switch (jpegColorSpace) { case JCS_UNKNOWN: pm_error("Input JPEG image has 'unknown' color space " "(JCS_UNKNOWN). " "We cannot interpret this image."); break; case JCS_GRAYSCALE: *outputTypeP = PGM_TYPE; *outColorSpaceP = JCS_GRAYSCALE; break; case JCS_RGB: *outputTypeP = PPM_TYPE; *outColorSpaceP = JCS_RGB; break; case JCS_YCbCr: *outputTypeP = PPM_TYPE; *outColorSpaceP = JCS_RGB; /* Design note: We found this YCbCr->RGB conversion increases user mode CPU time by 2.5%. 2002.10.12 */ break; case JCS_CMYK: *outputTypeP = PPM_TYPE; *outColorSpaceP = JCS_CMYK; break; case JCS_YCCK: *outputTypeP = PPM_TYPE; *outColorSpaceP = JCS_CMYK; break; default: pm_error("INTERNAL ERROR: unknown color space code %d passed " "to setColorSpaces().", jpegColorSpace); } pm_message("WRITING %s FILE", *outputTypeP == PPM_TYPE ? "PPM" : "PGM"); } static const char * colorspaceName(J_COLOR_SPACE const jpegColorSpace) { const char * retval; switch(jpegColorSpace) { case JCS_UNKNOWN: retval = "JCS_UNKNOWN"; break; case JCS_GRAYSCALE: retval = "JCS_GRAYSCALE"; break; case JCS_RGB: retval = "JCS_RGB"; break; case JCS_YCbCr: retval = "JCS_YCbCr"; break; case JCS_CMYK: retval = "JCS_CMYK"; break; case JCS_YCCK: retval = "JCS_YCCK"; break; default: retval = "invalid"; break; }; return retval; } static void printVerboseInfoAboutHeader(struct jpeg_decompress_struct const cinfo){ struct jpeg_marker_struct * markerP; pm_message("input color space is %d (%s)", cinfo.jpeg_color_space, colorspaceName(cinfo.jpeg_color_space)); /* Note that raw information about marker, including marker type code, was already printed by the jpeg library, because of the jpeg library trace level >= 1. Our job is to interpret it a little bit. */ if (cinfo.marker_list) pm_message("Miscellaneous markers (excluding APP0, APP12) " "in header:"); else pm_message("No miscellaneous markers (excluding APP0, APP12) " "in header"); for (markerP = cinfo.marker_list; markerP; markerP = markerP->next) { if (markerP->marker == JPEG_COM) pm_message("Comment marker (COM):"); else if (markerP->marker >= JPEG_APP0 && markerP->marker <= JPEG_APP0+15) pm_message("Miscellaneous marker type APP%d:", markerP->marker - JPEG_APP0); else pm_message("Miscellaneous marker of unknown type (0x%X):", markerP->marker); printMarker(*markerP); } } static void beginJpegInput(struct jpeg_decompress_struct * const cinfoP, bool const verbose, J_DCT_METHOD const dctMethod, int const maxMemoryToUse, bool const nosmooth) { /*---------------------------------------------------------------------------- Read the JPEG header, create decompressor object (and allocate memory for it), set up decompressor. -----------------------------------------------------------------------------*/ /* Read file header, set default decompression parameters */ jpeg_read_header(cinfoP, true); cinfoP->dct_method = dctMethod; if (maxMemoryToUse != -1) cinfoP->mem->max_memory_to_use = maxMemoryToUse; if (nosmooth) cinfoP->do_fancy_upsampling = false; } static void printComments(struct jpeg_decompress_struct const cinfo) { struct jpeg_marker_struct * markerP; for (markerP = cinfo.marker_list; markerP; markerP = markerP->next) { if (markerP->marker == JPEG_COM) { pm_message("COMMENT:"); printMarker(*markerP); } } } static void printExifInfo(struct jpeg_marker_struct const marker, bool const wantTagTrace) { /*---------------------------------------------------------------------------- Dump as informational messages the contents of the Jpeg miscellaneous marker 'marker', assuming it is an Exif header. -----------------------------------------------------------------------------*/ exif_ImageInfo imageInfo; const char * error; assert(marker.data_length >= 6); exif_parse(marker.data+6, marker.data_length-6, &imageInfo, wantTagTrace, &error); if (error) { pm_message("EXIF header is invalid. %s", error); pm_strfree(error); } else { exif_showImageInfo(&imageInfo); exif_terminateImageInfo(&imageInfo); } } static bool isExif(struct jpeg_marker_struct const marker) { /*---------------------------------------------------------------------------- Return true iff the JPEG miscellaneous marker 'marker' is an Exif header. -----------------------------------------------------------------------------*/ bool retval; if (marker.marker == JPEG_APP0+1) { if (marker.data_length >=6 && memcmp(marker.data, "Exif", 4) == 0) retval = true; else retval = false; } else retval = false; return retval; } static void dumpExif(struct jpeg_decompress_struct const cinfo, bool const wantTrace) { /*---------------------------------------------------------------------------- Dump as informational messages the contents of all EXIF headers in the image, interpreted. An EXIF header is an APP1 marker. -----------------------------------------------------------------------------*/ struct jpeg_marker_struct * markerP; bool foundOne; for (markerP = cinfo.marker_list, foundOne = false; markerP; markerP = markerP->next) { if (isExif(*markerP)) { pm_message("EXIF INFO:"); printExifInfo(*markerP, wantTrace); foundOne = true; } } if (!foundOne) pm_message("No EXIF info in image."); } static void saveExif(struct jpeg_decompress_struct const cinfo, const char * const exifFileName) { /*---------------------------------------------------------------------------- Write the contents of the first Exif header in the image into the file with name 'exifFileName'. Start with the two byte length field. If 'exifFileName' is "-", write to standard output. If there is no Exif header in the image, write just zero, as a two byte pure binary integer. -----------------------------------------------------------------------------*/ FILE * exifFileP; struct jpeg_marker_struct * markerP; exifFileP = pm_openw(exifFileName); for (markerP = cinfo.marker_list; markerP && !isExif(*markerP); markerP = markerP->next); if (markerP) { pm_writebigshort(exifFileP, markerP->data_length+2); if (ferror(exifFileP)) pm_error("Write of Exif header to file '%s' failed on first byte.", exifFileName); else { int rc; rc = fwrite(markerP->data, 1, markerP->data_length, exifFileP); if (rc != markerP->data_length) pm_error("Write of Exif header to '%s' failed. Wrote " "length successfully, but then failed after " "%d characters of data.", exifFileName, rc); } } else { /* There is no Exif header in the image */ pm_writebigshort(exifFileP, 0); if (ferror(exifFileP)) pm_error("Write of Exif header file '%s' failed.", exifFileName); } pm_close(exifFileP); } static void tellDetails(struct jpeg_decompress_struct const cinfo, xelval const maxval, int const outputType) { printVerboseInfoAboutHeader(cinfo); pm_message("Input image data precision = %d bits", cinfo.data_precision); pm_message("Output file will have format %c%c " "with max sample value of %d.", (char) (outputType/256), (char) (outputType % 256), maxval); } static enum Colorspace computeColorSpace(struct jpeg_decompress_struct * const cinfoP, enum Inklevel const inklevel) { enum Colorspace colorSpace; if (cinfoP->out_color_space == JCS_GRAYSCALE) colorSpace = GRAYSCALE_COLORSPACE; else if (cinfoP->out_color_space == JCS_RGB) colorSpace = RGB_COLORSPACE; else if (cinfoP->out_color_space == JCS_CMYK) { switch (inklevel) { case ADOBE: colorSpace = CMYK_ADOBE_COLORSPACE; break; case NORMAL: colorSpace = CMYK_NORMAL_COLORSPACE; break; case GUESS: colorSpace = CMYK_ADOBE_COLORSPACE; break; } } else pm_error("Internal error: unacceptable output color space from " "JPEG decompressor."); return colorSpace; } static void convertRaster(struct jpeg_decompress_struct * const cinfoP, enum Colorspace const colorspace, FILE * const ofP, xelval const format, unsigned int const maxval) { /*---------------------------------------------------------------------------- Read the raster from the input and write it out in Netpbm format to file *ofP, in format 'format', with maxval 'maxval'. -----------------------------------------------------------------------------*/ JSAMPROW jpegbuffer; /* Input buffer. Filled by jpeg_scanlines() */ xel * pnmbuffer; /* Output buffer */ if (cinfoP->output_width > UINT_MAX/cinfoP->output_components) pm_error("Image is too wide (%u columns) for computations", cinfoP->output_width); MALLOCARRAY(jpegbuffer, cinfoP->output_width * cinfoP->output_components); if (!jpegbuffer) pm_error("Failed to allocated a buffer for a row of %u columns " "of %u samples each", cinfoP->output_width, cinfoP->output_components); pnmbuffer = pnm_allocrow(cinfoP->output_width); while (cinfoP->output_scanline < cinfoP->output_height) { jpeg_read_scanlines(cinfoP, &jpegbuffer, 1); if (ofP) { convertPixelRow(jpegbuffer, cinfoP->output_width, cinfoP->out_color_components, colorspace, format, maxval, pnmbuffer); pnm_writepnmrow(ofP, pnmbuffer, cinfoP->output_width, maxval, format, false); } } free(jpegbuffer); pnm_freerow(pnmbuffer); } static void convertImage(FILE * const ofP, struct CmdlineInfo const cmdline, struct jpeg_decompress_struct * const cinfoP) { int format; /* The type of output file, PGM or PPM. Value is either PPM_TYPE or PGM_TYPE, which conveniently also pass as format values PPM_FORMAT and PGM_FORMAT. */ xelval maxval; /* The maximum value of a sample (color component), both in the input and the output. */ enum Colorspace colorspace; /* The color space of the pixels coming out of the JPEG decompressor */ beginJpegInput(cinfoP, cmdline.verbose, cmdline.dctMethod, cmdline.maxMemoryToUse, cmdline.nosmooth); setColorSpaces(cinfoP->jpeg_color_space, &format, &cinfoP->out_color_space); maxval = pm_bitstomaxval(cinfoP->data_precision); if (cmdline.verbose) tellDetails(*cinfoP, maxval, format); /* Calculate output image dimensions so we can allocate space */ jpeg_calc_output_dimensions(cinfoP); /* Start decompressor */ jpeg_start_decompress(cinfoP); if (ofP) /* Write pnm output header */ pnm_writepnminit(ofP, cinfoP->output_width, cinfoP->output_height, maxval, format, false); colorspace = computeColorSpace(cinfoP, cmdline.inklevel); convertRaster(cinfoP, colorspace, ofP, format, maxval); if (cmdline.comments) printComments(*cinfoP); if (cmdline.dumpexif) dumpExif(*cinfoP, cmdline.traceexif); if (cmdline.exifFileName) saveExif(*cinfoP, cmdline.exifFileName); /* Finish decompression and release decompressor memory. */ jpeg_finish_decompress(cinfoP); } static void saveMarkers(struct jpeg_decompress_struct * const cinfoP) { unsigned int appType; /* Get all the miscellaneous markers (COM and APPn) saved for our later access. */ jpeg_save_markers(cinfoP, JPEG_COM, 65535); for (appType = 0; appType <= 15; ++appType) { if (appType == 0 || appType == 14) { /* The jpeg library uses APP0 and APP14 internally (see libjpeg.doc), so we don't mess with those. */ } else jpeg_save_markers(cinfoP, JPEG_APP0 + appType, 65535); } } static void convertImages(FILE * const ofP, struct CmdlineInfo const cmdline, struct jpeg_decompress_struct * const cinfoP, struct sourceManager * const sourceManagerP) { if (cmdline.multiple) { unsigned int imageSequence; for (imageSequence = 0; dsDataLeft(sourceManagerP); ++imageSequence) { if (cmdline.verbose) pm_message("Reading Image %u", imageSequence); convertImage(ofP, cmdline, cinfoP); } } else { if (dsDataLeft(sourceManagerP)) { convertImage(ofP, cmdline, cinfoP); } else pm_error("Input stream is empty"); } if (dsPrematureEof(sourceManagerP)) { if (cmdline.repair) pm_message("Premature EOF on input; repaired by padding end " "of image."); else pm_error("Premature EOF on input. Use -repair to salvage."); } } int main(int argc, const char **argv) { FILE * ofP; struct CmdlineInfo cmdline; struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; struct sourceManager * sourceManagerP; pm_proginit(&argc, argv); parseCommandLine(argc, (char **)argv, &cmdline); if (cmdline.exifFileName && streq(cmdline.exifFileName, "-")) /* He's got exif going to stdout, so there can be no image output */ ofP = NULL; else ofP = stdout; displayComments = cmdline.comments; /* Initialize the JPEG decompression object with default error handling. */ cinfo.err = jpeg_std_error(&jerr); jpeg_create_decompress(&cinfo); if (cmdline.traceLevel == 0 && cmdline.verbose) cinfo.err->trace_level = 1; else cinfo.err->trace_level = cmdline.traceLevel; saveMarkers(&cinfo); sourceManagerP = dsCreateSource(cmdline.inputFileName); cinfo.src = dsJpegSourceMgr(sourceManagerP); convertImages(ofP, cmdline, &cinfo, sourceManagerP); jpeg_destroy_decompress(&cinfo); if (ofP) { int rc; rc = fclose(ofP); if (rc == EOF) pm_error("Error writing output file. Errno = %s (%d).", strerror(errno), errno); } dsDestroySource(sourceManagerP); free(cmdline.inputFileName); exit(jerr.num_warnings > 0 ? EXIT_WARNING : EXIT_SUCCESS); }