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