From: DRC Date: Thu, 19 Jan 2017 21:18:57 +0000 (-0600) Subject: libjpeg API: Support reading/writing ICC profiles X-Git-Tag: 1.5.90~77 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=44b2399a940869920b659f3cdbfc32ffecb37364;p=libjpeg-turbo libjpeg API: Support reading/writing ICC profiles This commit does the following: -- Merges the two glueware functions (read_icc_profile() and write_icc_profile()) from iccjpeg.c, which is contained in downstream projects such as LCMS, Ghostscript, Mozilla, etc. These functions were originally intended for inclusion in libjpeg, but Tom Lane left the IJG before that could be accomplished. Since then, programs and libraries that needed to embed/extract ICC profiles in JPEG files had to include their own local copy of iccjpeg.c, which is suboptimal. -- The new functions were prefixed with jpeg_ and split into separate files for the compressor and decompressor, per the existing libjpeg coding standards. -- jpeg_write_icc_profile() was made slightly more fault-tolerant. It will now trigger a libjpeg error if it is called before jpeg_start_compress() or if it is passed NULL arguments. -- jpeg_read_icc_profile() was made slightly more fault-tolerant. It will now trigger a libjpeg error if it is called before jpeg_read_header() or if it is passed NULL arguments. It will also now trigger libjpeg warnings if the ICC profile data is corrupt. -- The code comments have been wordsmithed. -- Note that the one-line setup_read_icc_profile() function was not included. Instead, libjpeg.txt now documents the need to call jpeg_save_markers(cinfo, JPEG_APP0 + 2, 0xFFFF) prior to calling jpeg_read_header(), if jpeg_read_icc_profile() is to be used. -- Adds documentation for the new functions to libjpeg.txt. -- Adds an -icc switch to cjpeg and jpegtran that allows those programs to embed an ICC profile in the JPEG files they generate. -- Adds an -icc switch to djpeg that allows that program to extract an ICC profile from a JPEG file while decompressing. -- Adds appropriate unit tests for all of the above. -- Bumps the SO_AGE of the libjpeg API library to indicate the presence of new API functions. Note that the licensing information was obtained from: https://github.com/mm2/Little-CMS/issues/37#issuecomment-66450180 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index dae1971..c772c01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -234,9 +234,9 @@ if(NOT WITH_JPEG8) report_option(WITH_MEM_SRCDST "In-memory source/destination managers") endif() -set(SO_AGE 1) +set(SO_AGE 2) if(WITH_MEM_SRCDST) - set(SO_AGE 2) + set(SO_AGE 3) endif() if(WITH_JPEG8) @@ -489,12 +489,12 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) ############################################################################### set(JPEG_SOURCES jcapimin.c jcapistd.c jccoefct.c jccolor.c jcdctmgr.c jchuff.c - jcinit.c jcmainct.c jcmarker.c jcmaster.c jcomapi.c jcparam.c jcphuff.c - jcprepct.c jcsample.c jctrans.c jdapimin.c jdapistd.c jdatadst.c jdatasrc.c - jdcoefct.c jdcolor.c jddctmgr.c jdhuff.c jdinput.c jdmainct.c jdmarker.c - jdmaster.c jdmerge.c jdphuff.c jdpostct.c jdsample.c jdtrans.c jerror.c - jfdctflt.c jfdctfst.c jfdctint.c jidctflt.c jidctfst.c jidctint.c jidctred.c - jquant1.c jquant2.c jutils.c jmemmgr.c jmemnobs.c) + jcicc.c jcinit.c jcmainct.c jcmarker.c jcmaster.c jcomapi.c jcparam.c + jcphuff.c jcprepct.c jcsample.c jctrans.c jdapimin.c jdapistd.c jdatadst.c + jdatasrc.c jdcoefct.c jdcolor.c jddctmgr.c jdhuff.c jdicc.c jdinput.c + jdmainct.c jdmarker.c jdmaster.c jdmerge.c jdphuff.c jdpostct.c jdsample.c + jdtrans.c jerror.c jfdctflt.c jfdctfst.c jfdctint.c jidctflt.c jidctfst.c + jidctint.c jidctred.c jquant1.c jquant2.c jutils.c jmemmgr.c jmemnobs.c) if(WITH_ARITH_ENC OR WITH_ARITH_DEC) set(JPEG_SOURCES ${JPEG_SOURCES} jaricom.c) @@ -647,7 +647,8 @@ enable_testing() if(WITH_12BIT) set(TESTORIG testorig12.jpg) - set(MD5_JPEG_RGB_ISLOW 9620f424569594bb9242b48498ad801f) + set(MD5_JPEG_RGB_ISLOW 9d7369207c520d37f2c1cbfcb82b2964) + set(MD5_JPEG_RGB_ISLOW2 a00bd20d8ae49684640ef7177d2e0b64) set(MD5_PPM_RGB_ISLOW f3301d2219783b8b3d942b7239fa50c0) set(MD5_JPEG_422_IFAST_OPT 7322e3bd2f127f7de4b40d4480ce60e4) set(MD5_PPM_422_IFAST 79807fa552899e66a04708f533e16950) @@ -692,7 +693,8 @@ if(WITH_12BIT) set(MD5_JPEG_CROP cdb35ff4b4519392690ea040c56ea99c) else() set(TESTORIG testorig.jpg) - set(MD5_JPEG_RGB_ISLOW 768e970dd57b340ff1b83c9d3d47c77b) + set(MD5_JPEG_RGB_ISLOW 1d44a406f61da743b5fd31c0a9abdca3) + set(MD5_JPEG_RGB_ISLOW2 31d121e57b6c2934c890a7fc7763bcd4) set(MD5_PPM_RGB_ISLOW 00a257f5393fef8821f2b88ac7421291) set(MD5_BMP_RGB_ISLOW_565 f07d2e75073e4bb10f6c6f4d36e2e3be) set(MD5_BMP_RGB_ISLOW_565D 4cfa0928ef3e6bb626d7728c924cfda4) @@ -955,15 +957,21 @@ foreach(libtype ${TEST_LIBTYPES}) endmacro() # CC: null SAMP: fullsize FDCT: islow ENT: huff - add_bittest(cjpeg rgb-islow "-rgb;-dct;int" + add_bittest(cjpeg rgb-islow "-rgb;-dct;int;-icc;${TESTIMAGES}/test1.icc" testout_rgb_islow.jpg ${TESTIMAGES}/testorig.ppm ${MD5_JPEG_RGB_ISLOW}) # CC: null SAMP: fullsize IDCT: islow ENT: huff - add_bittest(djpeg rgb-islow "-dct;int;-ppm" + add_bittest(djpeg rgb-islow "-dct;int;-ppm;-icc;testout_rgb_islow.icc" testout_rgb_islow.ppm testout_rgb_islow.jpg ${MD5_PPM_RGB_ISLOW} cjpeg-${libtype}-rgb-islow) + add_test(djpeg-${libtype}-rgb-islow-icc-cmp + ${MD5CMP} b06a39d730129122e85c1363ed1bbc9e testout_rgb_islow.icc) + + add_bittest(jpegtran icc "-copy;all;-icc;${TESTIMAGES}/test2.icc" + testout_rgb_islow2.jpg testout_rgb_islow.jpg ${MD5_JPEG_RGB_ISLOW2}) + if(NOT WITH_12BIT) # CC: RGB->RGB565 SAMP: fullsize IDCT: islow ENT: huff add_bittest(djpeg rgb-islow-565 "-dct;int;-rgb565;-dither;none;-bmp" diff --git a/ChangeLog.md b/ChangeLog.md index 6617bd3..7e0494f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -33,6 +33,13 @@ libjpeg-turbo to be configured without the use of a terminal/command prompt. Extensive testing was conducted to ensure that all features provided by the autotools-based build system are provided by the new build system. +3. The libjpeg API in this version of libjpeg-turbo now includes two additional +functions, `jpeg_read_icc_profile()` and `jpeg_write_icc_profile()`, that can +be used to extract ICC profile data from a JPEG file while decompressing or to +embed ICC profile data in a JPEG file while compressing or transforming. This +eliminates the need for downstream projects, such as color management libraries +and browsers, to include their own glueware for accomplishing this. + 1.5.2 ===== diff --git a/cjpeg.1 b/cjpeg.1 index d1dc304..36805ea 100644 --- a/cjpeg.1 +++ b/cjpeg.1 @@ -1,4 +1,4 @@ -.TH CJPEG 1 "17 February 2016" +.TH CJPEG 1 "19 January 2017" .SH NAME cjpeg \- compress an image file to a JPEG file .SH SYNOPSIS @@ -187,6 +187,9 @@ method may also give different results on different machines due to varying roundoff behavior, whereas the integer methods should give the same results on all machines. .TP +.BI \-icc " file" +Embed ICC color management profile contained in the specified file. +.TP .BI \-restart " N" Emit a JPEG restart marker every N MCU rows, or every N MCU blocks if "B" is attached to the number. diff --git a/cjpeg.c b/cjpeg.c index 9d282b8..da9af73 100644 --- a/cjpeg.c +++ b/cjpeg.c @@ -31,6 +31,11 @@ #include "jversion.h" /* for version message */ #include "jconfigint.h" +#ifndef HAVE_STDLIB_H /* should declare malloc(),free() */ +extern void *malloc (size_t size); +extern void free (void *ptr); +#endif + #ifdef USE_CCOMMAND /* command-line reader for Macintosh */ #ifdef __MWERKS__ #include /* Metrowerks needs this */ @@ -139,6 +144,7 @@ select_file_type (j_compress_ptr cinfo, FILE *infile) static const char *progname; /* program name for error messages */ +static char *icc_filename; /* for -icc switch */ static char *outfilename; /* for -outfile switch */ boolean memdst; /* for -memdst switch */ @@ -184,6 +190,7 @@ usage (void) fprintf(stderr, " -dct float Use floating-point DCT method%s\n", (JDCT_DEFAULT == JDCT_FLOAT ? " (default)" : "")); #endif + fprintf(stderr, " -icc FILE Embed ICC profile contained in FILE\n"); fprintf(stderr, " -restart N Set restart interval in rows, or in blocks with B\n"); #ifdef INPUT_SMOOTHING_SUPPORTED fprintf(stderr, " -smooth N Smooth dithered input (N=1..100 is strength)\n"); @@ -234,6 +241,7 @@ parse_switches (j_compress_ptr cinfo, int argc, char **argv, force_baseline = FALSE; /* by default, allow 16-bit quantizers */ simple_progressive = FALSE; is_targa = FALSE; + icc_filename = NULL; outfilename = NULL; memdst = FALSE; cinfo->err->trace_level = 0; @@ -307,6 +315,12 @@ parse_switches (j_compress_ptr cinfo, int argc, char **argv, /* Force an RGB JPEG file to be generated. */ jpeg_set_colorspace(cinfo, JCS_RGB); + } else if (keymatch(arg, "icc", 1)) { + /* Set ICC filename. */ + if (++argn >= argc) /* advance to next argument */ + usage(); + icc_filename = argv[argn]; + } else if (keymatch(arg, "maxmemory", 3)) { /* Maximum memory in Kb (or Mb with 'm'). */ long lval; @@ -496,6 +510,9 @@ main (int argc, char **argv) int file_index; cjpeg_source_ptr src_mgr; FILE *input_file; + FILE *icc_file; + JOCTET *icc_profile = NULL; + long icc_len = 0; FILE *output_file = NULL; unsigned char *outbuffer = NULL; unsigned long outsize = 0; @@ -583,6 +600,33 @@ main (int argc, char **argv) output_file = write_stdout(); } + if (icc_filename != NULL) { + if ((icc_file = fopen(icc_filename, READ_BINARY)) == NULL) { + fprintf(stderr, "%s: can't open %s\n", progname, icc_filename); + exit(EXIT_FAILURE); + } + if (fseek(icc_file, 0, SEEK_END) < 0 || + (icc_len = ftell(icc_file)) < 1 || + fseek(icc_file, 0, SEEK_SET) < 0) { + fprintf(stderr, "%s: can't determine size of %s\n", progname, + icc_filename); + exit(EXIT_FAILURE); + } + if ((icc_profile = (JOCTET *)malloc(icc_len)) == NULL) { + fprintf(stderr, "%s: can't allocate memory for ICC profile\n", progname); + fclose(icc_file); + exit(EXIT_FAILURE); + } + if (fread(icc_profile, icc_len, 1, icc_file) < 1) { + fprintf(stderr, "%s: can't read ICC profile from %s\n", progname, + icc_filename); + free(icc_profile); + fclose(icc_file); + exit(EXIT_FAILURE); + } + fclose(icc_file); + } + #ifdef PROGRESS_REPORT start_progress_monitor((j_common_ptr) &cinfo, &progress); #endif @@ -611,6 +655,9 @@ main (int argc, char **argv) /* Start compressor */ jpeg_start_compress(&cinfo, TRUE); + if (icc_profile != NULL) + jpeg_write_icc_profile(&cinfo, icc_profile, (unsigned int)icc_len); + /* Process data */ while (cinfo.next_scanline < cinfo.image_height) { num_scanlines = (*src_mgr->get_pixel_rows) (&cinfo, src_mgr); @@ -638,6 +685,9 @@ main (int argc, char **argv) free(outbuffer); } + if (icc_profile != NULL) + free(icc_profile); + /* All done. */ exit(jerr.num_warnings ? EXIT_WARNING : EXIT_SUCCESS); return 0; /* suppress no-return-value warnings */ diff --git a/djpeg.1 b/djpeg.1 index 7efde43..ed39286 100644 --- a/djpeg.1 +++ b/djpeg.1 @@ -1,4 +1,4 @@ -.TH DJPEG 1 "18 February 2016" +.TH DJPEG 1 "19 January 2017" .SH NAME djpeg \- decompress a JPEG file to an image file .SH SYNOPSIS @@ -157,6 +157,9 @@ Ordered dither is only available in .B \-onepass mode. .TP +.BI \-icc " file" +Extract ICC color management profile to the specified file. +.TP .BI \-map " file" Quantize to the colors used in the specified image file. This is useful for producing multiple files with identical color maps, or for forcing a diff --git a/djpeg.c b/djpeg.c index 54cd525..53a8009 100644 --- a/djpeg.c +++ b/djpeg.c @@ -5,7 +5,7 @@ * Copyright (C) 1991-1997, Thomas G. Lane. * Modified 2013 by Guido Vollbeding. * libjpeg-turbo Modifications: - * Copyright (C) 2010-2011, 2013-2016, D. R. Commander. + * Copyright (C) 2010-2011, 2013-2017, D. R. Commander. * Copyright (C) 2015, Google, Inc. * For conditions of distribution and use, see the accompanying README.ijg * file. @@ -33,6 +33,10 @@ #include "jconfigint.h" #include "wrppm.h" +#ifndef HAVE_STDLIB_H /* should declare free() */ +extern void free (void *ptr); +#endif + #include /* to declare isprint() */ #ifdef USE_CCOMMAND /* command-line reader for Macintosh */ @@ -90,6 +94,7 @@ static IMAGE_FORMATS requested_fmt; static const char *progname; /* program name for error messages */ +static char *icc_filename; /* for -icc switch */ static char *outfilename; /* for -outfile switch */ boolean memsrc; /* for -memsrc switch */ boolean skip, crop; @@ -158,6 +163,7 @@ usage (void) fprintf(stderr, " -dither fs Use F-S dithering (default)\n"); fprintf(stderr, " -dither none Don't use dithering in quantization\n"); fprintf(stderr, " -dither ordered Use ordered dither (medium speed, quality)\n"); + fprintf(stderr, " -icc FILE Extract ICC profile to FILE\n"); #ifdef QUANT_2PASS_SUPPORTED fprintf(stderr, " -map FILE Map to colors used in named image file\n"); #endif @@ -196,6 +202,7 @@ parse_switches (j_decompress_ptr cinfo, int argc, char **argv, /* Set up default JPEG parameters. */ requested_fmt = DEFAULT_FMT; /* set default output file format */ + icc_filename = NULL; outfilename = NULL; memsrc = FALSE; skip = FALSE; @@ -303,6 +310,13 @@ parse_switches (j_decompress_ptr cinfo, int argc, char **argv, /* Force RGB565 output. */ cinfo->out_color_space = JCS_RGB565; + } else if (keymatch(arg, "icc", 1)) { + /* Set ICC filename. */ + if (++argn >= argc) /* advance to next argument */ + usage(); + icc_filename = argv[argn]; + jpeg_save_markers(cinfo, JPEG_APP0 + 2, 0xFFFF); + } else if (keymatch(arg, "map", 3)) { /* Quantize to a color map taken from an input file. */ if (++argn >= argc) /* advance to next argument */ @@ -754,6 +768,29 @@ main (int argc, char **argv) progress.pub.completed_passes = progress.pub.total_passes; #endif + if (icc_filename != NULL) { + FILE *icc_file; + JOCTET *icc_profile; + unsigned int icc_len; + + if ((icc_file = fopen(icc_filename, WRITE_BINARY)) == NULL) { + fprintf(stderr, "%s: can't open %s\n", progname, icc_filename); + exit(EXIT_FAILURE); + } + if (jpeg_read_icc_profile(&cinfo, &icc_profile, &icc_len)) { + if (fwrite(icc_profile, icc_len, 1, icc_file) < 1) { + fprintf(stderr, "%s: can't read ICC profile from %s\n", progname, + icc_filename); + free(icc_profile); + fclose(icc_file); + exit(EXIT_FAILURE); + } + free(icc_profile); + fclose(icc_file); + } else if (cinfo.err->msg_code != JWRN_BOGUS_ICC) + fprintf(stderr, "%s: no ICC profile data in JPEG file\n", progname); + } + /* Finish decompression and release memory. * I must do it in this order because output module has allocated memory * of lifespan JPOOL_IMAGE; it needs to finish before releasing memory. diff --git a/jcicc.c b/jcicc.c new file mode 100644 index 0000000..0613779 --- /dev/null +++ b/jcicc.c @@ -0,0 +1,105 @@ +/* + * jcicc.c + * + * Copyright (C) 1997-1998, Thomas G. Lane, Todd Newman. + * Copyright (C) 2017, D. R. Commander. + * For conditions of distribution and use, see the accompanying README.ijg + * file. + * + * This file provides code to write International Color Consortium (ICC) device + * profiles embedded in JFIF JPEG image files. The ICC has defined a standard + * for including such data in JPEG "APP2" markers. The code given here does + * not know anything about the internal structure of the ICC profile data; it + * just knows how to embed the profile data in a JPEG file while writing it. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" + + +/* + * Since an ICC profile can be larger than the maximum size of a JPEG marker + * (64K), we need provisions to split it into multiple markers. The format + * defined by the ICC specifies one or more APP2 markers containing the + * following data: + * Identifying string ASCII "ICC_PROFILE\0" (12 bytes) + * Marker sequence number 1 for first APP2, 2 for next, etc (1 byte) + * Number of markers Total number of APP2's used (1 byte) + * Profile data (remainder of APP2 data) + * Decoders should use the marker sequence numbers to reassemble the profile, + * rather than assuming that the APP2 markers appear in the correct sequence. + */ + +#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ +#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ +#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ +#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) + + +/* + * This routine writes the given ICC profile data into a JPEG file. It *must* + * be called AFTER calling jpeg_start_compress() and BEFORE the first call to + * jpeg_write_scanlines(). (This ordering ensures that the APP2 marker(s) will + * appear after the SOI and JFIF or Adobe markers, but before all else.) + */ + +GLOBAL(void) +jpeg_write_icc_profile (j_compress_ptr cinfo, const JOCTET *icc_data_ptr, + unsigned int icc_data_len) +{ + unsigned int num_markers; /* total number of markers we'll write */ + int cur_marker = 1; /* per spec, counting starts at 1 */ + unsigned int length; /* number of bytes to write in this marker */ + + if (icc_data_ptr == NULL || icc_data_len == 0) + ERREXIT(cinfo, JERR_BUFFER_SIZE); + if (cinfo->global_state < CSTATE_SCANNING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Calculate the number of markers we'll need, rounding up of course */ + num_markers = icc_data_len / MAX_DATA_BYTES_IN_MARKER; + if (num_markers * MAX_DATA_BYTES_IN_MARKER != icc_data_len) + num_markers++; + + while (icc_data_len > 0) { + /* length of profile to put in this marker */ + length = icc_data_len; + if (length > MAX_DATA_BYTES_IN_MARKER) + length = MAX_DATA_BYTES_IN_MARKER; + icc_data_len -= length; + + /* Write the JPEG marker header (APP2 code and marker length) */ + jpeg_write_m_header(cinfo, ICC_MARKER, + (unsigned int) (length + ICC_OVERHEAD_LEN)); + + /* Write the marker identifying string "ICC_PROFILE" (null-terminated). We + * code it in this less-than-transparent way so that the code works even if + * the local character set is not ASCII. + */ + jpeg_write_m_byte(cinfo, 0x49); + jpeg_write_m_byte(cinfo, 0x43); + jpeg_write_m_byte(cinfo, 0x43); + jpeg_write_m_byte(cinfo, 0x5F); + jpeg_write_m_byte(cinfo, 0x50); + jpeg_write_m_byte(cinfo, 0x52); + jpeg_write_m_byte(cinfo, 0x4F); + jpeg_write_m_byte(cinfo, 0x46); + jpeg_write_m_byte(cinfo, 0x49); + jpeg_write_m_byte(cinfo, 0x4C); + jpeg_write_m_byte(cinfo, 0x45); + jpeg_write_m_byte(cinfo, 0x0); + + /* Add the sequencing info */ + jpeg_write_m_byte(cinfo, cur_marker); + jpeg_write_m_byte(cinfo, (int) num_markers); + + /* Add the profile data */ + while (length--) { + jpeg_write_m_byte(cinfo, *icc_data_ptr); + icc_data_ptr++; + } + cur_marker++; + } +} diff --git a/jdicc.c b/jdicc.c new file mode 100644 index 0000000..b233736 --- /dev/null +++ b/jdicc.c @@ -0,0 +1,171 @@ +/* + * jdicc.c + * + * Copyright (C) 1997-1998, Thomas G. Lane, Todd Newman. + * Copyright (C) 2017, D. R. Commander. + * For conditions of distribution and use, see the accompanying README.ijg + * file. + * + * This file provides code to read International Color Consortium (ICC) device + * profiles embedded in JFIF JPEG image files. The ICC has defined a standard + * for including such data in JPEG "APP2" markers. The code given here does + * not know anything about the internal structure of the ICC profile data; it + * just knows how to get the profile data from a JPEG file while reading it. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" + +#ifndef HAVE_STDLIB_H /* should declare malloc() */ +extern void *malloc (size_t size); +#endif + + +#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ +#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ + + +/* + * Handy subroutine to test whether a saved marker is an ICC profile marker. + */ + +LOCAL(boolean) +marker_is_icc (jpeg_saved_marker_ptr marker) +{ + return + marker->marker == ICC_MARKER && + marker->data_length >= ICC_OVERHEAD_LEN && + /* verify the identifying string */ + GETJOCTET(marker->data[0]) == 0x49 && + GETJOCTET(marker->data[1]) == 0x43 && + GETJOCTET(marker->data[2]) == 0x43 && + GETJOCTET(marker->data[3]) == 0x5F && + GETJOCTET(marker->data[4]) == 0x50 && + GETJOCTET(marker->data[5]) == 0x52 && + GETJOCTET(marker->data[6]) == 0x4F && + GETJOCTET(marker->data[7]) == 0x46 && + GETJOCTET(marker->data[8]) == 0x49 && + GETJOCTET(marker->data[9]) == 0x4C && + GETJOCTET(marker->data[10]) == 0x45 && + GETJOCTET(marker->data[11]) == 0x0; +} + + +/* + * See if there was an ICC profile in the JPEG file being read; if so, + * reassemble and return the profile data. + * + * TRUE is returned if an ICC profile was found, FALSE if not. If TRUE is + * returned, *icc_data_ptr is set to point to the returned data, and + * *icc_data_len is set to its length. + * + * IMPORTANT: the data at *icc_data_ptr is allocated with malloc() and must be + * freed by the caller with free() when the caller no longer needs it. + * (Alternatively, we could write this routine to use the IJG library's memory + * allocator, so that the data would be freed implicitly when + * jpeg_finish_decompress() is called. But it seems likely that many + * applications will prefer to have the data stick around after decompression + * finishes.) + */ + +GLOBAL(boolean) +jpeg_read_icc_profile (j_decompress_ptr cinfo, JOCTET **icc_data_ptr, + unsigned int *icc_data_len) +{ + jpeg_saved_marker_ptr marker; + int num_markers = 0; + int seq_no; + JOCTET *icc_data; + unsigned int total_length; +#define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */ + char marker_present[MAX_SEQ_NO+1]; /* 1 if marker found */ + unsigned int data_length[MAX_SEQ_NO+1]; /* size of profile data in marker */ + unsigned int data_offset[MAX_SEQ_NO+1]; /* offset for data in marker */ + + if (icc_data_ptr == NULL || icc_data_len == NULL) + ERREXIT(cinfo, JERR_BUFFER_SIZE); + if (cinfo->global_state < DSTATE_READY) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + *icc_data_ptr = NULL; /* avoid confusion if FALSE return */ + *icc_data_len = 0; + + /* This first pass over the saved markers discovers whether there are + * any ICC markers and verifies the consistency of the marker numbering. + */ + + for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++) + marker_present[seq_no] = 0; + + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (marker_is_icc(marker)) { + if (num_markers == 0) + num_markers = GETJOCTET(marker->data[13]); + else if (num_markers != GETJOCTET(marker->data[13])) { + WARNMS(cinfo, JWRN_BOGUS_ICC); /* inconsistent num_markers fields */ + return FALSE; + } + seq_no = GETJOCTET(marker->data[12]); + if (seq_no <= 0 || seq_no > num_markers) { + WARNMS(cinfo, JWRN_BOGUS_ICC); /* bogus sequence number */ + return FALSE; + } + if (marker_present[seq_no]) { + WARNMS(cinfo, JWRN_BOGUS_ICC); /* duplicate sequence numbers */ + return FALSE; + } + marker_present[seq_no] = 1; + data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN; + } + } + + if (num_markers == 0) + return FALSE; + + /* Check for missing markers, count total space needed, + * compute offset of each marker's part of the data. + */ + + total_length = 0; + for (seq_no = 1; seq_no <= num_markers; seq_no++) { + if (marker_present[seq_no] == 0) { + WARNMS(cinfo, JWRN_BOGUS_ICC); /* missing sequence number */ + return FALSE; + } + data_offset[seq_no] = total_length; + total_length += data_length[seq_no]; + } + + if (total_length == 0) { + WARNMS(cinfo, JWRN_BOGUS_ICC); /* found only empty markers? */ + return FALSE; + } + + /* Allocate space for assembled data */ + icc_data = (JOCTET *) malloc(total_length * sizeof(JOCTET)); + if (icc_data == NULL) + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 11); /* oops, out of memory */ + + /* and fill it in */ + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (marker_is_icc(marker)) { + JOCTET FAR *src_ptr; + JOCTET *dst_ptr; + unsigned int length; + seq_no = GETJOCTET(marker->data[12]); + dst_ptr = icc_data + data_offset[seq_no]; + src_ptr = marker->data + ICC_OVERHEAD_LEN; + length = data_length[seq_no]; + while (length--) { + *dst_ptr++ = *src_ptr++; + } + } + } + + *icc_data_ptr = icc_data; + *icc_data_len = total_length; + + return TRUE; +} diff --git a/jerror.h b/jerror.h index 11a07cb..a79586f 100644 --- a/jerror.h +++ b/jerror.h @@ -5,7 +5,7 @@ * Copyright (C) 1994-1997, Thomas G. Lane. * Modified 1997-2009 by Guido Vollbeding. * libjpeg-turbo Modifications: - * Copyright (C) 2014, D. R. Commander. + * Copyright (C) 2014, 2017, D. R. Commander. * For conditions of distribution and use, see the accompanying README.ijg * file. * @@ -208,6 +208,7 @@ JMESSAGE(JERR_NO_ARITH_TABLE, "Arithmetic table 0x%02x was not defined") JMESSAGE(JWRN_ARITH_BAD_CODE, "Corrupt JPEG data: bad arithmetic code") #endif #endif +JMESSAGE(JWRN_BOGUS_ICC, "Corrupt JPEG data: bad ICC marker") #ifdef JMAKE_ENUM_LIST diff --git a/jpeglib.h b/jpeglib.h index 6c63f58..de6883e 100644 --- a/jpeglib.h +++ b/jpeglib.h @@ -5,7 +5,7 @@ * Copyright (C) 1991-1998, Thomas G. Lane. * Modified 2002-2009 by Guido Vollbeding. * libjpeg-turbo Modifications: - * Copyright (C) 2009-2011, 2013-2014, 2016, D. R. Commander. + * Copyright (C) 2009-2011, 2013-2014, 2016-2017, D. R. Commander. * Copyright (C) 2015, Google, Inc. * For conditions of distribution and use, see the accompanying README.ijg * file. @@ -976,6 +976,12 @@ EXTERN(void) jpeg_write_m_byte (j_compress_ptr cinfo, int val); /* Alternate compression function: just write an abbreviated table file */ EXTERN(void) jpeg_write_tables (j_compress_ptr cinfo); +/* Write ICC profile. See libjpeg.txt for usage information. */ +EXTERN(void) jpeg_write_icc_profile (j_compress_ptr cinfo, + const JOCTET *icc_data_ptr, + unsigned int icc_data_len); + + /* Decompression startup: read start of JPEG datastream to see what's there */ EXTERN(int) jpeg_read_header (j_decompress_ptr cinfo, boolean require_image); /* Return value is one of: */ @@ -1057,6 +1063,11 @@ EXTERN(void) jpeg_destroy (j_common_ptr cinfo); /* Default restart-marker-resync procedure for use by data source modules */ EXTERN(boolean) jpeg_resync_to_restart (j_decompress_ptr cinfo, int desired); +/* Read ICC profile. See libjpeg.txt for usage information. */ +EXTERN(boolean) jpeg_read_icc_profile (j_decompress_ptr cinfo, + JOCTET **icc_data_ptr, + unsigned int *icc_data_len); + /* These marker codes are exported since applications and data source modules * are likely to want to use them. diff --git a/jpegtran.1 b/jpegtran.1 index 7f3c853..1357d5f 100644 --- a/jpegtran.1 +++ b/jpegtran.1 @@ -1,4 +1,4 @@ -.TH JPEGTRAN 1 "18 February 2016" +.TH JPEGTRAN 1 "19 January 2017" .SH NAME jpegtran \- lossless transformation of JPEG files .SH SYNOPSIS @@ -217,6 +217,11 @@ v6a, \fBjpegtran\fR always did the equivalent of \fB-copy none\fR.) .PP Additional switches recognized by jpegtran are: .TP +.BI \-icc " file" +Embed ICC color management profile contained in the specified file. Note that +this will cause \fBjpegtran\fR to ignore any APP2 markers in the input file, +even if \fB-copy all\fR is specified. +.TP .BI \-maxmemory " N" Set limit for amount of memory to use in processing large images. Value is in thousands of bytes, or millions of bytes if "M" is attached to the diff --git a/jpegtran.c b/jpegtran.c index 6f8fd5b..ebd751e 100644 --- a/jpegtran.c +++ b/jpegtran.c @@ -40,6 +40,7 @@ static const char *progname; /* program name for error messages */ +static char *icc_filename; /* for -icc switch */ static char *outfilename; /* for -outfile switch */ static JCOPY_OPTION copyoption; /* -copy switch */ static jpeg_transform_info transformoption; /* image transformation options */ @@ -83,6 +84,7 @@ usage (void) #ifdef C_ARITH_CODING_SUPPORTED fprintf(stderr, " -arithmetic Use arithmetic coding\n"); #endif + fprintf(stderr, " -icc FILE Embed ICC profile contained in FILE\n"); fprintf(stderr, " -restart N Set restart interval in rows, or in blocks with B\n"); fprintf(stderr, " -maxmemory N Maximum memory to use (in kbytes)\n"); fprintf(stderr, " -outfile name Specify name for output file\n"); @@ -138,6 +140,7 @@ parse_switches (j_compress_ptr cinfo, int argc, char **argv, /* Set up default JPEG parameters. */ simple_progressive = FALSE; + icc_filename = NULL; outfilename = NULL; copyoption = JCOPYOPT_DEFAULT; transformoption.transform = JXFORM_NONE; @@ -238,6 +241,12 @@ parse_switches (j_compress_ptr cinfo, int argc, char **argv, select_transform(JXFORM_NONE); /* force an error */ #endif + } else if (keymatch(arg, "icc", 1)) { + /* Set ICC filename. */ + if (++argn >= argc) /* advance to next argument */ + usage(); + icc_filename = argv[argn]; + } else if (keymatch(arg, "maxmemory", 3)) { /* Maximum memory in Kb (or Mb with 'm'). */ long lval; @@ -385,6 +394,9 @@ main (int argc, char **argv) * single file pointer for sequential input and output operation. */ FILE *fp; + FILE *icc_file; + JOCTET *icc_profile = NULL; + long icc_len = 0; /* On Mac, fetch a command line. */ #ifdef USE_CCOMMAND @@ -449,6 +461,35 @@ main (int argc, char **argv) fp = read_stdin(); } + if (icc_filename != NULL) { + if ((icc_file = fopen(icc_filename, READ_BINARY)) == NULL) { + fprintf(stderr, "%s: can't open %s\n", progname, icc_filename); + exit(EXIT_FAILURE); + } + if (fseek(icc_file, 0, SEEK_END) < 0 || + (icc_len = ftell(icc_file)) < 1 || + fseek(icc_file, 0, SEEK_SET) < 0) { + fprintf(stderr, "%s: can't determine size of %s\n", progname, + icc_filename); + exit(EXIT_FAILURE); + } + if ((icc_profile = (JOCTET *)malloc(icc_len)) == NULL) { + fprintf(stderr, "%s: can't allocate memory for ICC profile\n", progname); + fclose(icc_file); + exit(EXIT_FAILURE); + } + if (fread(icc_profile, icc_len, 1, icc_file) < 1) { + fprintf(stderr, "%s: can't read ICC profile from %s\n", progname, + icc_filename); + free(icc_profile); + fclose(icc_file); + exit(EXIT_FAILURE); + } + fclose(icc_file); + if (copyoption == JCOPYOPT_ALL) + copyoption = JCOPYOPT_ALL_EXCEPT_ICC; + } + #ifdef PROGRESS_REPORT start_progress_monitor((j_common_ptr) &dstinfo, &progress); #endif @@ -524,6 +565,9 @@ main (int argc, char **argv) /* Copy to the output file any extra markers that we want to preserve */ jcopy_markers_execute(&srcinfo, &dstinfo, copyoption); + if (icc_profile != NULL) + jpeg_write_icc_profile(&dstinfo, icc_profile, (unsigned int)icc_len); + /* Execute image transformation, if any */ #if TRANSFORMS_SUPPORTED jtransform_execute_transformation(&srcinfo, &dstinfo, @@ -545,6 +589,9 @@ main (int argc, char **argv) end_progress_monitor((j_common_ptr) &dstinfo); #endif + if (icc_profile != NULL) + free(icc_profile); + /* All done. */ exit(jsrcerr.num_warnings + jdsterr.num_warnings ?EXIT_WARNING:EXIT_SUCCESS); return 0; /* suppress no-return-value warnings */ diff --git a/libjpeg.txt b/libjpeg.txt index 2aa1027..a35faf4 100644 --- a/libjpeg.txt +++ b/libjpeg.txt @@ -3,7 +3,7 @@ USING THE IJG JPEG LIBRARY This file was part of the Independent JPEG Group's software: Copyright (C) 1994-2013, Thomas G. Lane, Guido Vollbeding. libjpeg-turbo Modifications: -Copyright (C) 2010, 2014-2016, D. R. Commander. +Copyright (C) 2010, 2014-2017, D. R. Commander. Copyright (C) 2015, Google, Inc. For conditions of distribution and use, see the accompanying README.ijg file. @@ -47,6 +47,7 @@ Advanced features: Buffered-image mode Abbreviated datastreams and multiple images Special markers + ICC profiles Raw (downsampled) image data Really raw data: DCT coefficients Progress monitoring @@ -2633,6 +2634,44 @@ A simple example of an external COM processor can be found in djpeg.c. Also, see jpegtran.c for an example of using jpeg_save_markers. +ICC profiles +------------ + +Two functions are provided for writing and reading International Color +Consortium (ICC) device profiles embedded in JFIF JPEG image files: + + void jpeg_write_icc_profile (j_compress_ptr cinfo, + const JOCTET *icc_data_ptr, + unsigned int icc_data_len); + boolean jpeg_read_icc_profile (j_decompress_ptr cinfo, + JOCTET **icc_data_ptr, + unsigned int *icc_data_len); + +The ICC has defined a standard for including such data in JPEG "APP2" markers. +The aforementioned functions do not know anything about the internal structure +of the ICC profile data; they just know how to embed the profile data into a +JPEG file while writing it, or to extract the profile data from a JPEG file +while reading it. + +jpeg_write_icc_profile() must be called after calling jpeg_start_compress() and +before the first call to jpeg_write_scanlines() or jpeg_write_raw_data(). This +ordering ensures that the APP2 marker(s) will appear after the SOI and JFIF or +Adobe markers, but before all other data. + +jpeg_read_icc_profile() returns TRUE if an ICC profile was found and FALSE +otherwise. If an ICC profile was found, then the function will allocate a +memory region containing the profile and will return a pointer to that memory +region in *icc_data_ptr, as well as the length of the region in *icc_data_len. +This memory region is allocated by the library using malloc() and must be freed +by the caller using free() when the memory region is no longer needed. Callers +wishing to use jpeg_read_icc_profile() must call + + jpeg_save_markers(cinfo, JPEG_APP0 + 2, 0xFFFF); + +prior to calling jpeg_read_header(). jpeg_read_icc_profile() can be called at +any point between jpeg_read_header() and jpeg_finish_decompress(). + + Raw (downsampled) image data ---------------------------- diff --git a/testimages/test1.icc b/testimages/test1.icc new file mode 100644 index 0000000..d0245c8 Binary files /dev/null and b/testimages/test1.icc differ diff --git a/testimages/test1.icc.txt b/testimages/test1.icc.txt new file mode 100644 index 0000000..57fc52f --- /dev/null +++ b/testimages/test1.icc.txt @@ -0,0 +1,20 @@ +Little CMS +Copyright (c) 1998-2011 Marti Maria Saguer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/testimages/test2.icc b/testimages/test2.icc new file mode 100644 index 0000000..73f1b5a Binary files /dev/null and b/testimages/test2.icc differ diff --git a/testimages/test2.icc.txt b/testimages/test2.icc.txt new file mode 100644 index 0000000..57fc52f --- /dev/null +++ b/testimages/test2.icc.txt @@ -0,0 +1,20 @@ +Little CMS +Copyright (c) 1998-2011 Marti Maria Saguer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/transupp.c b/transupp.c index b51ef39..569c509 100644 --- a/transupp.c +++ b/transupp.c @@ -1576,9 +1576,12 @@ jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option) jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF); } /* Save all types of APPn markers iff ALL option */ - if (option == JCOPYOPT_ALL) { - for (m = 0; m < 16; m++) + if (option == JCOPYOPT_ALL || option == JCOPYOPT_ALL_EXCEPT_ICC) { + for (m = 0; m < 16; m++) { + if (option == JCOPYOPT_ALL_EXCEPT_ICC && m == 2) + continue; jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF); + } } #endif /* SAVE_MARKERS_SUPPORTED */ } diff --git a/transupp.h b/transupp.h index bf3118a..ac96b98 100644 --- a/transupp.h +++ b/transupp.h @@ -3,8 +3,8 @@ * * This file was part of the Independent JPEG Group's software: * Copyright (C) 1997-2011, Thomas G. Lane, Guido Vollbeding. - * It was modified by The libjpeg-turbo Project to include only code relevant - * to libjpeg-turbo. + * libjpeg-turbo Modifications: + * Copyright (C) 2017, D. R. Commander. * For conditions of distribution and use, see the accompanying README.ijg * file. * @@ -193,7 +193,8 @@ EXTERN(boolean) jtransform_perfect_transform typedef enum { JCOPYOPT_NONE, /* copy no optional markers */ JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */ - JCOPYOPT_ALL /* copy all optional markers */ + JCOPYOPT_ALL, /* copy all optional markers */ + JCOPYOPT_ALL_EXCEPT_ICC /* copy all optional markers except APP2 */ } JCOPY_OPTION; #define JCOPYOPT_DEFAULT JCOPYOPT_COMMENTS /* recommended default */ diff --git a/win/jpeg62-memsrcdst.def b/win/jpeg62-memsrcdst.def index 6499316..6f44d28 100755 --- a/win/jpeg62-memsrcdst.def +++ b/win/jpeg62-memsrcdst.def @@ -104,3 +104,5 @@ EXPORTS jpeg_mem_src @ 103 ; jpeg_skip_scanlines @ 104 ; jpeg_crop_scanline @ 105 ; + jpeg_read_icc_profile @ 106 ; + jpeg_write_icc_profile @ 107 ; diff --git a/win/jpeg62.def b/win/jpeg62.def index 9f30b1a..8f70df6 100755 --- a/win/jpeg62.def +++ b/win/jpeg62.def @@ -102,3 +102,5 @@ EXPORTS jzero_far @ 101 ; jpeg_skip_scanlines @ 102 ; jpeg_crop_scanline @ 103 ; + jpeg_read_icc_profile @ 104 ; + jpeg_write_icc_profile @ 105 ; diff --git a/win/jpeg7-memsrcdst.def b/win/jpeg7-memsrcdst.def index 37a4777..521d957 100644 --- a/win/jpeg7-memsrcdst.def +++ b/win/jpeg7-memsrcdst.def @@ -106,3 +106,5 @@ EXPORTS jpeg_mem_src @ 105 ; jpeg_skip_scanlines @ 106 ; jpeg_crop_scanline @ 107 ; + jpeg_read_icc_profile @ 108 ; + jpeg_write_icc_profile @ 109 ; diff --git a/win/jpeg7.def b/win/jpeg7.def index 92463c5..2415bb2 100644 --- a/win/jpeg7.def +++ b/win/jpeg7.def @@ -104,3 +104,5 @@ EXPORTS jzero_far @ 103 ; jpeg_skip_scanlines @ 104 ; jpeg_crop_scanline @ 105 ; + jpeg_read_icc_profile @ 106 ; + jpeg_write_icc_profile @ 107 ; diff --git a/win/jpeg8.def b/win/jpeg8.def index 19246ac..b8d0455 100644 --- a/win/jpeg8.def +++ b/win/jpeg8.def @@ -107,3 +107,5 @@ EXPORTS jzero_far @ 106 ; jpeg_skip_scanlines @ 107 ; jpeg_crop_scanline @ 108 ; + jpeg_read_icc_profile @ 109 ; + jpeg_write_icc_profile @ 110 ;