summaryrefslogtreecommitdiff
path: root/plugins/AdvaImg/src/FreeImage/PluginJPEG.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/AdvaImg/src/FreeImage/PluginJPEG.cpp')
-rw-r--r--plugins/AdvaImg/src/FreeImage/PluginJPEG.cpp1801
1 files changed, 1801 insertions, 0 deletions
diff --git a/plugins/AdvaImg/src/FreeImage/PluginJPEG.cpp b/plugins/AdvaImg/src/FreeImage/PluginJPEG.cpp
new file mode 100644
index 0000000000..b69ace95bc
--- /dev/null
+++ b/plugins/AdvaImg/src/FreeImage/PluginJPEG.cpp
@@ -0,0 +1,1801 @@
+// ==========================================================
+// JPEG Loader and writer
+// Based on code developed by The Independent JPEG Group
+//
+// Design and implementation by
+// - Floris van den Berg (flvdberg@wxs.nl)
+// - Jan L. Nauta (jln@magentammt.com)
+// - Markus Loibl (markus.loibl@epost.de)
+// - Karl-Heinz Bussian (khbussian@moss.de)
+// - Hervé Drolon (drolon@infonie.fr)
+// - Jascha Wetzel (jascha@mainia.de)
+// - Mihail Naydenov (mnaydenov@users.sourceforge.net)
+//
+// This file is part of FreeImage 3
+//
+// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
+// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
+// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
+// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
+// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
+// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
+// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
+// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+// THIS DISCLAIMER.
+//
+// Use at your own risk!
+// ==========================================================
+
+#ifdef _MSC_VER
+#pragma warning (disable : 4786) // identifier was truncated to 'number' characters
+#endif
+
+extern "C" {
+#define XMD_H
+#undef FAR
+#include <setjmp.h>
+
+#include "../LibJPEG/jinclude.h"
+#include "../LibJPEG/jpeglib.h"
+#include "../LibJPEG/jerror.h"
+}
+
+#include "FreeImage.h"
+#include "Utilities.h"
+
+#include "../Metadata/FreeImageTag.h"
+
+
+// ==========================================================
+// Plugin Interface
+// ==========================================================
+
+static int s_format_id;
+
+// ----------------------------------------------------------
+// Constant declarations
+// ----------------------------------------------------------
+
+#define INPUT_BUF_SIZE 4096 // choose an efficiently fread'able size
+#define OUTPUT_BUF_SIZE 4096 // choose an efficiently fwrite'able size
+
+#define EXIF_MARKER (JPEG_APP0+1) // EXIF marker / Adobe XMP marker
+#define ICC_MARKER (JPEG_APP0+2) // ICC profile marker
+#define IPTC_MARKER (JPEG_APP0+13) // IPTC marker / BIM marker
+
+#define ICC_HEADER_SIZE 14 // size of non-profile data in APP2
+#define MAX_BYTES_IN_MARKER 65533L // maximum data length of a JPEG marker
+#define MAX_DATA_BYTES_IN_MARKER 65519L // maximum data length of a JPEG APP2 marker
+
+#define MAX_JFXX_THUMB_SIZE (MAX_BYTES_IN_MARKER - 5 - 1)
+
+#define JFXX_TYPE_JPEG 0x10 // JFIF extension marker: JPEG-compressed thumbnail image
+#define JFXX_TYPE_8bit 0x11 // JFIF extension marker: palette thumbnail image
+#define JFXX_TYPE_24bit 0x13 // JFIF extension marker: RGB thumbnail image
+
+// ----------------------------------------------------------
+// Typedef declarations
+// ----------------------------------------------------------
+
+typedef struct tagErrorManager {
+ /// "public" fields
+ struct jpeg_error_mgr pub;
+ /// for return to caller
+ jmp_buf setjmp_buffer;
+} ErrorManager;
+
+typedef struct tagSourceManager {
+ /// public fields
+ struct jpeg_source_mgr pub;
+ /// source stream
+ fi_handle infile;
+ FreeImageIO *m_io;
+ /// start of buffer
+ JOCTET * buffer;
+ /// have we gotten any data yet ?
+ boolean start_of_file;
+} SourceManager;
+
+typedef struct tagDestinationManager {
+ /// public fields
+ struct jpeg_destination_mgr pub;
+ /// destination stream
+ fi_handle outfile;
+ FreeImageIO *m_io;
+ /// start of buffer
+ JOCTET * buffer;
+} DestinationManager;
+
+typedef SourceManager* freeimage_src_ptr;
+typedef DestinationManager* freeimage_dst_ptr;
+typedef ErrorManager* freeimage_error_ptr;
+
+// ----------------------------------------------------------
+// Error handling
+// ----------------------------------------------------------
+
+/** Fatal errors (print message and exit) */
+static inline void
+JPEG_EXIT(j_common_ptr cinfo, int code) {
+ freeimage_error_ptr error_ptr = (freeimage_error_ptr)cinfo->err;
+ error_ptr->pub.msg_code = code;
+ error_ptr->pub.error_exit(cinfo);
+}
+
+/** Nonfatal errors (we can keep going, but the data is probably corrupt) */
+static inline void
+JPEG_WARNING(j_common_ptr cinfo, int code) {
+ freeimage_error_ptr error_ptr = (freeimage_error_ptr)cinfo->err;
+ error_ptr->pub.msg_code = code;
+ error_ptr->pub.emit_message(cinfo, -1);
+}
+
+/**
+ Receives control for a fatal error. Information sufficient to
+ generate the error message has been stored in cinfo->err; call
+ output_message to display it. Control must NOT return to the caller;
+ generally this routine will exit() or longjmp() somewhere.
+*/
+METHODDEF(void)
+jpeg_error_exit (j_common_ptr cinfo) {
+ freeimage_error_ptr error_ptr = (freeimage_error_ptr)cinfo->err;
+
+ // always display the message
+ error_ptr->pub.output_message(cinfo);
+
+ // allow JPEG with unknown markers
+ if(error_ptr->pub.msg_code != JERR_UNKNOWN_MARKER) {
+
+ // let the memory manager delete any temp files before we die
+ jpeg_destroy(cinfo);
+
+ // return control to the setjmp point
+ longjmp(error_ptr->setjmp_buffer, 1);
+ }
+}
+
+/**
+ Actual output of any JPEG message. Note that this method does not know
+ how to generate a message, only where to send it.
+*/
+METHODDEF(void)
+jpeg_output_message (j_common_ptr cinfo) {
+ char buffer[JMSG_LENGTH_MAX];
+ freeimage_error_ptr error_ptr = (freeimage_error_ptr)cinfo->err;
+
+ // create the message
+ error_ptr->pub.format_message(cinfo, buffer);
+ // send it to user's message proc
+ FreeImage_OutputMessageProc(s_format_id, buffer);
+}
+
+// ----------------------------------------------------------
+// Destination manager
+// ----------------------------------------------------------
+
+/**
+ Initialize destination. This is called by jpeg_start_compress()
+ before any data is actually written. It must initialize
+ next_output_byte and free_in_buffer. free_in_buffer must be
+ initialized to a positive value.
+*/
+METHODDEF(void)
+init_destination (j_compress_ptr cinfo) {
+ freeimage_dst_ptr dest = (freeimage_dst_ptr) cinfo->dest;
+
+ dest->buffer = (JOCTET *)
+ (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
+ OUTPUT_BUF_SIZE * sizeof(JOCTET));
+
+ dest->pub.next_output_byte = dest->buffer;
+ dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
+}
+
+/**
+ This is called whenever the buffer has filled (free_in_buffer
+ reaches zero). In typical applications, it should write out the
+ *entire* buffer (use the saved start address and buffer length;
+ ignore the current state of next_output_byte and free_in_buffer).
+ Then reset the pointer & count to the start of the buffer, and
+ return TRUE indicating that the buffer has been dumped.
+ free_in_buffer must be set to a positive value when TRUE is
+ returned. A FALSE return should only be used when I/O suspension is
+ desired.
+*/
+METHODDEF(boolean)
+empty_output_buffer (j_compress_ptr cinfo) {
+ freeimage_dst_ptr dest = (freeimage_dst_ptr) cinfo->dest;
+
+ if (dest->m_io->write_proc(dest->buffer, 1, OUTPUT_BUF_SIZE, dest->outfile) != OUTPUT_BUF_SIZE) {
+ // let the memory manager delete any temp files before we die
+ jpeg_destroy((j_common_ptr)cinfo);
+
+ JPEG_EXIT((j_common_ptr)cinfo, JERR_FILE_WRITE);
+ }
+
+ dest->pub.next_output_byte = dest->buffer;
+ dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
+
+ return TRUE;
+}
+
+/**
+ Terminate destination --- called by jpeg_finish_compress() after all
+ data has been written. In most applications, this must flush any
+ data remaining in the buffer. Use either next_output_byte or
+ free_in_buffer to determine how much data is in the buffer.
+*/
+METHODDEF(void)
+term_destination (j_compress_ptr cinfo) {
+ freeimage_dst_ptr dest = (freeimage_dst_ptr) cinfo->dest;
+
+ size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer;
+
+ // write any data remaining in the buffer
+
+ if (datacount > 0) {
+ if (dest->m_io->write_proc(dest->buffer, 1, (unsigned int)datacount, dest->outfile) != datacount) {
+ // let the memory manager delete any temp files before we die
+ jpeg_destroy((j_common_ptr)cinfo);
+
+ JPEG_EXIT((j_common_ptr)cinfo, JERR_FILE_WRITE);
+ }
+ }
+}
+
+// ----------------------------------------------------------
+// Source manager
+// ----------------------------------------------------------
+
+/**
+ Initialize source. This is called by jpeg_read_header() before any
+ data is actually read. Unlike init_destination(), it may leave
+ bytes_in_buffer set to 0 (in which case a fill_input_buffer() call
+ will occur immediately).
+*/
+METHODDEF(void)
+init_source (j_decompress_ptr cinfo) {
+ freeimage_src_ptr src = (freeimage_src_ptr) cinfo->src;
+
+ /* We reset the empty-input-file flag for each image,
+ * but we don't clear the input buffer.
+ * This is correct behavior for reading a series of images from one source.
+ */
+
+ src->start_of_file = TRUE;
+}
+
+/**
+ This is called whenever bytes_in_buffer has reached zero and more
+ data is wanted. In typical applications, it should read fresh data
+ into the buffer (ignoring the current state of next_input_byte and
+ bytes_in_buffer), reset the pointer & count to the start of the
+ buffer, and return TRUE indicating that the buffer has been reloaded.
+ It is not necessary to fill the buffer entirely, only to obtain at
+ least one more byte. bytes_in_buffer MUST be set to a positive value
+ if TRUE is returned. A FALSE return should only be used when I/O
+ suspension is desired.
+*/
+METHODDEF(boolean)
+fill_input_buffer (j_decompress_ptr cinfo) {
+ freeimage_src_ptr src = (freeimage_src_ptr) cinfo->src;
+
+ size_t nbytes = src->m_io->read_proc(src->buffer, 1, INPUT_BUF_SIZE, src->infile);
+
+ if (nbytes <= 0) {
+ if (src->start_of_file) {
+ // treat empty input file as fatal error
+
+ // let the memory manager delete any temp files before we die
+ jpeg_destroy((j_common_ptr)cinfo);
+
+ JPEG_EXIT((j_common_ptr)cinfo, JERR_INPUT_EMPTY);
+ }
+
+ JPEG_WARNING((j_common_ptr)cinfo, JWRN_JPEG_EOF);
+
+ /* Insert a fake EOI marker */
+
+ src->buffer[0] = (JOCTET) 0xFF;
+ src->buffer[1] = (JOCTET) JPEG_EOI;
+
+ nbytes = 2;
+ }
+
+ src->pub.next_input_byte = src->buffer;
+ src->pub.bytes_in_buffer = nbytes;
+ src->start_of_file = FALSE;
+
+ return TRUE;
+}
+
+/**
+ Skip num_bytes worth of data. The buffer pointer and count should
+ be advanced over num_bytes input bytes, refilling the buffer as
+ needed. This is used to skip over a potentially large amount of
+ uninteresting data (such as an APPn marker). In some applications
+ it may be possible to optimize away the reading of the skipped data,
+ but it's not clear that being smart is worth much trouble; large
+ skips are uncommon. bytes_in_buffer may be zero on return.
+ A zero or negative skip count should be treated as a no-op.
+*/
+METHODDEF(void)
+skip_input_data (j_decompress_ptr cinfo, long num_bytes) {
+ freeimage_src_ptr src = (freeimage_src_ptr) cinfo->src;
+
+ /* Just a dumb implementation for now. Could use fseek() except
+ * it doesn't work on pipes. Not clear that being smart is worth
+ * any trouble anyway --- large skips are infrequent.
+ */
+
+ if (num_bytes > 0) {
+ while (num_bytes > (long) src->pub.bytes_in_buffer) {
+ num_bytes -= (long) src->pub.bytes_in_buffer;
+
+ (void) fill_input_buffer(cinfo);
+
+ /* note we assume that fill_input_buffer will never return FALSE,
+ * so suspension need not be handled.
+ */
+ }
+
+ src->pub.next_input_byte += (size_t) num_bytes;
+ src->pub.bytes_in_buffer -= (size_t) num_bytes;
+ }
+}
+
+/**
+ Terminate source --- called by jpeg_finish_decompress
+ after all data has been read. Often a no-op.
+
+ NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
+ application must deal with any cleanup that should happen even
+ for error exit.
+*/
+METHODDEF(void)
+term_source (j_decompress_ptr cinfo) {
+ // no work necessary here
+}
+
+// ----------------------------------------------------------
+// Source manager & Destination manager setup
+// ----------------------------------------------------------
+
+/**
+ Prepare for input from a stdio stream.
+ The caller must have already opened the stream, and is responsible
+ for closing it after finishing decompression.
+*/
+GLOBAL(void)
+jpeg_freeimage_src (j_decompress_ptr cinfo, fi_handle infile, FreeImageIO *io) {
+ freeimage_src_ptr src;
+
+ // allocate memory for the buffer. is released automatically in the end
+
+ if (cinfo->src == NULL) {
+ cinfo->src = (struct jpeg_source_mgr *) (*cinfo->mem->alloc_small)
+ ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(SourceManager));
+
+ src = (freeimage_src_ptr) cinfo->src;
+
+ src->buffer = (JOCTET *) (*cinfo->mem->alloc_small)
+ ((j_common_ptr) cinfo, JPOOL_PERMANENT, INPUT_BUF_SIZE * sizeof(JOCTET));
+ }
+
+ // initialize the jpeg pointer struct with pointers to functions
+
+ src = (freeimage_src_ptr) cinfo->src;
+ src->pub.init_source = init_source;
+ src->pub.fill_input_buffer = fill_input_buffer;
+ src->pub.skip_input_data = skip_input_data;
+ src->pub.resync_to_restart = jpeg_resync_to_restart; // use default method
+ src->pub.term_source = term_source;
+ src->infile = infile;
+ src->m_io = io;
+ src->pub.bytes_in_buffer = 0; // forces fill_input_buffer on first read
+ src->pub.next_input_byte = NULL; // until buffer loaded
+}
+
+/**
+ Prepare for output to a stdio stream.
+ The caller must have already opened the stream, and is responsible
+ for closing it after finishing compression.
+*/
+GLOBAL(void)
+jpeg_freeimage_dst (j_compress_ptr cinfo, fi_handle outfile, FreeImageIO *io) {
+ freeimage_dst_ptr dest;
+
+ if (cinfo->dest == NULL) {
+ cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small)
+ ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(DestinationManager));
+ }
+
+ dest = (freeimage_dst_ptr) cinfo->dest;
+ dest->pub.init_destination = init_destination;
+ dest->pub.empty_output_buffer = empty_output_buffer;
+ dest->pub.term_destination = term_destination;
+ dest->outfile = outfile;
+ dest->m_io = io;
+}
+
+// ----------------------------------------------------------
+// Special markers read functions
+// ----------------------------------------------------------
+
+/**
+ Read JPEG_COM marker (comment)
+*/
+static BOOL
+jpeg_read_comment(FIBITMAP *dib, const BYTE *dataptr, unsigned int datalen) {
+ size_t length = datalen;
+ BYTE *profile = (BYTE*)dataptr;
+
+ // read the comment
+ char *value = (char*)malloc((length + 1) * sizeof(char));
+ if(value == NULL) return FALSE;
+ memcpy(value, profile, length);
+ value[length] = '\0';
+
+ // create a tag
+ FITAG *tag = FreeImage_CreateTag();
+ if(tag) {
+ unsigned int count = (unsigned int)length + 1; // includes the null value
+
+ FreeImage_SetTagID(tag, JPEG_COM);
+ FreeImage_SetTagKey(tag, "Comment");
+ FreeImage_SetTagLength(tag, count);
+ FreeImage_SetTagCount(tag, count);
+ FreeImage_SetTagType(tag, FIDT_ASCII);
+ FreeImage_SetTagValue(tag, value);
+
+ // store the tag
+ FreeImage_SetMetadata(FIMD_COMMENTS, dib, FreeImage_GetTagKey(tag), tag);
+
+ // destroy the tag
+ FreeImage_DeleteTag(tag);
+ }
+
+ free(value);
+
+ return TRUE;
+}
+
+/**
+ Read JPEG_APP2 marker (ICC profile)
+*/
+
+/**
+Handy subroutine to test whether a saved marker is an ICC profile marker.
+*/
+static BOOL
+marker_is_icc(jpeg_saved_marker_ptr marker) {
+ // marker identifying string "ICC_PROFILE" (null-terminated)
+ const BYTE icc_signature[12] = { 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00 };
+
+ if(marker->marker == ICC_MARKER) {
+ // verify the identifying string
+ if(marker->data_length >= ICC_HEADER_SIZE) {
+ if(memcmp(icc_signature, marker->data, sizeof(icc_signature)) == 0) {
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ 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 has been 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
+ at jpeg_finish_decompress() time. But it seems likely that many apps
+ will prefer to have the data stick around after decompression finishes.)
+
+ NOTE: if the file contains invalid ICC APP2 markers, we just silently
+ return FALSE. You might want to issue an error message instead.
+*/
+static BOOL
+jpeg_read_icc_profile(j_decompress_ptr cinfo, JOCTET **icc_data_ptr, unsigned *icc_data_len) {
+ jpeg_saved_marker_ptr marker;
+ int num_markers = 0;
+ int seq_no;
+ JOCTET *icc_data;
+ unsigned total_length;
+
+ const int MAX_SEQ_NO = 255; // sufficient since marker numbers are bytes
+ BYTE marker_present[MAX_SEQ_NO+1]; // 1 if marker found
+ unsigned data_length[MAX_SEQ_NO+1]; // size of profile data in marker
+ unsigned data_offset[MAX_SEQ_NO+1]; // offset for data in marker
+
+ *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.
+ */
+
+ memset(marker_present, 0, (MAX_SEQ_NO + 1));
+
+ for(marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
+ if (marker_is_icc(marker)) {
+ if (num_markers == 0) {
+ // number of markers
+ num_markers = GETJOCTET(marker->data[13]);
+ }
+ else if (num_markers != GETJOCTET(marker->data[13])) {
+ return FALSE; // inconsistent num_markers fields
+ }
+ // sequence number
+ seq_no = GETJOCTET(marker->data[12]);
+ if (seq_no <= 0 || seq_no > num_markers) {
+ return FALSE; // bogus sequence number
+ }
+ if (marker_present[seq_no]) {
+ return FALSE; // duplicate sequence numbers
+ }
+ marker_present[seq_no] = 1;
+ data_length[seq_no] = marker->data_length - ICC_HEADER_SIZE;
+ }
+ }
+
+ 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) {
+ return FALSE; // missing sequence number
+ }
+ data_offset[seq_no] = total_length;
+ total_length += data_length[seq_no];
+ }
+
+ if (total_length <= 0)
+ return FALSE; // found only empty markers ?
+
+ // allocate space for assembled data
+ icc_data = (JOCTET *) malloc(total_length * sizeof(JOCTET));
+ if (icc_data == NULL)
+ return FALSE; // 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 length;
+ seq_no = GETJOCTET(marker->data[12]);
+ dst_ptr = icc_data + data_offset[seq_no];
+ src_ptr = marker->data + ICC_HEADER_SIZE;
+ length = data_length[seq_no];
+ while (length--) {
+ *dst_ptr++ = *src_ptr++;
+ }
+ }
+ }
+
+ *icc_data_ptr = icc_data;
+ *icc_data_len = total_length;
+
+ return TRUE;
+}
+
+/**
+ Read JPEG_APPD marker (IPTC or Adobe Photoshop profile)
+*/
+static BOOL
+jpeg_read_iptc_profile(FIBITMAP *dib, const BYTE *dataptr, unsigned int datalen) {
+ return read_iptc_profile(dib, dataptr, datalen);
+}
+
+/**
+ Read JPEG_APP1 marker (XMP profile)
+ @param dib Input FIBITMAP
+ @param dataptr Pointer to the APP1 marker
+ @param datalen APP1 marker length
+ @return Returns TRUE if successful, FALSE otherwise
+*/
+static BOOL
+jpeg_read_xmp_profile(FIBITMAP *dib, const BYTE *dataptr, unsigned int datalen) {
+ // marker identifying string for XMP (null terminated)
+ const char *xmp_signature = "http://ns.adobe.com/xap/1.0/";
+ // XMP signature is 29 bytes long
+ const size_t xmp_signature_size = strlen(xmp_signature) + 1;
+
+ size_t length = datalen;
+ BYTE *profile = (BYTE*)dataptr;
+
+ if(length <= xmp_signature_size) {
+ // avoid reading corrupted or empty data
+ return FALSE;
+ }
+
+ // verify the identifying string
+
+ if(memcmp(xmp_signature, profile, strlen(xmp_signature)) == 0) {
+ // XMP profile
+
+ profile += xmp_signature_size;
+ length -= xmp_signature_size;
+
+ // create a tag
+ FITAG *tag = FreeImage_CreateTag();
+ if(tag) {
+ FreeImage_SetTagID(tag, JPEG_APP0+1); // 0xFFE1
+ FreeImage_SetTagKey(tag, g_TagLib_XMPFieldName);
+ FreeImage_SetTagLength(tag, (DWORD)length);
+ FreeImage_SetTagCount(tag, (DWORD)length);
+ FreeImage_SetTagType(tag, FIDT_ASCII);
+ FreeImage_SetTagValue(tag, profile);
+
+ // store the tag
+ FreeImage_SetMetadata(FIMD_XMP, dib, FreeImage_GetTagKey(tag), tag);
+
+ // destroy the tag
+ FreeImage_DeleteTag(tag);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ Read JPEG_APP1 marker (Exif profile)
+ @param dib Input FIBITMAP
+ @param dataptr Pointer to the APP1 marker
+ @param datalen APP1 marker length
+ @return Returns TRUE if successful, FALSE otherwise
+*/
+static BOOL
+jpeg_read_exif_profile_raw(FIBITMAP *dib, const BYTE *profile, unsigned int length) {
+ // marker identifying string for Exif = "Exif\0\0"
+ BYTE exif_signature[6] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 };
+
+ // verify the identifying string
+ if(memcmp(exif_signature, profile, sizeof(exif_signature)) != 0) {
+ // not an Exif profile
+ return FALSE;
+ }
+
+ // create a tag
+ FITAG *tag = FreeImage_CreateTag();
+ if(tag) {
+ FreeImage_SetTagID(tag, EXIF_MARKER); // (JPEG_APP0 + 1) => EXIF marker / Adobe XMP marker
+ FreeImage_SetTagKey(tag, g_TagLib_ExifRawFieldName);
+ FreeImage_SetTagLength(tag, (DWORD)length);
+ FreeImage_SetTagCount(tag, (DWORD)length);
+ FreeImage_SetTagType(tag, FIDT_BYTE);
+ FreeImage_SetTagValue(tag, profile);
+
+ // store the tag
+ FreeImage_SetMetadata(FIMD_EXIF_RAW, dib, FreeImage_GetTagKey(tag), tag);
+
+ // destroy the tag
+ FreeImage_DeleteTag(tag);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ Read JFIF "JFXX" extension APP0 marker
+ @param dib Input FIBITMAP
+ @param dataptr Pointer to the APP0 marker
+ @param datalen APP0 marker length
+ @return Returns TRUE if successful, FALSE otherwise
+*/
+static BOOL
+jpeg_read_jfxx(FIBITMAP *dib, const BYTE *dataptr, unsigned int datalen) {
+ if(datalen < 6) {
+ return FALSE;
+ }
+
+ const int id_length = 5;
+ const BYTE *data = dataptr + id_length;
+ unsigned remaining = datalen - id_length;
+
+ const BYTE type = *data;
+ ++data, --remaining;
+
+ switch(type) {
+ case JFXX_TYPE_JPEG:
+ {
+ // load the thumbnail
+ FIMEMORY* hmem = FreeImage_OpenMemory(const_cast<BYTE*>(data), remaining);
+ FIBITMAP* thumbnail = FreeImage_LoadFromMemory(FIF_JPEG, hmem);
+ FreeImage_CloseMemory(hmem);
+ // store the thumbnail
+ FreeImage_SetThumbnail(dib, thumbnail);
+ // then delete it
+ FreeImage_Unload(thumbnail);
+ break;
+ }
+ case JFXX_TYPE_8bit:
+ // colormapped uncompressed thumbnail (no supported)
+ break;
+ case JFXX_TYPE_24bit:
+ // truecolor uncompressed thumbnail (no supported)
+ break;
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+
+/**
+ Read JPEG special markers
+*/
+static BOOL
+read_markers(j_decompress_ptr cinfo, FIBITMAP *dib) {
+ jpeg_saved_marker_ptr marker;
+
+ for(marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
+ switch(marker->marker) {
+ case JPEG_APP0:
+ // JFIF is handled by libjpeg already, handle JFXX
+ if(memcmp(marker->data, "JFIF" , 5) == 0) {
+ continue;
+ }
+ if(memcmp(marker->data, "JFXX" , 5) == 0) {
+ if (!cinfo->saw_JFIF_marker || cinfo->JFIF_minor_version < 2) {
+ FreeImage_OutputMessageProc(s_format_id, "Warning: non-standard JFXX segment");
+ }
+ jpeg_read_jfxx(dib, marker->data, marker->data_length);
+ }
+ // other values such as 'Picasa' : ignore safely unknown APP0 marker
+ break;
+ case JPEG_COM:
+ // JPEG comment
+ jpeg_read_comment(dib, marker->data, marker->data_length);
+ break;
+ case EXIF_MARKER:
+ // Exif or Adobe XMP profile
+ jpeg_read_exif_profile(dib, marker->data, marker->data_length);
+ jpeg_read_xmp_profile(dib, marker->data, marker->data_length);
+ jpeg_read_exif_profile_raw(dib, marker->data, marker->data_length);
+ break;
+ case IPTC_MARKER:
+ // IPTC/NAA or Adobe Photoshop profile
+ jpeg_read_iptc_profile(dib, marker->data, marker->data_length);
+ break;
+ }
+ }
+
+ // ICC profile
+ BYTE *icc_profile = NULL;
+ unsigned icc_length = 0;
+
+ if ( jpeg_read_icc_profile(cinfo, &icc_profile, &icc_length)) {
+ // copy ICC profile data
+ FreeImage_CreateICCProfile(dib, icc_profile, icc_length);
+ // clean up
+ free(icc_profile);
+ }
+
+ return TRUE;
+}
+
+// ----------------------------------------------------------
+// Special markers write functions
+// ----------------------------------------------------------
+
+/**
+ Write JPEG_COM marker (comment)
+*/
+static BOOL
+jpeg_write_comment(j_compress_ptr cinfo, FIBITMAP *dib) {
+ FITAG *tag = NULL;
+
+ // write user comment as a JPEG_COM marker
+ FreeImage_GetMetadata(FIMD_COMMENTS, dib, "Comment", &tag);
+ if(tag) {
+ const char *tag_value = (char*)FreeImage_GetTagValue(tag);
+
+ if(NULL != tag_value) {
+ for(long i = 0; i < (long)strlen(tag_value); i+= MAX_BYTES_IN_MARKER) {
+ jpeg_write_marker(cinfo, JPEG_COM, (BYTE*)tag_value + i, MIN((long)strlen(tag_value + i), MAX_BYTES_IN_MARKER));
+ }
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ Write JPEG_APP2 marker (ICC profile)
+*/
+static BOOL
+jpeg_write_icc_profile(j_compress_ptr cinfo, FIBITMAP *dib) {
+ // marker identifying string "ICC_PROFILE" (null-terminated)
+ BYTE icc_signature[12] = { 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00 };
+
+ FIICCPROFILE *iccProfile = FreeImage_GetICCProfile(dib);
+
+ if (iccProfile->size && iccProfile->data) {
+ // ICC_HEADER_SIZE: ICC signature is 'ICC_PROFILE' + 2 bytes
+
+ BYTE *profile = (BYTE*)malloc((iccProfile->size + ICC_HEADER_SIZE) * sizeof(BYTE));
+ if(profile == NULL) return FALSE;
+ memcpy(profile, icc_signature, 12);
+
+ for(long i = 0; i < (long)iccProfile->size; i += MAX_DATA_BYTES_IN_MARKER) {
+ unsigned length = MIN((long)(iccProfile->size - i), MAX_DATA_BYTES_IN_MARKER);
+ // sequence number
+ profile[12] = (BYTE) ((i / MAX_DATA_BYTES_IN_MARKER) + 1);
+ // number of markers
+ profile[13] = (BYTE) (iccProfile->size / MAX_DATA_BYTES_IN_MARKER + 1);
+
+ memcpy(profile + ICC_HEADER_SIZE, (BYTE*)iccProfile->data + i, length);
+ jpeg_write_marker(cinfo, ICC_MARKER, profile, (length + ICC_HEADER_SIZE));
+ }
+
+ free(profile);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ Write JPEG_APPD marker (IPTC or Adobe Photoshop profile)
+ @return Returns TRUE if successful, FALSE otherwise
+*/
+static BOOL
+jpeg_write_iptc_profile(j_compress_ptr cinfo, FIBITMAP *dib) {
+ //const char *ps_header = "Photoshop 3.0\x08BIM\x04\x04\x0\x0\x0\x0";
+ const unsigned tag_length = 26;
+
+ if(FreeImage_GetMetadataCount(FIMD_IPTC, dib)) {
+ BYTE *profile = NULL;
+ unsigned profile_size = 0;
+
+ // create a binary profile
+ if(write_iptc_profile(dib, &profile, &profile_size)) {
+
+ // write the profile
+ for(long i = 0; i < (long)profile_size; i += 65517L) {
+ unsigned length = MIN((long)profile_size - i, 65517L);
+ unsigned roundup = length & 0x01; // needed for Photoshop
+ BYTE *iptc_profile = (BYTE*)malloc(length + roundup + tag_length);
+ if(iptc_profile == NULL) break;
+ // Photoshop identification string
+ memcpy(&iptc_profile[0], "Photoshop 3.0\x0", 14);
+ // 8BIM segment type
+ memcpy(&iptc_profile[14], "8BIM\x04\x04\x0\x0\x0\x0", 10);
+ // segment size
+ iptc_profile[24] = (BYTE)(length >> 8);
+ iptc_profile[25] = (BYTE)(length & 0xFF);
+ // segment data
+ memcpy(&iptc_profile[tag_length], &profile[i], length);
+ if(roundup)
+ iptc_profile[length + tag_length] = 0;
+ jpeg_write_marker(cinfo, IPTC_MARKER, iptc_profile, length + roundup + tag_length);
+ free(iptc_profile);
+ }
+
+ // release profile
+ free(profile);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ Write JPEG_APP1 marker (XMP profile)
+ @return Returns TRUE if successful, FALSE otherwise
+*/
+static BOOL
+jpeg_write_xmp_profile(j_compress_ptr cinfo, FIBITMAP *dib) {
+ // marker identifying string for XMP (null terminated)
+ const char *xmp_signature = "http://ns.adobe.com/xap/1.0/";
+
+ FITAG *tag_xmp = NULL;
+ FreeImage_GetMetadata(FIMD_XMP, dib, g_TagLib_XMPFieldName, &tag_xmp);
+
+ if(tag_xmp) {
+ const BYTE *tag_value = (BYTE*)FreeImage_GetTagValue(tag_xmp);
+
+ if(NULL != tag_value) {
+ // XMP signature is 29 bytes long
+ unsigned int xmp_header_size = (unsigned int)strlen(xmp_signature) + 1;
+
+ DWORD tag_length = FreeImage_GetTagLength(tag_xmp);
+
+ BYTE *profile = (BYTE*)malloc((tag_length + xmp_header_size) * sizeof(BYTE));
+ if(profile == NULL) return FALSE;
+ memcpy(profile, xmp_signature, xmp_header_size);
+
+ for(DWORD i = 0; i < tag_length; i += 65504L) {
+ unsigned length = MIN((long)(tag_length - i), 65504L);
+
+ memcpy(profile + xmp_header_size, tag_value + i, length);
+ jpeg_write_marker(cinfo, EXIF_MARKER, profile, (length + xmp_header_size));
+ }
+
+ free(profile);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ Write JPEG_APP1 marker (Exif profile)
+ @return Returns TRUE if successful, FALSE otherwise
+*/
+static BOOL
+jpeg_write_exif_profile_raw(j_compress_ptr cinfo, FIBITMAP *dib) {
+ // marker identifying string for Exif = "Exif\0\0"
+ BYTE exif_signature[6] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 };
+
+ FITAG *tag_exif = NULL;
+ FreeImage_GetMetadata(FIMD_EXIF_RAW, dib, g_TagLib_ExifRawFieldName, &tag_exif);
+
+ if(tag_exif) {
+ const BYTE *tag_value = (BYTE*)FreeImage_GetTagValue(tag_exif);
+
+ // verify the identifying string
+ if(memcmp(exif_signature, tag_value, sizeof(exif_signature)) != 0) {
+ // not an Exif profile
+ return FALSE;
+ }
+
+ if(NULL != tag_value) {
+ DWORD tag_length = FreeImage_GetTagLength(tag_exif);
+
+ BYTE *profile = (BYTE*)malloc(tag_length * sizeof(BYTE));
+ if(profile == NULL) return FALSE;
+
+ for(DWORD i = 0; i < tag_length; i += 65504L) {
+ unsigned length = MIN((long)(tag_length - i), 65504L);
+
+ memcpy(profile, tag_value + i, length);
+ jpeg_write_marker(cinfo, EXIF_MARKER, profile, length);
+ }
+
+ free(profile);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ Write thumbnail (JFXX segment, JPEG compressed)
+*/
+static BOOL
+jpeg_write_jfxx(j_compress_ptr cinfo, FIBITMAP *dib) {
+ // get the thumbnail to be stored
+ FIBITMAP* thumbnail = FreeImage_GetThumbnail(dib);
+ if (!thumbnail) {
+ return TRUE;
+ }
+ // check for a compatible output format
+ if ((FreeImage_GetImageType(thumbnail) != FIT_BITMAP) || (FreeImage_GetBPP(thumbnail) != 8) && (FreeImage_GetBPP(thumbnail) != 24)) {
+ FreeImage_OutputMessageProc(s_format_id, FI_MSG_WARNING_INVALID_THUMBNAIL);
+ return FALSE;
+ }
+
+ // stores the thumbnail as a baseline JPEG into a memory block
+ // return the memory block only if its size is within JFXX marker size limit!
+ FIMEMORY *stream = FreeImage_OpenMemory();
+
+ if(FreeImage_SaveToMemory(FIF_JPEG, thumbnail, stream, JPEG_BASELINE)) {
+ // check that the memory block size is within JFXX marker size limit
+ FreeImage_SeekMemory(stream, 0, SEEK_END);
+ const long eof = FreeImage_TellMemory(stream);
+ if(eof > MAX_JFXX_THUMB_SIZE) {
+ FreeImage_OutputMessageProc(s_format_id, "Warning: attached thumbnail is %d bytes larger than maximum supported size - Thumbnail saving aborted", eof - MAX_JFXX_THUMB_SIZE);
+ FreeImage_CloseMemory(stream);
+ return FALSE;
+ }
+ } else {
+ FreeImage_CloseMemory(stream);
+ return FALSE;
+ }
+
+ BYTE* thData = NULL;
+ DWORD thSize = 0;
+
+ FreeImage_AcquireMemory(stream, &thData, &thSize);
+
+ BYTE id_length = 5; //< "JFXX"
+ BYTE type = JFXX_TYPE_JPEG;
+
+ DWORD totalsize = id_length + sizeof(type) + thSize;
+ jpeg_write_m_header(cinfo, JPEG_APP0, totalsize);
+
+ jpeg_write_m_byte(cinfo, 'J');
+ jpeg_write_m_byte(cinfo, 'F');
+ jpeg_write_m_byte(cinfo, 'X');
+ jpeg_write_m_byte(cinfo, 'X');
+ jpeg_write_m_byte(cinfo, '\0');
+
+ jpeg_write_m_byte(cinfo, type);
+
+ // write thumbnail to destination.
+ // We "cram it straight into the data destination module", because write_m_byte is slow
+
+ freeimage_dst_ptr dest = (freeimage_dst_ptr) cinfo->dest;
+
+ BYTE* & out = dest->pub.next_output_byte;
+ size_t & bufRemain = dest->pub.free_in_buffer;
+
+ const BYTE *thData_end = thData + thSize;
+
+ while(thData < thData_end) {
+ *(out)++ = *(thData)++;
+ if (--bufRemain == 0) {
+ // buffer full - flush
+ if (!dest->pub.empty_output_buffer(cinfo)) {
+ break;
+ }
+ }
+ }
+
+ FreeImage_CloseMemory(stream);
+
+ return TRUE;
+}
+
+/**
+ Write JPEG special markers
+*/
+static BOOL
+write_markers(j_compress_ptr cinfo, FIBITMAP *dib) {
+ // write thumbnail as a JFXX marker
+ jpeg_write_jfxx(cinfo, dib);
+
+ // write user comment as a JPEG_COM marker
+ jpeg_write_comment(cinfo, dib);
+
+ // write ICC profile
+ jpeg_write_icc_profile(cinfo, dib);
+
+ // write IPTC profile
+ jpeg_write_iptc_profile(cinfo, dib);
+
+ // write Adobe XMP profile
+ jpeg_write_xmp_profile(cinfo, dib);
+
+ // write Exif raw data
+ jpeg_write_exif_profile_raw(cinfo, dib);
+
+ return TRUE;
+}
+
+// ------------------------------------------------------------
+// Keep original size info when using scale option on loading
+// ------------------------------------------------------------
+static void
+store_size_info(FIBITMAP *dib, JDIMENSION width, JDIMENSION height) {
+ char buffer[256];
+ // create a tag
+ FITAG *tag = FreeImage_CreateTag();
+ if(tag) {
+ size_t length = 0;
+ // set the original width
+ sprintf(buffer, "%d", (int)width);
+ length = strlen(buffer) + 1; // include the NULL/0 value
+ FreeImage_SetTagKey(tag, "OriginalJPEGWidth");
+ FreeImage_SetTagLength(tag, (DWORD)length);
+ FreeImage_SetTagCount(tag, (DWORD)length);
+ FreeImage_SetTagType(tag, FIDT_ASCII);
+ FreeImage_SetTagValue(tag, buffer);
+ FreeImage_SetMetadata(FIMD_COMMENTS, dib, FreeImage_GetTagKey(tag), tag);
+ // set the original height
+ sprintf(buffer, "%d", (int)height);
+ length = strlen(buffer) + 1; // include the NULL/0 value
+ FreeImage_SetTagKey(tag, "OriginalJPEGHeight");
+ FreeImage_SetTagLength(tag, (DWORD)length);
+ FreeImage_SetTagCount(tag, (DWORD)length);
+ FreeImage_SetTagType(tag, FIDT_ASCII);
+ FreeImage_SetTagValue(tag, buffer);
+ FreeImage_SetMetadata(FIMD_COMMENTS, dib, FreeImage_GetTagKey(tag), tag);
+ // destroy the tag
+ FreeImage_DeleteTag(tag);
+ }
+}
+
+// ------------------------------------------------------------
+// Rotate a dib according to Exif info
+// ------------------------------------------------------------
+
+static void
+rotate_exif(FIBITMAP **dib) {
+ // check for Exif rotation
+ if(FreeImage_GetMetadataCount(FIMD_EXIF_MAIN, *dib)) {
+ FIBITMAP *rotated = NULL;
+ // process Exif rotation
+ FITAG *tag = NULL;
+ FreeImage_GetMetadata(FIMD_EXIF_MAIN, *dib, "Orientation", &tag);
+ if(tag != NULL) {
+ if(FreeImage_GetTagID(tag) == TAG_ORIENTATION) {
+ unsigned short orientation = *((unsigned short *)FreeImage_GetTagValue(tag));
+ switch (orientation) {
+ case 1: // "top, left side" => 0°
+ break;
+ case 2: // "top, right side" => flip left-right
+ FreeImage_FlipHorizontal(*dib);
+ break;
+ case 3: // "bottom, right side"; => -180°
+ rotated = FreeImage_Rotate(*dib, 180);
+ FreeImage_Unload(*dib);
+ *dib = rotated;
+ break;
+ case 4: // "bottom, left side" => flip up-down
+ FreeImage_FlipVertical(*dib);
+ break;
+ case 5: // "left side, top" => +90° + flip up-down
+ rotated = FreeImage_Rotate(*dib, 90);
+ FreeImage_Unload(*dib);
+ *dib = rotated;
+ FreeImage_FlipVertical(*dib);
+ break;
+ case 6: // "right side, top" => -90°
+ rotated = FreeImage_Rotate(*dib, -90);
+ FreeImage_Unload(*dib);
+ *dib = rotated;
+ break;
+ case 7: // "right side, bottom" => -90° + flip up-down
+ rotated = FreeImage_Rotate(*dib, -90);
+ FreeImage_Unload(*dib);
+ *dib = rotated;
+ FreeImage_FlipVertical(*dib);
+ break;
+ case 8: // "left side, bottom" => +90°
+ rotated = FreeImage_Rotate(*dib, 90);
+ FreeImage_Unload(*dib);
+ *dib = rotated;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+}
+
+
+// ==========================================================
+// Plugin Implementation
+// ==========================================================
+
+static const char * DLL_CALLCONV
+Format() {
+ return "JPEG";
+}
+
+static const char * DLL_CALLCONV
+Description() {
+ return "JPEG - JFIF Compliant";
+}
+
+static const char * DLL_CALLCONV
+Extension() {
+ return "jpg,jif,jpeg,jpe";
+}
+
+static const char * DLL_CALLCONV
+RegExpr() {
+ return "^\377\330\377";
+}
+
+static const char * DLL_CALLCONV
+MimeType() {
+ return "image/jpeg";
+}
+
+static BOOL DLL_CALLCONV
+Validate(FreeImageIO *io, fi_handle handle) {
+ BYTE jpeg_signature[] = { 0xFF, 0xD8 };
+ BYTE signature[2] = { 0, 0 };
+
+ io->read_proc(signature, 1, sizeof(jpeg_signature), handle);
+
+ return (memcmp(jpeg_signature, signature, sizeof(jpeg_signature)) == 0);
+}
+
+static BOOL DLL_CALLCONV
+SupportsExportDepth(int depth) {
+ return (
+ (depth == 8) ||
+ (depth == 24)
+ );
+}
+
+static BOOL DLL_CALLCONV
+SupportsExportType(FREE_IMAGE_TYPE type) {
+ return (type == FIT_BITMAP) ? TRUE : FALSE;
+}
+
+static BOOL DLL_CALLCONV
+SupportsICCProfiles() {
+ return TRUE;
+}
+
+static BOOL DLL_CALLCONV
+SupportsNoPixels() {
+ return TRUE;
+}
+
+// ----------------------------------------------------------
+
+static FIBITMAP * DLL_CALLCONV
+Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) {
+ if (handle) {
+ FIBITMAP *dib = NULL;
+
+ BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS;
+
+ // set up the jpeglib structures
+
+ struct jpeg_decompress_struct cinfo;
+ ErrorManager fi_error_mgr;
+
+ try {
+
+ // step 1: allocate and initialize JPEG decompression object
+
+ // we set up the normal JPEG error routines, then override error_exit & output_message
+ cinfo.err = jpeg_std_error(&fi_error_mgr.pub);
+ fi_error_mgr.pub.error_exit = jpeg_error_exit;
+ fi_error_mgr.pub.output_message = jpeg_output_message;
+
+ // establish the setjmp return context for jpeg_error_exit to use
+ if (setjmp(fi_error_mgr.setjmp_buffer)) {
+ // If we get here, the JPEG code has signaled an error.
+ // We need to clean up the JPEG object, close the input file, and return.
+ jpeg_destroy_decompress(&cinfo);
+ throw (const char*)NULL;
+ }
+
+ jpeg_create_decompress(&cinfo);
+
+ // step 2a: specify data source (eg, a handle)
+
+ jpeg_freeimage_src(&cinfo, handle, io);
+
+ // step 2b: save special markers for later reading
+
+ jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
+ for(int m = 0; m < 16; m++) {
+ jpeg_save_markers(&cinfo, JPEG_APP0 + m, 0xFFFF);
+ }
+
+ // step 3: read handle parameters with jpeg_read_header()
+
+ jpeg_read_header(&cinfo, TRUE);
+
+ // step 4: set parameters for decompression
+
+ unsigned int scale_denom = 1; // fraction by which to scale image
+ int requested_size = flags >> 16; // requested user size in pixels
+ if(requested_size > 0) {
+ // the JPEG codec can perform x2, x4 or x8 scaling on loading
+ // try to find the more appropriate scaling according to user's need
+ double scale = MAX((double)cinfo.image_width, (double)cinfo.image_height) / (double)requested_size;
+ if(scale >= 8) {
+ scale_denom = 8;
+ } else if(scale >= 4) {
+ scale_denom = 4;
+ } else if(scale >= 2) {
+ scale_denom = 2;
+ }
+ }
+ cinfo.scale_num = 1;
+ cinfo.scale_denom = scale_denom;
+
+ if ((flags & JPEG_ACCURATE) != JPEG_ACCURATE) {
+ cinfo.dct_method = JDCT_IFAST;
+ cinfo.do_fancy_upsampling = FALSE;
+ }
+
+ // step 5a: start decompressor and calculate output width and height
+
+ jpeg_start_decompress(&cinfo);
+
+ // step 5b: allocate dib and init header
+
+ if ((cinfo.num_components == 4) && (cinfo.out_color_space == JCS_CMYK)) {
+ // CMYK image
+ if ((flags & JPEG_CMYK) == JPEG_CMYK) {
+ // load as CMYK
+ dib = FreeImage_AllocateHeader(header_only, cinfo.output_width, cinfo.output_height, 32, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
+ if (!dib) throw FI_MSG_ERROR_DIB_MEMORY;
+ FreeImage_GetICCProfile(dib)->flags |= FIICC_COLOR_IS_CMYK;
+ } else {
+ // load as CMYK and convert to RGB
+ dib = FreeImage_AllocateHeader(header_only, cinfo.output_width, cinfo.output_height, 24, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
+ if (!dib) throw FI_MSG_ERROR_DIB_MEMORY;
+ }
+ } else {
+ // RGB or greyscale image
+ dib = FreeImage_AllocateHeader(header_only, cinfo.output_width, cinfo.output_height, 8 * cinfo.num_components, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
+ if (!dib) throw FI_MSG_ERROR_DIB_MEMORY;
+
+ if (cinfo.num_components == 1) {
+ // build a greyscale palette
+ RGBQUAD *colors = FreeImage_GetPalette(dib);
+
+ for (int i = 0; i < 256; i++) {
+ colors[i].rgbRed = (BYTE)i;
+ colors[i].rgbGreen = (BYTE)i;
+ colors[i].rgbBlue = (BYTE)i;
+ }
+ }
+ }
+ if(scale_denom != 1) {
+ // store original size info if a scaling was requested
+ store_size_info(dib, cinfo.image_width, cinfo.image_height);
+ }
+
+ // step 5c: handle metrices
+
+ if (cinfo.density_unit == 1) {
+ // dots/inch
+ FreeImage_SetDotsPerMeterX(dib, (unsigned) (((float)cinfo.X_density) / 0.0254000 + 0.5));
+ FreeImage_SetDotsPerMeterY(dib, (unsigned) (((float)cinfo.Y_density) / 0.0254000 + 0.5));
+ } else if (cinfo.density_unit == 2) {
+ // dots/cm
+ FreeImage_SetDotsPerMeterX(dib, (unsigned) (cinfo.X_density * 100));
+ FreeImage_SetDotsPerMeterY(dib, (unsigned) (cinfo.Y_density * 100));
+ }
+
+ // step 6: read special markers
+
+ read_markers(&cinfo, dib);
+
+ // --- header only mode => clean-up and return
+
+ if (header_only) {
+ // release JPEG decompression object
+ jpeg_destroy_decompress(&cinfo);
+ // return header data
+ return dib;
+ }
+
+ // step 7a: while (scan lines remain to be read) jpeg_read_scanlines(...);
+
+ if ((cinfo.out_color_space == JCS_CMYK) && ((flags & JPEG_CMYK) != JPEG_CMYK)) {
+ // convert from CMYK to RGB
+
+ JSAMPARRAY buffer; // output row buffer
+ unsigned row_stride; // physical row width in output buffer
+
+ // JSAMPLEs per row in output buffer
+ row_stride = cinfo.output_width * cinfo.output_components;
+ // make a one-row-high sample array that will go away when done with image
+ buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
+
+ while (cinfo.output_scanline < cinfo.output_height) {
+ JSAMPROW src = buffer[0];
+ JSAMPROW dst = FreeImage_GetScanLine(dib, cinfo.output_height - cinfo.output_scanline - 1);
+
+ jpeg_read_scanlines(&cinfo, buffer, 1);
+
+ for(unsigned x = 0; x < cinfo.output_width; x++) {
+ WORD K = (WORD)src[3];
+ dst[FI_RGBA_RED] = (BYTE)((K * src[0]) / 255); // C -> R
+ dst[FI_RGBA_GREEN] = (BYTE)((K * src[1]) / 255); // M -> G
+ dst[FI_RGBA_BLUE] = (BYTE)((K * src[2]) / 255); // Y -> B
+ src += 4;
+ dst += 3;
+ }
+ }
+ } else if ((cinfo.out_color_space == JCS_CMYK) && ((flags & JPEG_CMYK) == JPEG_CMYK)) {
+ // convert from LibJPEG CMYK to standard CMYK
+
+ JSAMPARRAY buffer; // output row buffer
+ unsigned row_stride; // physical row width in output buffer
+
+ // JSAMPLEs per row in output buffer
+ row_stride = cinfo.output_width * cinfo.output_components;
+ // make a one-row-high sample array that will go away when done with image
+ buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
+
+ while (cinfo.output_scanline < cinfo.output_height) {
+ JSAMPROW src = buffer[0];
+ JSAMPROW dst = FreeImage_GetScanLine(dib, cinfo.output_height - cinfo.output_scanline - 1);
+
+ jpeg_read_scanlines(&cinfo, buffer, 1);
+
+ for(unsigned x = 0; x < cinfo.output_width; x++) {
+ // CMYK pixels are inverted
+ dst[0] = ~src[0]; // C
+ dst[1] = ~src[1]; // M
+ dst[2] = ~src[2]; // Y
+ dst[3] = ~src[3]; // K
+ src += 4;
+ dst += 4;
+ }
+ }
+
+ } else {
+ // normal case (RGB or greyscale image)
+
+ while (cinfo.output_scanline < cinfo.output_height) {
+ JSAMPROW dst = FreeImage_GetScanLine(dib, cinfo.output_height - cinfo.output_scanline - 1);
+
+ jpeg_read_scanlines(&cinfo, &dst, 1);
+ }
+
+ // step 7b: swap red and blue components (see LibJPEG/jmorecfg.h: #define RGB_RED, ...)
+ // The default behavior of the JPEG library is kept "as is" because LibTIFF uses
+ // LibJPEG "as is".
+
+#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
+ SwapRedBlue32(dib);
+#endif
+ }
+
+ // step 8: finish decompression
+
+ jpeg_finish_decompress(&cinfo);
+
+ // step 9: release JPEG decompression object
+
+ jpeg_destroy_decompress(&cinfo);
+
+ // check for automatic Exif rotation
+ if (!header_only && ((flags & JPEG_EXIFROTATE) == JPEG_EXIFROTATE)) {
+ rotate_exif(&dib);
+ }
+
+ // everything went well. return the loaded dib
+
+ return dib;
+
+ } catch (const char *text) {
+ jpeg_destroy_decompress(&cinfo);
+ if(NULL != dib) {
+ FreeImage_Unload(dib);
+ }
+ if(NULL != text) {
+ FreeImage_OutputMessageProc(s_format_id, text);
+ }
+ }
+ }
+
+ return NULL;
+}
+
+// ----------------------------------------------------------
+
+static BOOL DLL_CALLCONV
+Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) {
+ if ((dib) && (handle)) {
+ try {
+ // Check dib format
+
+ const char *sError = "only 24-bit highcolor or 8-bit greyscale/palette bitmaps can be saved as JPEG";
+
+ FREE_IMAGE_COLOR_TYPE color_type = FreeImage_GetColorType(dib);
+ WORD bpp = (WORD)FreeImage_GetBPP(dib);
+
+ if ((bpp != 24) && (bpp != 8)) {
+ throw sError;
+ }
+
+ if(bpp == 8) {
+ // allow grey, reverse grey and palette
+ if ((color_type != FIC_MINISBLACK) && (color_type != FIC_MINISWHITE) && (color_type != FIC_PALETTE)) {
+ throw sError;
+ }
+ }
+
+
+ struct jpeg_compress_struct cinfo;
+ ErrorManager fi_error_mgr;
+
+ // Step 1: allocate and initialize JPEG compression object
+
+ // we set up the normal JPEG error routines, then override error_exit & output_message
+ cinfo.err = jpeg_std_error(&fi_error_mgr.pub);
+ fi_error_mgr.pub.error_exit = jpeg_error_exit;
+ fi_error_mgr.pub.output_message = jpeg_output_message;
+
+ // establish the setjmp return context for jpeg_error_exit to use
+ if (setjmp(fi_error_mgr.setjmp_buffer)) {
+ // If we get here, the JPEG code has signaled an error.
+ // We need to clean up the JPEG object, close the input file, and return.
+ jpeg_destroy_compress(&cinfo);
+ throw (const char*)NULL;
+ }
+
+ // Now we can initialize the JPEG compression object
+
+ jpeg_create_compress(&cinfo);
+
+ // Step 2: specify data destination (eg, a file)
+
+ jpeg_freeimage_dst(&cinfo, handle, io);
+
+ // Step 3: set parameters for compression
+
+ cinfo.image_width = FreeImage_GetWidth(dib);
+ cinfo.image_height = FreeImage_GetHeight(dib);
+
+ switch(color_type) {
+ case FIC_MINISBLACK :
+ case FIC_MINISWHITE :
+ cinfo.in_color_space = JCS_GRAYSCALE;
+ cinfo.input_components = 1;
+ break;
+
+ default :
+ cinfo.in_color_space = JCS_RGB;
+ cinfo.input_components = 3;
+ break;
+ }
+
+ jpeg_set_defaults(&cinfo);
+
+ // progressive-JPEG support
+ if ((flags & JPEG_PROGRESSIVE) == JPEG_PROGRESSIVE) {
+ jpeg_simple_progression(&cinfo);
+ }
+
+ // compute optimal Huffman coding tables for the image
+ if ((flags & JPEG_OPTIMIZE) == JPEG_OPTIMIZE) {
+ cinfo.optimize_coding = TRUE;
+ }
+
+ // Set JFIF density parameters from the DIB data
+
+ cinfo.X_density = (UINT16) (0.5 + 0.0254 * FreeImage_GetDotsPerMeterX(dib));
+ cinfo.Y_density = (UINT16) (0.5 + 0.0254 * FreeImage_GetDotsPerMeterY(dib));
+ cinfo.density_unit = 1; // dots / inch
+
+ // thumbnail support (JFIF 1.02 extension markers)
+ if(FreeImage_GetThumbnail(dib) != NULL) {
+ cinfo.write_JFIF_header = 1; //<### force it, though when color is CMYK it will be incorrect
+ cinfo.JFIF_minor_version = 2;
+ }
+
+ // baseline JPEG support
+ if ((flags & JPEG_BASELINE) == JPEG_BASELINE) {
+ cinfo.write_JFIF_header = 0; // No marker for non-JFIF colorspaces
+ cinfo.write_Adobe_marker = 0; // write no Adobe marker by default
+ }
+
+ // set subsampling options if required
+
+ if(cinfo.in_color_space == JCS_RGB) {
+ if ((flags & JPEG_SUBSAMPLING_411) == JPEG_SUBSAMPLING_411) {
+ // 4:1:1 (4x1 1x1 1x1) - CrH 25% - CbH 25% - CrV 100% - CbV 100%
+ // the horizontal color resolution is quartered
+ cinfo.comp_info[0].h_samp_factor = 4; // Y
+ cinfo.comp_info[0].v_samp_factor = 1;
+ cinfo.comp_info[1].h_samp_factor = 1; // Cb
+ cinfo.comp_info[1].v_samp_factor = 1;
+ cinfo.comp_info[2].h_samp_factor = 1; // Cr
+ cinfo.comp_info[2].v_samp_factor = 1;
+ } else if ((flags & JPEG_SUBSAMPLING_420) == JPEG_SUBSAMPLING_420) {
+ // 4:2:0 (2x2 1x1 1x1) - CrH 50% - CbH 50% - CrV 50% - CbV 50%
+ // the chrominance resolution in both the horizontal and vertical directions is cut in half
+ cinfo.comp_info[0].h_samp_factor = 2; // Y
+ cinfo.comp_info[0].v_samp_factor = 2;
+ cinfo.comp_info[1].h_samp_factor = 1; // Cb
+ cinfo.comp_info[1].v_samp_factor = 1;
+ cinfo.comp_info[2].h_samp_factor = 1; // Cr
+ cinfo.comp_info[2].v_samp_factor = 1;
+ } else if ((flags & JPEG_SUBSAMPLING_422) == JPEG_SUBSAMPLING_422){ //2x1 (low)
+ // 4:2:2 (2x1 1x1 1x1) - CrH 50% - CbH 50% - CrV 100% - CbV 100%
+ // half of the horizontal resolution in the chrominance is dropped (Cb & Cr),
+ // while the full resolution is retained in the vertical direction, with respect to the luminance
+ cinfo.comp_info[0].h_samp_factor = 2; // Y
+ cinfo.comp_info[0].v_samp_factor = 1;
+ cinfo.comp_info[1].h_samp_factor = 1; // Cb
+ cinfo.comp_info[1].v_samp_factor = 1;
+ cinfo.comp_info[2].h_samp_factor = 1; // Cr
+ cinfo.comp_info[2].v_samp_factor = 1;
+ }
+ else if ((flags & JPEG_SUBSAMPLING_444) == JPEG_SUBSAMPLING_444){ //1x1 (no subsampling)
+ // 4:4:4 (1x1 1x1 1x1) - CrH 100% - CbH 100% - CrV 100% - CbV 100%
+ // the resolution of chrominance information (Cb & Cr) is preserved
+ // at the same rate as the luminance (Y) information
+ cinfo.comp_info[0].h_samp_factor = 1; // Y
+ cinfo.comp_info[0].v_samp_factor = 1;
+ cinfo.comp_info[1].h_samp_factor = 1; // Cb
+ cinfo.comp_info[1].v_samp_factor = 1;
+ cinfo.comp_info[2].h_samp_factor = 1; // Cr
+ cinfo.comp_info[2].v_samp_factor = 1;
+ }
+ }
+
+ // Step 4: set quality
+ // the first 7 bits are reserved for low level quality settings
+ // the other bits are high level (i.e. enum-ish)
+
+ int quality;
+
+ if ((flags & JPEG_QUALITYBAD) == JPEG_QUALITYBAD) {
+ quality = 10;
+ } else if ((flags & JPEG_QUALITYAVERAGE) == JPEG_QUALITYAVERAGE) {
+ quality = 25;
+ } else if ((flags & JPEG_QUALITYNORMAL) == JPEG_QUALITYNORMAL) {
+ quality = 50;
+ } else if ((flags & JPEG_QUALITYGOOD) == JPEG_QUALITYGOOD) {
+ quality = 75;
+ } else if ((flags & JPEG_QUALITYSUPERB) == JPEG_QUALITYSUPERB) {
+ quality = 100;
+ } else {
+ if ((flags & 0x7F) == 0) {
+ quality = 75;
+ } else {
+ quality = flags & 0x7F;
+ }
+ }
+
+ jpeg_set_quality(&cinfo, quality, TRUE); /* limit to baseline-JPEG values */
+
+ // Step 5: Start compressor
+
+ jpeg_start_compress(&cinfo, TRUE);
+
+ // Step 6: Write special markers
+
+ if ((flags & JPEG_BASELINE) != JPEG_BASELINE) {
+ write_markers(&cinfo, dib);
+ }
+
+ // Step 7: while (scan lines remain to be written)
+
+ if(color_type == FIC_RGB) {
+ // 24-bit RGB image : need to swap red and blue channels
+ unsigned pitch = FreeImage_GetPitch(dib);
+ BYTE *target = (BYTE*)malloc(pitch * sizeof(BYTE));
+ if (target == NULL) {
+ throw FI_MSG_ERROR_MEMORY;
+ }
+
+ while (cinfo.next_scanline < cinfo.image_height) {
+ // get a copy of the scanline
+ memcpy(target, FreeImage_GetScanLine(dib, FreeImage_GetHeight(dib) - cinfo.next_scanline - 1), pitch);
+#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
+ // swap R and B channels
+ BYTE *target_p = target;
+ for(unsigned x = 0; x < cinfo.image_width; x++) {
+ INPLACESWAP(target_p[0], target_p[2]);
+ target_p += 3;
+ }
+#endif
+ // write the scanline
+ jpeg_write_scanlines(&cinfo, &target, 1);
+ }
+ free(target);
+ }
+ else if(color_type == FIC_MINISBLACK) {
+ // 8-bit standard greyscale images
+ while (cinfo.next_scanline < cinfo.image_height) {
+ JSAMPROW b = FreeImage_GetScanLine(dib, FreeImage_GetHeight(dib) - cinfo.next_scanline - 1);
+
+ jpeg_write_scanlines(&cinfo, &b, 1);
+ }
+ }
+ else if(color_type == FIC_PALETTE) {
+ // 8-bit palettized images are converted to 24-bit images
+ RGBQUAD *palette = FreeImage_GetPalette(dib);
+ BYTE *target = (BYTE*)malloc(cinfo.image_width * 3);
+ if (target == NULL) {
+ throw FI_MSG_ERROR_MEMORY;
+ }
+
+ while (cinfo.next_scanline < cinfo.image_height) {
+ BYTE *source = FreeImage_GetScanLine(dib, FreeImage_GetHeight(dib) - cinfo.next_scanline - 1);
+ FreeImage_ConvertLine8To24(target, source, cinfo.image_width, palette);
+
+#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
+ // swap R and B channels
+ BYTE *target_p = target;
+ for(unsigned x = 0; x < cinfo.image_width; x++) {
+ INPLACESWAP(target_p[0], target_p[2]);
+ target_p += 3;
+ }
+#endif
+
+
+ jpeg_write_scanlines(&cinfo, &target, 1);
+ }
+
+ free(target);
+ }
+ else if(color_type == FIC_MINISWHITE) {
+ // reverse 8-bit greyscale image, so reverse grey value on the fly
+ unsigned i;
+ BYTE reverse[256];
+ BYTE *target = (BYTE *)malloc(cinfo.image_width);
+ if (target == NULL) {
+ throw FI_MSG_ERROR_MEMORY;
+ }
+
+ for(i = 0; i < 256; i++) {
+ reverse[i] = (BYTE)(255 - i);
+ }
+
+ while(cinfo.next_scanline < cinfo.image_height) {
+ BYTE *source = FreeImage_GetScanLine(dib, FreeImage_GetHeight(dib) - cinfo.next_scanline - 1);
+ for(i = 0; i < cinfo.image_width; i++) {
+ target[i] = reverse[ source[i] ];
+ }
+ jpeg_write_scanlines(&cinfo, &target, 1);
+ }
+
+ free(target);
+ }
+
+ // Step 8: Finish compression
+
+ jpeg_finish_compress(&cinfo);
+
+ // Step 9: release JPEG compression object
+
+ jpeg_destroy_compress(&cinfo);
+
+ return TRUE;
+
+ } catch (const char *text) {
+ if(text) {
+ FreeImage_OutputMessageProc(s_format_id, text);
+ }
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+// ==========================================================
+// Init
+// ==========================================================
+
+void DLL_CALLCONV
+InitJPEG(Plugin *plugin, int format_id) {
+ s_format_id = format_id;
+
+ plugin->format_proc = Format;
+ plugin->description_proc = Description;
+ plugin->extension_proc = Extension;
+ plugin->regexpr_proc = RegExpr;
+ plugin->open_proc = NULL;
+ plugin->close_proc = NULL;
+ plugin->pagecount_proc = NULL;
+ plugin->pagecapability_proc = NULL;
+ plugin->load_proc = Load;
+ plugin->save_proc = Save;
+ plugin->validate_proc = Validate;
+ plugin->mime_proc = MimeType;
+ plugin->supports_export_bpp_proc = SupportsExportDepth;
+ plugin->supports_export_type_proc = SupportsExportType;
+ plugin->supports_icc_profiles_proc = SupportsICCProfiles;
+ plugin->supports_no_pixels_proc = SupportsNoPixels;
+}