summaryrefslogtreecommitdiff
path: root/plugins/AdvaImg/src/Metadata/Exif.cpp
diff options
context:
space:
mode:
authorKirill Volinsky <mataes2007@gmail.com>2015-05-18 07:31:16 +0000
committerKirill Volinsky <mataes2007@gmail.com>2015-05-18 07:31:16 +0000
commit76b86227951fdb5096572c36a256f07aee76def3 (patch)
tree713dcf30179d88b685605bdc8a03a5068832e4ff /plugins/AdvaImg/src/Metadata/Exif.cpp
parent4b289716d4cdd6b3ea29aec8d50e0b69afdc8384 (diff)
git-svn-id: http://svn.miranda-ng.org/main/trunk@13671 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c
Diffstat (limited to 'plugins/AdvaImg/src/Metadata/Exif.cpp')
-rw-r--r--plugins/AdvaImg/src/Metadata/Exif.cpp478
1 files changed, 370 insertions, 108 deletions
diff --git a/plugins/AdvaImg/src/Metadata/Exif.cpp b/plugins/AdvaImg/src/Metadata/Exif.cpp
index abc840607b..35265cb2ab 100644
--- a/plugins/AdvaImg/src/Metadata/Exif.cpp
+++ b/plugins/AdvaImg/src/Metadata/Exif.cpp
@@ -120,15 +120,15 @@ ReadInt32(BOOL msb_order, const void *buffer) {
return value;
}
-static unsigned short
+static WORD
ReadUint16(BOOL msb_order, const void *buffer) {
- unsigned short value;
+ WORD value;
if(msb_order) {
- value = (unsigned short) ((((BYTE*) buffer)[0] << 8) | ((BYTE*) buffer)[1]);
+ value = (WORD) ((((BYTE*) buffer)[0] << 8) | ((BYTE*) buffer)[1]);
return value;
}
- value = (unsigned short) ((((BYTE*) buffer)[1] << 8) | ((BYTE*) buffer)[0]);
+ value = (WORD) ((((BYTE*) buffer)[1] << 8) | ((BYTE*) buffer)[0]);
return value;
}
@@ -146,9 +146,9 @@ 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) {
+processIFDOffset(FITAG *tag, const char *pval, BOOL msb_order, DWORD *subdirOffset, TagLib::MDMODEL *md_model) {
// get the IFD offset
- *subdirOffset = (DWORD) ReadUint32(msb_order, pval);
+ *subdirOffset = ReadUint32(msb_order, pval);
// select a tag info table
switch(FreeImage_GetTagID(tag)) {
@@ -162,7 +162,6 @@ processIFDOffset(FITAG *tag, char *pval, BOOL msb_order, DWORD *subdirOffset, Ta
*md_model = TagLib::EXIF_INTEROP;
break;
}
-
}
/**
@@ -170,7 +169,7 @@ 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) {
+processMakerNote(FIBITMAP *dib, const char *pval, BOOL msb_order, DWORD *subdirOffset, TagLib::MDMODEL *md_model) {
FITAG *tagMake = NULL;
*subdirOffset = 0;
@@ -238,12 +237,12 @@ processMakerNote(FIBITMAP *dib, char *pval, BOOL msb_order, DWORD *subdirOffset,
}
} 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.
+ // 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 = (DWORD) ReadUint32(msb_order, pval + 8);
+ 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) {
@@ -519,14 +518,15 @@ 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
+@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, unsigned long offset, unsigned int length, BOOL msb_order, TagLib::MDMODEL starting_md_model) {
+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
@@ -553,7 +553,7 @@ jpeg_read_exif_dir(FIBITMAP *dib, const BYTE *tiffp, unsigned long offset, unsig
// set the pointer to the first IFD (0th IFD) and follow it were it leads.
- const BYTE *ifd0th = (BYTE*)tiffp + offset;
+ const BYTE *ifd0th = (BYTE*)tiffp + (size_t)dwOffsetIfd0;
const BYTE *ifdp = ifd0th;
@@ -577,6 +577,10 @@ jpeg_read_exif_dir(FIBITMAP *dib, const BYTE *tiffp, unsigned long offset, unsig
// 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
@@ -590,7 +594,9 @@ jpeg_read_exif_dir(FIBITMAP *dib, const BYTE *tiffp, unsigned long offset, unsig
pde = (char*) DIR_ENTRY_ADDR(ifdp, de);
// get the tag ID
- FreeImage_SetTagID(tag, ReadUint16(msb_order, pde));
+ 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) {
@@ -602,7 +608,9 @@ jpeg_read_exif_dir(FIBITMAP *dib, const BYTE *tiffp, unsigned long offset, unsig
FreeImage_SetTagType(tag, (FREE_IMAGE_MDTYPE)tag_type);
// get number of components
- FreeImage_SetTagCount(tag, ReadUint32(msb_order, pde + 4));
+ 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) {
@@ -616,17 +624,21 @@ jpeg_read_exif_dir(FIBITMAP *dib, const BYTE *tiffp, unsigned long offset, unsig
// 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
+ // if its bigger than 4 bytes, the directory entry contains an offset
DWORD offset_value = ReadUint32(msb_order, pde + 8);
- if(offset_value > length) {
+ // 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) > length - offset_value){
+ if(FreeImage_GetTagLength(tag) > dwLength - offset_value){
// a problem occured : delete the tag (not free'd after)
FreeImage_DeleteTag(tag);
// jump to next entry
@@ -659,10 +671,10 @@ jpeg_read_exif_dir(FIBITMAP *dib, const BYTE *tiffp, unsigned long offset, unsig
next_ifd = (BYTE*)tiffp + sub_offset;
}
- if((sub_offset < (DWORD) length) && (next_mdmodel != TagLib::UNKNOWN)) {
+ if((sub_offset < dwLength) && (next_mdmodel != TagLib::UNKNOWN)) {
// push our current directory state onto the stack
ifdstack.push(ifdp);
- // bump to the next entry
+ // jump to the next entry
de++;
destack.push(de);
@@ -708,7 +720,7 @@ jpeg_read_exif_dir(FIBITMAP *dib, const BYTE *tiffp, unsigned long offset, unsig
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)) {
+ if((next_offset == 0) || (next_offset >= dwLength)) {
return TRUE; //< no thumbnail
}
@@ -726,7 +738,7 @@ jpeg_read_exif_dir(FIBITMAP *dib, const BYTE *tiffp, unsigned long offset, unsig
// check for buffer overflow
const size_t remaining = (size_t)base + 12 - (size_t)tiffp;
- if(remaining >= length) {
+ if(remaining >= dwLength) {
// bad IFD1 directory, ignore it
return FALSE;
}
@@ -734,9 +746,9 @@ jpeg_read_exif_dir(FIBITMAP *dib, const BYTE *tiffp, unsigned long offset, unsig
// get the tag ID
WORD tag = ReadUint16(msb_order, base);
// get the tag type
- WORD type = ReadUint16(msb_order, base + sizeof(WORD));
+ /*WORD type = */ReadUint16(msb_order, base + sizeof(WORD));
// get number of components
- DWORD count = ReadUint32(msb_order, base + sizeof(WORD) + sizeof(WORD));
+ /*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));
@@ -768,7 +780,7 @@ jpeg_read_exif_dir(FIBITMAP *dib, const BYTE *tiffp, unsigned long offset, unsig
return TRUE;
}
- if(thOffset + thSize > length) {
+ if(thOffset + thSize > dwLength) {
return TRUE;
}
@@ -791,43 +803,45 @@ jpeg_read_exif_dir(FIBITMAP *dib, const BYTE *tiffp, unsigned long offset, unsig
// --------------------------------------------------------------------------
/**
- 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
+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 *dataptr, unsigned datalen) {
+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 }; // Intel order
- BYTE msb_first[4] = { 0x4D, 0x4D, 0x00, 0x2A }; // Motorola order
+ 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
- unsigned int length = datalen;
- BYTE *profile = (BYTE*)dataptr;
+ // 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
- 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);
+ pbProfile += sizeof(exif_signature);
+ dwProfileLength -= sizeof(exif_signature);
// read the TIFF header (8 bytes)
// check the endianess order
- BOOL bMotorolaOrder = TRUE;
+ BOOL bBigEndian = TRUE;
- if(memcmp(profile, lsb_first, sizeof(lsb_first)) == 0) {
- // Exif section in Intel order
- bMotorolaOrder = FALSE;
+ if(memcmp(pbProfile, lsb_first, sizeof(lsb_first)) == 0) {
+ // Exif section is in little-endian order
+ bBigEndian = FALSE;
} else {
- if(memcmp(profile, msb_first, sizeof(msb_first)) == 0) {
- // Exif section in Motorola order
- bMotorolaOrder = TRUE;
+ 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;
@@ -835,8 +849,8 @@ jpeg_read_exif_profile(FIBITMAP *dib, const BYTE *dataptr, unsigned datalen) {
}
// this is the offset to the first IFD (Image File Directory)
- unsigned long first_offset = ReadUint32(bMotorolaOrder, profile + 4);
- if (first_offset > length) {
+ DWORD dwFirstOffset = ReadUint32(bBigEndian, pbProfile + 4);
+ if (dwFirstOffset > dwProfileLength) {
// bad Exif data
return FALSE;
}
@@ -844,7 +858,7 @@ jpeg_read_exif_profile(FIBITMAP *dib, const BYTE *dataptr, unsigned datalen) {
/*
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) {
+ 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");
@@ -853,18 +867,22 @@ jpeg_read_exif_profile(FIBITMAP *dib, const BYTE *dataptr, unsigned datalen) {
*/
// process Exif directories, starting with Exif-TIFF IFD
- return jpeg_read_exif_dir(dib, profile, first_offset, length, bMotorolaOrder, TagLib::EXIF_MAIN);
+ 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
+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) {
@@ -898,38 +916,48 @@ jpeg_read_exif_profile_raw(FIBITMAP *dib, const BYTE *profile, unsigned length)
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) {
+jpegxr_read_exif_profile(FIBITMAP *dib, const BYTE *profile, unsigned length, unsigned file_offset) {
// assume Little Endian order
- BOOL bMotorolaOrder = FALSE;
+ BOOL bBigEndian = FALSE;
// process Exif specific IFD
- return jpeg_read_exif_dir(dib, profile, 0, length, bMotorolaOrder, TagLib::EXIF_EXIF);
+ 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 marker
-@param length Exif-GPS marker length
+@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) {
+jpegxr_read_exif_gps_profile(FIBITMAP *dib, const BYTE *profile, unsigned length, unsigned file_offset) {
// assume Little Endian order
- BOOL bMotorolaOrder = FALSE;
+ BOOL bBigEndian = FALSE;
// process Exif GPS IFD
- return jpeg_read_exif_dir(dib, profile, 0, length, bMotorolaOrder, TagLib::EXIF_GPS);
+ 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
@@ -943,49 +971,283 @@ RotateExif(FIBITMAP **dib) {
// process Exif rotation
FITAG *tag = NULL;
FreeImage_GetMetadata(FIMD_EXIF_MAIN, *dib, "Orientation", &tag);
- if(tag != NULL) {
- if(FreeImage_GetTagID(tag) == TAG_ORIENTATION) {
- unsigned short orientation = *((unsigned short *)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;
+ 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;
}
}