void tiff_write_geotiff_profile(TIFF *tif, FIBITMAP *dib) { char defaultKey[16]; if(FreeImage_GetMetadataCount(FIMD_GEOTIFF, dib) == 0) { return; } size_t tag_size = sizeof(xtiffFieldInfo) / sizeof(xtiffFieldInfo[0]); TagLib& tag_lib = TagLib::instance(); for(unsigned i = 0; i < tag_size; i++) { const TIFFFieldInfo *fieldInfo = &xtiffFieldInfo[i]; FITAG *tag = NULL; const char *key = tag_lib.getTagFieldName(TagLib::GEOTIFF, (WORD)fieldInfo->field_tag, defaultKey); if(FreeImage_GetMetadata(FIMD_GEOTIFF, dib, key, &tag)) { if(FreeImage_GetTagType(tag) == FIDT_ASCII) { TIFFSetField(tif, fieldInfo->field_tag, FreeImage_GetTagValue(tag)); } else { TIFFSetField(tif, fieldInfo->field_tag, FreeImage_GetTagCount(tag), FreeImage_GetTagValue(tag)); } } } }
/** Write all known exif tags @param tif TIFF handle @param md_model Metadata model from where to load the tags @param dib Image being written @return Returns TRUE if successful, returns FALSE otherwise */ BOOL tiff_write_exif_tags(TIFF *tif, TagLib::MDMODEL md_model, FIBITMAP *dib) { char defaultKey[16]; // only EXIF_MAIN so far if(md_model != TagLib::EXIF_MAIN) { return FALSE; } if(FreeImage_GetMetadataCount(FIMD_EXIF_MAIN, dib) == 0) { return FALSE; } TagLib& tag_lib = TagLib::instance(); for (int fi = 0, nfi = (int)tif->tif_nfields; nfi > 0; nfi--, fi++) { const TIFFField *fld = tif->tif_fields[fi]; const uint32 tag_id = TIFFFieldTag(fld); if(skip_write_field(tif, tag_id)) { // skip tags that are already handled by the LibTIFF writing process continue; } FITAG *tag = NULL; // get the tag key const char *key = tag_lib.getTagFieldName(TagLib::EXIF_MAIN, (WORD)tag_id, defaultKey); if(FreeImage_GetMetadata(FIMD_EXIF_MAIN, dib, key, &tag)) { FREE_IMAGE_MDTYPE tag_type = FreeImage_GetTagType(tag); TIFFDataType tif_tag_type = TIFFFieldDataType(fld); // check for identical formats // (enum value are the sames between FREE_IMAGE_MDTYPE and TIFFDataType types) if((int)tif_tag_type != (int)tag_type) { // skip tag or _TIFFmemcpy will fail continue; } // type of storage may differ (e.g. rationnal array vs float array type) if((unsigned)_TIFFDataSize(tif_tag_type) != FreeImage_TagDataWidth(tag_type)) { // skip tag or _TIFFmemcpy will fail continue; } if(tag_type == FIDT_ASCII) { TIFFSetField(tif, tag_id, FreeImage_GetTagValue(tag)); } else { TIFFSetField(tif, tag_id, FreeImage_GetTagCount(tag), FreeImage_GetTagValue(tag)); } } } return TRUE; }
static void rotate_exif(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) { 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; } } } } }
unsigned fipImage::getMetadataCount(FREE_IMAGE_MDMODEL model) const { return FreeImage_GetMetadataCount(model, _dib); }
/** 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 ICC, XMP, Exif, Exif-GPS, IPTC, descriptive (i.e. Exif-TIFF) metadata */ static ERR WriteMetadata(PKImageEncode *pIE, FIBITMAP *dib) { ERR error_code = 0; // error code as returned by the interface BYTE *profile = NULL; unsigned profile_size = 0; try { // write ICC profile { FIICCPROFILE *iccProfile = FreeImage_GetICCProfile(dib); if(iccProfile->data) { error_code = pIE->SetColorContext(pIE, (U8*)iccProfile->data, iccProfile->size); JXR_CHECK(error_code); } } // write descriptive metadata if(FreeImage_GetMetadataCount(FIMD_EXIF_MAIN, dib)) { error_code = WriteDescriptiveMetadata(pIE, dib); JXR_CHECK(error_code); } // write IPTC metadata if(FreeImage_GetMetadataCount(FIMD_IPTC, dib)) { // create a binary profile if(write_iptc_profile(dib, &profile, &profile_size)) { // write the profile error_code = PKImageEncode_SetIPTCNAAMetadata_WMP(pIE, profile, profile_size); JXR_CHECK(error_code); // release profile free(profile); profile = NULL; } } // write XMP metadata { FITAG *tag_xmp = NULL; if(FreeImage_GetMetadata(FIMD_XMP, dib, g_TagLib_XMPFieldName, &tag_xmp)) { error_code = PKImageEncode_SetXMPMetadata_WMP(pIE, (BYTE*)FreeImage_GetTagValue(tag_xmp), FreeImage_GetTagLength(tag_xmp)); JXR_CHECK(error_code); } } // write Exif metadata { if(tiff_get_ifd_profile(dib, FIMD_EXIF_EXIF, &profile, &profile_size)) { error_code = PKImageEncode_SetEXIFMetadata_WMP(pIE, profile, profile_size); JXR_CHECK(error_code); // release profile free(profile); profile = NULL; } } // write Exif GPS metadata { if(tiff_get_ifd_profile(dib, FIMD_EXIF_GPS, &profile, &profile_size)) { error_code = PKImageEncode_SetGPSInfoMetadata_WMP(pIE, profile, profile_size); JXR_CHECK(error_code); // release profile free(profile); profile = NULL; } } return WMP_errSuccess; } catch(...) { free(profile); return error_code; } }