diff options
author | George Hazan <ghazan@miranda.im> | 2018-01-26 17:38:31 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2018-01-26 17:38:31 +0300 |
commit | dea9c030340e50324eba97c72a27c151bed12e1c (patch) | |
tree | 6fc156f40f52a9fc6e6b29e60001959477ee1a5e /plugins/AdvaImg/src/FreeImage/PluginJPEG.cpp | |
parent | c6e8f8223cab9d799593b7b2cfa22134aa9745d6 (diff) |
AdvaImg:
- freeimage extracted to the separate library;
- FI_INTERFACE removed, all references to it are replaced with direct calls of FreeImage_* functions;
- unified project for AdvaImg
Diffstat (limited to 'plugins/AdvaImg/src/FreeImage/PluginJPEG.cpp')
-rw-r--r-- | plugins/AdvaImg/src/FreeImage/PluginJPEG.cpp | 1706 |
1 files changed, 0 insertions, 1706 deletions
diff --git a/plugins/AdvaImg/src/FreeImage/PluginJPEG.cpp b/plugins/AdvaImg/src/FreeImage/PluginJPEG.cpp deleted file mode 100644 index 19e4a070a5..0000000000 --- a/plugins/AdvaImg/src/FreeImage/PluginJPEG.cpp +++ /dev/null @@ -1,1706 +0,0 @@ -// ========================================================== -// 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 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); - } -} - -// ========================================================== -// 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; - } - - if ((flags & JPEG_GREYSCALE) == JPEG_GREYSCALE) { - // force loading as a 8-bit greyscale image - cinfo.out_color_space = JCS_GRAYSCALE; - } - - // step 5a: start decompressor and calculate output width and height - - jpeg_start_decompress(&cinfo); - - // step 5b: allocate dib and init header - - if((cinfo.output_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.output_components, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); - if(!dib) throw FI_MSG_ERROR_DIB_MEMORY; - - if (cinfo.output_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)) { - RotateExif(&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; -} |