| // ========================================================== |
| // 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 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); |
| // don't risk processing this directory further |
| return FALSE; |
| } |
| 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; |
| } |
| } |