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