1 // ==========================================================
2 // Metadata functions implementation
3 //
4 // Design and implementation by
5 // - Herv� Drolon (drolon@infonie.fr)
6 //
7 // This file is part of FreeImage 3
8 //
9 // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
10 // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
11 // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
12 // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
13 // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
14 // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
15 // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
16 // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
17 // THIS DISCLAIMER.
18 //
19 // Use at your own risk!
20 // ==========================================================
21 
22 #ifdef _MSC_VER
23 #pragma warning (disable : 4786) // identifier was truncated to 'number' characters
24 #endif
25 
26 #include "FreeImage.h"
27 #include "Utilities.h"
28 #include "FreeImageTag.h"
29 
30 // ----------------------------------------------------------
31 //   IPTC JPEG / TIFF markers routines
32 // ----------------------------------------------------------
33 
34 static const char* IPTC_DELIMITER = ";";	// keywords/supplemental category delimiter
35 /**
36 	Read and decode IPTC binary data
37 */
38 BOOL
read_iptc_profile(FIBITMAP * dib,const BYTE * dataptr,unsigned int datalen)39 read_iptc_profile(FIBITMAP *dib, const BYTE *dataptr, unsigned int datalen) {
40 	char defaultKey[16];
41 	size_t length = datalen;
42 	BYTE *profile = (BYTE*)dataptr;
43 
44 	const char *JPEG_AdobeCM_Tag = "Adobe_CM";
45 
46 	std::string Keywords;
47 	std::string SupplementalCategory;
48 
49 	WORD tag_id;
50 
51 	if(!dataptr || (datalen == 0)) {
52 		return FALSE;
53 	}
54 
55 	if(datalen > 8) {
56 		if(memcmp(JPEG_AdobeCM_Tag, dataptr, 8) == 0) {
57 			// the "Adobe_CM" APP13 segment presumably contains color management information,
58 			// but the meaning of the data is currently unknown.
59 			// If anyone has an idea about what this means, please let me know.
60 			return FALSE;
61 		}
62 	}
63 
64 
65 	// create a tag
66 
67 	FITAG *tag = FreeImage_CreateTag();
68 
69 	TagLib& tag_lib = TagLib::instance();
70 
71     // find start of the BIM portion of the binary data
72     size_t offset = 0;
73 	while(offset < length - 1) {
74 		if((profile[offset] == 0x1C) && (profile[offset+1] == 0x02))
75 			break;
76 		offset++;
77 	}
78 
79     // for each tag
80     while (offset < length) {
81 
82         // identifies start of a tag
83         if (profile[offset] != 0x1c) {
84             break;
85         }
86         // we need at least five bytes left to read a tag
87         if ((offset + 5) >= length) {
88             break;
89         }
90 
91         offset++;
92 
93 		int directoryType	= profile[offset++];
94         int tagType			= profile[offset++];;
95         int tagByteCount	= ((profile[offset] & 0xFF) << 8) | (profile[offset + 1] & 0xFF);
96         offset += 2;
97 
98         if ((offset + tagByteCount) > length) {
99             // data for tag extends beyond end of iptc segment
100             break;
101         }
102 
103 		if(tagByteCount == 0) {
104 			// go to next tag
105 			continue;
106 		}
107 
108 		// process the tag
109 
110 		tag_id = (WORD)(tagType | (directoryType << 8));
111 
112 		FreeImage_SetTagID(tag, tag_id);
113 		FreeImage_SetTagLength(tag, tagByteCount);
114 
115 		// allocate a buffer to store the tag value
116 		BYTE *iptc_value = (BYTE*)malloc((tagByteCount + 1) * sizeof(BYTE));
117 		memset(iptc_value, 0, (tagByteCount + 1) * sizeof(BYTE));
118 
119 		// get the tag value
120 
121 		switch (tag_id) {
122 			case TAG_RECORD_VERSION:
123 			{
124 				// short
125 				FreeImage_SetTagType(tag, FIDT_SSHORT);
126 				FreeImage_SetTagCount(tag, 1);
127 				short *pvalue = (short*)&iptc_value[0];
128 				*pvalue = (short)((profile[offset] << 8) | profile[offset + 1]);
129 				FreeImage_SetTagValue(tag, pvalue);
130 				break;
131 			}
132 
133 			case TAG_RELEASE_DATE:
134 			case TAG_DATE_CREATED:
135 				// Date object
136 			case TAG_RELEASE_TIME:
137 			case TAG_TIME_CREATED:
138 				// time
139 			default:
140 			{
141 				// string
142 				FreeImage_SetTagType(tag, FIDT_ASCII);
143 				FreeImage_SetTagCount(tag, tagByteCount);
144 				for(int i = 0; i < tagByteCount; i++) {
145 					iptc_value[i] = profile[offset + i];
146 				}
147 				iptc_value[tagByteCount] = '\0';
148 				FreeImage_SetTagValue(tag, (char*)&iptc_value[0]);
149 				break;
150 			}
151 		}
152 
153 		if(tag_id == TAG_SUPPLEMENTAL_CATEGORIES) {
154 			// concatenate the categories
155 			if(SupplementalCategory.length() == 0) {
156 				SupplementalCategory.append((char*)iptc_value);
157 			} else {
158 				SupplementalCategory.append(IPTC_DELIMITER);
159 				SupplementalCategory.append((char*)iptc_value);
160 			}
161 		}
162 		else if(tag_id == TAG_KEYWORDS) {
163 			// concatenate the keywords
164 			if(Keywords.length() == 0) {
165 				Keywords.append((char*)iptc_value);
166 			} else {
167 				Keywords.append(IPTC_DELIMITER);
168 				Keywords.append((char*)iptc_value);
169 			}
170 		}
171 		else {
172 			// get the tag key and description
173 			const char *key = tag_lib.getTagFieldName(TagLib::IPTC, tag_id, defaultKey);
174 			FreeImage_SetTagKey(tag, key);
175 			const char *description = tag_lib.getTagDescription(TagLib::IPTC, tag_id);
176 			FreeImage_SetTagDescription(tag, description);
177 
178 			// store the tag
179 			if(key) {
180 				FreeImage_SetMetadata(FIMD_IPTC, dib, key, tag);
181 			}
182 		}
183 
184 		free(iptc_value);
185 
186         // next tag
187 		offset += tagByteCount;
188 
189     }
190 
191 	// store the 'keywords' tag
192 	if(Keywords.length()) {
193 		FreeImage_SetTagType(tag, FIDT_ASCII);
194 		FreeImage_SetTagID(tag, TAG_KEYWORDS);
195 		FreeImage_SetTagKey(tag, tag_lib.getTagFieldName(TagLib::IPTC, TAG_KEYWORDS, defaultKey));
196 		FreeImage_SetTagDescription(tag, tag_lib.getTagDescription(TagLib::IPTC, TAG_KEYWORDS));
197 		FreeImage_SetTagLength(tag, (DWORD)Keywords.length());
198 		FreeImage_SetTagCount(tag, (DWORD)Keywords.length());
199 		FreeImage_SetTagValue(tag, (char*)Keywords.c_str());
200 		FreeImage_SetMetadata(FIMD_IPTC, dib, FreeImage_GetTagKey(tag), tag);
201 	}
202 
203 	// store the 'supplemental category' tag
204 	if(SupplementalCategory.length()) {
205 		FreeImage_SetTagType(tag, FIDT_ASCII);
206 		FreeImage_SetTagID(tag, TAG_SUPPLEMENTAL_CATEGORIES);
207 		FreeImage_SetTagKey(tag, tag_lib.getTagFieldName(TagLib::IPTC, TAG_SUPPLEMENTAL_CATEGORIES, defaultKey));
208 		FreeImage_SetTagDescription(tag, tag_lib.getTagDescription(TagLib::IPTC, TAG_SUPPLEMENTAL_CATEGORIES));
209 		FreeImage_SetTagLength(tag, (DWORD)SupplementalCategory.length());
210 		FreeImage_SetTagCount(tag, (DWORD)SupplementalCategory.length());
211 		FreeImage_SetTagValue(tag, (char*)SupplementalCategory.c_str());
212 		FreeImage_SetMetadata(FIMD_IPTC, dib, FreeImage_GetTagKey(tag), tag);
213 	}
214 
215 	// delete the tag
216 
217 	FreeImage_DeleteTag(tag);
218 
219 	return TRUE;
220 }
221 
222 // --------------------------------------------------------------------------
223 
224 static BYTE*
append_iptc_tag(BYTE * profile,unsigned * profile_size,WORD id,DWORD length,const void * value)225 append_iptc_tag(BYTE *profile, unsigned *profile_size, WORD id, DWORD length, const void *value) {
226 	BYTE *buffer = NULL;
227 
228 	// calculate the new buffer size
229 	size_t buffer_size = (5 + *profile_size + length) * sizeof(BYTE);
230 	buffer = (BYTE*)malloc(buffer_size);
231 	if(!buffer)
232 		return NULL;
233 
234 	// add the header
235 	buffer[0] = 0x1C;
236 	buffer[1] = 0x02;
237 	// add the tag type
238 	buffer[2] = (BYTE)(id & 0x00FF);
239 	// add the tag length
240 	buffer[3] = (BYTE)(length >> 8);
241 	buffer[4] = (BYTE)(length & 0xFF);
242 	// add the tag value
243 	memcpy(buffer + 5, (BYTE*)value, length);
244 	// append the previous profile
245 	if(NULL == profile)	{
246 		*profile_size = (5 + length);
247 	}
248 	else {
249 		memcpy(buffer + 5 + length, profile, *profile_size);
250 		*profile_size += (5 + length);
251 		free(profile);
252 	}
253 
254 	return buffer;
255 }
256 
257 /**
258 Encode IPTC metadata into a binary buffer.
259 The buffer is allocated by the function and must be freed by the caller.
260 */
261 BOOL
write_iptc_profile(FIBITMAP * dib,BYTE ** profile,unsigned * profile_size)262 write_iptc_profile(FIBITMAP *dib, BYTE **profile, unsigned *profile_size) {
263 	FITAG *tag = NULL;
264 	FIMETADATA *mdhandle = NULL;
265 
266 	BYTE *buffer = NULL;
267 	unsigned buffer_size = 0;
268 
269 	// parse all IPTC tags and rebuild a IPTC profile
270 	mdhandle = FreeImage_FindFirstMetadata(FIMD_IPTC, dib, &tag);
271 
272 	if(mdhandle) {
273 		do {
274 			WORD tag_id	= FreeImage_GetTagID(tag);
275 
276 			// append the tag to the profile
277 
278 			switch(tag_id) {
279 				case TAG_RECORD_VERSION:
280 					// ignore (already handled)
281 					break;
282 
283 				case TAG_SUPPLEMENTAL_CATEGORIES:
284 				case TAG_KEYWORDS:
285 					if(FreeImage_GetTagType(tag) == FIDT_ASCII) {
286 						std::string value = (const char*)FreeImage_GetTagValue(tag);
287 
288 						// split the tag value
289 						std::vector<std::string> output;
290 						std::string delimiter = IPTC_DELIMITER;
291 
292 						size_t offset = 0;
293 						size_t delimiterIndex = 0;
294 
295 						delimiterIndex = value.find(delimiter, offset);
296 						while (delimiterIndex != std::string::npos) {
297 							output.push_back(value.substr(offset, delimiterIndex - offset));
298 							offset += delimiterIndex - offset + delimiter.length();
299 							delimiterIndex = value.find(delimiter, offset);
300 						}
301 						output.push_back(value.substr(offset));
302 
303 						// add as many tags as there are comma separated strings
304 						for(int i = 0; i < (int)output.size(); i++) {
305 							std::string& tag_value = output[i];
306 							buffer = append_iptc_tag(buffer, &buffer_size, tag_id, (DWORD)tag_value.length(), tag_value.c_str());
307 						}
308 
309 					}
310 					break;
311 
312 				case TAG_URGENCY:
313 					if(FreeImage_GetTagType(tag) == FIDT_ASCII) {
314 						DWORD length = 1;	// keep the first octet only
315 						buffer = append_iptc_tag(buffer, &buffer_size, tag_id, length, FreeImage_GetTagValue(tag));
316 					}
317 					break;
318 
319 				default:
320 					if(FreeImage_GetTagType(tag) == FIDT_ASCII) {
321 						DWORD length = FreeImage_GetTagLength(tag);
322 						buffer = append_iptc_tag(buffer, &buffer_size, tag_id, length, FreeImage_GetTagValue(tag));
323 					}
324 					break;
325 			}
326 
327 		} while(FreeImage_FindNextMetadata(mdhandle, &tag));
328 
329 		FreeImage_FindCloseMetadata(mdhandle);
330 
331 		// add the DirectoryVersion tag
332 		const short version = 0x0200;
333 		buffer = append_iptc_tag(buffer, &buffer_size, TAG_RECORD_VERSION, sizeof(version), &version);
334 
335 		*profile = buffer;
336 		*profile_size = buffer_size;
337 
338 		return TRUE;
339 	}
340 
341 	return FALSE;
342 }
343