// ==========================================================
// Metadata functions implementation
// Exif metadata model
//
// Design and implementation by
// - Hervé Drolon (drolon@infonie.fr)
// - Mihail Naydenov (mnaydenov@users.sourceforge.net)
// - Garrick Meeker (garrickmeeker@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 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;
	}
}

// ----------------------------------------------------------
//   Exif PSD routines
// ----------------------------------------------------------

/**
Read and decode PSD image resource (Exif profile)
@param dib Input FIBITMAP
@param data Pointer to the resource data
@param length Resource length
@return Returns TRUE if successful, FALSE otherwise
*/
BOOL
psd_read_exif_profile(FIBITMAP *dib, const BYTE *data, unsigned int length) {
	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;

	// 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

	// 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;
	}

	// process Exif directories, starting with Exif-TIFF IFD
	return jpeg_read_exif_dir(dib, pbProfile, dwFirstOffset, dwProfileLength, 0, bBigEndian, TagLib::EXIF_MAIN);
}

/**
Read PSD image resource (Exif profile)
@param dib Input FIBITMAP
@param dataptr Pointer to the resource data
@param datalen Resource length
@return Returns TRUE if successful, FALSE otherwise
*/
BOOL
psd_read_exif_profile_raw(FIBITMAP *dib, const BYTE *profile, unsigned length) {
    // marker identifying string for Exif = "Exif\0\0"
	// used by JPEG not PSD
    BYTE exif_signature[6] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 };

	if(NULL == profile || length == 0) {
		return FALSE;
	}

	DWORD dwProfileLength = (DWORD)length + sizeof(exif_signature);
	BYTE *pbProfile = (BYTE*)malloc(dwProfileLength);
	if(NULL == pbProfile) {
		// out of memory ...
		return FALSE;
	}
	memcpy(pbProfile, exif_signature, sizeof(exif_signature));
	memcpy(pbProfile + sizeof(exif_signature), profile, length);

	// create a tag
	FITAG *tag = FreeImage_CreateTag();
	BOOL bSuccess = FALSE;
	if(tag) {
		FreeImage_SetTagKey(tag, g_TagLib_ExifRawFieldName);
		FreeImage_SetTagLength(tag, dwProfileLength);
		FreeImage_SetTagCount(tag, dwProfileLength);
		FreeImage_SetTagType(tag, FIDT_BYTE);
		FreeImage_SetTagValue(tag, pbProfile);

		// store the tag
		FreeImage_SetMetadata(FIMD_EXIF_RAW, dib, FreeImage_GetTagKey(tag), tag);

		// destroy the tag
		FreeImage_DeleteTag(tag);

		bSuccess = TRUE;
	}
	free(pbProfile);

	return bSuccess;
}
