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