/src/graphicsmagick/magick/attribute.c
Line | Count | Source |
1 | | /* |
2 | | % Copyright (C) 2003-2025 GraphicsMagick Group |
3 | | % Copyright (C) 2002 ImageMagick Studio |
4 | | % |
5 | | % This program is covered by multiple licenses, which are described in |
6 | | % Copyright.txt. You should have received a copy of Copyright.txt with this |
7 | | % package; otherwise see http://www.graphicsmagick.org/www/Copyright.html. |
8 | | % |
9 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
10 | | % % |
11 | | % % |
12 | | % % |
13 | | % AAA TTTTT TTTTT RRRR IIIII BBBB U U TTTTT EEEEE % |
14 | | % A A T T R R I B B U U T E % |
15 | | % AAAAA T T RRRR I BBBB U U T EEE % |
16 | | % A A T T R R I B B U U T E % |
17 | | % A A T T R R IIIII BBBB UUU T EEEEE % |
18 | | % % |
19 | | % % |
20 | | % Methods to Get/Set/Destroy Image Text Attributes % |
21 | | % % |
22 | | % % |
23 | | % Software Design % |
24 | | % John Cristy % |
25 | | % February 2000 % |
26 | | % % |
27 | | % % |
28 | | % % |
29 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
30 | | % |
31 | | % The Attributes methods gets, sets, or destroys attributes associated |
32 | | % with a particular image (e.g. comments, copyright, author, etc). |
33 | | % |
34 | | % |
35 | | */ |
36 | | |
37 | | /* |
38 | | Include declarations. |
39 | | */ |
40 | | #include "magick/studio.h" |
41 | | #include "magick/attribute.h" |
42 | | #include "magick/blob.h" |
43 | | #include "magick/log.h" |
44 | | #include "magick/profile.h" |
45 | | #include "magick/render.h" |
46 | | #include "magick/tempfile.h" |
47 | | #include "magick/utility.h" |
48 | | |
49 | | /* |
50 | | Forward declarations. |
51 | | */ |
52 | | static void DestroyImageAttribute(ImageAttribute *attribute); |
53 | | |
54 | | /* |
55 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
56 | | % % |
57 | | % % |
58 | | % % |
59 | | % C l o n e I m a g e A t t r i b u t e s % |
60 | | % % |
61 | | % % |
62 | | % % |
63 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
64 | | % |
65 | | % CloneImageAttributes() copies the text attributes from one image to another. |
66 | | % Any text attributes in the destination image are preserved. |
67 | | % CloneImageAttributes returns MagickPass if all of the attribututes are |
68 | | % successfully cloned or MagickFail if there is a memory allocation error. |
69 | | % |
70 | | % The format of the CloneImageAttributes method is: |
71 | | % |
72 | | % MagickPassFail CloneImageAttributes(Image* clone_image, |
73 | | % const Image* original_image) |
74 | | % |
75 | | % A description of each parameter follows: |
76 | | % |
77 | | % o clone_image: The destination image. |
78 | | % |
79 | | % o original_image: The source image. |
80 | | % |
81 | | % |
82 | | */ |
83 | | MagickExport MagickPassFail |
84 | | CloneImageAttributes(Image* clone_image, |
85 | | const Image* original_image) |
86 | 440k | { |
87 | 440k | MagickPassFail |
88 | 440k | status; |
89 | | |
90 | 440k | ImageAttribute |
91 | 440k | *cloned_attribute, |
92 | 440k | *cloned_attributes; |
93 | | |
94 | 440k | const ImageAttribute |
95 | 440k | *attribute; |
96 | | |
97 | 440k | status = MagickPass; |
98 | | |
99 | | /* |
100 | | Search for tail of list (if any) |
101 | | */ |
102 | 440k | if ((cloned_attributes=clone_image->attributes)) |
103 | 0 | { |
104 | 0 | for( ; |
105 | 0 | cloned_attributes->next != (ImageAttribute *) NULL; |
106 | 0 | cloned_attributes=cloned_attributes->next); |
107 | 0 | } |
108 | | |
109 | 440k | attribute=GetImageAttribute(original_image,(char *) NULL); |
110 | 1.23M | for ( ; attribute != (const ImageAttribute *) NULL; |
111 | 796k | attribute=attribute->next) |
112 | 796k | { |
113 | | /* |
114 | | Construct AttributeInfo to append. |
115 | | */ |
116 | 796k | cloned_attribute=MagickAllocateMemory(ImageAttribute *, |
117 | 796k | sizeof(ImageAttribute)); |
118 | 796k | if (cloned_attribute == (ImageAttribute *) NULL) |
119 | 0 | { |
120 | 0 | status = MagickFail; |
121 | 0 | break; |
122 | 0 | } |
123 | 796k | cloned_attribute->key=AcquireString(attribute->key); |
124 | 796k | cloned_attribute->length=attribute->length; |
125 | 796k | cloned_attribute->value= |
126 | 796k | MagickAllocateMemory(char *,cloned_attribute->length+1); |
127 | 796k | cloned_attribute->previous=(ImageAttribute *) NULL; |
128 | 796k | cloned_attribute->next=(ImageAttribute *) NULL; |
129 | 796k | if ((cloned_attribute->value == (char *) NULL) || |
130 | 796k | (cloned_attribute->key == (char *) NULL)) |
131 | 0 | { |
132 | 0 | DestroyImageAttribute(cloned_attribute); |
133 | 0 | status = MagickFail; |
134 | 0 | break; |
135 | 0 | } |
136 | 796k | (void) strlcpy(cloned_attribute->value,attribute->value,cloned_attribute->length+1); |
137 | | |
138 | 796k | if (cloned_attributes == (ImageAttribute *) NULL) |
139 | 225k | { |
140 | | /* |
141 | | Start list |
142 | | */ |
143 | 225k | cloned_attributes=cloned_attribute; |
144 | 225k | clone_image->attributes=cloned_attributes; |
145 | 225k | } |
146 | 570k | else |
147 | 570k | { |
148 | | /* |
149 | | Append to list |
150 | | */ |
151 | 570k | cloned_attributes->next=cloned_attribute; |
152 | 570k | cloned_attribute->previous=cloned_attributes; |
153 | 570k | cloned_attributes=cloned_attribute; |
154 | 570k | } |
155 | 796k | } |
156 | | |
157 | 440k | return status; |
158 | 440k | } |
159 | | |
160 | | /* |
161 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
162 | | % % |
163 | | % % |
164 | | % % |
165 | | % D e s t r o y I m a g e A t t r i b u t e s % |
166 | | % % |
167 | | % % |
168 | | % % |
169 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
170 | | % |
171 | | % DestroyImageAttributes() deallocates memory associated with the image |
172 | | % attribute list. |
173 | | % |
174 | | % The format of the DestroyImageAttributes method is: |
175 | | % |
176 | | % DestroyImageAttributes(Image *image) |
177 | | % |
178 | | % A description of each parameter follows: |
179 | | % |
180 | | % o image: The image. |
181 | | % |
182 | | % |
183 | | */ |
184 | | static void |
185 | | DestroyImageAttribute(ImageAttribute *attribute) |
186 | 5.56M | { |
187 | 5.56M | if (attribute == (ImageAttribute *) NULL) |
188 | 0 | return; |
189 | 5.56M | MagickFreeMemory(attribute->value); |
190 | 5.56M | MagickFreeMemory(attribute->key); |
191 | 5.56M | (void) memset(attribute,0xbf,sizeof(ImageAttribute)); |
192 | 5.56M | MagickFreeMemory(attribute); |
193 | 5.56M | } |
194 | | MagickExport void DestroyImageAttributes(Image *image) |
195 | 14.7M | { |
196 | 14.7M | ImageAttribute |
197 | 14.7M | *attribute; |
198 | | |
199 | 14.7M | register ImageAttribute |
200 | 14.7M | *p; |
201 | | |
202 | 14.7M | assert(image != (Image *) NULL); |
203 | 14.7M | assert(image->signature == MagickSignature); |
204 | 17.6M | for (p=image->attributes; p != (ImageAttribute *) NULL; ) |
205 | 2.87M | { |
206 | 2.87M | attribute=p; |
207 | 2.87M | p=p->next; |
208 | 2.87M | DestroyImageAttribute(attribute); |
209 | 2.87M | } |
210 | 14.7M | image->attributes=(ImageAttribute *) NULL; |
211 | 14.7M | } |
212 | | |
213 | | /* |
214 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
215 | | % % |
216 | | % % |
217 | | % % |
218 | | % G e t I m a g e A t t r i b u t e % |
219 | | % % |
220 | | % % |
221 | | % % |
222 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
223 | | % |
224 | | % GetImageAttribute() searches the list of image attributes and returns |
225 | | % a pointer to the attribute if it exists otherwise NULL. |
226 | | % |
227 | | % The format of the GetImageAttribute method is: |
228 | | % |
229 | | % const ImageAttribute *GetImageAttribute(const Image *image, |
230 | | % const char *key) |
231 | | % |
232 | | % A description of each parameter follows: |
233 | | % |
234 | | % o image: The image. |
235 | | % |
236 | | % o key: These character strings are the name of an image attribute to |
237 | | % return. |
238 | | % |
239 | | % |
240 | | */ |
241 | | |
242 | | static unsigned int |
243 | | GenerateIPTCAttribute(Image *image,const char *key) |
244 | 0 | { |
245 | | #if 0 |
246 | | static const struct |
247 | | { |
248 | | char *name; |
249 | | int dataset; |
250 | | int record; |
251 | | } |
252 | | #define IPTC_ATTRIBUTE(dataset,record,name) {name,dataset,record} |
253 | | IPTCAttributes[] = |
254 | | { |
255 | | IPTC_ATTRIBUTE(2,5,"Image Name"), |
256 | | IPTC_ATTRIBUTE(2,7,"Edit Status"), |
257 | | IPTC_ATTRIBUTE(2,10,"Priority"), |
258 | | IPTC_ATTRIBUTE(2,15,"Category"), |
259 | | IPTC_ATTRIBUTE(2,20,"Supplemental Category"), |
260 | | IPTC_ATTRIBUTE(2,22,"Fixture Identifier"), |
261 | | IPTC_ATTRIBUTE(2,25,"Keyword"), |
262 | | IPTC_ATTRIBUTE(2,30,"Release Date"), |
263 | | IPTC_ATTRIBUTE(2,35,"Release Time"), |
264 | | IPTC_ATTRIBUTE(2,40,"Special Instructions"), |
265 | | IPTC_ATTRIBUTE(2,45,"Reference Service"), |
266 | | IPTC_ATTRIBUTE(2,47,"Reference Date"), |
267 | | IPTC_ATTRIBUTE(2,50,"Reference Number"), |
268 | | IPTC_ATTRIBUTE(2,55,"Created Date"), |
269 | | IPTC_ATTRIBUTE(2,60,"Created Time"), |
270 | | IPTC_ATTRIBUTE(2,65,"Originating Program"), |
271 | | IPTC_ATTRIBUTE(2,70,"Program Version"), |
272 | | IPTC_ATTRIBUTE(2,75,"Object Cycle"), |
273 | | IPTC_ATTRIBUTE(2,80,"Byline"), |
274 | | IPTC_ATTRIBUTE(2,85,"Byline Title"), |
275 | | IPTC_ATTRIBUTE(2,90,"City"), |
276 | | IPTC_ATTRIBUTE(2,95,"Province State"), |
277 | | IPTC_ATTRIBUTE(2,100,"Country Code"), |
278 | | IPTC_ATTRIBUTE(2,101,"Country"), |
279 | | IPTC_ATTRIBUTE(2,103,"Original Transmission Reference"), |
280 | | IPTC_ATTRIBUTE(2,105,"Headline"), |
281 | | IPTC_ATTRIBUTE(2,110,"Credit"), |
282 | | IPTC_ATTRIBUTE(2,115,"Source"), |
283 | | IPTC_ATTRIBUTE(2,116,"Copyright String"), |
284 | | IPTC_ATTRIBUTE(2,120,"Caption"), |
285 | | IPTC_ATTRIBUTE(2,121,"Local Caption"), |
286 | | IPTC_ATTRIBUTE(2,122,"Caption Writer"), |
287 | | IPTC_ATTRIBUTE(2,200,"Custom Field 1"), |
288 | | IPTC_ATTRIBUTE(2,201,"Custom Field 2"), |
289 | | IPTC_ATTRIBUTE(2,202,"Custom Field 3"), |
290 | | IPTC_ATTRIBUTE(2,203,"Custom Field 4"), |
291 | | IPTC_ATTRIBUTE(2,204,"Custom Field 5"), |
292 | | IPTC_ATTRIBUTE(2,205,"Custom Field 6"), |
293 | | IPTC_ATTRIBUTE(2,206,"Custom Field 7"), |
294 | | IPTC_ATTRIBUTE(2,207,"Custom Field 8"), |
295 | | IPTC_ATTRIBUTE(2,208,"Custom Field 9"), |
296 | | IPTC_ATTRIBUTE(2,209,"Custom Field 10"), |
297 | | IPTC_ATTRIBUTE(2,210,"Custom Field 11"), |
298 | | IPTC_ATTRIBUTE(2,211,"Custom Field 12"), |
299 | | IPTC_ATTRIBUTE(2,212,"Custom Field 13"), |
300 | | IPTC_ATTRIBUTE(2,213,"Custom Field 14"), |
301 | | IPTC_ATTRIBUTE(2,214,"Custom Field 15"), |
302 | | IPTC_ATTRIBUTE(2,215,"Custom Field 16"), |
303 | | IPTC_ATTRIBUTE(2,216,"Custom Field 17"), |
304 | | IPTC_ATTRIBUTE(2,217,"Custom Field 18"), |
305 | | IPTC_ATTRIBUTE(2,218,"Custom Field 19"), |
306 | | IPTC_ATTRIBUTE(2,219,"Custom Field 20") |
307 | | }; |
308 | | #endif |
309 | |
|
310 | 0 | char |
311 | 0 | *attribute; |
312 | |
|
313 | 0 | int |
314 | 0 | count, |
315 | 0 | dataset, |
316 | 0 | record; |
317 | |
|
318 | 0 | register long |
319 | 0 | i; |
320 | |
|
321 | 0 | size_t |
322 | 0 | length; |
323 | |
|
324 | 0 | const unsigned char |
325 | 0 | *profile; |
326 | |
|
327 | 0 | size_t |
328 | 0 | profile_length; |
329 | |
|
330 | 0 | if((profile=GetImageProfile(image,"IPTC",&profile_length)) == 0) |
331 | 0 | return(False); |
332 | 0 | count=sscanf(key,"IPTC:%d:%d",&dataset,&record); |
333 | 0 | if (count != 2) |
334 | 0 | return(False); |
335 | 0 | for (i=0; i < (long) profile_length; i++) |
336 | 0 | { |
337 | 0 | if (profile[i] != 0x1cU) |
338 | 0 | continue; |
339 | 0 | if (profile[i+1] != dataset) |
340 | 0 | { |
341 | | /* fprintf(stderr,"Skipping dataset %d\n",profile[i+1]); */ |
342 | 0 | continue; |
343 | 0 | } |
344 | 0 | if (profile[i+2] != record) |
345 | 0 | { |
346 | | /* fprintf(stderr,"Skipping record %d\n",profile[i+2]); */ |
347 | 0 | continue; |
348 | 0 | } |
349 | 0 | length=(size_t) profile[i+3] << 8; |
350 | 0 | length|=(size_t) profile[i+4]; |
351 | 0 | attribute=MagickAllocateMemory(char *,length+1); |
352 | 0 | if (attribute == (char *) NULL) |
353 | 0 | continue; |
354 | 0 | (void) strlcpy(attribute,(char *) profile+i+5,length+1); |
355 | 0 | (void) SetImageAttribute(image,key,(const char *) attribute); |
356 | 0 | MagickFreeMemory(attribute); |
357 | 0 | break; |
358 | 0 | } |
359 | 0 | return(i < (long) profile_length); |
360 | 0 | } |
361 | | |
362 | | static unsigned char |
363 | | ReadByte(unsigned char **p,size_t *length) |
364 | 9.78M | { |
365 | 9.78M | unsigned char |
366 | 9.78M | c; |
367 | | |
368 | 9.78M | if (*length < 1) |
369 | 1.06k | return(0xff); |
370 | 9.78M | c=(*(*p)++); |
371 | 9.78M | (*length)--; |
372 | 9.78M | return(c); |
373 | 9.78M | } |
374 | | |
375 | | static magick_int32_t |
376 | | ReadMSBLong(unsigned char **p,size_t *length) |
377 | 0 | { |
378 | 0 | int |
379 | 0 | c; |
380 | |
|
381 | 0 | union |
382 | 0 | { |
383 | 0 | magick_uint32_t u; |
384 | 0 | magick_int32_t s; |
385 | 0 | } value; |
386 | |
|
387 | 0 | register unsigned int |
388 | 0 | i; |
389 | |
|
390 | 0 | unsigned char |
391 | 0 | buffer[4]; |
392 | |
|
393 | 0 | if (*length < 4) |
394 | 0 | return(-1); |
395 | 0 | for (i=0; i < 4; i++) |
396 | 0 | { |
397 | 0 | c=(*(*p)++); |
398 | 0 | (*length)--; |
399 | 0 | buffer[i]=(unsigned char) c; |
400 | 0 | } |
401 | 0 | value.u=(magick_uint32_t) (buffer[0] & 0xff) << 24; |
402 | 0 | value.u|=(magick_uint32_t) buffer[1] << 16; |
403 | 0 | value.u|=(magick_uint32_t) buffer[2] << 8; |
404 | 0 | value.u|=(magick_uint32_t) buffer[3]; |
405 | 0 | return(value.s); |
406 | 0 | } |
407 | | |
408 | | static magick_int32_t |
409 | | ReadMSBShort(unsigned char **p,size_t *length) |
410 | 0 | { |
411 | 0 | int |
412 | 0 | c; |
413 | |
|
414 | 0 | union |
415 | 0 | { |
416 | 0 | magick_uint32_t u; |
417 | 0 | magick_int32_t s; |
418 | 0 | } value; |
419 | |
|
420 | 0 | register unsigned int |
421 | 0 | i; |
422 | |
|
423 | 0 | unsigned char |
424 | 0 | buffer[2]; |
425 | |
|
426 | 0 | if (*length < 2) |
427 | 0 | return(-1); |
428 | 0 | for (i=0; i < 2; i++) |
429 | 0 | { |
430 | 0 | c=(*(*p)++); |
431 | 0 | (*length)--; |
432 | 0 | buffer[i]=(unsigned char) c; |
433 | 0 | } |
434 | 0 | value.u=(magick_uint32_t) (buffer[0] & 0xff) << 8; |
435 | 0 | value.u|=(magick_uint32_t) buffer[1]; |
436 | 0 | return(value.s); |
437 | 0 | } |
438 | | |
439 | | /* |
440 | | Advance 'blob' by 'amount' or the amount remaining in 'length'. |
441 | | Decrement 'length' by 'amount' or to zero. |
442 | | */ |
443 | | static inline size_t AdvanceBlob(const size_t amount, size_t length, unsigned char **blob) |
444 | 0 | { |
445 | 0 | if (length > amount) |
446 | 0 | { |
447 | 0 | *blob+=amount; |
448 | 0 | length-=amount; |
449 | 0 | } |
450 | 0 | else |
451 | 0 | { |
452 | 0 | *blob+=length; |
453 | 0 | length=0U; |
454 | 0 | } |
455 | 0 | return length; |
456 | 0 | } |
457 | | |
458 | | static char * |
459 | | TracePSClippingPath(unsigned char *blob,size_t length, |
460 | | const unsigned long columns, |
461 | | const unsigned long rows) |
462 | 0 | { |
463 | 0 | char |
464 | 0 | *path, |
465 | 0 | *message; |
466 | |
|
467 | 0 | int |
468 | 0 | knot_count, |
469 | 0 | selector; |
470 | |
|
471 | 0 | long |
472 | 0 | x, |
473 | 0 | y; |
474 | |
|
475 | 0 | PointInfo |
476 | 0 | first[3], /* First Bezier knot in sub-path */ |
477 | 0 | last[3], /* Last seen Bezier knot in sub-path */ |
478 | 0 | point[3]; /* Current Bezier knot in sub-path */ |
479 | |
|
480 | 0 | register long |
481 | 0 | i; |
482 | |
|
483 | 0 | unsigned int |
484 | 0 | in_subpath; |
485 | |
|
486 | 0 | ARG_NOT_USED(columns); |
487 | 0 | ARG_NOT_USED(rows); |
488 | |
|
489 | 0 | first[0].x=first[0].y=first[1].x=first[1].y=0; |
490 | 0 | last[1].x=last[1].y=last[2].x=last[2].y=0; |
491 | 0 | path=AllocateString((char *) NULL); |
492 | 0 | if (path == (char *) NULL) |
493 | 0 | return((char *) NULL); |
494 | 0 | message=AllocateString((char *) NULL); |
495 | |
|
496 | 0 | FormatString(message,"/ClipImage {\n"); |
497 | 0 | (void) ConcatenateString(&path,message); |
498 | 0 | FormatString(message,"/c {curveto} bind def\n"); |
499 | 0 | (void) ConcatenateString(&path,message); |
500 | 0 | FormatString(message,"/l {lineto} bind def\n"); |
501 | 0 | (void) ConcatenateString(&path,message); |
502 | 0 | FormatString(message,"/m {moveto} bind def\n"); |
503 | 0 | (void) ConcatenateString(&path,message); |
504 | 0 | FormatString(message,"/v {currentpoint 6 2 roll curveto} bind def\n"); |
505 | 0 | (void) ConcatenateString(&path,message); |
506 | 0 | FormatString(message,"/y {2 copy curveto} bind def\n"); |
507 | 0 | (void) ConcatenateString(&path,message); |
508 | 0 | FormatString(message,"/z {closepath} bind def\n"); |
509 | 0 | (void) ConcatenateString(&path,message); |
510 | 0 | FormatString(message,"newpath\n"); |
511 | 0 | (void) ConcatenateString(&path,message); |
512 | |
|
513 | 0 | knot_count=0; |
514 | 0 | in_subpath=False; |
515 | | |
516 | | /* |
517 | | Open and closed subpaths are all closed in the following |
518 | | parser loop as there's no way for the polygon renderer |
519 | | to render an open path to a masking image. |
520 | | |
521 | | The clipping path format is defined in "Adobe Photoshop File |
522 | | Formats Specification" version 6.0 downloadable from adobe.com. |
523 | | */ |
524 | 0 | while (length > 0) |
525 | 0 | { |
526 | 0 | selector=ReadMSBShort(&blob,&length); |
527 | 0 | switch (selector) |
528 | 0 | { |
529 | 0 | case 0: |
530 | 0 | case 3: |
531 | 0 | { |
532 | 0 | if (knot_count == 0) |
533 | 0 | { |
534 | | /* |
535 | | Expected subpath length record |
536 | | */ |
537 | 0 | knot_count=ReadMSBShort(&blob,&length); |
538 | 0 | length=AdvanceBlob(22U,length,&blob); |
539 | 0 | } |
540 | 0 | else |
541 | 0 | { |
542 | 0 | length=AdvanceBlob(24U,length,&blob); |
543 | 0 | } |
544 | 0 | break; |
545 | 0 | } |
546 | 0 | case 1: |
547 | 0 | case 2: |
548 | 0 | case 4: |
549 | 0 | case 5: |
550 | 0 | { |
551 | 0 | if (knot_count == 0) |
552 | 0 | { |
553 | | /* |
554 | | Unexpected subpath knot |
555 | | */ |
556 | 0 | length=AdvanceBlob(24U,length,&blob); |
557 | 0 | } |
558 | 0 | else |
559 | 0 | { |
560 | | /* |
561 | | Add sub-path knot |
562 | | */ |
563 | 0 | for (i=0; i < 3; i++) |
564 | 0 | { |
565 | 0 | y=ReadMSBLong(&blob,&length); |
566 | 0 | x=ReadMSBLong(&blob,&length); |
567 | 0 | point[i].x=(double) x/4096/4096; |
568 | 0 | point[i].y=1.0-(double) y/4096/4096; |
569 | 0 | } |
570 | 0 | if (!in_subpath) |
571 | 0 | { |
572 | 0 | FormatString(message,"%.6f %.6f m\n", |
573 | 0 | point[1].x,point[1].y); |
574 | 0 | for (i=0; i < 3; i++) |
575 | 0 | { |
576 | 0 | first[i]=point[i]; |
577 | 0 | last[i]=point[i]; |
578 | 0 | } |
579 | 0 | } |
580 | 0 | else |
581 | 0 | { |
582 | | /* |
583 | | Handle special cases when Bezier curves are used |
584 | | to describe corners and straight lines. This |
585 | | special handling is desirable to bring down the |
586 | | size in bytes of the clipping path data. |
587 | | */ |
588 | 0 | if ((last[1].x == last[2].x) && |
589 | 0 | (last[1].y == last[2].y) && |
590 | 0 | (point[0].x == point[1].x) && |
591 | 0 | (point[0].y == point[1].y)) |
592 | 0 | { |
593 | | /* |
594 | | First control point equals first anchor |
595 | | point and last control point equals last |
596 | | anchor point. Straight line between anchor |
597 | | points. |
598 | | */ |
599 | 0 | FormatString(message,"%.6f %.6f l\n", |
600 | 0 | point[1].x,point[1].y); |
601 | 0 | } |
602 | 0 | else if ((last[1].x == last[2].x) && |
603 | 0 | (last[1].y == last[2].y)) |
604 | 0 | { |
605 | | /* First control point equals first anchor point */ |
606 | 0 | FormatString(message,"%.6f %.6f %.6f %.6f v\n", |
607 | 0 | point[0].x,point[0].y, |
608 | 0 | point[1].x,point[1].y); |
609 | 0 | } |
610 | 0 | else if ((point[0].x == point[1].x) && |
611 | 0 | (point[0].y == point[1].y)) |
612 | 0 | { |
613 | | /* Last control point equals last anchor point. */ |
614 | 0 | FormatString(message,"%.6f %.6f %.6f %.6f y\n", |
615 | 0 | last[2].x,last[2].y, |
616 | 0 | point[1].x,point[1].y); |
617 | 0 | } |
618 | 0 | else |
619 | 0 | { |
620 | | /* The full monty */ |
621 | 0 | FormatString(message, |
622 | 0 | "%.6f %.6f %.6f %.6f %.6f %.6f c\n", |
623 | 0 | last[2].x,last[2].y,point[0].x, |
624 | 0 | point[0].y,point[1].x, |
625 | 0 | point[1].y); |
626 | 0 | } |
627 | 0 | for (i=0; i < 3; i++) |
628 | 0 | last[i]=point[i]; |
629 | 0 | } |
630 | 0 | (void) ConcatenateString(&path,message); |
631 | 0 | in_subpath=True; |
632 | 0 | knot_count--; |
633 | | /* |
634 | | Close the subpath if there are no more knots. |
635 | | */ |
636 | 0 | if (knot_count == 0) |
637 | 0 | { |
638 | | /* |
639 | | Same special handling as above except we compare |
640 | | to the first point in the path and close the |
641 | | path. |
642 | | */ |
643 | 0 | if ((last[1].x == last[2].x) && |
644 | 0 | (last[1].y == last[2].y) && |
645 | 0 | (first[0].x == first[1].x) && |
646 | 0 | (first[0].y == first[1].y)) |
647 | 0 | { |
648 | 0 | FormatString(message,"%.6f %.6f l z\n", |
649 | 0 | first[1].x,first[1].y); |
650 | 0 | } |
651 | 0 | else if ((last[1].x == last[2].x) && |
652 | 0 | (last[1].y == last[2].y)) |
653 | 0 | { |
654 | 0 | FormatString(message,"%.6f %.6f %.6f %.6f v z\n", |
655 | 0 | first[0].x,first[0].y, |
656 | 0 | first[1].x,first[1].y); |
657 | 0 | } |
658 | 0 | else if ((first[0].x == first[1].x) && |
659 | 0 | (first[0].y == first[1].y)) |
660 | 0 | { |
661 | 0 | FormatString(message,"%.6f %.6f %.6f %.6f y z\n", |
662 | 0 | last[2].x,last[2].y, |
663 | 0 | first[1].x,first[1].y); |
664 | 0 | } |
665 | 0 | else |
666 | 0 | { |
667 | 0 | FormatString(message, |
668 | 0 | "%.6f %.6f %.6f %.6f %.6f %.6f c z\n", |
669 | 0 | last[2].x,last[2].y, |
670 | 0 | first[0].x,first[0].y, |
671 | 0 | first[1].x,first[1].y); |
672 | 0 | } |
673 | 0 | (void) ConcatenateString(&path,message); |
674 | 0 | in_subpath=False; |
675 | 0 | } |
676 | 0 | } |
677 | 0 | break; |
678 | 0 | } |
679 | 0 | case 6: |
680 | 0 | case 7: |
681 | 0 | case 8: |
682 | 0 | default: |
683 | 0 | { |
684 | 0 | length=AdvanceBlob(24U,length,&blob); |
685 | 0 | break; |
686 | 0 | } |
687 | 0 | } |
688 | 0 | } |
689 | | /* |
690 | | Returns an empty PS path if the path has no knots. |
691 | | */ |
692 | 0 | FormatString(message,"eoclip\n"); |
693 | 0 | (void) ConcatenateString(&path,message); |
694 | 0 | FormatString(message,"} bind def"); |
695 | 0 | (void) ConcatenateString(&path,message); |
696 | 0 | MagickFreeMemory(message); |
697 | 0 | return(path); |
698 | 0 | } |
699 | | |
700 | | static char * |
701 | | TraceSVGClippingPath(unsigned char *blob, |
702 | | size_t length, |
703 | | const unsigned long columns, |
704 | | const unsigned long rows) |
705 | 0 | { |
706 | 0 | char |
707 | 0 | *path, |
708 | 0 | *message; |
709 | |
|
710 | 0 | int |
711 | 0 | knot_count, |
712 | 0 | selector; |
713 | |
|
714 | 0 | long |
715 | 0 | x, |
716 | 0 | y; |
717 | |
|
718 | 0 | PointInfo |
719 | 0 | first[3], |
720 | 0 | last[3], |
721 | 0 | point[3]; |
722 | |
|
723 | 0 | register long |
724 | 0 | i; |
725 | |
|
726 | 0 | unsigned int |
727 | 0 | in_subpath; |
728 | |
|
729 | 0 | first[0].x=first[0].y=first[1].x=first[1].y=0; |
730 | 0 | last[1].x=last[1].y=last[2].x=last[2].y=0; |
731 | 0 | path=AllocateString((char *) NULL); |
732 | 0 | if (path == (char *) NULL) |
733 | 0 | return((char *) NULL); |
734 | 0 | message=AllocateString((char *) NULL); |
735 | |
|
736 | 0 | FormatString(message,"<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"); |
737 | 0 | (void) ConcatenateString(&path,message); |
738 | 0 | FormatString(message,"<svg width=\"%lu\" height=\"%lu\">\n",columns,rows); |
739 | 0 | (void) ConcatenateString(&path,message); |
740 | 0 | FormatString(message,"<g>\n"); |
741 | 0 | (void) ConcatenateString(&path,message); |
742 | 0 | FormatString(message,"<path style=\"fill:#00000000;stroke:#00000000;"); |
743 | 0 | (void) ConcatenateString(&path,message); |
744 | 0 | FormatString(message,"stroke-width:0;stroke-antialiasing:false\" d=\"\n"); |
745 | 0 | (void) ConcatenateString(&path,message); |
746 | |
|
747 | 0 | knot_count=0; |
748 | 0 | in_subpath=False; |
749 | | |
750 | | /* |
751 | | Open and closed subpaths are all closed in the following parser |
752 | | loop as there's no way for the polygon renderer to render an open |
753 | | path to a masking image. |
754 | | |
755 | | The clipping path format is defined in "Adobe Photoshop File |
756 | | Formats Specification" version 6.0 downloadable from adobe.com. |
757 | | */ |
758 | 0 | while (length > 0) |
759 | 0 | { |
760 | 0 | selector=ReadMSBShort(&blob,&length); |
761 | 0 | switch (selector) |
762 | 0 | { |
763 | 0 | case 0: |
764 | 0 | case 3: |
765 | 0 | { |
766 | 0 | if (knot_count == 0) |
767 | 0 | { |
768 | | /* |
769 | | Expected subpath length record |
770 | | */ |
771 | 0 | knot_count=ReadMSBShort(&blob,&length); |
772 | 0 | length=AdvanceBlob(22U,length,&blob); |
773 | 0 | } |
774 | 0 | else |
775 | 0 | { |
776 | 0 | length=AdvanceBlob(24U,length,&blob); |
777 | 0 | } |
778 | 0 | break; |
779 | 0 | } |
780 | 0 | case 1: |
781 | 0 | case 2: |
782 | 0 | case 4: |
783 | 0 | case 5: |
784 | 0 | { |
785 | 0 | if (knot_count == 0) |
786 | 0 | { |
787 | | /* |
788 | | Unexpected subpath knot |
789 | | */ |
790 | 0 | length=AdvanceBlob(24U,length,&blob); |
791 | 0 | } |
792 | 0 | else |
793 | 0 | { |
794 | | /* |
795 | | Add sub-path knot |
796 | | */ |
797 | 0 | for (i=0; i < 3; i++) |
798 | 0 | { |
799 | 0 | y=ReadMSBLong(&blob,&length); |
800 | 0 | x=ReadMSBLong(&blob,&length); |
801 | 0 | point[i].x=(double) x*columns/4096.0/4096.0; |
802 | 0 | point[i].y=(double) y*rows/4096.0/4096.0; |
803 | 0 | } |
804 | 0 | if (!in_subpath) |
805 | 0 | { |
806 | 0 | FormatString(message,"M %.6f,%.6f\n", |
807 | 0 | point[1].x,point[1].y); |
808 | 0 | for (i=0; i < 3; i++) |
809 | 0 | { |
810 | 0 | first[i]=point[i]; |
811 | 0 | last[i]=point[i]; |
812 | 0 | } |
813 | 0 | } |
814 | 0 | else |
815 | 0 | { |
816 | | /* |
817 | | Handle special case when Bezier curves are used |
818 | | to describe straight lines. |
819 | | */ |
820 | 0 | if ((last[1].x == last[2].x) && |
821 | 0 | (last[1].y == last[2].y) && |
822 | 0 | (point[0].x == point[1].x) && |
823 | 0 | (point[0].y == point[1].y)) |
824 | 0 | { |
825 | | /* |
826 | | First control point equals first anchor |
827 | | point and last control point equals last |
828 | | anchor point. Straight line between anchor |
829 | | points. |
830 | | */ |
831 | 0 | FormatString(message,"L %.6f,%.6f\n", |
832 | 0 | point[1].x,point[1].y); |
833 | 0 | } |
834 | 0 | else |
835 | 0 | { |
836 | 0 | FormatString(message, |
837 | 0 | "C %.6f,%.6f %.6f,%.6f %.6f,%.6f\n", |
838 | 0 | last[2].x,last[2].y, |
839 | 0 | point[0].x,point[0].y, |
840 | 0 | point[1].x,point[1].y); |
841 | 0 | } |
842 | 0 | for (i=0; i < 3; i++) |
843 | 0 | last[i]=point[i]; |
844 | 0 | } |
845 | 0 | (void) ConcatenateString(&path,message); |
846 | 0 | in_subpath=True; |
847 | 0 | knot_count--; |
848 | | /* |
849 | | Close the subpath if there are no more knots. |
850 | | */ |
851 | 0 | if (knot_count == 0) |
852 | 0 | { |
853 | | /* |
854 | | Same special handling as above except we compare |
855 | | to the first point in the path and close the |
856 | | path. |
857 | | */ |
858 | 0 | if ((last[1].x == last[2].x) && |
859 | 0 | (last[1].y == last[2].y) && |
860 | 0 | (first[0].x == first[1].x) && |
861 | 0 | (first[0].y == first[1].y)) |
862 | 0 | { |
863 | 0 | FormatString(message, |
864 | 0 | "L %.6f,%.6f Z\n",first[1].x,first[1].y); |
865 | 0 | } |
866 | 0 | else |
867 | 0 | { |
868 | 0 | FormatString(message, |
869 | 0 | "C %.6f,%.6f %.6f,%.6f %.6f,%.6f Z\n", |
870 | 0 | last[2].x,last[2].y, |
871 | 0 | first[0].x,first[0].y, |
872 | 0 | first[1].x,first[1].y); |
873 | 0 | (void) ConcatenateString(&path,message); |
874 | 0 | } |
875 | 0 | in_subpath=False; |
876 | 0 | } |
877 | 0 | } |
878 | 0 | break; |
879 | 0 | } |
880 | 0 | case 6: |
881 | 0 | case 7: |
882 | 0 | case 8: |
883 | 0 | default: |
884 | 0 | { |
885 | 0 | length=AdvanceBlob(24U,length,&blob); |
886 | 0 | break; |
887 | 0 | } |
888 | 0 | } |
889 | 0 | } |
890 | | /* |
891 | | Returns an empty SVG image if the path has no knots. |
892 | | */ |
893 | 0 | FormatString(message,"\"/>\n"); |
894 | 0 | (void) ConcatenateString(&path,message); |
895 | 0 | FormatString(message,"</g>\n"); |
896 | 0 | (void) ConcatenateString(&path,message); |
897 | 0 | FormatString(message,"</svg>\n"); |
898 | 0 | (void) ConcatenateString(&path,message); |
899 | 0 | MagickFreeMemory(message); |
900 | 0 | return(path); |
901 | 0 | } |
902 | | |
903 | | static int |
904 | | Generate8BIMAttribute(Image *image,const char *key) |
905 | 0 | { |
906 | 0 | char |
907 | 0 | *attribute, |
908 | 0 | name[MaxTextExtent], |
909 | 0 | format[MaxTextExtent], |
910 | 0 | *resource; |
911 | |
|
912 | 0 | int |
913 | 0 | id, |
914 | 0 | start, |
915 | 0 | stop, |
916 | 0 | sub_number; |
917 | |
|
918 | 0 | register long |
919 | 0 | i; |
920 | |
|
921 | 0 | size_t |
922 | 0 | length; |
923 | |
|
924 | 0 | unsigned char |
925 | 0 | *info; |
926 | |
|
927 | 0 | unsigned int |
928 | 0 | status; |
929 | |
|
930 | 0 | long |
931 | 0 | count; |
932 | |
|
933 | 0 | const unsigned char |
934 | 0 | *profile; |
935 | |
|
936 | 0 | size_t |
937 | 0 | profile_length; |
938 | |
|
939 | 0 | if ((profile=GetImageProfile(image,"IPTC",&profile_length)) == 0) |
940 | 0 | return(False); |
941 | | |
942 | | /* |
943 | | There may be spaces in resource names, but there are no newlines, |
944 | | so use a newline as terminator to get the full name. |
945 | | */ |
946 | 0 | count=sscanf(key,"8BIM:%d,%d:%[^\n]\n%[^\n]",&start,&stop,name,format); |
947 | 0 | if ((count != 2) && (count != 3) && (count != 4)) |
948 | 0 | return(False); |
949 | 0 | if (count < 4) |
950 | 0 | (void)strlcpy(format,"SVG",sizeof(format)); |
951 | 0 | if (count < 3) |
952 | 0 | *name='\0'; |
953 | 0 | sub_number=1; |
954 | 0 | if (*name == '#') |
955 | 0 | sub_number=MagickAtoL(&name[1]); |
956 | 0 | sub_number=Max(sub_number,1); |
957 | 0 | resource=(char *) NULL; |
958 | |
|
959 | 0 | status=False; |
960 | 0 | length=profile_length; |
961 | | /* |
962 | | FIXME: following cast should not be necessary but info can't be |
963 | | const due to odd function design. |
964 | | */ |
965 | 0 | info=(unsigned char *) profile; |
966 | |
|
967 | 0 | while ((length > 0) && (status == False)) |
968 | 0 | { |
969 | 0 | if (ReadByte(&info,&length) != '8') |
970 | 0 | continue; |
971 | 0 | if (ReadByte(&info,&length) != 'B') |
972 | 0 | continue; |
973 | 0 | if (ReadByte(&info,&length) != 'I') |
974 | 0 | continue; |
975 | 0 | if (ReadByte(&info,&length) != 'M') |
976 | 0 | continue; |
977 | 0 | id=ReadMSBShort(&info,&length); |
978 | 0 | if (id < start) |
979 | 0 | continue; |
980 | 0 | if (id > stop) |
981 | 0 | continue; |
982 | 0 | if (resource != (char *)NULL) |
983 | 0 | MagickFreeMemory(resource); |
984 | 0 | count=ReadByte(&info,&length); |
985 | 0 | if ((count > 0) && ((size_t) count <= length)) |
986 | 0 | { |
987 | 0 | resource=(char *) MagickAllocateMemory(char *, |
988 | 0 | (size_t) count+MaxTextExtent); |
989 | 0 | if (resource != (char *) NULL) |
990 | 0 | { |
991 | 0 | for (i=0; i < count; i++) |
992 | 0 | resource[i]=(char) ReadByte(&info,&length); |
993 | 0 | resource[count]='\0'; |
994 | 0 | } |
995 | 0 | } |
996 | 0 | if (!(count & 0x01)) |
997 | 0 | (void) ReadByte(&info,&length); |
998 | 0 | count=ReadMSBLong(&info,&length); |
999 | | /* |
1000 | | ReadMSBLong() can return negative values such as -1 or any |
1001 | | other negative value. Make sure that it is in range. |
1002 | | */ |
1003 | 0 | if ((count < 0) || ((size_t) count > length)) |
1004 | 0 | { |
1005 | 0 | length=0; /* Quit loop */ |
1006 | 0 | continue; |
1007 | 0 | } |
1008 | 0 | if ((*name != '\0') && (*name != '#')) |
1009 | 0 | { |
1010 | 0 | if ((resource == (char *) NULL) || |
1011 | 0 | (LocaleCompare(name,resource) != 0)) |
1012 | 0 | { |
1013 | | /* |
1014 | | No name match, scroll forward and try next resource. |
1015 | | */ |
1016 | 0 | info+=count; |
1017 | 0 | length-=count; |
1018 | 0 | continue; |
1019 | 0 | } |
1020 | 0 | } |
1021 | 0 | if ((*name == '#') && (sub_number != 1)) |
1022 | 0 | { |
1023 | | /* |
1024 | | No numbered match, scroll forward and try next resource. |
1025 | | */ |
1026 | 0 | sub_number--; |
1027 | 0 | info+=count; |
1028 | 0 | length-=count; |
1029 | 0 | continue; |
1030 | 0 | } |
1031 | | /* |
1032 | | We have the resource of interest. |
1033 | | */ |
1034 | 0 | attribute=(char *) MagickAllocateMemory(char *, |
1035 | 0 | (size_t) count+MaxTextExtent); |
1036 | 0 | if (attribute != (char *) NULL) |
1037 | 0 | { |
1038 | 0 | (void) memcpy(attribute,(char *) info,(size_t) count); |
1039 | 0 | attribute[count]='\0'; |
1040 | 0 | info+=count; |
1041 | 0 | length-=count; |
1042 | 0 | if ((id <= 1999) || (id >= 2999)) |
1043 | 0 | { |
1044 | 0 | (void) SetImageAttribute(image,key,(const char *) attribute); |
1045 | 0 | } |
1046 | 0 | else |
1047 | 0 | { |
1048 | 0 | char |
1049 | 0 | *path; |
1050 | 0 | if (LocaleCompare("SVG",format) == 0) |
1051 | 0 | path=TraceSVGClippingPath((unsigned char *) attribute,count, |
1052 | 0 | image->columns,image->rows); |
1053 | 0 | else |
1054 | 0 | path=TracePSClippingPath((unsigned char *) attribute,count, |
1055 | 0 | image->columns,image->rows); |
1056 | 0 | (void) SetImageAttribute(image,key,(const char *) path); |
1057 | 0 | MagickFreeMemory(path); |
1058 | 0 | } |
1059 | 0 | MagickFreeMemory(attribute); |
1060 | 0 | status=True; |
1061 | 0 | } |
1062 | 0 | } |
1063 | 0 | if (resource != (char *)NULL) |
1064 | 0 | MagickFreeMemory(resource); |
1065 | 0 | return(status); |
1066 | 0 | } |
1067 | | |
1068 | 9.40k | #define DE_STACK_SIZE 16 |
1069 | 1.59k | #define EXIF_DELIMITER "\n" |
1070 | | #define EXIF_NUM_FORMATS 12 |
1071 | 0 | #define EXIF_FMT_NOTYPE 0 /* Not yet assigned, unknown */ |
1072 | 6.96k | #define EXIF_FMT_BYTE 1 /* An 8-bit unsigned integer. */ |
1073 | 3.95k | #define EXIF_FMT_ASCII 2 /* An 8-bit byte containing one 7-bit ASCII code. NUL terminated */ |
1074 | 3.94k | #define EXIF_FMT_USHORT 3 /* A 16-bit (2-byte) unsigned integer */ |
1075 | 17.1k | #define EXIF_FMT_ULONG 4 /* A 32-bit (4-byte) unsigned intege */ |
1076 | 2.74k | #define EXIF_FMT_URATIONAL 5 /* Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator. */ |
1077 | 2.75k | #define EXIF_FMT_SBYTE 6 |
1078 | 5.16k | #define EXIF_FMT_UNDEFINED 7 /* An 8-bit byte that may take any value depending on the field definition. */ |
1079 | 1.22k | #define EXIF_FMT_SSHORT 8 |
1080 | 7.24k | #define EXIF_FMT_SLONG 9 /* A 32-bit (4-byte) signed integer (2's complement notation). */ |
1081 | 1.72k | #define EXIF_FMT_SRATIONAL 10 /* Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. */ |
1082 | 1.79k | #define EXIF_FMT_SINGLE 11 |
1083 | 12.6k | #define EXIF_FMT_DOUBLE 12 |
1084 | 77.0k | #define EXIF_TAG_START 0x0100 |
1085 | 67.7k | #define EXIF_TAG_STOP 0xFFFF |
1086 | 47.9k | #define TAG_EXIF_OFFSET 0x8769 |
1087 | 47.0k | #define TAG_INTEROP_OFFSET 0xa005 |
1088 | | #define GPS_TAG_START 0x0000 |
1089 | 20.0k | #define GPS_TAG_STOP 0x001F |
1090 | 152k | #define GPS_OFFSET 0x8825 |
1091 | 0 | #define GPS_LATITUDE 0x0002 |
1092 | 0 | #define GPS_LONGITUDE 0x0004 |
1093 | 0 | #define GPS_TIMESTAMP 0x0007 |
1094 | 34.0k | #define MAX_TAGS_PER_IFD 1024 /* Maximum tags allowed per IFD */ |
1095 | 179 | #define EXIF_ORIENTATION 0x0112 |
1096 | 39.3k | #define AnyCount -1 |
1097 | | |
1098 | | /* Bit-field flags for supporting OR logic to represent tags which accept multiple types */ |
1099 | 0 | #define F_NOTYPE 0x0000 /* Not yet assigned, unknown */ |
1100 | 6.96k | #define F_BYTE 0x0002 /* An 8-bit unsigned integer. */ |
1101 | 3.95k | #define F_ASCII 0x0004 /* An 8-bit byte containing one 7-bit ASCII code. NUL terminated (count includes NUL). */ |
1102 | 3.39k | #define F_SHORT 0x0008 /* A 16-bit (2-byte) unsigned integer */ |
1103 | 13.5k | #define F_LONG 0x0010 /* A 32-bit (4-byte) unsigned intege */ |
1104 | 2.74k | #define F_RATIONAL 0x0020 /* Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator. */ |
1105 | 2.75k | #define F_SBYTE 0x0040 /* An 8-bit signed integer. */ |
1106 | 5.16k | #define F_UNDEFINED 0x0080 /* An 8-bit byte that may take any value depending on the field definition. */ |
1107 | 1.22k | #define F_SSHORT 0x0100 /* A 16-bit (2-byte) signed integer */ |
1108 | 5.34k | #define F_SLONG 0x0200 /* A 32-bit (4-byte) signed integer (2's complement notation). */ |
1109 | 1.72k | #define F_SRATIONAL 0x0400 /* Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. */ |
1110 | 1.79k | #define F_SINGLE 0x0800 /* Single IEEE floating point value (32 bit) */ |
1111 | 12.6k | #define F_DOUBLE 0x1000 /* Double IEEE floating point value (64 bit) */ |
1112 | | |
1113 | | typedef struct _IFDTagTableType |
1114 | | { |
1115 | | /* TIFF IFD tag number */ |
1116 | | unsigned short |
1117 | | tag; |
1118 | | |
1119 | | /* Accepted tag formats declared by a standard */ |
1120 | | short |
1121 | | format; |
1122 | | |
1123 | | /* Other formats for the tag discovered to be in use (likely due to a bug) */ |
1124 | | short |
1125 | | format_alt; |
1126 | | |
1127 | | /* Expected component count. -1 == Any, 0 == not set yet */ |
1128 | | short |
1129 | | count; |
1130 | | |
1131 | | /* Tag description */ |
1132 | | const char |
1133 | | description[36]; |
1134 | | |
1135 | | /* |
1136 | | Add tag format and component numbers |
1137 | | https://www.media.mit.edu/pia/Research/deepview/exif.html |
1138 | | */ |
1139 | | } IFDTagTableType; |
1140 | | |
1141 | | #define TAG_INFO(tag,format,format_alt,count,description) {tag,format,format_alt,count,description} |
1142 | | |
1143 | | /* https://en.wikipedia.org/wiki/Exif */ |
1144 | | /* Microsoft Windows GDI+ tags are documented at https://learn.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-constant-property-tags-in-numerical-order */ |
1145 | | |
1146 | | static IFDTagTableType |
1147 | | tag_table[] = |
1148 | | { |
1149 | | /* |
1150 | | GPS Info IFD tags. These should be in a separate numeric space |
1151 | | from EXIF/TIFF tags! |
1152 | | */ |
1153 | | TAG_INFO(0x0000, F_BYTE, F_NOTYPE, 4, "GPSVersionID"), /* GPS IFD */ |
1154 | | TAG_INFO(0x0001, F_ASCII, F_NOTYPE, 2, "GPSLatitudeRef"), /* GPS IFD */ |
1155 | | TAG_INFO(0x0002, F_RATIONAL, F_NOTYPE, 3, "GPSLatitude"), /* GPS IFD */ |
1156 | | TAG_INFO(0x0003, F_ASCII, F_NOTYPE, 2, "GPSLongitudeRef"), /* GPS IFD */ |
1157 | | TAG_INFO(0x0004, F_RATIONAL, F_NOTYPE, 3, "GPSLongitude"), /* GPS IFD */ |
1158 | | TAG_INFO(0x0005, F_BYTE, F_NOTYPE, 1, "GPSAltitudeRef"), /* GPS IFD */ |
1159 | | TAG_INFO(0x0006, F_RATIONAL, F_NOTYPE, 1, "GPSAltitude"), /* GPS IFD */ |
1160 | | TAG_INFO(0x0007, F_RATIONAL, F_NOTYPE, 3, "GPSTimeStamp"), /* GPS IFD */ |
1161 | | TAG_INFO(0x0008, F_ASCII, F_NOTYPE, AnyCount, "GPSSatellites"), /* GPS IFD */ |
1162 | | TAG_INFO(0x0009, F_ASCII, F_NOTYPE, 2, "GPSStatus"), /* GPS IFD */ |
1163 | | TAG_INFO(0x000A, F_ASCII, F_NOTYPE, 2, "GPSMeasureMode"), /* GPS IFD */ |
1164 | | TAG_INFO(0x000B, F_RATIONAL, F_NOTYPE, 1, "GPSDOP"), /* GPS IFD */ |
1165 | | TAG_INFO(0x000C, F_ASCII, F_NOTYPE, 2, "GPSSpeedRef"), /* GPS IFD */ |
1166 | | TAG_INFO(0x000D, F_RATIONAL, F_NOTYPE, 1, "GPSSpeed"), /* GPS IFD */ |
1167 | | TAG_INFO(0x000E, F_ASCII, F_NOTYPE, 2, "GPSTrackRef"), /* GPS IFD */ |
1168 | | TAG_INFO(0x000F, F_RATIONAL, F_NOTYPE, 1, "GPSTrack"), /* GPS IFD */ |
1169 | | TAG_INFO(0x0010, F_ASCII, F_NOTYPE, 2, "GPSImgDirectionRef"), /* GPS IFD */ |
1170 | | TAG_INFO(0x0011, F_RATIONAL, F_NOTYPE, 1, "GPSImgDirection"), /* GPS IFD */ |
1171 | | TAG_INFO(0x0012, F_ASCII, F_NOTYPE, 0, "GPSMapDatum"), /* GPS IFD */ |
1172 | | TAG_INFO(0x0013, F_ASCII, F_NOTYPE, 2, "GPSDestLatitudeRef"), /* GPS IFD */ |
1173 | | TAG_INFO(0x0014, F_RATIONAL, F_NOTYPE, 3, "GPSDestLatitude"), /* GPS IFD */ |
1174 | | TAG_INFO(0x0015, F_ASCII, F_NOTYPE, 2, "GPSDestLongitudeRef"), /* GPS IFD */ |
1175 | | TAG_INFO(0x0016, F_RATIONAL, F_NOTYPE, 3, "GPSDestLongitude"), /* GPS IFD */ |
1176 | | TAG_INFO(0x0017, F_ASCII, F_NOTYPE, 2, "GPSDestBearingRef"), /* GPS IFD */ |
1177 | | TAG_INFO(0x0018, F_RATIONAL, F_NOTYPE, 1, "GPSDestBearing"), /* GPS IFD */ |
1178 | | TAG_INFO(0x0019, F_ASCII, F_NOTYPE, 2, "GPSDestDistanceRef"), /* GPS IFD */ |
1179 | | TAG_INFO(0x001A, F_RATIONAL, F_NOTYPE, 1, "GPSDestDistance"), /* GPS IFD */ |
1180 | | TAG_INFO(0x001B, F_UNDEFINED, F_NOTYPE, AnyCount, "GPSProcessingMethod"), /* GPS IFD */ |
1181 | | TAG_INFO(0x001C, F_UNDEFINED, F_NOTYPE, AnyCount, "GPSAreaInformation"), /* GPS IFD */ |
1182 | | TAG_INFO(0x001D, F_ASCII, F_NOTYPE, 11, "GPSDateStamp"), /* GPS IFD */ |
1183 | | TAG_INFO(0x001E, F_SHORT, F_NOTYPE, 1, "GPSDifferential"), /* GPS IFD */ |
1184 | | TAG_INFO(0x001F, F_RATIONAL, F_NOTYPE, 1, "GPSHPositioningError"), /* GPS IFD */ |
1185 | | |
1186 | | TAG_INFO(0x0100, F_SHORT | F_LONG, F_SLONG, 1, "ImageWidth"), |
1187 | | TAG_INFO(0x0101, F_SHORT | F_LONG, F_SLONG, 1, "ImageLength"), |
1188 | | TAG_INFO(0x0102, F_SHORT, F_NOTYPE, AnyCount /* Could be 1, 3, 4! */, "BitsPerSample"), |
1189 | | TAG_INFO(0x0103, F_SHORT, F_NOTYPE, 1, "Compression"), |
1190 | | TAG_INFO(0x0106, F_SHORT, F_NOTYPE, 1, "PhotometricInterpretation"), |
1191 | | TAG_INFO(0x010A, F_SHORT, F_NOTYPE, 1, "FillOrder"), |
1192 | | TAG_INFO(0x010D, F_ASCII, F_NOTYPE, AnyCount, "DocumentName"), |
1193 | | TAG_INFO(0x010E, F_ASCII, F_NOTYPE, AnyCount, "ImageDescription"), |
1194 | | TAG_INFO(0x010F, F_ASCII, F_NOTYPE, AnyCount, "Make"), |
1195 | | TAG_INFO(0x0110, F_ASCII, F_NOTYPE, AnyCount, "Model"), |
1196 | | TAG_INFO(0x0111, F_SHORT | F_LONG, F_NOTYPE, 0, "StripOffsets"), |
1197 | | TAG_INFO(0x0112, F_SHORT, F_SLONG, 1, "Orientation"), |
1198 | | TAG_INFO(0x0115, F_SHORT, F_NOTYPE, 1, "SamplesPerPixel"), |
1199 | | TAG_INFO(0x0116, F_SHORT | F_LONG, F_NOTYPE, 1, "RowsPerStrip"), |
1200 | | TAG_INFO(0x0117, F_LONG | F_SHORT, F_NOTYPE, 0, "StripByteCounts"), |
1201 | | TAG_INFO(0x0118, F_SHORT, F_NOTYPE, 0, "MinSampleValue"), |
1202 | | TAG_INFO(0x0119, F_SHORT, F_NOTYPE, 0, "MaxSampleValue"), |
1203 | | TAG_INFO(0x011A, F_RATIONAL, F_NOTYPE, 1, "XResolution"), |
1204 | | TAG_INFO(0x011B, F_RATIONAL, F_NOTYPE, 1, "YResolution"), |
1205 | | TAG_INFO(0x011C, F_SHORT, F_NOTYPE, 1, "PlanarConfiguration"), |
1206 | | TAG_INFO(0x011D, F_ASCII, F_NOTYPE, 0, "PageName"), |
1207 | | TAG_INFO(0x011E, F_RATIONAL, F_NOTYPE, 1, "XPosition"), |
1208 | | TAG_INFO(0x011F, F_RATIONAL, F_NOTYPE, 1, "YPosition"), |
1209 | | |
1210 | | TAG_INFO(0x0120, F_LONG, F_NOTYPE, AnyCount, "FreeOffsets"), /* Defunct feature */ |
1211 | | TAG_INFO(0x0121, F_LONG, F_NOTYPE, AnyCount, "FreeByteCounts"), /* Defunct feature */ |
1212 | | TAG_INFO(0x0122, F_SHORT, F_NOTYPE, 1, "GrayResponseUnit"), |
1213 | | TAG_INFO(0x0123, F_SHORT, F_NOTYPE, 0 /* 2**BitsPerSample */, "GrayResponseCurve"), |
1214 | | TAG_INFO(0x0124, F_LONG, F_NOTYPE, 1, "T4Options"), |
1215 | | TAG_INFO(0x0125, F_LONG, F_NOTYPE, 1, "T6Options"), |
1216 | | TAG_INFO(0x0128, F_SHORT, F_NOTYPE, 1, "ResolutionUnit"), |
1217 | | TAG_INFO(0x012D, F_SHORT, F_NOTYPE, 3*256 /* (1 or 3) * (1 << BitsPerSample) */, "TransferFunction"), |
1218 | | TAG_INFO(0x0131, F_ASCII, F_NOTYPE, AnyCount, "Software"), |
1219 | | TAG_INFO(0x0132, F_ASCII, F_NOTYPE, 21 /* 20+NUL? TIFF spec says 20 */, "DateTime"), |
1220 | | TAG_INFO(0x013B, F_ASCII, F_NOTYPE, AnyCount, "Artist"), |
1221 | | TAG_INFO(0x013C, F_ASCII, F_NOTYPE, AnyCount, "HostComputer"), |
1222 | | TAG_INFO(0x013D, F_SHORT, F_NOTYPE, 0, "Predictor"), |
1223 | | TAG_INFO(0x013E, F_RATIONAL, F_NOTYPE, 2, "WhitePoint"), |
1224 | | TAG_INFO(0x013F, F_RATIONAL, F_NOTYPE, 6, "PrimaryChromaticities"), |
1225 | | TAG_INFO(0x0140, F_SHORT, F_NOTYPE, 0 /* 3 * (2**BitsPerSample) */, "ColorMap"), |
1226 | | TAG_INFO(0x0141, F_SHORT, F_NOTYPE, 2, "HalfToneHints"), |
1227 | | TAG_INFO(0x0142, F_SHORT | F_LONG, F_NOTYPE, AnyCount /* TBD */, "TileWidth"), |
1228 | | TAG_INFO(0x0143, F_SHORT | F_LONG, F_NOTYPE, AnyCount /* TBD */, "TileLength"), |
1229 | | TAG_INFO(0x0144, F_LONG, F_NOTYPE, AnyCount /* TBD */, "TileOffsets"), |
1230 | | TAG_INFO(0x0145, F_SHORT | F_LONG, F_NOTYPE, AnyCount /* TBD */, "TileByteCounts"), |
1231 | | TAG_INFO(0x014A, F_LONG, F_NOTYPE, 1, "SubIFD"), |
1232 | | TAG_INFO(0x014C, F_SHORT, F_NOTYPE, 1, "InkSet"), |
1233 | | TAG_INFO(0x014D, F_ASCII, F_NOTYPE, AnyCount, "InkNames"), |
1234 | | TAG_INFO(0x014E, F_SHORT, F_NOTYPE, 1, "NumberOfInks"), |
1235 | | TAG_INFO(0x0150, F_BYTE | F_SHORT, F_NOTYPE, 0 /* 2, or 2*SamplesPerPixel */, "DotRange"), |
1236 | | TAG_INFO(0x0151, F_ASCII, F_NOTYPE, AnyCount, "TargetPrinter"), |
1237 | | TAG_INFO(0x0152, F_SHORT, F_NOTYPE, 0, "ExtraSample"), |
1238 | | TAG_INFO(0x0153, F_SHORT, F_NOTYPE, 0, "SampleFormat"), |
1239 | | TAG_INFO(0x0154, F_BYTE | F_SHORT | F_LONG | F_SRATIONAL | F_DOUBLE, F_NOTYPE, 0, "SMinSampleValue"), /* BYTE or SHORT or LONG or RATIONAL or DOUBLE */ |
1240 | | TAG_INFO(0x0155, F_BYTE | F_SHORT | F_LONG | F_SRATIONAL | F_DOUBLE, F_NOTYPE, 0, "SMaxSampleValue"), /* BYTE or SHORT or LONG or RATIONAL or DOUBLE */ |
1241 | | TAG_INFO(0x0156, F_SHORT, F_NOTYPE, 6, "TransferRange"), |
1242 | | TAG_INFO(0x0157, F_BYTE, F_NOTYPE, AnyCount, "ClipPath"), |
1243 | | TAG_INFO(0x0158, F_LONG, F_NOTYPE, 1, "XClipPathUnits"), |
1244 | | TAG_INFO(0x0159, F_LONG, F_NOTYPE, 1, "YClipPathUnits"), |
1245 | | TAG_INFO(0x015A, F_SHORT, F_NOTYPE, 1, "Indexed"), |
1246 | | TAG_INFO(0x015B, F_UNDEFINED, F_NOTYPE, 0, "JPEGTables"), |
1247 | | TAG_INFO(0x015F, F_SHORT, F_NOTYPE, 0, "OPIProxy"), |
1248 | | TAG_INFO(0x0200, F_SHORT, F_NOTYPE, 1, "JPEGProc"), |
1249 | | TAG_INFO(0x0201, F_LONG, F_NOTYPE, 1, "JPEGInterchangeFormat"), |
1250 | | TAG_INFO(0x0202, F_LONG, F_NOTYPE, 1, "JPEGInterchangeFormatLength"), |
1251 | | TAG_INFO(0x0203, F_SHORT, F_NOTYPE, 1, "JPEGRestartInterval"), |
1252 | | TAG_INFO(0x0205, F_SHORT, F_NOTYPE, 0, "JPEGLosslessPredictors"), |
1253 | | TAG_INFO(0x0206, F_SHORT, F_NOTYPE, 0, "JPEGPointTransforms"), |
1254 | | TAG_INFO(0x0207, F_LONG, F_NOTYPE, 0, "JPEGQTables"), |
1255 | | TAG_INFO(0x0208, F_LONG, F_NOTYPE, 0, "JPEGDCTables"), |
1256 | | TAG_INFO(0x0209, F_LONG, F_NOTYPE, 0, "JPEGACTables"), |
1257 | | TAG_INFO(0x0211, F_RATIONAL, F_NOTYPE, 3, "YCbCrCoefficients"), |
1258 | | TAG_INFO(0x0212, F_SHORT, F_NOTYPE, 2, "YCbCrSubSampling"), |
1259 | | TAG_INFO(0x0213, F_SHORT, F_NOTYPE, 1, "YCbCrPositioning"), |
1260 | | TAG_INFO(0x0214, F_RATIONAL, F_NOTYPE, 0 /* 2*SamplesPerPixel */, "ReferenceBlackWhite"), |
1261 | | TAG_INFO(0x02BC, F_NOTYPE, F_NOTYPE, 0, "ExtensibleMetadataPlatform"), /* XMP */ |
1262 | | TAG_INFO(0x0301, F_NOTYPE, F_NOTYPE, 0, "Gamma"), |
1263 | | TAG_INFO(0x0302, F_NOTYPE, F_NOTYPE, 0, "ICCProfileDescriptor"), |
1264 | | TAG_INFO(0x0303, F_NOTYPE, F_NOTYPE, 0, "SRGBRenderingIntent"), |
1265 | | TAG_INFO(0x0320, F_NOTYPE, F_NOTYPE, 0, "ImageTitle"), |
1266 | | |
1267 | | /* |
1268 | | Interoperability IFD tags. These should be in a separate |
1269 | | numeric space from EXIF/TIFF tags! |
1270 | | */ |
1271 | | TAG_INFO(0x1000, F_NOTYPE, F_NOTYPE, 0, "RelatedImageFileFormat"), /* Exif Interoperability IFD */ |
1272 | | TAG_INFO(0x1001, F_SHORT | F_LONG, F_NOTYPE, 0, "RelatedImageWidth"), /* Exif Interoperability IFD */ |
1273 | | TAG_INFO(0x1002, F_SHORT | F_LONG, F_NOTYPE, 0, "RelatedImageLength"), /* Exif Interoperability IFD */ |
1274 | | |
1275 | | TAG_INFO(0x5001, F_SHORT, F_NOTYPE, 1, "ResolutionXUnit"), /* Microsoft Windows GDI+ */ |
1276 | | TAG_INFO(0x5002, F_SHORT, F_NOTYPE, 1, "ResolutionYUnit"), /* Microsoft Windows GDI+ */ |
1277 | | TAG_INFO(0x5003, F_SHORT, F_NOTYPE, 1, "ResolutionXLengthUnit"), /* Microsoft Windows GDI+ */ |
1278 | | TAG_INFO(0x5004, F_SHORT, F_NOTYPE, 1, "ResolutionYLengthUnit"), /* Microsoft Windows GDI+ */ |
1279 | | TAG_INFO(0x5005, F_ASCII, F_NOTYPE, AnyCount /* Number of flags */, "PrintFlags"), /* Microsoft Windows GDI+ */ |
1280 | | TAG_INFO(0x5006, F_SHORT, F_NOTYPE, 1, "PrintFlagsVersion"), /* Microsoft Windows GDI+ */ |
1281 | | TAG_INFO(0x5007, F_BYTE, F_NOTYPE, 1, "PrintFlagsCrop"), /* Microsoft Windows GDI+ */ |
1282 | | TAG_INFO(0x5008, F_LONG, F_NOTYPE, 1, "PrintFlagsBleedWidth"), /* Microsoft Windows GDI+ */ |
1283 | | TAG_INFO(0x5009, F_SHORT, F_NOTYPE, 1, "PrintFlagsBleedWidthScale"), /* Microsoft Windows GDI+ */ |
1284 | | TAG_INFO(0x500A, F_RATIONAL, F_NOTYPE, 1, "HalftoneLPI"), /* Microsoft Windows GDI+ */ |
1285 | | TAG_INFO(0x500B, F_SHORT, F_NOTYPE, 1, "HalftoneLPIUnit"), /* Microsoft Windows GDI+ */ |
1286 | | TAG_INFO(0x500C, F_RATIONAL, F_NOTYPE, 1, "HalftoneDegree"), /* Microsoft Windows GDI+ */ |
1287 | | TAG_INFO(0x500D, F_SHORT, F_NOTYPE, 1, "HalftoneShape"), /* Microsoft Windows GDI+ */ |
1288 | | TAG_INFO(0x500E, F_LONG, F_NOTYPE, 1, "HalftoneMisc"), /* Microsoft Windows GDI+ */ |
1289 | | TAG_INFO(0x500F, F_BYTE, F_NOTYPE, 1, "HalftoneScreen"), /* Microsoft Windows GDI+ */ |
1290 | | TAG_INFO(0x5010, F_SHORT, F_NOTYPE, AnyCount, "JPEGQuality"), /* Microsoft Windows GDI+ */ |
1291 | | TAG_INFO(0x5011, F_UNDEFINED, F_NOTYPE, AnyCount, "GridSize"), /* Microsoft Windows GDI+ */ |
1292 | | TAG_INFO(0x5012, F_LONG, F_NOTYPE, 1, "ThumbnailFormat"), /* Microsoft Windows GDI+ */ |
1293 | | TAG_INFO(0x5013, F_LONG, F_NOTYPE, 1, "ThumbnailWidth"), /* Microsoft Windows GDI+ */ |
1294 | | TAG_INFO(0x5014, F_LONG, F_NOTYPE, 1, "ThumbnailHeight"), /* Microsoft Windows GDI+ */ |
1295 | | TAG_INFO(0x5015, F_SHORT, F_NOTYPE, 1, "ThumbnailColorDepth"), /* Microsoft Windows GDI+ */ |
1296 | | TAG_INFO(0x5016, F_SHORT, F_NOTYPE, 1, "ThumbnailPlanes"), /* Microsoft Windows GDI+ */ |
1297 | | TAG_INFO(0x5017, F_LONG, F_NOTYPE, 1, "ThumbnailRawBytes"), /* Microsoft Windows GDI+ */ |
1298 | | TAG_INFO(0x5018, F_LONG, F_NOTYPE, 1, "ThumbnailSize"), /* Microsoft Windows GDI+ */ |
1299 | | TAG_INFO(0x5019, F_LONG, F_NOTYPE, 1, "ThumbnailCompressedSize"), /* Microsoft Windows GDI+ */ |
1300 | | TAG_INFO(0x501A, F_UNDEFINED, F_NOTYPE, 0, "ColorTransferFunction"), /* Microsoft Windows GDI+ */ |
1301 | | TAG_INFO(0x501B, F_BYTE, F_NOTYPE, 0, "ThumbnailData"), /* Microsoft Windows GDI+ */ |
1302 | | TAG_INFO(0x5020, F_LONG | F_SHORT, F_NOTYPE, 1, "ThumbnailImageWidth"), /* Microsoft Windows GDI+ */ |
1303 | | TAG_INFO(0x5021, F_LONG | F_SHORT, F_NOTYPE, 1, "ThumbnailImageHeight"), /* Microsoft Windows GDI+ */ |
1304 | | TAG_INFO(0x5022, F_SHORT, F_NOTYPE, 1, "ThumbnailBitsPerSample"), /* Microsoft Windows GDI+ */ |
1305 | | TAG_INFO(0x5023, F_SHORT, F_NOTYPE, 1, "ThumbnailCompression"), /* Microsoft Windows GDI+ */ |
1306 | | TAG_INFO(0x5024, F_SHORT, F_NOTYPE, 1, "ThumbnailPhotometricInterp"), /* Microsoft Windows GDI+ */ |
1307 | | TAG_INFO(0x5025, F_ASCII, F_NOTYPE, AnyCount, "ThumbnailImageDescription"), /* Microsoft Windows GDI+ */ |
1308 | | TAG_INFO(0x5026, F_ASCII, F_NOTYPE, AnyCount, "ThumbnailEquipMake"), /* Microsoft Windows GDI+ */ |
1309 | | TAG_INFO(0x5027, F_ASCII, F_NOTYPE, AnyCount, "ThumbnailEquipModel"), /* Microsoft Windows GDI+ */ |
1310 | | TAG_INFO(0x5028, F_LONG | F_SHORT, F_NOTYPE, 0, "ThumbnailStripOffsets"), /* Microsoft Windows GDI+ */ |
1311 | | TAG_INFO(0x5029, F_SHORT, F_NOTYPE, 1, "ThumbnailOrientation"), /* Microsoft Windows GDI+ */ |
1312 | | TAG_INFO(0x502A, F_SHORT, F_NOTYPE, 1, "ThumbnailSamplesPerPixel"), /* Microsoft Windows GDI+ */ |
1313 | | TAG_INFO(0x502B, F_LONG | F_SHORT, F_NOTYPE, 1, "ThumbnailRowsPerStrip"), /* Microsoft Windows GDI+ */ |
1314 | | TAG_INFO(0x502C, F_LONG | F_SHORT, F_NOTYPE, 0, "ThumbnailStripBytesCount"), /* Microsoft Windows GDI+ */ |
1315 | | TAG_INFO(0x502D, F_SHORT /* type provided by ThumbnailResolutionUnit */, F_NOTYPE, 0, "ThumbnailResolutionX"), /* Microsoft Windows GDI+ */ |
1316 | | TAG_INFO(0x502E, F_SHORT /* type provided by ThumbnailResolutionUnit */, F_NOTYPE, 0, "ThumbnailResolutionY"), /* Microsoft Windows GDI+ */ |
1317 | | TAG_INFO(0x502F, F_SHORT, F_NOTYPE, 1, "ThumbnailPlanarConfig"), /* Microsoft Windows GDI+ */ |
1318 | | TAG_INFO(0x5030, F_SHORT, F_NOTYPE, 1, "ThumbnailResolutionUnit"), /* Microsoft Windows GDI+ */ |
1319 | | TAG_INFO(0x5031, F_SHORT, F_NOTYPE, 0, "ThumbnailTransferFunction"), /* Microsoft Windows GDI+ */ |
1320 | | TAG_INFO(0x5032, F_ASCII, F_NOTYPE, AnyCount, "ThumbnailSoftwareUsed"), /* Microsoft Windows GDI+ */ |
1321 | | TAG_INFO(0x5033, F_ASCII, F_NOTYPE, AnyCount, "ThumbnailDateTime"), /* Microsoft Windows GDI+ */ |
1322 | | TAG_INFO(0x5034, F_ASCII, F_NOTYPE, AnyCount, "ThumbnailArtist"), /* Microsoft Windows GDI+ */ |
1323 | | TAG_INFO(0x5035, F_RATIONAL, F_NOTYPE, 2, "ThumbnailWhitePoint"), /* Microsoft Windows GDI+ */ |
1324 | | TAG_INFO(0x5036, F_RATIONAL, F_NOTYPE, 6, "ThumbnailPrimaryChromaticities"), /* Microsoft Windows GDI+ */ |
1325 | | TAG_INFO(0x5037, F_RATIONAL, F_NOTYPE, 3, "ThumbnailYCbCrCoefficients"), /* Microsoft Windows GDI+ */ |
1326 | | TAG_INFO(0x5038, F_SHORT, F_NOTYPE, 2, "ThumbnailYCbCrSubsampling"), /* Microsoft Windows GDI+ */ |
1327 | | TAG_INFO(0x5039, F_SHORT, F_NOTYPE, 1, "ThumbnailYCbCrPositioning"), /* Microsoft Windows GDI+ */ |
1328 | | TAG_INFO(0x503A, F_RATIONAL, F_NOTYPE, 6, "ThumbnailRefBlackWhite"), /* Microsoft Windows GDI+ */ |
1329 | | TAG_INFO(0x503B, F_ASCII, F_NOTYPE, AnyCount, "ThumbnailCopyRight"), /* Microsoft Windows GDI+ */ |
1330 | | TAG_INFO(0x5090, F_SHORT, F_NOTYPE, 64, "LuminanceTable"), /* Microsoft Windows GDI+ */ |
1331 | | TAG_INFO(0x5091, F_SHORT, F_NOTYPE, 64, "ChrominanceTable"), /* Microsoft Windows GDI+ */ |
1332 | | TAG_INFO(0x5100, F_LONG, F_NOTYPE, 0, "FrameDelay"), /* Microsoft Windows GDI+ */ |
1333 | | TAG_INFO(0x5101, F_SHORT, F_NOTYPE, 1, "LoopCount"), /* Microsoft Windows GDI+ */ |
1334 | | TAG_INFO(0x5102, F_BYTE, F_NOTYPE, 0, "GlobalPalette"), /* Microsoft Windows GDI+ */ |
1335 | | TAG_INFO(0x5103, F_BYTE, F_NOTYPE, 1, "IndexBackground"), /* Microsoft Windows GDI+ */ |
1336 | | TAG_INFO(0x5104, F_BYTE, F_NOTYPE, 1, "IndexTransparent"), /* Microsoft Windows GDI+ */ |
1337 | | TAG_INFO(0x5110, F_BYTE, F_NOTYPE, 0, "PixelUnit"), /* Microsoft Windows GDI+ */ |
1338 | | TAG_INFO(0x5111, F_LONG, F_NOTYPE, 1, "PixelPerUnitX"), /* Microsoft Windows GDI+ */ |
1339 | | TAG_INFO(0x5112, F_LONG, F_NOTYPE, 1, "PixelPerUnitY"), /* Microsoft Windows GDI+ */ |
1340 | | TAG_INFO(0x5113, F_BYTE, F_NOTYPE, AnyCount, "PaletteHistogram"), /* Microsoft Windows GDI+ */ |
1341 | | TAG_INFO(0x800D, F_ASCII, F_NOTYPE, AnyCount, "ImageID"), /* Adobe OPI */ |
1342 | | TAG_INFO(0x80E3, F_NOTYPE, F_NOTYPE, 0, "Matteing"), |
1343 | | TAG_INFO(0x80E4, F_NOTYPE, F_NOTYPE, 0, "DataType"), |
1344 | | TAG_INFO(0x80E5, F_NOTYPE, F_NOTYPE, 0, "ImageDepth"), |
1345 | | TAG_INFO(0x80E6, F_NOTYPE, F_NOTYPE, 0, "TileDepth"), |
1346 | | TAG_INFO(0x828D, F_SHORT, F_NOTYPE, 0, "CFARepeatPatternDim"), |
1347 | | TAG_INFO(0x828E, F_BYTE, F_NOTYPE, 0, "CFAPattern"), |
1348 | | TAG_INFO(0x828F, F_RATIONAL, F_NOTYPE, 0, "BatteryLevel"), |
1349 | | TAG_INFO(0x8298, F_ASCII, F_NOTYPE, AnyCount, "Copyright"), |
1350 | | TAG_INFO(0x829A, F_RATIONAL, F_NOTYPE, 1, "ExposureTime"), |
1351 | | TAG_INFO(0x829D, F_RATIONAL, F_NOTYPE, 1, "FNumber"), |
1352 | | TAG_INFO(0x83BB, F_LONG, F_NOTYPE, 0, "IPTC/NAA"), |
1353 | | TAG_INFO(0x84E3, F_NOTYPE, F_NOTYPE, 0, "IT8RasterPadding"), |
1354 | | TAG_INFO(0x84E5, F_NOTYPE, F_NOTYPE, 0, "IT8ColorTable"), |
1355 | | TAG_INFO(0x8649, F_NOTYPE, F_NOTYPE, 0, "ImageResourceInformation"), |
1356 | | TAG_INFO(0x8769, F_LONG, F_NOTYPE, 1, "ExifOffset"), /* Exif IFD Pointer */ |
1357 | | TAG_INFO(0x8773, F_BYTE, F_NOTYPE, AnyCount, "InterColorProfile"), /* ICCProfile */ |
1358 | | TAG_INFO(0x8822, F_SHORT, F_NOTYPE, 1, "ExposureProgram"), |
1359 | | TAG_INFO(0x8824, F_ASCII, F_NOTYPE, AnyCount, "SpectralSensitivity"), |
1360 | | TAG_INFO(0x8825, F_LONG /* TBD */, F_SLONG, 1, "GPSInfo"), /* Offset to GPS IFD */ |
1361 | | TAG_INFO(0x8827, F_SHORT, F_NOTYPE, AnyCount, "PhotographicSensitivity"), /* Was "ISOSpeedRatings" */ |
1362 | | TAG_INFO(0x8828, F_UNDEFINED, F_NOTYPE, AnyCount, "OECF"), |
1363 | | TAG_INFO(0x8830, F_SHORT, F_NOTYPE, 1, "SensitivityType"), |
1364 | | TAG_INFO(0x8831, F_LONG, F_NOTYPE, 1, "StandardOutputSensitivity"), |
1365 | | TAG_INFO(0x8832, F_LONG, F_NOTYPE, 1, "RecommendedExposureIndex"), |
1366 | | TAG_INFO(0x8833, F_LONG, F_NOTYPE, 1, "ISOSpeed"), |
1367 | | TAG_INFO(0x8834, F_LONG, F_NOTYPE, 1, "ISOSpeedLatitudeyyy"), |
1368 | | TAG_INFO(0x8835, F_LONG, F_NOTYPE, 1, "ISOSpeedLatitudezzz"), |
1369 | | TAG_INFO(0x9000, F_UNDEFINED, F_NOTYPE, 4, "ExifVersion"), /* e.g. "0300" without NUL */ |
1370 | | TAG_INFO(0x9003, F_ASCII, F_NOTYPE, 20, "DateTimeOriginal"), |
1371 | | TAG_INFO(0x9004, F_ASCII, F_NOTYPE, 20, "DateTimeDigitized"), |
1372 | | TAG_INFO(0x9010, F_ASCII, F_NOTYPE, 7, "OffsetTime"), |
1373 | | TAG_INFO(0x9011, F_ASCII, F_NOTYPE, 7, "OffsetTimeOriginal"), |
1374 | | TAG_INFO(0x9012, F_ASCII, F_NOTYPE, 7, "OffsetTimeDigitized"), |
1375 | | TAG_INFO(0x9101, F_UNDEFINED, F_NOTYPE, 4, "ComponentsConfiguration"), |
1376 | | TAG_INFO(0x9102, F_RATIONAL, F_NOTYPE, 1, "CompressedBitsPerPixel"), |
1377 | | TAG_INFO(0x9201, F_SRATIONAL /* TBD */, F_RATIONAL, 1, "ShutterSpeedValue"), |
1378 | | TAG_INFO(0x9202, F_RATIONAL, F_NOTYPE, 1, "ApertureValue"), |
1379 | | TAG_INFO(0x9203, F_SRATIONAL, F_NOTYPE, 1, "BrightnessValue"), |
1380 | | TAG_INFO(0x9204, F_SRATIONAL /* TBD */, F_RATIONAL, 1, "ExposureBiasValue"), |
1381 | | TAG_INFO(0x9205, F_RATIONAL, F_NOTYPE, 1, "MaxApertureValue"), |
1382 | | TAG_INFO(0x9206, F_RATIONAL, F_NOTYPE, 1, "SubjectDistance"), |
1383 | | TAG_INFO(0x9207, F_SHORT, F_NOTYPE, 1, "MeteringMode"), |
1384 | | TAG_INFO(0x9208, F_SHORT, F_NOTYPE, 1, "LightSource"), |
1385 | | TAG_INFO(0x9209, F_SHORT, F_NOTYPE, 1, "Flash"), |
1386 | | TAG_INFO(0x920A, F_RATIONAL, F_NOTYPE, 1, "FocalLength"), |
1387 | | TAG_INFO(0x9214, F_SHORT, F_NOTYPE, AnyCount /* 2 or 3 or 4 */, "SubjectArea"), |
1388 | | TAG_INFO(0x927C, F_UNDEFINED, F_NOTYPE, AnyCount, "MakerNote"), |
1389 | | TAG_INFO(0x9286, F_UNDEFINED, F_NOTYPE, AnyCount, "UserComment"), |
1390 | | TAG_INFO(0x9290, F_ASCII, F_NOTYPE, AnyCount, "SubSecTime"), |
1391 | | TAG_INFO(0x9291, F_ASCII, F_NOTYPE, AnyCount, "SubSecTimeOriginal"), |
1392 | | TAG_INFO(0x9292, F_ASCII, F_NOTYPE, AnyCount, "SubSecTimeDigitized"), |
1393 | | TAG_INFO(0x9400, F_SRATIONAL, F_NOTYPE, 1, "Temperature"), |
1394 | | TAG_INFO(0x9401, F_RATIONAL, F_NOTYPE, 1, "Humidity"), |
1395 | | TAG_INFO(0x9402, F_RATIONAL, F_NOTYPE, 1, "Pressure"), |
1396 | | TAG_INFO(0x9403, F_SRATIONAL, F_NOTYPE, 1, "WaterDepth"), |
1397 | | TAG_INFO(0x9404, F_RATIONAL, F_NOTYPE, 1, "Acceleration"), |
1398 | | TAG_INFO(0x9405, F_SRATIONAL, F_NOTYPE, 1, "CameraElevationAngle "), |
1399 | | TAG_INFO(0x9C9B, F_BYTE, F_NOTYPE, AnyCount, "WinXP-Title"), /* Win XP specific, UTF-16 Unicode */ |
1400 | | TAG_INFO(0x9C9C, F_BYTE, F_NOTYPE, AnyCount, "WinXP-Comments"), /* Win XP specific, UTF-16 Unicode */ |
1401 | | TAG_INFO(0x9C9D, F_BYTE, F_NOTYPE, AnyCount, "WinXP-Author"), /* Win XP specific, UTF-16 Unicode */ |
1402 | | TAG_INFO(0x9C9E, F_BYTE, F_NOTYPE, AnyCount, "WinXP-Keywords"), /* Win XP specific, UTF-16 Unicode */ |
1403 | | TAG_INFO(0x9C9F, F_BYTE, F_NOTYPE, AnyCount, "WinXP-Subject"), /* Win XP specific, UTF-16 Unicode */ |
1404 | | TAG_INFO(0xA000, F_UNDEFINED, F_NOTYPE, 4, "FlashPixVersion"), |
1405 | | TAG_INFO(0xA001, F_SHORT , F_NOTYPE, 1, "ColorSpace"), |
1406 | | TAG_INFO(0xA002, F_SHORT | F_LONG /* TBD */, F_SLONG, 1, "PixelXDimension"), /* Was "ExifImageWidth" */ |
1407 | | TAG_INFO(0xA003, F_SHORT | F_LONG /* TBD */, F_SLONG, 1, "PixelYDimension"), /* Was "ExifImageLength" */ |
1408 | | TAG_INFO(0xA004, F_ASCII, F_NOTYPE, 13, "RelatedSoundFile"), |
1409 | | TAG_INFO(0xA005, F_LONG, F_NOTYPE, 0, "InteroperabilityOffset"), /* Interoperability IFD Pointer */ |
1410 | | TAG_INFO(0xA20B, F_RATIONAL, F_NOTYPE, 1, "FlashEnergy"), |
1411 | | TAG_INFO(0xA20C, F_UNDEFINED, F_NOTYPE, AnyCount, "SpatialFrequencyResponse"), |
1412 | | TAG_INFO(0xA20D, F_NOTYPE, F_NOTYPE, 0, "Noise"), |
1413 | | TAG_INFO(0xA20E, F_RATIONAL, F_NOTYPE, 1, "FocalPlaneXResolution"), |
1414 | | TAG_INFO(0xA20F, F_RATIONAL, F_NOTYPE, 1, "FocalPlaneYResolution"), |
1415 | | TAG_INFO(0xA210, F_SHORT, F_NOTYPE, 1, "FocalPlaneResolutionUnit"), |
1416 | | TAG_INFO(0xA211, F_NOTYPE, F_NOTYPE, 0, "ImageNumber"), |
1417 | | TAG_INFO(0xA212, F_NOTYPE, F_NOTYPE, 0, "SecurityClassification"), |
1418 | | TAG_INFO(0xA213, F_NOTYPE, F_NOTYPE, 0, "ImageHistory"), |
1419 | | TAG_INFO(0xA214, F_SHORT, F_NOTYPE, 2, "SubjectLocation"), |
1420 | | TAG_INFO(0xA215, F_RATIONAL, F_NOTYPE, 1, "ExposureIndex"), |
1421 | | TAG_INFO(0xA216, F_NOTYPE, F_NOTYPE, 0, "TIFF_EPStandardID"), |
1422 | | TAG_INFO(0xA217, F_SHORT, F_BYTE, 1, "SensingMethod"), |
1423 | | TAG_INFO(0xA300, F_UNDEFINED, F_BYTE, AnyCount /* varies (0,1,2,3), TBD */, "FileSource"), |
1424 | | TAG_INFO(0xA301, F_UNDEFINED, F_BYTE, 1, "SceneType"), |
1425 | | TAG_INFO(0xA302, F_UNDEFINED, F_NOTYPE, AnyCount, "CFAPattern"), |
1426 | | TAG_INFO(0xA401, F_SHORT, F_NOTYPE, 1, "CustomRendered"), |
1427 | | TAG_INFO(0xA402, F_SHORT, F_NOTYPE, 1, "ExposureMode"), |
1428 | | TAG_INFO(0xA403, F_SHORT, F_NOTYPE, 1, "WhiteBalance"), |
1429 | | TAG_INFO(0xA404, F_RATIONAL, F_NOTYPE, 1, "DigitalZoomRatio"), |
1430 | | TAG_INFO(0xA405, F_SHORT, F_NOTYPE, 1, "FocalLengthIn35mmFilm"), |
1431 | | TAG_INFO(0xA406, F_SHORT, F_NOTYPE, 1, "SceneCaptureType"), |
1432 | | TAG_INFO(0xA407, F_SHORT, F_NOTYPE, 1, "GainControl"), |
1433 | | TAG_INFO(0xA408, F_SHORT, F_NOTYPE, 1, "Contrast"), |
1434 | | TAG_INFO(0xA409, F_SHORT, F_NOTYPE, 1, "Saturation"), |
1435 | | TAG_INFO(0xA40A, F_SHORT, F_NOTYPE, 1, "Sharpness"), |
1436 | | TAG_INFO(0xA40B, F_UNDEFINED, F_NOTYPE, AnyCount, "DeviceSettingDescription"), |
1437 | | TAG_INFO(0xA40C, F_SHORT, F_NOTYPE, 1, "SubjectDistanceRange"), |
1438 | | TAG_INFO(0xA420, F_ASCII, F_NOTYPE, 33, "ImageUniqueID"), |
1439 | | TAG_INFO(0xA430, F_ASCII, F_NOTYPE, AnyCount, "CameraOwnerName"), |
1440 | | TAG_INFO(0xA431, F_ASCII, F_NOTYPE, AnyCount, "BodySerialNumber"), |
1441 | | TAG_INFO(0xA432, F_RATIONAL, F_NOTYPE, 4, "LensSpecification"), |
1442 | | TAG_INFO(0xA433, F_ASCII, F_NOTYPE, AnyCount, "LensMake"), |
1443 | | TAG_INFO(0xA434, F_ASCII, F_NOTYPE, AnyCount, "LensModel"), |
1444 | | TAG_INFO(0xA435, F_ASCII, F_NOTYPE, AnyCount, "LensSerialNumber"), |
1445 | | TAG_INFO(0xA436, F_ASCII, F_NOTYPE, AnyCount, "ImageTitle"), |
1446 | | TAG_INFO(0xA437, F_ASCII, F_NOTYPE, AnyCount, "Photographer"), |
1447 | | TAG_INFO(0xA438, F_ASCII, F_NOTYPE, AnyCount, "ImageEditor"), |
1448 | | TAG_INFO(0xA439, F_ASCII, F_NOTYPE, AnyCount, "CameraFirmware"), |
1449 | | TAG_INFO(0xA43A, F_ASCII, F_NOTYPE, AnyCount, "RAWDevelopingSoftware"), |
1450 | | TAG_INFO(0xA43B, F_ASCII, F_NOTYPE, AnyCount, "ImageEditingSoftware"), |
1451 | | TAG_INFO(0xA43C, F_ASCII, F_NOTYPE, AnyCount, "MetadataEditingSoftware"), |
1452 | | TAG_INFO(0xA460, F_SHORT, F_NOTYPE, 1, "CompositeImage"), |
1453 | | TAG_INFO(0xA461, F_SHORT, F_NOTYPE, 2, "SourceImageNumberOfCompositeImage"), |
1454 | | TAG_INFO(0xA462, F_UNDEFINED, F_NOTYPE, AnyCount, "SourceExposureTimesOfCompositeImage"), |
1455 | | TAG_INFO(0xA500, F_RATIONAL, F_NOTYPE, 1, "Gamma") |
1456 | | }; |
1457 | | |
1458 | | /* Set to non-zero in order to use bsearch rather than linear search. */ |
1459 | | #if !defined(EXIF_USE_BSEARCH) |
1460 | | #define EXIF_USE_BSEARCH 1 |
1461 | | #endif /* !defined(EXIF_USE_BSEARCH) */ |
1462 | | |
1463 | | #if EXIF_USE_BSEARCH |
1464 | | /* |
1465 | | Compare two IFDTagTableType entries by tag |
1466 | | */ |
1467 | | static int IFDTagTableTypeCompare(const void *l, const void *r) |
1468 | 542k | { |
1469 | 542k | const IFDTagTableType * restrict lp = l; |
1470 | 542k | const IFDTagTableType * restrict rp = r; |
1471 | 542k | int sense; |
1472 | 542k | unsigned int lptag = lp->tag; |
1473 | 542k | unsigned int rptag = rp->tag; |
1474 | | |
1475 | 542k | if (lptag > rptag) |
1476 | 307k | sense = 1; |
1477 | 235k | else if (lptag < rptag) |
1478 | 195k | sense = -1; |
1479 | 39.3k | else |
1480 | 39.3k | sense = 0; |
1481 | | |
1482 | 542k | return sense; |
1483 | 542k | } |
1484 | | #endif /* if EXIF_USE_BSEARCH */ |
1485 | | |
1486 | | /* |
1487 | | Find EXIF table entry matching tag |
1488 | | */ |
1489 | | static const IFDTagTableType * |
1490 | | FindEXIFTableEntryByTag(const unsigned int t, |
1491 | | const IFDTagTableType *tag_table, |
1492 | | const size_t tag_table_entries) MAGICK_FUNC_PURE; |
1493 | | |
1494 | | static const IFDTagTableType * |
1495 | | FindEXIFTableEntryByTag(const unsigned int t, |
1496 | | const IFDTagTableType *tag_table, |
1497 | | const size_t tag_table_entries) |
1498 | 69.7k | { |
1499 | 69.7k | const IFDTagTableType *key_p = (IFDTagTableType *) NULL; |
1500 | | |
1501 | 69.7k | #if EXIF_USE_BSEARCH |
1502 | 69.7k | IFDTagTableType key; |
1503 | | |
1504 | 69.7k | key.tag = t; |
1505 | | |
1506 | 69.7k | key_p=(void *) bsearch((const void *) &key,(void *) tag_table,tag_table_entries, |
1507 | 69.7k | sizeof(tag_table[0]),IFDTagTableTypeCompare); |
1508 | | #else |
1509 | | register unsigned int |
1510 | | ttt; |
1511 | | |
1512 | | size_t |
1513 | | i; |
1514 | | |
1515 | | for (i=0; i < tag_table_entries; i++) |
1516 | | { |
1517 | | ttt = tag_table[i].tag; |
1518 | | if (ttt == t) |
1519 | | { |
1520 | | key_p = &tag_table[i]; |
1521 | | break; |
1522 | | } |
1523 | | else if (ttt > t) |
1524 | | { |
1525 | | break; |
1526 | | } |
1527 | | } |
1528 | | #endif /* if EXIF_USE_BSEARCH */ |
1529 | | |
1530 | 69.7k | return key_p; |
1531 | 69.7k | } |
1532 | | |
1533 | | /* |
1534 | | Convert an EXIF IFD tag ID to a tag description |
1535 | | |
1536 | | An EXIF IFD tag value to be translated, and a buffer of at least |
1537 | | MaxTextExtent length are passed as arguments. For convenience, the |
1538 | | converted string is returned. |
1539 | | */ |
1540 | | |
1541 | | static const char * |
1542 | | EXIFIfdTagToDescription(unsigned int t, char *tag_description) |
1543 | 0 | { |
1544 | 0 | const IFDTagTableType *key_p; |
1545 | |
|
1546 | 0 | key_p = FindEXIFTableEntryByTag(t, tag_table, ArraySize(tag_table)); |
1547 | 0 | if (key_p != (IFDTagTableType *) NULL) |
1548 | 0 | { |
1549 | 0 | (void) strlcpy(tag_description,key_p->description,MaxTextExtent); |
1550 | 0 | return tag_description; |
1551 | 0 | } |
1552 | | #if 0 |
1553 | | unsigned int |
1554 | | i; |
1555 | | |
1556 | | for (i=0; i < ArraySize(tag_table); i++) |
1557 | | { |
1558 | | if (tag_table[i].tag == t) |
1559 | | { |
1560 | | (void) strlcpy(tag_description,tag_table[i].description,MaxTextExtent); |
1561 | | return tag_description; |
1562 | | } |
1563 | | else if (tag_table[i].tag > t) |
1564 | | { |
1565 | | break; |
1566 | | } |
1567 | | } |
1568 | | #endif |
1569 | | |
1570 | 0 | FormatString(tag_description,"0x%04X",t); |
1571 | 0 | return tag_description; |
1572 | 0 | } |
1573 | | |
1574 | | /* |
1575 | | Convert an EXIF IFD tag description to a tag ID. |
1576 | | */ |
1577 | | static int |
1578 | | EXIFIfdTagDescriptionToTagId(const char *description) |
1579 | 13.5k | { |
1580 | 13.5k | unsigned int |
1581 | 13.5k | i; |
1582 | | |
1583 | 596k | for (i=0; i < sizeof(tag_table)/sizeof(tag_table[0]); i++) |
1584 | 596k | if (LocaleCompare(tag_table[i].description,description) == 0) |
1585 | 13.5k | return tag_table[i].tag; |
1586 | | |
1587 | 0 | return -1; |
1588 | 13.5k | } |
1589 | | |
1590 | | /* |
1591 | | Convert a TIFF IFD field value type to string description |
1592 | | */ |
1593 | | static const char * |
1594 | | EXIFIfdFieldTypeToStr(unsigned int f) |
1595 | 0 | { |
1596 | 0 | const char |
1597 | 0 | *str="unknown"; |
1598 | |
|
1599 | 0 | switch (f) |
1600 | 0 | { |
1601 | 0 | case EXIF_FMT_BYTE: |
1602 | 0 | str="BYTE"; |
1603 | 0 | break; |
1604 | 0 | case EXIF_FMT_ASCII: |
1605 | 0 | str="STRING"; |
1606 | 0 | break; |
1607 | 0 | case EXIF_FMT_USHORT: |
1608 | 0 | str="SHORT"; |
1609 | 0 | break; |
1610 | 0 | case EXIF_FMT_ULONG: |
1611 | 0 | str="LONG"; |
1612 | 0 | break; |
1613 | 0 | case EXIF_FMT_URATIONAL: |
1614 | 0 | str="RATIONAL"; |
1615 | 0 | break; |
1616 | 0 | case EXIF_FMT_SBYTE: |
1617 | 0 | str="SBYTE"; |
1618 | 0 | break; |
1619 | 0 | case EXIF_FMT_UNDEFINED: |
1620 | 0 | str="UNDEFINED"; |
1621 | 0 | break; |
1622 | 0 | case EXIF_FMT_SSHORT: |
1623 | 0 | str="SSHORT"; |
1624 | 0 | break; |
1625 | 0 | case EXIF_FMT_SLONG: |
1626 | 0 | str="SLONG"; |
1627 | 0 | break; |
1628 | 0 | case EXIF_FMT_SRATIONAL: |
1629 | 0 | str="SRATIONAL"; |
1630 | 0 | break; |
1631 | 0 | case EXIF_FMT_SINGLE: |
1632 | 0 | str="SINGLE"; |
1633 | 0 | break; |
1634 | 0 | case EXIF_FMT_DOUBLE: |
1635 | 0 | str="DOUBLE"; |
1636 | 0 | break; |
1637 | 0 | } |
1638 | | |
1639 | 0 | return str; |
1640 | 0 | } |
1641 | | |
1642 | | static const unsigned int |
1643 | | format_bytes[] = |
1644 | | { |
1645 | | 0, |
1646 | | 1, /* BYTE */ |
1647 | | 1, /* STRING / ASCII */ |
1648 | | 2, /* USHORT */ |
1649 | | 4, /* ULONG */ |
1650 | | 8, /* URATIONAL */ |
1651 | | 1, /* SBYTE */ |
1652 | | 1, /* UNDEFINED */ |
1653 | | 2, /* SSHORT */ |
1654 | | 4, /* SLONG */ |
1655 | | 8, /* SRATIONAL */ |
1656 | | 4, /* SINGLE / FLOAT */ |
1657 | | 8 /* DOUBLE */ |
1658 | | }; |
1659 | | |
1660 | | /* |
1661 | | Translate from EXIF_FMT_FOO to F_FOO bit-field flag. |
1662 | | */ |
1663 | | static unsigned int ExifFmtToFmtBits(unsigned int f) |
1664 | 61.1k | { |
1665 | 61.1k | unsigned int |
1666 | 61.1k | bits; |
1667 | | |
1668 | 61.1k | switch (f) |
1669 | 61.1k | { |
1670 | 0 | case EXIF_FMT_NOTYPE: |
1671 | 0 | default: |
1672 | 0 | bits=F_NOTYPE; |
1673 | 0 | break; |
1674 | 6.96k | case EXIF_FMT_BYTE: |
1675 | 6.96k | bits=F_BYTE; |
1676 | 6.96k | break; |
1677 | 3.95k | case EXIF_FMT_ASCII: |
1678 | 3.95k | bits=F_ASCII; |
1679 | 3.95k | break; |
1680 | 3.39k | case EXIF_FMT_USHORT: |
1681 | 3.39k | bits=F_SHORT; |
1682 | 3.39k | break; |
1683 | 13.5k | case EXIF_FMT_ULONG: |
1684 | 13.5k | bits=F_LONG; |
1685 | 13.5k | break; |
1686 | 2.74k | case EXIF_FMT_URATIONAL: |
1687 | 2.74k | bits=F_RATIONAL; |
1688 | 2.74k | break; |
1689 | 2.75k | case EXIF_FMT_SBYTE: |
1690 | 2.75k | bits=F_SBYTE; |
1691 | 2.75k | break; |
1692 | 5.16k | case EXIF_FMT_UNDEFINED: |
1693 | 5.16k | bits=F_UNDEFINED; |
1694 | 5.16k | break; |
1695 | 1.22k | case EXIF_FMT_SSHORT: |
1696 | 1.22k | bits=F_SSHORT; |
1697 | 1.22k | break; |
1698 | 5.34k | case EXIF_FMT_SLONG: |
1699 | 5.34k | bits=F_SLONG; |
1700 | 5.34k | break; |
1701 | 1.72k | case EXIF_FMT_SRATIONAL: |
1702 | 1.72k | bits=F_SRATIONAL; |
1703 | 1.72k | break; |
1704 | 1.79k | case EXIF_FMT_SINGLE: |
1705 | 1.79k | bits=F_SINGLE; |
1706 | 1.79k | break; |
1707 | 12.6k | case EXIF_FMT_DOUBLE: |
1708 | 12.6k | bits=F_DOUBLE; |
1709 | 12.6k | break; |
1710 | 61.1k | } |
1711 | 61.1k | return bits; |
1712 | 61.1k | } |
1713 | | |
1714 | | /* |
1715 | | Get the expected bit-field bits for format and use it to verify if |
1716 | | it is an expected format (a bit matches). |
1717 | | */ |
1718 | | typedef enum |
1719 | | { |
1720 | | ExifFmtGood, |
1721 | | ExifFmtFishy, |
1722 | | ExifFmtBad |
1723 | | } ExifFmtStatus; |
1724 | | |
1725 | | static ExifFmtStatus ValidateExifFmtAgainstBitField(const IFDTagTableType *key_p,const unsigned int fmt) |
1726 | 39.3k | { |
1727 | 39.3k | MagickPassFail |
1728 | 39.3k | status; |
1729 | | |
1730 | 39.3k | if (key_p->format & ExifFmtToFmtBits(fmt)) |
1731 | 17.5k | { |
1732 | | /* Totally good! */ |
1733 | 17.5k | status=ExifFmtGood; |
1734 | 17.5k | } |
1735 | 21.8k | else if ((key_p->format | key_p->format_alt) & ExifFmtToFmtBits(fmt)) |
1736 | 2.14k | { |
1737 | | /* Fishy */ |
1738 | 2.14k | status=ExifFmtFishy; |
1739 | 2.14k | } |
1740 | 19.6k | else |
1741 | 19.6k | { |
1742 | | /* Bad */ |
1743 | 19.6k | status=ExifFmtBad; |
1744 | 19.6k | } |
1745 | 39.3k | return status; |
1746 | 39.3k | } |
1747 | | |
1748 | | static magick_int16_t |
1749 | | Read16s(int morder,unsigned char *ishort) |
1750 | 497 | { |
1751 | 497 | union |
1752 | 497 | { |
1753 | 497 | magick_uint16_t u; |
1754 | 497 | magick_int16_t s; |
1755 | 497 | } value; |
1756 | | |
1757 | 497 | if (morder) |
1758 | 155 | value.u=((magick_uint16_t) ishort[0] << 8) | ishort[1]; |
1759 | 342 | else |
1760 | 342 | value.u=((magick_uint16_t) ishort[1] << 8) | ishort[0]; |
1761 | 497 | return(value.s); |
1762 | 497 | } |
1763 | | |
1764 | | static magick_uint16_t |
1765 | | Read16u(int morder,unsigned char *ishort) |
1766 | 300k | { |
1767 | 300k | magick_uint16_t |
1768 | 300k | value; |
1769 | | |
1770 | 300k | if (morder) |
1771 | 247k | value=((magick_uint16_t) ishort[0] << 8) |
1772 | 247k | | (magick_uint16_t) ishort[1]; |
1773 | 52.9k | else |
1774 | 52.9k | value=((magick_uint16_t) ishort[1] << 8) |
1775 | 52.9k | | (magick_uint16_t) ishort[0]; |
1776 | 300k | return(value); |
1777 | 300k | } |
1778 | | |
1779 | | static magick_int32_t |
1780 | | Read32s(int morder,unsigned char *ilong) |
1781 | 1.90k | { |
1782 | 1.90k | union |
1783 | 1.90k | { |
1784 | 1.90k | magick_uint32_t u; |
1785 | 1.90k | magick_int32_t s; |
1786 | 1.90k | } value; |
1787 | | |
1788 | 1.90k | if (morder) |
1789 | 1.67k | value.u=((magick_uint32_t) ilong[0] << 24) |
1790 | 1.67k | | ((magick_uint32_t) ilong[1] << 16) |
1791 | 1.67k | | ((magick_uint32_t) ilong[2] << 8) |
1792 | 1.67k | | ((magick_uint32_t) ilong[3]); |
1793 | 224 | else |
1794 | 224 | value.u=((magick_uint32_t) ilong[3] << 24) |
1795 | 224 | | ((magick_uint32_t) ilong[2] << 16) |
1796 | 224 | | ((magick_uint32_t) ilong[1] << 8 ) |
1797 | 224 | | ((magick_uint32_t) ilong[0]); |
1798 | 1.90k | return(value.s); |
1799 | 1.90k | } |
1800 | | |
1801 | | static magick_uint32_t |
1802 | | Read32u(int morder, unsigned char *ilong) |
1803 | 168k | { |
1804 | 168k | magick_uint32_t |
1805 | 168k | value; |
1806 | | |
1807 | 168k | if (morder) |
1808 | 142k | value=((magick_uint32_t) ilong[0] << 24) |
1809 | 142k | | ((magick_uint32_t) ilong[1] << 16) |
1810 | 142k | | ((magick_uint32_t) ilong[2] << 8) |
1811 | 142k | | ((magick_uint32_t) ilong[3]); |
1812 | 26.5k | else |
1813 | 26.5k | value=((magick_uint32_t) ilong[3] << 24) |
1814 | 26.5k | | ((magick_uint32_t) ilong[2] << 16) |
1815 | 26.5k | | ((magick_uint32_t) ilong[1] << 8 ) |
1816 | 26.5k | | ((magick_uint32_t) ilong[0]); |
1817 | | |
1818 | 168k | return value; |
1819 | 168k | } |
1820 | | |
1821 | | static void |
1822 | | Write16u(int morder, void *location, magick_uint16_t value) |
1823 | 12 | { |
1824 | 12 | char |
1825 | 12 | *pval; |
1826 | | |
1827 | 12 | pval = (char *)location; |
1828 | 12 | if (morder) |
1829 | 0 | { |
1830 | 0 | *pval++ = (char)((value >> 8) & 0xff); |
1831 | 0 | *pval++ = (char)(value & 0xff); |
1832 | 0 | } |
1833 | 12 | else |
1834 | 12 | { |
1835 | 12 | *pval++ = (char)(value & 0xff); |
1836 | 12 | *pval++ = (char)((value >> 8) & 0xff); |
1837 | 12 | } |
1838 | 12 | } |
1839 | | |
1840 | | static int |
1841 | | GenerateEXIFAttribute(Image *image,const char *specification) |
1842 | 352k | { |
1843 | 352k | char |
1844 | 352k | *final, |
1845 | 352k | *key, |
1846 | 352k | tag_description[MaxTextExtent], |
1847 | 352k | *value; |
1848 | | |
1849 | 352k | int |
1850 | 352k | id, |
1851 | 352k | morder, |
1852 | 352k | all; |
1853 | | |
1854 | 352k | int |
1855 | 352k | tag; |
1856 | | |
1857 | 352k | register size_t |
1858 | 352k | i; |
1859 | | |
1860 | 352k | size_t |
1861 | 352k | length; |
1862 | | |
1863 | 352k | unsigned int |
1864 | 352k | level, |
1865 | 352k | offset; |
1866 | | |
1867 | 352k | unsigned int |
1868 | 352k | gpsoffset; |
1869 | | |
1870 | 352k | unsigned char |
1871 | 352k | *tiffp, |
1872 | 352k | *tiffp_max, |
1873 | 352k | *ifdstack[DE_STACK_SIZE], |
1874 | 352k | *ifdp, |
1875 | 352k | *info; |
1876 | | |
1877 | 352k | unsigned int |
1878 | 352k | de, |
1879 | 352k | destack[DE_STACK_SIZE], |
1880 | 352k | nde; |
1881 | | |
1882 | 352k | const unsigned char |
1883 | 352k | *profile_info; |
1884 | | |
1885 | 352k | size_t |
1886 | 352k | profile_length; |
1887 | | |
1888 | 352k | MagickBool |
1889 | 352k | logging, |
1890 | 352k | gpsfoundstack[DE_STACK_SIZE], |
1891 | 352k | gpsfound; |
1892 | | |
1893 | 352k | MagickBool |
1894 | 352k | debug=MagickFalse; |
1895 | | |
1896 | 352k | assert((ArraySize(format_bytes)-1) == EXIF_NUM_FORMATS); |
1897 | 352k | logging=IsEventLogged(TransformEvent); |
1898 | 352k | { |
1899 | 352k | const char * |
1900 | 352k | env_value; |
1901 | | |
1902 | | /* |
1903 | | Allow enabling debug of EXIF tags |
1904 | | */ |
1905 | 352k | if ((env_value=getenv("MAGICK_DEBUG_EXIF"))) |
1906 | 0 | { |
1907 | 0 | if (LocaleCompare(env_value,"TRUE") == 0) |
1908 | 0 | debug=MagickTrue; |
1909 | 0 | } |
1910 | 352k | } |
1911 | 352k | gpsfound=MagickFalse; |
1912 | 352k | gpsoffset=0; |
1913 | | /* |
1914 | | Determine if there is any EXIF data available in the image. |
1915 | | */ |
1916 | 352k | value=(char *) NULL; |
1917 | 352k | final=AllocateString(""); |
1918 | 352k | profile_info=GetImageProfile(image,"EXIF",&profile_length); |
1919 | 352k | if (profile_info == 0) |
1920 | 338k | { |
1921 | 338k | if (logging) |
1922 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
1923 | 0 | "No EXIF profile present"); |
1924 | 338k | goto generate_attribute_failure; |
1925 | 338k | } |
1926 | 13.5k | if (logging && debug) |
1927 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
1928 | 0 | "EXIF: profile_info=%p, profile_length=%" MAGICK_SIZE_T_F "u", |
1929 | 0 | profile_info, (MAGICK_SIZE_T) profile_length); |
1930 | | /* |
1931 | | If EXIF data exists, then try to parse the request for a tag in |
1932 | | the form "EXIF:key". |
1933 | | */ |
1934 | 13.5k | key=(char *) NULL; |
1935 | 13.5k | if (strlen(specification) > 5) |
1936 | 13.5k | { |
1937 | 13.5k | key=(char *) &specification[5]; /* "EXIF:key" */ |
1938 | 13.5k | } |
1939 | 0 | else |
1940 | 0 | { |
1941 | 0 | if (logging) |
1942 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
1943 | 0 | "No EXIF:key found"); |
1944 | 0 | goto generate_attribute_failure; |
1945 | 0 | } |
1946 | 13.5k | while (isspace((int) (*key))) |
1947 | 0 | key++; |
1948 | 13.5k | all=0; |
1949 | 13.5k | tag=(-1); |
1950 | 13.5k | switch(*key) |
1951 | 13.5k | { |
1952 | | /* |
1953 | | Caller has asked for all the tags in the EXIF data. |
1954 | | */ |
1955 | 0 | case '*': |
1956 | 0 | { |
1957 | 0 | tag=0; |
1958 | 0 | all=1; /* return the data in description=value format */ |
1959 | 0 | break; |
1960 | 0 | } |
1961 | 0 | case '!': |
1962 | 0 | { |
1963 | 0 | tag=0; |
1964 | 0 | all=2; /* return the data in tageid=value format */ |
1965 | 0 | break; |
1966 | 0 | } |
1967 | | /* |
1968 | | Check for a hex based tag specification first. |
1969 | | */ |
1970 | 0 | case '#': |
1971 | 0 | { |
1972 | 0 | char |
1973 | 0 | c; |
1974 | |
|
1975 | 0 | size_t |
1976 | 0 | n; |
1977 | |
|
1978 | 0 | tag=0; |
1979 | 0 | key++; |
1980 | 0 | n=strlen(key); |
1981 | 0 | if (n != 4) |
1982 | 0 | { |
1983 | 0 | if (logging) |
1984 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
1985 | 0 | "EXIF: Hex tag not 4 bytes"); |
1986 | 0 | goto generate_attribute_failure; |
1987 | 0 | } |
1988 | 0 | else |
1989 | 0 | { |
1990 | | /* |
1991 | | Parse tag specification as a hex number. |
1992 | | */ |
1993 | 0 | n/=4; |
1994 | 0 | do |
1995 | 0 | { |
1996 | 0 | for (i=n; i > 0; i--) |
1997 | 0 | { |
1998 | 0 | c=(*key++); |
1999 | 0 | tag<<=4; |
2000 | 0 | if ((c >= '0') && (c <= '9')) |
2001 | 0 | tag|=c-'0'; |
2002 | 0 | else |
2003 | 0 | if ((c >= 'A') && (c <= 'F')) |
2004 | 0 | tag|=c-('A'-10); |
2005 | 0 | else |
2006 | 0 | if ((c >= 'a') && (c <= 'f')) |
2007 | 0 | tag|=c-('a'-10); |
2008 | 0 | else |
2009 | 0 | { |
2010 | 0 | if (logging) |
2011 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2012 | 0 | "EXIF: Failed to parse hex tag"); |
2013 | 0 | goto generate_attribute_failure; |
2014 | 0 | } |
2015 | 0 | } |
2016 | 0 | } while (*key != '\0'); |
2017 | 0 | } |
2018 | 0 | break; |
2019 | 0 | } |
2020 | 13.5k | default: |
2021 | 13.5k | { |
2022 | | /* |
2023 | | Try to match the text with a tag name instead. |
2024 | | */ |
2025 | 13.5k | tag=EXIFIfdTagDescriptionToTagId(key); |
2026 | 13.5k | if (logging && debug) |
2027 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2028 | 0 | "EXIF: Found tag %d for key \"%s\"",tag,key); |
2029 | 13.5k | break; |
2030 | 0 | } |
2031 | 13.5k | } |
2032 | 13.5k | if (tag < 0) |
2033 | 0 | { |
2034 | 0 | if (logging) |
2035 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2036 | 0 | "EXIF: Negative tag value!"); |
2037 | 0 | goto generate_attribute_failure;; |
2038 | 0 | } |
2039 | 13.5k | length=profile_length; |
2040 | 13.5k | info=(unsigned char *) profile_info; |
2041 | 9.62M | while (length != 0) |
2042 | 9.62M | { |
2043 | 9.62M | if (ReadByte(&info,&length) != 0x45) |
2044 | 9.57M | continue; |
2045 | 53.1k | if (ReadByte(&info,&length) != 0x78) |
2046 | 17.1k | continue; |
2047 | 35.9k | if (ReadByte(&info,&length) != 0x69) |
2048 | 4.55k | continue; |
2049 | 31.3k | if (ReadByte(&info,&length) != 0x66) |
2050 | 2.77k | continue; |
2051 | 28.5k | if (ReadByte(&info,&length) != 0x00) |
2052 | 15.7k | continue; |
2053 | 12.8k | if (ReadByte(&info,&length) != 0x00) |
2054 | 2.60k | continue; |
2055 | 10.2k | break; |
2056 | 12.8k | } |
2057 | 13.5k | if (length < 16) |
2058 | 3.61k | { |
2059 | 3.61k | if (logging) |
2060 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2061 | 0 | "EXIF: Tag length length < 16 (have %" MAGICK_SIZE_T_F "u )", |
2062 | 0 | (MAGICK_SIZE_T)length); |
2063 | 3.61k | goto generate_attribute_failure; |
2064 | 3.61k | } |
2065 | 9.94k | tiffp=info; |
2066 | 9.94k | tiffp_max=tiffp+length; |
2067 | 9.94k | id=Read16u(0,tiffp); |
2068 | 9.94k | morder=0; |
2069 | 9.94k | if (id == 0x4949) /* LSB */ |
2070 | 2.09k | morder=0; |
2071 | 7.85k | else |
2072 | 7.85k | if (id == 0x4D4D) /* MSB */ |
2073 | 7.18k | morder=1; |
2074 | 671 | else |
2075 | 671 | { |
2076 | 671 | if (logging) |
2077 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2078 | 0 | "EXIF: Unknown byte order (%04x)", |
2079 | 0 | morder); |
2080 | 671 | goto generate_attribute_failure; |
2081 | 671 | } |
2082 | 9.27k | if (Read16u(morder,tiffp+2) != 0x002a) |
2083 | 166 | { |
2084 | 166 | if (logging) |
2085 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2086 | 0 | "EXIF: Expected 0x002a!"); |
2087 | 166 | goto generate_attribute_failure; |
2088 | 166 | } |
2089 | | /* |
2090 | | This is the offset to the first IFD. |
2091 | | */ |
2092 | 9.10k | offset=Read32u(morder,tiffp+4); |
2093 | 9.10k | if (offset >= length) |
2094 | 263 | { |
2095 | 263 | if (logging) |
2096 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2097 | 0 | "EXIF: Offset (%u) is > length (%" MAGICK_SIZE_T_F "u)!", |
2098 | 0 | offset, (MAGICK_SIZE_T) length); |
2099 | 263 | goto generate_attribute_failure; |
2100 | 263 | } |
2101 | | /* |
2102 | | Set the pointer to the first IFD and follow it were it leads. |
2103 | | */ |
2104 | 8.84k | ifdp=tiffp+offset; |
2105 | 8.84k | level=0; |
2106 | 8.84k | de=0U; |
2107 | 8.84k | do |
2108 | 26.9k | { |
2109 | | /* |
2110 | | If there is anything on the stack then pop it off. |
2111 | | */ |
2112 | 26.9k | if (level > 0) |
2113 | 18.0k | { |
2114 | 18.0k | level--; |
2115 | 18.0k | ifdp=ifdstack[level]; |
2116 | 18.0k | de=destack[level]; |
2117 | 18.0k | gpsfound=gpsfoundstack[level]; |
2118 | 18.0k | } |
2119 | | /* |
2120 | | Determine how many entries there are in the current IFD. |
2121 | | Limit the number of entries parsed to MAX_TAGS_PER_IFD. |
2122 | | */ |
2123 | 26.9k | if ((ifdp < tiffp) || (ifdp+2 > tiffp_max)) |
2124 | 64 | { |
2125 | 64 | if (logging) |
2126 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2127 | 0 | "EXIF: ifdp out of range!"); |
2128 | 64 | goto generate_attribute_failure; |
2129 | 64 | } |
2130 | 26.8k | nde=Read16u(morder,ifdp); |
2131 | 26.8k | if (logging && debug) |
2132 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2133 | 0 | "EXIF: IFD at offset %" MAGICK_SSIZE_T_F "d has %u tags", |
2134 | 0 | (MAGICK_SSIZE_T) (ifdp-tiffp), nde); |
2135 | 26.8k | if (nde > MAX_TAGS_PER_IFD) |
2136 | 6.78k | { |
2137 | 6.78k | nde=MAX_TAGS_PER_IFD; |
2138 | 6.78k | if (logging && debug) |
2139 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2140 | 0 | "EXIF: Limiting IFD at offset %" MAGICK_SSIZE_T_F "d to %u tags!", |
2141 | 0 | (MAGICK_SSIZE_T) (ifdp-tiffp), nde); |
2142 | 6.78k | } |
2143 | 128k | for (; de < nde; de++) |
2144 | 126k | { |
2145 | 126k | size_t |
2146 | 126k | n; |
2147 | | |
2148 | 126k | unsigned int |
2149 | 126k | c, |
2150 | 126k | f, |
2151 | 126k | t; |
2152 | | |
2153 | 126k | unsigned char |
2154 | 126k | *pde, |
2155 | 126k | *pval; |
2156 | | |
2157 | 126k | pde=(unsigned char *) (ifdp+2+(12*(size_t) de)); |
2158 | 126k | if (logging && debug) |
2159 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2160 | 0 | "EXIF: PDE offset %" MAGICK_SSIZE_T_F "d", (MAGICK_SSIZE_T) (pde-ifdp)); |
2161 | 126k | if ((pde < tiffp) || (pde + 12 > tiffp_max)) |
2162 | 2.56k | { |
2163 | 2.56k | if (logging) |
2164 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2165 | 0 | "EXIF: Invalid Exif, entry is beyond metadata limit."); |
2166 | 2.56k | goto generate_attribute_failure; |
2167 | 2.56k | } |
2168 | 124k | t=Read16u(morder,pde); /* get tag value */ |
2169 | 124k | f=Read16u(morder,pde+2); /* get the format */ |
2170 | 124k | if (logging && debug) |
2171 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2172 | 0 | "EXIF: Tag %u, Format %u", t, f); |
2173 | 124k | if (((size_t) f >= ArraySize(format_bytes)) || (format_bytes[f] == 0)) |
2174 | 9.19k | { |
2175 | 9.19k | if (logging) |
2176 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2177 | 0 | "EXIF: Invalid Exif, unsupported format %u.",(unsigned) f); |
2178 | 9.19k | break; |
2179 | 9.19k | } |
2180 | 114k | c=Read32u(morder,pde+4); /* get number of components */ |
2181 | 114k | n=MagickArraySize(c,format_bytes[f]); |
2182 | 114k | if (logging && debug) |
2183 | 0 | { |
2184 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2185 | 0 | "EXIF: %u components, %u bytes per component", c,format_bytes[f]); |
2186 | 0 | } |
2187 | 114k | if ((c > length) || (n > length) || ((n == 0) && (c != 0) && (format_bytes[f] != 0))) |
2188 | 2.56k | { |
2189 | 2.56k | if (logging) |
2190 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2191 | 0 | "EXIF: Invalid Exif, too many components (%u components).",c); |
2192 | 2.56k | goto generate_attribute_failure; |
2193 | 2.56k | } |
2194 | 112k | if (n <= 4) |
2195 | 82.9k | { |
2196 | 82.9k | pval=(unsigned char *) pde+8; |
2197 | 82.9k | } |
2198 | 29.5k | else |
2199 | 29.5k | { |
2200 | 29.5k | size_t |
2201 | 29.5k | oval; |
2202 | | |
2203 | | /* |
2204 | | The directory entry contains an offset. |
2205 | | */ |
2206 | 29.5k | oval=Read32u(morder,pde+8); |
2207 | 29.5k | if ((oval > length) || ((oval+n) > length)) /* Impossibly long! */ |
2208 | 17.7k | { |
2209 | 17.7k | if (logging && debug) |
2210 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2211 | 0 | "EXIF: Invalid Exif directory entry!" |
2212 | 0 | " (offset %" MAGICK_SIZE_T_F "u, %" MAGICK_SIZE_T_F "u components)", |
2213 | 0 | (MAGICK_SIZE_T) oval, (MAGICK_SIZE_T) n); |
2214 | 17.7k | continue; |
2215 | 17.7k | } |
2216 | 11.7k | pval=(unsigned char *)(tiffp+oval); |
2217 | 11.7k | } |
2218 | | |
2219 | 94.7k | if ((pval < tiffp) || (pval > tiffp_max)) |
2220 | 0 | { |
2221 | 0 | if (logging) |
2222 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2223 | 0 | "EXIF: Offset %" MAGICK_SSIZE_T_F "d out of valid range!", |
2224 | 0 | (MAGICK_SSIZE_T) (pval-tiffp)); |
2225 | 0 | goto generate_attribute_failure; |
2226 | 0 | } |
2227 | | |
2228 | 94.7k | if (gpsfound) |
2229 | 19.8k | { |
2230 | 19.8k | if (/* (t < GPS_TAG_START) || */ (t > GPS_TAG_STOP)) |
2231 | 15.9k | { |
2232 | 15.9k | if (logging & debug) |
2233 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2234 | 0 | "EXIF: Skipping bogus GPS IFD tag %d ...",t); |
2235 | 15.9k | continue; |
2236 | 15.9k | } |
2237 | 19.8k | } |
2238 | 74.8k | else |
2239 | 74.8k | { |
2240 | 74.8k | if ((t < EXIF_TAG_START) || ( t > EXIF_TAG_STOP)) |
2241 | 8.95k | { |
2242 | 8.95k | if (logging & debug) |
2243 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2244 | 0 | "EXIF: Skipping bogus EXIF IFD tag %d ...",t); |
2245 | 8.95k | continue; |
2246 | 8.95k | } |
2247 | 74.8k | } |
2248 | | |
2249 | | /* if (logging && debug) */ |
2250 | 69.7k | { |
2251 | 69.7k | const char *ifd_type; |
2252 | 69.7k | int valid = MagickTrue; |
2253 | | |
2254 | 69.7k | if (gpsfound) |
2255 | 3.85k | ifd_type="GPS"; |
2256 | 65.9k | else |
2257 | 65.9k | ifd_type="EXIF"; |
2258 | | |
2259 | | /* |
2260 | | Enable logging via: |
2261 | | |
2262 | | MAGICK_DEBUG=transform MAGICK_DEBUG_EXIF=TRUE gm convert file.jpg -format '%[EXIF:*]' info:- |
2263 | | */ |
2264 | | |
2265 | 69.7k | { |
2266 | | /* |
2267 | | For a given IFD type and tag, validate that the claimed format and components are valid. |
2268 | | */ |
2269 | 69.7k | const IFDTagTableType *key_p = FindEXIFTableEntryByTag(t,tag_table,ArraySize(tag_table)); |
2270 | 69.7k | if (key_p) |
2271 | 39.3k | { |
2272 | | /* Validate format */ |
2273 | 39.3k | ExifFmtStatus fmt_status = ValidateExifFmtAgainstBitField(key_p,f); |
2274 | | |
2275 | 39.3k | switch (fmt_status) |
2276 | 39.3k | { |
2277 | 17.5k | case ExifFmtGood: |
2278 | 17.5k | break; |
2279 | 2.14k | case ExifFmtFishy: |
2280 | | /* Support tracing tags which use the wrong field type */ |
2281 | 2.14k | if (logging) |
2282 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2283 | 0 | "%s IFD: TagVal=%d (0x%04x) TagDescr=\"%s\" Fishy field type %d (%s)", |
2284 | 0 | ifd_type, |
2285 | 0 | t,t, |
2286 | 0 | EXIFIfdTagToDescription(t,tag_description), f, EXIFIfdFieldTypeToStr(f)); |
2287 | 2.14k | break; |
2288 | 19.6k | case ExifFmtBad: |
2289 | 19.6k | valid=MagickFalse; |
2290 | 19.6k | break; |
2291 | 39.3k | } |
2292 | | |
2293 | | /* Validate number of components */ |
2294 | 39.3k | if (!((key_p->count == AnyCount) || (key_p->count <= 0) || (c <= (unsigned int) key_p->count))) |
2295 | 4.18k | { |
2296 | 4.18k | if (logging) |
2297 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2298 | 0 | "%s IFD: TagVal=%d (0x%04x) TagDescr=\"%s\" Expected %u components (have %u)", |
2299 | 0 | ifd_type, |
2300 | 0 | t,t, |
2301 | 0 | EXIFIfdTagToDescription(t,tag_description), (unsigned int) key_p->count, c); |
2302 | 4.18k | valid=MagickFalse; |
2303 | 4.18k | } |
2304 | 39.3k | } |
2305 | 69.7k | } |
2306 | | |
2307 | 69.7k | if (logging) |
2308 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2309 | 0 | "%s IFD: TagVal=%d (0x%04x) TagDescr=\"%s\" Format=%d " |
2310 | 0 | "FormatDescr=\"%s\" Components=%u Valid=%s", |
2311 | 0 | ifd_type, |
2312 | 0 | t,t, |
2313 | 0 | EXIFIfdTagToDescription(t,tag_description),f, |
2314 | 0 | EXIFIfdFieldTypeToStr(f),c, |
2315 | 0 | valid == MagickFail ? "No" : "Yes"); |
2316 | 69.7k | if (valid != MagickTrue) |
2317 | 20.5k | { |
2318 | 20.5k | if (logging & debug) |
2319 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2320 | 0 | "Skipping bogus %s tag %d (0x%04X) ...",ifd_type,t,t); |
2321 | 20.5k | continue; |
2322 | 20.5k | } |
2323 | 69.7k | } |
2324 | | |
2325 | | |
2326 | | /* |
2327 | | Return values for all the tags, or for a specific requested tag. |
2328 | | |
2329 | | Tags from the GPS sub-IFD are in a bit of a chicken and |
2330 | | egg situation in that the tag for the GPS sub-IFD will not |
2331 | | be seen unless we pass that tag through so it can be |
2332 | | processed. So we pass the GPS_OFFSET tag through, but if |
2333 | | it was not requested, then we don't return a string value |
2334 | | for it. |
2335 | | */ |
2336 | 49.2k | if (all || (tag == (int) t) || (GPS_OFFSET == t)) |
2337 | 6.02k | { |
2338 | 6.02k | char |
2339 | 6.02k | s[MaxTextExtent]; |
2340 | | |
2341 | 6.02k | switch (f) |
2342 | 6.02k | { |
2343 | 0 | case EXIF_FMT_SBYTE: |
2344 | 0 | { |
2345 | | /* 8-bit signed integer */ |
2346 | 0 | FormatString(s,"%d",(int) (*(char *) pval)); |
2347 | 0 | value=AllocateString(s); |
2348 | 0 | break; |
2349 | 0 | } |
2350 | 0 | case EXIF_FMT_BYTE: |
2351 | 0 | { |
2352 | | /* 8-bit unsigned integer */ |
2353 | 0 | value=MagickAllocateMemory(char *,n+1); |
2354 | 0 | if (value != (char *) NULL) |
2355 | 0 | { |
2356 | 0 | unsigned int |
2357 | 0 | a; |
2358 | |
|
2359 | 0 | for (a=0; a < n; a++) |
2360 | 0 | { |
2361 | 0 | value[a]='.'; |
2362 | 0 | if (isprint((int) pval[a])) |
2363 | 0 | value[a]=pval[a]; |
2364 | 0 | } |
2365 | 0 | value[a]='\0'; |
2366 | 0 | break; |
2367 | 0 | } |
2368 | | #if 0 |
2369 | | printf("format %u, length %u\n",f,n); |
2370 | | FormatString(s,"%ld",(long) (*(unsigned char *) pval)); |
2371 | | value=AllocateString(s); |
2372 | | #endif |
2373 | 0 | break; |
2374 | 0 | } |
2375 | 0 | case EXIF_FMT_SSHORT: |
2376 | 0 | { |
2377 | | /* 16-bit signed integer */ |
2378 | 0 | if (((pval < tiffp) || (pval+sizeof(magick_uint16_t)) > tiffp_max)) |
2379 | 0 | { |
2380 | 0 | if (logging) |
2381 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2382 | 0 | "EXIF: Offset %" MAGICK_SSIZE_T_F "d out of valid range!", |
2383 | 0 | (MAGICK_SSIZE_T) (pval-tiffp)); |
2384 | 0 | goto generate_attribute_failure; |
2385 | 0 | } |
2386 | 0 | FormatString(s,"%hd",Read16u(morder,pval)); |
2387 | 0 | value=AllocateString(s); |
2388 | 0 | break; |
2389 | 0 | } |
2390 | 497 | case EXIF_FMT_USHORT: |
2391 | 497 | { |
2392 | | /* 16-bit unsigned integer */ |
2393 | 497 | if ((pval < tiffp) || ((pval+sizeof(magick_uint16_t)) > tiffp_max)) |
2394 | 0 | { |
2395 | 0 | if (logging) |
2396 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2397 | 0 | "EXIF: Offset %" MAGICK_SSIZE_T_F "d out of valid range!", |
2398 | 0 | (MAGICK_SSIZE_T) (pval-tiffp)); |
2399 | 0 | goto generate_attribute_failure; |
2400 | 0 | } |
2401 | 497 | FormatString(s,"%hu",Read16s(morder,pval)); |
2402 | 497 | value=AllocateString(s); |
2403 | 497 | break; |
2404 | 497 | } |
2405 | 3.63k | case EXIF_FMT_ULONG: |
2406 | 3.63k | { |
2407 | 3.63k | if ((pval < tiffp) || ((pval+sizeof(magick_uint32_t)) > tiffp_max)) |
2408 | 0 | { |
2409 | 0 | if (logging) |
2410 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2411 | 0 | "EXIF: Offset %" MAGICK_SSIZE_T_F "d out of valid range!", |
2412 | 0 | (MAGICK_SSIZE_T) (pval-tiffp)); |
2413 | 0 | goto generate_attribute_failure; |
2414 | 0 | } |
2415 | 3.63k | offset=Read32u(morder,pval); |
2416 | | /* |
2417 | | Only report value if this tag was requested. |
2418 | | */ |
2419 | 3.63k | if (all || (tag == (int) t)) |
2420 | 0 | { |
2421 | 0 | FormatString(s,"%u",offset); |
2422 | 0 | value=AllocateString(s); |
2423 | 0 | } |
2424 | 3.63k | if (GPS_OFFSET == t) |
2425 | 3.63k | { |
2426 | 3.63k | gpsoffset=offset; |
2427 | 3.63k | } |
2428 | 3.63k | break; |
2429 | 3.63k | } |
2430 | 1.90k | case EXIF_FMT_SLONG: |
2431 | 1.90k | { |
2432 | 1.90k | if ((pval < tiffp) || ((pval+sizeof(magick_uint32_t)) > tiffp_max)) |
2433 | 0 | { |
2434 | 0 | if (logging) |
2435 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2436 | 0 | "EXIF: Offset %" MAGICK_SSIZE_T_F "d out of valid range!", |
2437 | 0 | (MAGICK_SSIZE_T) (pval-tiffp)); |
2438 | 0 | goto generate_attribute_failure; |
2439 | 0 | } |
2440 | 1.90k | FormatString(s,"%d",(int) Read32s(morder,pval)); |
2441 | 1.90k | value=AllocateString(s); |
2442 | 1.90k | break; |
2443 | 1.90k | } |
2444 | 0 | case EXIF_FMT_URATIONAL: |
2445 | 0 | { |
2446 | 0 | if (gpsfound && |
2447 | 0 | (t == GPS_LATITUDE || |
2448 | 0 | t == GPS_LONGITUDE || |
2449 | 0 | t == GPS_TIMESTAMP)) |
2450 | 0 | { |
2451 | 0 | if ((pval < tiffp) || ((pval+6*sizeof(magick_uint32_t)) > tiffp_max)) |
2452 | 0 | { |
2453 | 0 | if (logging) |
2454 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2455 | 0 | "EXIF: Offset %" MAGICK_SSIZE_T_F "d out of valid range!", |
2456 | 0 | (MAGICK_SSIZE_T) (pval-tiffp)); |
2457 | 0 | goto generate_attribute_failure; |
2458 | 0 | } |
2459 | 0 | FormatString(s,"%u/%u,%u/%u,%u/%u" |
2460 | 0 | ,(unsigned) Read32u(morder,pval), |
2461 | 0 | (unsigned) Read32u(morder,4+pval) |
2462 | 0 | ,(unsigned) Read32u(morder,8+pval), |
2463 | 0 | (unsigned) Read32u(morder,12+pval) |
2464 | 0 | ,(unsigned) Read32u(morder,16+pval), |
2465 | 0 | (unsigned) Read32u(morder,20+pval) |
2466 | 0 | ); |
2467 | 0 | } |
2468 | 0 | else |
2469 | 0 | { |
2470 | 0 | if ((pval < tiffp) || ((pval+2*sizeof(magick_uint32_t)) > tiffp_max)) |
2471 | 0 | { |
2472 | 0 | if (logging) |
2473 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2474 | 0 | "EXIF: Offset %" MAGICK_SSIZE_T_F "d out of valid range!", |
2475 | 0 | (MAGICK_SSIZE_T) (pval-tiffp)); |
2476 | 0 | goto generate_attribute_failure; |
2477 | 0 | } |
2478 | 0 | FormatString(s,"%u/%u" |
2479 | 0 | ,(unsigned) Read32u(morder,pval), |
2480 | 0 | (unsigned) Read32u(morder,4+pval) |
2481 | 0 | ); |
2482 | 0 | } |
2483 | 0 | value=AllocateString(s); |
2484 | 0 | break; |
2485 | 0 | } |
2486 | 0 | case EXIF_FMT_SRATIONAL: |
2487 | 0 | { |
2488 | 0 | if ((pval < tiffp) || ((pval+2*sizeof(magick_uint32_t)) > tiffp_max)) |
2489 | 0 | { |
2490 | 0 | if (logging) |
2491 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2492 | 0 | "EXIF: Offset %" MAGICK_SSIZE_T_F "d out of valid range!", |
2493 | 0 | (MAGICK_SSIZE_T) (pval-tiffp)); |
2494 | 0 | goto generate_attribute_failure; |
2495 | 0 | } |
2496 | 0 | FormatString(s,"%d/%d",(int) Read32s(morder,pval), |
2497 | 0 | (int) Read32s(morder,4+pval)); |
2498 | 0 | value=AllocateString(s); |
2499 | 0 | break; |
2500 | 0 | } |
2501 | 0 | case EXIF_FMT_SINGLE: |
2502 | 0 | { |
2503 | 0 | float fval; |
2504 | 0 | if ((pval < tiffp) || ((pval+sizeof(float)) > tiffp_max)) |
2505 | 0 | { |
2506 | 0 | if (logging) |
2507 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2508 | 0 | "EXIF: Offset %" MAGICK_SSIZE_T_F "d out of valid range!", |
2509 | 0 | (MAGICK_SSIZE_T) (pval-tiffp)); |
2510 | 0 | goto generate_attribute_failure; |
2511 | 0 | } |
2512 | 0 | (void) memcpy(&fval,pval,sizeof(fval)); |
2513 | 0 | FormatString(s,"%f",(double) fval); |
2514 | 0 | value=AllocateString(s); |
2515 | 0 | break; |
2516 | 0 | } |
2517 | 0 | case EXIF_FMT_DOUBLE: |
2518 | 0 | { |
2519 | 0 | double dval; |
2520 | 0 | if ((pval < tiffp) || ((pval+sizeof(double)) > tiffp_max)) |
2521 | 0 | { |
2522 | 0 | if (logging) |
2523 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2524 | 0 | "EXIF: Offset %" MAGICK_SSIZE_T_F "d out of valid range!", |
2525 | 0 | (MAGICK_SSIZE_T) (pval-tiffp)); |
2526 | 0 | goto generate_attribute_failure; |
2527 | 0 | } |
2528 | 0 | (void) memcpy(&dval,pval,sizeof(dval)); |
2529 | 0 | FormatString(s,"%f",dval); |
2530 | 0 | value=AllocateString(s); |
2531 | 0 | break; |
2532 | 0 | } |
2533 | 0 | default: |
2534 | 0 | case EXIF_FMT_UNDEFINED: |
2535 | 0 | case EXIF_FMT_ASCII: |
2536 | 0 | { |
2537 | 0 | unsigned int |
2538 | 0 | a; |
2539 | |
|
2540 | 0 | size_t |
2541 | 0 | allocation_size; |
2542 | |
|
2543 | 0 | MagickBool |
2544 | 0 | binary=MagickFalse; |
2545 | |
|
2546 | 0 | if (logging) |
2547 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2548 | 0 | "EXIF: pval=%p (offset=%" MAGICK_SSIZE_T_F "d), n=%" MAGICK_SIZE_T_F "u", |
2549 | 0 | pval, (MAGICK_SSIZE_T) (pval-tiffp), (MAGICK_SIZE_T) n); |
2550 | |
|
2551 | 0 | if ((pval < tiffp) || (pval+n) > tiffp_max) |
2552 | 0 | { |
2553 | 0 | if (logging) |
2554 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2555 | 0 | "EXIF: Offset %" MAGICK_SSIZE_T_F "d out of valid range!", |
2556 | 0 | (MAGICK_SSIZE_T) (pval-tiffp)); |
2557 | 0 | goto generate_attribute_failure; |
2558 | 0 | } |
2559 | 0 | allocation_size=n+1; |
2560 | 0 | for (a=0; a < n; a++) |
2561 | 0 | { |
2562 | 0 | if (!(isprint((int) pval[a]))) |
2563 | 0 | allocation_size += 3; |
2564 | 0 | } |
2565 | |
|
2566 | 0 | value=MagickAllocateMemory(char *,allocation_size); |
2567 | 0 | if (value != (char *) NULL) |
2568 | 0 | { |
2569 | 0 | i=0; |
2570 | 0 | for (a=0; a < n; a++) |
2571 | 0 | { |
2572 | 0 | if ((f == EXIF_FMT_ASCII) && (pval[a] == '\0')) |
2573 | 0 | break; |
2574 | 0 | if ((isprint((int) pval[a])) || |
2575 | 0 | ((pval[a] == '\0') && |
2576 | 0 | (a == (n-1) && (!binary)))) |
2577 | 0 | { |
2578 | 0 | value[i++]=pval[a]; |
2579 | 0 | } |
2580 | 0 | else |
2581 | 0 | { |
2582 | 0 | i += snprintf(&value[i],(allocation_size-i),"\\%03o", |
2583 | 0 | (unsigned int) pval[a]); |
2584 | 0 | binary |= MagickTrue; |
2585 | 0 | } |
2586 | 0 | } |
2587 | 0 | value[i]='\0'; |
2588 | 0 | } |
2589 | 0 | break; |
2590 | 0 | } |
2591 | 6.02k | } |
2592 | 6.02k | if (value != (char *) NULL) |
2593 | 2.39k | { |
2594 | 2.39k | const char |
2595 | 2.39k | *description; |
2596 | | |
2597 | 2.39k | if (strlen(final) != 0) |
2598 | 1.59k | (void) ConcatenateString(&final,EXIF_DELIMITER); |
2599 | 2.39k | description=(const char *) NULL; |
2600 | 2.39k | switch (all) |
2601 | 2.39k | { |
2602 | 0 | case 1: |
2603 | 0 | { |
2604 | 0 | description=EXIFIfdTagToDescription(t,tag_description); |
2605 | 0 | FormatString(s,"%.1024s=",description); |
2606 | 0 | (void) ConcatenateString(&final,s); |
2607 | 0 | break; |
2608 | 0 | } |
2609 | 0 | case 2: |
2610 | 0 | { |
2611 | 0 | FormatString(s,"#%04x=",t); |
2612 | 0 | (void) ConcatenateString(&final,s); |
2613 | 0 | break; |
2614 | 0 | } |
2615 | 2.39k | } |
2616 | 2.39k | (void) ConcatenateString(&final,value); |
2617 | 2.39k | MagickFreeMemory(value); |
2618 | 2.39k | } |
2619 | 6.02k | } |
2620 | 49.2k | if (t == GPS_OFFSET && (gpsoffset != 0)) |
2621 | 3.20k | { |
2622 | 3.20k | if ((gpsoffset < length) && (level < (DE_STACK_SIZE-2))) |
2623 | 2.47k | { |
2624 | | /* |
2625 | | Push our current directory state onto the stack. |
2626 | | */ |
2627 | 2.47k | ifdstack[level]=ifdp; |
2628 | 2.47k | de++; /* bump to the next entry */ |
2629 | 2.47k | destack[level]=de; |
2630 | 2.47k | gpsfoundstack[level]=gpsfound; |
2631 | 2.47k | level++; |
2632 | | /* |
2633 | | Push new state onto of stack to cause a jump. |
2634 | | */ |
2635 | 2.47k | ifdstack[level]=tiffp+gpsoffset; |
2636 | 2.47k | destack[level]=0; |
2637 | 2.47k | gpsfoundstack[level]=MagickTrue; |
2638 | 2.47k | level++; |
2639 | 2.47k | } |
2640 | 3.20k | gpsoffset=0; |
2641 | 3.20k | break; /* break out of the for loop */ |
2642 | 3.20k | } |
2643 | | |
2644 | 46.0k | if ((t == TAG_EXIF_OFFSET) || (t == TAG_INTEROP_OFFSET)) |
2645 | 7.58k | { |
2646 | 7.58k | offset=Read32u(morder,pval); |
2647 | 7.58k | if (logging & debug) |
2648 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2649 | 0 | "EXIF: %s at offset %u", |
2650 | 0 | t == TAG_EXIF_OFFSET ? "TAG_EXIF_OFFSET" : |
2651 | 0 | "TAG_INTEROP_OFFSET", offset); |
2652 | 7.58k | if ((offset < length) && (level < (DE_STACK_SIZE-2))) |
2653 | 6.82k | { |
2654 | | /* |
2655 | | Check that we are not being directed to read an |
2656 | | IFD that we are already parsing and quit in order |
2657 | | to avoid a loop. |
2658 | | */ |
2659 | 6.82k | unsigned char *new_ifdp = tiffp+offset; |
2660 | 6.82k | MagickBool dup_ifd = MagickFalse; |
2661 | | |
2662 | 6.82k | if (new_ifdp == ifdp) |
2663 | 86 | { |
2664 | 86 | dup_ifd = MagickTrue; |
2665 | 86 | } |
2666 | 6.73k | else |
2667 | 6.73k | { |
2668 | 15.7k | for (i=0; i < level; i++) |
2669 | 9.02k | { |
2670 | 9.02k | if (ifdstack[i] == new_ifdp) |
2671 | 15 | { |
2672 | 15 | dup_ifd = MagickTrue; |
2673 | 15 | break; |
2674 | 15 | } |
2675 | 9.02k | } |
2676 | 6.73k | } |
2677 | 6.82k | if (dup_ifd) |
2678 | 101 | { |
2679 | 101 | if (logging) |
2680 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2681 | 0 | "EXIF: Duplicate IFD detected, quitting to avoid loop!"); |
2682 | 101 | goto generate_attribute_failure; |
2683 | 101 | } |
2684 | | |
2685 | | /* |
2686 | | Push our current directory state onto the stack. |
2687 | | */ |
2688 | 6.72k | if (logging & debug) |
2689 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2690 | 0 | "ifdstack[%u]=%p", level, ifdp); |
2691 | 6.72k | ifdstack[level]=ifdp; |
2692 | 6.72k | de++; /* bump to the next entry */ |
2693 | 6.72k | destack[level]=de; |
2694 | 6.72k | gpsfoundstack[level]=gpsfound; |
2695 | 6.72k | level++; |
2696 | | /* |
2697 | | Push new state onto of stack to cause a jump. |
2698 | | */ |
2699 | 6.72k | ifdstack[level]=tiffp+offset; |
2700 | 6.72k | if (logging & debug) |
2701 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
2702 | 0 | "ifdstack[%u]=%p", level, ifdp); |
2703 | 6.72k | destack[level]=0; |
2704 | 6.72k | gpsfoundstack[level]=MagickFalse; |
2705 | 6.72k | level++; |
2706 | 6.72k | } |
2707 | 7.47k | break; /* break out of the for loop */ |
2708 | 7.58k | } |
2709 | 46.0k | } |
2710 | 26.8k | } while (level > 0); |
2711 | 3.55k | if (strlen(final) == 0) |
2712 | 2.89k | (void) ConcatenateString(&final,"unknown"); |
2713 | | |
2714 | 3.55k | (void) SetImageAttribute(image,specification,(const char *) NULL); |
2715 | 3.55k | (void) SetImageAttribute(image,specification,(const char *) final); |
2716 | 3.55k | MagickFreeMemory(final); |
2717 | 3.55k | return(True); |
2718 | 348k | generate_attribute_failure: |
2719 | 348k | MagickFreeMemory(final); |
2720 | 348k | return False; |
2721 | 8.84k | } |
2722 | | |
2723 | | /* |
2724 | | Generate an aggregate attribute result based on a wildcard |
2725 | | specification like "foo:*". |
2726 | | */ |
2727 | | static int |
2728 | | GenerateWildcardAttribute(Image *image,const char *key) |
2729 | 4.04k | { |
2730 | 4.04k | char |
2731 | 4.04k | *result=NULL; |
2732 | | |
2733 | 4.04k | size_t |
2734 | 4.04k | key_length=0; |
2735 | | |
2736 | 4.04k | register ImageAttribute |
2737 | 4.04k | *p = (ImageAttribute *) NULL; |
2738 | | |
2739 | 4.04k | MagickPassFail |
2740 | 4.04k | status=MagickFail; |
2741 | | |
2742 | | /* |
2743 | | Support a full "*" wildcard. |
2744 | | */ |
2745 | 4.04k | if (strcmp("*",key) == 0) |
2746 | 0 | { |
2747 | 0 | (void) GenerateIPTCAttribute((Image *) image,"IPTC:*"); |
2748 | 0 | (void) Generate8BIMAttribute((Image *) image,"8BIM:*"); |
2749 | 0 | (void) GenerateEXIFAttribute((Image *) image,"EXIF:*"); |
2750 | 0 | } |
2751 | | |
2752 | 4.04k | key_length=strlen(key)-1; |
2753 | 15.8k | for (p=image->attributes; p != (ImageAttribute *) NULL; p=p->next) |
2754 | 11.8k | if (LocaleNCompare(key,p->key,key_length) == 0) |
2755 | 3.61k | { |
2756 | 3.61k | char |
2757 | 3.61k | s[MaxTextExtent]; |
2758 | | |
2759 | 3.61k | if (result != NULL) |
2760 | 1.69k | (void) ConcatenateString(&result,"\n"); |
2761 | 3.61k | FormatString(s,"%.512s=%.1024s",p->key,p->value); |
2762 | 3.61k | (void) ConcatenateString(&result,s); |
2763 | 3.61k | } |
2764 | | |
2765 | 4.04k | if (result != NULL) |
2766 | 1.92k | { |
2767 | 1.92k | status=SetImageAttribute(image,key,result); |
2768 | 1.92k | MagickFreeMemory(result); |
2769 | 1.92k | } |
2770 | 4.04k | return status; |
2771 | 4.04k | } |
2772 | | |
2773 | | MagickExport const ImageAttribute * |
2774 | | GetImageAttribute(const Image *image,const char *key) |
2775 | 2.58M | { |
2776 | 2.58M | register ImageAttribute |
2777 | 2.58M | *p = (ImageAttribute *) NULL; |
2778 | | |
2779 | 2.58M | size_t |
2780 | 2.58M | key_length=0; |
2781 | | |
2782 | 2.58M | assert(image != (Image *) NULL); |
2783 | 2.58M | assert(image->signature == MagickSignature); |
2784 | | |
2785 | | /* |
2786 | | If key is null, then return a pointer to the attribute list. |
2787 | | */ |
2788 | 2.58M | if (key == (char *) NULL) |
2789 | 478k | return(image->attributes); |
2790 | | |
2791 | 2.10M | key_length=strlen(key); |
2792 | | |
2793 | 7.59M | for (p=image->attributes; p != (ImageAttribute *) NULL; p=p->next) |
2794 | 6.20M | if (LocaleCompare(key,p->key) == 0) |
2795 | 715k | return(p); |
2796 | | |
2797 | 1.38M | if (LocaleNCompare("IPTC:",key,5) == 0) |
2798 | 0 | { |
2799 | | /* |
2800 | | Create an attribute named "IPTC:*" with all matching |
2801 | | key=values and return it. |
2802 | | */ |
2803 | 0 | if (GenerateIPTCAttribute((Image *) image,key) == True) |
2804 | 0 | return(GetImageAttribute(image,key)); |
2805 | 0 | } |
2806 | 1.38M | else if (LocaleNCompare("8BIM:",key,5) == 0) |
2807 | 0 | { |
2808 | | /* |
2809 | | Create an attribute named "8BIM:*" with all matching |
2810 | | key=values and return it. |
2811 | | */ |
2812 | 0 | if (Generate8BIMAttribute((Image *) image,key) == True) |
2813 | 0 | return(GetImageAttribute(image,key)); |
2814 | 0 | } |
2815 | 1.38M | else if (LocaleNCompare("EXIF:",key,5) == 0) |
2816 | 352k | { |
2817 | | /* |
2818 | | Create an attribute named "EXIF:*" with all matching |
2819 | | key=values and return it. |
2820 | | */ |
2821 | 352k | if (GenerateEXIFAttribute((Image *) image,key) == True) |
2822 | 3.55k | return(GetImageAttribute(image,key)); |
2823 | 352k | } |
2824 | 1.03M | else if ((key_length >=2) && (key[key_length-1] == '*')) |
2825 | 4.04k | { |
2826 | | /* |
2827 | | Create an attribute named "foo:*" with all matching |
2828 | | key=values and return it. |
2829 | | */ |
2830 | 4.04k | if (GenerateWildcardAttribute((Image *) image,key) == True) |
2831 | 1.92k | return(GetImageAttribute(image,key)); |
2832 | 4.04k | } |
2833 | 1.03M | else if ((key_length ==1) && (key[0] == '*')) |
2834 | 0 | { |
2835 | | /* |
2836 | | Create an attribute named "*" with all key=values and return |
2837 | | it. |
2838 | | */ |
2839 | 0 | if (GenerateWildcardAttribute((Image *) image,key) == True) |
2840 | 0 | return(GetImageAttribute(image,key)); |
2841 | 0 | } |
2842 | 1.38M | return(p); |
2843 | 1.38M | } |
2844 | | |
2845 | | /* |
2846 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2847 | | % % |
2848 | | % % |
2849 | | % % |
2850 | | % G e t I m a g e C l i p p i n g P a t h A t t r i b u t e % |
2851 | | % % |
2852 | | % % |
2853 | | % % |
2854 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2855 | | % |
2856 | | % Method GetImageClippingPathAttribute searches the list of image attributes |
2857 | | % and returns a pointer to a clipping path if it exists otherwise NULL. |
2858 | | % |
2859 | | % The format of the GetImageClippingPathAttribute method is: |
2860 | | % |
2861 | | % const ImageAttribute *GetImageClippingPathAttribute(const Image *image) |
2862 | | % |
2863 | | % A description of each parameter follows: |
2864 | | % |
2865 | | % o attribute: Method GetImageClippingPathAttribute returns the clipping |
2866 | | % path if it exists otherwise NULL. |
2867 | | % |
2868 | | % o image: The image. |
2869 | | % |
2870 | | % |
2871 | | */ |
2872 | | MagickExport const ImageAttribute * |
2873 | | GetImageClippingPathAttribute(const Image *image) |
2874 | 0 | { |
2875 | | /* Get the name of the clipping path, if any. The clipping path |
2876 | | length is indicated by the first character of the Pascal |
2877 | | string. */ |
2878 | 0 | const ImageAttribute *path_name = GetImageAttribute(image, "8BIM:2999,2999"); |
2879 | 0 | if ((path_name != (const ImageAttribute *) NULL) && |
2880 | 0 | (path_name->length > 2) && |
2881 | 0 | ((size_t) path_name->value[0] < path_name->length)) |
2882 | 0 | { |
2883 | 0 | static const char clip_prefix[] = "8BIM:1999,2998"; |
2884 | 0 | char attr_name[271]; |
2885 | | /*sprintf(attr_name, "%s:%.255s", clip_prefix, path_name->value+1);*/ |
2886 | 0 | sprintf(attr_name, "%s:%.*s", clip_prefix, Min(255,(int) path_name->length-1), |
2887 | 0 | path_name->value+1); |
2888 | 0 | return GetImageAttribute(image, attr_name); |
2889 | 0 | } |
2890 | 0 | return NULL; |
2891 | 0 | } |
2892 | | |
2893 | | /* |
2894 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2895 | | % % |
2896 | | % % |
2897 | | % % |
2898 | | + G e t I m a g e I n f o A t t r i b u t e % |
2899 | | % % |
2900 | | % % |
2901 | | % % |
2902 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2903 | | % |
2904 | | % GetImageInfoAttribute() returns a "fake" attribute based on data in the |
2905 | | % image info or image structures. |
2906 | | % |
2907 | | % The format of the GetImageInfoAttribute method is: |
2908 | | % |
2909 | | % const ImageAttribute *GetImageAttribute(const ImageInfo *image_info, |
2910 | | % const Image *image,const char *key) |
2911 | | % |
2912 | | % A description of each parameter follows: |
2913 | | % |
2914 | | % o attribute: Method GetImageInfoAttribute returns the attribute if it |
2915 | | % exists otherwise NULL. |
2916 | | % |
2917 | | % o image_info: The imageInfo. |
2918 | | % |
2919 | | % o image: The image. |
2920 | | % |
2921 | | % o key: These character strings are the name of an image attribute to |
2922 | | % return. |
2923 | | % |
2924 | | */ |
2925 | | MagickExport const ImageAttribute * |
2926 | | GetImageInfoAttribute(const ImageInfo *image_info,const Image *image, |
2927 | | const char *key) |
2928 | 0 | { |
2929 | 0 | char |
2930 | 0 | attribute[MaxTextExtent], |
2931 | 0 | filename[MaxTextExtent]; |
2932 | |
|
2933 | 0 | attribute[0]='\0'; |
2934 | 0 | switch(*(key)) |
2935 | 0 | { |
2936 | 0 | case 'b': |
2937 | 0 | { |
2938 | 0 | if (LocaleNCompare("base",key,2) == 0) |
2939 | 0 | { |
2940 | 0 | GetPathComponent(image->magick_filename,BasePath,filename); |
2941 | 0 | (void) strlcpy(attribute,filename,MaxTextExtent); |
2942 | 0 | break; |
2943 | 0 | } |
2944 | 0 | break; |
2945 | 0 | } |
2946 | 0 | case 'd': |
2947 | 0 | { |
2948 | 0 | if (LocaleNCompare("depth",key,2) == 0) |
2949 | 0 | { |
2950 | 0 | FormatString(attribute,"%u",image->depth); |
2951 | 0 | break; |
2952 | 0 | } |
2953 | 0 | if (LocaleNCompare("directory",key,2) == 0) |
2954 | 0 | { |
2955 | 0 | GetPathComponent(image->magick_filename,HeadPath,filename); |
2956 | 0 | (void) strlcpy(attribute,filename,MaxTextExtent); |
2957 | 0 | break; |
2958 | 0 | } |
2959 | 0 | break; |
2960 | 0 | } |
2961 | 0 | case 'e': |
2962 | 0 | { |
2963 | 0 | if (LocaleNCompare("extension",key,2) == 0) |
2964 | 0 | { |
2965 | 0 | GetPathComponent(image->magick_filename,ExtensionPath,filename); |
2966 | 0 | (void) strlcpy(attribute,filename,MaxTextExtent); |
2967 | 0 | break; |
2968 | 0 | } |
2969 | 0 | break; |
2970 | 0 | } |
2971 | 0 | case 'g': |
2972 | 0 | { |
2973 | 0 | if (LocaleNCompare("group",key,2) == 0) |
2974 | 0 | { |
2975 | 0 | FormatString(attribute,"0x%lx",image_info->group); |
2976 | 0 | break; |
2977 | 0 | } |
2978 | 0 | break; |
2979 | 0 | } |
2980 | 0 | case 'h': |
2981 | 0 | { |
2982 | 0 | if (LocaleNCompare("height",key,2) == 0) |
2983 | 0 | { |
2984 | 0 | FormatString(attribute,"%lu", |
2985 | 0 | image->magick_rows ? image->magick_rows : 256L); |
2986 | 0 | break; |
2987 | 0 | } |
2988 | 0 | break; |
2989 | 0 | } |
2990 | 0 | case 'i': |
2991 | 0 | { |
2992 | 0 | if (LocaleNCompare("input",key,2) == 0) |
2993 | 0 | { |
2994 | 0 | (void) strlcpy(attribute,image->filename,MaxTextExtent); |
2995 | 0 | break; |
2996 | 0 | } |
2997 | 0 | break; |
2998 | 0 | } |
2999 | 0 | case 'm': |
3000 | 0 | { |
3001 | 0 | if (LocaleNCompare("magick",key,2) == 0) |
3002 | 0 | { |
3003 | 0 | (void) strlcpy(attribute,image->magick,MaxTextExtent); |
3004 | 0 | break; |
3005 | 0 | } |
3006 | 0 | break; |
3007 | 0 | } |
3008 | 0 | case 'n': |
3009 | 0 | { |
3010 | 0 | if (LocaleNCompare("name",key,2) == 0) |
3011 | 0 | { |
3012 | | /* What should this really be? */ |
3013 | 0 | GetPathComponent(image->magick_filename,BasePath,filename); |
3014 | 0 | (void) strlcpy(attribute,filename,MaxTextExtent); |
3015 | 0 | break; |
3016 | 0 | } |
3017 | 0 | break; |
3018 | 0 | } |
3019 | 0 | case 's': |
3020 | 0 | { |
3021 | 0 | if (LocaleNCompare("size",key,2) == 0) |
3022 | 0 | { |
3023 | 0 | char |
3024 | 0 | format[MaxTextExtent]; |
3025 | |
|
3026 | 0 | FormatSize(GetBlobSize(image),format); |
3027 | 0 | FormatString(attribute,"%.1024s",format); |
3028 | 0 | break; |
3029 | 0 | } |
3030 | 0 | if (LocaleNCompare("scene",key,2) == 0) |
3031 | 0 | { |
3032 | 0 | FormatString(attribute,"%lu",image->scene); |
3033 | 0 | if (image_info->subrange != 0) |
3034 | 0 | FormatString(attribute,"%lu",image_info->subimage); |
3035 | 0 | break; |
3036 | 0 | } |
3037 | 0 | if (LocaleNCompare("scenes",key,6) == 0) |
3038 | 0 | { |
3039 | 0 | FormatString(attribute,"%lu", |
3040 | 0 | (unsigned long) GetImageListLength(image)); |
3041 | 0 | break; |
3042 | 0 | } |
3043 | 0 | break; |
3044 | 0 | } |
3045 | 0 | case 'o': |
3046 | 0 | { |
3047 | 0 | if (LocaleNCompare("output",key,2) == 0) |
3048 | 0 | { |
3049 | 0 | (void) strlcpy(attribute,image_info->filename,MaxTextExtent); |
3050 | 0 | break; |
3051 | 0 | } |
3052 | 0 | break; |
3053 | 0 | } |
3054 | 0 | case 'p': |
3055 | 0 | { |
3056 | 0 | if (LocaleNCompare("page",key,2) == 0) |
3057 | 0 | { |
3058 | 0 | register const Image |
3059 | 0 | *p; |
3060 | |
|
3061 | 0 | unsigned int |
3062 | 0 | page; |
3063 | |
|
3064 | 0 | p=image; |
3065 | 0 | for (page=1; p->previous != (Image *) NULL; page++) |
3066 | 0 | p=p->previous; |
3067 | 0 | FormatString(attribute,"%u",page); |
3068 | 0 | break; |
3069 | 0 | } |
3070 | 0 | break; |
3071 | 0 | } |
3072 | 0 | case 'u': |
3073 | 0 | { |
3074 | 0 | if (LocaleNCompare("unique",key,2) == 0) |
3075 | 0 | { |
3076 | 0 | (void) strlcpy(filename,image_info->unique,MaxTextExtent); |
3077 | 0 | if (*filename == '\0') |
3078 | 0 | if(!AcquireTemporaryFileName(filename)) |
3079 | 0 | return((ImageAttribute *) NULL); |
3080 | 0 | (void) strlcpy(attribute,filename,MaxTextExtent); |
3081 | 0 | break; |
3082 | 0 | } |
3083 | 0 | break; |
3084 | 0 | } |
3085 | 0 | case 'w': |
3086 | 0 | { |
3087 | 0 | if (LocaleNCompare("width",key,2) == 0) |
3088 | 0 | { |
3089 | 0 | FormatString(attribute,"%lu", |
3090 | 0 | image->magick_columns ? image->magick_columns : 256L); |
3091 | 0 | break; |
3092 | 0 | } |
3093 | 0 | break; |
3094 | 0 | } |
3095 | 0 | case 'x': |
3096 | 0 | { |
3097 | 0 | if (LocaleNCompare("xresolution",key,2) == 0) |
3098 | 0 | { |
3099 | 0 | FormatString(attribute,"%g",image->x_resolution); |
3100 | 0 | break; |
3101 | 0 | } |
3102 | 0 | break; |
3103 | 0 | } |
3104 | 0 | case 'y': |
3105 | 0 | { |
3106 | 0 | if (LocaleNCompare("yresolution",key,2) == 0) |
3107 | 0 | { |
3108 | 0 | FormatString(attribute,"%g",image->y_resolution); |
3109 | 0 | break; |
3110 | 0 | } |
3111 | 0 | break; |
3112 | 0 | } |
3113 | 0 | case 'z': |
3114 | 0 | { |
3115 | 0 | if (LocaleNCompare("zero",key,2) == 0) |
3116 | 0 | { |
3117 | 0 | (void) strlcpy(filename,image_info->zero,MaxTextExtent); |
3118 | 0 | if (*filename == '\0') |
3119 | 0 | if(!AcquireTemporaryFileName(filename)) |
3120 | 0 | return((ImageAttribute *) NULL); |
3121 | 0 | (void) strlcpy(attribute,filename,MaxTextExtent); |
3122 | 0 | break; |
3123 | 0 | } |
3124 | 0 | break; |
3125 | 0 | } |
3126 | 0 | } |
3127 | 0 | if (strlen(image->magick_filename) != 0) |
3128 | 0 | return(GetImageAttribute(image,key)); |
3129 | 0 | return((ImageAttribute *) NULL); |
3130 | 0 | } |
3131 | | |
3132 | | /* |
3133 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
3134 | | % % |
3135 | | % % |
3136 | | % % |
3137 | | % S e t I m a g e A t t r i b u t e % |
3138 | | % % |
3139 | | % % |
3140 | | % % |
3141 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
3142 | | % |
3143 | | % SetImageAttribute() searches the list of image attributes and replaces the |
3144 | | % attribute value. If it is not found in the list, the attribute name |
3145 | | % and value is added to the list. If the attribute exists in the list, |
3146 | | % the value is concatenated to the attribute. SetImageAttribute returns |
3147 | | % True if the attribute is successfully concatenated or added to the list, |
3148 | | % otherwise False. If the value is NULL, the matching key is deleted |
3149 | | % from the list. |
3150 | | % |
3151 | | % There is special handling for the EXIF:Orientation attribute. Setting this |
3152 | | % attribute will also update the EXIF tag in the image's EXIF profile to the |
3153 | | % given value provided an EXIF profile exists and has an existing EXIF |
3154 | | % orientation tag and the attribute value is a valid orientation |
3155 | | % (see orientationType). The attribute value will be set regardless of |
3156 | | % whether the EXIF profile was successfully updated. The new |
3157 | | % EXIF:Orientation attribute replaces the existing value rather than |
3158 | | % being concatenated to it as when setting other attributes. |
3159 | | % |
3160 | | % The 'comment' and 'label' attributes are treated specially in that |
3161 | | % embedded format specifications are translated according to the formatting |
3162 | | % rules of TranslateText(). |
3163 | | % |
3164 | | % The format of the SetImageAttribute method is: |
3165 | | % |
3166 | | % unsigned int SetImageAttribute(Image *image,const char *key, |
3167 | | % const char *value) |
3168 | | % |
3169 | | % A description of each parameter follows: |
3170 | | % |
3171 | | % o image: The image. |
3172 | | % |
3173 | | % o key,value: These character strings are the name and value of an image |
3174 | | % attribute to replace or add to the list. |
3175 | | % |
3176 | | % |
3177 | | */ |
3178 | | |
3179 | | /* |
3180 | | Find the location of an EXIF attribute in an EXIF profile. The EXIF attribute |
3181 | | to be found is specified as its numeric tag value (see tag_table). Returns |
3182 | | a pointer to the attribute in the EXIF profile or NULL if missing or there |
3183 | | is an error parsing the profile. Returns the EXIF profile byte order if the |
3184 | | morderp parameter is not NULL. |
3185 | | */ |
3186 | | |
3187 | | static unsigned char * |
3188 | | FindEXIFAttribute(const unsigned char *profile_info, |
3189 | | const size_t profile_length, |
3190 | | const unsigned short tag, int *morderp) |
3191 | 179 | { |
3192 | 179 | char |
3193 | 179 | tag_description[MaxTextExtent]; |
3194 | | |
3195 | 179 | int |
3196 | 179 | id, |
3197 | 179 | level, |
3198 | 179 | morder; |
3199 | | |
3200 | 179 | size_t |
3201 | 179 | length; |
3202 | | |
3203 | 179 | unsigned long |
3204 | 179 | offset; |
3205 | | |
3206 | 179 | unsigned char |
3207 | 179 | *tiffp, |
3208 | 179 | *tiffp_max, |
3209 | 179 | *ifdstack[DE_STACK_SIZE], |
3210 | 179 | *ifdp, |
3211 | 179 | *info, |
3212 | 179 | *attribp; |
3213 | | |
3214 | 179 | unsigned int |
3215 | 179 | de, |
3216 | 179 | destack[DE_STACK_SIZE], |
3217 | 179 | nde; |
3218 | | |
3219 | 179 | MagickBool |
3220 | 179 | gpsfoundstack[DE_STACK_SIZE], |
3221 | 179 | gpsfound; |
3222 | | |
3223 | 179 | MagickBool |
3224 | 179 | debug=MagickFalse; |
3225 | | |
3226 | 179 | attribp = (unsigned char *)NULL; |
3227 | | |
3228 | 179 | assert((ArraySize(format_bytes)-1) == EXIF_NUM_FORMATS); |
3229 | | |
3230 | 179 | { |
3231 | 179 | const char * |
3232 | 179 | env_value; |
3233 | | |
3234 | | /* |
3235 | | Allow enabling debug of EXIF tags |
3236 | | */ |
3237 | 179 | if ((env_value=getenv("MAGICK_DEBUG_EXIF"))) |
3238 | 0 | { |
3239 | 0 | if (LocaleCompare(env_value,"TRUE") == 0) |
3240 | 0 | debug=MagickTrue; |
3241 | 0 | } |
3242 | 179 | } |
3243 | 179 | gpsfound=MagickFalse; |
3244 | 179 | length=profile_length; |
3245 | 179 | info=(unsigned char *) profile_info; |
3246 | 179 | while (length != 0) |
3247 | 179 | { |
3248 | 179 | if (ReadByte(&info,&length) != 0x45) |
3249 | 0 | continue; |
3250 | 179 | if (ReadByte(&info,&length) != 0x78) |
3251 | 0 | continue; |
3252 | 179 | if (ReadByte(&info,&length) != 0x69) |
3253 | 0 | continue; |
3254 | 179 | if (ReadByte(&info,&length) != 0x66) |
3255 | 0 | continue; |
3256 | 179 | if (ReadByte(&info,&length) != 0x00) |
3257 | 0 | continue; |
3258 | 179 | if (ReadByte(&info,&length) != 0x00) |
3259 | 0 | continue; |
3260 | 179 | break; |
3261 | 179 | } |
3262 | 179 | if (length < 16) |
3263 | 0 | goto find_attribute_failure; |
3264 | 179 | tiffp=info; |
3265 | 179 | tiffp_max=tiffp+length; |
3266 | 179 | id=Read16u(0,tiffp); |
3267 | 179 | morder=0; |
3268 | 179 | if (id == 0x4949) /* LSB */ |
3269 | 171 | morder=0; |
3270 | 8 | else |
3271 | 8 | if (id == 0x4D4D) /* MSB */ |
3272 | 0 | morder=1; |
3273 | 8 | else |
3274 | 8 | goto find_attribute_failure; |
3275 | 171 | if (morderp) |
3276 | 171 | *morderp = morder; |
3277 | 171 | if (Read16u(morder,tiffp+2) != 0x002a) |
3278 | 0 | goto find_attribute_failure; |
3279 | | /* |
3280 | | This is the offset to the first IFD. |
3281 | | */ |
3282 | 171 | offset=Read32u(morder,tiffp+4); |
3283 | 171 | if (offset >= length) |
3284 | 0 | goto find_attribute_failure; |
3285 | | /* |
3286 | | Set the pointer to the first IFD and follow it were it leads. |
3287 | | */ |
3288 | 171 | ifdp=tiffp+offset; |
3289 | 171 | level=0; |
3290 | 171 | de=0U; |
3291 | 171 | do |
3292 | 375 | { |
3293 | | /* |
3294 | | If there is anything on the stack then pop it off. |
3295 | | */ |
3296 | 375 | if (level > 0) |
3297 | 204 | { |
3298 | 204 | level--; |
3299 | 204 | ifdp=ifdstack[level]; |
3300 | 204 | de=destack[level]; |
3301 | 204 | gpsfound=gpsfoundstack[level]; |
3302 | 204 | } |
3303 | | /* |
3304 | | Determine how many entries there are in the current IFD. |
3305 | | Limit the number of entries parsed to MAX_TAGS_PER_IFD. |
3306 | | */ |
3307 | 375 | if ((ifdp < tiffp) || (ifdp+2 > tiffp_max)) |
3308 | 0 | goto find_attribute_failure; |
3309 | 375 | nde=Read16u(morder,ifdp); |
3310 | 375 | if (nde > MAX_TAGS_PER_IFD) |
3311 | 43 | nde=MAX_TAGS_PER_IFD; |
3312 | 2.69k | for (; de < nde; de++) |
3313 | 2.59k | { |
3314 | 2.59k | size_t |
3315 | 2.59k | n; |
3316 | | |
3317 | 2.59k | unsigned int |
3318 | 2.59k | c, |
3319 | 2.59k | f, |
3320 | 2.59k | t; |
3321 | | |
3322 | 2.59k | unsigned char |
3323 | 2.59k | *pde, |
3324 | 2.59k | *pval; |
3325 | | |
3326 | | |
3327 | 2.59k | pde=(unsigned char *) (ifdp+2+(12*(size_t) de)); |
3328 | 2.59k | if (pde + 12 > tiffp + length) |
3329 | 9 | { |
3330 | 9 | if (debug) |
3331 | 0 | fprintf(stderr, "EXIF: Invalid Exif, entry is beyond metadata limit.\n"); |
3332 | 9 | goto find_attribute_failure; |
3333 | 9 | } |
3334 | 2.58k | t=Read16u(morder,pde); /* get tag value */ |
3335 | 2.58k | f=Read16u(morder,pde+2); /* get the format */ |
3336 | 2.58k | if ((size_t) f >= ArraySize(format_bytes)) |
3337 | 102 | break; |
3338 | 2.47k | c=Read32u(morder,pde+4); /* get number of components */ |
3339 | 2.47k | n=MagickArraySize(c,format_bytes[f]); |
3340 | 2.47k | if ((n == 0) && (c != 0) && (format_bytes[f] != 0)) |
3341 | 0 | { |
3342 | 0 | if (debug) |
3343 | 0 | fprintf(stderr, "EXIF: Invalid Exif, too many components (%u).\n",c); |
3344 | 0 | goto find_attribute_failure; |
3345 | 0 | } |
3346 | 2.47k | if (n <= 4) |
3347 | 1.44k | pval=(unsigned char *) pde+8; |
3348 | 1.03k | else |
3349 | 1.03k | { |
3350 | 1.03k | unsigned long |
3351 | 1.03k | oval; |
3352 | | |
3353 | | /* |
3354 | | The directory entry contains an offset. |
3355 | | */ |
3356 | 1.03k | oval=Read32u(morder,pde+8); |
3357 | 1.03k | if ((oval+n) > length) |
3358 | 77 | continue; |
3359 | 962 | pval=(unsigned char *)(tiffp+oval); |
3360 | 962 | } |
3361 | | |
3362 | 2.40k | if (debug) |
3363 | 0 | { |
3364 | 0 | fprintf(stderr, |
3365 | 0 | "EXIF: TagVal=%d TagDescr=\"%s\" Format=%d " |
3366 | 0 | "FormatDescr=\"%s\" Components=%u\n",t, |
3367 | 0 | EXIFIfdTagToDescription(t,tag_description),f, |
3368 | 0 | EXIFIfdFieldTypeToStr(f),c); |
3369 | 0 | } |
3370 | | |
3371 | 2.40k | if (gpsfound) |
3372 | 204 | { |
3373 | 204 | if (/* (t < GPS_TAG_START) || */ (t > GPS_TAG_STOP)) |
3374 | 70 | { |
3375 | 70 | if (debug) |
3376 | 0 | fprintf(stderr, |
3377 | 0 | "EXIF: Skipping bogus GPS IFD tag %d (0x%04X) ...\n",t,t); |
3378 | 70 | continue; |
3379 | 70 | } |
3380 | 204 | } |
3381 | 2.19k | else |
3382 | 2.19k | { |
3383 | 2.19k | if ((t < EXIF_TAG_START) || ( t > EXIF_TAG_STOP)) |
3384 | 322 | { |
3385 | 322 | if (debug) |
3386 | 0 | fprintf(stderr, |
3387 | 0 | "EXIF: Skipping bogus EXIF IFD tag %d (0x%04X) ...\n",t,t); |
3388 | 322 | continue; |
3389 | 322 | } |
3390 | 2.19k | } |
3391 | | |
3392 | | /* |
3393 | | Return values for all the tags, or for a specific requested tag. |
3394 | | |
3395 | | Tags from the GPS sub-IFD are in a bit of a chicken and |
3396 | | egg situation in that the tag for the GPS sub-IFD will not |
3397 | | be seen unless we pass that tag through so it can be |
3398 | | processed. So we pass the GPS_OFFSET tag through, but if |
3399 | | it was not requested, then we don't return a string value |
3400 | | for it. |
3401 | | */ |
3402 | 2.01k | if (tag == t) |
3403 | 57 | { |
3404 | 57 | attribp = pde; |
3405 | 57 | break; |
3406 | 57 | } |
3407 | | |
3408 | 1.95k | if (t == GPS_OFFSET) |
3409 | 51 | { |
3410 | 51 | offset=Read32u(morder,pval); |
3411 | 51 | if ((offset < length) && (level < (DE_STACK_SIZE-2))) |
3412 | 49 | { |
3413 | | /* |
3414 | | Push our current directory state onto the stack. |
3415 | | */ |
3416 | 49 | ifdstack[level]=ifdp; |
3417 | 49 | de++; /* bump to the next entry */ |
3418 | 49 | destack[level]=de; |
3419 | 49 | gpsfoundstack[level]=gpsfound; |
3420 | 49 | level++; |
3421 | | /* |
3422 | | Push new state onto of stack to cause a jump. |
3423 | | */ |
3424 | 49 | ifdstack[level]=tiffp+offset; |
3425 | 49 | destack[level]=0; |
3426 | 49 | gpsfoundstack[level]=MagickTrue; |
3427 | 49 | level++; |
3428 | 49 | } |
3429 | 51 | break; /* break out of the for loop */ |
3430 | 51 | } |
3431 | | |
3432 | 1.90k | if ((t == TAG_EXIF_OFFSET) || (t == TAG_INTEROP_OFFSET)) |
3433 | 56 | { |
3434 | 56 | offset=Read32u(morder,pval); |
3435 | 56 | if ((offset < length) && (level < (DE_STACK_SIZE-2))) |
3436 | 54 | { |
3437 | | /* |
3438 | | Push our current directory state onto the stack. |
3439 | | */ |
3440 | 54 | ifdstack[level]=ifdp; |
3441 | 54 | de++; /* bump to the next entry */ |
3442 | 54 | destack[level]=de; |
3443 | 54 | gpsfoundstack[level]=gpsfound; |
3444 | 54 | level++; |
3445 | | /* |
3446 | | Push new state onto of stack to cause a jump. |
3447 | | */ |
3448 | 54 | ifdstack[level]=tiffp+offset; |
3449 | 54 | destack[level]=0; |
3450 | 54 | gpsfoundstack[level]=MagickFalse; |
3451 | 54 | level++; |
3452 | 54 | } |
3453 | 56 | break; /* break out of the for loop */ |
3454 | 56 | } |
3455 | 1.90k | } |
3456 | 375 | } while (!attribp && (level > 0)); |
3457 | 162 | return attribp; |
3458 | 17 | find_attribute_failure: |
3459 | 17 | return (unsigned char *)NULL; |
3460 | 171 | } |
3461 | | |
3462 | | /* |
3463 | | SetEXIFOrientation() updates the EXIF orientation tag in the image's EXIF |
3464 | | profile to the value provided. Returns MagickPass on success or MagickFail |
3465 | | if either there is no EXIF profile in the image, there was an error parsing |
3466 | | the EXIF profile, there was no existing EXIF orientation attribute in |
3467 | | the EXIF profile or there was a memory allocation error. |
3468 | | */ |
3469 | | |
3470 | | static MagickPassFail |
3471 | | SetEXIFOrientation(Image *image, const int orientation) |
3472 | 179 | { |
3473 | 179 | MagickPassFail |
3474 | 179 | result; |
3475 | | |
3476 | 179 | const unsigned char |
3477 | 179 | *current_profile; |
3478 | | |
3479 | 179 | size_t |
3480 | 179 | profile_length; |
3481 | | |
3482 | 179 | unsigned char |
3483 | 179 | *orientp, |
3484 | 179 | *pval, |
3485 | 179 | *new_profile; |
3486 | | |
3487 | 179 | int |
3488 | 179 | morder, |
3489 | 179 | current_orientation; |
3490 | | |
3491 | 179 | unsigned short |
3492 | 179 | f; |
3493 | | |
3494 | 179 | unsigned long |
3495 | 179 | c; |
3496 | | |
3497 | 179 | if (orientation < TopLeftOrientation || orientation > LeftBottomOrientation) |
3498 | 0 | return(MagickFail); |
3499 | | |
3500 | 179 | current_profile=GetImageProfile(image,"EXIF",&profile_length); |
3501 | 179 | if (current_profile == 0) |
3502 | 0 | return(MagickFail); |
3503 | | |
3504 | | /* Clone profile so orientation can be set */ |
3505 | 179 | new_profile = MagickAllocateMemory(unsigned char *,profile_length); |
3506 | 179 | if (new_profile == 0) |
3507 | 0 | return(MagickFail); |
3508 | | |
3509 | 179 | result = MagickFail; |
3510 | 179 | memcpy(new_profile, current_profile, profile_length); |
3511 | 179 | orientp = FindEXIFAttribute(new_profile, profile_length, |
3512 | 179 | (unsigned short)EXIF_ORIENTATION, &morder); |
3513 | 179 | if (orientp) |
3514 | 57 | { |
3515 | | /* Make sure EXIF orientation attribute is valid */ |
3516 | 57 | f=Read16u(morder,orientp+2); /* get the format */ |
3517 | 57 | c=Read32u(morder,orientp+4); /* get number of components */ |
3518 | 57 | if ((f == EXIF_FMT_USHORT) && (c == 1)) |
3519 | 39 | { |
3520 | 39 | pval=(unsigned char *) orientp+8; |
3521 | 39 | current_orientation=(int)Read16u(morder, pval); |
3522 | 39 | if (current_orientation != (unsigned short)orientation) |
3523 | 6 | { |
3524 | 6 | Write16u(morder, pval, orientation); |
3525 | 6 | Write16u(morder, pval+2, 0); |
3526 | 6 | result=SetImageProfile(image,"EXIF",new_profile,profile_length); |
3527 | 6 | } |
3528 | 33 | else |
3529 | 33 | result = MagickPass; |
3530 | 39 | } |
3531 | 57 | } |
3532 | 179 | MagickFreeMemory(new_profile); |
3533 | | |
3534 | 179 | return result; |
3535 | 179 | } |
3536 | | |
3537 | | MagickExport MagickPassFail |
3538 | | SetImageAttribute(Image *image,const char *key,const char *value) |
3539 | 7.02M | { |
3540 | 7.02M | ImageAttribute |
3541 | 7.02M | *attribute; |
3542 | | |
3543 | 7.02M | register ImageAttribute |
3544 | 7.02M | *p; |
3545 | | |
3546 | | /* |
3547 | | Initialize new attribute. |
3548 | | */ |
3549 | 7.02M | assert(image != (Image *) NULL); |
3550 | 7.02M | assert(image->signature == MagickSignature); |
3551 | 7.02M | if ((key == (const char *) NULL) || (*key == '\0')) |
3552 | 2.43k | return(MagickFail); |
3553 | | |
3554 | 7.01M | if (value == (const char *) NULL) |
3555 | 2.25M | { |
3556 | | /* |
3557 | | Delete attribute from the image attributes list. |
3558 | | */ |
3559 | 3.66M | for (p=image->attributes; p != (ImageAttribute *) NULL; p=p->next) |
3560 | 3.49M | if (LocaleCompare(key,p->key) == 0) |
3561 | 2.09M | break; |
3562 | 2.25M | if (p == (ImageAttribute *) NULL) |
3563 | 161k | return(False); |
3564 | 2.09M | if (p->previous != (ImageAttribute *) NULL) |
3565 | 193k | p->previous->next=p->next; |
3566 | 1.89M | else |
3567 | 1.89M | { |
3568 | 1.89M | image->attributes=p->next; |
3569 | 1.89M | if (p->next != (ImageAttribute *) NULL) |
3570 | 13.7k | p->next->previous=(ImageAttribute *) NULL; |
3571 | 1.89M | } |
3572 | 2.09M | if (p->next != (ImageAttribute *) NULL) |
3573 | 109k | p->next->previous=p->previous; |
3574 | 2.09M | attribute=p; |
3575 | 2.09M | DestroyImageAttribute(attribute); |
3576 | 2.09M | return(MagickPass); |
3577 | 2.25M | } |
3578 | 4.76M | attribute=MagickAllocateMemory(ImageAttribute *,sizeof(ImageAttribute)); |
3579 | 4.76M | if (attribute == (ImageAttribute *) NULL) |
3580 | 0 | return(MagickFail); |
3581 | 4.76M | attribute->key=AllocateString(key); |
3582 | 4.76M | attribute->length=strlen(value); |
3583 | 4.76M | attribute->value=MagickAllocateMemory(char *,attribute->length+1); |
3584 | 4.76M | if (attribute->value != (char *) NULL) |
3585 | 4.76M | (void) strlcpy(attribute->value,value,attribute->length+1); |
3586 | 4.76M | if ((attribute->value == (char *) NULL) || |
3587 | 4.76M | (attribute->key == (char *) NULL)) |
3588 | 0 | { |
3589 | 0 | DestroyImageAttribute(attribute); |
3590 | 0 | return(MagickFail); |
3591 | 0 | } |
3592 | | |
3593 | 4.76M | attribute->previous=(ImageAttribute *) NULL; |
3594 | 4.76M | attribute->next=(ImageAttribute *) NULL; |
3595 | 4.76M | if (image->attributes == (ImageAttribute *) NULL) |
3596 | 2.42M | { |
3597 | 2.42M | image->attributes=attribute; |
3598 | 2.42M | return(MagickPass); |
3599 | 2.42M | } |
3600 | 15.8M | for (p=image->attributes; p != (ImageAttribute *) NULL; p=p->next) |
3601 | 15.8M | { |
3602 | 15.8M | if (LocaleCompare(attribute->key,p->key) == 0) |
3603 | 593k | { |
3604 | 593k | size_t |
3605 | 593k | min_l, |
3606 | 593k | realloc_l; |
3607 | | |
3608 | 593k | if (LocaleCompare(attribute->key,"EXIF:Orientation") == 0) |
3609 | 179 | { |
3610 | 179 | int |
3611 | 179 | orientation = 0; |
3612 | | |
3613 | | /* |
3614 | | Special handling for EXIF orientation tag. |
3615 | | If new value differs from existing value, |
3616 | | EXIF profile is updated as well if it exists and |
3617 | | is valid. Don't append new value to existing value, |
3618 | | replace it instead. |
3619 | | */ |
3620 | 179 | if ((MagickAtoIChk(value, &orientation) == MagickPass) && |
3621 | 179 | (orientation > 0 || orientation <= (int)LeftBottomOrientation)) |
3622 | 179 | { |
3623 | 179 | SetEXIFOrientation(image, orientation); |
3624 | 179 | } |
3625 | | /* Assign changed value to attribute in list */ |
3626 | 179 | if (LocaleCompare(p->value, attribute->value) != 0) |
3627 | 141 | { |
3628 | 141 | MagickFreeMemory(p->value); |
3629 | 141 | p->value=attribute->value; |
3630 | 141 | attribute->value = (char *) NULL; |
3631 | 141 | } |
3632 | 179 | DestroyImageAttribute(attribute); |
3633 | 179 | return(MagickPass); |
3634 | 179 | } |
3635 | 593k | else |
3636 | 593k | { |
3637 | | /* |
3638 | | Extend existing text string. |
3639 | | */ |
3640 | 593k | min_l=p->length+attribute->length+1; |
3641 | 6.61M | for (realloc_l=2; realloc_l <= min_l; realloc_l *= 2) |
3642 | 6.02M | { /* nada */}; |
3643 | 593k | MagickReallocMemory(char *,p->value,realloc_l); |
3644 | 593k | if (p->value != (char *) NULL) |
3645 | 593k | { |
3646 | 593k | (void) memcpy(p->value+p->length,attribute->value,min_l-p->length-1); |
3647 | 593k | p->length += attribute->length; |
3648 | 593k | p->value[p->length] = '\0'; |
3649 | 593k | } |
3650 | 593k | DestroyImageAttribute(attribute); |
3651 | 593k | } |
3652 | 593k | if (p->value != (char *) NULL) |
3653 | 593k | return(MagickPass); |
3654 | 0 | (void) SetImageAttribute(image,key,NULL); |
3655 | 0 | return(MagickFail); |
3656 | 593k | } |
3657 | 15.2M | if (p->next == (ImageAttribute *) NULL) |
3658 | 1.74M | break; |
3659 | 15.2M | } |
3660 | | /* |
3661 | | Place new attribute at the end of the attribute list. |
3662 | | */ |
3663 | 1.74M | attribute->previous=p; |
3664 | 1.74M | p->next=attribute; |
3665 | 1.74M | return(MagickPass); |
3666 | 2.33M | } |