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