]> 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-2016 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 %    http://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->alpha_color=clone_image->alpha_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 *)
708             attribute,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         node=GetXMLTreeChild(description,(const char *) NULL);
1770         while (node != (XMLTreeInfo *) NULL)
1771         {
1772           child=GetXMLTreeChild(node,(const char *) NULL);
1773           content=GetXMLTreeContent(node);
1774           if ((child == (XMLTreeInfo *) NULL) &&
1775               (SkipXMPValue(content) == MagickFalse))
1776             (void) AddValueToSplayTree((SplayTreeInfo *) image->properties,
1777               ConstantString(GetXMLTreeTag(node)),ConstantString(content));
1778           while (child != (XMLTreeInfo *) NULL)
1779           {
1780             content=GetXMLTreeContent(child);
1781             if (SkipXMPValue(content) == MagickFalse)
1782               (void) AddValueToSplayTree((SplayTreeInfo *) image->properties,
1783                 ConstantString(GetXMLTreeTag(child)),ConstantString(content));
1784             child=GetXMLTreeSibling(child);
1785           }
1786           node=GetXMLTreeSibling(node);
1787         }
1788         description=GetNextXMLTreeTag(description);
1789       }
1790     }
1791   xmp=DestroyXMLTree(xmp);
1792   return(status);
1793 }
1794
1795 static char *TracePSClippath(const unsigned char *blob,size_t length)
1796 {
1797   char
1798     *path,
1799     *message;
1800
1801   MagickBooleanType
1802     in_subpath;
1803
1804   PointInfo
1805     first[3],
1806     last[3],
1807     point[3];
1808
1809   register ssize_t
1810     i,
1811     x;
1812
1813   ssize_t
1814     knot_count,
1815     selector,
1816     y;
1817
1818   path=AcquireString((char *) NULL);
1819   if (path == (char *) NULL)
1820     return((char *) NULL);
1821   message=AcquireString((char *) NULL);
1822   (void) FormatLocaleString(message,MagickPathExtent,"/ClipImage\n");
1823   (void) ConcatenateString(&path,message);
1824   (void) FormatLocaleString(message,MagickPathExtent,"{\n");
1825   (void) ConcatenateString(&path,message);
1826   (void) FormatLocaleString(message,MagickPathExtent,
1827     "  /c {curveto} bind def\n");
1828   (void) ConcatenateString(&path,message);
1829   (void) FormatLocaleString(message,MagickPathExtent,
1830     "  /l {lineto} bind def\n");
1831   (void) ConcatenateString(&path,message);
1832   (void) FormatLocaleString(message,MagickPathExtent,
1833     "  /m {moveto} bind def\n");
1834   (void) ConcatenateString(&path,message);
1835   (void) FormatLocaleString(message,MagickPathExtent,
1836     "  /v {currentpoint 6 2 roll curveto} bind def\n");
1837   (void) ConcatenateString(&path,message);
1838   (void) FormatLocaleString(message,MagickPathExtent,
1839     "  /y {2 copy curveto} bind def\n");
1840   (void) ConcatenateString(&path,message);
1841   (void) FormatLocaleString(message,MagickPathExtent,
1842     "  /z {closepath} bind def\n");
1843   (void) ConcatenateString(&path,message);
1844   (void) FormatLocaleString(message,MagickPathExtent,"  newpath\n");
1845   (void) ConcatenateString(&path,message);
1846   /*
1847     The clipping path format is defined in "Adobe Photoshop File Formats
1848     Specification" version 6.0 downloadable from adobe.com.
1849   */
1850   (void) ResetMagickMemory(point,0,sizeof(point));
1851   (void) ResetMagickMemory(first,0,sizeof(first));
1852   (void) ResetMagickMemory(last,0,sizeof(last));
1853   knot_count=0;
1854   in_subpath=MagickFalse;
1855   while (length > 0)
1856   {
1857     selector=(ssize_t) ReadPropertyMSBShort(&blob,&length);
1858     switch (selector)
1859     {
1860       case 0:
1861       case 3:
1862       {
1863         if (knot_count != 0)
1864           {
1865             blob+=24;
1866             length-=MagickMin(24,(ssize_t) length);
1867             break;
1868           }
1869         /*
1870           Expected subpath length record.
1871         */
1872         knot_count=(ssize_t) ReadPropertyMSBShort(&blob,&length);
1873         blob+=22;
1874         length-=MagickMin(22,(ssize_t) length);
1875         break;
1876       }
1877       case 1:
1878       case 2:
1879       case 4:
1880       case 5:
1881       {
1882         if (knot_count == 0)
1883           {
1884             /*
1885               Unexpected subpath knot
1886             */
1887             blob+=24;
1888             length-=MagickMin(24,(ssize_t) length);
1889             break;
1890           }
1891         /*
1892           Add sub-path knot
1893         */
1894         for (i=0; i < 3; i++)
1895         {
1896           size_t
1897             xx,
1898             yy;
1899
1900           yy=(size_t) ReadPropertyMSBLong(&blob,&length);
1901           xx=(size_t) ReadPropertyMSBLong(&blob,&length);
1902           x=(ssize_t) xx;
1903           if (xx > 2147483647)
1904             x=(ssize_t) xx-4294967295U-1;
1905           y=(ssize_t) yy;
1906           if (yy > 2147483647)
1907             y=(ssize_t) yy-4294967295U-1;
1908           point[i].x=(double) x/4096/4096;
1909           point[i].y=1.0-(double) y/4096/4096;
1910         }
1911         if (in_subpath == MagickFalse)
1912           {
1913             (void) FormatLocaleString(message,MagickPathExtent,"  %g %g m\n",
1914               point[1].x,point[1].y);
1915             for (i=0; i < 3; i++)
1916             {
1917               first[i]=point[i];
1918               last[i]=point[i];
1919             }
1920           }
1921         else
1922           {
1923             /*
1924               Handle special cases when Bezier curves are used to describe
1925               corners and straight lines.
1926             */
1927             if ((last[1].x == last[2].x) && (last[1].y == last[2].y) &&
1928                 (point[0].x == point[1].x) && (point[0].y == point[1].y))
1929               (void) FormatLocaleString(message,MagickPathExtent,
1930                 "  %g %g l\n",point[1].x,point[1].y);
1931             else
1932               if ((last[1].x == last[2].x) && (last[1].y == last[2].y))
1933                 (void) FormatLocaleString(message,MagickPathExtent,
1934                   "  %g %g %g %g v\n",point[0].x,point[0].y,
1935                   point[1].x,point[1].y);
1936               else
1937                 if ((point[0].x == point[1].x) && (point[0].y == point[1].y))
1938                   (void) FormatLocaleString(message,MagickPathExtent,
1939                     "  %g %g %g %g y\n",last[2].x,last[2].y,
1940                     point[1].x,point[1].y);
1941                 else
1942                   (void) FormatLocaleString(message,MagickPathExtent,
1943                     "  %g %g %g %g %g %g c\n",last[2].x,
1944                     last[2].y,point[0].x,point[0].y,point[1].x,point[1].y);
1945             for (i=0; i < 3; i++)
1946               last[i]=point[i];
1947           }
1948         (void) ConcatenateString(&path,message);
1949         in_subpath=MagickTrue;
1950         knot_count--;
1951         /*
1952           Close the subpath if there are no more knots.
1953         */
1954         if (knot_count == 0)
1955           {
1956             /*
1957               Same special handling as above except we compare to the
1958               first point in the path and close the path.
1959             */
1960             if ((last[1].x == last[2].x) && (last[1].y == last[2].y) &&
1961                 (first[0].x == first[1].x) && (first[0].y == first[1].y))
1962               (void) FormatLocaleString(message,MagickPathExtent,
1963                 "  %g %g l z\n",first[1].x,first[1].y);
1964             else
1965               if ((last[1].x == last[2].x) && (last[1].y == last[2].y))
1966                 (void) FormatLocaleString(message,MagickPathExtent,
1967                   "  %g %g %g %g v z\n",first[0].x,first[0].y,
1968                   first[1].x,first[1].y);
1969               else
1970                 if ((first[0].x == first[1].x) && (first[0].y == first[1].y))
1971                   (void) FormatLocaleString(message,MagickPathExtent,
1972                     "  %g %g %g %g y z\n",last[2].x,last[2].y,
1973                     first[1].x,first[1].y);
1974                 else
1975                   (void) FormatLocaleString(message,MagickPathExtent,
1976                     "  %g %g %g %g %g %g c z\n",last[2].x,
1977                     last[2].y,first[0].x,first[0].y,first[1].x,first[1].y);
1978             (void) ConcatenateString(&path,message);
1979             in_subpath=MagickFalse;
1980           }
1981         break;
1982       }
1983       case 6:
1984       case 7:
1985       case 8:
1986       default:
1987       {
1988         blob+=24;
1989         length-=MagickMin(24,(ssize_t) length);
1990         break;
1991       }
1992     }
1993   }
1994   /*
1995     Returns an empty PS path if the path has no knots.
1996   */
1997   (void) FormatLocaleString(message,MagickPathExtent,"  eoclip\n");
1998   (void) ConcatenateString(&path,message);
1999   (void) FormatLocaleString(message,MagickPathExtent,"} bind def");
2000   (void) ConcatenateString(&path,message);
2001   message=DestroyString(message);
2002   return(path);
2003 }
2004
2005 static char *TraceSVGClippath(const unsigned char *blob,size_t length,
2006   const size_t columns,const size_t rows)
2007 {
2008   char
2009     *path,
2010     *message;
2011
2012   MagickBooleanType
2013     in_subpath;
2014
2015   PointInfo
2016     first[3],
2017     last[3],
2018     point[3];
2019
2020   register ssize_t
2021     i;
2022
2023   ssize_t
2024     knot_count,
2025     selector,
2026     x,
2027     y;
2028
2029   path=AcquireString((char *) NULL);
2030   if (path == (char *) NULL)
2031     return((char *) NULL);
2032   message=AcquireString((char *) NULL);
2033   (void) FormatLocaleString(message,MagickPathExtent,(
2034     "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
2035     "<svg xmlns=\"http://www.w3.org/2000/svg\""
2036     " width=\"%.20g\" height=\"%.20g\">\n"
2037     "<g>\n"
2038     "<path fill-rule=\"evenodd\" style=\"fill:#00000000;stroke:#00000000;"
2039     "stroke-width:0;stroke-antialiasing:false\" d=\"\n"),(double) columns,
2040     (double) rows);
2041   (void) ConcatenateString(&path,message);
2042   (void) ResetMagickMemory(point,0,sizeof(point));
2043   (void) ResetMagickMemory(first,0,sizeof(first));
2044   (void) ResetMagickMemory(last,0,sizeof(last));
2045   knot_count=0;
2046   in_subpath=MagickFalse;
2047   while (length != 0)
2048   {
2049     selector=(ssize_t) ReadPropertyMSBShort(&blob,&length);
2050     switch (selector)
2051     {
2052       case 0:
2053       case 3:
2054       {
2055         if (knot_count != 0)
2056           {
2057             blob+=24;
2058             length-=MagickMin(24,(ssize_t) length);
2059             break;
2060           }
2061         /*
2062           Expected subpath length record.
2063         */
2064         knot_count=(ssize_t) ReadPropertyMSBShort(&blob,&length);
2065         blob+=22;
2066         length-=MagickMin(22,(ssize_t) length);
2067         break;
2068       }
2069       case 1:
2070       case 2:
2071       case 4:
2072       case 5:
2073       {
2074         if (knot_count == 0)
2075           {
2076             /*
2077               Unexpected subpath knot.
2078             */
2079             blob+=24;
2080             length-=MagickMin(24,(ssize_t) length);
2081             break;
2082           }
2083         /*
2084           Add sub-path knot
2085         */
2086         for (i=0; i < 3; i++)
2087         {
2088           unsigned int
2089             xx,
2090             yy;
2091
2092           yy=(unsigned int) ReadPropertyMSBLong(&blob,&length);
2093           xx=(unsigned int) ReadPropertyMSBLong(&blob,&length);
2094           x=(ssize_t) xx;
2095           if (xx > 2147483647)
2096             x=(ssize_t) xx-4294967295U-1;
2097           y=(ssize_t) yy;
2098           if (yy > 2147483647)
2099             y=(ssize_t) yy-4294967295U-1;
2100           point[i].x=(double) x*columns/4096/4096;
2101           point[i].y=(double) y*rows/4096/4096;
2102         }
2103         if (in_subpath == MagickFalse)
2104           {
2105             (void) FormatLocaleString(message,MagickPathExtent,"M %g %g\n",
2106               point[1].x,point[1].y);
2107             for (i=0; i < 3; i++)
2108             {
2109               first[i]=point[i];
2110               last[i]=point[i];
2111             }
2112           }
2113         else
2114           {
2115             /*
2116               Handle special cases when Bezier curves are used to describe
2117               corners and straight lines.
2118             */
2119             if ((last[1].x == last[2].x) && (last[1].y == last[2].y) &&
2120                 (point[0].x == point[1].x) && (point[0].y == point[1].y))
2121               (void) FormatLocaleString(message,MagickPathExtent,
2122                 "L %g %g\n",point[1].x,point[1].y);
2123             else
2124               (void) FormatLocaleString(message,MagickPathExtent,
2125                 "C %g %g %g %g %g %g\n",last[2].x,
2126                 last[2].y,point[0].x,point[0].y,point[1].x,point[1].y);
2127             for (i=0; i < 3; i++)
2128               last[i]=point[i];
2129           }
2130         (void) ConcatenateString(&path,message);
2131         in_subpath=MagickTrue;
2132         knot_count--;
2133         /*
2134           Close the subpath if there are no more knots.
2135         */
2136         if (knot_count == 0)
2137           {
2138            /*
2139               Same special handling as above except we compare to the
2140               first point in the path and close the path.
2141             */
2142             if ((last[1].x == last[2].x) && (last[1].y == last[2].y) &&
2143                 (first[0].x == first[1].x) && (first[0].y == first[1].y))
2144               (void) FormatLocaleString(message,MagickPathExtent,
2145                 "L %g %g Z\n",first[1].x,first[1].y);
2146             else
2147               (void) FormatLocaleString(message,MagickPathExtent,
2148                 "C %g %g %g %g %g %g Z\n",last[2].x,
2149                 last[2].y,first[0].x,first[0].y,first[1].x,first[1].y);
2150             (void) ConcatenateString(&path,message);
2151             in_subpath=MagickFalse;
2152           }
2153         break;
2154       }
2155       case 6:
2156       case 7:
2157       case 8:
2158       default:
2159       {
2160         blob+=24;
2161         length-=MagickMin(24,(ssize_t) length);
2162         break;
2163       }
2164     }
2165   }
2166   /*
2167     Return an empty SVG image if the path does not have knots.
2168   */
2169   (void) ConcatenateString(&path,"\"/>\n</g>\n</svg>\n");
2170   message=DestroyString(message);
2171   return(path);
2172 }
2173
2174 MagickExport const char *GetImageProperty(const Image *image,
2175   const char *property,ExceptionInfo *exception)
2176 {
2177   register const char
2178     *p;
2179
2180   assert(image != (Image *) NULL);
2181   assert(image->signature == MagickCoreSignature);
2182   if (image->debug != MagickFalse)
2183     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2184   p=(const char *) NULL;
2185   if (image->properties != (void *) NULL)
2186     {
2187       if (property == (const char *) NULL)
2188         {
2189           ResetSplayTreeIterator((SplayTreeInfo *) image->properties);
2190           p=(const char *) GetNextValueInSplayTree((SplayTreeInfo *)
2191             image->properties);
2192           return(p);
2193         }
2194         p=(const char *) GetValueFromSplayTree((SplayTreeInfo *)
2195           image->properties,property);
2196         if (p != (const char *) NULL)
2197           return(p);
2198     }
2199   if ((property == (const char *) NULL) ||
2200       (strchr(property,':') == (char *) NULL))
2201     return(p);
2202   switch (*property)
2203   {
2204     case '8':
2205     {
2206       if (LocaleNCompare("8bim:",property,5) == 0)
2207         {
2208           (void) Get8BIMProperty(image,property,exception);
2209           break;
2210         }
2211       break;
2212     }
2213     case 'E':
2214     case 'e':
2215     {
2216       if (LocaleNCompare("exif:",property,5) == 0)
2217         {
2218           (void) GetEXIFProperty(image,property,exception);
2219           break;
2220         }
2221       break;
2222     }
2223     case 'I':
2224     case 'i':
2225     {
2226       if ((LocaleNCompare("icc:",property,4) == 0) ||
2227           (LocaleNCompare("icm:",property,4) == 0))
2228         {
2229           (void) GetICCProperty(image,property,exception);
2230           break;
2231         }
2232       if (LocaleNCompare("iptc:",property,5) == 0)
2233         {
2234           (void) GetIPTCProperty(image,property,exception);
2235           break;
2236         }
2237       break;
2238     }
2239     case 'X':
2240     case 'x':
2241     {
2242       if (LocaleNCompare("xmp:",property,4) == 0)
2243         {
2244           (void) GetXMPProperty(image,property);
2245           break;
2246         }
2247       break;
2248     }
2249     default:
2250       break;
2251   }
2252   if (image->properties != (void *) NULL)
2253     {
2254       p=(const char *) GetValueFromSplayTree((SplayTreeInfo *)
2255         image->properties,property);
2256       return(p);
2257     }
2258   return((const char *) NULL);
2259 }
2260 \f
2261 /*
2262 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2263 %                                                                             %
2264 %                                                                             %
2265 %                                                                             %
2266 +   G e t M a g i c k P r o p e r t y                                         %
2267 %                                                                             %
2268 %                                                                             %
2269 %                                                                             %
2270 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2271 %
2272 %  GetMagickProperty() gets attributes or calculated values that is associated
2273 %  with a fixed known property name, or single letter property. It may be
2274 %  called if no image is defined (IMv7), in which case only global image_info
2275 %  values are available:
2276 %
2277 %    \n   newline
2278 %    \r   carriage return
2279 %    <    less-than character.
2280 %    >    greater-than character.
2281 %    &    ampersand character.
2282 %    %%   a percent sign
2283 %    %b   file size of image read in
2284 %    %c   comment meta-data property
2285 %    %d   directory component of path
2286 %    %e   filename extension or suffix
2287 %    %f   filename (including suffix)
2288 %    %g   layer canvas page geometry   (equivalent to "%Wx%H%X%Y")
2289 %    %h   current image height in pixels
2290 %    %i   image filename (note: becomes output filename for "info:")
2291 %    %k   CALCULATED: number of unique colors
2292 %    %l   label meta-data property
2293 %    %m   image file format (file magic)
2294 %    %n   number of images in current image sequence
2295 %    %o   output filename  (used for delegates)
2296 %    %p   index of image in current image list
2297 %    %q   quantum depth (compile-time constant)
2298 %    %r   image class and colorspace
2299 %    %s   scene number (from input unless re-assigned)
2300 %    %t   filename without directory or extension (suffix)
2301 %    %u   unique temporary filename (used for delegates)
2302 %    %w   current width in pixels
2303 %    %x   x resolution (density)
2304 %    %y   y resolution (density)
2305 %    %z   image depth (as read in unless modified, image save depth)
2306 %    %A   image transparency channel enabled (true/false)
2307 %    %C   image compression type
2308 %    %D   image GIF dispose method
2309 %    %G   original image size (%wx%h; before any resizes)
2310 %    %H   page (canvas) height
2311 %    %M   Magick filename (original file exactly as given,  including read mods)
2312 %    %O   page (canvas) offset ( = %X%Y )
2313 %    %P   page (canvas) size ( = %Wx%H )
2314 %    %Q   image compression quality ( 0 = default )
2315 %    %S   ?? scenes ??
2316 %    %T   image time delay (in centi-seconds)
2317 %    %U   image resolution units
2318 %    %W   page (canvas) width
2319 %    %X   page (canvas) x offset (including sign)
2320 %    %Y   page (canvas) y offset (including sign)
2321 %    %Z   unique filename (used for delegates)
2322 %    %@   CALCULATED: trim bounding box (without actually trimming)
2323 %    %#   CALCULATED: 'signature' hash of image values
2324 %
2325 %  This routine only handles specifically known properties.  It does not
2326 %  handle special prefixed properties, profiles, or expressions. Nor does
2327 %  it return any free-form property strings.
2328 %
2329 %  The returned string is stored in a structure somewhere, and should not be
2330 %  directly freed.  If the string was generated (common) the string will be
2331 %  stored as as either as artifact or option 'get-property'.  These may be
2332 %  deleted (cleaned up) when no longer required, but neither artifact or
2333 %  option is guranteed to exist.
2334 %
2335 %  The format of the GetMagickProperty method is:
2336 %
2337 %      const char *GetMagickProperty(ImageInfo *image_info,Image *image,
2338 %        const char *property,ExceptionInfo *exception)
2339 %
2340 %  A description of each parameter follows:
2341 %
2342 %    o image_info: the image info (optional)
2343 %
2344 %    o image: the image (optional)
2345 %
2346 %    o key: the key.
2347 %
2348 %    o exception: return any errors or warnings in this structure.
2349 %
2350 */
2351 static const char *GetMagickPropertyLetter(ImageInfo *image_info,
2352   Image *image,const char letter,ExceptionInfo *exception)
2353 {
2354 #define WarnNoImageReturn(format,arg) \
2355   if (image == (Image *) NULL ) { \
2356     (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning, \
2357       "NoImageForProperty",format,arg); \
2358     return((const char *) NULL); \
2359   }
2360 #define WarnNoImageInfoReturn(format,arg) \
2361   if (image_info == (ImageInfo *) NULL ) { \
2362     (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning, \
2363       "NoImageInfoForProperty",format,arg); \
2364     return((const char *) NULL); \
2365   }
2366
2367   char
2368     value[MagickPathExtent];  /* formatted string to store as an artifact */
2369
2370   const char
2371     *string;     /* return a string already stored somewher */
2372
2373   if ((image != (Image *) NULL) && (image->debug != MagickFalse))
2374     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2375   else
2376     if ((image_info != (ImageInfo *) NULL) &&
2377         (image_info->debug != MagickFalse))
2378     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s","no-images");
2379   *value='\0';           /* formatted string */
2380   string=(char *) NULL;  /* constant string reference */
2381   /*
2382     Get properities that are directly defined by images.
2383   */
2384   switch (letter)
2385   {
2386     case 'b':  /* image size read in - in bytes */
2387     {
2388       WarnNoImageReturn("\"%%%c\"",letter);
2389       (void) FormatMagickSize(image->extent,MagickFalse,"B",MagickPathExtent,
2390         value);
2391       if (image->extent == 0)
2392         (void) FormatMagickSize(GetBlobSize(image),MagickFalse,"B",
2393           MagickPathExtent,value);
2394       break;
2395     }
2396     case 'c':  /* image comment property - empty string by default */
2397     {
2398       WarnNoImageReturn("\"%%%c\"",letter);
2399       string=GetImageProperty(image,"comment",exception);
2400       if ( string == (const char *) NULL )
2401         string="";
2402       break;
2403     }
2404     case 'd':  /* Directory component of filename */
2405     {
2406       WarnNoImageReturn("\"%%%c\"",letter);
2407       GetPathComponent(image->magick_filename,HeadPath,value);
2408       if (*value == '\0')
2409         string="";
2410       break;
2411     }
2412     case 'e': /* Filename extension (suffix) of image file */
2413     {
2414       WarnNoImageReturn("\"%%%c\"",letter);
2415       GetPathComponent(image->magick_filename,ExtensionPath,value);
2416       if (*value == '\0')
2417         string="";
2418       break;
2419     }
2420     case 'f': /* Filename without directory component */
2421     {
2422       WarnNoImageReturn("\"%%%c\"",letter);
2423       GetPathComponent(image->magick_filename,TailPath,value);
2424       if (*value == '\0')
2425         string="";
2426       break;
2427     }
2428     case 'g': /* Image geometry, canvas and offset  %Wx%H+%X+%Y */
2429     {
2430       WarnNoImageReturn("\"%%%c\"",letter);
2431       (void) FormatLocaleString(value,MagickPathExtent,
2432         "%.20gx%.20g%+.20g%+.20g",(double) image->page.width,(double)
2433         image->page.height,(double) image->page.x,(double) image->page.y);
2434       break;
2435     }
2436     case 'h': /* Image height (current) */
2437     {
2438       WarnNoImageReturn("\"%%%c\"",letter);
2439       (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2440         (image->rows != 0 ? image->rows : image->magick_rows));
2441       break;
2442     }
2443     case 'i': /* Filename last used for an image (read or write) */
2444     {
2445       WarnNoImageReturn("\"%%%c\"",letter);
2446       string=image->filename;
2447       break;
2448     }
2449     case 'k': /* Number of unique colors  */
2450     {
2451       /*
2452         FUTURE: ensure this does not generate the formatted comment!
2453       */
2454       WarnNoImageReturn("\"%%%c\"",letter);
2455       (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2456         GetNumberColors(image,(FILE *) NULL,exception));
2457       break;
2458     }
2459     case 'l': /* Image label property - empty string by default */
2460     {
2461       WarnNoImageReturn("\"%%%c\"",letter);
2462       string=GetImageProperty(image,"label",exception);
2463       if (string == (const char *) NULL)
2464         string="";
2465       break;
2466     }
2467     case 'm': /* Image format (file magick) */
2468     {
2469       WarnNoImageReturn("\"%%%c\"",letter);
2470       string=image->magick;
2471       break;
2472     }
2473     case 'n': /* Number of images in the list.  */
2474     {
2475       if ( image != (Image *) NULL )
2476         (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2477           GetImageListLength(image));
2478       else
2479         string="0";    /* no images or scenes */
2480       break;
2481     }
2482     case 'o': /* Output Filename - for delegate use only */
2483       WarnNoImageInfoReturn("\"%%%c\"",letter);
2484       string=image_info->filename;
2485       break;
2486     case 'p': /* Image index in current image list */
2487     {
2488       WarnNoImageReturn("\"%%%c\"",letter);
2489       (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2490         GetImageIndexInList(image));
2491       break;
2492     }
2493     case 'q': /* Quantum depth of image in memory */
2494     {
2495       WarnNoImageReturn("\"%%%c\"",letter);
2496       (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2497         MAGICKCORE_QUANTUM_DEPTH);
2498       break;
2499     }
2500     case 'r': /* Image storage class, colorspace, and alpha enabled.  */
2501     {
2502       ColorspaceType
2503         colorspace;
2504
2505       WarnNoImageReturn("\"%%%c\"",letter);
2506       colorspace=image->colorspace;
2507       if (SetImageGray(image,exception) != MagickFalse)
2508         colorspace=GRAYColorspace;   /* FUTURE: this is IMv6 not IMv7 */
2509       (void) FormatLocaleString(value,MagickPathExtent,"%s %s %s",
2510         CommandOptionToMnemonic(MagickClassOptions,(ssize_t)
2511         image->storage_class),CommandOptionToMnemonic(MagickColorspaceOptions,
2512         (ssize_t) colorspace),image->alpha_trait != UndefinedPixelTrait ?
2513         "Alpha" : "");
2514       break;
2515     }
2516     case 's': /* Image scene number */
2517     {
2518 #if 0  /* this seems non-sensical -- simplifing */
2519       if (image_info->number_scenes != 0)
2520         (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2521           image_info->scene);
2522       else if (image != (Image *) NULL)
2523         (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2524           image->scene);
2525       else
2526           string="0";
2527 #else
2528       WarnNoImageReturn("\"%%%c\"",letter);
2529       (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2530          image->scene);
2531 #endif
2532       break;
2533     }
2534     case 't': /* Base filename without directory or extention */
2535     {
2536       WarnNoImageReturn("\"%%%c\"",letter);
2537       GetPathComponent(image->magick_filename,BasePath,value);
2538       if (*value == '\0')
2539         string="";
2540       break;
2541     }
2542     case 'u': /* Unique filename */
2543     {
2544       WarnNoImageInfoReturn("\"%%%c\"",letter);
2545       string=image_info->unique;
2546       break;
2547     }
2548     case 'w': /* Image width (current) */
2549     {
2550       WarnNoImageReturn("\"%%%c\"",letter);
2551       (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2552         (image->columns != 0 ? image->columns : image->magick_columns));
2553       break;
2554     }
2555     case 'x': /* Image horizontal resolution (with units) */
2556     {
2557       WarnNoImageReturn("\"%%%c\"",letter);
2558       (void) FormatLocaleString(value,MagickPathExtent,"%.20g",
2559         fabs(image->resolution.x) > MagickEpsilon ? image->resolution.x : 72.0);
2560       break;
2561     }
2562     case 'y': /* Image vertical resolution (with units) */
2563     {
2564       WarnNoImageReturn("\"%%%c\"",letter);
2565       (void) FormatLocaleString(value,MagickPathExtent,"%.20g",
2566         fabs(image->resolution.y) > MagickEpsilon ? image->resolution.y : 72.0);
2567       break;
2568     }
2569     case 'z': /* Image depth as read in */
2570     {
2571       WarnNoImageReturn("\"%%%c\"",letter);
2572       (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2573         image->depth);
2574       break;
2575     }
2576     case 'A': /* Image alpha channel  */
2577     {
2578       WarnNoImageReturn("\"%%%c\"",letter);
2579       string=CommandOptionToMnemonic(MagickPixelTraitOptions,(ssize_t)
2580         image->alpha_trait);
2581       break;
2582     }
2583     case 'C': /* Image compression method.  */
2584     {
2585       WarnNoImageReturn("\"%%%c\"",letter);
2586       string=CommandOptionToMnemonic(MagickCompressOptions,(ssize_t)
2587         image->compression);
2588       break;
2589     }
2590     case 'D': /* Image dispose method.  */
2591     {
2592       WarnNoImageReturn("\"%%%c\"",letter);
2593       string=CommandOptionToMnemonic(MagickDisposeOptions,(ssize_t)
2594         image->dispose);
2595       break;
2596     }
2597     case 'G': /* Image size as geometry = "%wx%h" */
2598     {
2599       WarnNoImageReturn("\"%%%c\"",letter);
2600       (void) FormatLocaleString(value,MagickPathExtent,"%.20gx%.20g",(double)
2601         image->magick_columns,(double) image->magick_rows);
2602       break;
2603     }
2604     case 'H': /* layer canvas height */
2605     {
2606       WarnNoImageReturn("\"%%%c\"",letter);
2607       (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2608         image->page.height);
2609       break;
2610     }
2611     case 'M': /* Magick filename - filename given incl. coder & read mods */
2612     {
2613       WarnNoImageReturn("\"%%%c\"",letter);
2614       string=image->magick_filename;
2615       break;
2616     }
2617     case 'O': /* layer canvas offset with sign = "+%X+%Y" */
2618     {
2619       WarnNoImageReturn("\"%%%c\"",letter);
2620       (void) FormatLocaleString(value,MagickPathExtent,"%+ld%+ld",(long)
2621         image->page.x,(long) image->page.y);
2622       break;
2623     }
2624     case 'P': /* layer canvas page size = "%Wx%H" */
2625     {
2626       WarnNoImageReturn("\"%%%c\"",letter);
2627       (void) FormatLocaleString(value,MagickPathExtent,"%.20gx%.20g",(double)
2628         image->page.width,(double) image->page.height);
2629       break;
2630     }
2631     case 'Q': /* image compression quality */
2632     {
2633       WarnNoImageReturn("\"%%%c\"",letter);
2634       (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2635         (image->quality == 0 ? 92 : image->quality));
2636       break;
2637     }
2638     case 'S': /* Number of scenes in image list.  */
2639     {
2640       WarnNoImageInfoReturn("\"%%%c\"",letter);
2641 #if 0 /* What is this number? -- it makes no sense - simplifing */
2642       if (image_info->number_scenes == 0)
2643          string="2147483647";
2644       else if ( image != (Image *) NULL )
2645         (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2646                 image_info->scene+image_info->number_scenes);
2647       else
2648         string="0";
2649 #else
2650       (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2651         (image_info->number_scenes == 0 ? 2147483647 :
2652          image_info->number_scenes));
2653 #endif
2654       break;
2655     }
2656     case 'T': /* image time delay for animations */
2657     {
2658       WarnNoImageReturn("\"%%%c\"",letter);
2659       (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2660         image->delay);
2661       break;
2662     }
2663     case 'U': /* Image resolution units. */
2664     {
2665       WarnNoImageReturn("\"%%%c\"",letter);
2666       string=CommandOptionToMnemonic(MagickResolutionOptions,(ssize_t)
2667         image->units);
2668       break;
2669     }
2670     case 'W': /* layer canvas width */
2671     {
2672       WarnNoImageReturn("\"%%%c\"",letter);
2673       (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2674         image->page.width);
2675       break;
2676     }
2677     case 'X': /* layer canvas X offset */
2678     {
2679       WarnNoImageReturn("\"%%%c\"",letter);
2680       (void) FormatLocaleString(value,MagickPathExtent,"%+.20g",(double)
2681         image->page.x);
2682       break;
2683     }
2684     case 'Y': /* layer canvas Y offset */
2685     {
2686       WarnNoImageReturn("\"%%%c\"",letter);
2687       (void) FormatLocaleString(value,MagickPathExtent,"%+.20g",(double)
2688         image->page.y);
2689       break;
2690     }
2691     case '%': /* percent escaped */
2692     {
2693       string="%";
2694       break;
2695     }
2696     case '@': /* Trim bounding box, without actually Trimming! */
2697     {
2698       RectangleInfo
2699         page;
2700
2701       WarnNoImageReturn("\"%%%c\"",letter);
2702       page=GetImageBoundingBox(image,exception);
2703       (void) FormatLocaleString(value,MagickPathExtent,
2704         "%.20gx%.20g%+.20g%+.20g",(double) page.width,(double) page.height,
2705         (double) page.x,(double)page.y);
2706       break;
2707     }
2708     case '#':
2709     {
2710       /*
2711         Image signature.
2712       */
2713       WarnNoImageReturn("\"%%%c\"",letter);
2714       (void) SignatureImage(image,exception);
2715       string=GetImageProperty(image,"signature",exception);
2716       break;
2717     }
2718   }
2719   if (string != (char *) NULL)
2720     return(string);
2721   if (*value != '\0')
2722     {
2723       /*
2724         Create a cloned copy of result.
2725       */
2726       if (image != (Image *) NULL)
2727         {
2728           (void) SetImageArtifact(image,"get-property",value);
2729           return(GetImageArtifact(image,"get-property"));
2730         }
2731       else
2732         {
2733           (void) SetImageOption(image_info,"get-property",value);
2734           return(GetImageOption(image_info,"get-property"));
2735         }
2736     }
2737   return((char *) NULL);
2738 }
2739
2740 MagickExport const char *GetMagickProperty(ImageInfo *image_info,
2741   Image *image,const char *property,ExceptionInfo *exception)
2742 {
2743   char
2744     value[MagickPathExtent];
2745
2746   const char
2747     *string;
2748
2749   assert(property[0] != '\0');
2750   assert(image != (Image *) NULL || image_info != (ImageInfo *) NULL );
2751   if (property[1] == '\0')  /* single letter property request */
2752     return(GetMagickPropertyLetter(image_info,image,*property,exception));
2753   if ((image != (Image *) NULL) && (image->debug != MagickFalse))
2754     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2755   else
2756     if ((image_info != (ImageInfo *) NULL) &&
2757         (image_info->debug != MagickFalse))
2758     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s","no-images");
2759   *value='\0';           /* formated string */
2760   string=(char *) NULL;  /* constant string reference */
2761   switch (*property)
2762   {
2763     case 'b':
2764     {
2765       if (LocaleCompare("basename",property) == 0)
2766         {
2767           WarnNoImageReturn("\"%%[%s]\"",property);
2768           GetPathComponent(image->magick_filename,BasePath,value);
2769           if (*value == '\0')
2770             string="";
2771           break;
2772         }
2773       if (LocaleCompare("bit-depth",property) == 0)
2774         {
2775           (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2776             GetImageDepth(image,exception));
2777           break;
2778         }
2779       break;
2780     }
2781     case 'c':
2782     {
2783       if (LocaleCompare("channels",property) == 0)
2784         {
2785           WarnNoImageReturn("\"%%[%s]\"",property);
2786           /* FUTURE: return actual image channels */
2787           (void) FormatLocaleString(value,MagickPathExtent,"%s",
2788             CommandOptionToMnemonic(MagickColorspaceOptions,(ssize_t)
2789             image->colorspace));
2790           LocaleLower(value);
2791           if( image->alpha_trait != UndefinedPixelTrait )
2792             (void) ConcatenateMagickString(value,"a",MagickPathExtent);
2793           break;
2794         }
2795       if (LocaleCompare("colors",property) == 0)
2796         {
2797           WarnNoImageReturn("\"%%[%s]\"",property);
2798           (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2799             GetNumberColors(image,(FILE *) NULL,exception));
2800           break;
2801         }
2802       if (LocaleCompare("colorspace",property) == 0)
2803         {
2804           WarnNoImageReturn("\"%%[%s]\"",property);
2805           /* FUTURE: return actual colorspace - no 'gray' stuff */
2806           string=CommandOptionToMnemonic(MagickColorspaceOptions,(ssize_t)
2807             image->colorspace);
2808           break;
2809         }
2810       if (LocaleCompare("compose",property) == 0)
2811         {
2812           WarnNoImageReturn("\"%%[%s]\"",property);
2813           string=CommandOptionToMnemonic(MagickComposeOptions,(ssize_t)
2814             image->compose);
2815           break;
2816         }
2817       if (LocaleCompare("compression",property) == 0)
2818         {
2819           WarnNoImageReturn("\"%%[%s]\"",property);
2820           string=CommandOptionToMnemonic(MagickCompressOptions,(ssize_t)
2821             image->compression);
2822           break;
2823         }
2824       if (LocaleCompare("copyright",property) == 0)
2825         {
2826           (void) CopyMagickString(value,GetMagickCopyright(),MagickPathExtent);
2827           break;
2828         }
2829       break;
2830     }
2831     case 'd':
2832     {
2833       if (LocaleCompare("depth",property) == 0)
2834         {
2835           WarnNoImageReturn("\"%%[%s]\"",property);
2836           (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
2837             image->depth);
2838           break;
2839         }
2840       if (LocaleCompare("directory",property) == 0)
2841         {
2842           WarnNoImageReturn("\"%%[%s]\"",property);
2843           GetPathComponent(image->magick_filename,HeadPath,value);
2844           if (*value == '\0')
2845             string="";
2846           break;
2847         }
2848       break;
2849     }
2850     case 'e':
2851     {
2852       if (LocaleCompare("entropy",property) == 0)
2853         {
2854           double
2855             entropy;
2856
2857           WarnNoImageReturn("\"%%[%s]\"",property);
2858           (void) GetImageEntropy(image,&entropy,exception);
2859           (void) FormatLocaleString(value,MagickPathExtent,"%.*g",
2860             GetMagickPrecision(),entropy);
2861           break;
2862         }
2863       if (LocaleCompare("extension",property) == 0)
2864         {
2865           WarnNoImageReturn("\"%%[%s]\"",property);
2866           GetPathComponent(image->magick_filename,ExtensionPath,value);
2867           if (*value == '\0')
2868             string="";
2869           break;
2870         }
2871       break;
2872     }
2873     case 'g':
2874     {
2875       if (LocaleCompare("gamma",property) == 0)
2876         {
2877           WarnNoImageReturn("\"%%[%s]\"",property);
2878           (void) FormatLocaleString(value,MagickPathExtent,"%.*g",
2879             GetMagickPrecision(),image->gamma);
2880           break;
2881         }
2882       break;
2883     }
2884     case 'h':
2885     {
2886       if (LocaleCompare("height",property) == 0)
2887         {
2888           WarnNoImageReturn("\"%%[%s]\"",property);
2889           (void) FormatLocaleString(value,MagickPathExtent,"%.20g",
2890             image->magick_rows != 0 ? (double) image->magick_rows : 256.0);
2891           break;
2892         }
2893       break;
2894     }
2895     case 'i':
2896     {
2897       if (LocaleCompare("input",property) == 0)
2898         {
2899           WarnNoImageReturn("\"%%[%s]\"",property);
2900           string=image->filename;
2901           break;
2902         }
2903       if (LocaleCompare("interlace",property) == 0)
2904         {
2905           WarnNoImageReturn("\"%%[%s]\"",property);
2906           string=CommandOptionToMnemonic(MagickInterlaceOptions,(ssize_t)
2907             image->interlace);
2908           break;
2909         }
2910       break;
2911     }
2912     case 'k':
2913     {
2914       if (LocaleCompare("kurtosis",property) == 0)
2915         {
2916           double
2917             kurtosis,
2918             skewness;
2919
2920           WarnNoImageReturn("\"%%[%s]\"",property);
2921           (void) GetImageKurtosis(image,&kurtosis,&skewness,exception);
2922           (void) FormatLocaleString(value,MagickPathExtent,"%.*g",
2923             GetMagickPrecision(),kurtosis);
2924           break;
2925         }
2926       break;
2927     }
2928     case 'm':
2929     {
2930       if (LocaleCompare("magick",property) == 0)
2931         {
2932           WarnNoImageReturn("\"%%[%s]\"",property);
2933           string=image->magick;
2934           break;
2935         }
2936       if ((LocaleCompare("maxima",property) == 0) ||
2937           (LocaleCompare("max",property) == 0))
2938         {
2939           double
2940             maximum,
2941             minimum;
2942
2943           WarnNoImageReturn("\"%%[%s]\"",property);
2944           (void) GetImageRange(image,&minimum,&maximum,exception);
2945           (void) FormatLocaleString(value,MagickPathExtent,"%.*g",
2946             GetMagickPrecision(),maximum);
2947           break;
2948         }
2949       if (LocaleCompare("mean",property) == 0)
2950         {
2951           double
2952             mean,
2953             standard_deviation;
2954
2955           WarnNoImageReturn("\"%%[%s]\"",property);
2956           (void) GetImageMean(image,&mean,&standard_deviation,exception);
2957           (void) FormatLocaleString(value,MagickPathExtent,"%.*g",
2958             GetMagickPrecision(),mean);
2959           break;
2960         }
2961       if ((LocaleCompare("minima",property) == 0) ||
2962           (LocaleCompare("min",property) == 0))
2963         {
2964           double
2965             maximum,
2966             minimum;
2967
2968           WarnNoImageReturn("\"%%[%s]\"",property);
2969           (void) GetImageRange(image,&minimum,&maximum,exception);
2970           (void) FormatLocaleString(value,MagickPathExtent,"%.*g",
2971             GetMagickPrecision(),minimum);
2972           break;
2973         }
2974       break;
2975     }
2976     case 'o':
2977     {
2978       if (LocaleCompare("opaque",property) == 0)
2979         {
2980           WarnNoImageReturn("\"%%[%s]\"",property);
2981           string=CommandOptionToMnemonic(MagickBooleanOptions,(ssize_t)
2982             IsImageOpaque(image,exception));
2983           break;
2984         }
2985       if (LocaleCompare("orientation",property) == 0)
2986         {
2987           WarnNoImageReturn("\"%%[%s]\"",property);
2988           string=CommandOptionToMnemonic(MagickOrientationOptions,(ssize_t)
2989             image->orientation);
2990           break;
2991         }
2992       if (LocaleCompare("output",property) == 0)
2993         {
2994           WarnNoImageInfoReturn("\"%%[%s]\"",property);
2995           (void) CopyMagickString(value,image_info->filename,MagickPathExtent);
2996           break;
2997         }
2998      break;
2999     }
3000     case 'p':
3001     {
3002 #if defined(MAGICKCORE_LCMS_DELEGATE)
3003       if (LocaleCompare("profile:icc",property) == 0 ||
3004           LocaleCompare("profile:icm",property) == 0)
3005         {
3006 #if !defined(LCMS_VERSION) || (LCMS_VERSION < 2000)
3007 #define cmsUInt32Number  DWORD
3008 #endif
3009
3010           const StringInfo
3011             *profile;
3012
3013           cmsHPROFILE
3014             icc_profile;
3015
3016           profile=GetImageProfile(image,property+8);
3017           if (profile == (StringInfo *) NULL)
3018             break;
3019           icc_profile=cmsOpenProfileFromMem(GetStringInfoDatum(profile),
3020             (cmsUInt32Number) GetStringInfoLength(profile));
3021           if (icc_profile != (cmsHPROFILE *) NULL)
3022             {
3023 #if defined(LCMS_VERSION) && (LCMS_VERSION < 2000)
3024               string=cmsTakeProductName(icc_profile);
3025 #else
3026               (void) cmsGetProfileInfoASCII(icc_profile,cmsInfoDescription,
3027                 "en","US",value,MagickPathExtent);
3028 #endif
3029               (void) cmsCloseProfile(icc_profile);
3030             }
3031       }
3032 #endif
3033       if (LocaleCompare("profiles",property) == 0)
3034         {
3035           const char
3036             *name;
3037
3038           ResetImageProfileIterator(image);
3039           name=GetNextImageProfile(image);
3040           if (name != (char *) NULL)
3041             {
3042               (void) CopyMagickString(value,name,MagickPathExtent);
3043               name=GetNextImageProfile(image);
3044               while (name != (char *) NULL)
3045               {
3046                 ConcatenateMagickString(value,",",MagickPathExtent);
3047                 ConcatenateMagickString(value,name,MagickPathExtent);
3048                 name=GetNextImageProfile(image);
3049               }
3050             }
3051           break;
3052         }
3053       break;
3054     }
3055     case 'q':
3056     {
3057       if (LocaleCompare("quality",property) == 0)
3058         {
3059           WarnNoImageReturn("\"%%[%s]\"",property);
3060           (void) FormatLocaleString(value,MagickPathExtent,"%.*g",
3061             GetMagickPrecision(),(double) image->quality);
3062           break;
3063         }
3064       break;
3065     }
3066     case 'r':
3067     {
3068       if (LocaleCompare("rendering-intent",property) == 0)
3069         {
3070           WarnNoImageReturn("\"%%[%s]\"",property);
3071           string=CommandOptionToMnemonic(MagickIntentOptions,(ssize_t)
3072             image->rendering_intent);
3073           break;
3074         }
3075       if (LocaleCompare("resolution.x",property) == 0)
3076         {
3077           WarnNoImageReturn("\"%%[%s]\"",property);
3078           (void) FormatLocaleString(value,MagickPathExtent,"%g",
3079             image->resolution.x);
3080           break;
3081         }
3082       if (LocaleCompare("resolution.y",property) == 0)
3083         {
3084           WarnNoImageReturn("\"%%[%s]\"",property);
3085           (void) FormatLocaleString(value,MagickPathExtent,"%g",
3086             image->resolution.y);
3087           break;
3088         }
3089       break;
3090     }
3091     case 's':
3092     {
3093       if (LocaleCompare("scene",property) == 0)
3094         {
3095           WarnNoImageInfoReturn("\"%%[%s]\"",property);
3096           if (image_info->number_scenes != 0)
3097             (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
3098               image_info->scene);
3099           else {
3100             WarnNoImageReturn("\"%%[%s]\"",property);
3101             (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
3102               image->scene);
3103           }
3104           break;
3105         }
3106       if (LocaleCompare("scenes",property) == 0)
3107         {
3108           /* FUTURE: equivelent to %n? */
3109           WarnNoImageReturn("\"%%[%s]\"",property);
3110           (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
3111             GetImageListLength(image));
3112           break;
3113         }
3114       if (LocaleCompare("size",property) == 0)
3115         {
3116           WarnNoImageReturn("\"%%[%s]\"",property);
3117           (void) FormatMagickSize(GetBlobSize(image),MagickFalse,"B",
3118             MagickPathExtent,value);
3119           break;
3120         }
3121       if (LocaleCompare("skewness",property) == 0)
3122         {
3123           double
3124             kurtosis,
3125             skewness;
3126
3127           WarnNoImageReturn("\"%%[%s]\"",property);
3128           (void) GetImageKurtosis(image,&kurtosis,&skewness,exception);
3129           (void) FormatLocaleString(value,MagickPathExtent,"%.*g",
3130             GetMagickPrecision(),skewness);
3131           break;
3132         }
3133       if (LocaleCompare("standard-deviation",property) == 0)
3134         {
3135           double
3136             mean,
3137             standard_deviation;
3138
3139           WarnNoImageReturn("\"%%[%s]\"",property);
3140           (void) GetImageMean(image,&mean,&standard_deviation,exception);
3141           (void) FormatLocaleString(value,MagickPathExtent,"%.*g",
3142             GetMagickPrecision(),standard_deviation);
3143           break;
3144         }
3145        break;
3146     }
3147     case 't':
3148     {
3149       if (LocaleCompare("type",property) == 0)
3150         {
3151           WarnNoImageReturn("\"%%[%s]\"",property);
3152           string=CommandOptionToMnemonic(MagickTypeOptions,(ssize_t)
3153             IdentifyImageType(image,exception));
3154           break;
3155         }
3156        break;
3157     }
3158     case 'u':
3159     {
3160       if (LocaleCompare("unique",property) == 0)
3161         {
3162           WarnNoImageInfoReturn("\"%%[%s]\"",property);
3163           string=image_info->unique;
3164           break;
3165         }
3166       if (LocaleCompare("units",property) == 0)
3167         {
3168           WarnNoImageReturn("\"%%[%s]\"",property);
3169           string=CommandOptionToMnemonic(MagickResolutionOptions,(ssize_t)
3170             image->units);
3171           break;
3172         }
3173       break;
3174     }
3175     case 'v':
3176     {
3177       if (LocaleCompare("version",property) == 0)
3178         {
3179           string=GetMagickVersion((size_t *) NULL);
3180           break;
3181         }
3182       break;
3183     }
3184     case 'w':
3185     {
3186       if (LocaleCompare("width",property) == 0)
3187         {
3188           WarnNoImageReturn("\"%%[%s]\"",property);
3189           (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
3190             (image->magick_columns != 0 ? image->magick_columns : 256));
3191           break;
3192         }
3193       break;
3194     }
3195   }
3196   if (string != (char *) NULL)
3197     return(string);
3198   if (*value != '\0')
3199     {
3200       /*
3201         Create a cloned copy of result, that will get cleaned up, eventually.
3202       */
3203       if (image != (Image *) NULL)
3204         {
3205           (void) SetImageArtifact(image,"get-property",value);
3206           return(GetImageArtifact(image,"get-property"));
3207         }
3208       else
3209         {
3210           (void) SetImageOption(image_info,"get-property",value);
3211           return(GetImageOption(image_info,"get-property"));
3212         }
3213     }
3214   return((char *) NULL);
3215 }
3216 #undef WarnNoImageReturn
3217 \f
3218 /*
3219 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3220 %                                                                             %
3221 %                                                                             %
3222 %                                                                             %
3223 %   G e t N e x t I m a g e P r o p e r t y                                   %
3224 %                                                                             %
3225 %                                                                             %
3226 %                                                                             %
3227 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3228 %
3229 %  GetNextImageProperty() gets the next free-form string property name.
3230 %
3231 %  The format of the GetNextImageProperty method is:
3232 %
3233 %      char *GetNextImageProperty(const Image *image)
3234 %
3235 %  A description of each parameter follows:
3236 %
3237 %    o image: the image.
3238 %
3239 */
3240 MagickExport const char *GetNextImageProperty(const Image *image)
3241 {
3242   assert(image != (Image *) NULL);
3243   assert(image->signature == MagickCoreSignature);
3244   if (image->debug != MagickFalse)
3245     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
3246       image->filename);
3247   if (image->properties == (void *) NULL)
3248     return((const char *) NULL);
3249   return((const char *) GetNextKeyInSplayTree((SplayTreeInfo *) image->properties));
3250 }
3251 \f
3252 /*
3253 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3254 %                                                                             %
3255 %                                                                             %
3256 %                                                                             %
3257 %   I n t e r p r e t I m a g e P r o p e r t i e s                           %
3258 %                                                                             %
3259 %                                                                             %
3260 %                                                                             %
3261 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3262 %
3263 %  InterpretImageProperties() replaces any embedded formatting characters with
3264 %  the appropriate image property and returns the interpreted text.
3265 %
3266 %  This searches for and replaces
3267 %     \n \r \%          replaced by newline, return, and percent resp.
3268 %     &lt; &gt; &amp;   replaced by '<', '>', '&' resp.
3269 %     %%                replaced by percent
3270 %
3271 %     %x %[x]       where 'x' is a single letter properity, case sensitive).
3272 %     %[type:name]  where 'type' a is special and known prefix.
3273 %     %[name]       where 'name' is a specifically known attribute, calculated
3274 %                   value, or a per-image property string name, or a per-image
3275 %                   'artifact' (as generated from a global option).
3276 %                   It may contain ':' as long as the prefix is not special.
3277 %
3278 %  Single letter % substitutions will only happen if the character before the
3279 %  percent is NOT a number. But braced substitutions will always be performed.
3280 %  This prevents the typical usage of percent in a interpreted geometry
3281 %  argument from being substituted when the percent is a geometry flag.
3282 %
3283 %  If 'glob-expresions' ('*' or '?' characters) is used for 'name' it may be
3284 %  used as a search pattern to print multiple lines of "name=value\n" pairs of
3285 %  the associacted set of properties.
3286 %
3287 %  The returned string must be freed using DestoryString() by the caller.
3288 %
3289 %  The format of the InterpretImageProperties method is:
3290 %
3291 %      char *InterpretImageProperties(ImageInfo *image_info,
3292 %        Image *image,const char *embed_text,ExceptionInfo *exception)
3293 %
3294 %  A description of each parameter follows:
3295 %
3296 %    o image_info: the image info. (required)
3297 %
3298 %    o image: the image. (optional)
3299 %
3300 %    o embed_text: the address of a character string containing the embedded
3301 %      formatting characters.
3302 %
3303 %    o exception: return any errors or warnings in this structure.
3304 %
3305 */
3306 MagickExport char *InterpretImageProperties(ImageInfo *image_info,Image *image,
3307   const char *embed_text,ExceptionInfo *exception)
3308 {
3309 #define ExtendInterpretText(string_length) \
3310 DisableMSCWarning(4127) \
3311 { \
3312   size_t length=(string_length); \
3313   if ((size_t) (q-interpret_text+length+1) >= extent) \
3314     { \
3315       extent+=length; \
3316       interpret_text=(char *) ResizeQuantumMemory(interpret_text,extent+ \
3317         MaxTextExtent,sizeof(*interpret_text)); \
3318       if (interpret_text == (char *) NULL) \
3319         return((char *) NULL); \
3320       q=interpret_text+strlen(interpret_text); \
3321    } \
3322 } \
3323 RestoreMSCWarning
3324
3325 #define AppendKeyValue2Text(key,value)\
3326 DisableMSCWarning(4127) \
3327 { \
3328   size_t length=strlen(key)+strlen(value)+2; \
3329   if ((size_t) (q-interpret_text+length+1) >= extent) \
3330     { \
3331       extent+=length; \
3332       interpret_text=(char *) ResizeQuantumMemory(interpret_text,extent+ \
3333         MaxTextExtent,sizeof(*interpret_text)); \
3334       if (interpret_text == (char *) NULL) \
3335         return((char *) NULL); \
3336       q=interpret_text+strlen(interpret_text); \
3337      } \
3338    q+=FormatLocaleString(q,extent,"%s=%s\n",(key),(value)); \
3339 } \
3340 RestoreMSCWarning
3341
3342 #define AppendString2Text(string) \
3343 DisableMSCWarning(4127) \
3344 { \
3345   size_t length=strlen((string)); \
3346   if ((size_t) (q-interpret_text+length+1) >= extent) \
3347     { \
3348       extent+=length; \
3349       interpret_text=(char *) ResizeQuantumMemory(interpret_text,extent+ \
3350         MaxTextExtent,sizeof(*interpret_text)); \
3351       if (interpret_text == (char *) NULL) \
3352         return((char *) NULL); \
3353       q=interpret_text+strlen(interpret_text); \
3354     } \
3355   (void) CopyMagickString(q,(string),extent); \
3356   q+=length; \
3357 } \
3358 RestoreMSCWarning
3359
3360   char
3361     *interpret_text;
3362
3363   MagickBooleanType
3364     number;
3365
3366   register char
3367     *q;  /* current position in interpret_text */
3368
3369   register const char
3370     *p;  /* position in embed_text string being expanded */
3371
3372   size_t
3373     extent;  /* allocated length of interpret_text */
3374
3375   assert(image == NULL || image->signature == MagickCoreSignature);
3376   assert(image_info == NULL || image_info->signature == MagickCoreSignature);
3377   if ((image != (Image *) NULL) && (image->debug != MagickFalse))
3378     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3379   else
3380    if ((image_info != (ImageInfo *) NULL) && (image_info->debug != MagickFalse))
3381      (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s","no-image");
3382   if (embed_text == (const char *) NULL)
3383     return(ConstantString(""));
3384   p=embed_text;
3385   while ((isspace((int) ((unsigned char) *p)) != 0) && (*p != '\0'))
3386     p++;
3387   if (*p == '\0')
3388     return(ConstantString(""));
3389   if ((*p == '@') && (IsPathAccessible(p+1) != MagickFalse))
3390     {
3391       /*
3392         Handle a '@' replace string from file.
3393       */
3394       if (IsRightsAuthorized(PathPolicyDomain,ReadPolicyRights,p) == MagickFalse)
3395         {
3396           errno=EPERM;
3397           (void) ThrowMagickException(exception,GetMagickModule(),PolicyError,
3398             "NotAuthorized","`%s'",p);
3399           return(ConstantString(""));
3400         }
3401       interpret_text=FileToString(p+1,~0UL,exception);
3402       if (interpret_text != (char *) NULL)
3403         return(interpret_text);
3404     }
3405   /*
3406     Translate any embedded format characters.
3407   */
3408   interpret_text=AcquireString(embed_text); /* new string with extra space */
3409   extent=MagickPathExtent;                     /* allocated space in string */
3410   number=MagickFalse;                       /* is last char a number? */
3411   for (q=interpret_text; *p!='\0'; number=isdigit(*p) ? MagickTrue : MagickFalse,p++)
3412   {
3413     /*
3414       Look for the various escapes, (and handle other specials)
3415     */
3416     *q='\0';
3417     ExtendInterpretText(MagickPathExtent);
3418     switch (*p)
3419     {
3420       case '\\':
3421       {
3422         switch (*(p+1))
3423         {
3424           case '\0':
3425             continue;
3426           case 'r':  /* convert to RETURN */
3427           {
3428             *q++='\r';
3429             p++;
3430             continue;
3431           }
3432           case 'n':  /* convert to NEWLINE */
3433           {
3434             *q++='\n';
3435             p++;
3436             continue;
3437           }
3438           case '\n':  /* EOL removal UNIX,MacOSX */
3439           {
3440             p++;
3441             continue;
3442           }
3443           case '\r':  /* EOL removal DOS,Windows */
3444           {
3445             p++;
3446             if (*p == '\n') /* return-newline EOL */
3447               p++;
3448             continue;
3449           }
3450           default:
3451           {
3452             p++;
3453             *q++=(*p);
3454           }
3455         }
3456         continue;
3457       }
3458       case '&':
3459       {
3460         if (LocaleNCompare("&lt;",p,4) == 0)
3461           {
3462             *q++='<';
3463             p+=3;
3464           }
3465         else
3466           if (LocaleNCompare("&gt;",p,4) == 0)
3467             {
3468               *q++='>';
3469               p+=3;
3470             }
3471           else
3472             if (LocaleNCompare("&amp;",p,5) == 0)
3473               {
3474                 *q++='&';
3475                 p+=4;
3476               }
3477             else
3478               *q++=(*p);
3479         continue;
3480       }
3481       case '%':
3482         break;  /* continue to next set of handlers */
3483       default:
3484       {
3485         *q++=(*p);  /* any thing else is 'as normal' */
3486         continue;
3487       }
3488     }
3489     p++; /* advance beyond the percent */
3490     /*
3491       Doubled Percent - or percent at end of string.
3492     */
3493     if ((*p == '\0') || (*p == '\'') || (*p == '"'))
3494       p--;
3495     if (*p == '%')
3496       {
3497         *q++='%';
3498         continue;
3499       }
3500     /*
3501       Single letter escapes %c.
3502     */
3503     if (*p != '[')
3504       {
3505         const char
3506           *string;
3507
3508         if (number != MagickFalse)
3509           {
3510             /*
3511               But only if not preceeded by a number!
3512             */
3513             *q++='%'; /* do NOT substitute the percent */
3514             p--;      /* back up one */
3515             continue;
3516           }
3517         string=GetMagickPropertyLetter(image_info,image,*p, exception);
3518         if (string != (char *) NULL)
3519           {
3520             AppendString2Text(string);
3521             if (image != (Image *) NULL)
3522               (void) DeleteImageArtifact(image,"get-property");
3523             if (image_info != (ImageInfo *) NULL)
3524               (void) DeleteImageOption(image_info,"get-property");
3525             continue;
3526           }
3527         (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
3528           "UnknownImageProperty","\"%%%c\"",*p);
3529         continue;
3530       }
3531     {
3532       char
3533         pattern[2*MagickPathExtent];
3534
3535       const char
3536         *key,
3537         *string;
3538
3539       register ssize_t
3540         len;
3541
3542       ssize_t
3543         depth;
3544
3545       /*
3546         Braced Percent Escape %[...].
3547       */
3548       p++;  /* advance p to just inside the opening brace */
3549       depth=1;
3550       if (*p == ']')
3551         {
3552           (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
3553             "UnknownImageProperty","\"%%[]\"");
3554           break;
3555         }
3556       for (len=0; len<(MagickPathExtent-1L) && (*p != '\0');)
3557       {
3558         if ((*p == '\\') && (*(p+1) != '\0'))
3559           {
3560             /*
3561               Skip escaped braces within braced pattern.
3562             */
3563             pattern[len++]=(*p++);
3564             pattern[len++]=(*p++);
3565             continue;
3566           }
3567         if (*p == '[')
3568           depth++;
3569         if (*p == ']')
3570           depth--;
3571         if (depth <= 0)
3572           break;
3573         pattern[len++]=(*p++);
3574       }
3575       pattern[len]='\0';
3576       if (depth != 0)
3577         {
3578           /*
3579             Check for unmatched final ']' for "%[...]".
3580           */
3581           if (len >= 64)
3582             {
3583               pattern[61] = '.';  /* truncate string for error message */
3584               pattern[62] = '.';
3585               pattern[63] = '.';
3586               pattern[64] = '\0';
3587             }
3588           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
3589             "UnbalancedBraces","\"%%[%s\"",pattern);
3590           interpret_text=DestroyString(interpret_text);
3591           return((char *) NULL);
3592         }
3593       /*
3594         Special Lookup Prefixes %[prefix:...].
3595       */
3596       if (LocaleNCompare("fx:",pattern,3) == 0)
3597         {
3598           double
3599             value;
3600
3601           FxInfo
3602             *fx_info;
3603
3604           MagickBooleanType
3605             status;
3606
3607           /*
3608             FX - value calculator.
3609           */
3610           if (image == (Image *) NULL )
3611             {
3612               (void) ThrowMagickException(exception,GetMagickModule(),
3613                 OptionWarning,"NoImageForProperty","\"%%[%s]\"",pattern);
3614               continue; /* else no image to retrieve artifact */
3615             }
3616           fx_info=AcquireFxInfo(image,pattern+3,exception);
3617           status=FxEvaluateChannelExpression(fx_info,IntensityPixelChannel,0,0,
3618             &value,exception);
3619           fx_info=DestroyFxInfo(fx_info);
3620           if (status != MagickFalse)
3621             {
3622               char
3623                 result[MagickPathExtent];
3624
3625               (void) FormatLocaleString(result,MagickPathExtent,"%.*g",
3626                 GetMagickPrecision(),(double) value);
3627               AppendString2Text(result);
3628             }
3629           continue;
3630         }
3631       if (LocaleNCompare("pixel:",pattern,6) == 0)
3632         {
3633           FxInfo
3634             *fx_info;
3635
3636           double
3637             value;
3638
3639           MagickStatusType
3640             status;
3641
3642           PixelInfo
3643             pixel;
3644
3645           /*
3646             Pixel - color value calculator.
3647           */
3648           if (image == (Image *) NULL)
3649             {
3650               (void) ThrowMagickException(exception,GetMagickModule(),
3651                 OptionWarning,"NoImageForProperty","\"%%[%s]\"",pattern);
3652               continue; /* else no image to retrieve artifact */
3653             }
3654           GetPixelInfo(image,&pixel);
3655           fx_info=AcquireFxInfo(image,pattern+6,exception);
3656           status=FxEvaluateChannelExpression(fx_info,RedPixelChannel,0,0,
3657             &value,exception);
3658           pixel.red=(double) QuantumRange*value;
3659           status&=FxEvaluateChannelExpression(fx_info,GreenPixelChannel,0,0,
3660             &value,exception);
3661           pixel.green=(double) QuantumRange*value;
3662           status&=FxEvaluateChannelExpression(fx_info,BluePixelChannel,0,0,
3663             &value,exception);
3664           pixel.blue=(double) QuantumRange*value;
3665           if (image->colorspace == CMYKColorspace)
3666             {
3667               status&=FxEvaluateChannelExpression(fx_info,BlackPixelChannel,0,0,
3668                 &value,exception);
3669               pixel.black=(double) QuantumRange*value;
3670             }
3671           status&=FxEvaluateChannelExpression(fx_info,AlphaPixelChannel,0,0,
3672             &value,exception);
3673           pixel.alpha=(double) QuantumRange*value;
3674           fx_info=DestroyFxInfo(fx_info);
3675           if (status != MagickFalse)
3676             {
3677               char
3678                 name[MagickPathExtent];
3679
3680               (void) QueryColorname(image,&pixel,SVGCompliance,name,
3681                 exception);
3682               AppendString2Text(name);
3683             }
3684           continue;
3685         }
3686       if (LocaleNCompare("option:",pattern,7) == 0)
3687         {
3688           /*
3689             Option - direct global option lookup (with globbing).
3690           */
3691           if (image_info == (ImageInfo *) NULL )
3692             {
3693               (void) ThrowMagickException(exception,GetMagickModule(),
3694                 OptionWarning,"NoImageForProperty","\"%%[%s]\"",pattern);
3695               continue; /* else no image to retrieve artifact */
3696             }
3697           if (IsGlob(pattern+7) != MagickFalse)
3698             {
3699               ResetImageOptionIterator(image_info);
3700               while ((key=GetNextImageOption(image_info)) != (const char *) NULL)
3701                 if (GlobExpression(key,pattern+7,MagickTrue) != MagickFalse)
3702                   {
3703                     string=GetImageOption(image_info,key);
3704                     if (string != (const char *) NULL)
3705                       AppendKeyValue2Text(key,string);
3706                     /* else - assertion failure? key found but no string value! */
3707                   }
3708               continue;
3709             }
3710           string=GetImageOption(image_info,pattern+7);
3711           if (string == (char *) NULL)
3712             goto PropertyLookupFailure; /* no artifact of this specifc name */
3713           AppendString2Text(string);
3714           continue;
3715         }
3716       if (LocaleNCompare("artifact:",pattern,9) == 0)
3717         {
3718           /*
3719             Artifact - direct image artifact lookup (with glob).
3720           */
3721           if (image == (Image *) NULL)
3722             {
3723               (void) ThrowMagickException(exception,GetMagickModule(),
3724                 OptionWarning,"NoImageForProperty","\"%%[%s]\"",pattern);
3725               continue; /* else no image to retrieve artifact */
3726             }
3727           if (IsGlob(pattern+9) != MagickFalse)
3728             {
3729               ResetImageArtifactIterator(image);
3730               while ((key=GetNextImageArtifact(image)) != (const char *) NULL)
3731               if (GlobExpression(key,pattern+9,MagickTrue) != MagickFalse)
3732                 {
3733                   string=GetImageArtifact(image,key);
3734                   if (string != (const char *) NULL)
3735                     AppendKeyValue2Text(key,string);
3736                   /* else - assertion failure? key found but no string value! */
3737                 }
3738               continue;
3739             }
3740           string=GetImageArtifact(image,pattern+9);
3741           if (string == (char *) NULL)
3742             goto PropertyLookupFailure; /* no artifact of this specifc name */
3743           AppendString2Text(string);
3744           continue;
3745         }
3746       if (LocaleNCompare("property:",pattern,9) == 0)
3747         {
3748           /*
3749             Property - direct image property lookup (with glob).
3750           */
3751           if (image == (Image *) NULL)
3752             {
3753               (void) ThrowMagickException(exception,GetMagickModule(),
3754                 OptionWarning,"NoImageForProperty","\"%%[%s]\"",pattern);
3755               continue; /* else no image to retrieve artifact */
3756             }
3757           if (IsGlob(pattern+9) != MagickFalse)
3758             {
3759               ResetImagePropertyIterator(image);
3760               while ((key=GetNextImageProperty(image)) != (const char *) NULL)
3761                 if (GlobExpression(key,pattern,MagickTrue) != MagickFalse)
3762                   {
3763                     string=GetImageProperty(image,key,exception);
3764                     if (string != (const char *) NULL)
3765                       AppendKeyValue2Text(key,string);
3766                     /* else - assertion failure? */
3767                   }
3768               continue;
3769             }
3770           string=GetImageProperty(image,pattern+9,exception);
3771           if (string == (char *) NULL)
3772             goto PropertyLookupFailure; /* no artifact of this specifc name */
3773           AppendString2Text(string);
3774           continue;
3775         }
3776       if (image != (Image *) NULL)
3777         {
3778           /*
3779             Properties without special prefix.  This handles attributes,
3780             properties, and profiles such as %[exif:...].  Note the profile
3781             properties may also include a glob expansion pattern.
3782           */
3783           string=GetImageProperty(image,pattern,exception);
3784           if (string != (const char *) NULL)
3785             {
3786               AppendString2Text(string);
3787               if (image != (Image *) NULL)
3788                 (void)DeleteImageArtifact(image,"get-property");
3789               if (image_info != (ImageInfo *) NULL)
3790                 (void)DeleteImageOption(image_info,"get-property");
3791               continue;
3792             }
3793         }
3794       if (IsGlob(pattern) != MagickFalse)
3795         {
3796           /*
3797             Handle property 'glob' patterns such as:
3798             %[*] %[user:array_??] %[filename:e*]>
3799           */
3800           if (image == (Image *) NULL)
3801             continue; /* else no image to retrieve proprty - no list */
3802           ResetImagePropertyIterator(image);
3803           while ((key=GetNextImageProperty(image)) != (const char *) NULL)
3804             if (GlobExpression(key,pattern,MagickTrue) != MagickFalse)
3805               {
3806                 string=GetImageProperty(image,key,exception);
3807                 if (string != (const char *) NULL)
3808                   AppendKeyValue2Text(key,string);
3809                 /* else - assertion failure? */
3810               }
3811           continue;
3812         }
3813       /*
3814         Look for a known property or image attribute such as
3815         %[basename] %[denisty] %[delay].  Also handles a braced single
3816         letter: %[b] %[G] %[g].
3817       */
3818       string=GetMagickProperty(image_info,image,pattern,exception);
3819       if (string != (const char *) NULL)
3820         {
3821           AppendString2Text(string);
3822           continue;
3823         }
3824       /*
3825         Look for a per-image artifact. This includes option lookup
3826         (FUTURE: interpreted according to image).
3827       */
3828       if (image != (Image *) NULL)
3829         {
3830           string=GetImageArtifact(image,pattern);
3831           if (string != (char *) NULL)
3832             {
3833               AppendString2Text(string);
3834               continue;
3835             }
3836         }
3837       else
3838         if (image_info != (ImageInfo *) NULL)
3839           {
3840             /*
3841               No image, so direct 'option' lookup (no delayed percent escapes).
3842             */
3843             string=GetImageOption(image_info,pattern);
3844             if (string != (char *) NULL)
3845               {
3846                 AppendString2Text(string);
3847                 continue;
3848               }
3849           }
3850 PropertyLookupFailure:
3851       /*
3852         Failed to find any match anywhere!
3853       */
3854       if (len >= 64)
3855         {
3856           pattern[61] = '.';  /* truncate string for error message */
3857           pattern[62] = '.';
3858           pattern[63] = '.';
3859           pattern[64] = '\0';
3860         }
3861       (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
3862         "UnknownImageProperty","\"%%[%s]\"",pattern);
3863     }
3864   }
3865   *q='\0';
3866   return(interpret_text);
3867 }
3868 \f
3869 /*
3870 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3871 %                                                                             %
3872 %                                                                             %
3873 %                                                                             %
3874 %   R e m o v e I m a g e P r o p e r t y                                     %
3875 %                                                                             %
3876 %                                                                             %
3877 %                                                                             %
3878 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3879 %
3880 %  RemoveImageProperty() removes a property from the image and returns its
3881 %  value.
3882 %
3883 %  In this case the ConstantString() value returned should be freed by the
3884 %  caller when finished.
3885 %
3886 %  The format of the RemoveImageProperty method is:
3887 %
3888 %      char *RemoveImageProperty(Image *image,const char *property)
3889 %
3890 %  A description of each parameter follows:
3891 %
3892 %    o image: the image.
3893 %
3894 %    o property: the image property.
3895 %
3896 */
3897 MagickExport char *RemoveImageProperty(Image *image,const char *property)
3898 {
3899   char
3900     *value;
3901
3902   assert(image != (Image *) NULL);
3903   assert(image->signature == MagickCoreSignature);
3904   if (image->debug != MagickFalse)
3905     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3906   if (image->properties == (void *) NULL)
3907     return((char *) NULL);
3908   value=(char *) RemoveNodeFromSplayTree((SplayTreeInfo *) image->properties,
3909     property);
3910   return(value);
3911 }
3912 \f
3913 /*
3914 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3915 %                                                                             %
3916 %                                                                             %
3917 %                                                                             %
3918 %   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                       %
3919 %                                                                             %
3920 %                                                                             %
3921 %                                                                             %
3922 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3923 %
3924 %  ResetImagePropertyIterator() resets the image properties iterator.  Use it
3925 %  in conjunction with GetNextImageProperty() to iterate over all the values
3926 %  associated with an image property.
3927 %
3928 %  The format of the ResetImagePropertyIterator method is:
3929 %
3930 %      ResetImagePropertyIterator(Image *image)
3931 %
3932 %  A description of each parameter follows:
3933 %
3934 %    o image: the image.
3935 %
3936 */
3937 MagickExport void ResetImagePropertyIterator(const Image *image)
3938 {
3939   assert(image != (Image *) NULL);
3940   assert(image->signature == MagickCoreSignature);
3941   if (image->debug != MagickFalse)
3942     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3943   if (image->properties == (void *) NULL)
3944     return;
3945   ResetSplayTreeIterator((SplayTreeInfo *) image->properties);
3946 }
3947 \f
3948 /*
3949 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3950 %                                                                             %
3951 %                                                                             %
3952 %                                                                             %
3953 %   S e t I m a g e P r o p e r t y                                           %
3954 %                                                                             %
3955 %                                                                             %
3956 %                                                                             %
3957 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3958 %
3959 %  SetImageProperty() saves the given string value either to specific known
3960 %  attribute or to a freeform property string.
3961 %
3962 %  Attempting to set a property that is normally calculated will produce
3963 %  an exception.
3964 %
3965 %  The format of the SetImageProperty method is:
3966 %
3967 %      MagickBooleanType SetImageProperty(Image *image,const char *property,
3968 %        const char *value,ExceptionInfo *exception)
3969 %
3970 %  A description of each parameter follows:
3971 %
3972 %    o image: the image.
3973 %
3974 %    o property: the image property.
3975 %
3976 %    o values: the image property values.
3977 %
3978 %    o exception: return any errors or warnings in this structure.
3979 %
3980 */
3981 MagickExport MagickBooleanType SetImageProperty(Image *image,
3982   const char *property,const char *value,ExceptionInfo *exception)
3983 {
3984   MagickBooleanType
3985     status;
3986
3987   MagickStatusType
3988     flags;
3989
3990   assert(image != (Image *) NULL);
3991   assert(image->signature == MagickCoreSignature);
3992   if (image->debug != MagickFalse)
3993     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3994   if (image->properties == (void *) NULL)
3995     image->properties=NewSplayTree(CompareSplayTreeString,
3996       RelinquishMagickMemory,RelinquishMagickMemory);  /* create splay-tree */
3997   if (value == (const char *) NULL)
3998     return(DeleteImageProperty(image,property));  /* delete if NULL */
3999   status=MagickTrue;
4000   if (strlen(property) <= 1)
4001     {
4002       /*
4003         Do not 'set' single letter properties - read only shorthand.
4004       */
4005       (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4006         "SetReadOnlyProperty","`%s'",property);
4007       return(MagickFalse);
4008     }
4009
4010   /* FUTURE: binary chars or quotes in key should produce a error */
4011   /* Set attributes with known names or special prefixes
4012      return result is found, or break to set a free form properity
4013   */
4014   switch (*property)
4015   {
4016 #if 0  /* Percent escape's sets values with this prefix: for later use
4017           Throwing an exception causes this setting to fail */
4018     case '8':
4019     {
4020       if (LocaleNCompare("8bim:",property,5) == 0)
4021         {
4022           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4023             "SetReadOnlyProperty","`%s'",property);
4024           return(MagickFalse);
4025         }
4026       break;
4027     }
4028 #endif
4029     case 'B':
4030     case 'b':
4031     {
4032       if (LocaleCompare("background",property) == 0)
4033         {
4034           (void) QueryColorCompliance(value,AllCompliance,
4035                &image->background_color,exception);
4036           /* check for FUTURE: value exception?? */
4037           /* also add user input to splay tree */
4038         }
4039       break; /* not an attribute, add as a property */
4040     }
4041     case 'C':
4042     case 'c':
4043     {
4044       if (LocaleCompare("channels",property) == 0)
4045         {
4046           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4047             "SetReadOnlyProperty","`%s'",property);
4048           return(MagickFalse);
4049         }
4050       if (LocaleCompare("colors",property) == 0)
4051         {
4052           image->colors=GetNumberColors(image,(FILE *) NULL,exception);
4053           return(MagickTrue);
4054         }
4055       if (LocaleCompare("colorspace",property) == 0)
4056         {
4057           ssize_t
4058             colorspace;
4059
4060           colorspace=ParseCommandOption(MagickColorspaceOptions,MagickFalse,
4061             value);
4062           if (colorspace < 0)
4063             return(MagickFalse); /* FUTURE: value exception?? */
4064           return(SetImageColorspace(image,(ColorspaceType) colorspace,exception));
4065         }
4066       if (LocaleCompare("compose",property) == 0)
4067         {
4068           ssize_t
4069             compose;
4070
4071           compose=ParseCommandOption(MagickComposeOptions,MagickFalse,value);
4072           if (compose < 0)
4073             return(MagickFalse); /* FUTURE: value exception?? */
4074           image->compose=(CompositeOperator) compose;
4075           return(MagickTrue);
4076         }
4077       if (LocaleCompare("compress",property) == 0)
4078         {
4079           ssize_t
4080             compression;
4081
4082           compression=ParseCommandOption(MagickCompressOptions,MagickFalse,
4083             value);
4084           if (compression < 0)
4085             return(MagickFalse); /* FUTURE: value exception?? */
4086           image->compression=(CompressionType) compression;
4087           return(MagickTrue);
4088         }
4089       break; /* not an attribute, add as a property */
4090     }
4091     case 'D':
4092     case 'd':
4093     {
4094       if (LocaleCompare("delay",property) == 0)
4095         {
4096           GeometryInfo
4097             geometry_info;
4098
4099           flags=ParseGeometry(value,&geometry_info);
4100           if ((flags & GreaterValue) != 0)
4101             {
4102               if (image->delay > (size_t) floor(geometry_info.rho+0.5))
4103                 image->delay=(size_t) floor(geometry_info.rho+0.5);
4104             }
4105           else
4106             if ((flags & LessValue) != 0)
4107               {
4108                 if (image->delay < (size_t) floor(geometry_info.rho+0.5))
4109                   image->delay=(ssize_t)
4110                     floor(geometry_info.sigma+0.5);
4111               }
4112             else
4113               image->delay=(size_t) floor(geometry_info.rho+0.5);
4114           if ((flags & SigmaValue) != 0)
4115             image->ticks_per_second=(ssize_t) floor(geometry_info.sigma+0.5);
4116           return(MagickTrue);
4117         }
4118       if (LocaleCompare("delay_units",property) == 0)
4119         {
4120           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4121             "SetReadOnlyProperty","`%s'",property);
4122           return(MagickFalse);
4123         }
4124       if (LocaleCompare("density",property) == 0)
4125         {
4126           GeometryInfo
4127             geometry_info;
4128
4129           flags=ParseGeometry(value,&geometry_info);
4130           image->resolution.x=geometry_info.rho;
4131           image->resolution.y=geometry_info.sigma;
4132           if ((flags & SigmaValue) == 0)
4133             image->resolution.y=image->resolution.x;
4134           return(MagickTrue);
4135         }
4136       if (LocaleCompare("depth",property) == 0)
4137         {
4138           image->depth=StringToUnsignedLong(value);
4139           return(MagickTrue);
4140         }
4141       if (LocaleCompare("dispose",property) == 0)
4142         {
4143           ssize_t
4144             dispose;
4145
4146           dispose=ParseCommandOption(MagickDisposeOptions,MagickFalse,value);
4147           if (dispose < 0)
4148             return(MagickFalse); /* FUTURE: value exception?? */
4149           image->dispose=(DisposeType) dispose;
4150           return(MagickTrue);
4151         }
4152       break; /* not an attribute, add as a property */
4153     }
4154 #if 0  /* Percent escape's sets values with this prefix: for later use
4155           Throwing an exception causes this setting to fail */
4156     case 'E':
4157     case 'e':
4158     {
4159       if (LocaleNCompare("exif:",property,5) == 0)
4160         {
4161           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4162             "SetReadOnlyProperty","`%s'",property);
4163           return(MagickFalse);
4164         }
4165       break; /* not an attribute, add as a property */
4166     }
4167     case 'F':
4168     case 'f':
4169     {
4170       if (LocaleNCompare("fx:",property,3) == 0)
4171         {
4172           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4173             "SetReadOnlyProperty","`%s'",property);
4174           return(MagickFalse);
4175         }
4176       break; /* not an attribute, add as a property */
4177     }
4178 #endif
4179     case 'G':
4180     case 'g':
4181     {
4182       if (LocaleCompare("gamma",property) == 0)
4183         {
4184           image->gamma=StringToDouble(value,(char **) NULL);
4185           return(MagickTrue);
4186         }
4187       if (LocaleCompare("gravity",property) == 0)
4188         {
4189           ssize_t
4190             gravity;
4191
4192           gravity=ParseCommandOption(MagickGravityOptions,MagickFalse,value);
4193           if (gravity < 0)
4194             return(MagickFalse); /* FUTURE: value exception?? */
4195           image->gravity=(GravityType) gravity;
4196           return(MagickTrue);
4197         }
4198       break; /* not an attribute, add as a property */
4199     }
4200     case 'H':
4201     case 'h':
4202     {
4203       if (LocaleCompare("height",property) == 0)
4204         {
4205           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4206             "SetReadOnlyProperty","`%s'",property);
4207           return(MagickFalse);
4208         }
4209       break; /* not an attribute, add as a property */
4210     }
4211     case 'I':
4212     case 'i':
4213     {
4214       if (LocaleCompare("intensity",property) == 0)
4215         {
4216           ssize_t
4217             intensity;
4218
4219           intensity=ParseCommandOption(MagickIntentOptions,MagickFalse,value);
4220           if (intensity < 0)
4221             return(MagickFalse);
4222           image->intensity=(PixelIntensityMethod) intensity;
4223           return(MagickTrue);
4224         }
4225       if (LocaleCompare("intent",property) == 0)
4226         {
4227           ssize_t
4228             rendering_intent;
4229
4230           rendering_intent=ParseCommandOption(MagickIntentOptions,MagickFalse,
4231             value);
4232           if (rendering_intent < 0)
4233             return(MagickFalse); /* FUTURE: value exception?? */
4234           image->rendering_intent=(RenderingIntent) rendering_intent;
4235           return(MagickTrue);
4236         }
4237       if (LocaleCompare("interpolate",property) == 0)
4238         {
4239           ssize_t
4240             interpolate;
4241
4242           interpolate=ParseCommandOption(MagickInterpolateOptions,MagickFalse,
4243             value);
4244           if (interpolate < 0)
4245             return(MagickFalse); /* FUTURE: value exception?? */
4246           image->interpolate=(PixelInterpolateMethod) interpolate;
4247           return(MagickTrue);
4248         }
4249 #if 0  /* Percent escape's sets values with this prefix: for later use
4250           Throwing an exception causes this setting to fail */
4251       if (LocaleNCompare("iptc:",property,5) == 0)
4252         {
4253           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4254             "SetReadOnlyProperty","`%s'",property);
4255           return(MagickFalse);
4256         }
4257 #endif
4258       break; /* not an attribute, add as a property */
4259     }
4260     case 'K':
4261     case 'k':
4262       if (LocaleCompare("kurtosis",property) == 0)
4263         {
4264           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4265             "SetReadOnlyProperty","`%s'",property);
4266           return(MagickFalse);
4267         }
4268       break; /* not an attribute, add as a property */
4269     case 'L':
4270     case 'l':
4271     {
4272       if (LocaleCompare("loop",property) == 0)
4273         {
4274           image->iterations=StringToUnsignedLong(value);
4275           return(MagickTrue);
4276         }
4277       break; /* not an attribute, add as a property */
4278     }
4279     case 'M':
4280     case 'm':
4281       if ((LocaleCompare("magick",property) == 0) ||
4282           (LocaleCompare("max",property) == 0) ||
4283           (LocaleCompare("mean",property) == 0) ||
4284           (LocaleCompare("min",property) == 0) ||
4285           (LocaleCompare("min",property) == 0))
4286         {
4287           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4288              "SetReadOnlyProperty","`%s'",property);
4289           return(MagickFalse);
4290         }
4291       break; /* not an attribute, add as a property */
4292     case 'O':
4293     case 'o':
4294       if (LocaleCompare("opaque",property) == 0)
4295         {
4296           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4297             "SetReadOnlyProperty","`%s'",property);
4298           return(MagickFalse);
4299         }
4300       break; /* not an attribute, add as a property */
4301     case 'P':
4302     case 'p':
4303     {
4304       if (LocaleCompare("page",property) == 0)
4305         {
4306           char
4307             *geometry;
4308
4309           geometry=GetPageGeometry(value);
4310           flags=ParseAbsoluteGeometry(geometry,&image->page);
4311           geometry=DestroyString(geometry);
4312           return(MagickTrue);
4313         }
4314 #if 0  /* Percent escape's sets values with this prefix: for later use
4315           Throwing an exception causes this setting to fail */
4316       if (LocaleNCompare("pixel:",property,6) == 0)
4317         {
4318           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4319             "SetReadOnlyProperty","`%s'",property);
4320           return(MagickFalse);
4321         }
4322 #endif
4323       if (LocaleCompare("profile",property) == 0)
4324         {
4325           ImageInfo
4326             *image_info;
4327
4328           StringInfo
4329             *profile;
4330
4331           image_info=AcquireImageInfo();
4332           (void) CopyMagickString(image_info->filename,value,MagickPathExtent);
4333           (void) SetImageInfo(image_info,1,exception);
4334           profile=FileToStringInfo(image_info->filename,~0UL,exception);
4335           if (profile != (StringInfo *) NULL)
4336             status=SetImageProfile(image,image_info->magick,profile,exception);
4337           image_info=DestroyImageInfo(image_info);
4338           return(MagickTrue);
4339         }
4340       break; /* not an attribute, add as a property */
4341     }
4342     case 'R':
4343     case 'r':
4344     {
4345       if (LocaleCompare("rendering-intent",property) == 0)
4346         {
4347           ssize_t
4348             rendering_intent;
4349
4350           rendering_intent=ParseCommandOption(MagickIntentOptions,MagickFalse,
4351             value);
4352           if (rendering_intent < 0)
4353             return(MagickFalse); /* FUTURE: value exception?? */
4354           image->rendering_intent=(RenderingIntent) rendering_intent;
4355           return(MagickTrue);
4356         }
4357       break; /* not an attribute, add as a property */
4358     }
4359     case 'S':
4360     case 's':
4361       if ((LocaleCompare("size",property) == 0) ||
4362           (LocaleCompare("skewness",property) == 0) ||
4363           (LocaleCompare("scenes",property) == 0) ||
4364           (LocaleCompare("standard-deviation",property) == 0))
4365         {
4366           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4367             "SetReadOnlyProperty","`%s'",property);
4368           return(MagickFalse);
4369         }
4370       break; /* not an attribute, add as a property */
4371     case 'T':
4372     case 't':
4373     {
4374       if (LocaleCompare("tile-offset",property) == 0)
4375         {
4376           char
4377             *geometry;
4378
4379           geometry=GetPageGeometry(value);
4380           flags=ParseAbsoluteGeometry(geometry,&image->tile_offset);
4381           geometry=DestroyString(geometry);
4382           return(MagickTrue);
4383         }
4384       break; /* not an attribute, add as a property */
4385     }
4386     case 'U':
4387     case 'u':
4388     {
4389       if (LocaleCompare("units",property) == 0)
4390         {
4391           ssize_t
4392             units;
4393
4394           units=ParseCommandOption(MagickResolutionOptions,MagickFalse,value);
4395           if (units < 0)
4396             return(MagickFalse); /* FUTURE: value exception?? */
4397           image->units=(ResolutionType) units;
4398           return(MagickTrue);
4399         }
4400       break; /* not an attribute, add as a property */
4401     }
4402     case 'V':
4403     case 'v':
4404     {
4405       if (LocaleCompare("version",property) == 0)
4406         {
4407           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4408             "SetReadOnlyProperty","`%s'",property);
4409           return(MagickFalse);
4410         }
4411       break; /* not an attribute, add as a property */
4412     }
4413     case 'W':
4414     case 'w':
4415     {
4416       if (LocaleCompare("width",property) == 0)
4417         {
4418           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4419             "SetReadOnlyProperty","`%s'",property);
4420           return(MagickFalse);
4421         }
4422       break; /* not an attribute, add as a property */
4423     }
4424 #if 0  /* Percent escape's sets values with this prefix: for later use
4425           Throwing an exception causes this setting to fail */
4426     case 'X':
4427     case 'x':
4428     {
4429       if (LocaleNCompare("xmp:",property,4) == 0)
4430         {
4431           (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
4432             "SetReadOnlyProperty","`%s'",property);
4433           return(MagickFalse);
4434         }
4435       break; /* not an attribute, add as a property */
4436     }
4437 #endif
4438   }
4439   /* Default: not an attribute, add as a property */
4440   status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4441     ConstantString(property),ConstantString(value));
4442   /* FUTURE: error if status is bad? */
4443   return(status);
4444 }