MagickCore 7.1.2-27
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 status=MagickFalse; \
3716 goto cleanup; \
3717 } \
3718 q=interpret_text+strlen(interpret_text); \
3719 } \
3720}
3721
3722#define AppendKeyValue2Text(key,value)\
3723{ \
3724 size_t length=strlen(key)+strlen(value)+2; \
3725 if ((size_t) (q-interpret_text+(ssize_t) length+1) >= extent) \
3726 { \
3727 extent+=length; \
3728 interpret_text=(char *) ResizeQuantumMemory(interpret_text,extent+ \
3729 MagickPathExtent,sizeof(*interpret_text)); \
3730 if (interpret_text == (char *) NULL) \
3731 { \
3732 status=MagickFalse; \
3733 goto cleanup; \
3734 } \
3735 q=interpret_text+strlen(interpret_text); \
3736 } \
3737 q+=(ptrdiff_t) FormatLocaleString(q,extent,"%s=%s\n",(key),(value)); \
3738}
3739
3740#define AppendString2Text(string) \
3741{ \
3742 size_t length = strlen((string)); \
3743 if ((size_t) (q-interpret_text+(ssize_t) length+1) >= extent) \
3744 { \
3745 extent+=length; \
3746 interpret_text=(char *) ResizeQuantumMemory(interpret_text,extent+ \
3747 MagickPathExtent,sizeof(*interpret_text)); \
3748 if (interpret_text == (char *) NULL) \
3749 { \
3750 status=MagickFalse; \
3751 goto cleanup; \
3752 } \
3753 q=interpret_text+strlen(interpret_text); \
3754 } \
3755 (void) CopyMagickString(q,(string),extent); \
3756 q+=(ptrdiff_t) length; \
3757}
3758
3759 char
3760 *interpret_text = (char *) NULL,
3761 *q; /* current position in interpret_text */
3762
3763 const char
3764 *p; /* position in embed_text string being expanded */
3765
3766 Image
3767 *property_image = (Image *) NULL;
3768
3769 ImageInfo
3770 *property_info = (ImageInfo *) NULL;
3771
3772 MagickBooleanType
3773 number,
3774 status = MagickTrue;
3775
3776 size_t
3777 extent; /* allocated length of interpret_text */
3778
3779 if ((image != (Image *) NULL) && (IsEventLogging() != MagickFalse))
3780 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3781 else
3782 if ((image_info != (ImageInfo *) NULL) && (IsEventLogging() != MagickFalse))
3783 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
3784 image_info->filename);
3785 else
3786 if (IsEventLogging() != MagickFalse)
3787 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s","no image");
3788 if (embed_text == (const char *) NULL)
3789 return(ConstantString(""));
3790 p=embed_text;
3791 while ((isspace((int) ((unsigned char) *p)) != 0) && (*p != '\0'))
3792 p++;
3793 if (*p == '\0')
3794 return(ConstantString(""));
3795 if ((*p == '@') && (IsPathAccessible(p+1) != MagickFalse))
3796 {
3797 /*
3798 Handle a '@' replace string from file.
3799 */
3800 interpret_text=FileToString(p,~0UL,exception);
3801 if (interpret_text != (char *) NULL)
3802 return(interpret_text);
3803 }
3804 /*
3805 Translate any embedded format characters.
3806 */
3807 if (image_info != (ImageInfo *) NULL)
3808 property_info=image_info;
3809 else
3810 property_info=CloneImageInfo(image_info);
3811 if ((image != (Image *) NULL) && (image->columns != 0) && (image->rows != 0))
3812 property_image=image;
3813 else
3814 {
3815 property_image=AcquireImage(image_info,exception);
3816 if (property_image == (Image *) NULL)
3817 {
3818 status=MagickFalse;
3819 goto cleanup;
3820 }
3821 (void) SetImageExtent(property_image,1,1,exception);
3822 (void) SetImageBackgroundColor(property_image,exception);
3823 }
3824 interpret_text=AcquireString(embed_text); /* new string with extra space */
3825 if (interpret_text == (char *) NULL)
3826 {
3827 status=MagickFalse;
3828 goto cleanup;
3829 }
3830 extent=MagickPathExtent; /* allocated space in string */
3831 number=MagickFalse; /* is last char a number? */
3832 for (q=interpret_text; *p!='\0'; number=isdigit((int) ((unsigned char) *p)) ? MagickTrue : MagickFalse,p++)
3833 {
3834 /*
3835 Look for the various escapes, (and handle other specials)
3836 */
3837 *q='\0';
3838 ExtendInterpretText(MagickPathExtent);
3839 switch (*p)
3840 {
3841 case '\\':
3842 {
3843 switch (*(p+1))
3844 {
3845 case '\0':
3846 continue;
3847 case 'r': /* convert to RETURN */
3848 {
3849 *q++='\r';
3850 p++;
3851 continue;
3852 }
3853 case 'n': /* convert to NEWLINE */
3854 {
3855 *q++='\n';
3856 p++;
3857 continue;
3858 }
3859 case '\n': /* EOL removal UNIX,MacOSX */
3860 {
3861 p++;
3862 continue;
3863 }
3864 case '\r': /* EOL removal DOS,Windows */
3865 {
3866 p++;
3867 if (*p == '\n') /* return-newline EOL */
3868 p++;
3869 continue;
3870 }
3871 default:
3872 {
3873 p++;
3874 *q++=(*p);
3875 }
3876 }
3877 continue;
3878 }
3879 case '&':
3880 {
3881 if (LocaleNCompare("&lt;",p,4) == 0)
3882 {
3883 *q++='<';
3884 p+=(ptrdiff_t) 3;
3885 }
3886 else
3887 if (LocaleNCompare("&gt;",p,4) == 0)
3888 {
3889 *q++='>';
3890 p+=(ptrdiff_t) 3;
3891 }
3892 else
3893 if (LocaleNCompare("&amp;",p,5) == 0)
3894 {
3895 *q++='&';
3896 p+=(ptrdiff_t) 4;
3897 }
3898 else
3899 *q++=(*p);
3900 continue;
3901 }
3902 case '%':
3903 break; /* continue to next set of handlers */
3904 default:
3905 {
3906 *q++=(*p); /* any thing else is 'as normal' */
3907 continue;
3908 }
3909 }
3910 p++; /* advance beyond the percent */
3911 /*
3912 Doubled Percent - or percent at end of string.
3913 */
3914 if ((*p == '\0') || (*p == '\'') || (*p == '"'))
3915 p--;
3916 if (*p == '%')
3917 {
3918 *q++='%';
3919 continue;
3920 }
3921 /*
3922 Single letter escapes %c.
3923 */
3924 if (*p != '[')
3925 {
3926 const char
3927 *string;
3928
3929 if (number != MagickFalse)
3930 {
3931 /*
3932 But only if not preceded by a number!
3933 */
3934 *q++='%'; /* do NOT substitute the percent */
3935 p--; /* back up one */
3936 continue;
3937 }
3938 string=GetMagickPropertyLetter(property_info,image,*p,exception);
3939 if (string != (char *) NULL)
3940 {
3941 AppendString2Text(string);
3942 (void) DeleteImageArtifact(property_image,"magick-property");
3943 (void) DeleteImageOption(property_info,"magick-property");
3944 continue;
3945 }
3946 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
3947 "UnknownImageProperty","\"%%%c\"",*p);
3948 continue;
3949 }
3950 {
3951 char
3952 pattern[2*MagickPathExtent] = "\0";
3953
3954 const char
3955 *key,
3956 *string;
3957
3958 ssize_t
3959 depth,
3960 offset;
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 status=MagickFalse;
3972 goto cleanup;
3973 }
3974 for (offset=0; offset < (MagickPathExtent-1L) && (*p != '\0'); )
3975 {
3976 if ((*p == '\\') && (*(p+1) != '\0'))
3977 {
3978 /*
3979 Skip escaped braces within braced pattern.
3980 */
3981 pattern[offset++]=(*p++);
3982 pattern[offset++]=(*p++);
3983 continue;
3984 }
3985 if (*p == '[')
3986 depth++;
3987 if (*p == ']')
3988 depth--;
3989 if (depth <= 0)
3990 break;
3991 pattern[offset++]=(*p++);
3992 }
3993 pattern[offset]='\0';
3994 if (depth != 0)
3995 {
3996 /*
3997 Check for unmatched final ']' for "%[...]".
3998 */
3999 if (offset >= 64)
4000 {
4001 pattern[61]='.'; /* truncate string for error message */
4002 pattern[62]='.';
4003 pattern[63]='.';
4004 pattern[64]='\0';
4005 }
4006 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4007 "UnbalancedBraces","\"%%[%s\"",pattern);
4008 status=MagickFalse;
4009 goto cleanup;
4010 }
4011 /*
4012 Special Lookup Prefixes %[prefix:...].
4013 */
4014 if (LocaleNCompare("fx:",pattern,3) == 0)
4015 {
4016 double
4017 value;
4018
4019 FxInfo
4020 *fx_info;
4021
4022 MagickBooleanType
4023 fx_status;
4024
4025 /*
4026 FX - value calculator.
4027 */
4028 fx_info=AcquireFxInfo(property_image,pattern+3,exception);
4029 if (fx_info == (FxInfo *) NULL)
4030 continue;
4031 fx_status=FxEvaluateChannelExpression(fx_info,CompositePixelChannel,
4032 0,0,&value,exception);
4033 fx_info=DestroyFxInfo(fx_info);
4034 if (fx_status != MagickFalse)
4035 {
4036 char
4037 result[MagickPathExtent];
4038
4039 (void) FormatLocaleString(result,MagickPathExtent,"%.*g",
4040 GetMagickPrecision(),(double) value);
4041 AppendString2Text(result);
4042 }
4043 continue;
4044 }
4045 if (LocaleNCompare("hex:",pattern,4) == 0)
4046 {
4047 double
4048 value;
4049
4050 FxInfo
4051 *fx_info;
4052
4053 MagickStatusType
4054 fx_status;
4055
4056 PixelInfo
4057 pixel;
4058
4059 /*
4060 Pixel - color value calculator.
4061 */
4062 GetPixelInfo(property_image,&pixel);
4063 fx_info=AcquireFxInfo(property_image,pattern+4,exception);
4064 if (fx_info == (FxInfo *) NULL)
4065 continue;
4066 fx_status=FxEvaluateChannelExpression(fx_info,RedPixelChannel,0,0,
4067 &value,exception);
4068 pixel.red=(double) QuantumRange*value;
4069 fx_status&=(MagickStatusType) FxEvaluateChannelExpression(fx_info,
4070 GreenPixelChannel,0,0,&value,exception);
4071 pixel.green=(double) QuantumRange*value;
4072 fx_status&=(MagickStatusType) FxEvaluateChannelExpression(fx_info,
4073 BluePixelChannel,0,0,&value,exception);
4074 pixel.blue=(double) QuantumRange*value;
4075 if (property_image->colorspace == CMYKColorspace)
4076 {
4077 fx_status&=(MagickStatusType) FxEvaluateChannelExpression(fx_info,
4078 BlackPixelChannel,0,0,&value,exception);
4079 pixel.black=(double) QuantumRange*value;
4080 }
4081 fx_status&=(MagickStatusType) FxEvaluateChannelExpression(fx_info,
4082 AlphaPixelChannel,0,0,&value,exception);
4083 pixel.alpha=(double) QuantumRange*value;
4084 fx_info=DestroyFxInfo(fx_info);
4085 if (fx_status != MagickFalse)
4086 {
4087 char
4088 hex[MagickPathExtent];
4089
4090 GetColorTuple(&pixel,MagickTrue,hex);
4091 AppendString2Text(hex+1);
4092 }
4093 continue;
4094 }
4095 if (LocaleNCompare("pixel:",pattern,6) == 0)
4096 {
4097 double
4098 value;
4099
4100 FxInfo
4101 *fx_info;
4102
4103 MagickStatusType
4104 fx_status;
4105
4106 PixelInfo
4107 pixel;
4108
4109 /*
4110 Pixel - color value calculator.
4111 */
4112 GetPixelInfo(property_image,&pixel);
4113 fx_info=AcquireFxInfo(property_image,pattern+6,exception);
4114 if (fx_info == (FxInfo *) NULL)
4115 continue;
4116 fx_status=FxEvaluateChannelExpression(fx_info,RedPixelChannel,0,0,
4117 &value,exception);
4118 pixel.red=(double) QuantumRange*value;
4119 fx_status&=(MagickStatusType) FxEvaluateChannelExpression(fx_info,
4120 GreenPixelChannel,0,0,&value,exception);
4121 pixel.green=(double) QuantumRange*value;
4122 fx_status&=(MagickStatusType) FxEvaluateChannelExpression(fx_info,
4123 BluePixelChannel,0,0,&value,exception);
4124 pixel.blue=(double) QuantumRange*value;
4125 if (property_image->colorspace == CMYKColorspace)
4126 {
4127 fx_status&=(MagickStatusType) FxEvaluateChannelExpression(fx_info,
4128 BlackPixelChannel,0,0,&value,exception);
4129 pixel.black=(double) QuantumRange*value;
4130 }
4131 fx_status&=(MagickStatusType) FxEvaluateChannelExpression(fx_info,
4132 AlphaPixelChannel,0,0,&value,exception);
4133 pixel.alpha=(double) QuantumRange*value;
4134 fx_info=DestroyFxInfo(fx_info);
4135 if (fx_status != MagickFalse)
4136 {
4137 char
4138 name[MagickPathExtent];
4139
4140 GetColorTuple(&pixel,MagickFalse,name);
4141 string=GetImageArtifact(property_image,"pixel:compliance");
4142 if (string != (char *) NULL)
4143 {
4144 ComplianceType compliance=(ComplianceType) ParseCommandOption(
4145 MagickComplianceOptions,MagickFalse,string);
4146 (void) QueryColorname(property_image,&pixel,compliance,name,
4147 exception);
4148 }
4149 AppendString2Text(name);
4150 }
4151 continue;
4152 }
4153 if (LocaleNCompare("option:",pattern,7) == 0)
4154 {
4155 /*
4156 Option - direct global option lookup (with globbing).
4157 */
4158 if (IsGlob(pattern+7) != MagickFalse)
4159 {
4160 ResetImageOptionIterator(property_info);
4161 while ((key=GetNextImageOption(property_info)) != (const char *) NULL)
4162 if (GlobExpression(key,pattern+7,MagickTrue) != MagickFalse)
4163 {
4164 string=GetImageOption(property_info,key);
4165 if (string != (const char *) NULL)
4166 AppendKeyValue2Text(key,string);
4167 }
4168 continue;
4169 }
4170 string=GetImageOption(property_info,pattern+7);
4171 if (string == (char *) NULL)
4172 goto PropertyLookupFailure;
4173 AppendString2Text(string);
4174 continue;
4175 }
4176 if (LocaleNCompare("artifact:",pattern,9) == 0)
4177 {
4178 /*
4179 Artifact - direct image artifact lookup (with glob).
4180 */
4181 if (IsGlob(pattern+9) != MagickFalse)
4182 {
4183 ResetImageArtifactIterator(property_image);
4184 while ((key=GetNextImageArtifact(property_image)) != (const char *) NULL)
4185 if (GlobExpression(key,pattern+9,MagickTrue) != MagickFalse)
4186 {
4187 string=GetImageArtifact(property_image,key);
4188 if (string != (const char *) NULL)
4189 AppendKeyValue2Text(key,string);
4190 }
4191 continue;
4192 }
4193 string=GetImageArtifact(property_image,pattern+9);
4194 if (string == (char *) NULL)
4195 goto PropertyLookupFailure;
4196 AppendString2Text(string);
4197 continue;
4198 }
4199 if (LocaleNCompare("property:",pattern,9) == 0)
4200 {
4201 /*
4202 Property - direct image property lookup (with glob).
4203 */
4204 if (IsGlob(pattern+9) != MagickFalse)
4205 {
4206 ResetImagePropertyIterator(property_image);
4207 while ((key=GetNextImageProperty(property_image)) != (const char *) NULL)
4208 if (GlobExpression(key,pattern,MagickTrue) != MagickFalse)
4209 {
4210 string=GetImageProperty(property_image,key,exception);
4211 if (string != (const char *) NULL)
4212 AppendKeyValue2Text(key,string);
4213 }
4214 continue;
4215 }
4216 string=GetImageProperty(property_image,pattern+9,exception);
4217 if (string == (char *) NULL)
4218 goto PropertyLookupFailure;
4219 AppendString2Text(string);
4220 continue;
4221 }
4222 /*
4223 Properties without special prefix.
4224 */
4225 string=GetImageProperty(property_image,pattern,exception);
4226 if (string != (const char *) NULL)
4227 {
4228 AppendString2Text(string);
4229 (void) DeleteImageArtifact(property_image,"magick-property");
4230 (void) DeleteImageOption(property_info,"magick-property");
4231 continue;
4232 }
4233 if (IsGlob(pattern) != MagickFalse)
4234 {
4235 ResetImagePropertyIterator(property_image);
4236 while ((key=GetNextImageProperty(property_image)) != (const char *) NULL)
4237 if (GlobExpression(key,pattern,MagickTrue) != MagickFalse)
4238 {
4239 string=GetImageProperty(property_image,key,exception);
4240 if (string != (const char *) NULL)
4241 AppendKeyValue2Text(key,string);
4242 }
4243 continue;
4244 }
4245 /*
4246 Known property or attribute.
4247 */
4248 string=GetMagickProperty(property_info,property_image,pattern,exception);
4249 if (string != (const char *) NULL)
4250 {
4251 AppendString2Text(string);
4252 continue;
4253 }
4254 /*
4255 Per-image artifact.
4256 */
4257 string=GetImageArtifact(property_image,pattern);
4258 if (string != (char *) NULL)
4259 {
4260 AppendString2Text(string);
4261 continue;
4262 }
4263 /*
4264 Direct option lookup.
4265 */
4266 string=GetImageOption(property_info,pattern);
4267 if (string != (char *) NULL)
4268 {
4269 AppendString2Text(string);
4270 continue;
4271 }
4272
4273PropertyLookupFailure:
4274 if (offset >= 64)
4275 {
4276 pattern[61]='.'; /* truncate string for error message */
4277 pattern[62]='.';
4278 pattern[63]='.';
4279 pattern[64]='\0';
4280 }
4281 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4282 "UnknownImageProperty","\"%%[%s]\"",pattern);
4283 }
4284 }
4285 *q='\0';
4286
4287cleanup:
4288 if ((property_image != image) && (property_image != (Image *) NULL))
4289 property_image=DestroyImage(property_image);
4290 if ((property_info != image_info) && (property_info != (ImageInfo *) NULL))
4291 property_info=DestroyImageInfo(property_info);
4292 if (status == MagickFalse)
4293 {
4294 if (interpret_text != (char *) NULL)
4295 interpret_text=DestroyString(interpret_text);
4296 return((char *) NULL);
4297 }
4298 return(interpret_text);
4299}
4300
4301/*
4302%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4303% %
4304% %
4305% %
4306% R e m o v e I m a g e P r o p e r t y %
4307% %
4308% %
4309% %
4310%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4311%
4312% RemoveImageProperty() removes a property from the image and returns its
4313% value.
4314%
4315% In this case the ConstantString() value returned should be freed by the
4316% caller when finished.
4317%
4318% The format of the RemoveImageProperty method is:
4319%
4320% char *RemoveImageProperty(Image *image,const char *property)
4321%
4322% A description of each parameter follows:
4323%
4324% o image: the image.
4325%
4326% o property: the image property.
4327%
4328*/
4329MagickExport char *RemoveImageProperty(Image *image,const char *property)
4330{
4331 char
4332 *value;
4333
4334 assert(image != (Image *) NULL);
4335 assert(image->signature == MagickCoreSignature);
4336 if (IsEventLogging() != MagickFalse)
4337 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4338 if (image->properties == (void *) NULL)
4339 return((char *) NULL);
4340 value=(char *) RemoveNodeFromSplayTree((SplayTreeInfo *) image->properties,
4341 property);
4342 return(value);
4343}
4344
4345/*
4346%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4347% %
4348% %
4349% %
4350% 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 %
4351% %
4352% %
4353% %
4354%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4355%
4356% ResetImagePropertyIterator() resets the image properties iterator. Use it
4357% in conjunction with GetNextImageProperty() to iterate over all the values
4358% associated with an image property.
4359%
4360% The format of the ResetImagePropertyIterator method is:
4361%
4362% void ResetImagePropertyIterator(const Image *image)
4363%
4364% A description of each parameter follows:
4365%
4366% o image: the image.
4367%
4368*/
4369MagickExport void ResetImagePropertyIterator(const Image *image)
4370{
4371 assert(image != (Image *) NULL);
4372 assert(image->signature == MagickCoreSignature);
4373 if (IsEventLogging() != MagickFalse)
4374 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4375 if (image->properties == (void *) NULL)
4376 return;
4377 ResetSplayTreeIterator((SplayTreeInfo *) image->properties);
4378}
4379
4380/*
4381%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4382% %
4383% %
4384% %
4385% S e t I m a g e P r o p e r t y %
4386% %
4387% %
4388% %
4389%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4390%
4391% SetImageProperty() saves the given string value either to specific known
4392% attribute or to a freeform property string.
4393%
4394% Attempting to set a property that is normally calculated will produce
4395% an exception.
4396%
4397% The format of the SetImageProperty method is:
4398%
4399% MagickBooleanType SetImageProperty(Image *image,const char *property,
4400% const char *value,ExceptionInfo *exception)
4401%
4402% A description of each parameter follows:
4403%
4404% o image: the image.
4405%
4406% o property: the image property.
4407%
4408% o values: the image property values.
4409%
4410% o exception: return any errors or warnings in this structure.
4411%
4412*/
4413MagickExport MagickBooleanType SetImageProperty(Image *image,
4414 const char *property,const char *value,ExceptionInfo *exception)
4415{
4416 MagickBooleanType
4417 status;
4418
4419 MagickStatusType
4420 flags;
4421
4422 size_t
4423 property_length;
4424
4425 assert(image != (Image *) NULL);
4426 assert(image->signature == MagickCoreSignature);
4427 if (IsEventLogging() != MagickFalse)
4428 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4429 if (image->properties == (void *) NULL)
4430 image->properties=NewSplayTree(CompareSplayTreeString,
4431 RelinquishMagickMemory,RelinquishMagickMemory); /* create splay-tree */
4432 if (value == (const char *) NULL)
4433 return(DeleteImageProperty(image,property)); /* delete if NULL */
4434 if (strlen(property) <= 1)
4435 {
4436 /*
4437 Do not 'set' single letter properties - read only shorthand.
4438 */
4439 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4440 "SetReadOnlyProperty","`%s'",property);
4441 return(MagickFalse);
4442 }
4443 property_length=strlen(property);
4444 if ((property_length > 2) && (*(property+(property_length-2)) == ':') &&
4445 (*(property+(property_length-1)) == '*'))
4446 {
4447 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4448 "SetReadOnlyProperty","`%s'",property);
4449 return(MagickFalse);
4450 }
4451 /*
4452 FUTURE: binary chars or quotes in key should produce a error
4453 Set attributes with known names or special prefixes
4454 return result is found, or break to set a free form property
4455 */
4456 status=MagickTrue;
4457 switch (*property)
4458 {
4459#if 0 /* Percent escape's sets values with this prefix: for later use
4460 Throwing an exception causes this setting to fail */
4461 case '8':
4462 {
4463 if (LocaleNCompare("8bim:",property,5) == 0)
4464 {
4465 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4466 "SetReadOnlyProperty","`%s'",property);
4467 return(MagickFalse);
4468 }
4469 break;
4470 }
4471#endif
4472 case 'B':
4473 case 'b':
4474 {
4475 if (LocaleCompare("background",property) == 0)
4476 {
4477 (void) QueryColorCompliance(value,AllCompliance,
4478 &image->background_color,exception);
4479 /* check for FUTURE: value exception?? */
4480 /* also add user input to splay tree */
4481 }
4482 break; /* not an attribute, add as a property */
4483 }
4484 case 'C':
4485 case 'c':
4486 {
4487 if (LocaleCompare("channels",property) == 0)
4488 {
4489 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4490 "SetReadOnlyProperty","`%s'",property);
4491 return(MagickFalse);
4492 }
4493 if (LocaleCompare("colorspace",property) == 0)
4494 {
4495 ssize_t
4496 colorspace;
4497
4498 colorspace=ParseCommandOption(MagickColorspaceOptions,MagickFalse,
4499 value);
4500 if (colorspace < 0)
4501 return(MagickFalse); /* FUTURE: value exception?? */
4502 return(SetImageColorspace(image,(ColorspaceType) colorspace,exception));
4503 }
4504 if (LocaleCompare("compose",property) == 0)
4505 {
4506 ssize_t
4507 compose;
4508
4509 compose=ParseCommandOption(MagickComposeOptions,MagickFalse,value);
4510 if (compose < 0)
4511 return(MagickFalse); /* FUTURE: value exception?? */
4512 image->compose=(CompositeOperator) compose;
4513 return(MagickTrue);
4514 }
4515 if (LocaleCompare("compress",property) == 0)
4516 {
4517 ssize_t
4518 compression;
4519
4520 compression=ParseCommandOption(MagickCompressOptions,MagickFalse,
4521 value);
4522 if (compression < 0)
4523 return(MagickFalse); /* FUTURE: value exception?? */
4524 image->compression=(CompressionType) compression;
4525 return(MagickTrue);
4526 }
4527 break; /* not an attribute, add as a property */
4528 }
4529 case 'D':
4530 case 'd':
4531 {
4532 if (LocaleCompare("delay",property) == 0)
4533 {
4534 GeometryInfo
4535 geometry_info;
4536
4537 flags=ParseGeometry(value,&geometry_info);
4538 if ((flags & GreaterValue) != 0)
4539 {
4540 if (image->delay > (size_t) floor(geometry_info.rho+0.5))
4541 image->delay=(size_t) floor(geometry_info.rho+0.5);
4542 }
4543 else
4544 if ((flags & LessValue) != 0)
4545 {
4546 if ((double) image->delay < floor(geometry_info.rho+0.5))
4547 image->delay=(size_t) CastDoubleToSsizeT(floor(
4548 geometry_info.sigma+0.5));
4549 }
4550 else
4551 image->delay=(size_t) floor(geometry_info.rho+0.5);
4552 if ((flags & SigmaValue) != 0)
4553 image->ticks_per_second=CastDoubleToSsizeT(floor(
4554 geometry_info.sigma+0.5));
4555 return(MagickTrue);
4556 }
4557 if (LocaleCompare("delay_units",property) == 0)
4558 {
4559 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4560 "SetReadOnlyProperty","`%s'",property);
4561 return(MagickFalse);
4562 }
4563 if (LocaleCompare("density",property) == 0)
4564 {
4565 GeometryInfo
4566 geometry_info;
4567
4568 flags=ParseGeometry(value,&geometry_info);
4569 if ((flags & RhoValue) != 0)
4570 image->resolution.x=geometry_info.rho;
4571 image->resolution.y=image->resolution.x;
4572 if ((flags & SigmaValue) != 0)
4573 image->resolution.y=geometry_info.sigma;
4574 return(MagickTrue);
4575 }
4576 if (LocaleCompare("depth",property) == 0)
4577 {
4578 image->depth=StringToUnsignedLong(value);
4579 return(MagickTrue);
4580 }
4581 if (LocaleCompare("dispose",property) == 0)
4582 {
4583 ssize_t
4584 dispose;
4585
4586 dispose=ParseCommandOption(MagickDisposeOptions,MagickFalse,value);
4587 if (dispose < 0)
4588 return(MagickFalse); /* FUTURE: value exception?? */
4589 image->dispose=(DisposeType) dispose;
4590 return(MagickTrue);
4591 }
4592 break; /* not an attribute, add as a property */
4593 }
4594#if 0 /* Percent escape's sets values with this prefix: for later use
4595 Throwing an exception causes this setting to fail */
4596 case 'E':
4597 case 'e':
4598 {
4599 if (LocaleNCompare("exif:",property,5) == 0)
4600 {
4601 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4602 "SetReadOnlyProperty","`%s'",property);
4603 return(MagickFalse);
4604 }
4605 break; /* not an attribute, add as a property */
4606 }
4607 case 'F':
4608 case 'f':
4609 {
4610 if (LocaleNCompare("fx:",property,3) == 0)
4611 {
4612 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4613 "SetReadOnlyProperty","`%s'",property);
4614 return(MagickFalse);
4615 }
4616 break; /* not an attribute, add as a property */
4617 }
4618#endif
4619 case 'G':
4620 case 'g':
4621 {
4622 if (LocaleCompare("gamma",property) == 0)
4623 {
4624 image->gamma=StringToDouble(value,(char **) NULL);
4625 return(MagickTrue);
4626 }
4627 if (LocaleCompare("gravity",property) == 0)
4628 {
4629 ssize_t
4630 gravity;
4631
4632 gravity=ParseCommandOption(MagickGravityOptions,MagickFalse,value);
4633 if (gravity < 0)
4634 return(MagickFalse); /* FUTURE: value exception?? */
4635 image->gravity=(GravityType) gravity;
4636 return(MagickTrue);
4637 }
4638 break; /* not an attribute, add as a property */
4639 }
4640 case 'H':
4641 case 'h':
4642 {
4643 if (LocaleCompare("height",property) == 0)
4644 {
4645 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4646 "SetReadOnlyProperty","`%s'",property);
4647 return(MagickFalse);
4648 }
4649 break; /* not an attribute, add as a property */
4650 }
4651 case 'I':
4652 case 'i':
4653 {
4654 if (LocaleCompare("intensity",property) == 0)
4655 {
4656 ssize_t
4657 intensity;
4658
4659 intensity=ParseCommandOption(MagickIntensityOptions,MagickFalse,
4660 value);
4661 if (intensity < 0)
4662 return(MagickFalse);
4663 image->intensity=(PixelIntensityMethod) intensity;
4664 return(MagickTrue);
4665 }
4666 if (LocaleCompare("intent",property) == 0)
4667 {
4668 ssize_t
4669 rendering_intent;
4670
4671 rendering_intent=ParseCommandOption(MagickIntentOptions,MagickFalse,
4672 value);
4673 if (rendering_intent < 0)
4674 return(MagickFalse); /* FUTURE: value exception?? */
4675 image->rendering_intent=(RenderingIntent) rendering_intent;
4676 return(MagickTrue);
4677 }
4678 if (LocaleCompare("interpolate",property) == 0)
4679 {
4680 ssize_t
4681 interpolate;
4682
4683 interpolate=ParseCommandOption(MagickInterpolateOptions,MagickFalse,
4684 value);
4685 if (interpolate < 0)
4686 return(MagickFalse); /* FUTURE: value exception?? */
4687 image->interpolate=(PixelInterpolateMethod) interpolate;
4688 return(MagickTrue);
4689 }
4690#if 0 /* Percent escape's sets values with this prefix: for later use
4691 Throwing an exception causes this setting to fail */
4692 if (LocaleNCompare("iptc:",property,5) == 0)
4693 {
4694 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4695 "SetReadOnlyProperty","`%s'",property);
4696 return(MagickFalse);
4697 }
4698#endif
4699 break; /* not an attribute, add as a property */
4700 }
4701 case 'K':
4702 case 'k':
4703 if (LocaleCompare("kurtosis",property) == 0)
4704 {
4705 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4706 "SetReadOnlyProperty","`%s'",property);
4707 return(MagickFalse);
4708 }
4709 break; /* not an attribute, add as a property */
4710 case 'L':
4711 case 'l':
4712 {
4713 if (LocaleCompare("loop",property) == 0)
4714 {
4715 image->iterations=StringToUnsignedLong(value);
4716 return(MagickTrue);
4717 }
4718 break; /* not an attribute, add as a property */
4719 }
4720 case 'M':
4721 case 'm':
4722 if ((LocaleCompare("magick",property) == 0) ||
4723 (LocaleCompare("max",property) == 0) ||
4724 (LocaleCompare("mean",property) == 0) ||
4725 (LocaleCompare("min",property) == 0) ||
4726 (LocaleCompare("min",property) == 0))
4727 {
4728 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4729 "SetReadOnlyProperty","`%s'",property);
4730 return(MagickFalse);
4731 }
4732 break; /* not an attribute, add as a property */
4733 case 'O':
4734 case 'o':
4735 if (LocaleCompare("opaque",property) == 0)
4736 {
4737 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4738 "SetReadOnlyProperty","`%s'",property);
4739 return(MagickFalse);
4740 }
4741 break; /* not an attribute, add as a property */
4742 case 'P':
4743 case 'p':
4744 {
4745 if (LocaleCompare("page",property) == 0)
4746 {
4747 char
4748 *geometry;
4749
4750 geometry=GetPageGeometry(value);
4751 flags=ParseAbsoluteGeometry(geometry,&image->page);
4752 geometry=DestroyString(geometry);
4753 return(MagickTrue);
4754 }
4755#if 0 /* Percent escape's sets values with this prefix: for later use
4756 Throwing an exception causes this setting to fail */
4757 if (LocaleNCompare("pixel:",property,6) == 0)
4758 {
4759 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4760 "SetReadOnlyProperty","`%s'",property);
4761 return(MagickFalse);
4762 }
4763#endif
4764 break; /* not an attribute, add as a property */
4765 }
4766 case 'R':
4767 case 'r':
4768 {
4769 if (LocaleCompare("rendering-intent",property) == 0)
4770 {
4771 ssize_t
4772 rendering_intent;
4773
4774 rendering_intent=ParseCommandOption(MagickIntentOptions,MagickFalse,
4775 value);
4776 if (rendering_intent < 0)
4777 return(MagickFalse); /* FUTURE: value exception?? */
4778 image->rendering_intent=(RenderingIntent) rendering_intent;
4779 return(MagickTrue);
4780 }
4781 break; /* not an attribute, add as a property */
4782 }
4783 case 'S':
4784 case 's':
4785 if ((LocaleCompare("size",property) == 0) ||
4786 (LocaleCompare("skewness",property) == 0) ||
4787 (LocaleCompare("scenes",property) == 0) ||
4788 (LocaleCompare("standard-deviation",property) == 0))
4789 {
4790 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4791 "SetReadOnlyProperty","`%s'",property);
4792 return(MagickFalse);
4793 }
4794 break; /* not an attribute, add as a property */
4795 case 'T':
4796 case 't':
4797 {
4798 if (LocaleCompare("tile-offset",property) == 0)
4799 {
4800 char
4801 *geometry;
4802
4803 geometry=GetPageGeometry(value);
4804 flags=ParseAbsoluteGeometry(geometry,&image->tile_offset);
4805 geometry=DestroyString(geometry);
4806 return(MagickTrue);
4807 }
4808 if (LocaleCompare("type",property) == 0)
4809 {
4810 ssize_t
4811 type;
4812
4813 type=ParseCommandOption(MagickTypeOptions,MagickFalse,value);
4814 if (type < 0)
4815 return(MagickFalse);
4816 image->type=(ImageType) type;
4817 return(MagickTrue);
4818 }
4819 break; /* not an attribute, add as a property */
4820 }
4821 case 'U':
4822 case 'u':
4823 {
4824 if (LocaleCompare("units",property) == 0)
4825 {
4826 ssize_t
4827 units;
4828
4829 units=ParseCommandOption(MagickResolutionOptions,MagickFalse,value);
4830 if (units < 0)
4831 return(MagickFalse); /* FUTURE: value exception?? */
4832 image->units=(ResolutionType) units;
4833 return(MagickTrue);
4834 }
4835 break; /* not an attribute, add as a property */
4836 }
4837 case 'V':
4838 case 'v':
4839 {
4840 if (LocaleCompare("version",property) == 0)
4841 {
4842 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4843 "SetReadOnlyProperty","`%s'",property);
4844 return(MagickFalse);
4845 }
4846 break; /* not an attribute, add as a property */
4847 }
4848 case 'W':
4849 case 'w':
4850 {
4851 if (LocaleCompare("width",property) == 0)
4852 {
4853 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4854 "SetReadOnlyProperty","`%s'",property);
4855 return(MagickFalse);
4856 }
4857 break; /* not an attribute, add as a property */
4858 }
4859#if 0 /* Percent escape's sets values with this prefix: for later use
4860 Throwing an exception causes this setting to fail */
4861 case 'X':
4862 case 'x':
4863 {
4864 if (LocaleNCompare("xmp:",property,4) == 0)
4865 {
4866 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4867 "SetReadOnlyProperty","`%s'",property);
4868 return(MagickFalse);
4869 }
4870 break; /* not an attribute, add as a property */
4871 }
4872#endif
4873 }
4874 /* Default: not an attribute, add as a property */
4875 status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4876 ConstantString(property),ConstantString(value));
4877 /* FUTURE: error if status is bad? */
4878 return(status);
4879}