diff --git a/src/add-ons/translators/jpeg/JPEGTranslator.cpp b/src/add-ons/translators/jpeg/JPEGTranslator.cpp index da3b2d9e32..9966e58779 100644 --- a/src/add-ons/translators/jpeg/JPEGTranslator.cpp +++ b/src/add-ons/translators/jpeg/JPEGTranslator.cpp @@ -32,9 +32,13 @@ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "JPEGTranslator.h" +#include "exif_parser.h" + #include +#define MARKER_EXIF 0xe1 + // Set these accordingly #define JPEG_ACRONYM "JPEG" #define JPEG_FORMAT 'JPEG' @@ -79,6 +83,12 @@ translation_format outputFormats[] = { {} }; +// Main functions of translator :) +static status_t Copy(BPositionIO *in, BPositionIO *out); +static status_t Compress(BPositionIO *in, BPositionIO *out); +static status_t Decompress(BPositionIO *in, BPositionIO *out, BMessage* ioExtension); +static status_t Error(j_common_ptr cinfo, status_t error = B_ERROR); + bool gAreSettingsRunning = false; @@ -1054,14 +1064,14 @@ Translate(BPositionIO *inSource, const translator_info *inInfo, } else if (inInfo->type == B_TRANSLATOR_BITMAP && outType == JPEG_FORMAT) { return Compress(inSource, outDestination); } else if (inInfo->type == JPEG_FORMAT && outType == B_TRANSLATOR_BITMAP) { - return Decompress(inSource, outDestination); + return Decompress(inSource, outDestination, ioExtension); } return B_NO_TRANSLATOR; } /*! The user has requested the same format for input and output, so just copy */ -status_t +static status_t Copy(BPositionIO *in, BPositionIO *out) { int block_size = 65536; @@ -1095,7 +1105,7 @@ Copy(BPositionIO *in, BPositionIO *out) /*! Encode into the native format */ -status_t +static status_t Compress(BPositionIO *in, BPositionIO *out) { // Load Settings @@ -1307,8 +1317,8 @@ Compress(BPositionIO *in, BPositionIO *out) /*! Decode the native format */ -status_t -Decompress(BPositionIO *in, BPositionIO *out) +static status_t +Decompress(BPositionIO *in, BPositionIO *out, BMessage* ioExtension) { // Load Settings jpeg_settings settings; @@ -1321,16 +1331,39 @@ Decompress(BPositionIO *in, BPositionIO *out) jpeg_create_decompress(&cinfo); be_jpeg_stdio_src(&cinfo, in); + jpeg_save_markers(&cinfo, MARKER_EXIF, 131072); + // make sure the EXIF tag is stored + // Read info about image jpeg_read_header(&cinfo, TRUE); + BMessage exif; + + if (ioExtension != NULL) { + // add EXIF data to message, if any + jpeg_marker_struct* marker = cinfo.marker_list; + while (marker != NULL) { + if (marker->marker == MARKER_EXIF + && !strncmp((char*)marker->data, "Exif", 4)) { + // Strip EXIF header from TIFF data + ioExtension->AddData("exif", B_RAW_TYPE, + (uint8 *)marker->data + 6, marker->data_length - 6); + + BMemoryIO io(marker->data + 6, marker->data_length - 6); + convert_exif_to_message(io, exif); + } + marker = marker->next; + } + } + // Default color info - color_space out_color_space = B_RGB32; - int out_color_components = 4; + color_space outColorSpace = B_RGB32; + int outColorComponents = 4; // Function pointer to convert function // It will point to proper function if needed - void (*converter)(uchar *inscanline, uchar *outscanline, int inrow_bytes) = convert_from_24_to_32; + void (*converter)(uchar *inScanLine, uchar *outScanLine, + int inRowBytes) = convert_from_24_to_32; // If color space isn't rgb if (cinfo.out_color_space != JCS_RGB) { @@ -1342,8 +1375,8 @@ Decompress(BPositionIO *in, BPositionIO *out) // Check if user wants to read only as RGB32 or not if (!settings.Always_B_RGB32) { // Grayscale - out_color_space = B_GRAY8; - out_color_components = 1; + outColorSpace = B_GRAY8; + outColorComponents = 1; converter = NULL; } else { // RGB @@ -1376,12 +1409,21 @@ Decompress(BPositionIO *in, BPositionIO *out) // Initialize decompression jpeg_start_decompress(&cinfo); - // !!! Initialize this bounds rect to the size of your image - BRect bounds(0, 0, cinfo.output_width-1, cinfo.output_height-1); + int32 orientation; + if (exif.FindInt32("Orientation", &orientation) != B_OK) + orientation = 1; + + // Initialize this bounds rect to the size of your image + BRect bounds(0, 0, cinfo.output_width - 1, cinfo.output_height - 1); + if (orientation & 4) { + // image is rotated + bounds.right = cinfo.output_height - 1; + bounds.bottom = cinfo.output_width - 1; + } // Bytes count in one line of image (scanline) - int64 row_bytes = cinfo.output_width * out_color_components; - + int64 rowBytes = cinfo.output_width * outColorComponents; + // Fill out the B_TRANSLATOR_BITMAP's header TranslatorBitmap header; header.magic = B_HOST_TO_BENDIAN_INT32(B_TRANSLATOR_BITMAP); @@ -1389,9 +1431,9 @@ Decompress(BPositionIO *in, BPositionIO *out) header.bounds.top = B_HOST_TO_BENDIAN_FLOAT(bounds.top); header.bounds.right = B_HOST_TO_BENDIAN_FLOAT(bounds.right); header.bounds.bottom = B_HOST_TO_BENDIAN_FLOAT(bounds.bottom); - header.colors = (color_space)B_HOST_TO_BENDIAN_INT32(out_color_space); - header.rowBytes = B_HOST_TO_BENDIAN_INT32(row_bytes); - header.dataSize = B_HOST_TO_BENDIAN_INT32(row_bytes * cinfo.output_height); + header.colors = (color_space)B_HOST_TO_BENDIAN_INT32(outColorSpace); + header.rowBytes = B_HOST_TO_BENDIAN_INT32(rowBytes); + header.dataSize = B_HOST_TO_BENDIAN_INT32(rowBytes * cinfo.output_height); // Write out the header status_t err = out->Write(&header, sizeof(TranslatorBitmap)); @@ -1408,14 +1450,14 @@ Decompress(BPositionIO *in, BPositionIO *out) // Allocate scanline // Use libjpeg memory allocation functions, so in case of error it will free them itself in_scanline = (unsigned char *)(cinfo.mem->alloc_large)((j_common_ptr)&cinfo, - JPOOL_PERMANENT, row_bytes); + JPOOL_PERMANENT, rowBytes); // We need 2nd scanline storage only for conversion if (converter != NULL) { // There will be conversion, allocate second scanline... // Use libjpeg memory allocation functions, so in case of error it will free them itself out_scanline = (unsigned char *)(cinfo.mem->alloc_large)((j_common_ptr)&cinfo, - JPOOL_PERMANENT, row_bytes); + JPOOL_PERMANENT, rowBytes); // ... and make it the one to write to file writeline = out_scanline; } else @@ -1427,11 +1469,11 @@ Decompress(BPositionIO *in, BPositionIO *out) // Convert if needed if (converter != NULL) - converter(in_scanline, out_scanline, row_bytes); + converter(in_scanline, out_scanline, rowBytes); // Write the scanline buffer to the output stream - err = out->Write(writeline, row_bytes); - if (err < row_bytes) + err = out->Write(writeline, rowBytes); + if (err < rowBytes) return err < B_OK ? Error((j_common_ptr)&cinfo, err) : Error((j_common_ptr)&cinfo, B_ERROR); } @@ -1445,7 +1487,7 @@ Decompress(BPositionIO *in, BPositionIO *out) Frees jpeg alocated memory Returns given error (B_ERROR by default) */ -status_t +static status_t Error(j_common_ptr cinfo, status_t error) { jpeg_destroy(cinfo); diff --git a/src/add-ons/translators/jpeg/JPEGTranslator.h b/src/add-ons/translators/jpeg/JPEGTranslator.h index 1dbc609c12..778b582ff4 100644 --- a/src/add-ons/translators/jpeg/JPEGTranslator.h +++ b/src/add-ons/translators/jpeg/JPEGTranslator.h @@ -215,10 +215,4 @@ EXTERN(void) be_jpeg_stdio_dest(j_compress_ptr cinfo, BPositionIO *outfile); // EXTERN(struct jpeg_error_mgr *) be_jpeg_std_error (struct jpeg_error_mgr * err, jpeg_settings * settings); // from "be_jerror.cpp" -// Main functions of translator :) -status_t Copy(BPositionIO *in, BPositionIO *out); -status_t Compress(BPositionIO *in, BPositionIO *out); -status_t Decompress(BPositionIO *in, BPositionIO *out); -status_t Error(j_common_ptr cinfo, status_t error = B_ERROR); - #endif // _JPEGTRANSLATOR_H_ diff --git a/src/add-ons/translators/jpeg/Jamfile b/src/add-ons/translators/jpeg/Jamfile index 23add10594..407f2df08e 100644 --- a/src/add-ons/translators/jpeg/Jamfile +++ b/src/add-ons/translators/jpeg/Jamfile @@ -9,6 +9,9 @@ SubDirC++Flags [ FDefines BEOS_R5_COMPATIBLE ] ; SubDirSysHdrs [ FDirName $(SUBDIR) libjpeg ] ; +SubDirSysHdrs [ FDirName $(SUBDIR) $(DOTDOT) raw ] ; + # for TIFF.h and ReadHelper.h + local jpeg_files = # libjpeg jcapimin.c @@ -82,6 +85,7 @@ Translator JPEGTranslator : be_jdatadst.cpp be_jdatasrc.cpp be_jerror.cpp + exif_parser.cpp JPEGTranslator.cpp $(jpeg_files) diff --git a/src/add-ons/translators/jpeg/exif_parser.cpp b/src/add-ons/translators/jpeg/exif_parser.cpp new file mode 100644 index 0000000000..24a89177a8 --- /dev/null +++ b/src/add-ons/translators/jpeg/exif_parser.cpp @@ -0,0 +1,327 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ + + +#include "exif_parser.h" + +#include + +#include + +#include +#include +#include + + +enum { + TAG_EXIF_OFFSET = 0x8769, + TAG_SUB_DIR_OFFSET = 0xa005, + + TAG_MAKER = 0x10f, + TAG_MODEL = 0x110, + TAG_ORIENTATION = 0x112, + TAG_EXPOSURE_TIME = 0x829a, + TAG_ISO = 0x8827, +}; + +static const convert_tag kDefaultTags[] = { + {TAG_MAKER, B_ANY_TYPE, "Maker"}, + {TAG_MODEL, B_ANY_TYPE, "Model"}, + {TAG_ORIENTATION, B_INT32_TYPE, "Orientation"}, + {TAG_EXPOSURE_TIME, B_DOUBLE_TYPE, "ExposureTime"}, + {TAG_ISO, B_INT32_TYPE, "ISO"}, +}; +static const size_t kNumDefaultTags = sizeof(kDefaultTags) + / sizeof(kDefaultTags[0]); + + +static status_t parse_tiff_directory(TReadHelper& read, BMessage& target, + const convert_tag* tags, size_t tagCount); + + +static status_t +add_to_message(TReadHelper& source, BMessage& target, tiff_tag& tag, + const char* name, type_code type) +{ + type_code defaultType = B_INT32_TYPE; + double doubleValue = 0.0; + int32 intValue = 0; + + switch (tag.type) { + case TIFF_STRING_TYPE: + { + if (type != B_ANY_TYPE && type != B_STRING_TYPE) + return B_BAD_VALUE; + + char* buffer = (char*)malloc(tag.length); + if (buffer == NULL) + return B_NO_MEMORY; + + source(buffer, tag.length); + + // remove trailing spaces + int32 i = tag.length; + while (--i > 0 && isspace(buffer[i]) || !buffer[i]) { + buffer[i] = '\0'; + } + + status_t status = target.AddString(name, buffer); + free(buffer); + + return status; + } + + case TIFF_UNDEFINED_TYPE: + { + if (type != B_ANY_TYPE && type != B_STRING_TYPE && type != B_RAW_TYPE) + return B_BAD_VALUE; + + char* buffer = (char*)malloc(tag.length); + if (buffer == NULL) + return B_NO_MEMORY; + + source(buffer, tag.length); + + status_t status; + if (type == B_STRING_TYPE) + status = target.AddString(name, buffer); + else + status = target.AddData(name, B_RAW_TYPE, buffer, tag.length); + + free(buffer); + + return status; + } + + // unsigned + case TIFF_UINT8_TYPE: + intValue = source.Next(); + break; + case TIFF_UINT16_TYPE: + defaultType = B_INT32_TYPE; + intValue = source.Next(); + break; + case TIFF_UINT32_TYPE: + defaultType = B_INT32_TYPE; + intValue = source.Next(); + break; + case TIFF_UFRACTION_TYPE: + { + defaultType = B_DOUBLE_TYPE; + double value = source.Next(); + doubleValue = value / source.Next(); + break; + } + + // signed + case TIFF_INT8_TYPE: + intValue = source.Next(); + break; + case TIFF_INT16_TYPE: + intValue = source.Next(); + break; + case TIFF_INT32_TYPE: + intValue = source.Next(); + break; + case TIFF_FRACTION_TYPE: + { + defaultType = B_DOUBLE_TYPE; + double value = source.Next(); + doubleValue = value / source.Next(); + } + + // floating point + case TIFF_FLOAT_TYPE: + defaultType = B_FLOAT_TYPE; + doubleValue = source.Next(); + break; + case TIFF_DOUBLE_TYPE: + defaultType = B_DOUBLE_TYPE; + doubleValue = source.Next(); + break; + + default: + return B_BAD_VALUE; + } + + if (defaultType == B_INT32_TYPE) + doubleValue = intValue; + else + intValue = int32(doubleValue + 0.5); + + if (type == B_ANY_TYPE) + type = defaultType; + + switch (type) { + case B_INT32_TYPE: + return target.AddInt32(name, intValue); + case B_FLOAT_TYPE: + return target.AddFloat(name, doubleValue); + case B_DOUBLE_TYPE: + return target.AddDouble(name, doubleValue); + + default: + return B_BAD_VALUE; + } +} + + +static const convert_tag* +find_convert_tag(uint16 id, const convert_tag* tags, size_t count) +{ + for (size_t i = 0; i < count; i++) { + if (tags[i].tag == id) + return &tags[i]; + } + + return NULL; +} + + +/*! + Reads a TIFF tag and positions the file stream to its data section +*/ +void +parse_tiff_tag(TReadHelper& read, tiff_tag& tag, off_t& offset) +{ + read(tag.tag); + read(tag.type); + read(tag.length); + + offset = read.Position() + 4; + + uint32 length = tag.length; + + switch (tag.type) { + case TIFF_UINT16_TYPE: + case TIFF_INT16_TYPE: + length *= 2; + break; + + case TIFF_UINT32_TYPE: + case TIFF_INT32_TYPE: + case TIFF_FLOAT_TYPE: + length *= 4; + break; + + case TIFF_UFRACTION_TYPE: + case TIFF_FRACTION_TYPE: + case TIFF_DOUBLE_TYPE: + length *= 8; + break; + + default: + break; + } + + if (length > 4) { + uint32 position; + read(position); + + read.Seek(position, SEEK_SET); + } +} + + +static status_t +parse_tiff_directory(TReadHelper& read, off_t offset, BMessage& target, + const convert_tag* convertTags, size_t convertTagCount) +{ + read.Seek(offset, SEEK_SET); + + uint16 tags; + read(tags); + if (tags > 512) + return B_BAD_DATA; + + while (tags--) { + off_t nextOffset; + tiff_tag tag; + parse_tiff_tag(read, tag, nextOffset); + + //printf("TAG %u\n", tag.tag); + + switch (tag.tag) { + case TAG_EXIF_OFFSET: + case TAG_SUB_DIR_OFFSET: + { + status_t status = parse_tiff_directory(read, target, + convertTags, convertTagCount); + if (status < B_OK) + return status; + break; + } + + default: + const convert_tag* convertTag = find_convert_tag(tag.tag, + convertTags, convertTagCount); + if (convertTag != NULL) { + add_to_message(read, target, tag, convertTag->name, + convertTag->type); + } + break; + } + read.Seek(nextOffset, SEEK_SET); + } + + return B_OK; +} + + +static status_t +parse_tiff_directory(TReadHelper& read, BMessage& target, + const convert_tag* tags, size_t tagCount) +{ + while (true) { + int32 offset; + read(offset); + if (offset == 0) + break; + + status_t status = parse_tiff_directory(read, offset, target, + tags, tagCount); + if (status < B_OK) + return status; + } + + return B_OK; +} + + +// #pragma mark - + + +status_t +convert_exif_to_message(BPositionIO& source, BMessage& target, + const convert_tag* tags, size_t tagCount) +{ + TReadHelper read(source); + + uint16 endian; + read(endian); + if (endian != 'MM' && endian != 'II') + return B_BAD_TYPE; + +#if B_HOST_IS_LENDIAN + read.SetSwap(endian == 'MM'); +#else + read.SetSwap(endian == 'II'); +#endif + + int16 magic; + read(magic); + if (magic != 42) + return B_BAD_TYPE; + + return parse_tiff_directory(read, target, tags, tagCount); +} + + +status_t +convert_exif_to_message(BPositionIO& source, BMessage& target) +{ + return convert_exif_to_message(source, target, kDefaultTags, + kNumDefaultTags); +} diff --git a/src/add-ons/translators/jpeg/exif_parser.h b/src/add-ons/translators/jpeg/exif_parser.h new file mode 100644 index 0000000000..9527c2c343 --- /dev/null +++ b/src/add-ons/translators/jpeg/exif_parser.h @@ -0,0 +1,25 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef EXIF_PARSER_H +#define EXIF_PARSER_H + + +#include +#include + +class BMessage; + + +struct convert_tag { + uint16 tag; + type_code type; + const char* name; +}; + +status_t convert_exif_to_message(BPositionIO& source, BMessage& target); +status_t convert_exif_to_message_etc(BPositionIO& source, BMessage& target, + const convert_tag* tags, size_t tagCount); + +#endif // EXIF_PARSER_H