Coverage Report

Created: 2023-12-08 06:53

/src/freeimage-svn/FreeImage/trunk/Source/Metadata/IPTC.cpp
Line
Count
Source (jump to first uncovered line)
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 
39
0
read_iptc_profile(FIBITMAP *dib, const BYTE *dataptr, unsigned int datalen) {
40
0
  char defaultKey[16];
41
0
  size_t length = datalen;
42
0
  BYTE *profile = (BYTE*)dataptr;
43
44
0
  const char *JPEG_AdobeCM_Tag = "Adobe_CM";
45
46
0
  std::string Keywords;
47
0
  std::string SupplementalCategory;
48
49
0
  WORD tag_id;
50
51
0
  if(!dataptr || (datalen == 0)) {
52
0
    return FALSE;
53
0
  }
54
55
0
  if(datalen > 8) {
56
0
    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
0
      return FALSE;
61
0
    }
62
0
  }
63
64
65
  // create a tag
66
67
0
  FITAG *tag = FreeImage_CreateTag();
68
69
0
  TagLib& tag_lib = TagLib::instance();
70
71
    // find start of the BIM portion of the binary data
72
0
    size_t offset = 0;
73
0
  while(offset < length - 1) {
74
0
    if((profile[offset] == 0x1C) && (profile[offset+1] == 0x02))
75
0
      break;
76
0
    offset++;
77
0
  }
78
79
    // for each tag
80
0
    while (offset < length) {
81
82
        // identifies start of a tag
83
0
        if (profile[offset] != 0x1c) {
84
0
            break;
85
0
        }
86
        // we need at least five bytes left to read a tag
87
0
        if ((offset + 5) >= length) {
88
0
            break;
89
0
        }
90
91
0
        offset++;
92
93
0
    int directoryType = profile[offset++];
94
0
        int tagType     = profile[offset++];;
95
0
        int tagByteCount  = ((profile[offset] & 0xFF) << 8) | (profile[offset + 1] & 0xFF);
96
0
        offset += 2;
97
98
0
        if ((offset + tagByteCount) > length) {
99
            // data for tag extends beyond end of iptc segment
100
0
            break;
101
0
        }
102
103
0
    if(tagByteCount == 0) {
104
      // go to next tag
105
0
      continue;
106
0
    }
107
108
    // process the tag
109
110
0
    tag_id = (WORD)(tagType | (directoryType << 8));
111
112
0
    FreeImage_SetTagID(tag, tag_id);
113
0
    FreeImage_SetTagLength(tag, tagByteCount);
114
115
    // allocate a buffer to store the tag value
116
0
    BYTE *iptc_value = (BYTE*)malloc((tagByteCount + 1) * sizeof(BYTE));
117
0
    memset(iptc_value, 0, (tagByteCount + 1) * sizeof(BYTE));
118
119
    // get the tag value
120
121
0
    switch (tag_id) {
122
0
      case TAG_RECORD_VERSION:
123
0
      {
124
        // short
125
0
        FreeImage_SetTagType(tag, FIDT_SSHORT);
126
0
        FreeImage_SetTagCount(tag, 1);
127
0
        short *pvalue = (short*)&iptc_value[0];
128
0
        *pvalue = (short)((profile[offset] << 8) | profile[offset + 1]);
129
0
        FreeImage_SetTagValue(tag, pvalue);
130
0
        break;
131
0
      }
132
133
0
      case TAG_RELEASE_DATE:
134
0
      case TAG_DATE_CREATED:
135
        // Date object
136
0
      case TAG_RELEASE_TIME:
137
0
      case TAG_TIME_CREATED:
138
        // time
139
0
      default:
140
0
      {
141
        // string
142
0
        FreeImage_SetTagType(tag, FIDT_ASCII);
143
0
        FreeImage_SetTagCount(tag, tagByteCount);
144
0
        for(int i = 0; i < tagByteCount; i++) {
145
0
          iptc_value[i] = profile[offset + i];
146
0
        }
147
0
        iptc_value[tagByteCount] = '\0';
148
0
        FreeImage_SetTagValue(tag, (char*)&iptc_value[0]);
149
0
        break;
150
0
      }
151
0
    }
152
153
0
    if(tag_id == TAG_SUPPLEMENTAL_CATEGORIES) {
154
      // concatenate the categories
155
0
      if(SupplementalCategory.length() == 0) {
156
0
        SupplementalCategory.append((char*)iptc_value);
157
0
      } else {
158
0
        SupplementalCategory.append(IPTC_DELIMITER);
159
0
        SupplementalCategory.append((char*)iptc_value);
160
0
      }
161
0
    }
162
0
    else if(tag_id == TAG_KEYWORDS) {
163
      // concatenate the keywords
164
0
      if(Keywords.length() == 0) {
165
0
        Keywords.append((char*)iptc_value);
166
0
      } else {
167
0
        Keywords.append(IPTC_DELIMITER);
168
0
        Keywords.append((char*)iptc_value);
169
0
      }
170
0
    }
171
0
    else {
172
      // get the tag key and description
173
0
      const char *key = tag_lib.getTagFieldName(TagLib::IPTC, tag_id, defaultKey);
174
0
      FreeImage_SetTagKey(tag, key);
175
0
      const char *description = tag_lib.getTagDescription(TagLib::IPTC, tag_id);
176
0
      FreeImage_SetTagDescription(tag, description);
177
178
      // store the tag
179
0
      if(key) {
180
0
        FreeImage_SetMetadata(FIMD_IPTC, dib, key, tag);
181
0
      }
182
0
    }
183
184
0
    free(iptc_value);
185
186
        // next tag
187
0
    offset += tagByteCount;
188
189
0
    }
