diff options
Diffstat (limited to 'converter/other/jpegtopnm.c')
-rw-r--r-- | converter/other/jpegtopnm.c | 970 |
1 files changed, 970 insertions, 0 deletions
diff --git a/converter/other/jpegtopnm.c b/converter/other/jpegtopnm.c new file mode 100644 index 00000000..60ae7e42 --- /dev/null +++ b/converter/other/jpegtopnm.c @@ -0,0 +1,970 @@ +/***************************************************************************** + 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 signficant 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 _BSD_SOURCE 1 /* Make sure strdup() is in string.h */ +#define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */ + +#include <ctype.h> /* to declare isprint() */ +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <assert.h> +/* Note: jpeglib.h prerequires stdlib.h and ctype.h. It should include them + itself, but doesn't. +*/ +#include <jpeglib.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 *input_filespec; + char *exif_filespec; + /* Filespec in which to save EXIF information. NULL means don't + save. "-" means standard output + */ + unsigned int verbose; + unsigned int nosmooth; + J_DCT_METHOD dct_method; + long int max_memory_to_use; + unsigned int trace_level; + enum inklevel inklevel; + unsigned int comments; + unsigned int dumpexif; + unsigned int multiple; +}; + + +static bool displayComments; + /* User wants comments from the JPEG to be displayed */ + +static void +interpret_maxmemory (bool const maxmemorySpec, + const char * const maxmemory, + long int * const max_memory_to_use_p) { +/*---------------------------------------------------------------------------- + Interpret the "maxmemory" command line option. +-----------------------------------------------------------------------------*/ + long int lval; + char ch; + + if (!maxmemorySpec) { + *max_memory_to_use_p = -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; + *max_memory_to_use_p = lval * 1000L; + } +} + + +static void +interpret_adobe(const int adobe, const int notadobe, + enum inklevel * const inklevel_p) { +/*---------------------------------------------------------------------------- + Interpret the adobe/notadobe command line options +-----------------------------------------------------------------------------*/ + if (adobe && notadobe) + pm_error("You cannot specify both -adobe and -notadobe options."); + else { + if (adobe) + *inklevel_p = ADOBE; + else if (notadobe) + *inklevel_p = NORMAL; + else + *inklevel_p = GUESS; + } +} + + + +static void +parse_command_line(const int argc, char ** argv, + struct cmdlineInfo *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 optParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + int i; /* local loop variable */ + + char *maxmemory; + 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->trace_level, + &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->exif_filespec, + &exifSpec, 0); + OPTENT3(0, "dumpexif", OPT_FLAG, NULL, &cmdlineP->dumpexif, 0); + OPTENT3(0, "multiple", OPT_FLAG, NULL, &cmdlineP->multiple, 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 optParseOptions to corrupt */ + argc_parse = argc; + for (i=0; i < argc; i++) argv_parse[i] = argv[i]; + + 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->trace_level = 0; + + if (!exifSpec) + cmdlineP->exif_filespec = NULL; + + if (argc_parse - 1 == 0) + cmdlineP->input_filespec = strdup("-"); /* he wants stdin */ + else if (argc_parse - 1 == 1) + cmdlineP->input_filespec = strdup(argv_parse[1]); + else + pm_error("Too many arguments. The only argument accepted " + "is the input file specification"); + + if (!dctvalSpec) + cmdlineP->dct_method = JDCT_DEFAULT; + else { + if (STREQ(dctval, "int")) + cmdlineP->dct_method = JDCT_ISLOW; + else if (STREQ(dctval, "fast")) + cmdlineP->dct_method = JDCT_IFAST; + else if (STREQ(dctval, "float")) + cmdlineP->dct_method = JDCT_FLOAT; + else pm_error("Invalid value for the --dct option: '%s'.", dctval); + } + + interpret_maxmemory(maxmemorySpec, maxmemory, + &cmdlineP->max_memory_to_use); + + interpret_adobe(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 +jpeg_getc (j_decompress_ptr cinfo) +/* Read next byte */ +{ + struct jpeg_source_mgr * datasrc = cinfo->src; + + if (datasrc->bytes_in_buffer == 0) { + if (! (*datasrc->fill_input_buffer) (cinfo)) + pm_error("Can't suspend here."); + } + datasrc->bytes_in_buffer--; + return GETJOCTET(*datasrc->next_input_byte++); +} + + +static boolean +print_text_marker (j_decompress_ptr cinfo) { +/*---------------------------------------------------------------------------- + This is a routine that you can register with the Jpeg decompressor + with e.g. + + jpeg_set_marker_processor(cinfoP, JPEG_APP0 + app_type, + print_text_marker); + + 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 boolean traceit = (cinfo->err->trace_level >= 1); + const boolean display_value = + traceit || (cinfo->unread_marker == JPEG_COM && displayComments); + + INT32 length; + unsigned int ch; + unsigned int lastch = 0; + + length = jpeg_getc(cinfo) << 8; + length += jpeg_getc(cinfo); + length -= 2; /* discount the length word itself */ + + if (traceit) { + if (cinfo->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", + cinfo->unread_marker - JPEG_APP0, (long) length); + } + + if (cinfo->unread_marker == JPEG_COM && displayComments) + fprintf(stderr, "COMMENT: "); + + while (--length >= 0) { + ch = jpeg_getc(cinfo); + 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 +print_marker(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; + 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 *ptr, const enum colorspace color_space, + const unsigned int 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 (color_space) { + case RGB_COLORSPACE: { + rgb.r = GETJSAMPLE(*(ptr+0)); + rgb.g = GETJSAMPLE(*(ptr+1)); + rgb.b = GETJSAMPLE(*(ptr+2)); + } + break; + case CMYK_NORMAL_COLORSPACE: { + const int c = GETJSAMPLE(*(ptr+0)); + const int m = GETJSAMPLE(*(ptr+1)); + const int y = GETJSAMPLE(*(ptr+2)); + const int 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: { + const int c = GETJSAMPLE(*(ptr+0)); + const int m = GETJSAMPLE(*(ptr+1)); + const int y = GETJSAMPLE(*(ptr+2)); + const int 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) color_space); + } + return(&rgb); +} + + + +/* pnmbuffer is declared global because it would be improper to pass a + pointer to it as input to copy_pixel_row(), since it isn't + logically a parameter of the operation, but rather is private to + copy_pixel_row(). But it would be impractical to allocate and free + the storage with every call to copy_pixel_row(). +*/ +static xel *pnmbuffer; /* Output buffer. Input to pnm_writepnmrow() */ + +static void +copy_pixel_row(const JSAMPROW jpegbuffer, const int width, + const unsigned int samples_per_pixel, + const enum colorspace color_space, + const unsigned int maxval, + FILE * const output_file, const int output_type) { + JSAMPLE *ptr; + unsigned int output_cursor; /* Cursor into output buffer 'pnmbuffer' */ + + ptr = jpegbuffer; /* Start at beginning of input row */ + + for (output_cursor = 0; output_cursor < width; output_cursor++) { + xel current_pixel; + if (samples_per_pixel >= 3) { + const rgb_type * const rgb_p = read_rgb(ptr, color_space, maxval); + PPM_ASSIGN(current_pixel, rgb_p->r, rgb_p->g, rgb_p->b); + } else { + PNM_ASSIGN1(current_pixel, GETJSAMPLE(*ptr)); + } + ptr += samples_per_pixel; /* move to next pixel of input */ + pnmbuffer[output_cursor] = current_pixel; + } + pnm_writepnmrow(output_file, pnmbuffer, width, + maxval, output_type, FALSE); +} + + + +static void +set_color_spaces(const J_COLOR_SPACE jpeg_color_space, + int * const output_type_p, + J_COLOR_SPACE * const out_color_space_p) { +/*---------------------------------------------------------------------------- + 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, 'jpeg_color_space'. + + 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 (jpeg_color_space) { + case JCS_UNKNOWN: + pm_error("Input JPEG image has 'unknown' color space " + "(JCS_UNKNOWN).\n" + "We cannot interpret this image."); + break; + case JCS_GRAYSCALE: + *output_type_p = PGM_TYPE; + *out_color_space_p = JCS_GRAYSCALE; + break; + case JCS_RGB: + *output_type_p = PPM_TYPE; + *out_color_space_p = JCS_RGB; + break; + case JCS_YCbCr: + *output_type_p = PPM_TYPE; + *out_color_space_p = 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: + *output_type_p = PPM_TYPE; + *out_color_space_p = JCS_CMYK; + break; + case JCS_YCCK: + *output_type_p = PPM_TYPE; + *out_color_space_p = JCS_CMYK; + break; + default: + pm_error("Internal error: unknown color space code %d passed " + "to set_color_spaces().", jpeg_color_space); + } + pm_message("WRITING %s FILE", + *output_type_p == PPM_TYPE ? "PPM" : "PGM"); +} + + + +static const char * +colorspace_name(const J_COLOR_SPACE jpeg_color_space) { + + const char *retval; + + switch(jpeg_color_space) { + 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 +print_verbose_info_about_header(struct jpeg_decompress_struct const cinfo){ + + struct jpeg_marker_struct * markerP; + + pm_message("input color space is %d (%s)\n", + cinfo.jpeg_color_space, + colorspace_name(cinfo.jpeg_color_space)); + + /* Note that raw information about marker, including marker type code, + was already printed by the jpeg library, due to 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); + + print_marker(*markerP); + } +} + + + +static void +beginJpegInput(struct jpeg_decompress_struct * const cinfoP, + const boolean verbose, + const J_DCT_METHOD dct_method, + const int max_memory_to_use, + const boolean 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 = dct_method; + if (max_memory_to_use != -1) + cinfoP->mem->max_memory_to_use = max_memory_to_use; + if (nosmooth) + cinfoP->do_fancy_upsampling = FALSE; + +} + + + +static void +print_comments(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:"); + print_marker(*markerP); + } +} + + + +static void +print_exif_info(struct jpeg_marker_struct const marker) { +/*---------------------------------------------------------------------------- + Dump as informational messages the contents of the Jpeg miscellaneous + marker 'marker', assuming it is an Exif header. +-----------------------------------------------------------------------------*/ + ImageInfo_t imageInfo; + const char * error; + + assert(marker.data_length >= 6); + + process_EXIF(marker.data+6, marker.data_length-6, + &imageInfo, FALSE, &error); + + if (error) { + pm_message("EXIF header is invalid. %s", error); + strfree(error); + } else + ShowImageInfo(&imageInfo); +} + + + +static boolean +is_exif(struct jpeg_marker_struct const marker) { +/*---------------------------------------------------------------------------- + Return true iff the JPEG miscellaneous marker 'marker' is an Exif + header. +-----------------------------------------------------------------------------*/ + boolean 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 +dump_exif(struct jpeg_decompress_struct const cinfo) { +/*---------------------------------------------------------------------------- + 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; + boolean found_one; + + found_one = FALSE; /* initial value */ + + for (markerP = cinfo.marker_list; + markerP; markerP = markerP->next) + if (is_exif(*markerP)) { + pm_message("EXIF INFO:"); + print_exif_info(*markerP); + found_one = TRUE; + } + if (!found_one) + pm_message("No EXIF info in image."); +} + + + +static void +save_exif(struct jpeg_decompress_struct const cinfo, + const char * const exif_filespec) { +/*---------------------------------------------------------------------------- + Write the contents of the first Exif header in the image into the + file with filespec 'exif_filespec'. Start with the two byte length + field. If 'exif_filespec' 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 * exif_file; + struct jpeg_marker_struct * markerP; + + exif_file = pm_openw(exif_filespec); + + for (markerP = cinfo.marker_list; + markerP && !is_exif(*markerP); + markerP = markerP->next); + + if (markerP) { + pm_writebigshort(exif_file, markerP->data_length+2); + if (ferror(exif_file)) + pm_error("Write of Exif header to %s failed on first byte.", + exif_filespec); + else { + int rc; + + rc = fwrite(markerP->data, 1, markerP->data_length, exif_file); + 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.", exif_filespec, rc); + } + } else { + /* There is no Exif header in the image */ + pm_writebigshort(exif_file, 0); + if (ferror(exif_file)) + pm_error("Write of Exif header file '%s' failed.", exif_filespec); + } + pm_close(exif_file); +} + + + +static void +tellDetails(struct jpeg_decompress_struct const cinfo, + xelval const maxval, + int const output_type) { + + print_verbose_info_about_header(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) (output_type/256), (char) (output_type % 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 +convertImage(FILE * const ofP, + struct cmdlineInfo const cmdline, + struct jpeg_decompress_struct * const cinfoP) { + + int output_type; + /* 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. + */ + JSAMPROW jpegbuffer; /* Input buffer. Filled by jpeg_scanlines() */ + unsigned int maxval; + /* The maximum value of a sample (color component), both in the input + and the output. + */ + enum colorspace color_space; + /* The color space of the pixels coming out of the JPEG decompressor */ + + beginJpegInput(cinfoP, cmdline.verbose, + cmdline.dct_method, + cmdline.max_memory_to_use, cmdline.nosmooth); + + set_color_spaces(cinfoP->jpeg_color_space, &output_type, + &cinfoP->out_color_space); + + maxval = (1 << cinfoP->data_precision) - 1; + + if (cmdline.verbose) + tellDetails(*cinfoP, maxval, output_type); + + /* Calculate output image dimensions so we can allocate space */ + jpeg_calc_output_dimensions(cinfoP); + + jpegbuffer = ((*cinfoP->mem->alloc_sarray) + ((j_common_ptr) cinfoP, JPOOL_IMAGE, + cinfoP->output_width * cinfoP->output_components, + (JDIMENSION) 1) + )[0]; + + /* Start decompressor */ + jpeg_start_decompress(cinfoP); + + if (ofP) + /* Write pnm output header */ + pnm_writepnminit(ofP, cinfoP->output_width, cinfoP->output_height, + maxval, output_type, FALSE); + + pnmbuffer = pnm_allocrow(cinfoP->output_width); + + color_space = computeColorSpace(cinfoP, cmdline.inklevel); + + /* Process data */ + while (cinfoP->output_scanline < cinfoP->output_height) { + jpeg_read_scanlines(cinfoP, &jpegbuffer, 1); + if (ofP) + copy_pixel_row(jpegbuffer, cinfoP->output_width, + cinfoP->out_color_components, + color_space, maxval, ofP, output_type); + } + + if (cmdline.comments) + print_comments(*cinfoP); + if (cmdline.dumpexif) + dump_exif(*cinfoP); + if (cmdline.exif_filespec) + save_exif(*cinfoP, cmdline.exif_filespec); + + pnm_freerow(pnmbuffer); + + /* Finish decompression and release decompressor memory. */ + jpeg_finish_decompress(cinfoP); +} + + + + +static void +saveMarkers(struct jpeg_decompress_struct * const cinfoP) { + + unsigned int app_type; + /* Get all the miscellaneous markers (COM and APPn) saved for our + later access. + */ + jpeg_save_markers(cinfoP, JPEG_COM, 65535); + for (app_type = 0; app_type <= 15; ++app_type) { + if (app_type == 0 || app_type == 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 + app_type, 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"); + } +} + + + +int +main(int argc, char **argv) { + FILE * ofP; + struct cmdlineInfo cmdline; + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + struct sourceManager * sourceManagerP; + + pnm_init(&argc, argv); + + parse_command_line(argc, argv, &cmdline); + + if (cmdline.exif_filespec && STREQ(cmdline.exif_filespec, "-")) + /* 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.trace_level == 0 && cmdline.verbose) + cinfo.err->trace_level = 1; + else + cinfo.err->trace_level = cmdline.trace_level; + + saveMarkers(&cinfo); + + sourceManagerP = dsCreateSource(cmdline.input_filespec); + + 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.input_filespec); + + exit(jerr.num_warnings > 0 ? EXIT_WARNING : EXIT_SUCCESS); +} + |