summaryrefslogtreecommitdiff
path: root/plugins/FreeImage/src/Metadata/Exif.cpp
diff options
context:
space:
mode:
authorVadim Dashevskiy <watcherhd@gmail.com>2012-10-12 10:49:29 +0000
committerVadim Dashevskiy <watcherhd@gmail.com>2012-10-12 10:49:29 +0000
commit557f2816f5cd30705dec989c97a8a67c026d36d1 (patch)
tree4900789ab5d52219bf065744a61f9ea5863b3428 /plugins/FreeImage/src/Metadata/Exif.cpp
parent69fe21d6e2262d6fb2b14278b7a20364468cbdcf (diff)
FreeImage: folders restructurization
git-svn-id: http://svn.miranda-ng.org/main/trunk@1884 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c
Diffstat (limited to 'plugins/FreeImage/src/Metadata/Exif.cpp')
-rw-r--r--plugins/FreeImage/src/Metadata/Exif.cpp859
1 files changed, 859 insertions, 0 deletions
diff --git a/plugins/FreeImage/src/Metadata/Exif.cpp b/plugins/FreeImage/src/Metadata/Exif.cpp
new file mode 100644
index 0000000000..7d0955c5db
--- /dev/null
+++ b/plugins/FreeImage/src/Metadata/Exif.cpp
@@ -0,0 +1,859 @@
+// ==========================================================
+// 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 "FreeImage.h"
+#include "Utilities.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 unsigned short
+ReadUint16(BOOL msb_order, const void *buffer) {
+ unsigned short value;
+
+ if(msb_order) {
+ value = (unsigned short) ((((BYTE*) buffer)[0] << 8) | ((BYTE*) buffer)[1]);
+ return value;
+ }
+ value = (unsigned short) ((((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, char *pval, BOOL msb_order, DWORD *subdirOffset, TagLib::MDMODEL *md_model) {
+ // get the IFD offset
+ *subdirOffset = (DWORD) 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, 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 Intel order altough the Exif section maybe in Intel order or in Motorola 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 = (DWORD) 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 offset 0th IFD offset
+ @param length Length of the datafile
+ @param msb_order Endianess order of the datafile
+ @return
+*/
+static BOOL
+jpeg_read_exif_dir(FIBITMAP *dib, const BYTE *tiffp, unsigned long offset, unsigned int length, BOOL msb_order) {
+ 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 = TagLib::EXIF_MAIN;
+
+ // set the pointer to the first IFD (0th IFD) and follow it were it leads.
+
+ const BYTE *ifd0th = (BYTE*)tiffp + offset;
+
+ 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);
+
+ 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
+ FreeImage_SetTagID(tag, ReadUint16(msb_order, pde));
+ // 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
+ FreeImage_SetTagCount(tag, ReadUint32(msb_order, pde + 4));
+ // 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
+ // first check if offset exceeds buffer, at this stage FreeImage_GetTagLength may return invalid data
+ DWORD offset_value = ReadUint32(msb_order, pde + 8);
+ if(offset_value > length) {
+ // 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) > length - 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 < (DWORD) length) && (next_mdmodel != TagLib::UNKNOWN)) {
+ // push our current directory state onto the stack
+ ifdstack.push(ifdp);
+ // bump 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 >= length)) {
+ 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 >= length) {
+ // 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 > length) {
+ 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 dataptr Pointer to the APP1 marker
+ @param datalen APP1 marker length
+ @return Returns TRUE if successful, FALSE otherwise
+*/
+BOOL
+jpeg_read_exif_profile(FIBITMAP *dib, const BYTE *dataptr, unsigned int datalen) {
+ // 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 }; // Intel order
+ BYTE msb_first[4] = { 0x4D, 0x4D, 0x00, 0x2A }; // Motorola order
+
+ unsigned int length = datalen;
+ BYTE *profile = (BYTE*)dataptr;
+
+ // verify the identifying string
+
+ if(memcmp(exif_signature, profile, sizeof(exif_signature)) == 0) {
+ // Exif profile - TIFF header with 2 IFDs. 0th - the image attributes, 1st - may be used for thumbnail
+
+ profile += sizeof(exif_signature);
+ length -= sizeof(exif_signature);
+
+ // read the TIFF header (8 bytes)
+
+ // check the endianess order
+
+ BOOL bMotorolaOrder = TRUE;
+
+ if(memcmp(profile, lsb_first, sizeof(lsb_first)) == 0) {
+ // Exif section in Intel order
+ bMotorolaOrder = FALSE;
+ } else {
+ if(memcmp(profile, msb_first, sizeof(msb_first)) == 0) {
+ // Exif section in Motorola order
+ bMotorolaOrder = TRUE;
+ } else {
+ // Invalid Exif alignment marker
+ return FALSE;
+ }
+ }
+
+ // this is the offset to the first IFD (Image File Directory)
+ unsigned long first_offset = ReadUint32(bMotorolaOrder, profile + 4);
+ if (first_offset > length) {
+ // 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 (first_offset < 8 || first_offset > 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
+ return jpeg_read_exif_dir(dib, profile, first_offset, length, bMotorolaOrder);
+ }
+
+ return FALSE;
+}
+
+