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