190
191
  // store the 'keywords' tag
192
0
  if(Keywords.length()) {
193
0
    FreeImage_SetTagType(tag, FIDT_ASCII);
194
0
    FreeImage_SetTagID(tag, TAG_KEYWORDS);
195
0
    FreeImage_SetTagKey(tag, tag_lib.getTagFieldName(TagLib::IPTC, TAG_KEYWORDS, defaultKey));
196
0
    FreeImage_SetTagDescription(tag, tag_lib.getTagDescription(TagLib::IPTC, TAG_KEYWORDS));
197
0
    FreeImage_SetTagLength(tag, (DWORD)Keywords.length());
198
0
    FreeImage_SetTagCount(tag, (DWORD)Keywords.length());
199
0
    FreeImage_SetTagValue(tag, (char*)Keywords.c_str());
200
0
    FreeImage_SetMetadata(FIMD_IPTC, dib, FreeImage_GetTagKey(tag), tag);
201
0
  }
202
203
  // store the 'supplemental category' tag
204
0
  if(SupplementalCategory.length()) {
205
0
    FreeImage_SetTagType(tag, FIDT_ASCII);
206
0
    FreeImage_SetTagID(tag, TAG_SUPPLEMENTAL_CATEGORIES);
207
0
    FreeImage_SetTagKey(tag, tag_lib.getTagFieldName(TagLib::IPTC, TAG_SUPPLEMENTAL_CATEGORIES, defaultKey));
208
0
    FreeImage_SetTagDescription(tag, tag_lib.getTagDescription(TagLib::IPTC, TAG_SUPPLEMENTAL_CATEGORIES));
209
0
    FreeImage_SetTagLength(tag, (DWORD)SupplementalCategory.length());
210
0
    FreeImage_SetTagCount(tag, (DWORD)SupplementalCategory.length());
211
0
    FreeImage_SetTagValue(tag, (char*)SupplementalCategory.c_str());
212
0
    FreeImage_SetMetadata(FIMD_IPTC, dib, FreeImage_GetTagKey(tag), tag);
213
0
  }
214
215
  // delete the tag
216
217
0
  FreeImage_DeleteTag(tag);
218
219
0
  return TRUE;
