diff options
Diffstat (limited to 'src/im_format_jpeg.cpp')
-rw-r--r-- | src/im_format_jpeg.cpp | 820 |
1 files changed, 820 insertions, 0 deletions
diff --git a/src/im_format_jpeg.cpp b/src/im_format_jpeg.cpp new file mode 100644 index 0000000..91d0ae9 --- /dev/null +++ b/src/im_format_jpeg.cpp @@ -0,0 +1,820 @@ +/** \file + * \brief JPEG File Interchange Format + * + * See Copyright Notice in im_lib.h + * See libJPEG Copyright Notice in jpeglib.h + * $Id: im_format_jpeg.cpp,v 1.1 2008/10/17 06:10:16 scuri Exp $ + */ + +#include "im_format.h" +#include "im_format_all.h" +#include "im_util.h" +#include "im_counter.h" +#include "im_math.h" + +#include "im_binfile.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <setjmp.h> +#include <math.h> + +extern "C" { +#include "jpeglib.h" +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" +} + +#ifdef USE_EXIF +#include "exif-data.h" +#include "exif-entry.h" +#include "exif-utils.h" +extern "C" const char *exif_tag_get_name_index (unsigned int i, ExifTag *tag); +#endif + +/* libjpeg error handlers */ + +struct JPEGerror_mgr +{ + jpeg_error_mgr pub; /* "public" fields */ + jmp_buf setjmp_buffer; /* for return to caller */ +}; + +METHODDEF(void) +JPEGerror_exit (j_common_ptr cinfo) +{ + /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */ + JPEGerror_mgr* err_mgr = (JPEGerror_mgr*)cinfo->err; + + /* Return control to the setjmp point */ + longjmp(err_mgr->setjmp_buffer, 1); +} + +METHODDEF(void) +JPEGoutput_message (j_common_ptr cinfo) +{ + (void)cinfo; +} + +METHODDEF(void) +JPEGemit_message (j_common_ptr cinfo, int msg_level) +{ + (void)cinfo; (void)msg_level; +} + +static const char* iJPEGCompTable[1] = +{ + "JPEG" +}; + +class imFormatJPEG: public imFormat +{ + jpeg_decompress_struct dinfo; + jpeg_compress_struct cinfo; + JPEGerror_mgr jerr; + + imBinFile* handle; + int fix_adobe; + +#ifdef USE_EXIF + void iReadExifAttrib(unsigned char* data, int data_length, imAttribTable* attrib_table); + void iWriteExifAttrib(imAttribTable* attrib_table); +#endif + +public: + imFormatJPEG() + :imFormat("JPEG", + "JPEG File Interchange Format", + "*.jpg;*.jpeg;*.jpe;*.jfif;*.jif;*.jfi;", + iJPEGCompTable, + 1, + 0) + {} + ~imFormatJPEG() {} + + int Open(const char* file_name); + int New(const char* file_name); + void Close(); + void* Handle(int index); + int ReadImageInfo(int index); + int ReadImageData(void* data); + int WriteImageInfo(); + int WriteImageData(void* data); + int CanWrite(const char* compression, int color_mode, int data_type) const; +}; + +void imFormatRegisterJPEG(void) +{ + imFormatRegister(new imFormatJPEG()); +} + +int imFormatJPEG::Open(const char* file_name) +{ + this->handle = imBinFileOpen(file_name); + if (this->handle == NULL) + return IM_ERR_OPEN; + + unsigned char sig[2]; + if (!imBinFileRead(this->handle, sig, 2, 1)) + { + imBinFileClose(this->handle); + return IM_ERR_ACCESS; + } + + if (sig[0] != 0xFF || sig[1] != 0xD8) + { + imBinFileClose(this->handle); + return IM_ERR_FORMAT; + } + + imBinFileSeekTo(this->handle, 0); + + strcpy(this->compression, "JPEG"); + this->image_count = 1; + + this->dinfo.err = jpeg_std_error(&this->jerr.pub); + this->jerr.pub.error_exit = JPEGerror_exit; + this->jerr.pub.output_message = JPEGoutput_message; + this->jerr.pub.emit_message = JPEGemit_message; + + /* Establish the setjmp return context for error_exit to use. */ + if (setjmp(this->jerr.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(&this->dinfo); + imBinFileClose(this->handle); + return IM_ERR_FORMAT; + } + + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress(&this->dinfo); + + /* Step 2: specify data source (eg, a file) */ + jpeg_stdio_src(&this->dinfo, (FILE*)this->handle); + + return IM_ERR_NONE; +} + +int imFormatJPEG::New(const char* file_name) +{ + this->handle = imBinFileNew(file_name); + if (this->handle == NULL) + return IM_ERR_OPEN; + + this->cinfo.err = jpeg_std_error(&this->jerr.pub); + this->jerr.pub.error_exit = JPEGerror_exit; + this->jerr.pub.output_message = JPEGoutput_message; + this->jerr.pub.emit_message = JPEGemit_message; + + /* Establish the setjmp return context for error_exit to use. */ + if (setjmp(this->jerr.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(&this->cinfo); + imBinFileClose(this->handle); + return IM_ERR_ACCESS; + } + + jpeg_create_compress(&this->cinfo); + + /* Step 2: specify data destination (eg, a file) */ + jpeg_stdio_dest(&this->cinfo, (FILE*)this->handle); + + strcpy(this->compression, "JPEG"); + this->image_count = 1; + + return IM_ERR_NONE; +} + +void imFormatJPEG::Close() +{ + if (this->is_new) + jpeg_destroy_compress(&this->cinfo); + else + jpeg_destroy_decompress(&this->dinfo); + + imBinFileClose(this->handle); +} + +void* imFormatJPEG::Handle(int index) +{ + if (index == 0) + return this->handle; + else if (index == 1) + { + if (this->is_new) + return (void*)&this->cinfo; + else + return (void*)&this->dinfo; + } + else + return NULL; +} + +#ifdef USE_EXIF +void imFormatJPEG::iReadExifAttrib(unsigned char* data, int data_length, imAttribTable* attrib_table) +{ + ExifData* exif = exif_data_new_from_data(data, data_length); + if (!exif) + return; + + void* value = NULL; + int c, value_size = 0; + + ExifByteOrder byte_order = exif_data_get_byte_order(exif); + + for (int i = 0; i < 3; i += 2) // Only scan for IFD_0 (0) and IFD_EXIF (2) + { + ExifContent *content = exif->ifd[i]; + + if (content && content->count) + { + for (int j = 0; j < (int)content->count; j++) + { + ExifEntry *entry = content->entries[j]; + int type = 0; + + const char* name = exif_tag_get_name(entry->tag); + if (!name) + continue; + + if (value_size < (int)entry->size) + { + value = realloc(value, entry->size); + value_size = entry->size; + } + + int format_size = exif_format_get_size(entry->format); + + if (entry->tag == EXIF_TAG_RESOLUTION_UNIT) + { + int res_unit = (int)exif_get_short (entry->data, byte_order); + + if (res_unit == 2) + attrib_table->Set("ResolutionUnit", IM_BYTE, 4, "DPI"); + else if (res_unit == 3) + attrib_table->Set("ResolutionUnit", IM_BYTE, 4, "DPC"); + + continue; + } + + switch (entry->format) + { + case EXIF_FORMAT_UNDEFINED: + case EXIF_FORMAT_ASCII: + case EXIF_FORMAT_SBYTE: + case EXIF_FORMAT_BYTE: + { + type = IM_BYTE; + imbyte *bvalue = (imbyte*)value; + for (c = 0; c < (int)entry->components; c++) + bvalue[c] = entry->data[c]; + } + break; + case EXIF_FORMAT_SSHORT: + case EXIF_FORMAT_SHORT: + { + type = IM_USHORT; + imushort *usvalue = (imushort*)value; + for (c = 0; c < (int)entry->components; c++) + usvalue[c] = exif_get_short(entry->data + format_size * c, byte_order); + } + break; + case EXIF_FORMAT_LONG: + { + type = IM_INT; + int *ivalue = (int*)value; + for (c = 0; c < (int)entry->components; c++) + ivalue[c] = (int)exif_get_long(entry->data + format_size * c, byte_order); + } + break; + case EXIF_FORMAT_SLONG: + { + type = IM_INT; + int *ivalue = (int*)value; + for (c = 0; c < (int)entry->components; c++) + ivalue[c] = (int)exif_get_slong(entry->data + format_size * c, byte_order); + } + break; + case EXIF_FORMAT_RATIONAL: + { + ExifRational v_rat; + type = IM_FLOAT; + float *fvalue = (float*)value; + for (c = 0; c < (int)entry->components; c++) + { + v_rat = exif_get_rational(entry->data + format_size * c, byte_order); + fvalue[c] = (float)v_rat.numerator / (float)v_rat.denominator; + } + } + break; + case EXIF_FORMAT_SRATIONAL: + { + ExifSRational v_srat; + type = IM_FLOAT; + float *fvalue = (float*)value; + for (c = 0; c < (int)entry->components; c++) + { + v_srat = exif_get_srational(entry->data + format_size * c, byte_order); + fvalue[c] = (float)v_srat.numerator / (float)v_srat.denominator; + } + } + break; + case EXIF_FORMAT_FLOAT: // missing from libEXIF + case EXIF_FORMAT_DOUBLE: + break; + } + + attrib_table->Set(name, type, entry->components, value); + } + } + } + + if (value) free(value); + + exif_data_free(exif); +} + +static void iGetRational(float fvalue, int *num, int *den, int sign) +{ + if (floorf(fvalue) == fvalue) + { + *num = (int)floorf(fvalue); + *den = 1; + return; + } + + float ivalue = 1.0f/fvalue; + if (floorf(ivalue) == ivalue) + { + *den = (int)floorf(ivalue); + *num = 1; + return; + } + + if (fvalue < 0) + { + if (sign == 1) + fvalue = 0; + else + fvalue = -fvalue; + } + + *den = 1; + if (fvalue > 0) + { + while (fvalue < 1L<<(31-3) && *den < 1L<<(31-3)) + { + fvalue *= 1<<3; + *den *= 1<<3; + } + } + + *num = sign * imRound(fvalue); +} + +void imFormatJPEG::iWriteExifAttrib(imAttribTable* attrib_table) +{ + ExifData* exif = exif_data_new(); + + ExifByteOrder byte_order; + if (imBinCPUByteOrder() == IM_LITTLEENDIAN) + byte_order = EXIF_BYTE_ORDER_INTEL; + else + byte_order = EXIF_BYTE_ORDER_MOTOROLA; + + exif_data_set_byte_order(exif, byte_order); + + int c, i = 0; + while(i>=0) + { + ExifTag tag; + const char * name = exif_tag_get_name_index(i, &tag); + if (!name) + break; + + ExifEntry *entry; + int attrib_count; + const void* attrib_data = attrib_table->Get(name, NULL, &attrib_count); + if (attrib_data) + { + entry = exif_entry_new(); + + ExifContent *content; + if (tag > EXIF_TAG_COPYRIGHT) + content = exif->ifd[2]; // IFD_EXIF (2) contains EXIF tags + else + content = exif->ifd[0]; // IFD_0 (0) contains TIFF tags + + exif_content_add_entry(content, entry); + + exif_entry_initialize(entry, tag); + + if (!entry->format) // unsupported tag + { + i++; + continue; + } + + int format_size = exif_format_get_size(entry->format); + + if (tag == EXIF_TAG_RESOLUTION_UNIT) + { + int res_unit; + if (imStrEqual((char*)attrib_data, "DPI")) + res_unit = 2; + else + res_unit = 3; + + exif_set_short (entry->data, byte_order, (imushort)res_unit); + + i++; + continue; + } + + if (entry->components == 0) + { + entry->components = attrib_count; + if (entry->data) free(entry->data); + entry->size = format_size * entry->components; + entry->data = (imbyte*)malloc(entry->size); + } + + switch (entry->format) + { + case EXIF_FORMAT_UNDEFINED: + case EXIF_FORMAT_ASCII: + case EXIF_FORMAT_BYTE: + { + imbyte *bvalue = (imbyte*)attrib_data; + for (c = 0; c < (int)entry->components; c++) + entry->data[c] = bvalue[c]; + } + break; + case EXIF_FORMAT_SHORT: + { + imushort *usvalue = (imushort*)attrib_data; + for (c = 0; c < (int)entry->components; c++) + exif_set_short(entry->data + format_size * c, byte_order, usvalue[c]); + } + break; + case EXIF_FORMAT_LONG: + { + int *ivalue = (int*)attrib_data; + for (c = 0; c < (int)entry->components; c++) + exif_set_long(entry->data + format_size * c, byte_order, (unsigned int)ivalue[c]); + } + break; + case EXIF_FORMAT_SLONG: + { + int *ivalue = (int*)attrib_data; + for (c = 0; c < (int)entry->components; c++) + exif_set_slong(entry->data + format_size * c, byte_order, (int)ivalue[c]); + } + break; + case EXIF_FORMAT_RATIONAL: + { + ExifRational v_rat; + int num, den; + float *fvalue = (float*)attrib_data; + for (c = 0; c < (int)entry->components; c++) + { + iGetRational(fvalue[c], &num, &den, 1); + v_rat.numerator = num; + v_rat.denominator = den; + exif_set_rational(entry->data + format_size * c, byte_order, v_rat); + } + } + break; + case EXIF_FORMAT_SRATIONAL: + { + ExifSRational v_srat; + int num, den; + float *fvalue = (float*)attrib_data; + for (c = 0; c < (int)entry->components; c++) + { + iGetRational(fvalue[c], &num, &den, 1); + v_srat.numerator = num; + v_srat.denominator = den; + exif_set_srational(entry->data + format_size * c, byte_order, v_srat); + } + } + break; + } + } + + i++; + } + + imbyte* data = NULL; + unsigned int data_size = 0; + + exif_data_save_data(exif, &data, &data_size); + + if (data) + { + jpeg_write_marker(&this->cinfo, JPEG_APP0+1, data, data_size); + free(data); + } + + exif_data_free(exif); +} +#endif + +int imFormatJPEG::ReadImageInfo(int index) +{ + (void)index; + this->fix_adobe = 0; + + if (setjmp(this->jerr.setjmp_buffer)) + return IM_ERR_ACCESS; + + // notify libjpeg to save the COM marker + jpeg_save_markers(&this->dinfo, JPEG_COM, 0xFFFF); + jpeg_save_markers(&this->dinfo, JPEG_APP0+1, 0xFFFF); + + /* Step 3: read file parameters with jpeg_read_header() */ + if (jpeg_read_header(&this->dinfo, TRUE) != JPEG_HEADER_OK) + return IM_ERR_ACCESS; + + this->width = this->dinfo.image_width; + this->height = this->dinfo.image_height; + this->file_data_type = IM_BYTE; + + switch(this->dinfo.jpeg_color_space) + { + case JCS_GRAYSCALE: + this->file_color_mode = IM_GRAY; + break; + case JCS_RGB: + this->file_color_mode = IM_RGB; + break; + case JCS_YCbCr: + this->file_color_mode = IM_RGB; + break; + case JCS_CMYK: + this->file_color_mode = IM_CMYK; + break; + case JCS_YCCK: + this->file_color_mode = IM_CMYK; // this is the only supported conversion in libjpeg + this->dinfo.out_color_space = JCS_CMYK; + this->fix_adobe = 1; + break; + default: /* JCS_UNKNOWN */ + return IM_ERR_DATA; + } + + imAttribTable* attrib_table = AttribTable(); + + int* auto_ycbcr = (int*)attrib_table->Get("AutoYCbCr"); + if (auto_ycbcr && *auto_ycbcr == 0 && + this->dinfo.jpeg_color_space == JCS_YCbCr) + { + this->file_color_mode = IM_YCBCR; + this->dinfo.out_color_space = JCS_YCbCr; + } + + this->file_color_mode |= IM_TOPDOWN; + + if (imColorModeDepth(this->file_color_mode) > 1) + this->file_color_mode |= IM_PACKED; + + if (this->dinfo.progressive_mode != 0) + { + int progressive = 1; + attrib_table->Set("Interlaced", IM_INT, 1, &progressive); + } + + if (this->dinfo.density_unit != 0) + { + float xres = (float)this->dinfo.X_density, + yres = (float)this->dinfo.Y_density; + + if (this->dinfo.density_unit == 1) + attrib_table->Set("ResolutionUnit", IM_BYTE, 4, "DPI"); + else + attrib_table->Set("ResolutionUnit", IM_BYTE, 4, "DPC"); + + attrib_table->Set("XResolution", IM_FLOAT, 1, (void*)&xres); + attrib_table->Set("YResolution", IM_FLOAT, 1, (void*)&yres); + } + + if (this->dinfo.marker_list) + { + jpeg_saved_marker_ptr cur_marker = this->dinfo.marker_list; + + // search for COM marker + while (cur_marker) + { + if (cur_marker->marker == JPEG_COM) + { + char* desc = new char [cur_marker->data_length+1]; + memcpy(desc, cur_marker->data, cur_marker->data_length); + desc[cur_marker->data_length] = 0; + attrib_table->Set("Description", IM_BYTE, cur_marker->data_length+1, desc); + delete [] desc; + } + +#ifdef USE_EXIF + if (cur_marker->marker == JPEG_APP0+1) + iReadExifAttrib(cur_marker->data, cur_marker->data_length, attrib_table); +#endif + + cur_marker = cur_marker->next; + } + } + + /* Step 5: Start decompressor */ + if (jpeg_start_decompress(&this->dinfo) == FALSE) + return IM_ERR_ACCESS; + + return IM_ERR_NONE; +} + +int imFormatJPEG::WriteImageInfo() +{ + this->file_color_mode = imColorModeSpace(this->user_color_mode); + this->file_color_mode |= IM_TOPDOWN; + + if (imColorModeDepth(this->file_color_mode) > 1) + this->file_color_mode |= IM_PACKED; + + this->file_data_type = IM_BYTE; + + /* Step 3: set parameters for compression */ + this->cinfo.image_width = this->width; /* image width and height, in pixels */ + this->cinfo.image_height = this->height; + + this->cinfo.input_components = imColorModeDepth(this->file_color_mode); + + switch (imColorModeSpace(this->user_color_mode)) + { + case IM_BINARY: + this->convert_bpp = -1; // expand 1 to 255 + case IM_GRAY: + this->cinfo.in_color_space = JCS_GRAYSCALE; + break; + case IM_RGB: + this->cinfo.in_color_space = JCS_RGB; + break; + case IM_CMYK: + this->cinfo.in_color_space = JCS_CMYK; + break; + case IM_YCBCR: + this->cinfo.in_color_space = JCS_YCbCr; + break; + default: + this->cinfo.in_color_space = JCS_UNKNOWN; + break; + } + + if (setjmp(this->jerr.setjmp_buffer)) + return IM_ERR_ACCESS; + + jpeg_set_defaults(&this->cinfo); + + imAttribTable* attrib_table = AttribTable(); + + int* auto_ycbcr = (int*)attrib_table->Get("AutoYCbCr"); + if (auto_ycbcr && *auto_ycbcr == 0 && + this->cinfo.in_color_space == JCS_RGB) + { + jpeg_set_colorspace(&this->cinfo, JCS_RGB); + } + + int* interlaced = (int*)attrib_table->Get("Interlaced"); + if (interlaced && *interlaced) + jpeg_simple_progression(&this->cinfo); + + int* quality = (int*)attrib_table->Get("JPEGQuality"); + if (quality) + jpeg_set_quality(&this->cinfo, *quality, TRUE); + + char* res_unit = (char*)attrib_table->Get("ResolutionUnit"); + if (res_unit) + { + float* xres = (float*)attrib_table->Get("XResolution"); + float* yres = (float*)attrib_table->Get("YResolution"); + + if (xres && yres) + { + if (imStrEqual(res_unit, "DPI")) + this->cinfo.density_unit = 1; + else + this->cinfo.density_unit = 2; + + this->cinfo.X_density = (UINT16)*xres; + this->cinfo.Y_density = (UINT16)*yres; + } + } + + /* Step 4: Start compressor */ + jpeg_start_compress(&this->cinfo, TRUE); + + int desc_size; + char* desc = (char*)attrib_table->Get("Description", NULL, &desc_size); + if (desc) + jpeg_write_marker(&this->cinfo, JPEG_COM, (JOCTET*)desc, desc_size-1); + +#ifdef USE_EXIF + iWriteExifAttrib(attrib_table); +#endif + + return IM_ERR_NONE; +} + +static void iFixAdobe(unsigned char* line_buffer, int width) +{ + width *= 4; + for (int i = 0; i < width; i++) + { + *line_buffer = 255 - *line_buffer; + line_buffer++; + } +} + +int imFormatJPEG::ReadImageData(void* data) +{ + if (setjmp(this->jerr.setjmp_buffer)) + return IM_ERR_ACCESS; + + imCounterTotal(this->counter, this->dinfo.output_height, "Reading JPEG..."); + + int row = 0, plane = 0; + while (this->dinfo.output_scanline < this->dinfo.output_height) + { + if (jpeg_read_scanlines(&this->dinfo, (JSAMPARRAY)&this->line_buffer, 1) == 0) + return IM_ERR_ACCESS; + + if (this->fix_adobe) + iFixAdobe((unsigned char*)this->line_buffer, this->width); + + imFileLineBufferRead(this, data, row, plane); + + if (!imCounterInc(this->counter)) + { + jpeg_finish_decompress(&this->dinfo); + return IM_ERR_COUNTER; + } + + imFileLineBufferInc(this, &row, &plane); + } + + jpeg_finish_decompress(&this->dinfo); + + return IM_ERR_NONE; +} + +int imFormatJPEG::WriteImageData(void* data) +{ + if (setjmp(this->jerr.setjmp_buffer)) + return IM_ERR_ACCESS; + + imCounterTotal(this->counter, this->dinfo.output_height, "Writing JPEG..."); + + int row = 0, plane = 0; + while (this->cinfo.next_scanline < this->cinfo.image_height) + { + imFileLineBufferWrite(this, data, row, plane); + + if (jpeg_write_scanlines(&this->cinfo, (JSAMPARRAY)&this->line_buffer, 1) == 0) + return IM_ERR_ACCESS; + + if (!imCounterInc(this->counter)) + { + jpeg_finish_compress(&this->cinfo); + return IM_ERR_COUNTER; + } + + imFileLineBufferInc(this, &row, &plane); + } + + jpeg_finish_compress(&this->cinfo); + + return IM_ERR_NONE; +} + +int imFormatJPEG::CanWrite(const char* compression, int color_mode, int data_type) const +{ + int color_space = imColorModeSpace(color_mode); + + if (color_space == IM_MAP || color_space == IM_LAB || + color_space == IM_LUV || color_space == IM_XYZ) + return IM_ERR_DATA; + + if (data_type != IM_BYTE) + return IM_ERR_DATA; + + if (!compression || compression[0] == 0) + return IM_ERR_NONE; + + if (!imStrEqual(compression, "JPEG")) + return IM_ERR_COMPRESS; + + return IM_ERR_NONE; +} + |