static BOOL WriteMetadata(png_structp png_ptr, png_infop info_ptr, FIBITMAP *dib) { // XMP keyword char *g_png_xmp_keyword = "XML:com.adobe.xmp"; FITAG *tag = NULL; FIMETADATA *mdhandle = NULL; BOOL bResult = TRUE; png_text text_metadata; int num_text = 0; // set the 'Comments' metadata as iTXt chuncks mdhandle = FreeImage_FindFirstMetadata(FIMD_COMMENTS, dib, &tag); if(mdhandle) { do { memset(&text_metadata, 0, sizeof(png_text)); text_metadata.compression = 1; // iTXt, none text_metadata.key = (char*)FreeImage_GetTagKey(tag); // keyword, 1-79 character description of "text" text_metadata.text = (char*)FreeImage_GetTagValue(tag); // comment, may be an empty string (ie "") text_metadata.text_length = FreeImage_GetTagLength(tag);// length of the text string text_metadata.itxt_length = FreeImage_GetTagLength(tag);// length of the itxt string text_metadata.lang = 0; // language code, 0-79 characters or a NULL pointer text_metadata.lang_key = 0; // keyword translated UTF-8 string, 0 or more chars or a NULL pointer // set the tag png_set_text(png_ptr, info_ptr, &text_metadata, 1); } while(FreeImage_FindNextMetadata(mdhandle, &tag)); FreeImage_FindCloseMetadata(mdhandle); bResult &= TRUE; } // set the 'XMP' metadata as iTXt chuncks tag = NULL; FreeImage_GetMetadata(FIMD_XMP, dib, g_TagLib_XMPFieldName, &tag); if(tag && FreeImage_GetTagLength(tag)) { memset(&text_metadata, 0, sizeof(png_text)); text_metadata.compression = 1; // iTXt, none text_metadata.key = g_png_xmp_keyword; // keyword, 1-79 character description of "text" text_metadata.text = (char*)FreeImage_GetTagValue(tag); // comment, may be an empty string (ie "") text_metadata.text_length = FreeImage_GetTagLength(tag);// length of the text string text_metadata.itxt_length = FreeImage_GetTagLength(tag);// length of the itxt string text_metadata.lang = 0; // language code, 0-79 characters or a NULL pointer text_metadata.lang_key = 0; // keyword translated UTF-8 string, 0 or more chars or a NULL pointer // set the tag png_set_text(png_ptr, info_ptr, &text_metadata, 1); bResult &= TRUE; } return bResult; }
/** 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; }
static BOOL WriteMetadata(png_structp png_ptr, png_infop info_ptr, FIBITMAP *dib) { // XMP keyword const char *g_png_xmp_keyword = "XML:com.adobe.xmp"; FITAG *tag = NULL; FIMETADATA *mdhandle = NULL; BOOL bResult = TRUE; png_text text_metadata; png_time mod_time; // set the 'Comments' metadata as iTXt chuncks mdhandle = FreeImage_FindFirstMetadata(FIMD_COMMENTS, dib, &tag); if(mdhandle) { do { memset(&text_metadata, 0, sizeof(png_text)); text_metadata.compression = 1; // iTXt, none text_metadata.key = (char*)FreeImage_GetTagKey(tag); // keyword, 1-79 character description of "text" text_metadata.text = (char*)FreeImage_GetTagValue(tag); // comment, may be an empty string (ie "") text_metadata.text_length = FreeImage_GetTagLength(tag);// length of the text string text_metadata.itxt_length = FreeImage_GetTagLength(tag);// length of the itxt string text_metadata.lang = 0; // language code, 0-79 characters or a NULL pointer text_metadata.lang_key = 0; // keyword translated UTF-8 string, 0 or more chars or a NULL pointer // set the tag png_set_text(png_ptr, info_ptr, &text_metadata, 1); } while(FreeImage_FindNextMetadata(mdhandle, &tag)); FreeImage_FindCloseMetadata(mdhandle); bResult &= TRUE; } // set the 'XMP' metadata as iTXt chuncks tag = NULL; FreeImage_GetMetadata(FIMD_XMP, dib, g_TagLib_XMPFieldName, &tag); if(tag && FreeImage_GetTagLength(tag)) { memset(&text_metadata, 0, sizeof(png_text)); text_metadata.compression = 1; // iTXt, none text_metadata.key = (char*)g_png_xmp_keyword; // keyword, 1-79 character description of "text" text_metadata.text = (char*)FreeImage_GetTagValue(tag); // comment, may be an empty string (ie "") text_metadata.text_length = FreeImage_GetTagLength(tag);// length of the text string text_metadata.itxt_length = FreeImage_GetTagLength(tag);// length of the itxt string text_metadata.lang = 0; // language code, 0-79 characters or a NULL pointer text_metadata.lang_key = 0; // keyword translated UTF-8 string, 0 or more chars or a NULL pointer // set the tag png_set_text(png_ptr, info_ptr, &text_metadata, 1); bResult &= TRUE; } // set the Exif-TIFF 'DateTime' metadata as a tIME chunk tag = NULL; FreeImage_GetMetadata(FIMD_EXIF_MAIN, dib, "DateTime", &tag); if(tag && FreeImage_GetTagLength(tag)) { int year, month, day, hour, minute, second; const char *value = (char*)FreeImage_GetTagValue(tag); if(sscanf(value, "%4d:%02d:%02d %2d:%02d:%02d", &year, &month, &day, &hour, &minute, &second) == 6) { mod_time.year = year; mod_time.month = month; mod_time.day = day; mod_time.hour = hour; mod_time.minute = minute; mod_time.second = second; png_set_tIME (png_ptr, info_ptr, &mod_time); } } return bResult; }
/** 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; } }