diff options
Diffstat (limited to 'libs/freeimage/src/Metadata/Exif.cpp')
-rw-r--r-- | libs/freeimage/src/Metadata/Exif.cpp | 1253 |
1 files changed, 1253 insertions, 0 deletions
diff --git a/libs/freeimage/src/Metadata/Exif.cpp b/libs/freeimage/src/Metadata/Exif.cpp new file mode 100644 index 0000000000..1d456ab3bd --- /dev/null +++ b/libs/freeimage/src/Metadata/Exif.cpp @@ -0,0 +1,1253 @@ +// ========================================================== +// Metadata functions implementation +// Exif metadata model +// +// Design and implementation by +// - Hervé Drolon (drolon@infonie.fr) +// - Mihail Naydenov (mnaydenov@users.sourceforge.net) +// +// Based on the following implementations: +// - metadata-extractor : http://www.drewnoakes.com/code/exif/ +// - jhead : http://www.sentex.net/~mwandel/jhead/ +// - ImageMagick : http://www.imagemagick.org/ +// +// 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 + +#include "../stdafx.h" + +#include "FreeImageTag.h" + +// ========================================================== +// Exif JPEG routines +// ========================================================== + +#define EXIF_NUM_FORMATS 12 + +#define TAG_EXIF_OFFSET 0x8769 // Exif IFD Pointer +#define TAG_GPS_OFFSET 0x8825 // GPS Info IFD Pointer +#define TAG_INTEROP_OFFSET 0xA005 // Interoperability IFD Pointer +#define TAG_MAKER_NOTE 0x927C // Maker note + +// CANON cameras have some funny bespoke fields that need further processing... +#define TAG_CANON_CAMERA_STATE_0x01 0x0001 // tags under tag 0x001 (CameraSettings) +#define TAG_CANON_CAMERA_STATE_0x02 0x0002 // tags under tag 0x002 (FocalLength) +#define TAG_CANON_CAMERA_STATE_0x04 0x0004 // tags under tag 0x004 (ShotInfo) +#define TAG_CANON_CAMERA_STATE_0x12 0x0012 // tags under tag 0x012 (AFInfo) +#define TAG_CANON_CAMERA_STATE_0xA0 0x00A0 // tags under tag 0x0A0 (ProcessingInfo) +#define TAG_CANON_CAMERA_STATE_0xE0 0x00E0 // tags under tag 0x0E0 (SensorInfo) + + +// ===================================================================== +// Reimplementation of strnicmp (it is not supported on some systems) +// ===================================================================== + +/** +Compare characters of two strings without regard to case. +@param s1 Null-terminated string to compare. +@param s2 Null-terminated string to compare. +@param len Number of characters to compare +@return Returns 0 if s1 substring identical to s2 substring +*/ +static int +FreeImage_strnicmp(const char *s1, const char *s2, size_t len) { + unsigned char c1, c2; + + if(!s1 || !s2) return -1; + + c1 = 0; c2 = 0; + if(len) { + do { + c1 = *s1; c2 = *s2; + s1++; s2++; + if (!c1) + break; + if (!c2) + break; + if (c1 == c2) + continue; + c1 = (BYTE)tolower(c1); + c2 = (BYTE)tolower(c2); + if (c1 != c2) + break; + } while (--len); + } + return (int)c1 - (int)c2; +} + + +// ---------------------------------------------------------- +// Little Endian / Big Endian io routines +// ---------------------------------------------------------- + +static short +ReadInt16(BOOL msb_order, const void *buffer) { + short value; + + if(msb_order) { + value = (short)((((BYTE*) buffer)[0] << 8) | ((BYTE*) buffer)[1]); + return value; + } + value = (short)((((BYTE*) buffer)[1] << 8) | ((BYTE*) buffer)[0]); + return value; +} + +static LONG +ReadInt32(BOOL msb_order, const void *buffer) { + LONG value; + + if(msb_order) { + value = (LONG)((((BYTE*) buffer)[0] << 24) | (((BYTE*) buffer)[1] << 16) | (((BYTE*) buffer)[2] << 8) | (((BYTE*) buffer)[3])); + return value; + } + value = (LONG)((((BYTE*) buffer)[3] << 24) | (((BYTE*) buffer)[2] << 16) | (((BYTE*) buffer)[1] << 8 ) | (((BYTE*) buffer)[0])); + return value; +} + +static WORD +ReadUint16(BOOL msb_order, const void *buffer) { + WORD value; + + if(msb_order) { + value = (WORD) ((((BYTE*) buffer)[0] << 8) | ((BYTE*) buffer)[1]); + return value; + } + value = (WORD) ((((BYTE*) buffer)[1] << 8) | ((BYTE*) buffer)[0]); + return value; +} + +static DWORD +ReadUint32(BOOL msb_order, const void *buffer) { + return ((DWORD) ReadInt32(msb_order, buffer) & 0xFFFFFFFF); +} + +// ---------------------------------------------------------- +// Exif JPEG markers routines +// ---------------------------------------------------------- + +/** +Process a IFD offset +Returns the offset and the metadata model for this tag +*/ +static void +processIFDOffset(FITAG *tag, const char *pval, BOOL msb_order, DWORD *subdirOffset, TagLib::MDMODEL *md_model) { + // get the IFD offset + *subdirOffset = ReadUint32(msb_order, pval); + + // select a tag info table + switch(FreeImage_GetTagID(tag)) { + case TAG_EXIF_OFFSET: + *md_model = TagLib::EXIF_EXIF; + break; + case TAG_GPS_OFFSET: + *md_model = TagLib::EXIF_GPS; + break; + case TAG_INTEROP_OFFSET: + *md_model = TagLib::EXIF_INTEROP; + break; + } +} + +/** +Process a maker note IFD offset +Returns the offset and the metadata model for this tag +*/ +static void +processMakerNote(FIBITMAP *dib, const char *pval, BOOL msb_order, DWORD *subdirOffset, TagLib::MDMODEL *md_model) { + FITAG *tagMake = NULL; + + *subdirOffset = 0; + *md_model = TagLib::UNKNOWN; + + // Determine the camera model and makernote format + // WARNING: note that Maker may be NULL sometimes so check its value before using it + // (NULL pointer checking is done by FreeImage_strnicmp) + FreeImage_GetMetadata(FIMD_EXIF_MAIN, dib, "Make", &tagMake); + const char *Maker = (char*)FreeImage_GetTagValue(tagMake); + + if((memcmp("OLYMP\x00\x01", pval, 7) == 0) || (memcmp("OLYMP\x00\x02", pval, 7) == 0) || (memcmp("EPSON", pval, 5) == 0) || (memcmp("AGFA", pval, 4) == 0)) { + // Olympus Type 1 Makernote + // Epson and Agfa use Olympus maker note standard, + // see: http://www.ozhiker.com/electronics/pjmt/jpeg_info/ + *md_model = TagLib::EXIF_MAKERNOTE_OLYMPUSTYPE1; + *subdirOffset = 8; + } + else if(memcmp("OLYMPUS\x00\x49\x49\x03\x00", pval, 12) == 0) { + // Olympus Type 2 Makernote + // !!! NOT YET SUPPORTED !!! + *subdirOffset = 0; + *md_model = TagLib::UNKNOWN; + } + else if(memcmp("Nikon", pval, 5) == 0) { + /* There are two scenarios here: + * Type 1: + * :0000: 4E 69 6B 6F 6E 00 01 00-05 00 02 00 02 00 06 00 Nikon........... + * :0010: 00 00 EC 02 00 00 03 00-03 00 01 00 00 00 06 00 ................ + * Type 3: + * :0000: 4E 69 6B 6F 6E 00 02 00-00 00 4D 4D 00 2A 00 00 Nikon....MM.*... + * :0010: 00 08 00 1E 00 01 00 07-00 00 00 04 30 32 30 30 ............0200 + */ + if (pval[6] == 1) { + // Nikon type 1 Makernote + *md_model = TagLib::EXIF_MAKERNOTE_NIKONTYPE1; + *subdirOffset = 8; + } else if (pval[6] == 2) { + // Nikon type 3 Makernote + *md_model = TagLib::EXIF_MAKERNOTE_NIKONTYPE3; + *subdirOffset = 18; + } else { + // Unsupported makernote data ignored + *subdirOffset = 0; + *md_model = TagLib::UNKNOWN; + } + } else if(Maker && (FreeImage_strnicmp("NIKON", Maker, 5) == 0)) { + // Nikon type 2 Makernote + *md_model = TagLib::EXIF_MAKERNOTE_NIKONTYPE2; + *subdirOffset = 0; + } else if(Maker && (FreeImage_strnicmp("Canon", Maker, 5) == 0)) { + // Canon Makernote + *md_model = TagLib::EXIF_MAKERNOTE_CANON; + *subdirOffset = 0; + } else if(Maker && (FreeImage_strnicmp("Casio", Maker, 5) == 0)) { + // Casio Makernote + if(memcmp("QVC\x00\x00\x00", pval, 6) == 0) { + // Casio Type 2 Makernote + *md_model = TagLib::EXIF_MAKERNOTE_CASIOTYPE2; + *subdirOffset = 6; + } else { + // Casio Type 1 Makernote + *md_model = TagLib::EXIF_MAKERNOTE_CASIOTYPE1; + *subdirOffset = 0; + } + } else if ((memcmp("FUJIFILM", pval, 8) == 0) || (Maker && (FreeImage_strnicmp("Fujifilm", Maker, 8) == 0))) { + // Fujifile Makernote + // Fujifilm's Makernote always use little-endian order altough the Exif section maybe in little-endian order or in big-endian order. + // If msb_order == TRUE, the Makernote won't be read: + // the value of ifdStart will be 0x0c000000 instead of 0x0000000c and the MakerNote section will be discarded later + // in jpeg_read_exif_dir because the IFD is too high + *md_model = TagLib::EXIF_MAKERNOTE_FUJIFILM; + DWORD ifdStart = ReadUint32(msb_order, pval + 8); + *subdirOffset = ifdStart; + } + else if(memcmp("KYOCERA\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x00\x00\x00", pval, 22) == 0) { + *md_model = TagLib::EXIF_MAKERNOTE_KYOCERA; + *subdirOffset = 22; + } + else if(Maker && (FreeImage_strnicmp("Minolta", Maker, 7) == 0)) { + // Minolta maker note + *md_model = TagLib::EXIF_MAKERNOTE_MINOLTA; + *subdirOffset = 0; + } + else if(memcmp("Panasonic\x00\x00\x00", pval, 12) == 0) { + // Panasonic maker note + *md_model = TagLib::EXIF_MAKERNOTE_PANASONIC; + *subdirOffset = 12; + } + else if(Maker && (FreeImage_strnicmp("LEICA", Maker, 5) == 0)) { + // Leica maker note + if(memcmp("LEICA\x00\x00\x00", pval, 8) == 0) { + // not yet supported makernote data ignored + *subdirOffset = 0; + *md_model = TagLib::UNKNOWN; + } + } + else if(Maker && ((FreeImage_strnicmp("Pentax", Maker, 6) == 0) || (FreeImage_strnicmp("Asahi", Maker, 5) == 0))) { + // Pentax maker note + if(memcmp("AOC\x00", pval, 4) == 0) { + // Type 2 Pentax Makernote + *md_model = TagLib::EXIF_MAKERNOTE_PENTAX; + *subdirOffset = 6; + } else { + // Type 1 Pentax Makernote + *md_model = TagLib::EXIF_MAKERNOTE_ASAHI; + *subdirOffset = 0; + } + } + else if((memcmp("SONY CAM\x20\x00\x00\x00", pval, 12) == 0) || (memcmp("SONY DSC\x20\x00\x00\x00", pval, 12) == 0)) { + *md_model = TagLib::EXIF_MAKERNOTE_SONY; + *subdirOffset = 12; + } + else if((memcmp("SIGMA\x00\x00\x00", pval, 8) == 0) || (memcmp("FOVEON\x00\x00", pval, 8) == 0)) { + FITAG *tagModel = NULL; + FreeImage_GetMetadata(FIMD_EXIF_MAIN, dib, "Model", &tagModel); + const char *Model = (char*)FreeImage_GetTagValue(tagModel); + if(Model && (memcmp("SIGMA SD1\x00", Model, 10) == 0)) { + // Sigma SD1 maker note + *subdirOffset = 10; + *md_model = TagLib::EXIF_MAKERNOTE_SIGMA_SD1; + } else { + // Sigma / Foveon makernote + *subdirOffset = 10; + *md_model = TagLib::EXIF_MAKERNOTE_SIGMA_FOVEON; + } + } +} + +/** +Process a Canon maker note tag. +A single Canon tag may contain many other tags within. +*/ +static BOOL +processCanonMakerNoteTag(FIBITMAP *dib, FITAG *tag) { + char defaultKey[16]; + DWORD startIndex = 0; + TagLib& s = TagLib::instance(); + + WORD tag_id = FreeImage_GetTagID(tag); + + int subTagTypeBase = 0; + + switch(tag_id) { + case TAG_CANON_CAMERA_STATE_0x01: + subTagTypeBase = 0xC100; + startIndex = 1; + break; + case TAG_CANON_CAMERA_STATE_0x02: + subTagTypeBase = 0xC200; + startIndex = 0; + break; + case TAG_CANON_CAMERA_STATE_0x04: + subTagTypeBase = 0xC400; + startIndex = 1; + break; + case TAG_CANON_CAMERA_STATE_0x12: + subTagTypeBase = 0x1200; + startIndex = 0; + break; + case TAG_CANON_CAMERA_STATE_0xA0: + subTagTypeBase = 0xCA00; + startIndex = 1; + break; + case TAG_CANON_CAMERA_STATE_0xE0: + subTagTypeBase = 0xCE00; + startIndex = 1; + break; + + default: + { + // process as a normal tag + + // get the tag key and description + const char *key = s.getTagFieldName(TagLib::EXIF_MAKERNOTE_CANON, tag_id, defaultKey); + FreeImage_SetTagKey(tag, key); + const char *description = s.getTagDescription(TagLib::EXIF_MAKERNOTE_CANON, tag_id); + FreeImage_SetTagDescription(tag, description); + + // store the tag + if(key) { + FreeImage_SetMetadata(FIMD_EXIF_MAKERNOTE, dib, key, tag); + } + + return TRUE; + } + break; + + } + + WORD *pvalue = (WORD*)FreeImage_GetTagValue(tag); + + // create a tag + FITAG *canonTag = FreeImage_CreateTag(); + if(!canonTag) return FALSE; + + // we intentionally skip the first array member (if needed) + for (DWORD i = startIndex; i < FreeImage_GetTagCount(tag); i++) { + + tag_id = (WORD)(subTagTypeBase + i); + + FreeImage_SetTagID(canonTag, tag_id); + FreeImage_SetTagType(canonTag, FIDT_SHORT); + FreeImage_SetTagCount(canonTag, 1); + FreeImage_SetTagLength(canonTag, 2); + FreeImage_SetTagValue(canonTag, &pvalue[i]); + + // get the tag key and description + const char *key = s.getTagFieldName(TagLib::EXIF_MAKERNOTE_CANON, tag_id, defaultKey); + FreeImage_SetTagKey(canonTag, key); + const char *description = s.getTagDescription(TagLib::EXIF_MAKERNOTE_CANON, tag_id); + FreeImage_SetTagDescription(canonTag, description); + + // store the tag + if(key) { + FreeImage_SetMetadata(FIMD_EXIF_MAKERNOTE, dib, key, canonTag); + } + } + + // delete the tag + FreeImage_DeleteTag(canonTag); + + return TRUE; +} + +/** +Process a standard Exif tag +*/ +static void +processExifTag(FIBITMAP *dib, FITAG *tag, char *pval, BOOL msb_order, TagLib::MDMODEL md_model) { + char defaultKey[16]; + int n; + DWORD i; + + // allocate a buffer to store the tag value + BYTE *exif_value = (BYTE*)malloc(FreeImage_GetTagLength(tag) * sizeof(BYTE)); + if(NULL == exif_value) { + // out of memory ... + return; + } + memset(exif_value, 0, FreeImage_GetTagLength(tag) * sizeof(BYTE)); + + // get the tag value + switch(FreeImage_GetTagType(tag)) { + + case FIDT_SHORT: + { + WORD *value = (WORD*)&exif_value[0]; + for(i = 0; i < FreeImage_GetTagCount(tag); i++) { + value[i] = ReadUint16(msb_order, pval + i * sizeof(WORD)); + } + FreeImage_SetTagValue(tag, value); + break; + } + case FIDT_SSHORT: + { + short *value = (short*)&exif_value[0]; + for(i = 0; i < FreeImage_GetTagCount(tag); i++) { + value[i] = ReadInt16(msb_order, pval + i * sizeof(short)); + } + FreeImage_SetTagValue(tag, value); + break; + } + case FIDT_LONG: + { + DWORD *value = (DWORD*)&exif_value[0]; + for(i = 0; i < FreeImage_GetTagCount(tag); i++) { + value[i] = ReadUint32(msb_order, pval + i * sizeof(DWORD)); + } + FreeImage_SetTagValue(tag, value); + break; + } + case FIDT_SLONG: + { + LONG *value = (LONG*)&exif_value[0]; + for(i = 0; i < FreeImage_GetTagCount(tag); i++) { + value[i] = ReadInt32(msb_order, pval + i * sizeof(LONG)); + } + FreeImage_SetTagValue(tag, value); + break; + } + case FIDT_RATIONAL: + { + n = sizeof(DWORD); + + DWORD *value = (DWORD*)&exif_value[0]; + for(i = 0; i < 2 * FreeImage_GetTagCount(tag); i++) { + // read a sequence of (numerator, denominator) + value[i] = ReadUint32(msb_order, n*i + (char*)pval); + } + FreeImage_SetTagValue(tag, value); + break; + } + case FIDT_SRATIONAL: + { + n = sizeof(LONG); + + LONG *value = (LONG*)&exif_value[0]; + for(i = 0; i < 2 * FreeImage_GetTagCount(tag); i++) { + // read a sequence of (numerator, denominator) + value[i] = ReadInt32(msb_order, n*i + (char*)pval); + } + FreeImage_SetTagValue(tag, value); + break; + } + case FIDT_BYTE: + case FIDT_ASCII: + case FIDT_SBYTE: + case FIDT_UNDEFINED: + case FIDT_FLOAT: + case FIDT_DOUBLE: + default: + FreeImage_SetTagValue(tag, pval); + break; + } + + if(md_model == TagLib::EXIF_MAKERNOTE_CANON) { + // A single Canon tag can have multiple values within + processCanonMakerNoteTag(dib, tag); + } + else { + TagLib& s = TagLib::instance(); + + WORD tag_id = FreeImage_GetTagID(tag); + + // get the tag key and description + const char *key = s.getTagFieldName(md_model, tag_id, defaultKey); + FreeImage_SetTagKey(tag, key); + const char *description = s.getTagDescription(md_model, tag_id); + FreeImage_SetTagDescription(tag, description); + + // store the tag + if(key) { + FreeImage_SetMetadata(s.getFreeImageModel(md_model), dib, key, tag); + } + } + + + // free the temporary buffer + free(exif_value); + +} + +/** +Process Exif directory + +@param dib Input FIBITMAP +@param tiffp Pointer to the TIFF header +@param dwOffsetIfd0 Offset to the 0th IFD (first IFD) +@param dwLength Length of the Exif file +@param dwProfileOffset File offset to be used when reading 'offset/value' tags +@param msb_order Endianness order of the Exif file (TRUE if big-endian, FALSE if little-endian) +@param starting_md_model Metadata model of the IFD (should be TagLib::EXIF_MAIN for a jpeg) +@return Returns TRUE if sucessful, returns FALSE otherwise +*/ +static BOOL +jpeg_read_exif_dir(FIBITMAP *dib, const BYTE *tiffp, DWORD dwOffsetIfd0, DWORD dwLength, DWORD dwProfileOffset, BOOL msb_order, TagLib::MDMODEL starting_md_model) { + WORD de, nde; + + std::stack<WORD> destack; // directory entries stack + std::stack<const BYTE*> ifdstack; // IFD stack + std::stack<TagLib::MDMODEL> modelstack; // metadata model stack + + // Keep a list of already visited IFD to avoid stack overflows + // when recursive/cyclic directory structures exist. + // This kind of recursive Exif file was encountered with Kodak images coming from + // KODAK PROFESSIONAL DCS Photo Desk JPEG Export v3.2 W + std::map<DWORD, int> visitedIFD; + + /* + "An Image File Directory (IFD) consists of a 2-byte count of the number of directory + entries (i.e. the number of fields), followed by a sequence of 12-byte field + entries, followed by a 4-byte offset of the next IFD (or 0 if none)." + The "next IFD" (1st IFD) is the thumbnail. + */ + #define DIR_ENTRY_ADDR(_start, _entry) (_start + 2 + (12 * _entry)) + + // set the metadata model to Exif + + TagLib::MDMODEL md_model = starting_md_model; + + // set the pointer to the first IFD (0th IFD) and follow it were it leads. + + const BYTE *ifd0th = (BYTE*)tiffp + (size_t)dwOffsetIfd0; + + const BYTE *ifdp = ifd0th; + + de = 0; + + do { + // if there is anything on the stack then pop it off + if(!destack.empty()) { + ifdp = ifdstack.top(); ifdstack.pop(); + de = destack.top(); destack.pop(); + md_model = modelstack.top(); modelstack.pop(); + } + + // remember that we've visited this directory and entry so that we don't visit it again later + DWORD visited = (DWORD)( (((size_t)ifdp & 0xFFFF) << 16) | (size_t)de ); + if(visitedIFD.find(visited) != visitedIFD.end()) { + continue; + } else { + visitedIFD[visited] = 1; // processed + } + + // determine how many entries there are in the current IFD + nde = ReadUint16(msb_order, ifdp); + if (((size_t)(ifdp - tiffp) + 12 * nde) > (size_t)dwLength) { + // suspicious IFD offset, ignore + continue; + } + + for(; de < nde; de++) { + char *pde = NULL; // pointer to the directory entry + char *pval = NULL; // pointer to the tag value + + // create a tag + FITAG *tag = FreeImage_CreateTag(); + if(!tag) return FALSE; + + // point to the directory entry + pde = (char*) DIR_ENTRY_ADDR(ifdp, de); + + // get the tag ID + WORD tag_id = ReadUint16(msb_order, pde); + FreeImage_SetTagID(tag, tag_id); + + // get the tag type + WORD tag_type = (WORD)ReadUint16(msb_order, pde + 2); + if((tag_type - 1) >= EXIF_NUM_FORMATS) { + // a problem occured : delete the tag (not free'd after) + FreeImage_DeleteTag(tag); + // break out of the for loop + break; + } + FreeImage_SetTagType(tag, (FREE_IMAGE_MDTYPE)tag_type); + + // get number of components + DWORD tag_count = ReadUint32(msb_order, pde + 4); + FreeImage_SetTagCount(tag, tag_count); + + // check that tag length (size of the tag value in bytes) will fit in a DWORD + unsigned tag_data_width = FreeImage_TagDataWidth(FreeImage_GetTagType(tag)); + if (tag_data_width != 0 && FreeImage_GetTagCount(tag) > ~(DWORD)0 / tag_data_width) { + FreeImage_DeleteTag(tag); + // jump to next entry + continue; + } + FreeImage_SetTagLength(tag, FreeImage_GetTagCount(tag) * tag_data_width); + + if(FreeImage_GetTagLength(tag) <= 4) { + // 4 bytes or less and value is in the dir entry itself + pval = pde + 8; + } else { + // if its bigger than 4 bytes, the directory entry contains an offset + DWORD offset_value = ReadUint32(msb_order, pde + 8); + // the offset can be relative to tiffp or to an external reference (see JPEG-XR) + if(dwProfileOffset) { + offset_value -= dwProfileOffset; + } + // first check if offset exceeds buffer, at this stage FreeImage_GetTagLength may return invalid data + if(offset_value > dwLength) { + // a problem occured : delete the tag (not free'd after) + FreeImage_DeleteTag(tag); + // jump to next entry + continue; + } + // now check that length does not exceed the buffer size + if(FreeImage_GetTagLength(tag) > dwLength - offset_value){ + // a problem occured : delete the tag (not free'd after) + FreeImage_DeleteTag(tag); + // jump to next entry + continue; + } + pval = (char*)(tiffp + offset_value); + } + + // check for a IFD offset + BOOL isIFDOffset = FALSE; + switch(FreeImage_GetTagID(tag)) { + case TAG_EXIF_OFFSET: + case TAG_GPS_OFFSET: + case TAG_INTEROP_OFFSET: + case TAG_MAKER_NOTE: + isIFDOffset = TRUE; + break; + } + if(isIFDOffset) { + DWORD sub_offset = 0; + TagLib::MDMODEL next_mdmodel = md_model; + const BYTE *next_ifd = ifdp; + + // get offset and metadata model + if (FreeImage_GetTagID(tag) == TAG_MAKER_NOTE) { + processMakerNote(dib, pval, msb_order, &sub_offset, &next_mdmodel); + next_ifd = (BYTE*)pval + sub_offset; + } else { + processIFDOffset(tag, pval, msb_order, &sub_offset, &next_mdmodel); + next_ifd = (BYTE*)tiffp + sub_offset; + } + + if((sub_offset < dwLength) && (next_mdmodel != TagLib::UNKNOWN)) { + // push our current directory state onto the stack + ifdstack.push(ifdp); + // jump to the next entry + de++; + destack.push(de); + + // push our current metadata model + modelstack.push(md_model); + + // push new state onto of stack to cause a jump + ifdstack.push(next_ifd); + destack.push(0); + + // select a new metadata model + modelstack.push(next_mdmodel); + + // delete the tag as it won't be stored nor deleted in the for() loop + FreeImage_DeleteTag(tag); + + break; // break out of the for loop + } + else { + // unsupported camera model, canon maker tag or something unknown + // process as a standard tag + processExifTag(dib, tag, pval, msb_order, md_model); + } + + } else { + // process as a standard tag + processExifTag(dib, tag, pval, msb_order, md_model); + } + + // delete the tag + FreeImage_DeleteTag(tag); + + } // for(nde) + + // additional thumbnail data is skipped + + } while (!destack.empty()); + + // + // --- handle thumbnail data --- + // + + const WORD entriesCount0th = ReadUint16(msb_order, ifd0th); + + DWORD next_offset = ReadUint32(msb_order, DIR_ENTRY_ADDR(ifd0th, entriesCount0th)); + if((next_offset == 0) || (next_offset >= dwLength)) { + return TRUE; //< no thumbnail + } + + const BYTE* const ifd1st = (BYTE*)tiffp + next_offset; + const WORD entriesCount1st = ReadUint16(msb_order, ifd1st); + + unsigned thCompression = 0; + unsigned thOffset = 0; + unsigned thSize = 0; + + for(int e = 0; e < entriesCount1st; e++) { + + // point to the directory entry + const BYTE* base = DIR_ENTRY_ADDR(ifd1st, e); + + // check for buffer overflow + const size_t remaining = (size_t)base + 12 - (size_t)tiffp; + if(remaining >= dwLength) { + // bad IFD1 directory, ignore it + return FALSE; + } + + // get the tag ID + WORD tag = ReadUint16(msb_order, base); + // get the tag type + /*WORD type = */ReadUint16(msb_order, base + sizeof(WORD)); + // get number of components + /*DWORD count = */ReadUint32(msb_order, base + sizeof(WORD) + sizeof(WORD)); + // get the tag value + DWORD offset = ReadUint32(msb_order, base + sizeof(WORD) + sizeof(WORD) + sizeof(DWORD)); + + switch(tag) { + case TAG_COMPRESSION: + // Tiff Compression Tag (should be COMPRESSION_OJPEG (6), but is not always respected) + thCompression = offset; + break; + case TAG_JPEG_INTERCHANGE_FORMAT: + // Tiff JPEGInterchangeFormat Tag + thOffset = offset; + break; + case TAG_JPEG_INTERCHANGE_FORMAT_LENGTH: + // Tiff JPEGInterchangeFormatLength Tag + thSize = offset; + break; + // ### X and Y Resolution ignored, orientation ignored + case TAG_X_RESOLUTION: // XResolution + case TAG_Y_RESOLUTION: // YResolution + case TAG_RESOLUTION_UNIT: // ResolutionUnit + case TAG_ORIENTATION: // Orientation + break; + default: + break; + } + } + + if(/*thCompression != 6 ||*/ thOffset == 0 || thSize == 0) { + return TRUE; + } + + if(thOffset + thSize > dwLength) { + return TRUE; + } + + // load the thumbnail + + const BYTE *thLocation = tiffp + thOffset; + + FIMEMORY* hmem = FreeImage_OpenMemory(const_cast<BYTE*>(thLocation), thSize); + FIBITMAP* thumbnail = FreeImage_LoadFromMemory(FIF_JPEG, hmem); + FreeImage_CloseMemory(hmem); + + // store the thumbnail + FreeImage_SetThumbnail(dib, thumbnail); + // then delete it + FreeImage_Unload(thumbnail); + + return TRUE; +} + +// -------------------------------------------------------------------------- + +/** +Read and decode JPEG_APP1 marker (Exif profile) +@param dib Input FIBITMAP +@param data Pointer to the APP1 marker +@param length APP1 marker length +@return Returns TRUE if successful, FALSE otherwise +*/ +BOOL +jpeg_read_exif_profile(FIBITMAP *dib, const BYTE *data, unsigned length) { + // marker identifying string for Exif = "Exif\0\0" + BYTE exif_signature[6] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; + BYTE lsb_first[4] = { 0x49, 0x49, 0x2A, 0x00 }; // Classic TIFF signature - little-endian order + BYTE msb_first[4] = { 0x4D, 0x4D, 0x00, 0x2A }; // Classic TIFF signature - big-endian order + + // profile size is up to 32-bit + DWORD dwProfileLength = (DWORD)length; + BYTE *pbProfile = (BYTE*)data; + + // verify the identifying string + if(memcmp(exif_signature, pbProfile, sizeof(exif_signature)) == 0) { + // This is an Exif profile + // should contain a TIFF header with up to 2 IFDs (IFD stands for 'Image File Directory') + // 0th IFD : the image attributes, 1st IFD : may be used for thumbnail + + pbProfile += sizeof(exif_signature); + dwProfileLength -= sizeof(exif_signature); + + // read the TIFF header (8 bytes) + + // check the endianess order + + BOOL bBigEndian = TRUE; + + if(memcmp(pbProfile, lsb_first, sizeof(lsb_first)) == 0) { + // Exif section is in little-endian order + bBigEndian = FALSE; + } else { + if(memcmp(pbProfile, msb_first, sizeof(msb_first)) == 0) { + // Exif section is in big-endian order + bBigEndian = TRUE; + } else { + // Invalid Exif alignment marker + return FALSE; + } + } + + // this is the offset to the first IFD (Image File Directory) + DWORD dwFirstOffset = ReadUint32(bBigEndian, pbProfile + 4); + if (dwFirstOffset > dwProfileLength) { + // bad Exif data + return FALSE; + } + + /* + Note: as FreeImage 3.14.0, this test is no longer needed for images with similar suspicious offset + => tested with Pentax Optio 230, FujiFilm SP-2500 and Canon EOS 300D + if (dwFirstOffset < 8 || dwFirstOffset > 16) { + // This is usually set to 8 + // but PENTAX Optio 230 has it set differently, and uses it as offset. + FreeImage_OutputMessageProc(FIF_JPEG, "Exif: Suspicious offset of first IFD value"); + return FALSE; + } + */ + + // process Exif directories, starting with Exif-TIFF IFD + return jpeg_read_exif_dir(dib, pbProfile, dwFirstOffset, dwProfileLength, 0, bBigEndian, TagLib::EXIF_MAIN); + } + + return FALSE; +} + +// ========================================================== +// Exif JPEG helper routines +// ========================================================== + +/** +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 +*/ +BOOL +jpeg_read_exif_profile_raw(FIBITMAP *dib, const BYTE *profile, unsigned 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_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; +} + +// ========================================================== +// Exif JPEG-XR helper routines +// ========================================================== + +/** +Read and decode JPEG-XR Exif IFD +@param dib Input FIBITMAP +@param profile Pointer to the Exif marker +@param length Exif marker length +@param file_offset Reference offset in the original file of each tag value whose length is > 4 +@return Returns TRUE if successful, FALSE otherwise +*/ +BOOL +jpegxr_read_exif_profile(FIBITMAP *dib, const BYTE *profile, unsigned length, unsigned file_offset) { + // assume Little Endian order + BOOL bBigEndian = FALSE; + + // process Exif specific IFD + return jpeg_read_exif_dir(dib, profile, 0, length, file_offset, bBigEndian, TagLib::EXIF_EXIF); +} + +/** +Read and decode JPEG-XR Exif-GPS IFD +@param dib Input FIBITMAP +@param profile Pointer to the Exif-GPS profile +@param length Exif-GPS profile length +@param file_offset Reference offset in the original file of each tag value whose length is > 4 +@return Returns TRUE if successful, FALSE otherwise +*/ +BOOL +jpegxr_read_exif_gps_profile(FIBITMAP *dib, const BYTE *profile, unsigned length, unsigned file_offset) { + // assume Little Endian order + BOOL bBigEndian = FALSE; + + // process Exif GPS IFD + return jpeg_read_exif_dir(dib, profile, 0, length, file_offset, bBigEndian, TagLib::EXIF_GPS); +} + +// ========================================================== +// Exif common helper routines +// ========================================================== + +/** +Rotate a dib according to Exif info +@param dib Input / Output dib to rotate +@see PluginJPEG.cpp +*/ +void +RotateExif(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) && (FreeImage_GetTagID(tag) == TAG_ORIENTATION)) { + const WORD orientation = *((WORD *)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; + } + } + } +} + +// ========================================================== +// Exif TIFF JPEG-XR helper routines +// ========================================================== + +class PredicateTagIDCompare { +public: + bool operator()(FITAG *a, FITAG *b) { + WORD tag_id_a = FreeImage_GetTagID(a); + WORD tag_id_b = FreeImage_GetTagID(b); + return (tag_id_a < tag_id_b); + } +}; + +/** +Write a metadata model as a TIF IFD to a FIMEMORY handle. +The entries in the TIF IFD are sorted in ascending order by tag id. +The last entry is written as 0 (4 bytes) which means no more IFD to follow. +Supported metadata models are +<ul> +<li>FIMD_EXIF_MAIN +<li>FIMD_EXIF_EXIF +<li>FIMD_EXIF_GPS +<li>FIMD_EXIF_INTEROP +</ul> +The end of the buffer is filled with 4 bytes equal to 0 (end of IFD offset) + +@param dib Input FIBITMAP +@param md_model Metadata model to write +@param hmem Memory handle +@return Returns TRUE if successful, FALSE otherwise +@see tiff_get_ifd_profile +*/ +static BOOL +tiff_write_ifd(FIBITMAP *dib, FREE_IMAGE_MDMODEL md_model, FIMEMORY *hmem) { + FITAG *tag = NULL; + FIMETADATA *mdhandle = NULL; + std::vector<FITAG*> vTagList; + TagLib::MDMODEL internal_md_model; + + DWORD ifd_offset = 0; // WORD-aligned IFD value offset + + const BYTE empty_byte = 0; + + // start of the file + const long start_of_file = FreeImage_TellMemory(hmem); + + // get the metadata count + unsigned metadata_count = FreeImage_GetMetadataCount(md_model, dib); + if(metadata_count == 0) { + return FALSE; + } + + TagLib& s = TagLib::instance(); + + // check for supported metadata models + switch(md_model) { + case FIMD_EXIF_MAIN: + internal_md_model = TagLib::EXIF_MAIN; + break; + case FIMD_EXIF_EXIF: + internal_md_model = TagLib::EXIF_EXIF; + break; + case FIMD_EXIF_GPS: + internal_md_model = TagLib::EXIF_GPS; + break; + case FIMD_EXIF_INTEROP: + internal_md_model = TagLib::EXIF_INTEROP; + break; + default: + return FALSE; + } + + try { + // 1) according to the TIFF specifications, + // the entries in a TIF IFD must be sorted in ascending order by tag id + + // store the tags into a vector + vTagList.reserve(metadata_count); + mdhandle = FreeImage_FindFirstMetadata(md_model, dib, &tag); + if(mdhandle) { + // parse the tags and store them inside vTagList + do { + // rewrite the tag id using FreeImage internal database + // (in case the tag id is wrong or missing) + const char *key = FreeImage_GetTagKey(tag); + int tag_id = s.getTagID(internal_md_model, key); + if(tag_id != -1) { + // this is a known tag, set the tag ID + FreeImage_SetTagID(tag, (WORD)tag_id); + // record the tag + vTagList.push_back(tag); + } + // else ignore this tag + } while(FreeImage_FindNextMetadata(mdhandle, &tag)); + + FreeImage_FindCloseMetadata(mdhandle); + + // sort the vector by tag id + std::sort(vTagList.begin(), vTagList.end(), PredicateTagIDCompare()); + + // update the metadata_count + metadata_count = (unsigned)vTagList.size(); + + } else { + throw(1); + } + + // 2) prepare the place for each IFD entries. + + /* + An Image File Directory (IFD) consists of a 2-byte count of the number of directory entries (i.e., the number of fields), + followed by a sequence of 12-byte field entries, + followed by a 4-byte offset of the next IFD (or 0 if none). Do not forget to write the 4 bytes of 0 after the last IFD. + */ + + { + // prepare place for 2 bytes for number of entries + 12 bytes for each entry + unsigned ifd_size = 2 + 12 * metadata_count; + FreeImage_WriteMemory(&empty_byte, 1, ifd_size, hmem); + // record the offset used to write values > 4-bytes + ifd_offset = FreeImage_TellMemory(hmem); + // rewind + FreeImage_SeekMemory(hmem, start_of_file, SEEK_SET); + } + + // 3) write each IFD entry in tag id ascending order + + // number of directory entries + WORD nde = (WORD)metadata_count; + FreeImage_WriteMemory(&nde, 1, 2, hmem); + + // for each entry ... + for(unsigned i = 0; i < metadata_count; i++) { + FITAG *tag = vTagList[i]; + // tag id + WORD tag_id = FreeImage_GetTagID(tag); + FreeImage_WriteMemory(&tag_id, 1, 2, hmem); + // tag type (compliant with TIFF specification) + WORD tag_type = (WORD)FreeImage_GetTagType(tag); + FreeImage_WriteMemory(&tag_type, 1, 2, hmem); + // tag count + DWORD tag_count = FreeImage_GetTagCount(tag); + FreeImage_WriteMemory(&tag_count, 1, 4, hmem); + // tag value or offset (results are in BYTE's units) + unsigned tag_length = FreeImage_GetTagLength(tag); + if(tag_length <= 4) { + // 4 bytes or less, write the value (left justified) + const BYTE *tag_value = (BYTE*)FreeImage_GetTagValue(tag); + FreeImage_WriteMemory(tag_value, 1, tag_length, hmem); + for(unsigned k = tag_length; k < 4; k++) { + FreeImage_WriteMemory(&empty_byte, 1, 1, hmem); + } + } else { + // write an offset + FreeImage_WriteMemory(&ifd_offset, 1, 4, hmem); + // write the value + long current_position = FreeImage_TellMemory(hmem); + FreeImage_SeekMemory(hmem, ifd_offset, SEEK_SET); + FreeImage_WriteMemory(FreeImage_GetTagValue(tag), 1, tag_length, hmem); + if(tag_length & 1) { + // align to the next WORD boundary + FreeImage_WriteMemory(&empty_byte, 1, 1, hmem); + } + // next offset to use + ifd_offset = FreeImage_TellMemory(hmem); + // rewind + FreeImage_SeekMemory(hmem, current_position, SEEK_SET); + } + } + + // end-of-IFD or next IFD (0 == none) + FreeImage_SeekMemory(hmem, ifd_offset, SEEK_SET); + FreeImage_WriteMemory(&empty_byte, 1, 4, hmem); + + return TRUE; + } + catch(int) { + return FALSE; + } +} + +/** +Write a metadata model as a TIF IFD, returns the IFD as a buffer. +The buffer is allocated by the function and must be freed by the caller, using 'free'. +@param dib Input FIBITMAP +@param md_model Metadata model to write +@param ppbProfile Returned buffer +@param uProfileLength Returned buffer size +@return Returns TRUE if successful, FALSE otherwise +@see tiff_write_ifd +*/ +BOOL +tiff_get_ifd_profile(FIBITMAP *dib, FREE_IMAGE_MDMODEL md_model, BYTE **ppbProfile, unsigned *uProfileLength) { + FIMEMORY *hmem = NULL; + + try { + // open a memory stream + hmem = FreeImage_OpenMemory(NULL, 0); + if(!hmem) { + throw(1); + } + + // write the metadata model as a TIF IFD + BOOL bResult = tiff_write_ifd(dib, md_model, hmem); + + if(bResult) { + BYTE *data = NULL; + DWORD size_in_bytes = 0; + + // get a pointer to the stream buffer + FreeImage_AcquireMemory(hmem, &data, &size_in_bytes); + + // (re-)allocate output buffer + BYTE *pbProfile = *ppbProfile; + pbProfile = (BYTE*)realloc(pbProfile, size_in_bytes); + if(!pbProfile) { + throw(1); + } else { + // copy IFD + memcpy(pbProfile, data, size_in_bytes); + *ppbProfile = pbProfile; + *uProfileLength = size_in_bytes; + } + } + + // free the memory stream + FreeImage_CloseMemory(hmem); + + return bResult; + + } catch(int) { + FreeImage_CloseMemory(hmem); + return FALSE; + } +} |