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