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