| // ========================================================== |
| // Metadata functions implementation |
| // |
| // Design and implementation by |
| // - Hervé Drolon (drolon@infonie.fr) |
| // |
| // 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" |
| |
| // ---------------------------------------------------------- |
| // IPTC JPEG / TIFF markers routines |
| // ---------------------------------------------------------- |
| |
| static const char* IPTC_DELIMITER = ";"; // keywords/supplemental category delimiter |
| /** |
| Read and decode IPTC binary data |
| */ |
| BOOL |
| read_iptc_profile(FIBITMAP *dib, const BYTE *dataptr, unsigned int datalen) { |
| char defaultKey[16]; |
| size_t length = datalen; |
| BYTE *profile = (BYTE*)dataptr; |
| |
| std::string Keywords; |
| std::string SupplementalCategory; |
| |
| WORD tag_id; |
| |
| // create a tag |
| |
| FITAG *tag = FreeImage_CreateTag(); |
| |
| TagLib& tag_lib = TagLib::instance(); |
| |
| // find start of the BIM portion of the binary data |
| size_t offset = 0; |
| while(offset < length - 1) { |
| if((profile[offset] == 0x1C) && (profile[offset+1] == 0x02)) |
| break; |
| offset++; |
| } |
| |
| // for each tag |
| while (offset < length) { |
| |
| // identifies start of a tag |
| if (profile[offset] != 0x1c) { |
| break; |
| } |
| // we need at least five bytes left to read a tag |
| if ((offset + 5) >= length) { |
| break; |
| } |
| |
| offset++; |
| |
| int directoryType = profile[offset++]; |
| int tagType = profile[offset++];; |
| int tagByteCount = ((profile[offset] & 0xFF) << 8) | (profile[offset + 1] & 0xFF); |
| offset += 2; |
| |
| if ((offset + tagByteCount) > length) { |
| // data for tag extends beyond end of iptc segment |
| break; |
| } |
| |
| // process the tag |
| |
| tag_id = (WORD)(tagType | (directoryType << 8)); |
| |
| FreeImage_SetTagID(tag, tag_id); |
| FreeImage_SetTagLength(tag, tagByteCount); |
| |
| // allocate a buffer to store the tag value |
| BYTE *iptc_value = (BYTE*)malloc((tagByteCount + 1) * sizeof(BYTE)); |
| memset(iptc_value, 0, (tagByteCount + 1) * sizeof(BYTE)); |
| |
| // get the tag value |
| |
| switch (tag_id) { |
| case TAG_RECORD_VERSION: |
| { |
| // short |
| FreeImage_SetTagType(tag, FIDT_SSHORT); |
| FreeImage_SetTagCount(tag, 1); |
| short *pvalue = (short*)&iptc_value[0]; |
| *pvalue = (short)((profile[offset] << 8) | profile[offset + 1]); |
| FreeImage_SetTagValue(tag, pvalue); |
| break; |
| } |
| |
| case TAG_RELEASE_DATE: |
| case TAG_DATE_CREATED: |
| // Date object |
| case TAG_RELEASE_TIME: |
| case TAG_TIME_CREATED: |
| // time |
| default: |
| { |
| // string |
| FreeImage_SetTagType(tag, FIDT_ASCII); |
| FreeImage_SetTagCount(tag, tagByteCount); |
| for(int i = 0; i < tagByteCount; i++) { |
| iptc_value[i] = profile[offset + i]; |
| } |
| iptc_value[tagByteCount] = '\0'; |
| FreeImage_SetTagValue(tag, (char*)&iptc_value[0]); |
| break; |
| } |
| } |
| |
| if(tag_id == TAG_SUPPLEMENTAL_CATEGORIES) { |
| // concatenate the categories |
| if(SupplementalCategory.length() == 0) { |
| SupplementalCategory.append((char*)iptc_value); |
| } else { |
| SupplementalCategory.append(IPTC_DELIMITER); |
| SupplementalCategory.append((char*)iptc_value); |
| } |
| } |
| else if(tag_id == TAG_KEYWORDS) { |
| // concatenate the keywords |
| if(Keywords.length() == 0) { |
| Keywords.append((char*)iptc_value); |
| } else { |
| Keywords.append(IPTC_DELIMITER); |
| Keywords.append((char*)iptc_value); |
| } |
| } |
| else { |
| // get the tag key and description |
| const char *key = tag_lib.getTagFieldName(TagLib::IPTC, tag_id, defaultKey); |
| FreeImage_SetTagKey(tag, key); |
| const char *description = tag_lib.getTagDescription(TagLib::IPTC, tag_id); |
| FreeImage_SetTagDescription(tag, description); |
| |
| // store the tag |
| if(key) { |
| FreeImage_SetMetadata(FIMD_IPTC, dib, key, tag); |
| } |
| } |
| |
| free(iptc_value); |
| |
| // next tag |
| offset += tagByteCount; |
| |
| } |
| |
| // store the 'keywords' tag |
| if(Keywords.length()) { |
| FreeImage_SetTagType(tag, FIDT_ASCII); |
| FreeImage_SetTagID(tag, TAG_KEYWORDS); |
| FreeImage_SetTagKey(tag, tag_lib.getTagFieldName(TagLib::IPTC, TAG_KEYWORDS, defaultKey)); |
| FreeImage_SetTagDescription(tag, tag_lib.getTagDescription(TagLib::IPTC, TAG_KEYWORDS)); |
| FreeImage_SetTagLength(tag, (DWORD)Keywords.length()); |
| FreeImage_SetTagCount(tag, (DWORD)Keywords.length()); |
| FreeImage_SetTagValue(tag, (char*)Keywords.c_str()); |
| FreeImage_SetMetadata(FIMD_IPTC, dib, FreeImage_GetTagKey(tag), tag); |
| } |
| |
| // store the 'supplemental category' tag |
| if(SupplementalCategory.length()) { |
| FreeImage_SetTagType(tag, FIDT_ASCII); |
| FreeImage_SetTagID(tag, TAG_SUPPLEMENTAL_CATEGORIES); |
| FreeImage_SetTagKey(tag, tag_lib.getTagFieldName(TagLib::IPTC, TAG_SUPPLEMENTAL_CATEGORIES, defaultKey)); |
| FreeImage_SetTagDescription(tag, tag_lib.getTagDescription(TagLib::IPTC, TAG_SUPPLEMENTAL_CATEGORIES)); |
| FreeImage_SetTagLength(tag, (DWORD)SupplementalCategory.length()); |
| FreeImage_SetTagCount(tag, (DWORD)SupplementalCategory.length()); |
| FreeImage_SetTagValue(tag, (char*)SupplementalCategory.c_str()); |
| FreeImage_SetMetadata(FIMD_IPTC, dib, FreeImage_GetTagKey(tag), tag); |
| } |
| |
| // delete the tag |
| |
| FreeImage_DeleteTag(tag); |
| |
| return TRUE; |
| } |
| |
| // -------------------------------------------------------------------------- |
| |
| static BYTE* |
| append_iptc_tag(BYTE *profile, unsigned *profile_size, WORD id, DWORD length, const void *value) { |
| BYTE *buffer = NULL; |
| |
| // calculate the new buffer size |
| size_t buffer_size = (5 + *profile_size + length) * sizeof(BYTE); |
| buffer = (BYTE*)malloc(buffer_size); |
| if(!buffer) |
| return NULL; |
| |
| // add the header |
| buffer[0] = 0x1C; |
| buffer[1] = 0x02; |
| // add the tag type |
| buffer[2] = (BYTE)(id & 0x00FF); |
| // add the tag length |
| buffer[3] = (BYTE)(length >> 8); |
| buffer[4] = (BYTE)(length & 0xFF); |
| // add the tag value |
| memcpy(buffer + 5, (BYTE*)value, length); |
| // append the previous profile |
| if(NULL == profile) { |
| *profile_size = (5 + length); |
| } |
| else { |
| memcpy(buffer + 5 + length, profile, *profile_size); |
| *profile_size += (5 + length); |
| free(profile); |
| } |
| |
| return buffer; |
| } |
| |
| /** |
| Encode IPTC metadata into a binary buffer. |
| The buffer is allocated by the function and must be freed by the caller. |
| */ |
| BOOL |
| write_iptc_profile(FIBITMAP *dib, BYTE **profile, unsigned *profile_size) { |
| FITAG *tag = NULL; |
| FIMETADATA *mdhandle = NULL; |
| |
| BYTE *buffer = NULL; |
| unsigned buffer_size = 0; |
| |
| // parse all IPTC tags and rebuild a IPTC profile |
| mdhandle = FreeImage_FindFirstMetadata(FIMD_IPTC, dib, &tag); |
| |
| if(mdhandle) { |
| do { |
| WORD tag_id = FreeImage_GetTagID(tag); |
| |
| // append the tag to the profile |
| |
| switch(tag_id) { |
| case TAG_RECORD_VERSION: |
| // ignore (already handled) |
| break; |
| |
| case TAG_SUPPLEMENTAL_CATEGORIES: |
| case TAG_KEYWORDS: |
| if(FreeImage_GetTagType(tag) == FIDT_ASCII) { |
| std::string value = (const char*)FreeImage_GetTagValue(tag); |
| |
| // split the tag value |
| std::vector<std::string> output; |
| std::string delimiter = IPTC_DELIMITER; |
| |
| size_t offset = 0; |
| size_t delimiterIndex = 0; |
| |
| delimiterIndex = value.find(delimiter, offset); |
| while (delimiterIndex != std::string::npos) { |
| output.push_back(value.substr(offset, delimiterIndex - offset)); |
| offset += delimiterIndex - offset + delimiter.length(); |
| delimiterIndex = value.find(delimiter, offset); |
| } |
| output.push_back(value.substr(offset)); |
| |
| // add as many tags as there are comma separated strings |
| for(int i = 0; i < (int)output.size(); i++) { |
| std::string& tag_value = output[i]; |
| buffer = append_iptc_tag(buffer, &buffer_size, tag_id, (DWORD)tag_value.length(), tag_value.c_str()); |
| } |
| |
| } |
| break; |
| |
| case TAG_URGENCY: |
| if(FreeImage_GetTagType(tag) == FIDT_ASCII) { |
| DWORD length = 1; // keep the first octet only |
| buffer = append_iptc_tag(buffer, &buffer_size, tag_id, length, FreeImage_GetTagValue(tag)); |
| } |
| break; |
| |
| default: |
| if(FreeImage_GetTagType(tag) == FIDT_ASCII) { |
| DWORD length = FreeImage_GetTagLength(tag); |
| buffer = append_iptc_tag(buffer, &buffer_size, tag_id, length, FreeImage_GetTagValue(tag)); |
| } |
| break; |
| } |
| |
| } while(FreeImage_FindNextMetadata(mdhandle, &tag)); |
| |
| FreeImage_FindCloseMetadata(mdhandle); |
| |
| // add the DirectoryVersion tag |
| const short version = 0x0200; |
| buffer = append_iptc_tag(buffer, &buffer_size, TAG_RECORD_VERSION, sizeof(version), &version); |
| |
| *profile = buffer; |
| *profile_size = buffer_size; |
| |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |