1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 % %
4 % %
5 % %
6 % PPPP RRRR OOO PPPP EEEEE RRRR TTTTT Y Y %
7 % P P R R O O P P E R R T Y Y %
8 % PPPP RRRR O O PPPP EEE RRRR T Y %
9 % P R R O O P E R R T Y %
10 % P R R OOO P EEEEE R R T Y %
11 % %
12 % %
13 % MagickCore Property Methods %
14 % %
15 % Software Design %
16 % Cristy %
17 % March 2000 %
18 % %
19 % %
20 % Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
22 % %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
25 % %
26 % https://imagemagick.org/script/license.php %
27 % %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
33 % %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35 %
36 %
37 %
38 */
39
40 /*
41 Include declarations.
42 */
43 #include "magick/studio.h"
44 #include "magick/artifact.h"
45 #include "magick/attribute.h"
46 #include "magick/cache.h"
47 #include "magick/cache-private.h"
48 #include "magick/color.h"
49 #include "magick/colorspace-private.h"
50 #include "magick/compare.h"
51 #include "magick/constitute.h"
52 #include "magick/draw.h"
53 #include "magick/effect.h"
54 #include "magick/exception.h"
55 #include "magick/exception-private.h"
56 #include "magick/fx.h"
57 #include "magick/fx-private.h"
58 #include "magick/gem.h"
59 #include "magick/geometry.h"
60 #include "magick/histogram.h"
61 #include "magick/image.h"
62 #include "magick/image.h"
63 #include "magick/layer.h"
64 #include "magick/list.h"
65 #include "magick/magick.h"
66 #include "magick/memory_.h"
67 #include "magick/monitor.h"
68 #include "magick/montage.h"
69 #include "magick/option.h"
70 #include "magick/policy.h"
71 #include "magick/profile.h"
72 #include "magick/property.h"
73 #include "magick/quantum.h"
74 #include "magick/resource_.h"
75 #include "magick/splay-tree.h"
76 #include "magick/signature-private.h"
77 #include "magick/statistic.h"
78 #include "magick/string_.h"
79 #include "magick/string-private.h"
80 #include "magick/token.h"
81 #include "magick/utility.h"
82 #include "magick/version.h"
83 #include "magick/xml-tree.h"
84 #if defined(MAGICKCORE_LCMS_DELEGATE)
85 #if defined(MAGICKCORE_HAVE_LCMS2_LCMS2_H)
86 #include <lcms2/lcms2.h>
87 #elif defined(MAGICKCORE_HAVE_LCMS2_H)
88 #include "lcms2.h"
89 #elif defined(MAGICKCORE_HAVE_LCMS_LCMS_H)
90 #include <lcms/lcms.h>
91 #else
92 #include "lcms.h"
93 #endif
94 #endif
95
96 /*
97 Define declarations.
98 */
99 #if defined(MAGICKCORE_LCMS_DELEGATE)
100 #if defined(LCMS_VERSION) && (LCMS_VERSION < 2000)
101 #define cmsUInt32Number DWORD
102 #endif
103 #endif
104
105 /*
106 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
107 % %
108 % %
109 % %
110 % C l o n e I m a g e P r o p e r t i e s %
111 % %
112 % %
113 % %
114 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
115 %
116 % CloneImageProperties() clones all the image properties to another image.
117 %
118 % The format of the CloneImageProperties method is:
119 %
120 % MagickBooleanType CloneImageProperties(Image *image,
121 % const Image *clone_image)
122 %
123 % A description of each parameter follows:
124 %
125 % o image: the image.
126 %
127 % o clone_image: the clone image.
128 %
129 */
CloneImageProperties(Image * image,const Image * clone_image)130 MagickExport MagickBooleanType CloneImageProperties(Image *image,
131 const Image *clone_image)
132 {
133 assert(image != (Image *) NULL);
134 assert(image->signature == MagickCoreSignature);
135 if (image->debug != MagickFalse)
136 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
137 assert(clone_image != (const Image *) NULL);
138 assert(clone_image->signature == MagickCoreSignature);
139 if (clone_image->debug != MagickFalse)
140 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
141 clone_image->filename);
142 (void) CopyMagickString(image->filename,clone_image->filename,MaxTextExtent);
143 (void) CopyMagickString(image->magick_filename,clone_image->magick_filename,
144 MaxTextExtent);
145 image->compression=clone_image->compression;
146 image->quality=clone_image->quality;
147 image->depth=clone_image->depth;
148 image->background_color=clone_image->background_color;
149 image->border_color=clone_image->border_color;
150 image->matte_color=clone_image->matte_color;
151 image->transparent_color=clone_image->transparent_color;
152 image->gamma=clone_image->gamma;
153 image->chromaticity=clone_image->chromaticity;
154 image->rendering_intent=clone_image->rendering_intent;
155 image->black_point_compensation=clone_image->black_point_compensation;
156 image->units=clone_image->units;
157 image->montage=(char *) NULL;
158 image->directory=(char *) NULL;
159 (void) CloneString(&image->geometry,clone_image->geometry);
160 image->offset=clone_image->offset;
161 image->x_resolution=clone_image->x_resolution;
162 image->y_resolution=clone_image->y_resolution;
163 image->page=clone_image->page;
164 image->tile_offset=clone_image->tile_offset;
165 image->extract_info=clone_image->extract_info;
166 image->bias=clone_image->bias;
167 image->filter=clone_image->filter;
168 image->blur=clone_image->blur;
169 image->fuzz=clone_image->fuzz;
170 image->intensity=clone_image->intensity;
171 image->interlace=clone_image->interlace;
172 image->interpolate=clone_image->interpolate;
173 image->endian=clone_image->endian;
174 image->gravity=clone_image->gravity;
175 image->compose=clone_image->compose;
176 image->orientation=clone_image->orientation;
177 image->scene=clone_image->scene;
178 image->dispose=clone_image->dispose;
179 image->delay=clone_image->delay;
180 image->ticks_per_second=clone_image->ticks_per_second;
181 image->iterations=clone_image->iterations;
182 image->total_colors=clone_image->total_colors;
183 image->taint=clone_image->taint;
184 image->progress_monitor=clone_image->progress_monitor;
185 image->client_data=clone_image->client_data;
186 image->start_loop=clone_image->start_loop;
187 image->error=clone_image->error;
188 image->signature=clone_image->signature;
189 if (clone_image->properties != (void *) NULL)
190 {
191 if (image->properties != (void *) NULL)
192 DestroyImageProperties(image);
193 image->properties=CloneSplayTree((SplayTreeInfo *)
194 clone_image->properties,(void *(*)(void *)) ConstantString,
195 (void *(*)(void *)) ConstantString);
196 }
197 return(MagickTrue);
198 }
199
200 /*
201 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
202 % %
203 % %
204 % %
205 % D e f i n e I m a g e P r o p e r t y %
206 % %
207 % %
208 % %
209 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
210 %
211 % DefineImageProperty() associates an assignment string of the form
212 % "key=value" with an artifact or options. It is equivelent to
213 % SetImageProperty().
214 %
215 % The format of the DefineImageProperty method is:
216 %
217 % MagickBooleanType DefineImageProperty(Image *image,
218 % const char *property)
219 %
220 % A description of each parameter follows:
221 %
222 % o image: the image.
223 %
224 % o property: the image property.
225 %
226 */
DefineImageProperty(Image * image,const char * property)227 MagickExport MagickBooleanType DefineImageProperty(Image *image,
228 const char *property)
229 {
230 char
231 key[MaxTextExtent],
232 value[MaxTextExtent];
233
234 char
235 *p;
236
237 assert(image != (Image *) NULL);
238 assert(property != (const char *) NULL);
239 (void) CopyMagickString(key,property,MaxTextExtent-1);
240 for (p=key; *p != '\0'; p++)
241 if (*p == '=')
242 break;
243 *value='\0';
244 if (*p == '=')
245 (void) CopyMagickString(value,p+1,MaxTextExtent);
246 *p='\0';
247 return(SetImageProperty(image,key,value));
248 }
249
250 /*
251 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
252 % %
253 % %
254 % %
255 % D e l e t e I m a g e P r o p e r t y %
256 % %
257 % %
258 % %
259 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
260 %
261 % DeleteImageProperty() deletes an image property.
262 %
263 % The format of the DeleteImageProperty method is:
264 %
265 % MagickBooleanType DeleteImageProperty(Image *image,const char *property)
266 %
267 % A description of each parameter follows:
268 %
269 % o image: the image.
270 %
271 % o property: the image property.
272 %
273 */
DeleteImageProperty(Image * image,const char * property)274 MagickExport MagickBooleanType DeleteImageProperty(Image *image,
275 const char *property)
276 {
277 assert(image != (Image *) NULL);
278 assert(image->signature == MagickCoreSignature);
279 if (image->debug != MagickFalse)
280 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
281 if (image->properties == (void *) NULL)
282 return(MagickFalse);
283 return(DeleteNodeFromSplayTree((SplayTreeInfo *) image->properties,property));
284 }
285
286 /*
287 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
288 % %
289 % %
290 % %
291 % D e s t r o y I m a g e P r o p e r t i e s %
292 % %
293 % %
294 % %
295 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
296 %
297 % DestroyImageProperties() destroys all properties and associated memory
298 % attached to the given image.
299 %
300 % The format of the DestroyDefines method is:
301 %
302 % void DestroyImageProperties(Image *image)
303 %
304 % A description of each parameter follows:
305 %
306 % o image: the image.
307 %
308 */
DestroyImageProperties(Image * image)309 MagickExport void DestroyImageProperties(Image *image)
310 {
311 assert(image != (Image *) NULL);
312 assert(image->signature == MagickCoreSignature);
313 if (image->debug != MagickFalse)
314 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
315 if (image->properties != (void *) NULL)
316 image->properties=(void *) DestroySplayTree((SplayTreeInfo *)
317 image->properties);
318 }
319
320 /*
321 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
322 % %
323 % %
324 % %
325 % F o r m a t I m a g e P r o p e r t y %
326 % %
327 % %
328 % %
329 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
330 %
331 % FormatImageProperty() permits formatted property/value pairs to be saved as
332 % an image property.
333 %
334 % The format of the FormatImageProperty method is:
335 %
336 % MagickBooleanType FormatImageProperty(Image *image,const char *property,
337 % const char *format,...)
338 %
339 % A description of each parameter follows.
340 %
341 % o image: The image.
342 %
343 % o property: The attribute property.
344 %
345 % o format: A string describing the format to use to write the remaining
346 % arguments.
347 %
348 */
FormatImageProperty(Image * image,const char * property,const char * format,...)349 MagickExport MagickBooleanType FormatImageProperty(Image *image,
350 const char *property,const char *format,...)
351 {
352 char
353 value[MaxTextExtent];
354
355 ssize_t
356 n;
357
358 va_list
359 operands;
360
361 va_start(operands,format);
362 n=FormatLocaleStringList(value,MaxTextExtent,format,operands);
363 (void) n;
364 va_end(operands);
365 return(SetImageProperty(image,property,value));
366 }
367
368 /*
369 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
370 % %
371 % %
372 % %
373 % G e t I m a g e P r o p e r t y %
374 % %
375 % %
376 % %
377 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
378 %
379 % GetImageProperty() gets a value associated with an image property.
380 %
381 % This includes, profile prefixes, such as "exif:", "iptc:" and "8bim:"
382 % It does not handle non-prifile prefixes, such as "fx:", "option:", or
383 % "artifact:".
384 %
385 % The returned string is stored as a properity of the same name for faster
386 % lookup later. It should NOT be freed by the caller.
387 %
388 % The format of the GetImageProperty method is:
389 %
390 % const char *GetImageProperty(const Image *image,const char *key)
391 %
392 % A description of each parameter follows:
393 %
394 % o image: the image.
395 %
396 % o key: the key.
397 %
398 */
399
400 static char
401 *TracePSClippath(const unsigned char *,size_t,const size_t,
402 const size_t),
403 *TraceSVGClippath(const unsigned char *,size_t,const size_t,
404 const size_t);
405
GetIPTCProperty(const Image * image,const char * key)406 static MagickBooleanType GetIPTCProperty(const Image *image,const char *key)
407 {
408 char
409 *attribute,
410 *message;
411
412 const StringInfo
413 *profile;
414
415 long
416 count,
417 dataset,
418 record;
419
420 ssize_t
421 i;
422
423 size_t
424 length;
425
426 profile=GetImageProfile(image,"iptc");
427 if (profile == (StringInfo *) NULL)
428 profile=GetImageProfile(image,"8bim");
429 if (profile == (StringInfo *) NULL)
430 return(MagickFalse);
431 count=sscanf(key,"IPTC:%ld:%ld",&dataset,&record);
432 if (count != 2)
433 return(MagickFalse);
434 attribute=(char *) NULL;
435 for (i=0; i < (ssize_t) GetStringInfoLength(profile); i+=(ssize_t) length)
436 {
437 length=1;
438 if ((ssize_t) GetStringInfoDatum(profile)[i] != 0x1c)
439 continue;
440 length=(size_t) (GetStringInfoDatum(profile)[i+3] << 8);
441 length|=GetStringInfoDatum(profile)[i+4];
442 if (((long) GetStringInfoDatum(profile)[i+1] == dataset) &&
443 ((long) GetStringInfoDatum(profile)[i+2] == record))
444 {
445 message=(char *) NULL;
446 if (~length >= 1)
447 message=(char *) AcquireQuantumMemory(length+1UL,sizeof(*message));
448 if (message != (char *) NULL)
449 {
450 (void) CopyMagickString(message,(char *) GetStringInfoDatum(
451 profile)+i+5,length+1);
452 (void) ConcatenateString(&attribute,message);
453 (void) ConcatenateString(&attribute,";");
454 message=DestroyString(message);
455 }
456 }
457 i+=5;
458 }
459 if ((attribute == (char *) NULL) || (*attribute == ';'))
460 {
461 if (attribute != (char *) NULL)
462 attribute=DestroyString(attribute);
463 return(MagickFalse);
464 }
465 attribute[strlen(attribute)-1]='\0';
466 (void) SetImageProperty((Image *) image,key,(const char *) attribute);
467 attribute=DestroyString(attribute);
468 return(MagickTrue);
469 }
470
ReadPropertyByte(const unsigned char ** p,size_t * length)471 static inline int ReadPropertyByte(const unsigned char **p,size_t *length)
472 {
473 int
474 c;
475
476 if (*length < 1)
477 return(EOF);
478 c=(int) (*(*p)++);
479 (*length)--;
480 return(c);
481 }
482
ReadPropertyMSBLong(const unsigned char ** p,size_t * length)483 static inline signed int ReadPropertyMSBLong(const unsigned char **p,
484 size_t *length)
485 {
486 union
487 {
488 unsigned int
489 unsigned_value;
490
491 signed int
492 signed_value;
493 } quantum;
494
495 int
496 c;
497
498 ssize_t
499 i;
500
501 unsigned char
502 buffer[4];
503
504 unsigned int
505 value;
506
507 if (*length < 4)
508 return(-1);
509 for (i=0; i < 4; i++)
510 {
511 c=(int) (*(*p)++);
512 (*length)--;
513 buffer[i]=(unsigned char) c;
514 }
515 value=(unsigned int) buffer[0] << 24;
516 value|=(unsigned int) buffer[1] << 16;
517 value|=(unsigned int) buffer[2] << 8;
518 value|=(unsigned int) buffer[3];
519 quantum.unsigned_value=value & 0xffffffff;
520 return(quantum.signed_value);
521 }
522
ReadPropertyMSBShort(const unsigned char ** p,size_t * length)523 static inline signed short ReadPropertyMSBShort(const unsigned char **p,
524 size_t *length)
525 {
526 union
527 {
528 unsigned short
529 unsigned_value;
530
531 signed short
532 signed_value;
533 } quantum;
534
535 int
536 c;
537
538 ssize_t
539 i;
540
541 unsigned char
542 buffer[2];
543
544 unsigned short
545 value;
546
547 if (*length < 2)
548 return((unsigned short) ~0);
549 for (i=0; i < 2; i++)
550 {
551 c=(int) (*(*p)++);
552 (*length)--;
553 buffer[i]=(unsigned char) c;
554 }
555 value=(unsigned short) buffer[0] << 8;
556 value|=(unsigned short) buffer[1];
557 quantum.unsigned_value=value & 0xffff;
558 return(quantum.signed_value);
559 }
560
Get8BIMProperty(const Image * image,const char * key)561 static MagickBooleanType Get8BIMProperty(const Image *image,const char *key)
562 {
563 char
564 *attribute,
565 format[MaxTextExtent],
566 name[MaxTextExtent],
567 *resource;
568
569 const StringInfo
570 *profile;
571
572 const unsigned char
573 *info;
574
575 long
576 start,
577 stop;
578
579 MagickBooleanType
580 status;
581
582 ssize_t
583 i;
584
585 size_t
586 length;
587
588 ssize_t
589 count,
590 id,
591 sub_number;
592
593 /*
594 There are no newlines in path names, so it's safe as terminator.
595 */
596 profile=GetImageProfile(image,"8bim");
597 if (profile == (StringInfo *) NULL)
598 return(MagickFalse);
599 count=(ssize_t) sscanf(key,"8BIM:%ld,%ld:%1024[^\n]\n%1024[^\n]",&start,&stop,
600 name,format);
601 if ((count != 2) && (count != 3) && (count != 4))
602 return(MagickFalse);
603 if (count < 4)
604 (void) CopyMagickString(format,"SVG",MaxTextExtent);
605 if (count < 3)
606 *name='\0';
607 sub_number=1;
608 if (*name == '#')
609 sub_number=(ssize_t) StringToLong(&name[1]);
610 sub_number=MagickMax(sub_number,1L);
611 resource=(char *) NULL;
612 status=MagickFalse;
613 length=GetStringInfoLength(profile);
614 info=GetStringInfoDatum(profile);
615 while ((length > 0) && (status == MagickFalse))
616 {
617 if (ReadPropertyByte(&info,&length) != (unsigned char) '8')
618 continue;
619 if (ReadPropertyByte(&info,&length) != (unsigned char) 'B')
620 continue;
621 if (ReadPropertyByte(&info,&length) != (unsigned char) 'I')
622 continue;
623 if (ReadPropertyByte(&info,&length) != (unsigned char) 'M')
624 continue;
625 id=(ssize_t) ReadPropertyMSBShort(&info,&length);
626 if (id < (ssize_t) start)
627 continue;
628 if (id > (ssize_t) stop)
629 continue;
630 if (resource != (char *) NULL)
631 resource=DestroyString(resource);
632 count=(ssize_t) ReadPropertyByte(&info,&length);
633 if ((count != 0) && ((size_t) count <= length))
634 {
635 resource=(char *) NULL;
636 if (~((size_t) count) >= (MaxTextExtent-1))
637 resource=(char *) AcquireQuantumMemory((size_t) count+MaxTextExtent,
638 sizeof(*resource));
639 if (resource != (char *) NULL)
640 {
641 for (i=0; i < (ssize_t) count; i++)
642 resource[i]=(char) ReadPropertyByte(&info,&length);
643 resource[count]='\0';
644 }
645 }
646 if ((count & 0x01) == 0)
647 (void) ReadPropertyByte(&info,&length);
648 count=(ssize_t) ReadPropertyMSBLong(&info,&length);
649 if ((count < 0) || ((size_t) count > length))
650 {
651 length=0;
652 continue;
653 }
654 if ((*name != '\0') && (*name != '#'))
655 if ((resource == (char *) NULL) || (LocaleCompare(name,resource) != 0))
656 {
657 /*
658 No name match, scroll forward and try next.
659 */
660 info+=count;
661 length-=MagickMin(count,(ssize_t) length);
662 continue;
663 }
664 if ((*name == '#') && (sub_number != 1))
665 {
666 /*
667 No numbered match, scroll forward and try next.
668 */
669 sub_number--;
670 info+=count;
671 length-=MagickMin(count,(ssize_t) length);
672 continue;
673 }
674 /*
675 We have the resource of interest.
676 */
677 attribute=(char *) NULL;
678 if (~((size_t) count) >= (MaxTextExtent-1))
679 attribute=(char *) AcquireQuantumMemory((size_t) count+MaxTextExtent,
680 sizeof(*attribute));
681 if (attribute != (char *) NULL)
682 {
683 (void) memcpy(attribute,(char *) info,(size_t) count);
684 attribute[count]='\0';
685 info+=count;
686 length-=MagickMin(count,(ssize_t) length);
687 if ((id <= 1999) || (id >= 2999))
688 (void) SetImageProperty((Image *) image,key,(const char *) attribute);
689 else
690 {
691 char
692 *path;
693
694 if (LocaleCompare(format,"svg") == 0)
695 path=TraceSVGClippath((unsigned char *) attribute,(size_t) count,
696 image->columns,image->rows);
697 else
698 path=TracePSClippath((unsigned char *) attribute,(size_t) count,
699 image->columns,image->rows);
700 (void) SetImageProperty((Image *) image,key,(const char *) path);
701 path=DestroyString(path);
702 }
703 attribute=DestroyString(attribute);
704 status=MagickTrue;
705 }
706 }
707 if (resource != (char *) NULL)
708 resource=DestroyString(resource);
709 return(status);
710 }
711
ReadPropertySignedLong(const EndianType endian,const unsigned char * buffer)712 static inline signed int ReadPropertySignedLong(const EndianType endian,
713 const unsigned char *buffer)
714 {
715 union
716 {
717 unsigned int
718 unsigned_value;
719
720 signed int
721 signed_value;
722 } quantum;
723
724 unsigned int
725 value;
726
727 if (endian == LSBEndian)
728 {
729 value=(unsigned int) buffer[3] << 24;
730 value|=(unsigned int) buffer[2] << 16;
731 value|=(unsigned int) buffer[1] << 8;
732 value|=(unsigned int) buffer[0];
733 quantum.unsigned_value=value & 0xffffffff;
734 return(quantum.signed_value);
735 }
736 value=(unsigned int) buffer[0] << 24;
737 value|=(unsigned int) buffer[1] << 16;
738 value|=(unsigned int) buffer[2] << 8;
739 value|=(unsigned int) buffer[3];
740 quantum.unsigned_value=value & 0xffffffff;
741 return(quantum.signed_value);
742 }
743
ReadPropertyUnsignedLong(const EndianType endian,const unsigned char * buffer)744 static inline unsigned int ReadPropertyUnsignedLong(const EndianType endian,
745 const unsigned char *buffer)
746 {
747 unsigned int
748 value;
749
750 if (endian == LSBEndian)
751 {
752 value=(unsigned int) buffer[3] << 24;
753 value|=(unsigned int) buffer[2] << 16;
754 value|=(unsigned int) buffer[1] << 8;
755 value|=(unsigned int) buffer[0];
756 return(value & 0xffffffff);
757 }
758 value=(unsigned int) buffer[0] << 24;
759 value|=(unsigned int) buffer[1] << 16;
760 value|=(unsigned int) buffer[2] << 8;
761 value|=(unsigned int) buffer[3];
762 return(value & 0xffffffff);
763 }
764
ReadPropertySignedShort(const EndianType endian,const unsigned char * buffer)765 static inline signed short ReadPropertySignedShort(const EndianType endian,
766 const unsigned char *buffer)
767 {
768 union
769 {
770 unsigned short
771 unsigned_value;
772
773 signed short
774 signed_value;
775 } quantum;
776
777 unsigned short
778 value;
779
780 if (endian == LSBEndian)
781 {
782 value=(unsigned short) buffer[1] << 8;
783 value|=(unsigned short) buffer[0];
784 quantum.unsigned_value=value & 0xffff;
785 return(quantum.signed_value);
786 }
787 value=(unsigned short) buffer[0] << 8;
788 value|=(unsigned short) buffer[1];
789 quantum.unsigned_value=value & 0xffff;
790 return(quantum.signed_value);
791 }
792
ReadPropertyUnsignedShort(const EndianType endian,const unsigned char * buffer)793 static inline unsigned short ReadPropertyUnsignedShort(const EndianType endian,
794 const unsigned char *buffer)
795 {
796 unsigned short
797 value;
798
799 if (endian == LSBEndian)
800 {
801 value=(unsigned short) buffer[1] << 8;
802 value|=(unsigned short) buffer[0];
803 return(value & 0xffff);
804 }
805 value=(unsigned short) buffer[0] << 8;
806 value|=(unsigned short) buffer[1];
807 return(value & 0xffff);
808 }
809
GetEXIFProperty(const Image * image,const char * property)810 static MagickBooleanType GetEXIFProperty(const Image *image,
811 const char *property)
812 {
813 #define MaxDirectoryStack 16
814 #define EXIF_DELIMITER "\n"
815 #define EXIF_NUM_FORMATS 12
816 #define EXIF_FMT_BYTE 1
817 #define EXIF_FMT_STRING 2
818 #define EXIF_FMT_USHORT 3
819 #define EXIF_FMT_ULONG 4
820 #define EXIF_FMT_URATIONAL 5
821 #define EXIF_FMT_SBYTE 6
822 #define EXIF_FMT_UNDEFINED 7
823 #define EXIF_FMT_SSHORT 8
824 #define EXIF_FMT_SLONG 9
825 #define EXIF_FMT_SRATIONAL 10
826 #define EXIF_FMT_SINGLE 11
827 #define EXIF_FMT_DOUBLE 12
828 #define TAG_EXIF_OFFSET 0x8769
829 #define TAG_GPS_OFFSET 0x8825
830 #define TAG_INTEROP_OFFSET 0xa005
831
832 #define EXIFMultipleValues(size,format,arg) \
833 { \
834 ssize_t \
835 component; \
836 \
837 size_t \
838 length; \
839 \
840 unsigned char \
841 *p1; \
842 \
843 length=0; \
844 p1=p; \
845 for (component=0; component < components; component++) \
846 { \
847 length+=FormatLocaleString(buffer+length,MaxTextExtent-length, \
848 format", ",arg); \
849 if (length >= (MaxTextExtent-1)) \
850 length=MaxTextExtent-1; \
851 p1+=size; \
852 } \
853 if (length > 1) \
854 buffer[length-2]='\0'; \
855 value=AcquireString(buffer); \
856 }
857
858 #define EXIFMultipleFractions(size,format,arg1,arg2) \
859 { \
860 ssize_t \
861 component; \
862 \
863 size_t \
864 length; \
865 \
866 unsigned char \
867 *p1; \
868 \
869 length=0; \
870 p1=p; \
871 for (component=0; component < components; component++) \
872 { \
873 length+=FormatLocaleString(buffer+length,MaxTextExtent-length, \
874 format", ",(arg1),(arg2)); \
875 if (length >= (MaxTextExtent-1)) \
876 length=MaxTextExtent-1; \
877 p1+=size; \
878 } \
879 if (length > 1) \
880 buffer[length-2]='\0'; \
881 value=AcquireString(buffer); \
882 }
883
884 typedef struct _DirectoryInfo
885 {
886 const unsigned char
887 *directory;
888
889 size_t
890 entry;
891
892 ssize_t
893 offset;
894 } DirectoryInfo;
895
896 typedef struct _TagInfo
897 {
898 size_t
899 tag;
900
901 const char
902 description[36];
903 } TagInfo;
904
905 static const TagInfo
906 EXIFTag[] =
907 {
908 { 0x001, "exif:InteroperabilityIndex" },
909 { 0x002, "exif:InteroperabilityVersion" },
910 { 0x100, "exif:ImageWidth" },
911 { 0x101, "exif:ImageLength" },
912 { 0x102, "exif:BitsPerSample" },
913 { 0x103, "exif:Compression" },
914 { 0x106, "exif:PhotometricInterpretation" },
915 { 0x10a, "exif:FillOrder" },
916 { 0x10d, "exif:DocumentName" },
917 { 0x10e, "exif:ImageDescription" },
918 { 0x10f, "exif:Make" },
919 { 0x110, "exif:Model" },
920 { 0x111, "exif:StripOffsets" },
921 { 0x112, "exif:Orientation" },
922 { 0x115, "exif:SamplesPerPixel" },
923 { 0x116, "exif:RowsPerStrip" },
924 { 0x117, "exif:StripByteCounts" },
925 { 0x11a, "exif:XResolution" },
926 { 0x11b, "exif:YResolution" },
927 { 0x11c, "exif:PlanarConfiguration" },
928 { 0x11d, "exif:PageName" },
929 { 0x11e, "exif:XPosition" },
930 { 0x11f, "exif:YPosition" },
931 { 0x118, "exif:MinSampleValue" },
932 { 0x119, "exif:MaxSampleValue" },
933 { 0x120, "exif:FreeOffsets" },
934 { 0x121, "exif:FreeByteCounts" },
935 { 0x122, "exif:GrayResponseUnit" },
936 { 0x123, "exif:GrayResponseCurve" },
937 { 0x124, "exif:T4Options" },
938 { 0x125, "exif:T6Options" },
939 { 0x128, "exif:ResolutionUnit" },
940 { 0x12d, "exif:TransferFunction" },
941 { 0x131, "exif:Software" },
942 { 0x132, "exif:DateTime" },
943 { 0x13b, "exif:Artist" },
944 { 0x13e, "exif:WhitePoint" },
945 { 0x13f, "exif:PrimaryChromaticities" },
946 { 0x140, "exif:ColorMap" },
947 { 0x141, "exif:HalfToneHints" },
948 { 0x142, "exif:TileWidth" },
949 { 0x143, "exif:TileLength" },
950 { 0x144, "exif:TileOffsets" },
951 { 0x145, "exif:TileByteCounts" },
952 { 0x14a, "exif:SubIFD" },
953 { 0x14c, "exif:InkSet" },
954 { 0x14d, "exif:InkNames" },
955 { 0x14e, "exif:NumberOfInks" },
956 { 0x150, "exif:DotRange" },
957 { 0x151, "exif:TargetPrinter" },
958 { 0x152, "exif:ExtraSample" },
959 { 0x153, "exif:SampleFormat" },
960 { 0x154, "exif:SMinSampleValue" },
961 { 0x155, "exif:SMaxSampleValue" },
962 { 0x156, "exif:TransferRange" },
963 { 0x157, "exif:ClipPath" },
964 { 0x158, "exif:XClipPathUnits" },
965 { 0x159, "exif:YClipPathUnits" },
966 { 0x15a, "exif:Indexed" },
967 { 0x15b, "exif:JPEGTables" },
968 { 0x15f, "exif:OPIProxy" },
969 { 0x200, "exif:JPEGProc" },
970 { 0x201, "exif:JPEGInterchangeFormat" },
971 { 0x202, "exif:JPEGInterchangeFormatLength" },
972 { 0x203, "exif:JPEGRestartInterval" },
973 { 0x205, "exif:JPEGLosslessPredictors" },
974 { 0x206, "exif:JPEGPointTransforms" },
975 { 0x207, "exif:JPEGQTables" },
976 { 0x208, "exif:JPEGDCTables" },
977 { 0x209, "exif:JPEGACTables" },
978 { 0x211, "exif:YCbCrCoefficients" },
979 { 0x212, "exif:YCbCrSubSampling" },
980 { 0x213, "exif:YCbCrPositioning" },
981 { 0x214, "exif:ReferenceBlackWhite" },
982 { 0x2bc, "exif:ExtensibleMetadataPlatform" },
983 { 0x301, "exif:Gamma" },
984 { 0x302, "exif:ICCProfileDescriptor" },
985 { 0x303, "exif:SRGBRenderingIntent" },
986 { 0x320, "exif:ImageTitle" },
987 { 0x5001, "exif:ResolutionXUnit" },
988 { 0x5002, "exif:ResolutionYUnit" },
989 { 0x5003, "exif:ResolutionXLengthUnit" },
990 { 0x5004, "exif:ResolutionYLengthUnit" },
991 { 0x5005, "exif:PrintFlags" },
992 { 0x5006, "exif:PrintFlagsVersion" },
993 { 0x5007, "exif:PrintFlagsCrop" },
994 { 0x5008, "exif:PrintFlagsBleedWidth" },
995 { 0x5009, "exif:PrintFlagsBleedWidthScale" },
996 { 0x500A, "exif:HalftoneLPI" },
997 { 0x500B, "exif:HalftoneLPIUnit" },
998 { 0x500C, "exif:HalftoneDegree" },
999 { 0x500D, "exif:HalftoneShape" },
1000 { 0x500E, "exif:HalftoneMisc" },
1001 { 0x500F, "exif:HalftoneScreen" },
1002 { 0x5010, "exif:JPEGQuality" },
1003 { 0x5011, "exif:GridSize" },
1004 { 0x5012, "exif:ThumbnailFormat" },
1005 { 0x5013, "exif:ThumbnailWidth" },
1006 { 0x5014, "exif:ThumbnailHeight" },
1007 { 0x5015, "exif:ThumbnailColorDepth" },
1008 { 0x5016, "exif:ThumbnailPlanes" },
1009 { 0x5017, "exif:ThumbnailRawBytes" },
1010 { 0x5018, "exif:ThumbnailSize" },
1011 { 0x5019, "exif:ThumbnailCompressedSize" },
1012 { 0x501a, "exif:ColorTransferFunction" },
1013 { 0x501b, "exif:ThumbnailData" },
1014 { 0x5020, "exif:ThumbnailImageWidth" },
1015 { 0x5021, "exif:ThumbnailImageHeight" },
1016 { 0x5022, "exif:ThumbnailBitsPerSample" },
1017 { 0x5023, "exif:ThumbnailCompression" },
1018 { 0x5024, "exif:ThumbnailPhotometricInterp" },
1019 { 0x5025, "exif:ThumbnailImageDescription" },
1020 { 0x5026, "exif:ThumbnailEquipMake" },
1021 { 0x5027, "exif:ThumbnailEquipModel" },
1022 { 0x5028, "exif:ThumbnailStripOffsets" },
1023 { 0x5029, "exif:ThumbnailOrientation" },
1024 { 0x502a, "exif:ThumbnailSamplesPerPixel" },
1025 { 0x502b, "exif:ThumbnailRowsPerStrip" },
1026 { 0x502c, "exif:ThumbnailStripBytesCount" },
1027 { 0x502d, "exif:ThumbnailResolutionX" },
1028 { 0x502e, "exif:ThumbnailResolutionY" },
1029 { 0x502f, "exif:ThumbnailPlanarConfig" },
1030 { 0x5030, "exif:ThumbnailResolutionUnit" },
1031 { 0x5031, "exif:ThumbnailTransferFunction" },
1032 { 0x5032, "exif:ThumbnailSoftwareUsed" },
1033 { 0x5033, "exif:ThumbnailDateTime" },
1034 { 0x5034, "exif:ThumbnailArtist" },
1035 { 0x5035, "exif:ThumbnailWhitePoint" },
1036 { 0x5036, "exif:ThumbnailPrimaryChromaticities" },
1037 { 0x5037, "exif:ThumbnailYCbCrCoefficients" },
1038 { 0x5038, "exif:ThumbnailYCbCrSubsampling" },
1039 { 0x5039, "exif:ThumbnailYCbCrPositioning" },
1040 { 0x503A, "exif:ThumbnailRefBlackWhite" },
1041 { 0x503B, "exif:ThumbnailCopyRight" },
1042 { 0x5090, "exif:LuminanceTable" },
1043 { 0x5091, "exif:ChrominanceTable" },
1044 { 0x5100, "exif:FrameDelay" },
1045 { 0x5101, "exif:LoopCount" },
1046 { 0x5110, "exif:PixelUnit" },
1047 { 0x5111, "exif:PixelPerUnitX" },
1048 { 0x5112, "exif:PixelPerUnitY" },
1049 { 0x5113, "exif:PaletteHistogram" },
1050 { 0x1000, "exif:RelatedImageFileFormat" },
1051 { 0x1001, "exif:RelatedImageLength" },
1052 { 0x1002, "exif:RelatedImageWidth" },
1053 { 0x800d, "exif:ImageID" },
1054 { 0x80e3, "exif:Matteing" },
1055 { 0x80e4, "exif:DataType" },
1056 { 0x80e5, "exif:ImageDepth" },
1057 { 0x80e6, "exif:TileDepth" },
1058 { 0x828d, "exif:CFARepeatPatternDim" },
1059 { 0x828e, "exif:CFAPattern2" },
1060 { 0x828f, "exif:BatteryLevel" },
1061 { 0x8298, "exif:Copyright" },
1062 { 0x829a, "exif:ExposureTime" },
1063 { 0x829d, "exif:FNumber" },
1064 { 0x83bb, "exif:IPTC/NAA" },
1065 { 0x84e3, "exif:IT8RasterPadding" },
1066 { 0x84e5, "exif:IT8ColorTable" },
1067 { 0x8649, "exif:ImageResourceInformation" },
1068 { 0x8769, "exif:ExifOffset" }, /* specs as "Exif IFD Pointer"? */
1069 { 0x8773, "exif:InterColorProfile" },
1070 { 0x8822, "exif:ExposureProgram" },
1071 { 0x8824, "exif:SpectralSensitivity" },
1072 { 0x8825, "exif:GPSInfo" }, /* specs as "GPSInfo IFD Pointer"? */
1073 { 0x8827, "exif:PhotographicSensitivity" },
1074 { 0x8828, "exif:OECF" },
1075 { 0x8829, "exif:Interlace" },
1076 { 0x882a, "exif:TimeZoneOffset" },
1077 { 0x882b, "exif:SelfTimerMode" },
1078 { 0x8830, "exif:SensitivityType" },
1079 { 0x8831, "exif:StandardOutputSensitivity" },
1080 { 0x8832, "exif:RecommendedExposureIndex" },
1081 { 0x8833, "exif:ISOSpeed" },
1082 { 0x8834, "exif:ISOSpeedLatitudeyyy" },
1083 { 0x8835, "exif:ISOSpeedLatitudezzz" },
1084 { 0x9000, "exif:ExifVersion" },
1085 { 0x9003, "exif:DateTimeOriginal" },
1086 { 0x9004, "exif:DateTimeDigitized" },
1087 { 0x9010, "exif:OffsetTime" },
1088 { 0x9011, "exif:OffsetTimeOriginal" },
1089 { 0x9012, "exif:OffsetTimeDigitized" },
1090 { 0x9101, "exif:ComponentsConfiguration" },
1091 { 0x9102, "exif:CompressedBitsPerPixel" },
1092 { 0x9201, "exif:ShutterSpeedValue" },
1093 { 0x9202, "exif:ApertureValue" },
1094 { 0x9203, "exif:BrightnessValue" },
1095 { 0x9204, "exif:ExposureBiasValue" },
1096 { 0x9205, "exif:MaxApertureValue" },
1097 { 0x9206, "exif:SubjectDistance" },
1098 { 0x9207, "exif:MeteringMode" },
1099 { 0x9208, "exif:LightSource" },
1100 { 0x9209, "exif:Flash" },
1101 { 0x920a, "exif:FocalLength" },
1102 { 0x920b, "exif:FlashEnergy" },
1103 { 0x920c, "exif:SpatialFrequencyResponse" },
1104 { 0x920d, "exif:Noise" },
1105 { 0x9214, "exif:SubjectArea" },
1106 { 0x9290, "exif:SubSecTime" },
1107 { 0x9291, "exif:SubSecTimeOriginal" },
1108 { 0x9292, "exif:SubSecTimeDigitized" },
1109 { 0x9211, "exif:ImageNumber" },
1110 { 0x9212, "exif:SecurityClassification" },
1111 { 0x9213, "exif:ImageHistory" },
1112 { 0x9214, "exif:SubjectArea" },
1113 { 0x9215, "exif:ExposureIndex" },
1114 { 0x9216, "exif:TIFF-EPStandardID" },
1115 { 0x927c, "exif:MakerNote" },
1116 { 0x9286, "exif:UserComment" },
1117 { 0x9290, "exif:SubSecTime" },
1118 { 0x9291, "exif:SubSecTimeOriginal" },
1119 { 0x9292, "exif:SubSecTimeDigitized" },
1120 { 0x9400, "exif:Temperature" },
1121 { 0x9401, "exif:Humidity" },
1122 { 0x9402, "exif:Pressure" },
1123 { 0x9403, "exif:WaterDepth" },
1124 { 0x9404, "exif:Acceleration" },
1125 { 0x9405, "exif:CameraElevationAngle" },
1126 { 0x9C9b, "exif:WinXP-Title" },
1127 { 0x9C9c, "exif:WinXP-Comments" },
1128 { 0x9C9d, "exif:WinXP-Author" },
1129 { 0x9C9e, "exif:WinXP-Keywords" },
1130 { 0x9C9f, "exif:WinXP-Subject" },
1131 { 0xa000, "exif:FlashPixVersion" },
1132 { 0xa001, "exif:ColorSpace" },
1133 { 0xa002, "exif:PixelXDimension" },
1134 { 0xa003, "exif:PixelYDimension" },
1135 { 0xa004, "exif:RelatedSoundFile" },
1136 { 0xa005, "exif:InteroperabilityOffset" },
1137 { 0xa20b, "exif:FlashEnergy" },
1138 { 0xa20c, "exif:SpatialFrequencyResponse" },
1139 { 0xa20d, "exif:Noise" },
1140 { 0xa20e, "exif:FocalPlaneXResolution" },
1141 { 0xa20f, "exif:FocalPlaneYResolution" },
1142 { 0xa210, "exif:FocalPlaneResolutionUnit" },
1143 { 0xa214, "exif:SubjectLocation" },
1144 { 0xa215, "exif:ExposureIndex" },
1145 { 0xa216, "exif:TIFF/EPStandardID" },
1146 { 0xa217, "exif:SensingMethod" },
1147 { 0xa300, "exif:FileSource" },
1148 { 0xa301, "exif:SceneType" },
1149 { 0xa302, "exif:CFAPattern" },
1150 { 0xa401, "exif:CustomRendered" },
1151 { 0xa402, "exif:ExposureMode" },
1152 { 0xa403, "exif:WhiteBalance" },
1153 { 0xa404, "exif:DigitalZoomRatio" },
1154 { 0xa405, "exif:FocalLengthIn35mmFilm" },
1155 { 0xa406, "exif:SceneCaptureType" },
1156 { 0xa407, "exif:GainControl" },
1157 { 0xa408, "exif:Contrast" },
1158 { 0xa409, "exif:Saturation" },
1159 { 0xa40a, "exif:Sharpness" },
1160 { 0xa40b, "exif:DeviceSettingDescription" },
1161 { 0xa40c, "exif:SubjectDistanceRange" },
1162 { 0xa420, "exif:ImageUniqueID" },
1163 { 0xa430, "exif:CameraOwnerName" },
1164 { 0xa431, "exif:BodySerialNumber" },
1165 { 0xa432, "exif:LensSpecification" },
1166 { 0xa433, "exif:LensMake" },
1167 { 0xa434, "exif:LensModel" },
1168 { 0xa435, "exif:LensSerialNumber" },
1169 { 0xc4a5, "exif:PrintImageMatching" },
1170 { 0xa500, "exif:Gamma" },
1171 { 0xc640, "exif:CR2Slice" },
1172 { 0x10000, "exif:GPSVersionID" },
1173 { 0x10001, "exif:GPSLatitudeRef" },
1174 { 0x10002, "exif:GPSLatitude" },
1175 { 0x10003, "exif:GPSLongitudeRef" },
1176 { 0x10004, "exif:GPSLongitude" },
1177 { 0x10005, "exif:GPSAltitudeRef" },
1178 { 0x10006, "exif:GPSAltitude" },
1179 { 0x10007, "exif:GPSTimeStamp" },
1180 { 0x10008, "exif:GPSSatellites" },
1181 { 0x10009, "exif:GPSStatus" },
1182 { 0x1000a, "exif:GPSMeasureMode" },
1183 { 0x1000b, "exif:GPSDop" },
1184 { 0x1000c, "exif:GPSSpeedRef" },
1185 { 0x1000d, "exif:GPSSpeed" },
1186 { 0x1000e, "exif:GPSTrackRef" },
1187 { 0x1000f, "exif:GPSTrack" },
1188 { 0x10010, "exif:GPSImgDirectionRef" },
1189 { 0x10011, "exif:GPSImgDirection" },
1190 { 0x10012, "exif:GPSMapDatum" },
1191 { 0x10013, "exif:GPSDestLatitudeRef" },
1192 { 0x10014, "exif:GPSDestLatitude" },
1193 { 0x10015, "exif:GPSDestLongitudeRef" },
1194 { 0x10016, "exif:GPSDestLongitude" },
1195 { 0x10017, "exif:GPSDestBearingRef" },
1196 { 0x10018, "exif:GPSDestBearing" },
1197 { 0x10019, "exif:GPSDestDistanceRef" },
1198 { 0x1001a, "exif:GPSDestDistance" },
1199 { 0x1001b, "exif:GPSProcessingMethod" },
1200 { 0x1001c, "exif:GPSAreaInformation" },
1201 { 0x1001d, "exif:GPSDateStamp" },
1202 { 0x1001e, "exif:GPSDifferential" },
1203 { 0x1001f, "exif:GPSHPositioningError" },
1204 { 0x00000, "" }
1205 }; /* http://www.cipa.jp/std/documents/e/DC-008-Translation-2016-E.pdf */
1206
1207 const StringInfo
1208 *profile;
1209
1210 const unsigned char
1211 *directory,
1212 *exif;
1213
1214 DirectoryInfo
1215 directory_stack[MaxDirectoryStack];
1216
1217 EndianType
1218 endian;
1219
1220 MagickBooleanType
1221 status;
1222
1223 ssize_t
1224 i;
1225
1226 size_t
1227 entry,
1228 length,
1229 number_entries,
1230 tag,
1231 tag_value;
1232
1233 SplayTreeInfo
1234 *exif_resources;
1235
1236 ssize_t
1237 all,
1238 id,
1239 level,
1240 offset,
1241 tag_offset;
1242
1243 static int
1244 tag_bytes[] = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8};
1245
1246 /*
1247 If EXIF data exists, then try to parse the request for a tag.
1248 */
1249 profile=GetImageProfile(image,"exif");
1250 if (profile == (const StringInfo *) NULL)
1251 return(MagickFalse);
1252 if ((property == (const char *) NULL) || (*property == '\0'))
1253 return(MagickFalse);
1254 while (isspace((int) ((unsigned char) *property)) != 0)
1255 property++;
1256 if (strlen(property) <= 5)
1257 return(MagickFalse);
1258 all=0;
1259 tag=(~0UL);
1260 switch (*(property+5))
1261 {
1262 case '*':
1263 {
1264 /*
1265 Caller has asked for all the tags in the EXIF data.
1266 */
1267 tag=0;
1268 all=1; /* return the data in description=value format */
1269 break;
1270 }
1271 case '!':
1272 {
1273 tag=0;
1274 all=2; /* return the data in tagid=value format */
1275 break;
1276 }
1277 case '#':
1278 case '@':
1279 {
1280 int
1281 c;
1282
1283 size_t
1284 n;
1285
1286 /*
1287 Check for a hex based tag specification first.
1288 */
1289 tag=(*(property+5) == '@') ? 1UL : 0UL;
1290 property+=6;
1291 n=strlen(property);
1292 if (n != 4)
1293 return(MagickFalse);
1294 /*
1295 Parse tag specification as a hex number.
1296 */
1297 n/=4;
1298 do
1299 {
1300 for (i=(ssize_t) n-1L; i >= 0; i--)
1301 {
1302 c=(*property++);
1303 tag<<=4;
1304 if ((c >= '0') && (c <= '9'))
1305 tag|=(c-'0');
1306 else
1307 if ((c >= 'A') && (c <= 'F'))
1308 tag|=(c-('A'-10));
1309 else
1310 if ((c >= 'a') && (c <= 'f'))
1311 tag|=(c-('a'-10));
1312 else
1313 return(MagickFalse);
1314 }
1315 } while (*property != '\0');
1316 break;
1317 }
1318 default:
1319 {
1320 /*
1321 Try to match the text with a tag name instead.
1322 */
1323 for (i=0; ; i++)
1324 {
1325 if (EXIFTag[i].tag == 0)
1326 break;
1327 if (LocaleCompare(EXIFTag[i].description,property) == 0)
1328 {
1329 tag=(size_t) EXIFTag[i].tag;
1330 break;
1331 }
1332 }
1333 break;
1334 }
1335 }
1336 if (tag == (~0UL))
1337 return(MagickFalse);
1338 length=GetStringInfoLength(profile);
1339 if (length < 6)
1340 return(MagickFalse);
1341 exif=GetStringInfoDatum(profile);
1342 while (length != 0)
1343 {
1344 if (ReadPropertyByte(&exif,&length) != 0x45)
1345 continue;
1346 if (ReadPropertyByte(&exif,&length) != 0x78)
1347 continue;
1348 if (ReadPropertyByte(&exif,&length) != 0x69)
1349 continue;
1350 if (ReadPropertyByte(&exif,&length) != 0x66)
1351 continue;
1352 if (ReadPropertyByte(&exif,&length) != 0x00)
1353 continue;
1354 if (ReadPropertyByte(&exif,&length) != 0x00)
1355 continue;
1356 break;
1357 }
1358 if (length < 16)
1359 return(MagickFalse);
1360 id=(ssize_t) ReadPropertySignedShort(LSBEndian,exif);
1361 endian=LSBEndian;
1362 if (id == 0x4949)
1363 endian=LSBEndian;
1364 else
1365 if (id == 0x4D4D)
1366 endian=MSBEndian;
1367 else
1368 return(MagickFalse);
1369 if (ReadPropertyUnsignedShort(endian,exif+2) != 0x002a)
1370 return(MagickFalse);
1371 /*
1372 This the offset to the first IFD.
1373 */
1374 offset=(ssize_t) ReadPropertySignedLong(endian,exif+4);
1375 if ((offset < 0) || (size_t) offset >= length)
1376 return(MagickFalse);
1377 /*
1378 Set the pointer to the first IFD and follow it were it leads.
1379 */
1380 status=MagickFalse;
1381 directory=exif+offset;
1382 level=0;
1383 entry=0;
1384 tag_offset=0;
1385 exif_resources=NewSplayTree((int (*)(const void *,const void *)) NULL,
1386 (void *(*)(void *)) NULL,(void *(*)(void *)) NULL);
1387 do
1388 {
1389 /*
1390 If there is anything on the stack then pop it off.
1391 */
1392 if (level > 0)
1393 {
1394 level--;
1395 directory=directory_stack[level].directory;
1396 entry=directory_stack[level].entry;
1397 tag_offset=directory_stack[level].offset;
1398 }
1399 if ((directory < exif) || (directory > (exif+length-2)))
1400 break;
1401 /*
1402 Determine how many entries there are in the current IFD.
1403 */
1404 number_entries=(size_t) ReadPropertyUnsignedShort(endian,directory);
1405 for ( ; entry < number_entries; entry++)
1406 {
1407 unsigned char
1408 *p,
1409 *q;
1410
1411 size_t
1412 format;
1413
1414 ssize_t
1415 number_bytes,
1416 components;
1417
1418 q=(unsigned char *) (directory+(12*entry)+2);
1419 if (q > (exif+length-12))
1420 break; /* corrupt EXIF */
1421 if (GetValueFromSplayTree(exif_resources,q) == q)
1422 break;
1423 (void) AddValueToSplayTree(exif_resources,q,q);
1424 tag_value=(size_t) ReadPropertyUnsignedShort(endian,q)+tag_offset;
1425 format=(size_t) ReadPropertyUnsignedShort(endian,q+2);
1426 if (format >= (sizeof(tag_bytes)/sizeof(*tag_bytes)))
1427 break;
1428 if (format == 0)
1429 break; /* corrupt EXIF */
1430 components=(ssize_t) ReadPropertySignedLong(endian,q+4);
1431 if (components < 0)
1432 break; /* corrupt EXIF */
1433 number_bytes=(size_t) components*tag_bytes[format];
1434 if (number_bytes < components)
1435 break; /* prevent overflow */
1436 if (number_bytes <= 4)
1437 p=q+8;
1438 else
1439 {
1440 ssize_t
1441 dir_offset;
1442
1443 /*
1444 The directory entry contains an offset.
1445 */
1446 dir_offset=(ssize_t) ReadPropertySignedLong(endian,q+8);
1447 if ((dir_offset < 0) || (size_t) dir_offset >= length)
1448 continue;
1449 if (((size_t) dir_offset+number_bytes) < (size_t) dir_offset)
1450 continue; /* prevent overflow */
1451 if (((size_t) dir_offset+number_bytes) > length)
1452 continue;
1453 p=(unsigned char *) (exif+dir_offset);
1454 }
1455 if ((all != 0) || (tag == (size_t) tag_value))
1456 {
1457 char
1458 buffer[MaxTextExtent],
1459 *value;
1460
1461 if ((p < exif) || (p > (exif+length-tag_bytes[format])))
1462 break;
1463 value=(char *) NULL;
1464 *buffer='\0';
1465 switch (format)
1466 {
1467 case EXIF_FMT_BYTE:
1468 case EXIF_FMT_UNDEFINED:
1469 {
1470 EXIFMultipleValues(1,"%.20g",(double) (*(unsigned char *) p1));
1471 break;
1472 }
1473 case EXIF_FMT_SBYTE:
1474 {
1475 EXIFMultipleValues(1,"%.20g",(double) (*(signed char *) p1));
1476 break;
1477 }
1478 case EXIF_FMT_SSHORT:
1479 {
1480 EXIFMultipleValues(2,"%hd",ReadPropertySignedShort(endian,p1));
1481 break;
1482 }
1483 case EXIF_FMT_USHORT:
1484 {
1485 EXIFMultipleValues(2,"%hu",ReadPropertyUnsignedShort(endian,p1));
1486 break;
1487 }
1488 case EXIF_FMT_ULONG:
1489 {
1490 EXIFMultipleValues(4,"%.20g",(double)
1491 ReadPropertyUnsignedLong(endian,p1));
1492 break;
1493 }
1494 case EXIF_FMT_SLONG:
1495 {
1496 EXIFMultipleValues(4,"%.20g",(double)
1497 ReadPropertySignedLong(endian,p1));
1498 break;
1499 }
1500 case EXIF_FMT_URATIONAL:
1501 {
1502 EXIFMultipleFractions(8,"%.20g/%.20g",(double)
1503 ReadPropertyUnsignedLong(endian,p1),(double)
1504 ReadPropertyUnsignedLong(endian,p1+4));
1505 break;
1506 }
1507 case EXIF_FMT_SRATIONAL:
1508 {
1509 EXIFMultipleFractions(8,"%.20g/%.20g",(double)
1510 ReadPropertySignedLong(endian,p1),(double)
1511 ReadPropertySignedLong(endian,p1+4));
1512 break;
1513 }
1514 case EXIF_FMT_SINGLE:
1515 {
1516 EXIFMultipleValues(4,"%f",(double) *(float *) p1);
1517 break;
1518 }
1519 case EXIF_FMT_DOUBLE:
1520 {
1521 EXIFMultipleValues(8,"%f",*(double *) p1);
1522 break;
1523 }
1524 case EXIF_FMT_STRING:
1525 default:
1526 {
1527 if ((p < exif) || (p > (exif+length-number_bytes)))
1528 break;
1529 value=(char *) NULL;
1530 if (~((size_t) number_bytes) >= 1)
1531 value=(char *) AcquireQuantumMemory((size_t) number_bytes+1UL,
1532 sizeof(*value));
1533 if (value != (char *) NULL)
1534 {
1535 ssize_t
1536 i;
1537
1538 for (i=0; i < (ssize_t) number_bytes; i++)
1539 {
1540 value[i]='.';
1541 if ((isprint((int) p[i]) != 0) || (p[i] == '\0'))
1542 value[i]=(char) p[i];
1543 }
1544 value[i]='\0';
1545 }
1546 break;
1547 }
1548 }
1549 if (value != (char *) NULL)
1550 {
1551 char
1552 *key;
1553
1554 const char
1555 *p;
1556
1557 key=AcquireString(property);
1558 switch (all)
1559 {
1560 case 1:
1561 {
1562 const char
1563 *description;
1564
1565 ssize_t
1566 i;
1567
1568 description="unknown";
1569 for (i=0; ; i++)
1570 {
1571 if (EXIFTag[i].tag == 0)
1572 break;
1573 if (EXIFTag[i].tag == tag_value)
1574 {
1575 description=EXIFTag[i].description;
1576 break;
1577 }
1578 }
1579 (void) FormatLocaleString(key,MaxTextExtent,"%s",
1580 description);
1581 if (level == 2)
1582 (void) SubstituteString(&key,"exif:","exif:thumbnail:");
1583 break;
1584 }
1585 case 2:
1586 {
1587 if (tag_value < 0x10000)
1588 (void) FormatLocaleString(key,MaxTextExtent,"#%04lx",
1589 (unsigned long) tag_value);
1590 else
1591 if (tag_value < 0x20000)
1592 (void) FormatLocaleString(key,MaxTextExtent,"@%04lx",
1593 (unsigned long) (tag_value & 0xffff));
1594 else
1595 (void) FormatLocaleString(key,MaxTextExtent,"unknown");
1596 break;
1597 }
1598 default:
1599 {
1600 if (level == 2)
1601 (void) SubstituteString(&key,"exif:","exif:thumbnail:");
1602 }
1603 }
1604 p=(const char *) NULL;
1605 if (image->properties != (void *) NULL)
1606 p=(const char *) GetValueFromSplayTree((SplayTreeInfo *)
1607 image->properties,key);
1608 if (p == (const char *) NULL)
1609 (void) SetImageProperty((Image *) image,key,value);
1610 value=DestroyString(value);
1611 key=DestroyString(key);
1612 status=MagickTrue;
1613 }
1614 }
1615 if ((tag_value == TAG_EXIF_OFFSET) ||
1616 (tag_value == TAG_INTEROP_OFFSET) || (tag_value == TAG_GPS_OFFSET))
1617 {
1618 ssize_t
1619 offset;
1620
1621 offset=(ssize_t) ReadPropertySignedLong(endian,p);
1622 if (((size_t) offset < length) && (level < (MaxDirectoryStack-2)))
1623 {
1624 ssize_t
1625 tag_offset1;
1626
1627 tag_offset1=(ssize_t) ((tag_value == TAG_GPS_OFFSET) ? 0x10000 :
1628 0);
1629 directory_stack[level].directory=directory;
1630 entry++;
1631 directory_stack[level].entry=entry;
1632 directory_stack[level].offset=tag_offset;
1633 level++;
1634 /*
1635 Check for duplicate tag.
1636 */
1637 for (i=0; i < level; i++)
1638 if (directory_stack[i].directory == (exif+tag_offset1))
1639 break;
1640 if (i < level)
1641 break; /* duplicate tag */
1642 directory_stack[level].directory=exif+offset;
1643 directory_stack[level].offset=tag_offset1;
1644 directory_stack[level].entry=0;
1645 level++;
1646 if ((directory+2+(12*number_entries)+4) > (exif+length))
1647 break;
1648 offset=(ssize_t) ReadPropertySignedLong(endian,directory+2+(12*
1649 number_entries));
1650 if ((offset != 0) && ((size_t) offset < length) &&
1651 (level < (MaxDirectoryStack-2)))
1652 {
1653 directory_stack[level].directory=exif+offset;
1654 directory_stack[level].entry=0;
1655 directory_stack[level].offset=tag_offset1;
1656 level++;
1657 }
1658 }
1659 break;
1660 }
1661 }
1662 } while (level > 0);
1663 exif_resources=DestroySplayTree(exif_resources);
1664 return(status);
1665 }
1666
GetICCProperty(const Image * image,const char * property)1667 static MagickBooleanType GetICCProperty(const Image *image,const char *property)
1668 {
1669 const StringInfo
1670 *profile;
1671
1672 magick_unreferenced(property);
1673
1674 profile=GetImageProfile(image,"icc");
1675 if (profile == (StringInfo *) NULL)
1676 profile=GetImageProfile(image,"icm");
1677 if (profile == (StringInfo *) NULL)
1678 return(MagickFalse);
1679 if (GetStringInfoLength(profile) < 128)
1680 return(MagickFalse); /* minimum ICC profile length */
1681 #if defined(MAGICKCORE_LCMS_DELEGATE)
1682 {
1683 cmsHPROFILE
1684 icc_profile;
1685
1686 icc_profile=cmsOpenProfileFromMem(GetStringInfoDatum(profile),
1687 (cmsUInt32Number) GetStringInfoLength(profile));
1688 if (icc_profile != (cmsHPROFILE *) NULL)
1689 {
1690 #if defined(LCMS_VERSION) && (LCMS_VERSION < 2000)
1691 const char
1692 *name;
1693
1694 name=cmsTakeProductName(icc_profile);
1695 if (name != (const char *) NULL)
1696 (void) SetImageProperty((Image *) image,"icc:name",name);
1697 #else
1698 char
1699 info[MagickPathExtent];
1700
1701 unsigned int
1702 extent;
1703
1704 (void) memset(info,0,sizeof(info));
1705 extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoDescription,"en","US",
1706 NULL,0);
1707 if (extent != 0)
1708 {
1709 extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoDescription,"en",
1710 "US",info,MagickMin(MagickPathExtent-1,extent));
1711 (void) SetImageProperty((Image *) image,"icc:description",info);
1712 }
1713 extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoManufacturer,"en","US",
1714 NULL,0);
1715 if (extent != 0)
1716 {
1717 extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoManufacturer,"en",
1718 "US",info,MagickMin(MagickPathExtent-1,extent));
1719 (void) SetImageProperty((Image *) image,"icc:manufacturer",info);
1720 }
1721 extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoModel,"en","US",
1722 NULL,0);
1723 if (extent != 0)
1724 {
1725 extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoModel,"en","US",
1726 info,MagickMin(MagickPathExtent-1,extent));
1727 (void) SetImageProperty((Image *) image,"icc:model",info);
1728 }
1729 extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoCopyright,"en","US",
1730 NULL,0);
1731 if (extent != 0)
1732 {
1733 extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoCopyright,"en",
1734 "US",info,MagickMin(MagickPathExtent-1,extent));
1735 (void) SetImageProperty((Image *) image,"icc:copyright",info);
1736 }
1737 #endif
1738 (void) cmsCloseProfile(icc_profile);
1739 }
1740 }
1741 #endif
1742 return(MagickTrue);
1743 }
1744
SkipXMPValue(const char * value)1745 static MagickBooleanType SkipXMPValue(const char *value)
1746 {
1747 if (value == (const char*) NULL)
1748 return(MagickTrue);
1749 while (*value != '\0')
1750 {
1751 if (isspace((int) ((unsigned char) *value)) == 0)
1752 return(MagickFalse);
1753 value++;
1754 }
1755 return(MagickTrue);
1756 }
1757
GetXMPProperty(const Image * image,const char * property)1758 static MagickBooleanType GetXMPProperty(const Image *image,const char *property)
1759 {
1760 char
1761 *xmp_profile;
1762
1763 const char
1764 *content;
1765
1766 const StringInfo
1767 *profile;
1768
1769 ExceptionInfo
1770 *exception;
1771
1772 MagickBooleanType
1773 status;
1774
1775 const char
1776 *p;
1777
1778 XMLTreeInfo
1779 *child,
1780 *description,
1781 *node,
1782 *rdf,
1783 *xmp;
1784
1785 profile=GetImageProfile(image,"xmp");
1786 if (profile == (StringInfo *) NULL)
1787 return(MagickFalse);
1788 if (GetStringInfoLength(profile) < 17)
1789 return(MagickFalse);
1790 if ((property == (const char *) NULL) || (*property == '\0'))
1791 return(MagickFalse);
1792 xmp_profile=StringInfoToString(profile);
1793 if (xmp_profile == (char *) NULL)
1794 return(MagickFalse);
1795 for (p=xmp_profile; *p != '\0'; p++)
1796 if ((*p == '<') && (*(p+1) == 'x'))
1797 break;
1798 exception=AcquireExceptionInfo();
1799 xmp=NewXMLTree((char *) p,exception);
1800 xmp_profile=DestroyString(xmp_profile);
1801 exception=DestroyExceptionInfo(exception);
1802 if (xmp == (XMLTreeInfo *) NULL)
1803 return(MagickFalse);
1804 status=MagickFalse;
1805 rdf=GetXMLTreeChild(xmp,"rdf:RDF");
1806 if (rdf != (XMLTreeInfo *) NULL)
1807 {
1808 if (image->properties == (void *) NULL)
1809 ((Image *) image)->properties=NewSplayTree(CompareSplayTreeString,
1810 RelinquishMagickMemory,RelinquishMagickMemory);
1811 description=GetXMLTreeChild(rdf,"rdf:Description");
1812 while (description != (XMLTreeInfo *) NULL)
1813 {
1814 node=GetXMLTreeChild(description,(const char *) NULL);
1815 while (node != (XMLTreeInfo *) NULL)
1816 {
1817 char
1818 *xmp_namespace;
1819
1820 child=GetXMLTreeChild(node,(const char *) NULL);
1821 content=GetXMLTreeContent(node);
1822 if ((child == (XMLTreeInfo *) NULL) &&
1823 (SkipXMPValue(content) == MagickFalse))
1824 {
1825 xmp_namespace=ConstantString(GetXMLTreeTag(node));
1826 (void) SubstituteString(&xmp_namespace,"exif:","xmp:");
1827 (void) AddValueToSplayTree((SplayTreeInfo *) image->properties,
1828 xmp_namespace,ConstantString(content));
1829 }
1830 while (child != (XMLTreeInfo *) NULL)
1831 {
1832 content=GetXMLTreeContent(child);
1833 if (SkipXMPValue(content) == MagickFalse)
1834 {
1835 xmp_namespace=ConstantString(GetXMLTreeTag(node));
1836 (void) SubstituteString(&xmp_namespace,"exif:","xmp:");
1837 (void) AddValueToSplayTree((SplayTreeInfo *) image->properties,
1838 xmp_namespace,ConstantString(content));
1839 }
1840 child=GetXMLTreeSibling(child);
1841 }
1842 node=GetXMLTreeSibling(node);
1843 }
1844 description=GetNextXMLTreeTag(description);
1845 }
1846 }
1847 xmp=DestroyXMLTree(xmp);
1848 return(status);
1849 }
1850
TracePSClippath(const unsigned char * blob,size_t length,const size_t magick_unused (columns),const size_t magick_unused (rows))1851 static char *TracePSClippath(const unsigned char *blob,size_t length,
1852 const size_t magick_unused(columns),const size_t magick_unused(rows))
1853 {
1854 char
1855 *path,
1856 *message;
1857
1858 MagickBooleanType
1859 in_subpath;
1860
1861 PointInfo
1862 first[3],
1863 last[3],
1864 point[3];
1865
1866 ssize_t
1867 i,
1868 x;
1869
1870 ssize_t
1871 knot_count,
1872 selector,
1873 y;
1874
1875 magick_unreferenced(columns);
1876 magick_unreferenced(rows);
1877
1878 path=AcquireString((char *) NULL);
1879 if (path == (char *) NULL)
1880 return((char *) NULL);
1881 message=AcquireString((char *) NULL);
1882 (void) FormatLocaleString(message,MaxTextExtent,"/ClipImage\n");
1883 (void) ConcatenateString(&path,message);
1884 (void) FormatLocaleString(message,MaxTextExtent,"{\n");
1885 (void) ConcatenateString(&path,message);
1886 (void) FormatLocaleString(message,MaxTextExtent," /c {curveto} bind def\n");
1887 (void) ConcatenateString(&path,message);
1888 (void) FormatLocaleString(message,MaxTextExtent," /l {lineto} bind def\n");
1889 (void) ConcatenateString(&path,message);
1890 (void) FormatLocaleString(message,MaxTextExtent," /m {moveto} bind def\n");
1891 (void) ConcatenateString(&path,message);
1892 (void) FormatLocaleString(message,MaxTextExtent,
1893 " /v {currentpoint 6 2 roll curveto} bind def\n");
1894 (void) ConcatenateString(&path,message);
1895 (void) FormatLocaleString(message,MaxTextExtent,
1896 " /y {2 copy curveto} bind def\n");
1897 (void) ConcatenateString(&path,message);
1898 (void) FormatLocaleString(message,MaxTextExtent,
1899 " /z {closepath} bind def\n");
1900 (void) ConcatenateString(&path,message);
1901 (void) FormatLocaleString(message,MaxTextExtent," newpath\n");
1902 (void) ConcatenateString(&path,message);
1903 /*
1904 The clipping path format is defined in "Adobe Photoshop File
1905 Formats Specification" version 6.0 downloadable from adobe.com.
1906 */
1907 (void) memset(point,0,sizeof(point));
1908 (void) memset(first,0,sizeof(first));
1909 (void) memset(last,0,sizeof(last));
1910 knot_count=0;
1911 in_subpath=MagickFalse;
1912 while (length > 0)
1913 {
1914 selector=(ssize_t) ReadPropertyMSBShort(&blob,&length);
1915 switch (selector)
1916 {
1917 case 0:
1918 case 3:
1919 {
1920 if (knot_count != 0)
1921 {
1922 blob+=24;
1923 length-=MagickMin(24,(ssize_t) length);
1924 break;
1925 }
1926 /*
1927 Expected subpath length record.
1928 */
1929 knot_count=(ssize_t) ReadPropertyMSBShort(&blob,&length);
1930 blob+=22;
1931 length-=MagickMin(22,(ssize_t) length);
1932 break;
1933 }
1934 case 1:
1935 case 2:
1936 case 4:
1937 case 5:
1938 {
1939 if (knot_count == 0)
1940 {
1941 /*
1942 Unexpected subpath knot
1943 */
1944 blob+=24;
1945 length-=MagickMin(24,(ssize_t) length);
1946 break;
1947 }
1948 /*
1949 Add sub-path knot
1950 */
1951 for (i=0; i < 3; i++)
1952 {
1953 y=(size_t) ReadPropertyMSBLong(&blob,&length);
1954 x=(size_t) ReadPropertyMSBLong(&blob,&length);
1955 point[i].x=(double) x/4096.0/4096.0;
1956 point[i].y=1.0-(double) y/4096.0/4096.0;
1957 }
1958 if (in_subpath == MagickFalse)
1959 {
1960 (void) FormatLocaleString(message,MaxTextExtent," %g %g m\n",
1961 point[1].x,point[1].y);
1962 for (i=0; i < 3; i++)
1963 {
1964 first[i]=point[i];
1965 last[i]=point[i];
1966 }
1967 }
1968 else
1969 {
1970 /*
1971 Handle special cases when Bezier curves are used to describe
1972 corners and straight lines.
1973 */
1974 if ((last[1].x == last[2].x) && (last[1].y == last[2].y) &&
1975 (point[0].x == point[1].x) && (point[0].y == point[1].y))
1976 (void) FormatLocaleString(message,MaxTextExtent,
1977 " %g %g l\n",point[1].x,point[1].y);
1978 else
1979 if ((last[1].x == last[2].x) && (last[1].y == last[2].y))
1980 (void) FormatLocaleString(message,MaxTextExtent,
1981 " %g %g %g %g v\n",point[0].x,point[0].y,
1982 point[1].x,point[1].y);
1983 else
1984 if ((point[0].x == point[1].x) && (point[0].y == point[1].y))
1985 (void) FormatLocaleString(message,MaxTextExtent,
1986 " %g %g %g %g y\n",last[2].x,last[2].y,
1987 point[1].x,point[1].y);
1988 else
1989 (void) FormatLocaleString(message,MaxTextExtent,
1990 " %g %g %g %g %g %g c\n",last[2].x,
1991 last[2].y,point[0].x,point[0].y,point[1].x,point[1].y);
1992 for (i=0; i < 3; i++)
1993 last[i]=point[i];
1994 }
1995 (void) ConcatenateString(&path,message);
1996 in_subpath=MagickTrue;
1997 knot_count--;
1998 /*
1999 Close the subpath if there are no more knots.
2000 */
2001 if (knot_count == 0)
2002 {
2003 /*
2004 Same special handling as above except we compare to the
2005 first point in the path and close the path.
2006 */
2007 if ((last[1].x == last[2].x) && (last[1].y == last[2].y) &&
2008 (first[0].x == first[1].x) && (first[0].y == first[1].y))
2009 (void) FormatLocaleString(message,MaxTextExtent,
2010 " %g %g l z\n",first[1].x,first[1].y);
2011 else
2012 if ((last[1].x == last[2].x) && (last[1].y == last[2].y))
2013 (void) FormatLocaleString(message,MaxTextExtent,
2014 " %g %g %g %g v z\n",first[0].x,first[0].y,
2015 first[1].x,first[1].y);
2016 else
2017 if ((first[0].x == first[1].x) && (first[0].y == first[1].y))
2018 (void) FormatLocaleString(message,MaxTextExtent,
2019 " %g %g %g %g y z\n",last[2].x,last[2].y,
2020 first[1].x,first[1].y);
2021 else
2022 (void) FormatLocaleString(message,MaxTextExtent,
2023 " %g %g %g %g %g %g c z\n",last[2].x,
2024 last[2].y,first[0].x,first[0].y,first[1].x,first[1].y);
2025 (void) ConcatenateString(&path,message);
2026 in_subpath=MagickFalse;
2027 }
2028 break;
2029 }
2030 case 6:
2031 case 7:
2032 case 8:
2033 default:
2034 {
2035 blob+=24;
2036 length-=MagickMin(24,(ssize_t) length);
2037 break;
2038 }
2039 }
2040 }
2041 /*
2042 Returns an empty PS path if the path has no knots.
2043 */
2044 (void) FormatLocaleString(message,MaxTextExtent," eoclip\n");
2045 (void) ConcatenateString(&path,message);
2046 (void) FormatLocaleString(message,MaxTextExtent,"} bind def");
2047 (void) ConcatenateString(&path,message);
2048 message=DestroyString(message);
2049 return(path);
2050 }
2051
TraceBezierCurve(char * message,PointInfo * last,PointInfo * point)2052 static inline void TraceBezierCurve(char *message,PointInfo *last,
2053 PointInfo *point)
2054 {
2055 /*
2056 Handle special cases when Bezier curves are used to describe
2057 corners and straight lines.
2058 */
2059 if (((last+1)->x == (last+2)->x) && ((last+1)->y == (last+2)->y) &&
2060 (point->x == (point+1)->x) && (point->y == (point+1)->y))
2061 (void) FormatLocaleString(message,MagickPathExtent,
2062 "L %g %g\n",point[1].x,point[1].y);
2063 else
2064 (void) FormatLocaleString(message,MagickPathExtent,"C %g %g %g %g %g %g\n",
2065 (last+2)->x,(last+2)->y,point->x,point->y,(point+1)->x,(point+1)->y);
2066 }
2067
TraceSVGClippath(const unsigned char * blob,size_t length,const size_t columns,const size_t rows)2068 static char *TraceSVGClippath(const unsigned char *blob,size_t length,
2069 const size_t columns,const size_t rows)
2070 {
2071 char
2072 *path,
2073 *message;
2074
2075 MagickBooleanType
2076 in_subpath;
2077
2078 PointInfo
2079 first[3],
2080 last[3],
2081 point[3];
2082
2083 ssize_t
2084 i;
2085
2086 ssize_t
2087 knot_count,
2088 selector,
2089 x,
2090 y;
2091
2092 path=AcquireString((char *) NULL);
2093 if (path == (char *) NULL)
2094 return((char *) NULL);
2095 message=AcquireString((char *) NULL);
2096 (void) FormatLocaleString(message,MaxTextExtent,(
2097 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
2098 "<svg xmlns=\"http://www.w3.org/2000/svg\""
2099 " width=\"%.20g\" height=\"%.20g\">\n"
2100 "<g>\n"
2101 "<path fill-rule=\"evenodd\" style=\"fill:#000000;stroke:#000000;"
2102 "stroke-width:0;stroke-antialiasing:false\" d=\"\n"),(double) columns,
2103 (double) rows);
2104 (void) ConcatenateString(&path,message);
2105 (void) memset(point,0,sizeof(point));
2106 (void) memset(first,0,sizeof(first));
2107 (void) memset(last,0,sizeof(last));
2108 knot_count=0;
2109 in_subpath=MagickFalse;
2110 while (length != 0)
2111 {
2112 selector=(ssize_t) ReadPropertyMSBShort(&blob,&length);
2113 switch (selector)
2114 {
2115 case 0:
2116 case 3:
2117 {
2118 if (knot_count != 0)
2119 {
2120 blob+=24;
2121 length-=MagickMin(24,(ssize_t) length);
2122 break;
2123 }
2124 /*
2125 Expected subpath length record.
2126 */
2127 knot_count=(ssize_t) ReadPropertyMSBShort(&blob,&length);
2128 blob+=22;
2129 length-=MagickMin(22,(ssize_t) length);
2130 break;
2131 }
2132 case 1:
2133 case 2:
2134 case 4:
2135 case 5:
2136 {
2137 if (knot_count == 0)
2138 {
2139 /*
2140 Unexpected subpath knot.
2141 */
2142 blob+=24;
2143 length-=MagickMin(24,(ssize_t) length);
2144 break;
2145 }
2146 /*
2147 Add sub-path knot.
2148 */
2149 for (i=0; i < 3; i++)
2150 {
2151 y=(ssize_t) ReadPropertyMSBLong(&blob,&length);
2152 x=(ssize_t) ReadPropertyMSBLong(&blob,&length);
2153 point[i].x=(double) x*columns/4096.0/4096.0;
2154 point[i].y=(double) y*rows/4096.0/4096.0;
2155 }
2156 if (in_subpath == MagickFalse)
2157 {
2158 (void) FormatLocaleString(message,MaxTextExtent,"M %g %g\n",
2159 point[1].x,point[1].y);
2160 for (i=0; i < 3; i++)
2161 {
2162 first[i]=point[i];
2163 last[i]=point[i];
2164 }
2165 }
2166 else
2167 {
2168 TraceBezierCurve(message,last,point);
2169 for (i=0; i < 3; i++)
2170 last[i]=point[i];
2171 }
2172 (void) ConcatenateString(&path,message);
2173 in_subpath=MagickTrue;
2174 knot_count--;
2175 /*
2176 Close the subpath if there are no more knots.
2177 */
2178 if (knot_count == 0)
2179 {
2180 TraceBezierCurve(message,last,first);
2181 (void) ConcatenateString(&path,message);
2182 in_subpath=MagickFalse;
2183 }
2184 break;
2185 }
2186 case 6:
2187 case 7:
2188 case 8:
2189 default:
2190 {
2191 blob+=24;
2192 length-=MagickMin(24,(ssize_t) length);
2193 break;
2194 }
2195 }
2196 }
2197 /*
2198 Return an empty SVG image if the path does not have knots.
2199 */
2200 (void) ConcatenateString(&path,"\"/>\n</g>\n</svg>\n");
2201 message=DestroyString(message);
2202 return(path);
2203 }
2204
GetImageProperty(const Image * image,const char * property)2205 MagickExport const char *GetImageProperty(const Image *image,
2206 const char *property)
2207 {
2208 double
2209 alpha;
2210
2211 ExceptionInfo
2212 *exception;
2213
2214 FxInfo
2215 *fx_info;
2216
2217 MagickStatusType
2218 status;
2219
2220 const char
2221 *p;
2222
2223 assert(image != (Image *) NULL);
2224 assert(image->signature == MagickCoreSignature);
2225 if (image->debug != MagickFalse)
2226 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2227 p=(const char *) NULL;
2228 if (image->properties != (void *) NULL)
2229 {
2230 if (property == (const char *) NULL)
2231 {
2232 ResetSplayTreeIterator((SplayTreeInfo *) image->properties);
2233 p=(const char *) GetNextValueInSplayTree((SplayTreeInfo *)
2234 image->properties);
2235 return(p);
2236 }
2237 if (LocaleNCompare("fx:",property,3) != 0) /* NOT fx: !!!! */
2238 {
2239 p=(const char *) GetValueFromSplayTree((SplayTreeInfo *)
2240 image->properties,property);
2241 if (p != (const char *) NULL)
2242 return(p);
2243 }
2244 }
2245 if ((property == (const char *) NULL) ||
2246 (strchr(property,':') == (char *) NULL))
2247 return(p);
2248 exception=(&((Image *) image)->exception);
2249 switch (*property)
2250 {
2251 case '8':
2252 {
2253 if (LocaleNCompare("8bim:",property,5) == 0)
2254 {
2255 (void) Get8BIMProperty(image,property);
2256 break;
2257 }
2258 break;
2259 }
2260 case 'E':
2261 case 'e':
2262 {
2263 if (LocaleNCompare("exif:",property,5) == 0)
2264 {
2265 (void) GetEXIFProperty(image,property);
2266 break;
2267 }
2268 break;
2269 }
2270 case 'F':
2271 case 'f':
2272 {
2273 if (LocaleNCompare("fx:",property,3) == 0)
2274 {
2275 if ((image->columns == 0) || (image->rows == 0))
2276 break;
2277 fx_info=AcquireFxInfo(image,property+3);
2278 status=FxEvaluateChannelExpression(fx_info,DefaultChannels,0,0,&alpha,
2279 exception);
2280 fx_info=DestroyFxInfo(fx_info);
2281 if (status != MagickFalse)
2282 {
2283 char
2284 value[MaxTextExtent];
2285
2286 (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
2287 GetMagickPrecision(),(double) alpha);
2288 (void) SetImageProperty((Image *) image,property,value);
2289 }
2290 break;
2291 }
2292 break;
2293 }
2294 case 'H':
2295 case 'h':
2296 {
2297 if (LocaleNCompare("hex:",property,4) == 0)
2298 {
2299 MagickPixelPacket
2300 pixel;
2301
2302 if ((image->columns == 0) || (image->rows == 0))
2303 break;
2304 GetMagickPixelPacket(image,&pixel);
2305 fx_info=AcquireFxInfo(image,property+4);
2306 status=FxEvaluateChannelExpression(fx_info,RedChannel,0,0,&alpha,
2307 exception);
2308 pixel.red=(MagickRealType) QuantumRange*alpha;
2309 status&=FxEvaluateChannelExpression(fx_info,GreenChannel,0,0,&alpha,
2310 exception);
2311 pixel.green=(MagickRealType) QuantumRange*alpha;
2312 status&=FxEvaluateChannelExpression(fx_info,BlueChannel,0,0,&alpha,
2313 exception);
2314 pixel.blue=(MagickRealType) QuantumRange*alpha;
2315 status&=FxEvaluateChannelExpression(fx_info,OpacityChannel,0,0,&alpha,
2316 exception);
2317 pixel.opacity=(MagickRealType) QuantumRange*(1.0-alpha);
2318 if (image->colorspace == CMYKColorspace)
2319 {
2320 status&=FxEvaluateChannelExpression(fx_info,BlackChannel,0,0,
2321 &alpha,exception);
2322 pixel.index=(MagickRealType) QuantumRange*alpha;
2323 }
2324 fx_info=DestroyFxInfo(fx_info);
2325 if (status != MagickFalse)
2326 {
2327 char
2328 hex[MaxTextExtent];
2329
2330 GetColorTuple(&pixel,MagickTrue,hex);
2331 (void) SetImageProperty((Image *) image,property,hex+1);
2332 }
2333 break;
2334 }
2335 break;
2336 }
2337 case 'I':
2338 case 'i':
2339 {
2340 if ((LocaleNCompare("icc:",property,4) == 0) ||
2341 (LocaleNCompare("icm:",property,4) == 0))
2342 {
2343 (void) GetICCProperty(image,property);
2344 break;
2345 }
2346 if (LocaleNCompare("iptc:",property,5) == 0)
2347 {
2348 (void) GetIPTCProperty(image,property);
2349 break;
2350 }
2351 break;
2352 }
2353 case 'P':
2354 case 'p':
2355 {
2356 if (LocaleNCompare("pixel:",property,6) == 0)
2357 {
2358 MagickPixelPacket
2359 pixel;
2360
2361 GetMagickPixelPacket(image,&pixel);
2362 fx_info=AcquireFxInfo(image,property+6);
2363 status=FxEvaluateChannelExpression(fx_info,RedChannel,0,0,&alpha,
2364 exception);
2365 pixel.red=(MagickRealType) QuantumRange*alpha;
2366 status&=FxEvaluateChannelExpression(fx_info,GreenChannel,0,0,&alpha,
2367 exception);
2368 pixel.green=(MagickRealType) QuantumRange*alpha;
2369 status&=FxEvaluateChannelExpression(fx_info,BlueChannel,0,0,&alpha,
2370 exception);
2371 pixel.blue=(MagickRealType) QuantumRange*alpha;
2372 status&=FxEvaluateChannelExpression(fx_info,OpacityChannel,0,0,&alpha,
2373 exception);
2374 pixel.opacity=(MagickRealType) QuantumRange*(1.0-alpha);
2375 if (image->colorspace == CMYKColorspace)
2376 {
2377 status&=FxEvaluateChannelExpression(fx_info,BlackChannel,0,0,
2378 &alpha,exception);
2379 pixel.index=(MagickRealType) QuantumRange*alpha;
2380 }
2381 fx_info=DestroyFxInfo(fx_info);
2382 if (status != MagickFalse)
2383 {
2384 char
2385 name[MaxTextExtent];
2386
2387 const char
2388 *value;
2389
2390 GetColorTuple(&pixel,MagickFalse,name);
2391 value=GetImageArtifact(image,"pixel:compliance");
2392 if (value != (char *) NULL)
2393 {
2394 ComplianceType compliance=(ComplianceType) ParseCommandOption(
2395 MagickComplianceOptions,MagickFalse,value);
2396 (void) QueryMagickColorname(image,&pixel,compliance,name,
2397 exception);
2398 }
2399 (void) SetImageProperty((Image *) image,property,name);
2400 }
2401 break;
2402 }
2403 break;
2404 }
2405 case 'X':
2406 case 'x':
2407 {
2408 if (LocaleNCompare("xmp:",property,4) == 0)
2409 {
2410 (void) GetXMPProperty(image,property);
2411 break;
2412 }
2413 break;
2414 }
2415 default:
2416 break;
2417 }
2418 if (image->properties != (void *) NULL)
2419 {
2420 p=(const char *) GetValueFromSplayTree((SplayTreeInfo *)
2421 image->properties,property);
2422 return(p);
2423 }
2424 return((const char *) NULL);
2425 }
2426
2427 /*
2428 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2429 % %
2430 % %
2431 % %
2432 + G e t M a g i c k P r o p e r t y %
2433 % %
2434 % %
2435 % %
2436 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2437 %
2438 % GetMagickProperty() gets attributes or calculated values that is associated
2439 % with a fixed known property name, or single letter property:
2440 %
2441 % \n newline
2442 % \r carriage return
2443 % < less-than character.
2444 % > greater-than character.
2445 % & ampersand character.
2446 % %% a percent sign
2447 % %b file size of image read in
2448 % %c comment meta-data property
2449 % %d directory component of path
2450 % %e filename extension or suffix
2451 % %f filename (including suffix)
2452 % %g layer canvas page geometry (equivalent to "%Wx%H%X%Y")
2453 % %h current image height in pixels
2454 % %i image filename (note: becomes output filename for "info:")
2455 % %k CALCULATED: number of unique colors
2456 % %l label meta-data property
2457 % %m image file format (file magic)
2458 % %n number of images in current image sequence
2459 % %o output filename (used for delegates)
2460 % %p index of image in current image list
2461 % %q quantum depth (compile-time constant)
2462 % %r image class and colorspace
2463 % %s scene number (from input unless re-assigned)
2464 % %t filename without directory or extension (suffix)
2465 % %u unique temporary filename (used for delegates)
2466 % %w current width in pixels
2467 % %x x resolution (density)
2468 % %y y resolution (density)
2469 % %z image depth (as read in unless modified, image save depth)
2470 % %A image transparency channel enabled (true/false)
2471 % %B file size of image in bytes
2472 % %C image compression type
2473 % %D image GIF dispose method
2474 % %G original image size (%wx%h; before any resizes)
2475 % %H page (canvas) height
2476 % %M Magick filename (original file exactly as given, including read mods)
2477 % %O page (canvas) offset ( = %X%Y )
2478 % %P page (canvas) size ( = %Wx%H )
2479 % %Q image compression quality ( 0 = default )
2480 % %S ?? scenes ??
2481 % %T image time delay (in centi-seconds)
2482 % %U image resolution units
2483 % %W page (canvas) width
2484 % %X page (canvas) x offset (including sign)
2485 % %Y page (canvas) y offset (including sign)
2486 % %Z unique filename (used for delegates)
2487 % %@ CALCULATED: trim bounding box (without actually trimming)
2488 % %# CALCULATED: 'signature' hash of image values
2489 %
2490 % This does not return, special profile or property expressions. Nor does it
2491 % return free-form property strings, unless referenced by a single letter
2492 % property name.
2493 %
2494 % The returned string is stored as the image artifact 'get-property' (not as
2495 % another property), and as such should not be freed. Later calls however
2496 % will overwrite this value so if needed for a longer period a copy should be
2497 % made. This artifact can be deleted when no longer required.
2498 %
2499 % The format of the GetMagickProperty method is:
2500 %
2501 % const char *GetMagickProperty(const ImageInfo *image_info,Image *image,
2502 % const char *property)
2503 %
2504 % A description of each parameter follows:
2505 %
2506 % o image_info: the image info.
2507 %
2508 % o image: the image.
2509 %
2510 % o key: the key.
2511 %
2512 */
GetMagickPropertyLetter(const ImageInfo * image_info,Image * image,const char letter)2513 static const char *GetMagickPropertyLetter(const ImageInfo *image_info,
2514 Image *image,const char letter)
2515 {
2516 #define WarnNoImageInfoReturn(format,arg) \
2517 if (image_info == (ImageInfo *) NULL ) { \
2518 (void) ThrowMagickException(&image->exception,GetMagickModule(), \
2519 OptionWarning,"NoImageInfoForProperty",format,arg); \
2520 return((const char *) NULL); \
2521 }
2522
2523 char
2524 value[MaxTextExtent];
2525
2526 const char
2527 *string;
2528
2529 assert(image != (Image *) NULL);
2530 assert(image->signature == MagickCoreSignature);
2531 if (image->debug != MagickFalse)
2532 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2533 *value='\0';
2534 string=(char *) NULL;
2535 switch (letter)
2536 {
2537 case 'b':
2538 {
2539 /*
2540 Image size read in - in bytes.
2541 */
2542 (void) FormatMagickSize(image->extent,MagickFalse,value);
2543 if (image->extent == 0)
2544 (void) FormatMagickSize(GetBlobSize(image),MagickFalse,value);
2545 break;
2546 }
2547 case 'c':
2548 {
2549 /*
2550 Image comment property - empty string by default.
2551 */
2552 string=GetImageProperty(image,"comment");
2553 if (string == (const char *) NULL)
2554 string="";
2555 break;
2556 }
2557 case 'd':
2558 {
2559 /*
2560 Directory component of filename.
2561 */
2562 GetPathComponent(image->magick_filename,HeadPath,value);
2563 if (*value == '\0')
2564 string="";
2565 break;
2566 }
2567 case 'e':
2568 {
2569 /*
2570 Filename extension (suffix) of image file.
2571 */
2572 GetPathComponent(image->magick_filename,ExtensionPath,value);
2573 if (*value == '\0')
2574 string="";
2575 break;
2576 }
2577 case 'f':
2578 {
2579 /*
2580 Filename without directory component.
2581 */
2582 GetPathComponent(image->magick_filename,TailPath,value);
2583 if (*value == '\0')
2584 string="";
2585 break;
2586 }
2587 case 'g':
2588 {
2589 /*
2590 Image geometry, canvas and offset %Wx%H+%X+%Y.
2591 */
2592 (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g%+.20g%+.20g",
2593 (double) image->page.width,(double) image->page.height,
2594 (double) image->page.x,(double) image->page.y);
2595 break;
2596 }
2597 case 'h':
2598 {
2599 /*
2600 Image height (current).
2601 */
2602 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2603 (image->rows != 0 ? image->rows : image->magick_rows));
2604 break;
2605 }
2606 case 'i':
2607 {
2608 /*
2609 Filename last used for image (read or write).
2610 */
2611 string=image->filename;
2612 break;
2613 }
2614 case 'k':
2615 {
2616 /*
2617 Number of unique colors.
2618 */
2619 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2620 GetNumberColors(image,(FILE *) NULL,&image->exception));
2621 break;
2622 }
2623 case 'l':
2624 {
2625 /*
2626 Image label property - empty string by default.
2627 */
2628 string=GetImageProperty(image,"label");
2629 if (string == (const char *) NULL)
2630 string="";
2631 break;
2632 }
2633 case 'm':
2634 {
2635 /*
2636 Image format (file magick).
2637 */
2638 string=image->magick;
2639 break;
2640 }
2641 case 'n':
2642 {
2643 /*
2644 Number of images in the list.
2645 */
2646 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2647 GetImageListLength(image));
2648 break;
2649 }
2650 case 'o':
2651 {
2652 /*
2653 Output Filename - for delegate use only
2654 */
2655 WarnNoImageInfoReturn("\"%%%c\"",letter);
2656 string=image_info->filename;
2657 break;
2658 }
2659 case 'p':
2660 {
2661 /*
2662 Image index in current image list -- As 'n' OBSOLETE.
2663 */
2664 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2665 GetImageIndexInList(image));
2666 break;
2667 }
2668 case 'q':
2669 {
2670 /*
2671 Quantum depth of image in memory.
2672 */
2673 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2674 MAGICKCORE_QUANTUM_DEPTH);
2675 break;
2676 }
2677 case 'r':
2678 {
2679 ColorspaceType
2680 colorspace;
2681
2682 /*
2683 Image storage class and colorspace.
2684 */
2685 colorspace=image->colorspace;
2686 if ((image->columns != 0) && (image->rows != 0) &&
2687 (SetImageGray(image,&image->exception) != MagickFalse))
2688 colorspace=GRAYColorspace;
2689 (void) FormatLocaleString(value,MaxTextExtent,"%s %s %s",
2690 CommandOptionToMnemonic(MagickClassOptions,(ssize_t)
2691 image->storage_class),CommandOptionToMnemonic(MagickColorspaceOptions,
2692 (ssize_t) colorspace),image->matte != MagickFalse ? "Matte" : "" );
2693 break;
2694 }
2695 case 's':
2696 {
2697 /*
2698 Image scene number.
2699 */
2700 WarnNoImageInfoReturn("\"%%%c\"",letter);
2701 if (image_info->number_scenes != 0)
2702 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2703 image_info->scene);
2704 else
2705 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2706 image->scene);
2707 break;
2708 }
2709 case 't':
2710 {
2711 /*
2712 Base filename without directory or extension.
2713 */
2714 GetPathComponent(image->magick_filename,BasePath,value);
2715 break;
2716 }
2717 case 'u':
2718 {
2719 /*
2720 Unique filename.
2721 */
2722 WarnNoImageInfoReturn("\"%%%c\"",letter);
2723 string=image_info->unique;
2724 break;
2725 }
2726 case 'w':
2727 {
2728 /*
2729 Image width (current).
2730 */
2731 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2732 (image->columns != 0 ? image->columns : image->magick_columns));
2733 break;
2734 }
2735 case 'x':
2736 {
2737 /*
2738 Image horizontal resolution.
2739 */
2740 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",
2741 fabs(image->x_resolution) > MagickEpsilon ? image->x_resolution :
2742 image->units == PixelsPerCentimeterResolution ? DefaultResolution/2.54 :
2743 DefaultResolution);
2744 break;
2745 }
2746 case 'y':
2747 {
2748 /*
2749 Image vertical resolution.
2750 */
2751 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",
2752 fabs(image->y_resolution) > MagickEpsilon ? image->y_resolution :
2753 image->units == PixelsPerCentimeterResolution ? DefaultResolution/2.54 :
2754 DefaultResolution);
2755 break;
2756 }
2757 case 'z':
2758 {
2759 /*
2760 Image depth.
2761 */
2762 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2763 image->depth);
2764 break;
2765 }
2766 case 'A':
2767 {
2768 /*
2769 Image alpha channel.
2770 */
2771 (void) FormatLocaleString(value,MaxTextExtent,"%s",
2772 CommandOptionToMnemonic(MagickBooleanOptions,(ssize_t) image->matte));
2773 break;
2774 }
2775 case 'B':
2776 {
2777 /*
2778 Image size read in - in bytes.
2779 */
2780 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2781 image->extent);
2782 if (image->extent == 0)
2783 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2784 GetBlobSize(image));
2785 break;
2786 }
2787 case 'C':
2788 {
2789 /*
2790 Image compression method.
2791 */
2792 (void) FormatLocaleString(value,MaxTextExtent,"%s",
2793 CommandOptionToMnemonic(MagickCompressOptions,(ssize_t)
2794 image->compression));
2795 break;
2796 }
2797 case 'D':
2798 {
2799 /*
2800 Image dispose method.
2801 */
2802 (void) FormatLocaleString(value,MaxTextExtent,"%s",
2803 CommandOptionToMnemonic(MagickDisposeOptions,(ssize_t) image->dispose));
2804 break;
2805 }
2806 case 'F':
2807 {
2808 const char
2809 *q;
2810
2811 char
2812 *p;
2813
2814 static char
2815 allowlist[] =
2816 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 "
2817 "$-_.+!*'(),{}|\\^~[]`\"><#%;/?:@&=";
2818
2819 /*
2820 Magick filename (sanitized) - filename given incl. coder & read mods.
2821 */
2822 (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
2823 p=value;
2824 q=value+strlen(value);
2825 for (p+=strspn(p,allowlist); p != q; p+=strspn(p,allowlist))
2826 *p='_';
2827 break;
2828 }
2829 case 'G':
2830 {
2831 /*
2832 Image size as geometry = "%wx%h".
2833 */
2834 (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g",(double)
2835 image->magick_columns,(double) image->magick_rows);
2836 break;
2837 }
2838 case 'H':
2839 {
2840 /*
2841 Layer canvas height.
2842 */
2843 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2844 image->page.height);
2845 break;
2846 }
2847 case 'M':
2848 {
2849 /*
2850 Magick filename - filename given incl. coder & read mods.
2851 */
2852 string=image->magick_filename;
2853 break;
2854 }
2855 case 'N': /* Number of images in the list. */
2856 {
2857 if ((image != (Image *) NULL) && (image->next == (Image *) NULL))
2858 (void) FormatLocaleString(value,MagickPathExtent,"%.20g\n",(double)
2859 GetImageListLength(image));
2860 else
2861 string="";
2862 break;
2863 }
2864 case 'O':
2865 {
2866 /*
2867 Layer canvas offset with sign = "+%X+%Y".
2868 */
2869 (void) FormatLocaleString(value,MaxTextExtent,"%+ld%+ld",(long)
2870 image->page.x,(long) image->page.y);
2871 break;
2872 }
2873 case 'P':
2874 {
2875 /*
2876 Layer canvas page size = "%Wx%H".
2877 */
2878 (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g",(double)
2879 image->page.width,(double) image->page.height);
2880 break;
2881 }
2882 case 'Q':
2883 {
2884 /*
2885 Image compression quality.
2886 */
2887 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2888 (image->quality == 0 ? 92 : image->quality));
2889 break;
2890 }
2891 case 'S':
2892 {
2893 /*
2894 Image scenes.
2895 */
2896 WarnNoImageInfoReturn("\"%%%c\"",letter);
2897 if (image_info->number_scenes == 0)
2898 string="2147483647";
2899 else
2900 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2901 image_info->scene+image_info->number_scenes);
2902 break;
2903 }
2904 case 'T':
2905 {
2906 /*
2907 Image time delay for animations.
2908 */
2909 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2910 image->delay);
2911 break;
2912 }
2913 case 'U':
2914 {
2915 /*
2916 Image resolution units.
2917 */
2918 (void) FormatLocaleString(value,MaxTextExtent,"%s",
2919 CommandOptionToMnemonic(MagickResolutionOptions,(ssize_t)
2920 image->units));
2921 break;
2922 }
2923 case 'W':
2924 {
2925 /*
2926 Layer canvas width.
2927 */
2928 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2929 image->page.width);
2930 break;
2931 }
2932 case 'X':
2933 {
2934 /*
2935 Layer canvas X offset.
2936 */
2937 (void) FormatLocaleString(value,MaxTextExtent,"%+.20g",(double)
2938 image->page.x);
2939 break;
2940 }
2941 case 'Y':
2942 {
2943 /*
2944 Layer canvas Y offset.
2945 */
2946 (void) FormatLocaleString(value,MaxTextExtent,"%+.20g",(double)
2947 image->page.y);
2948 break;
2949 }
2950 case 'Z':
2951 {
2952 /*
2953 Zero filename.
2954 */
2955 WarnNoImageInfoReturn("\"%%%c\"",letter);
2956 string=image_info->zero;
2957 break;
2958 }
2959 case '@':
2960 {
2961 RectangleInfo
2962 page;
2963
2964 /*
2965 Image bounding box.
2966 */
2967 page=GetImageBoundingBox(image,&image->exception);
2968 (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g%+.20g%+.20g",
2969 (double) page.width,(double) page.height,(double) page.x,(double)
2970 page.y);
2971 break;
2972 }
2973 case '#':
2974 {
2975 /*
2976 Image signature.
2977 */
2978 if ((image->columns != 0) && (image->rows != 0))
2979 (void) SignatureImage(image);
2980 string=GetImageProperty(image,"signature");
2981 break;
2982 }
2983 case '%':
2984 {
2985 /*
2986 Percent escaped.
2987 */
2988 string="%";
2989 break;
2990 }
2991 }
2992 if (*value != '\0')
2993 string=value;
2994 if (string != (char *) NULL)
2995 {
2996 (void) SetImageArtifact(image,"get-property",string);
2997 return(GetImageArtifact(image,"get-property"));
2998 }
2999 return((char *) NULL);
3000 }
3001
GetMagickProperty(const ImageInfo * image_info,Image * image,const char * property)3002 MagickExport const char *GetMagickProperty(const ImageInfo *image_info,
3003 Image *image,const char *property)
3004 {
3005 char
3006 value[MaxTextExtent];
3007
3008 const char
3009 *string;
3010
3011 assert(property[0] != '\0');
3012 if (property[1] == '\0') /* single letter property request */
3013 return(GetMagickPropertyLetter(image_info,image,*property));
3014 *value='\0'; /* formatted string */
3015 string=(char *) NULL; /* constant string reference */
3016 switch (*property)
3017 {
3018 case 'b':
3019 {
3020 if ((LocaleCompare("base",property) == 0) ||
3021 (LocaleCompare("basename",property) == 0) )
3022 {
3023 GetPathComponent(image->magick_filename,BasePath,value);
3024 break;
3025 }
3026 if (LocaleCompare("bit-depth",property) == 0)
3027 {
3028 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3029 GetImageDepth(image,&image->exception));
3030 break;
3031 }
3032 if (LocaleCompare("bounding-box",property) == 0)
3033 {
3034 RectangleInfo
3035 geometry;
3036
3037 geometry=GetImageBoundingBox(image,&image->exception);
3038 (void) FormatLocaleString(value,MagickPathExtent,"%g,%g %g,%g\n",
3039 (double) geometry.x,(double) geometry.y,
3040 (double) geometry.x+geometry.width,
3041 (double) geometry.y+geometry.height);
3042 break;
3043 }
3044 break;
3045 }
3046 case 'c':
3047 {
3048 if (LocaleCompare("channels",property) == 0)
3049 {
3050 /*
3051 Image channels.
3052 */
3053 (void) FormatLocaleString(value,MaxTextExtent,"%s",
3054 CommandOptionToMnemonic(MagickColorspaceOptions,(ssize_t)
3055 image->colorspace));
3056 LocaleLower(value);
3057 if (image->matte != MagickFalse)
3058 (void) ConcatenateMagickString(value,"a",MaxTextExtent);
3059 break;
3060 }
3061 if (LocaleCompare("colors",property) == 0)
3062 {
3063 image->colors=GetNumberColors(image,(FILE *) NULL,&image->exception);
3064 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3065 image->colors);
3066 break;
3067 }
3068 if (LocaleCompare("colorspace",property) == 0)
3069 {
3070 string=CommandOptionToMnemonic(MagickColorspaceOptions,(ssize_t)
3071 image->colorspace);
3072 break;
3073 }
3074 if (LocaleCompare("compose",property) == 0)
3075 {
3076 string=CommandOptionToMnemonic(MagickComposeOptions,(ssize_t)
3077 image->compose);
3078 break;
3079 }
3080 if (LocaleCompare("compression",property) == 0)
3081 {
3082 string=CommandOptionToMnemonic(MagickCompressOptions,(ssize_t)
3083 image->compression);
3084 break;
3085 }
3086 if (LocaleCompare("copyright",property) == 0)
3087 {
3088 (void) CopyMagickString(value,GetMagickCopyright(),MaxTextExtent);
3089 break;
3090 }
3091 break;
3092 }
3093 case 'd':
3094 {
3095 if (LocaleCompare("depth",property) == 0)
3096 {
3097 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3098 image->depth);
3099 break;
3100 }
3101 if (LocaleCompare("directory",property) == 0)
3102 {
3103 GetPathComponent(image->magick_filename,HeadPath,value);
3104 break;
3105 }
3106 break;
3107 }
3108 case 'e':
3109 {
3110 if (LocaleCompare("entropy",property) == 0)
3111 {
3112 double
3113 entropy;
3114
3115 (void) GetImageChannelEntropy(image,image_info->channel,&entropy,
3116 &image->exception);
3117 (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3118 GetMagickPrecision(),entropy);
3119 break;
3120 }
3121 if (LocaleCompare("extension",property) == 0)
3122 {
3123 GetPathComponent(image->magick_filename,ExtensionPath,value);
3124 break;
3125 }
3126 break;
3127 }
3128 case 'g':
3129 {
3130 if (LocaleCompare("gamma",property) == 0)
3131 {
3132 (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3133 GetMagickPrecision(),image->gamma);
3134 break;
3135 }
3136 if ((image_info != (ImageInfo *) NULL) &&
3137 (LocaleCompare("group",property) == 0))
3138 {
3139 (void) FormatLocaleString(value,MaxTextExtent,"0x%lx",(unsigned long)
3140 image_info->group);
3141 break;
3142 }
3143 break;
3144 }
3145 case 'h':
3146 {
3147 if (LocaleCompare("height",property) == 0)
3148 {
3149 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",
3150 image->magick_rows != 0 ? (double) image->magick_rows : 256.0);
3151 break;
3152 }
3153 break;
3154 }
3155 case 'i':
3156 {
3157 if (LocaleCompare("input",property) == 0)
3158 {
3159 string=image->filename;
3160 break;
3161 }
3162 if (LocaleCompare("interlace",property) == 0)
3163 {
3164 string=CommandOptionToMnemonic(MagickInterlaceOptions,(ssize_t)
3165 image->interlace);
3166 break;
3167 }
3168 break;
3169 }
3170 case 'k':
3171 {
3172 if (LocaleCompare("kurtosis",property) == 0)
3173 {
3174 double
3175 kurtosis,
3176 skewness;
3177
3178 (void) GetImageChannelKurtosis(image,image_info->channel,&kurtosis,
3179 &skewness,&image->exception);
3180 (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3181 GetMagickPrecision(),kurtosis);
3182 break;
3183 }
3184 break;
3185 }
3186 case 'm':
3187 {
3188 if (LocaleCompare("magick",property) == 0)
3189 {
3190 string=image->magick;
3191 break;
3192 }
3193 if ((LocaleCompare("max",property) == 0) ||
3194 (LocaleCompare("maxima",property) == 0))
3195 {
3196 double
3197 maximum,
3198 minimum;
3199
3200 (void) GetImageChannelRange(image,image_info->channel,&minimum,
3201 &maximum,&image->exception);
3202 (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3203 GetMagickPrecision(),maximum);
3204 break;
3205 }
3206 if (LocaleCompare("mean",property) == 0)
3207 {
3208 double
3209 mean,
3210 standard_deviation;
3211
3212 (void) GetImageChannelMean(image,image_info->channel,&mean,
3213 &standard_deviation,&image->exception);
3214 (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3215 GetMagickPrecision(),mean);
3216 break;
3217 }
3218 if ((LocaleCompare("min",property) == 0) ||
3219 (LocaleCompare("minima",property) == 0))
3220 {
3221 double
3222 maximum,
3223 minimum;
3224
3225 (void) GetImageChannelRange(image,image_info->channel,&minimum,
3226 &maximum,&image->exception);
3227 (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3228 GetMagickPrecision(),minimum);
3229 break;
3230 }
3231 break;
3232 }
3233 case 'o':
3234 {
3235 if (LocaleCompare("opaque",property) == 0)
3236 {
3237 MagickBooleanType
3238 opaque;
3239
3240 opaque=IsOpaqueImage(image,&image->exception);
3241 (void) CopyMagickString(value,opaque != MagickFalse ? "true" :
3242 "false",MaxTextExtent);
3243 break;
3244 }
3245 if (LocaleCompare("orientation",property) == 0)
3246 {
3247 string=CommandOptionToMnemonic(MagickOrientationOptions,(ssize_t)
3248 image->orientation);
3249 break;
3250 }
3251 if ((image_info != (ImageInfo *) NULL) &&
3252 (LocaleCompare("output",property) == 0))
3253 {
3254 (void) CopyMagickString(value,image_info->filename,MaxTextExtent);
3255 break;
3256 }
3257 break;
3258 }
3259 case 'p':
3260 {
3261 if (LocaleCompare("page",property) == 0)
3262 {
3263 (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g",(double)
3264 image->page.width,(double) image->page.height);
3265 break;
3266 }
3267 if (LocaleNCompare("papersize:",property,10) == 0)
3268 {
3269 const char
3270 *papersize;
3271
3272 *value='\0';
3273 papersize=GetPageGeometry(property+10);
3274 if (papersize != (const char *) NULL)
3275 {
3276 RectangleInfo
3277 page;
3278
3279 (void) ParseAbsoluteGeometry(papersize,&page);
3280 (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g",
3281 (double) page.width,(double) page.height);
3282 }
3283 break;
3284 }
3285 #if defined(MAGICKCORE_LCMS_DELEGATE)
3286 if (LocaleCompare("profile:icc",property) == 0 ||
3287 LocaleCompare("profile:icm",property) == 0)
3288 {
3289 #if !defined(LCMS_VERSION) || (LCMS_VERSION < 2000)
3290 #define cmsUInt32Number DWORD
3291 #endif
3292
3293 const StringInfo
3294 *profile;
3295
3296 cmsHPROFILE
3297 icc_profile;
3298
3299 profile=GetImageProfile(image,property+8);
3300 if (profile == (StringInfo *) NULL)
3301 break;
3302
3303 icc_profile=cmsOpenProfileFromMem(GetStringInfoDatum(profile),
3304 (cmsUInt32Number) GetStringInfoLength(profile));
3305 if (icc_profile != (cmsHPROFILE *) NULL)
3306 {
3307 #if defined(LCMS_VERSION) && (LCMS_VERSION < 2000)
3308 string=cmsTakeProductName(icc_profile);
3309 #else
3310 (void) cmsGetProfileInfoASCII(icc_profile,cmsInfoDescription,
3311 "en","US",value,MaxTextExtent);
3312 #endif
3313 (void) cmsCloseProfile(icc_profile);
3314 }
3315 }
3316 #endif
3317 if (LocaleCompare("printsize.x",property) == 0)
3318 {
3319 (void) FormatLocaleString(value,MagickPathExtent,"%.*g",
3320 GetMagickPrecision(),PerceptibleReciprocal(image->x_resolution)*
3321 image->columns);
3322 break;
3323 }
3324 if (LocaleCompare("printsize.y",property) == 0)
3325 {
3326 (void) FormatLocaleString(value,MagickPathExtent,"%.*g",
3327 GetMagickPrecision(),PerceptibleReciprocal(image->y_resolution)*
3328 image->rows);
3329 break;
3330 }
3331 if (LocaleCompare("profiles",property) == 0)
3332 {
3333 const char
3334 *name;
3335
3336 ResetImageProfileIterator(image);
3337 name=GetNextImageProfile(image);
3338 if (name != (char *) NULL)
3339 {
3340 (void) CopyMagickString(value,name,MaxTextExtent);
3341 name=GetNextImageProfile(image);
3342 while (name != (char *) NULL)
3343 {
3344 ConcatenateMagickString(value,",",MaxTextExtent);
3345 ConcatenateMagickString(value,name,MaxTextExtent);
3346 name=GetNextImageProfile(image);
3347 }
3348 }
3349 break;
3350 }
3351 break;
3352 }
3353 case 'q':
3354 {
3355 if (LocaleCompare("quality",property) == 0)
3356 {
3357 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3358 image->quality);
3359 break;
3360 }
3361 break;
3362 }
3363 case 'r':
3364 {
3365 if (LocaleCompare("rendering-intent",property) == 0)
3366 {
3367 string=CommandOptionToMnemonic(MagickIntentOptions,(ssize_t)
3368 image->rendering_intent);
3369 break;
3370 }
3371 if (LocaleCompare("resolution.x",property) == 0)
3372 {
3373 (void) FormatLocaleString(value,MaxTextExtent,"%g",
3374 image->x_resolution);
3375 break;
3376 }
3377 if (LocaleCompare("resolution.y",property) == 0)
3378 {
3379 (void) FormatLocaleString(value,MaxTextExtent,"%g",
3380 image->y_resolution);
3381 break;
3382 }
3383 break;
3384 }
3385 case 's':
3386 {
3387 if (LocaleCompare("scene",property) == 0)
3388 {
3389 if ((image_info != (ImageInfo *) NULL) &&
3390 (image_info->number_scenes != 0))
3391 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3392 image_info->scene);
3393 else
3394 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3395 image->scene);
3396 break;
3397 }
3398 if (LocaleCompare("scenes",property) == 0)
3399 {
3400 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3401 GetImageListLength(image));
3402 break;
3403 }
3404 if (LocaleCompare("size",property) == 0)
3405 {
3406 (void) FormatMagickSize(GetBlobSize(image),MagickFalse,value);
3407 break;
3408 }
3409 if (LocaleCompare("skewness",property) == 0)
3410 {
3411 double
3412 kurtosis,
3413 skewness;
3414
3415 (void) GetImageChannelKurtosis(image,image_info->channel,&kurtosis,
3416 &skewness,&image->exception);
3417 (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3418 GetMagickPrecision(),skewness);
3419 break;
3420 }
3421 if ((LocaleCompare("standard-deviation",property) == 0) ||
3422 (LocaleCompare("standard_deviation",property) == 0))
3423 {
3424 double
3425 mean,
3426 standard_deviation;
3427
3428 (void) GetImageChannelMean(image,image_info->channel,&mean,
3429 &standard_deviation,&image->exception);
3430 (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3431 GetMagickPrecision(),standard_deviation);
3432 break;
3433 }
3434 break;
3435 }
3436 case 't':
3437 {
3438 if (LocaleCompare("type",property) == 0)
3439 {
3440 string=CommandOptionToMnemonic(MagickTypeOptions,(ssize_t)
3441 IdentifyImageType(image,&image->exception));
3442 break;
3443 }
3444 break;
3445 }
3446 case 'u':
3447 {
3448 if ((image_info != (ImageInfo *) NULL) &&
3449 (LocaleCompare("unique",property) == 0))
3450 {
3451 string=image_info->unique;
3452 break;
3453 }
3454 if (LocaleCompare("units",property) == 0)
3455 {
3456 /*
3457 Image resolution units.
3458 */
3459 string=CommandOptionToMnemonic(MagickResolutionOptions,(ssize_t)
3460 image->units);
3461 break;
3462 }
3463 break;
3464 }
3465 case 'v':
3466 {
3467 if (LocaleCompare("version",property) == 0)
3468 {
3469 string=GetMagickVersion((size_t *) NULL);
3470 break;
3471 }
3472 break;
3473 }
3474 case 'w':
3475 {
3476 if (LocaleCompare("width",property) == 0)
3477 {
3478 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3479 (image->magick_columns != 0 ? image->magick_columns : 256));
3480 break;
3481 }
3482 break;
3483 }
3484 case 'x': /* FUTURE: Obsolete X resolution */
3485 {
3486 if ((LocaleCompare("xresolution",property) == 0) ||
3487 (LocaleCompare("x-resolution",property) == 0) )
3488 {
3489 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",
3490 image->x_resolution);
3491 break;
3492 }
3493 break;
3494 }
3495 case 'y': /* FUTURE: Obsolete Y resolution */
3496 {
3497 if ((LocaleCompare("yresolution",property) == 0) ||
3498 (LocaleCompare("y-resolution",property) == 0) )
3499 {
3500 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",
3501 image->y_resolution);
3502 break;
3503 }
3504 break;
3505 }
3506 case 'z':
3507 {
3508 if ((image_info != (ImageInfo *) NULL) &&
3509 (LocaleCompare("zero",property) == 0))
3510 {
3511 string=image_info->zero;
3512 break;
3513 }
3514 break;
3515 }
3516 }
3517 if (*value != '\0')
3518 string=value;
3519 if (string != (char *) NULL)
3520 {
3521 (void) SetImageArtifact(image,"get-property", string);
3522 return(GetImageArtifact(image,"get-property"));
3523 }
3524 return((char *) NULL);
3525 }
3526
3527 /*
3528 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3529 % %
3530 % %
3531 % %
3532 % G e t N e x t I m a g e P r o p e r t y %
3533 % %
3534 % %
3535 % %
3536 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3537 %
3538 % GetNextImageProperty() gets the next free-form string property name.
3539 %
3540 % The format of the GetNextImageProperty method is:
3541 %
3542 % char *GetNextImageProperty(const Image *image)
3543 %
3544 % A description of each parameter follows:
3545 %
3546 % o image: the image.
3547 %
3548 */
GetNextImageProperty(const Image * image)3549 MagickExport char *GetNextImageProperty(const Image *image)
3550 {
3551 assert(image != (Image *) NULL);
3552 assert(image->signature == MagickCoreSignature);
3553 if (image->debug != MagickFalse)
3554 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
3555 image->filename);
3556 if (image->properties == (void *) NULL)
3557 return((char *) NULL);
3558 return((char *) GetNextKeyInSplayTree((SplayTreeInfo *) image->properties));
3559 }
3560
3561 /*
3562 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3563 % %
3564 % %
3565 % %
3566 % I n t e r p r e t I m a g e P r o p e r t i e s %
3567 % %
3568 % %
3569 % %
3570 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3571 %
3572 % InterpretImageProperties() replaces any embedded formatting characters with
3573 % the appropriate image property and returns the interpreted text.
3574 %
3575 % This searches for and replaces
3576 % \n \r \% replaced by newline, return, and percent resp.
3577 % < > & replaced by '<', '>', '&' resp.
3578 % %% replaced by percent
3579 %
3580 % %x %[x] where 'x' is a single letter properity, case sensitive).
3581 % %[type:name] where 'type' a is special and known prefix.
3582 % %[name] where 'name' is a specifically known attribute, calculated
3583 % value, or a per-image property string name, or a per-image
3584 % 'artifact' (as generated from a global option).
3585 % It may contain ':' as long as the prefix is not special.
3586 %
3587 % Single letter % substitutions will only happen if the character before the
3588 % percent is NOT a number. But braced substitutions will always be performed.
3589 % This prevents the typical usage of percent in a interpreted geometry
3590 % argument from being substituted when the percent is a geometry flag.
3591 %
3592 % If 'glob-expresions' ('*' or '?' characters) is used for 'name' it may be
3593 % used as a search pattern to print multiple lines of "name=value\n" pairs of
3594 % the associacted set of properities.
3595 %
3596 % The returned string must be freed using DestoryString() by the caller.
3597 %
3598 % The format of the InterpretImageProperties method is:
3599 %
3600 % char *InterpretImageProperties(const ImageInfo *image_info,Image *image,
3601 % const char *embed_text)
3602 %
3603 % A description of each parameter follows:
3604 %
3605 % o image_info: the image info.
3606 %
3607 % o image: the image.
3608 %
3609 % o embed_text: the address of a character string containing the embedded
3610 % formatting characters.
3611 %
3612 */
InterpretImageProperties(const ImageInfo * image_info,Image * image,const char * embed_text)3613 MagickExport char *InterpretImageProperties(const ImageInfo *image_info,
3614 Image *image,const char *embed_text)
3615 {
3616 #define ExtendInterpretText(string_length) \
3617 { \
3618 size_t length=(string_length); \
3619 if ((size_t) (q-interpret_text+length+1) >= extent) \
3620 { \
3621 extent+=length; \
3622 interpret_text=(char *) ResizeQuantumMemory(interpret_text,extent+ \
3623 MaxTextExtent,sizeof(*interpret_text)); \
3624 if (interpret_text == (char *) NULL) \
3625 { \
3626 if (property_info != image_info) \
3627 property_info=DestroyImageInfo(property_info); \
3628 return((char *) NULL); \
3629 } \
3630 q=interpret_text+strlen(interpret_text); \
3631 } \
3632 }
3633
3634 #define AppendKeyValue2Text(key,value)\
3635 { \
3636 size_t length=strlen(key)+strlen(value)+2; \
3637 if ((size_t) (q-interpret_text+length+1) >= extent) \
3638 { \
3639 extent+=length; \
3640 interpret_text=(char *) ResizeQuantumMemory(interpret_text,extent+ \
3641 MaxTextExtent,sizeof(*interpret_text)); \
3642 if (interpret_text == (char *) NULL) \
3643 { \
3644 if (property_info != image_info) \
3645 property_info=DestroyImageInfo(property_info); \
3646 return((char *) NULL); \
3647 } \
3648 q=interpret_text+strlen(interpret_text); \
3649 } \
3650 q+=FormatLocaleString(q,extent,"%s=%s\n",(key),(value)); \
3651 }
3652
3653 #define AppendString2Text(string) \
3654 { \
3655 size_t length=strlen((string)); \
3656 if ((size_t) (q-interpret_text+length+1) >= extent) \
3657 { \
3658 extent+=length; \
3659 interpret_text=(char *) ResizeQuantumMemory(interpret_text,extent+ \
3660 MaxTextExtent,sizeof(*interpret_text)); \
3661 if (interpret_text == (char *) NULL) \
3662 { \
3663 if (property_info != image_info) \
3664 property_info=DestroyImageInfo(property_info); \
3665 return((char *) NULL); \
3666 } \
3667 q=interpret_text+strlen(interpret_text); \
3668 } \
3669 (void) CopyMagickString(q,(string),extent); \
3670 q+=length; \
3671 }
3672
3673 char
3674 *interpret_text;
3675
3676 ImageInfo
3677 *property_info;
3678
3679 char
3680 *q; /* current position in interpret_text */
3681
3682 const char
3683 *p; /* position in embed_text string being expanded */
3684
3685 size_t
3686 extent; /* allocated length of interpret_text */
3687
3688 MagickBooleanType
3689 number;
3690
3691 assert(image != (Image *) NULL);
3692 assert(image->signature == MagickCoreSignature);
3693 if (image->debug != MagickFalse)
3694 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3695 if (embed_text == (const char *) NULL)
3696 return(ConstantString(""));
3697 p=embed_text;
3698 while ((isspace((int) ((unsigned char) *p)) != 0) && (*p != '\0'))
3699 p++;
3700 if (*p == '\0')
3701 return(ConstantString(""));
3702 if ((*p == '@') && (IsPathAccessible(p+1) != MagickFalse))
3703 {
3704 /*
3705 Replace string from file.
3706 */
3707 if (IsRightsAuthorized(PathPolicyDomain,ReadPolicyRights,p) == MagickFalse)
3708 {
3709 errno=EPERM;
3710 (void) ThrowMagickException(&image->exception,GetMagickModule(),
3711 PolicyError,"NotAuthorized","`%s'",p);
3712 return(ConstantString(""));
3713 }
3714 interpret_text=FileToString(p+1,~0UL,&image->exception);
3715 if (interpret_text != (char *) NULL)
3716 return(interpret_text);
3717 }
3718 /*
3719 Translate any embedded format characters.
3720 */
3721 if (image_info != (ImageInfo *) NULL)
3722 property_info=(ImageInfo *) image_info;
3723 else
3724 property_info=CloneImageInfo(image_info);
3725 interpret_text=AcquireString(embed_text); /* new string with extra space */
3726 extent=MaxTextExtent; /* how many extra space */
3727 number=MagickFalse; /* is last char a number? */
3728 for (q=interpret_text; *p!='\0';
3729 number=(isdigit((int) ((unsigned char) *p))) ? MagickTrue : MagickFalse,p++)
3730 {
3731 /*
3732 Look for the various escapes, (and handle other specials).
3733 */
3734 *q='\0';
3735 ExtendInterpretText(MaxTextExtent);
3736 switch (*p)
3737 {
3738 case '\\':
3739 {
3740 switch (*(p+1))
3741 {
3742 case '\0':
3743 continue;
3744 case 'r': /* convert to RETURN */
3745 {
3746 *q++='\r';
3747 p++;
3748 continue;
3749 }
3750 case 'n': /* convert to NEWLINE */
3751 {
3752 *q++='\n';
3753 p++;
3754 continue;
3755 }
3756 case '\n': /* EOL removal UNIX,MacOSX */
3757 {
3758 p++;
3759 continue;
3760 }
3761 case '\r': /* EOL removal DOS,Windows */
3762 {
3763 p++;
3764 if (*p == '\n') /* return-newline EOL */
3765 p++;
3766 continue;
3767 }
3768 default:
3769 {
3770 p++;
3771 *q++=(*p);
3772 }
3773 }
3774 continue;
3775 }
3776 case '&':
3777 {
3778 if (LocaleNCompare("<",p,4) == 0)
3779 {
3780 *q++='<';
3781 p+=3;
3782 }
3783 else
3784 if (LocaleNCompare(">",p,4) == 0)
3785 {
3786 *q++='>';
3787 p+=3;
3788 }
3789 else
3790 if (LocaleNCompare("&",p,5) == 0)
3791 {
3792 *q++='&';
3793 p+=4;
3794 }
3795 else
3796 *q++=(*p);
3797 continue;
3798 }
3799 case '%':
3800 break; /* continue to next set of handlers */
3801 default:
3802 {
3803 *q++=(*p); /* any thing else is 'as normal' */
3804 continue;
3805 }
3806 }
3807 p++; /* advance beyond the percent */
3808 /*
3809 Doubled percent - or percent at end of string.
3810 */
3811 if ((*p == '\0') || (*p == '\'') || (*p == '"'))
3812 p--;
3813 if (*p == '%')
3814 {
3815 *q++='%';
3816 continue;
3817 }
3818 /*
3819 Single letter escapes %c.
3820 */
3821 if (*p != '[')
3822 {
3823 const char
3824 *value;
3825
3826 /* But only if not preceeded by a number! */
3827 if (number != MagickFalse)
3828 {
3829 *q++='%'; /* do NOT substitute the percent */
3830 p--; /* back up one */
3831 continue;
3832 }
3833 value=GetMagickPropertyLetter(property_info,image,*p);
3834 if (value != (char *) NULL)
3835 {
3836 AppendString2Text(value);
3837 continue;
3838 }
3839 (void) ThrowMagickException(&image->exception,GetMagickModule(),
3840 OptionWarning,"UnknownImageProperty","\"%%%c\"",*p);
3841 continue;
3842 }
3843 {
3844 char
3845 pattern[2*MaxTextExtent];
3846
3847 const char
3848 *key,
3849 *value;
3850
3851 ssize_t
3852 len;
3853
3854 ssize_t
3855 depth;
3856
3857 /*
3858 Braced Percent Escape %[...]
3859 */
3860 p++; /* advance p to just inside the opening brace */
3861 depth=1;
3862 if ( *p == ']' )
3863 {
3864 (void) ThrowMagickException(&image->exception,GetMagickModule(),
3865 OptionWarning,"UnknownImageProperty","\"%%[]\"");
3866 break;
3867 }
3868 for (len=0; len<(MaxTextExtent-1L) && (*p != '\0');)
3869 {
3870 if ((*p == '\\') && (*(p+1) != '\0'))
3871 {
3872 /*
3873 Skip escaped braces within braced pattern.
3874 */
3875 pattern[len++]=(*p++);
3876 pattern[len++]=(*p++);
3877 continue;
3878 }
3879 if (*p == '[')
3880 depth++;
3881 if (*p == ']')
3882 depth--;
3883 if (depth <= 0)
3884 break;
3885 pattern[len++]=(*p++);
3886 }
3887 pattern[len]='\0';
3888 if (depth != 0)
3889 {
3890 /*
3891 Check for unmatched final ']' for "%[...]".
3892 */
3893 if (len >= 64)
3894 {
3895 pattern[61] = '.'; /* truncate string for error message */
3896 pattern[62] = '.';
3897 pattern[63] = '.';
3898 pattern[64] = '\0';
3899 }
3900 (void) ThrowMagickException(&image->exception,GetMagickModule(),
3901 OptionError,"UnbalancedBraces","\"%%[%s\"",pattern);
3902 interpret_text=DestroyString(interpret_text);
3903 if (property_info != image_info)
3904 property_info=DestroyImageInfo(property_info);
3905 return((char *) NULL);
3906 }
3907 /*
3908 Special Lookup Prefixes %[prefix:...]
3909 */
3910 if (LocaleNCompare("fx:",pattern,3) == 0)
3911 {
3912 double
3913 value;
3914
3915 FxInfo
3916 *fx_info;
3917
3918 MagickBooleanType
3919 status;
3920
3921 /*
3922 FX - value calculator.
3923 */
3924 fx_info=AcquireFxInfo(image,pattern+3);
3925 status=FxEvaluateChannelExpression(fx_info,property_info->channel,0,0,
3926 &value,&image->exception);
3927 fx_info=DestroyFxInfo(fx_info);
3928 if (status != MagickFalse)
3929 {
3930 char
3931 result[MagickPathExtent];
3932
3933 (void) FormatLocaleString(result,MagickPathExtent,"%.*g",
3934 GetMagickPrecision(),(double) value);
3935 AppendString2Text(result);
3936 }
3937 continue;
3938 }
3939 if (LocaleNCompare("option:",pattern,7) == 0)
3940 {
3941 /*
3942 Option - direct global option lookup (with globbing).
3943 */
3944 if (IsGlob(pattern+7) != MagickFalse)
3945 {
3946 ResetImageOptionIterator(property_info);
3947 while ((key=GetNextImageOption(property_info)) != (const char *) NULL)
3948 if (GlobExpression(key,pattern+7,MagickTrue) != MagickFalse)
3949 {
3950 value=GetImageOption(property_info,key);
3951 if (value != (const char *) NULL)
3952 AppendKeyValue2Text(key,value);
3953 /* else - assertion failure? key but no value! */
3954 }
3955 continue;
3956 }
3957 value=GetImageOption(property_info,pattern+7);
3958 if (value != (char *) NULL)
3959 AppendString2Text(value);
3960 /* else - no global option of this specifc name */
3961 continue;
3962 }
3963 if (LocaleNCompare("artifact:",pattern,9) == 0)
3964 {
3965 /*
3966 Artifact - direct image artifact lookup (with glob).
3967 */
3968 if (IsGlob(pattern+9) != MagickFalse)
3969 {
3970 ResetImageArtifactIterator(image);
3971 while ((key=GetNextImageArtifact(image)) != (const char *) NULL)
3972 if (GlobExpression(key,pattern+9,MagickTrue) != MagickFalse)
3973 {
3974 value=GetImageArtifact(image,key);
3975 if (value != (const char *) NULL)
3976 AppendKeyValue2Text(key,value);
3977 /* else - assertion failure? key but no value! */
3978 }
3979 continue;
3980 }
3981 value=GetImageArtifact(image,pattern+9);
3982 if (value != (char *) NULL)
3983 AppendString2Text(value);
3984 /* else - no artifact of this specifc name */
3985 continue;
3986 }
3987 /*
3988 Handle special image properties, for example:
3989 %[exif:...] %[fx:...] %[pixel:...].
3990
3991 FUTURE: handle %[property:...] prefix - abort other lookups.
3992 */
3993 value=GetImageProperty(image,pattern);
3994 if (value != (const char *) NULL)
3995 {
3996 AppendString2Text(value);
3997 continue;
3998 }
3999 /*
4000 Handle property 'glob' patterns such as:
4001 %[*] %[user:array_??] %[filename:e*]
4002 */
4003 if (IsGlob(pattern) != MagickFalse)
4004 {
4005 ResetImagePropertyIterator(image);
4006 while ((key=GetNextImageProperty(image)) != (const char *) NULL)
4007 if (GlobExpression(key,pattern,MagickTrue) != MagickFalse)
4008 {
4009 value=GetImageProperty(image,key);
4010 if (value != (const char *) NULL)
4011 AppendKeyValue2Text(key,value);
4012 /* else - assertion failure? */
4013 }
4014 continue;
4015 }
4016 /*
4017 Look for a known property or image attribute such as
4018 %[basename] %[denisty] %[delay]. Also handles a braced single
4019 letter: %[b] %[G] %[g].
4020 */
4021 value=GetMagickProperty(property_info,image,pattern);
4022 if (value != (const char *) NULL)
4023 {
4024 AppendString2Text(value);
4025 continue;
4026 }
4027 /*
4028 Look for a per-image Artifact (user option, post-interpreted)
4029 */
4030 value=GetImageArtifact(image,pattern);
4031 if (value != (char *) NULL)
4032 {
4033 AppendString2Text(value);
4034 continue;
4035 }
4036 /*
4037 Look for user option of this name (should never match in CLI usage).
4038 */
4039 value=GetImageOption(property_info,pattern);
4040 if (value != (char *) NULL)
4041 {
4042 AppendString2Text(value);
4043 continue;
4044 }
4045 /*
4046 Failed to find any match anywhere!
4047 */
4048 if (len >= 64)
4049 {
4050 pattern[61] = '.'; /* truncate string for error message */
4051 pattern[62] = '.';
4052 pattern[63] = '.';
4053 pattern[64] = '\0';
4054 }
4055 (void) ThrowMagickException(&image->exception,GetMagickModule(),
4056 OptionWarning,"UnknownImageProperty","\"%%[%s]\"",pattern);
4057 /* continue */
4058 } /* Braced Percent Escape */
4059 } /* for each char in 'embed_text' */
4060 *q='\0';
4061 if (property_info != image_info)
4062 property_info=DestroyImageInfo(property_info);
4063 return(interpret_text);
4064 }
4065
4066 /*
4067 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4068 % %
4069 % %
4070 % %
4071 % R e m o v e I m a g e P r o p e r t y %
4072 % %
4073 % %
4074 % %
4075 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4076 %
4077 % RemoveImageProperty() removes a property from the image and returns its
4078 % value.
4079 %
4080 % In this case the ConstantString() value returned should be freed by the
4081 % caller when finished.
4082 %
4083 % The format of the RemoveImageProperty method is:
4084 %
4085 % char *RemoveImageProperty(Image *image,const char *property)
4086 %
4087 % A description of each parameter follows:
4088 %
4089 % o image: the image.
4090 %
4091 % o property: the image property.
4092 %
4093 */
RemoveImageProperty(Image * image,const char * property)4094 MagickExport char *RemoveImageProperty(Image *image,const char *property)
4095 {
4096 char
4097 *value;
4098
4099 assert(image != (Image *) NULL);
4100 assert(image->signature == MagickCoreSignature);
4101 if (image->debug != MagickFalse)
4102 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
4103 image->filename);
4104 if (image->properties == (void *) NULL)
4105 return((char *) NULL);
4106 value=(char *) RemoveNodeFromSplayTree((SplayTreeInfo *) image->properties,
4107 property);
4108 return(value);
4109 }
4110
4111 /*
4112 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4113 % %
4114 % %
4115 % %
4116 % R e s e t I m a g e P r o p e r t y I t e r a t o r %
4117 % %
4118 % %
4119 % %
4120 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4121 %
4122 % ResetImagePropertyIterator() resets the image properties iterator. Use it
4123 % in conjunction with GetNextImageProperty() to iterate over all the values
4124 % associated with an image property.
4125 %
4126 % The format of the ResetImagePropertyIterator method is:
4127 %
4128 % ResetImagePropertyIterator(Image *image)
4129 %
4130 % A description of each parameter follows:
4131 %
4132 % o image: the image.
4133 %
4134 */
ResetImagePropertyIterator(const Image * image)4135 MagickExport void ResetImagePropertyIterator(const Image *image)
4136 {
4137 assert(image != (Image *) NULL);
4138 assert(image->signature == MagickCoreSignature);
4139 if (image->debug != MagickFalse)
4140 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
4141 image->filename);
4142 if (image->properties == (void *) NULL)
4143 return;
4144 ResetSplayTreeIterator((SplayTreeInfo *) image->properties);
4145 }
4146
4147 /*
4148 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4149 % %
4150 % %
4151 % %
4152 % S e t I m a g e P r o p e r t y %
4153 % %
4154 % %
4155 % %
4156 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4157 %
4158 % SetImageProperty() saves the given string value either to specific known
4159 % attribute or to a freeform property string.
4160 %
4161 % The format of the SetImageProperty method is:
4162 %
4163 % MagickBooleanType SetImageProperty(Image *image,const char *property,
4164 % const char *value)
4165 %
4166 % A description of each parameter follows:
4167 %
4168 % o image: the image.
4169 %
4170 % o property: the image property.
4171 %
4172 % o values: the image property values.
4173 %
4174 */
SetImageProperty(Image * image,const char * property,const char * value)4175 MagickExport MagickBooleanType SetImageProperty(Image *image,
4176 const char *property,const char *value)
4177 {
4178 ExceptionInfo
4179 *exception;
4180
4181 MagickBooleanType
4182 status;
4183
4184 MagickStatusType
4185 flags;
4186
4187 assert(image != (Image *) NULL);
4188 assert(image->signature == MagickCoreSignature);
4189 if (image->debug != MagickFalse)
4190 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
4191 image->filename);
4192 if (image->properties == (void *) NULL)
4193 image->properties=NewSplayTree(CompareSplayTreeString,
4194 RelinquishMagickMemory,RelinquishMagickMemory); /* create splay-tree */
4195 if (value == (const char *) NULL)
4196 return(DeleteImageProperty(image,property)); /* delete if NULL */
4197 /*
4198 FUTURE: These should produce 'illegal settings'
4199 * binary chars in p[roperty key
4200 * first letter must be a alphabetic
4201 * single letter property keys (read only)
4202 * known special prefix (read only, they don't get saved!)
4203 */
4204 status=MagickTrue;
4205 exception=(&image->exception);
4206 switch (*property)
4207 {
4208 case 'B':
4209 case 'b':
4210 {
4211 if (LocaleCompare("background",property) == 0)
4212 {
4213 (void) QueryColorDatabase(value,&image->background_color,exception);
4214 break;
4215 }
4216 if (LocaleCompare("bias",property) == 0)
4217 {
4218 image->bias=StringToDoubleInterval(value,(double) QuantumRange+1.0);
4219 break;
4220 }
4221 status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4222 ConstantString(property),ConstantString(value));
4223 break;
4224 }
4225 case 'C':
4226 case 'c':
4227 {
4228 if (LocaleCompare("colorspace",property) == 0)
4229 {
4230 ssize_t
4231 colorspace;
4232
4233 colorspace=ParseCommandOption(MagickColorspaceOptions,MagickFalse,
4234 value);
4235 if (colorspace < 0)
4236 break;
4237 status=SetImageColorspace(image,(ColorspaceType) colorspace);
4238 break;
4239 }
4240 if (LocaleCompare("compose",property) == 0)
4241 {
4242 ssize_t
4243 compose;
4244
4245 compose=ParseCommandOption(MagickComposeOptions,MagickFalse,value);
4246 if (compose < 0)
4247 break;
4248 image->compose=(CompositeOperator) compose;
4249 break;
4250 }
4251 if (LocaleCompare("compress",property) == 0)
4252 {
4253 ssize_t
4254 compression;
4255
4256 compression=ParseCommandOption(MagickCompressOptions,MagickFalse,
4257 value);
4258 if (compression < 0)
4259 break;
4260 image->compression=(CompressionType) compression;
4261 break;
4262 }
4263 status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4264 ConstantString(property),ConstantString(value));
4265 break;
4266 }
4267 case 'D':
4268 case 'd':
4269 {
4270 if (LocaleCompare("delay",property) == 0)
4271 {
4272 GeometryInfo
4273 geometry_info;
4274
4275 flags=ParseGeometry(value,&geometry_info);
4276 if ((flags & GreaterValue) != 0)
4277 {
4278 if (image->delay > (size_t) floor(geometry_info.rho+0.5))
4279 image->delay=(size_t) floor(geometry_info.rho+0.5);
4280 }
4281 else
4282 if ((flags & LessValue) != 0)
4283 {
4284 if ((double) image->delay < floor(geometry_info.rho+0.5))
4285 image->ticks_per_second=CastDoubleToLong(
4286 floor(geometry_info.sigma+0.5));
4287 }
4288 else
4289 image->delay=(size_t) floor(geometry_info.rho+0.5);
4290 if ((flags & SigmaValue) != 0)
4291 image->ticks_per_second=CastDoubleToLong(floor(
4292 geometry_info.sigma+0.5));
4293 break;
4294 }
4295 if (LocaleCompare("density",property) == 0)
4296 {
4297 GeometryInfo
4298 geometry_info;
4299
4300 flags=ParseGeometry(value,&geometry_info);
4301 if ((flags & RhoValue) != 0)
4302 image->x_resolution=geometry_info.rho;
4303 image->y_resolution=image->x_resolution;
4304 if ((flags & SigmaValue) != 0)
4305 image->y_resolution=geometry_info.sigma;
4306 }
4307 if (LocaleCompare("depth",property) == 0)
4308 {
4309 image->depth=StringToUnsignedLong(value);
4310 break;
4311 }
4312 if (LocaleCompare("dispose",property) == 0)
4313 {
4314 ssize_t
4315 dispose;
4316
4317 dispose=ParseCommandOption(MagickDisposeOptions,MagickFalse,value);
4318 if (dispose < 0)
4319 break;
4320 image->dispose=(DisposeType) dispose;
4321 break;
4322 }
4323 status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4324 ConstantString(property),ConstantString(value));
4325 break;
4326 }
4327 case 'G':
4328 case 'g':
4329 {
4330 if (LocaleCompare("gamma",property) == 0)
4331 {
4332 image->gamma=StringToDouble(value,(char **) NULL);
4333 break;
4334 }
4335 if (LocaleCompare("gravity",property) == 0)
4336 {
4337 ssize_t
4338 gravity;
4339
4340 gravity=ParseCommandOption(MagickGravityOptions,MagickFalse,value);
4341 if (gravity < 0)
4342 break;
4343 image->gravity=(GravityType) gravity;
4344 break;
4345 }
4346 status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4347 ConstantString(property),ConstantString(value));
4348 break;
4349 }
4350 case 'I':
4351 case 'i':
4352 {
4353 if (LocaleCompare("intensity",property) == 0)
4354 {
4355 ssize_t
4356 intensity;
4357
4358 intensity=ParseCommandOption(MagickPixelIntensityOptions,MagickFalse,
4359 value);
4360 if (intensity < 0)
4361 break;
4362 image->intensity=(PixelIntensityMethod) intensity;
4363 break;
4364 }
4365 if (LocaleCompare("interpolate",property) == 0)
4366 {
4367 ssize_t
4368 interpolate;
4369
4370 interpolate=ParseCommandOption(MagickInterpolateOptions,MagickFalse,
4371 value);
4372 if (interpolate < 0)
4373 break;
4374 image->interpolate=(InterpolatePixelMethod) interpolate;
4375 break;
4376 }
4377 status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4378 ConstantString(property),ConstantString(value));
4379 break;
4380 }
4381 case 'L':
4382 case 'l':
4383 {
4384 if (LocaleCompare("loop",property) == 0)
4385 {
4386 image->iterations=StringToUnsignedLong(value);
4387 break;
4388 }
4389 status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4390 ConstantString(property),ConstantString(value));
4391 break;
4392 }
4393 case 'P':
4394 case 'p':
4395 {
4396 if (LocaleCompare("page",property) == 0)
4397 {
4398 char
4399 *geometry;
4400
4401 geometry=GetPageGeometry(value);
4402 flags=ParseAbsoluteGeometry(geometry,&image->page);
4403 geometry=DestroyString(geometry);
4404 break;
4405 }
4406 if (LocaleCompare("profile",property) == 0)
4407 {
4408 ImageInfo
4409 *image_info;
4410
4411 StringInfo
4412 *profile;
4413
4414 image_info=AcquireImageInfo();
4415 (void) CopyMagickString(image_info->filename,value,MaxTextExtent);
4416 (void) SetImageInfo(image_info,1,exception);
4417 profile=FileToStringInfo(image_info->filename,~0UL,exception);
4418 if (profile != (StringInfo *) NULL)
4419 {
4420 status=SetImageProfile(image,image_info->magick,profile);
4421 profile=DestroyStringInfo(profile);
4422 }
4423 image_info=DestroyImageInfo(image_info);
4424 break;
4425 }
4426 status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4427 ConstantString(property),ConstantString(value));
4428 break;
4429 }
4430 case 'R':
4431 case 'r':
4432 {
4433 if (LocaleCompare("rendering-intent",property) == 0)
4434 {
4435 ssize_t
4436 rendering_intent;
4437
4438 rendering_intent=ParseCommandOption(MagickIntentOptions,MagickFalse,
4439 value);
4440 if (rendering_intent < 0)
4441 break;
4442 image->rendering_intent=(RenderingIntent) rendering_intent;
4443 break;
4444 }
4445 status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4446 ConstantString(property),ConstantString(value));
4447 break;
4448 }
4449 case 'T':
4450 case 't':
4451 {
4452 if (LocaleCompare("tile-offset",property) == 0)
4453 {
4454 char
4455 *geometry;
4456
4457 geometry=GetPageGeometry(value);
4458 flags=ParseAbsoluteGeometry(geometry,&image->tile_offset);
4459 geometry=DestroyString(geometry);
4460 break;
4461 }
4462 if (LocaleCompare("type",property) == 0)
4463 {
4464 ssize_t
4465 type;
4466
4467 type=ParseCommandOption(MagickTypeOptions,MagickFalse,value);
4468 if (type < 0)
4469 return(MagickFalse);
4470 image->type=(ImageType) type;
4471 break;
4472 }
4473 status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4474 ConstantString(property),ConstantString(value));
4475 break;
4476 }
4477 case 'U':
4478 case 'u':
4479 {
4480 if (LocaleCompare("units",property) == 0)
4481 {
4482 ssize_t
4483 units;
4484
4485 units=ParseCommandOption(MagickResolutionOptions,MagickFalse,value);
4486 if (units < 0)
4487 break;
4488 image->units=(ResolutionType) units;
4489 break;
4490 }
4491 status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4492 ConstantString(property),ConstantString(value));
4493 break;
4494 }
4495 default:
4496 {
4497 status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4498 ConstantString(property),ConstantString(value));
4499 break;
4500 }
4501 }
4502 return(status);
4503 }
4504