220
0
}
221
222
// --------------------------------------------------------------------------
223
224
static BYTE* 
225
0
append_iptc_tag(BYTE *profile, unsigned *profile_size, WORD id, DWORD length, const void *value) {
226
0
  BYTE *buffer = NULL;
227
228
  // calculate the new buffer size
229
0
  size_t buffer_size = (5 + *profile_size + length) * sizeof(BYTE);
230
0
  buffer = (BYTE*)malloc(buffer_size);
231
0
  if(!buffer)
232
0
    return NULL;
233
234
  // add the header
235
0
  buffer[0] = 0x1C;
236
0
  buffer[1] = 0x02;
237
  // add the tag type
238
0
  buffer[2] = (BYTE)(id & 0x00FF);
239
  // add the tag length
240
0
  buffer[3] = (BYTE)(length >> 8);
241
0
  buffer[4] = (BYTE)(length & 0xFF);
242
  // add the tag value
243
0
  memcpy(buffer + 5, (BYTE*)value, length);
244
  // append the previous profile
245
0
  if(NULL == profile) {
246
0
    *profile_size = (5 + length);
247
0
  }
248
0
  else {
249
0
    memcpy(buffer + 5 + length, profile, *profile_size);
250
0
    *profile_size += (5 + length);
251
0
    free(profile);
252
0
  }
253
  
254
0
  return buffer;
255
0
}
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 
262
0
write_iptc_profile(FIBITMAP *dib, BYTE **profile, unsigned *profile_size) {
263
0
  FITAG *tag = NULL;
264
0
  FIMETADATA *mdhandle = NULL;
265
266
0
  BYTE *buffer = NULL;
267
0
  unsigned buffer_size = 0;
268
269
  // parse all IPTC tags and rebuild a IPTC profile
270
0
  mdhandle = FreeImage_FindFirstMetadata(FIMD_IPTC, dib, &tag);
271
272
0
  if(mdhandle) {
273
0
    do {
274
0
      WORD tag_id = FreeImage_GetTagID(tag);
275
276
      // append the tag to the profile
277
278
0
      switch(tag_id) {
279
0
        case TAG_RECORD_VERSION:
280
          // ignore (already handled)
281
0
          break;
282
283
0
        case TAG_SUPPLEMENTAL_CATEGORIES:
284
0
        case TAG_KEYWORDS:
285
0
          if(FreeImage_GetTagType(tag) == FIDT_ASCII) {
286
0
            std::string value = (const char*)FreeImage_GetTagValue(tag);
287
288
            // split the tag value
289
0
            std::vector<std::string> output;
290
0
            std::string delimiter = IPTC_DELIMITER;   
291
            
292
0
            size_t offset = 0;
293
0
            size_t delimiterIndex = 0;
294
295
0
            delimiterIndex = value.find(delimiter, offset);
296
0
            while (delimiterIndex != std::string::npos) {
297
0
              output.push_back(value.substr(offset, delimiterIndex - offset));
298
0
              offset += delimiterIndex - offset + delimiter.length();
299
0
              delimiterIndex = value.find(delimiter, offset);
300
0
            }
301
0
            output.push_back(value.substr(offset));
302
303
            // add as many tags as there are comma separated strings
304
0
            for(int i = 0; i < (int)output.size(); i++) {
305
0
              std::string& tag_value = output[i];
306
0
              buffer = append_iptc_tag(buffer, &buffer_size, tag_id, (DWORD)tag_value.length(), tag_value.c_str());
307
0
            }
308
309
0
          }
310
0
          break;
311
312
0
        case TAG_URGENCY:
313
0
          if(FreeImage_GetTagType(tag) == FIDT_ASCII) {
314
0
            DWORD length = 1; // keep the first octet only
315
0
            buffer = append_iptc_tag(buffer, &buffer_size, tag_id, length, FreeImage_GetTagValue(tag));
316
0
          }
317
0
          break;
318
319
0
        default:
320
0
          if(FreeImage_GetTagType(tag) == FIDT_ASCII) {
321
0
            DWORD length = FreeImage_GetTagLength(tag); 
322
0
            buffer = append_iptc_tag(buffer, &buffer_size, tag_id, length, FreeImage_GetTagValue(tag));
323
0
          }          
324
0
          break;
325
0
      }
326
327
0
    } while(FreeImage_FindNextMetadata(mdhandle, &tag));
328
    
329
0
    FreeImage_FindCloseMetadata(mdhandle);
330
331
    // add the DirectoryVersion tag
332
0
    const short version = 0x0200;
333
0
    buffer = append_iptc_tag(buffer, &buffer_size, TAG_RECORD_VERSION, sizeof(version), &version);
334
    
335
0
    *profile = buffer;
336
0
    *profile_size = buffer_size;
337
338
0
    return TRUE;
339
0
  }
340
341
0
  return FALSE;
342
0
}