]> 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           y=(long) ReadPropertyMSBLong(&blob,&length);
1663           x=(long) ReadPropertyMSBLong(&blob,&length);
1664           point[i].x=(double) x/4096/4096;
1665           point[i].y=1.0-(double) y/4096/4096;
1666         }
1667         if (in_subpath == MagickFalse)
1668           {
1669             (void) FormatMagickString(message,MaxTextExtent,"  %g %g m\n",
1670               point[1].x,point[1].y);
1671             for (i=0; i < 3; i++)
1672             {
1673               first[i]=point[i];
1674               last[i]=point[i];
1675             }
1676           }
1677         else
1678           {
1679             /*
1680               Handle special cases when Bezier curves are used to describe
1681               corners and straight lines.
1682             */
1683             if ((last[1].x == last[2].x) && (last[1].y == last[2].y) &&
1684                 (point[0].x == point[1].x) && (point[0].y == point[1].y))
1685               (void) FormatMagickString(message,MaxTextExtent,"  %g %g l\n",
1686                 point[1].x,point[1].y);
1687             else
1688               if ((last[1].x == last[2].x) && (last[1].y == last[2].y))
1689                 (void) FormatMagickString(message,MaxTextExtent,
1690                   "  %g %g %g %g v\n",point[0].x,point[0].y,point[1].x,
1691                   point[1].y);
1692               else
1693                 if ((point[0].x == point[1].x) && (point[0].y == point[1].y))
1694                   (void) FormatMagickString(message,MaxTextExtent,
1695                     "  %g %g %g %g y\n",last[2].x,last[2].y,point[1].x,
1696                     point[1].y);
1697                 else
1698                   (void) FormatMagickString(message,MaxTextExtent,
1699                     "  %g %g %g %g %g %g c\n",last[2].x,last[2].y,point[0].x,
1700                     point[0].y,point[1].x,point[1].y);
1701             for (i=0; i < 3; i++)
1702               last[i]=point[i];
1703           }
1704         (void) ConcatenateString(&path,message);
1705         in_subpath=MagickTrue;
1706         knot_count--;
1707         /*
1708           Close the subpath if there are no more knots.
1709         */
1710         if (knot_count == 0)
1711           {
1712             /*
1713               Same special handling as above except we compare to the
1714               first point in the path and close the path.
1715             */
1716             if ((last[1].x == last[2].x) && (last[1].y == last[2].y) &&
1717                 (first[0].x == first[1].x) && (first[0].y == first[1].y))
1718               (void) FormatMagickString(message,MaxTextExtent,"  %g %g l z\n",
1719                 first[1].x,first[1].y);
1720             else
1721               if ((last[1].x == last[2].x) && (last[1].y == last[2].y))
1722                 (void) FormatMagickString(message,MaxTextExtent,
1723                   "  %g %g %g %g v z\n",first[0].x,first[0].y,first[1].x,
1724                   first[1].y);
1725               else
1726                 if ((first[0].x == first[1].x) && (first[0].y == first[1].y))
1727                   (void) FormatMagickString(message,MaxTextExtent,
1728                     "  %g %g %g %g y z\n",last[2].x,last[2].y,first[1].x,
1729                     first[1].y);
1730                 else
1731                   (void) FormatMagickString(message,MaxTextExtent,
1732                     "  %g %g %g %g %g %g c z\n",last[2].x,last[2].y,first[0].x,
1733                     first[0].y,first[1].x,first[1].y);
1734             (void) ConcatenateString(&path,message);
1735             in_subpath=MagickFalse;
1736           }
1737         break;
1738       }
1739       case 6:
1740       case 7:
1741       case 8:
1742       default:
1743       {
1744         blob+=24;
1745         length-=24;
1746         break;
1747       }
1748     }
1749   }
1750   /*
1751     Returns an empty PS path if the path has no knots.
1752   */
1753   (void) FormatMagickString(message,MaxTextExtent,"  eoclip\n");
1754   (void) ConcatenateString(&path,message);
1755   (void) FormatMagickString(message,MaxTextExtent,"} bind def");
1756   (void) ConcatenateString(&path,message);
1757   message=DestroyString(message);
1758   return(path);
1759 }
1760
1761 static char *TraceSVGClippath(const unsigned char *blob,size_t length,
1762   const unsigned long columns,const unsigned long rows)
1763 {
1764   char
1765     *path,
1766     *message;
1767
1768   long
1769     knot_count,
1770     selector,
1771     x,
1772     y;
1773
1774   MagickBooleanType
1775     in_subpath;
1776
1777   PointInfo
1778     first[3],
1779     last[3],
1780     point[3];
1781
1782   register long
1783     i;
1784
1785   path=AcquireString((char *) NULL);
1786   if (path == (char *) NULL)
1787     return((char *) NULL);
1788   message=AcquireString((char *) NULL);
1789   (void) FormatMagickString(message,MaxTextExtent,
1790     "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n");
1791   (void) ConcatenateString(&path,message);
1792   (void) FormatMagickString(message,MaxTextExtent,
1793     "<svg width=\"%lu\" height=\"%lu\">\n",columns,rows);
1794   (void) ConcatenateString(&path,message);
1795   (void) FormatMagickString(message,MaxTextExtent,"<g>\n");
1796   (void) ConcatenateString(&path,message);
1797   (void) FormatMagickString(message,MaxTextExtent,
1798     "<path style=\"fill:#00000000;stroke:#00000000;");
1799   (void) ConcatenateString(&path,message);
1800   (void) FormatMagickString(message,MaxTextExtent,
1801     "stroke-width:0;stroke-antialiasing:false\" d=\"\n");
1802   (void) ConcatenateString(&path,message);
1803   (void) ResetMagickMemory(point,0,sizeof(point));
1804   (void) ResetMagickMemory(first,0,sizeof(first));
1805   (void) ResetMagickMemory(last,0,sizeof(last));
1806   knot_count=0;
1807   in_subpath=MagickFalse;
1808   while (length != 0)
1809   {
1810     selector=(long) ReadPropertyMSBShort(&blob,&length);
1811     switch (selector)
1812     {
1813       case 0:
1814       case 3:
1815       {
1816         if (knot_count != 0)
1817           {
1818             blob+=24;
1819             length-=24;
1820             break;
1821           }
1822         /*
1823           Expected subpath length record.
1824         */
1825         knot_count=(long) ReadPropertyMSBShort(&blob,&length);
1826         blob+=22;
1827         length-=22;
1828         break;
1829       }
1830       case 1:
1831       case 2:
1832       case 4:
1833       case 5:
1834       {
1835         if (knot_count == 0)
1836           {
1837             /*
1838               Unexpected subpath knot.
1839             */
1840             blob+=24;
1841             length-=24;
1842           }
1843         else
1844           {
1845             /*
1846               Add sub-path knot
1847             */
1848             for (i=0; i < 3; i++)
1849             {
1850               y=(long) ReadPropertyMSBLong(&blob,&length);
1851               x=(long) ReadPropertyMSBLong(&blob,&length);
1852               point[i].x=(double) x*columns/4096/4096;
1853               point[i].y=(double) y*rows/4096/4096;
1854             }
1855             if (in_subpath == MagickFalse)
1856               {
1857                 (void) FormatMagickString(message,MaxTextExtent,"M %g,%g\n",
1858                   point[1].x,point[1].y);
1859                 for (i=0; i < 3; i++)
1860                 {
1861                   first[i]=point[i];
1862                   last[i]=point[i];
1863                 }
1864               }
1865             else
1866               {
1867                 if ((last[1].x == last[2].x) && (last[1].y == last[2].y) &&
1868                     (point[0].x == point[1].x) && (point[0].y == point[1].y))
1869                   (void) FormatMagickString(message,MaxTextExtent,"L %g,%g\n",
1870                     point[1].x,point[1].y);
1871                 else
1872                   (void) FormatMagickString(message,MaxTextExtent,
1873                     "C %g,%g %g,%g %g,%g\n",last[2].x,last[2].y,
1874                     point[0].x,point[0].y,point[1].x,point[1].y);
1875                 for (i=0; i < 3; i++)
1876                   last[i]=point[i];
1877               }
1878             (void) ConcatenateString(&path,message);
1879             in_subpath=MagickTrue;
1880             knot_count--;
1881             /*
1882               Close the subpath if there are no more knots.
1883             */
1884             if (knot_count == 0)
1885               {
1886                 if ((last[1].x == last[2].x) && (last[1].y == last[2].y) &&
1887                     (first[0].x == first[1].x) && (first[0].y == first[1].y))
1888                   (void) FormatMagickString(message,MaxTextExtent,"L %g,%g Z\n",
1889                     first[1].x,first[1].y);
1890                 else
1891                   {
1892                     (void) FormatMagickString(message,MaxTextExtent,
1893                       "C %g,%g %g,%g %g,%g Z\n",last[2].x,last[2].y,
1894                       first[0].x,first[0].y,first[1].x,first[1].y);
1895                     (void) ConcatenateString(&path,message);
1896                   }
1897                 in_subpath=MagickFalse;
1898               }
1899           }
1900           break;
1901       }
1902       case 6:
1903       case 7:
1904       case 8:
1905       default:
1906       {
1907         blob+=24;
1908         length-=24;
1909         break;
1910       }
1911     }
1912   }
1913   /*
1914     Return an empty SVG image if the path does not have knots.
1915   */
1916   (void) FormatMagickString(message,MaxTextExtent,"\"/>\n");
1917   (void) ConcatenateString(&path,message);
1918   (void) FormatMagickString(message,MaxTextExtent,"</g>\n");
1919   (void) ConcatenateString(&path,message);
1920   (void) FormatMagickString(message,MaxTextExtent,"</svg>\n");
1921   (void) ConcatenateString(&path,message);
1922   message=DestroyString(message);
1923   return(path);
1924 }
1925
1926 MagickExport const char *GetImageProperty(const Image *image,
1927   const char *property)
1928 {
1929   ExceptionInfo
1930     *exception;
1931
1932   FxInfo
1933     *fx_info;
1934
1935   MagickRealType
1936     alpha;
1937
1938   MagickStatusType
1939     status;
1940
1941   register const char
1942     *p;
1943
1944   assert(image != (Image *) NULL);
1945   assert(image->signature == MagickSignature);
1946   if (image->debug != MagickFalse)
1947     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1948   p=(const char *) NULL;
1949   if (property == (const char *) NULL)
1950     {
1951       ResetSplayTreeIterator((SplayTreeInfo *) image->properties);
1952       p=(const char *) GetNextValueInSplayTree((SplayTreeInfo *)
1953         image->properties);
1954       return(p);
1955     }
1956   if ((image->properties != (void *) NULL) &&
1957       (LocaleNCompare("fx:",property,3) != 0))
1958     {
1959       p=(const char *) GetValueFromSplayTree((SplayTreeInfo *)
1960         image->properties,property);
1961       if (p != (const char *) NULL)
1962         return(p);
1963     }
1964   if (strchr(property,':') == (char *) NULL)
1965     return(p);
1966   exception=(&((Image *) image)->exception);
1967   switch (*property)
1968   {
1969     case '8':
1970     {
1971       if (LocaleNCompare("8bim:",property,5) == 0)
1972         {
1973           if (Get8BIMProperty(image,property) != MagickFalse)
1974             {
1975               p=(const char *) GetValueFromSplayTree((SplayTreeInfo *)
1976                 image->properties,property);
1977               return(p);
1978             }
1979         }
1980       break;
1981     }
1982     case 'E':
1983     case 'e':
1984     {
1985       if (LocaleNCompare("exif:",property,5) == 0)
1986         {
1987           if (GetEXIFProperty(image,property) != MagickFalse)
1988             {
1989               p=(const char *) GetValueFromSplayTree((SplayTreeInfo *)
1990                 image->properties,property);
1991               return(p);
1992             }
1993         }
1994       break;
1995     }
1996     case 'F':
1997     case 'f':
1998     {
1999       if (LocaleNCompare("fx:",property,3) == 0)
2000         {
2001           fx_info=AcquireFxInfo(image,property+3);
2002           status=FxEvaluateExpression(fx_info,&alpha,exception);
2003           fx_info=DestroyFxInfo(fx_info);
2004           if (status != MagickFalse)
2005             {
2006               char
2007                 value[MaxTextExtent];
2008
2009               (void) FormatMagickString(value,MaxTextExtent,"%g",(double)
2010                 alpha);
2011               (void) SetImageProperty((Image *) image,property,value);
2012             }
2013           p=(const char *) GetValueFromSplayTree((SplayTreeInfo *)
2014             image->properties,property);
2015           return(p);
2016         }
2017       break;
2018     }
2019     case 'I':
2020     case 'i':
2021     {
2022       if (LocaleNCompare("iptc:",property,5) == 0)
2023         {
2024           if (GetIPTCProperty(image,property) != MagickFalse)
2025             {
2026               p=(const char *) GetValueFromSplayTree((SplayTreeInfo *)
2027                 image->properties,property);
2028               return(p);
2029             }
2030         }
2031       break;
2032     }
2033     case 'P':
2034     case 'p':
2035     {
2036       if (LocaleNCompare("pixel:",property,6) == 0)
2037         {
2038           MagickPixelPacket
2039             pixel;
2040
2041           GetMagickPixelPacket(image,&pixel);
2042           fx_info=AcquireFxInfo(image,property+6);
2043           status=FxEvaluateChannelExpression(fx_info,RedChannel,0,0,&alpha,
2044             exception);
2045           pixel.red=(MagickRealType) QuantumRange*alpha;
2046           status|=FxEvaluateChannelExpression(fx_info,GreenChannel,0,0,&alpha,
2047             exception);
2048           pixel.green=(MagickRealType) QuantumRange*alpha;
2049           status|=FxEvaluateChannelExpression(fx_info,BlueChannel,0,0,&alpha,
2050             exception);
2051           pixel.blue=(MagickRealType) QuantumRange*alpha;
2052           status|=FxEvaluateChannelExpression(fx_info,OpacityChannel,0,0,&alpha,
2053             exception);
2054           pixel.opacity=(MagickRealType) QuantumRange*(1.0-alpha);
2055           if (image->colorspace == CMYKColorspace)
2056             {
2057               status|=FxEvaluateChannelExpression(fx_info,BlackChannel,0,0,
2058                 &alpha,exception);
2059               pixel.index=(MagickRealType) QuantumRange*alpha;
2060             }
2061           fx_info=DestroyFxInfo(fx_info);
2062           if (status != MagickFalse)
2063             {
2064               char
2065                 name[MaxTextExtent];
2066
2067               (void) QueryMagickColorname(image,&pixel,SVGCompliance,name,
2068                 exception);
2069               (void) SetImageProperty((Image *) image,property,name);
2070               return(GetImageProperty(image,property));
2071             }
2072         }
2073       break;
2074     }
2075     case 'X':
2076     case 'x':
2077     {
2078       if (LocaleNCompare("xmp:",property,4) == 0)
2079         {
2080           if (GetXMPProperty(image,property) != MagickFalse)
2081             {
2082               p=(const char *) GetValueFromSplayTree((SplayTreeInfo *)
2083                 image->properties,property);
2084               return(p);
2085             }
2086         }
2087       break;
2088     }
2089   }
2090   return(p);
2091 }
2092 \f
2093 /*
2094 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2095 %                                                                             %
2096 %                                                                             %
2097 %                                                                             %
2098 +   G e t M a g i c k P r o p e r t y                                         %
2099 %                                                                             %
2100 %                                                                             %
2101 %                                                                             %
2102 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2103 %
2104 %  GetMagickProperty() gets a value associated with an image property.
2105 %
2106 %  The format of the GetMagickProperty method is:
2107 %
2108 %      const char *GetMagickProperty(const ImageInfo *image_info,
2109 %        Image *image,const char *key)
2110 %
2111 %  A description of each parameter follows:
2112 %
2113 %    o image_info: the image info.
2114 %
2115 %    o image: the image.
2116 %
2117 %    o key: the key.
2118 %
2119 */
2120 MagickExport const char *GetMagickProperty(const ImageInfo *image_info,
2121   Image *image,const char *property)
2122 {
2123   char
2124     value[MaxTextExtent],
2125     filename[MaxTextExtent];
2126
2127   *value='\0';
2128   switch (*(property))
2129   {
2130     case 'b':
2131     {
2132       if (LocaleNCompare("base",property,4) == 0)
2133         {
2134           GetPathComponent(image->magick_filename,BasePath,filename);
2135           (void) CopyMagickString(value,filename,MaxTextExtent);
2136           break;
2137         }
2138       break;
2139     }
2140     case 'c':
2141     {
2142       if (LocaleNCompare("channels",property,8) == 0)
2143         {
2144           /*
2145             Image channels.
2146           */
2147           (void) FormatMagickString(value,MaxTextExtent,"%s",
2148             MagickOptionToMnemonic(MagickColorspaceOptions,(long)
2149             image->colorspace));
2150           LocaleLower(value);
2151           if (image->matte != MagickFalse)
2152             (void) ConcatenateMagickString(value,"a",MaxTextExtent);
2153           break;
2154         }
2155       if (LocaleNCompare("colorspace",property,10) == 0)
2156         {
2157           ColorspaceType
2158             colorspace;
2159
2160           /*
2161             Image storage class and colorspace.
2162           */
2163           colorspace=image->colorspace;
2164           if (IsGrayImage(image,&image->exception) != MagickFalse)
2165             colorspace=GRAYColorspace;
2166           (void) FormatMagickString(value,MaxTextExtent,"%s",
2167             MagickOptionToMnemonic(MagickColorspaceOptions,(long) colorspace));
2168           break;
2169         }
2170       break;
2171     }
2172     case 'd':
2173     {
2174       if (LocaleNCompare("depth",property,5) == 0)
2175         {
2176           (void) FormatMagickString(value,MaxTextExtent,"%lu",image->depth);
2177           break;
2178         }
2179       if (LocaleNCompare("directory",property,9) == 0)
2180         {
2181           GetPathComponent(image->magick_filename,HeadPath,filename);
2182           (void) CopyMagickString(value,filename,MaxTextExtent);
2183           break;
2184         }
2185       break;
2186     }
2187     case 'e':
2188     {
2189       if (LocaleNCompare("extension",property,9) == 0)
2190         {
2191           GetPathComponent(image->magick_filename,ExtensionPath,filename);
2192           (void) CopyMagickString(value,filename,MaxTextExtent);
2193           break;
2194         }
2195       break;
2196     }
2197     case 'g':
2198     {
2199       if (LocaleNCompare("group",property,5) == 0)
2200         {
2201           (void) FormatMagickString(value,MaxTextExtent,"0x%lx",
2202             image_info->group);
2203           break;
2204         }
2205       break;
2206     }
2207     case 'h':
2208     {
2209       if (LocaleNCompare("height",property,6) == 0)
2210         {
2211           (void) FormatMagickString(value,MaxTextExtent,"%lu",
2212             image->magick_rows != 0 ? image->magick_rows : 256UL);
2213           break;
2214         }
2215       break;
2216     }
2217     case 'i':
2218     {
2219       if (LocaleNCompare("input",property,5) == 0)
2220         {
2221           (void) CopyMagickString(value,image->filename,MaxTextExtent);
2222           break;
2223         }
2224       break;
2225     }
2226     case 'k':
2227     {
2228       if (LocaleNCompare("kurtosis",property,8) == 0)
2229         {
2230           double
2231             kurtosis,
2232             skewness;
2233
2234           (void) GetImageChannelKurtosis(image,image_info->channel,&kurtosis,
2235             &skewness,&image->exception);
2236           (void) FormatMagickString(value,MaxTextExtent,"%g",kurtosis);
2237           break;
2238         }
2239       break;
2240     }
2241     case 'm':
2242     {
2243       if (LocaleNCompare("magick",property,6) == 0)
2244         {
2245           (void) CopyMagickString(value,image->magick,MaxTextExtent);
2246           break;
2247         }
2248       if (LocaleNCompare("max",property,3) == 0)
2249         {
2250           double
2251             maximum,
2252             minimum;
2253
2254           (void) GetImageChannelRange(image,image_info->channel,&minimum,
2255             &maximum,&image->exception);
2256           (void) FormatMagickString(value,MaxTextExtent,"%g",maximum);
2257           break;
2258         }
2259       if (LocaleNCompare("mean",property,4) == 0)
2260         {
2261           double
2262             mean,
2263             standard_deviation;
2264
2265           (void) GetImageChannelMean(image,image_info->channel,&mean,
2266             &standard_deviation,&image->exception);
2267           (void) FormatMagickString(value,MaxTextExtent,"%g",mean);
2268           break;
2269         }
2270       if (LocaleNCompare("min",property,3) == 0)
2271         {
2272           double
2273             maximum,
2274             minimum;
2275
2276           (void) GetImageChannelRange(image,image_info->channel,&minimum,
2277             &maximum,&image->exception);
2278           (void) FormatMagickString(value,MaxTextExtent,"%g",minimum);
2279           break;
2280         }
2281       break;
2282     }
2283     case 'n':
2284     {
2285       if (LocaleNCompare("name",property,4) == 0)
2286         {
2287           (void) CopyMagickString(value,filename,MaxTextExtent);
2288           break;
2289         }
2290      break;
2291     }
2292     case 'o':
2293     {
2294       if (LocaleNCompare("output",property,6) == 0)
2295         {
2296           (void) CopyMagickString(value,image_info->filename,MaxTextExtent);
2297           break;
2298         }
2299      break;
2300     }
2301     case 'p':
2302     {
2303       if (LocaleNCompare("page",property,4) == 0)
2304         {
2305           register const Image
2306             *p;
2307
2308           unsigned long
2309             page;
2310
2311           p=image;
2312           for (page=1; GetPreviousImageInList(p) != (Image *) NULL; page++)
2313             p=GetPreviousImageInList(p);
2314           (void) FormatMagickString(value,MaxTextExtent,"%lu",page);
2315           break;
2316         }
2317       break;
2318     }
2319     case 's':
2320     {
2321       if (LocaleNCompare("size",property,4) == 0)
2322         {
2323           char
2324             format[MaxTextExtent];
2325
2326           (void) FormatMagickSize(GetBlobSize(image),format);
2327           (void) FormatMagickString(value,MaxTextExtent,"%s",format);
2328           break;
2329         }
2330       if (LocaleNCompare("scenes",property,6) == 0)
2331         {
2332           (void) FormatMagickString(value,MaxTextExtent,"%lu",
2333             (unsigned long) GetImageListLength(image));
2334           break;
2335         }
2336       if (LocaleNCompare("scene",property,5) == 0)
2337         {
2338           (void) FormatMagickString(value,MaxTextExtent,"%lu",image->scene);
2339           if (image_info->number_scenes != 0)
2340             (void) FormatMagickString(value,MaxTextExtent,"%lu",
2341               image_info->scene);
2342           break;
2343         }
2344       if (LocaleNCompare("skewness",property,8) == 0)
2345         {
2346           double
2347             kurtosis,
2348             skewness;
2349
2350           (void) GetImageChannelKurtosis(image,image_info->channel,&kurtosis,
2351             &skewness,&image->exception);
2352           (void) FormatMagickString(value,MaxTextExtent,"%g",skewness);
2353           break;
2354         }
2355       if ((LocaleNCompare("standard-deviation",property,18) == 0) ||
2356           (LocaleNCompare("standard_deviation",property,18) == 0))
2357         {
2358           double
2359             mean,
2360             standard_deviation;
2361
2362           (void) GetImageChannelMean(image,image_info->channel,&mean,
2363             &standard_deviation,&image->exception);
2364           (void) FormatMagickString(value,MaxTextExtent,"%g",
2365             standard_deviation);
2366           break;
2367         }
2368        break;
2369     }
2370     case 'u':
2371     {
2372       if (LocaleNCompare("unique",property,6) == 0)
2373         {
2374           (void) CopyMagickString(filename,image_info->unique,MaxTextExtent);
2375           (void) CopyMagickString(value,filename,MaxTextExtent);
2376           break;
2377         }
2378       break;
2379     }
2380     case 'w':
2381     {
2382       if (LocaleNCompare("width",property,5) == 0)
2383         {
2384           (void) FormatMagickString(value,MaxTextExtent,"%lu",
2385             image->magick_columns != 0 ? image->magick_columns : 256UL);
2386           break;
2387         }
2388       break;
2389     }
2390     case 'x':
2391     {
2392       if (LocaleNCompare("xresolution",property,11) == 0)
2393         {
2394           (void) FormatMagickString(value,MaxTextExtent,"%g",
2395             image->x_resolution);
2396           break;
2397         }
2398       break;
2399     }
2400     case 'y':
2401     {
2402       if (LocaleNCompare("yresolution",property,11) == 0)
2403         {
2404           (void) FormatMagickString(value,MaxTextExtent,"%g",
2405             image->y_resolution);
2406           break;
2407         }
2408       break;
2409     }
2410     case 'z':
2411     {
2412       if (LocaleNCompare("zero",property,4) == 0)
2413         {
2414           (void) CopyMagickString(filename,image_info->zero,MaxTextExtent);
2415           (void) CopyMagickString(value,filename,MaxTextExtent);
2416           break;
2417         }
2418       break;
2419     }
2420   }
2421   if (*value != '\0')
2422    {
2423      if (image->properties == (void *) NULL)
2424        image->properties=NewSplayTree(CompareSplayTreeString,
2425          RelinquishMagickMemory,RelinquishMagickMemory);
2426      (void) AddValueToSplayTree((SplayTreeInfo *) image->properties,
2427        ConstantString(property),ConstantString(value));
2428    }
2429   return(GetImageProperty(image,property));
2430 }
2431 \f
2432 /*
2433 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2434 %                                                                             %
2435 %                                                                             %
2436 %                                                                             %
2437 %   G e t N e x t I m a g e P r o p e r t y                                   %
2438 %                                                                             %
2439 %                                                                             %
2440 %                                                                             %
2441 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2442 %
2443 %  GetNextImageProperty() gets the next image property value.
2444 %
2445 %  The format of the GetNextImageProperty method is:
2446 %
2447 %      char *GetNextImageProperty(const Image *image)
2448 %
2449 %  A description of each parameter follows:
2450 %
2451 %    o image: the image.
2452 %
2453 */
2454 MagickExport char *GetNextImageProperty(const Image *image)
2455 {
2456   assert(image != (Image *) NULL);
2457   assert(image->signature == MagickSignature);
2458   if (image->debug != MagickFalse)
2459     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
2460       image->filename);
2461   if (image->properties == (void *) NULL)
2462     return((char *) NULL);
2463   return((char *) GetNextKeyInSplayTree((SplayTreeInfo *) image->properties));
2464 }
2465 \f
2466 /*
2467 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2468 %                                                                             %
2469 %                                                                             %
2470 %                                                                             %
2471 %   I n t e r p r e t I m a g e P r o p e r t i e s                           %
2472 %                                                                             %
2473 %                                                                             %
2474 %                                                                             %
2475 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2476 %
2477 %  InterpretImageProperties() replaces any embedded formatting characters with
2478 %  the appropriate image property and returns the interpretted text.
2479 %
2480 %  The format of the InterpretImageProperties method is:
2481 %
2482 %      char *InterpretImageProperties(const ImageInfo *image_info,Image *image,
2483 %        const char *embed_text)
2484 %
2485 %  A description of each parameter follows:
2486 %
2487 %    o image_info: the image info.
2488 %
2489 %    o image: the image.
2490 %
2491 %    o embed_text: the address of a character string containing the embedded
2492 %      formatting characters.
2493 %
2494 */
2495 MagickExport char *InterpretImageProperties(const ImageInfo *image_info,
2496   Image *image,const char *embed_text)
2497 {
2498   char
2499     filename[MaxTextExtent],
2500     *interpret_text,
2501     *text;
2502
2503   const char
2504     *value;
2505
2506   ImageInfo
2507     *text_info;
2508
2509   register char
2510     *q;
2511
2512   register const char
2513     *p;
2514
2515   register long
2516     i;
2517
2518   size_t
2519     extent,
2520     length;
2521
2522   assert(image != (Image *) NULL);
2523   assert(image->signature == MagickSignature);
2524   if (image->debug != MagickFalse)
2525     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2526   if ((embed_text == (const char *) NULL) || (*embed_text == '\0'))
2527     return((char *) NULL);
2528   text=(char *) embed_text;
2529   if ((*text == '@') && ((*(text+1) == '-') ||
2530       (IsPathAccessible(text+1) != MagickFalse)))
2531     return(FileToString(embed_text+1,~0,&image->exception));
2532   /*
2533     Translate any embedded format characters.
2534   */
2535   text_info=CloneImageInfo(image_info);
2536   interpret_text=AcquireString(text);
2537   extent=MaxTextExtent;
2538   p=text;
2539   for (q=interpret_text; *p != '\0'; p++)
2540   {
2541     *q='\0';
2542     if ((size_t) (q-interpret_text+MaxTextExtent) >= extent)
2543       {
2544         extent+=MaxTextExtent;
2545         interpret_text=(char *) ResizeQuantumMemory(interpret_text,extent+
2546           MaxTextExtent+1,sizeof(*interpret_text));
2547         if (interpret_text == (char *) NULL)
2548           break;
2549         q=interpret_text+strlen(interpret_text);
2550       }
2551     /*
2552       Process formatting characters in text.
2553     */
2554     if ((*p == '\\') && (*(p+1) == 'r'))
2555       {
2556         *q++='\r';
2557         p++;
2558         continue;
2559       }
2560     if ((*p == '\\') && (*(p+1) == 'n'))
2561       {
2562         *q++='\n';
2563         p++;
2564         continue;
2565       }
2566     if (*p == '\\')
2567       {
2568         p++;
2569         *q++=(*p);
2570         continue;
2571       }
2572     if (*p != '%')
2573       {
2574         *q++=(*p);
2575         continue;
2576       }
2577     p++;
2578     switch (*p)
2579     {
2580       case 'b':
2581       {
2582         char
2583           format[MaxTextExtent];
2584
2585         MagickSizeType
2586           length;
2587
2588         /*
2589           File size.
2590         */
2591         length=GetBlobSize(image);
2592         (void) FormatMagickString(format,MaxTextExtent,"%lu",(unsigned long)
2593           length);
2594         if (length != (MagickSizeType) ((size_t) length))
2595           (void) FormatMagickSize(length,format);
2596         q+=ConcatenateMagickString(q,format,extent);
2597         break;
2598       }
2599       case 'c':
2600       {
2601         /*
2602           Image comment.
2603         */
2604         value=GetImageProperty(image,"comment");
2605         if (value == (const char *) NULL)
2606           break;
2607         length=strlen(value);
2608         if ((size_t) (q-interpret_text+length+1) >= extent)
2609           {
2610             extent+=length;
2611             interpret_text=(char *) ResizeQuantumMemory(interpret_text,
2612               extent+MaxTextExtent,sizeof(*interpret_text));
2613             if (interpret_text == (char *) NULL)
2614               break;
2615             q=interpret_text+strlen(interpret_text);
2616           }
2617         (void) CopyMagickString(q,value,extent);
2618         q+=length;
2619         break;
2620       }
2621       case 'd':
2622       case 'e':
2623       case 'f':
2624       case 't':
2625       {
2626         /*
2627           Label segment is the base of the filename.
2628         */
2629         if (*image->magick_filename == '\0')
2630           break;
2631         switch (*p)
2632         {
2633           case 'd':
2634           {
2635             /*
2636               Directory.
2637             */
2638             GetPathComponent(image->magick_filename,HeadPath,filename);
2639             q+=CopyMagickString(q,filename,extent);
2640             break;
2641           }
2642           case 'e':
2643           {
2644             /*
2645               Filename extension.
2646             */
2647             GetPathComponent(image->magick_filename,ExtensionPath,filename);
2648             q+=CopyMagickString(q,filename,extent);
2649             break;
2650           }
2651           case 'f':
2652           {
2653             /*
2654               Filename.
2655             */
2656             GetPathComponent(image->magick_filename,TailPath,filename);
2657             q+=CopyMagickString(q,filename,extent);
2658             break;
2659           }
2660           case 't':
2661           {
2662             /*
2663               Base filename.
2664             */
2665             GetPathComponent(image->magick_filename,BasePath,filename);
2666             q+=CopyMagickString(q,filename,extent);
2667             break;
2668           }
2669         }
2670         break;
2671       }
2672       case 'g':
2673       {
2674         /*
2675           Image geometry.
2676         */
2677         q+=FormatMagickString(q,extent,"%lux%lu%+ld%+ld",image->page.width,
2678           image->page.height,image->page.x,image->page.y);
2679         break;
2680       }
2681       case 'h':
2682       {
2683         /*
2684           Image height.
2685         */
2686         q+=FormatMagickString(q,extent,"%lu",image->rows != 0 ? image->rows :
2687           image->magick_rows);
2688         break;
2689       }
2690       case 'i':
2691       {
2692         /*
2693           Image filename.
2694         */
2695         q+=CopyMagickString(q,image->filename,extent);
2696         break;
2697       }
2698       case 'k':
2699       {
2700         /*
2701           Number of unique colors.
2702         */
2703         q+=FormatMagickString(q,extent,"%lu",GetNumberColors(image,
2704           (FILE *) NULL,&image->exception));
2705         break;
2706       }
2707       case 'l':
2708       {
2709         /*
2710           Image label.
2711         */
2712         value=GetImageProperty(image,"label");
2713         if (value == (const char *) NULL)
2714           break;
2715         length=strlen(value);
2716         if ((size_t) (q-interpret_text+length+1) >= extent)
2717           {
2718             extent+=length;
2719             interpret_text=(char *) ResizeQuantumMemory(interpret_text,
2720               extent+MaxTextExtent,sizeof(*interpret_text));
2721             if (interpret_text == (char *) NULL)
2722               break;
2723             q=interpret_text+strlen(interpret_text);
2724           }
2725         q+=CopyMagickString(q,value,extent);
2726         break;
2727       }
2728       case 'm':
2729       {
2730         /*
2731           Image format.
2732         */
2733         q+=CopyMagickString(q,image->magick,extent);
2734         break;
2735       }
2736       case 'M':
2737       {
2738         /*
2739           Image magick filename.
2740         */
2741         q+=CopyMagickString(q,image->magick_filename,extent);
2742         break;
2743       }
2744       case 'n':
2745       {
2746         /*
2747           Number of images in the list.
2748         */
2749         q+=FormatMagickString(q,extent,"%lu",(unsigned long)
2750           GetImageListLength(image));
2751         break;
2752       }
2753       case 'o':
2754       {
2755         /*
2756           Image output filename.
2757         */
2758         q+=CopyMagickString(q,text_info->filename,extent);
2759         break;
2760       }
2761       case 'p':
2762       {
2763         register const Image
2764           *p;
2765
2766         unsigned long
2767           page;
2768
2769         /*
2770           Image page number.
2771         */
2772         p=image;
2773         for (page=1; GetPreviousImageInList(p) != (Image *) NULL; page++)
2774           p=GetPreviousImageInList(p);
2775         q+=FormatMagickString(q,extent,"%lu",page);
2776         break;
2777       }
2778       case 'q':
2779       {
2780         /*
2781           Image depth.
2782         */
2783         q+=FormatMagickString(q,extent,"%lu",image->depth);
2784         break;
2785       }
2786       case 'r':
2787       {
2788         ColorspaceType
2789           colorspace;
2790
2791         /*
2792           Image storage class and colorspace.
2793         */
2794         colorspace=image->colorspace;
2795         if (IsGrayImage(image,&image->exception) != MagickFalse)
2796           colorspace=GRAYColorspace;
2797         q+=FormatMagickString(q,extent,"%s%s%s",MagickOptionToMnemonic(
2798           MagickClassOptions,(long) image->storage_class),
2799           MagickOptionToMnemonic(MagickColorspaceOptions,(long) colorspace),
2800           image->matte != MagickFalse ? "Matte" : "");
2801         break;
2802       }
2803       case 's':
2804       {
2805         /*
2806           Image scene number.
2807         */
2808         if (text_info->number_scenes == 0)
2809           q+=FormatMagickString(q,extent,"%lu",image->scene);
2810         else
2811           q+=FormatMagickString(q,extent,"%lu",text_info->scene);
2812         break;
2813       }
2814       case 'u':
2815       {
2816         /*
2817           Unique filename.
2818         */
2819         (void) CopyMagickString(filename,text_info->unique,extent);
2820         q+=CopyMagickString(q,filename,extent);
2821         break;
2822       }
2823       case 'w':
2824       {
2825         /*
2826           Image width.
2827         */
2828         q+=FormatMagickString(q,extent,"%lu",image->columns != 0 ?
2829           image->columns : image->magick_columns);
2830         break;
2831       }
2832       case 'x':
2833       {
2834         /*
2835           Image horizontal resolution.
2836         */
2837         q+=FormatMagickString(q,extent,"%g %s",image->x_resolution,
2838           MagickOptionToMnemonic(MagickResolutionOptions,(long) image->units));
2839         break;
2840       }
2841       case 'y':
2842       {
2843         /*
2844           Image vertical resolution.
2845         */
2846         q+=FormatMagickString(q,extent,"%g %s",image->y_resolution,
2847           MagickOptionToMnemonic(MagickResolutionOptions,(long) image->units));
2848         break;
2849       }
2850       case 'z':
2851       {
2852         /*
2853           Image depth.
2854         */
2855         q+=FormatMagickString(q,extent,"%lu",image->depth);
2856         break;
2857       }
2858       case 'A':
2859       {
2860         /*
2861           Image alpha channel.
2862         */
2863         q+=FormatMagickString(q,extent,"%s",MagickOptionToMnemonic(
2864           MagickBooleanOptions,(long) image->matte));
2865         break;
2866       }
2867       case 'C':
2868       {
2869         /*
2870           Image compression method.
2871         */
2872         q+=FormatMagickString(q,extent,"%s",MagickOptionToMnemonic(
2873           MagickCompressOptions,(long) image->compression));
2874         break;
2875       }
2876       case 'D':
2877       {
2878         /*
2879           Image dispose method.
2880         */
2881         q+=FormatMagickString(q,extent,"%s",MagickOptionToMnemonic(
2882           MagickDisposeOptions,(long) image->dispose));
2883         break;
2884       }
2885       case 'G':
2886       {
2887         q+=FormatMagickString(q,extent,"%lux%lu",image->magick_columns,
2888           image->magick_rows);
2889         break;
2890       }
2891       case 'H':
2892       {
2893         q+=FormatMagickString(q,extent,"%ld",image->page.height);
2894         break;
2895       }
2896       case 'O':
2897       {
2898         q+=FormatMagickString(q,extent,"%+ld%+ld",image->page.x,image->page.y);
2899         break;
2900       }
2901       case 'P':
2902       {
2903         q+=FormatMagickString(q,extent,"%lux%lu",image->page.width,
2904           image->page.height);
2905         break;
2906       }
2907       case 'Q':
2908       {
2909         q+=FormatMagickString(q,extent,"%lu",image->quality);
2910         break;
2911       }
2912       case 'S':
2913       {
2914         /*
2915           Image scenes.
2916         */
2917         if (text_info->number_scenes == 0)
2918           q+=CopyMagickString(q,"2147483647",extent);
2919         else
2920           q+=FormatMagickString(q,extent,"%lu",text_info->scene+
2921             text_info->number_scenes);
2922         break;
2923       }
2924       case 'T':
2925       {
2926         q+=FormatMagickString(q,extent,"%lu",image->delay);
2927         break;
2928       }
2929       case 'W':
2930       {
2931         q+=FormatMagickString(q,extent,"%ld",image->page.width);
2932         break;
2933       }
2934       case 'X':
2935       {
2936         q+=FormatMagickString(q,extent,"%+ld",image->page.x);
2937         break;
2938       }
2939       case 'Y':
2940       {
2941         q+=FormatMagickString(q,extent,"%+ld",image->page.y);
2942         break;
2943       }
2944       case 'Z':
2945       {
2946         /*
2947           Unique filename.
2948         */
2949         (void) CopyMagickString(filename,text_info->zero,extent);
2950         q+=CopyMagickString(q,filename,extent);
2951         break;
2952       }
2953       case '[':
2954       {
2955         char
2956           pattern[MaxTextExtent];
2957
2958         const char
2959           *key,
2960           *value;
2961
2962         long
2963           depth;
2964
2965         /*
2966           Image value.
2967         */
2968         if (strchr(p,']') == (char *) NULL)
2969           break;
2970         depth=1;
2971         p++;
2972         for (i=0; (i < (MaxTextExtent-1L)) && (*p != '\0'); i++)
2973         {
2974           if (*p == '[')
2975             depth++;
2976           if (*p == ']')
2977             depth--;
2978           if (depth <= 0)
2979             break;
2980           pattern[i]=(*p++);
2981         }
2982         pattern[i]='\0';
2983         value=GetImageProperty(image,pattern);
2984         if (value != (const char *) NULL)
2985           {
2986             length=strlen(value);
2987             if ((size_t) (q-interpret_text+length+1) >= extent)
2988               {
2989                 extent+=length;
2990                 interpret_text=(char *) ResizeQuantumMemory(interpret_text,
2991                   extent+MaxTextExtent,sizeof(*interpret_text));
2992                 if (interpret_text == (char *) NULL)
2993                   break;
2994                 q=interpret_text+strlen(interpret_text);
2995               }
2996             (void) CopyMagickString(q,value,extent);
2997             q+=length;
2998             break;
2999           }
3000         else
3001           if (IsGlob(pattern) != MagickFalse)
3002             {
3003               /*
3004                 Iterate over image properties.
3005               */
3006               ResetImagePropertyIterator(image);
3007               key=GetNextImageProperty(image);
3008               while (key != (const char *) NULL)
3009               {
3010                 if (GlobExpression(key,pattern,MagickTrue) != MagickFalse)
3011                   {
3012                     value=GetImageProperty(image,key);
3013                     if (value != (const char *) NULL)
3014                       {
3015                         length=strlen(key)+strlen(value)+2;
3016                         if ((size_t) (q-interpret_text+length+1) >= extent)
3017                           {
3018                             extent+=length;
3019                             interpret_text=(char *) ResizeQuantumMemory(
3020                               interpret_text,extent+MaxTextExtent,
3021                               sizeof(*interpret_text));
3022                             if (interpret_text == (char *) NULL)
3023                               break;
3024                             q=interpret_text+strlen(interpret_text);
3025                           }
3026                         q+=FormatMagickString(q,extent,"%s=%s\n",key,value);
3027                       }
3028                   }
3029                 key=GetNextImageProperty(image);
3030               }
3031             }
3032         value=GetMagickProperty(text_info,image,pattern);
3033         if (value != (const char *) NULL)
3034           {
3035             length=strlen(value);
3036             if ((size_t) (q-interpret_text+length+1) >= extent)
3037               {
3038                 extent+=length;
3039                 interpret_text=(char *) ResizeQuantumMemory(interpret_text,
3040                   extent+MaxTextExtent,sizeof(*interpret_text));
3041                 if (interpret_text == (char *) NULL)
3042                   break;
3043                 q=interpret_text+strlen(interpret_text);
3044               }
3045             (void) CopyMagickString(q,value,extent);
3046             q+=length;
3047           }
3048         if (image_info == (ImageInfo *) NULL)
3049           break;
3050         value=GetImageOption(image_info,pattern);
3051         if (value != (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         break;
3067       }
3068       case '@':
3069       {
3070         RectangleInfo
3071           page;
3072
3073         /*
3074           Image bounding box.
3075         */
3076         page=GetImageBoundingBox(image,&image->exception);
3077         q+=FormatMagickString(q,MaxTextExtent,"%lux%lu%+ld%+ld",page.width,
3078           page.height,page.x,page.y);
3079         break;
3080       }
3081       case '#':
3082       {
3083         /*
3084           Image signature.
3085         */
3086         (void) SignatureImage(image);
3087         value=GetImageProperty(image,"signature");
3088         if (value == (const char *) NULL)
3089           break;
3090         q+=CopyMagickString(q,value,extent);
3091         break;
3092       }
3093       case '%':
3094       {
3095         *q++=(*p);
3096         break;
3097       }
3098       default:
3099       {
3100         *q++='%';
3101         *q++=(*p);
3102         break;
3103       }
3104     }
3105   }
3106   *q='\0';
3107   text_info=DestroyImageInfo(text_info);
3108   if (text != (const char *) embed_text)
3109     text=DestroyString(text);
3110   (void) SubstituteString(&interpret_text,"&lt;","<");
3111   (void) SubstituteString(&interpret_text,"&gt;",">");
3112   return(interpret_text);
3113 }
3114 \f
3115 /*
3116 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3117 %                                                                             %
3118 %                                                                             %
3119 %                                                                             %
3120 %   R e m o v e I m a g e P r o p e r t y                                     %
3121 %                                                                             %
3122 %                                                                             %
3123 %                                                                             %
3124 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3125 %
3126 %  RemoveImageProperty() removes a property from the image and returns its
3127 %  value.
3128 %
3129 %  The format of the RemoveImageProperty method is:
3130 %
3131 %      char *RemoveImageProperty(Image *image,const char *property)
3132 %
3133 %  A description of each parameter follows:
3134 %
3135 %    o image: the image.
3136 %
3137 %    o property: the image property.
3138 %
3139 */
3140 MagickExport char *RemoveImageProperty(Image *image,
3141   const char *property)
3142 {
3143   char
3144     *value;
3145
3146   assert(image != (Image *) NULL);
3147   assert(image->signature == MagickSignature);
3148   if (image->debug != MagickFalse)
3149     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
3150       image->filename);
3151   if (image->properties == (void *) NULL)
3152     return((char *) NULL);
3153   value=(char *) RemoveNodeFromSplayTree((SplayTreeInfo *) image->properties,
3154     property);
3155   return(value);
3156 }
3157 \f
3158 /*
3159 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3160 %                                                                             %
3161 %                                                                             %
3162 %                                                                             %
3163 %   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                       %
3164 %                                                                             %
3165 %                                                                             %
3166 %                                                                             %
3167 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3168 %
3169 %  ResetImagePropertyIterator() resets the image properties iterator.  Use it
3170 %  in conjunction with GetNextImageProperty() to iterate over all the values
3171 %  associated with an image property.
3172 %
3173 %  The format of the ResetImagePropertyIterator method is:
3174 %
3175 %      ResetImagePropertyIterator(Image *image)
3176 %
3177 %  A description of each parameter follows:
3178 %
3179 %    o image: the image.
3180 %
3181 */
3182 MagickExport void ResetImagePropertyIterator(const Image *image)
3183 {
3184   assert(image != (Image *) NULL);
3185   assert(image->signature == MagickSignature);
3186   if (image->debug != MagickFalse)
3187     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
3188       image->filename);
3189   if (image->properties == (void *) NULL)
3190     return;
3191   ResetSplayTreeIterator((SplayTreeInfo *) image->properties);
3192 }
3193 \f
3194 /*
3195 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3196 %                                                                             %
3197 %                                                                             %
3198 %                                                                             %
3199 %   S e t I m a g e P r o p e r t y                                           %
3200 %                                                                             %
3201 %                                                                             %
3202 %                                                                             %
3203 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3204 %
3205 %  SetImageProperty() associates an value with an image property.
3206 %
3207 %  The format of the SetImageProperty method is:
3208 %
3209 %      MagickBooleanType SetImageProperty(Image *image,const char *property,
3210 %        const char *value)
3211 %
3212 %  A description of each parameter follows:
3213 %
3214 %    o image: the image.
3215 %
3216 %    o property: the image property.
3217 %
3218 %    o values: the image property values.
3219 %
3220 */
3221 MagickExport MagickBooleanType SetImageProperty(Image *image,
3222   const char *property,const char *value)
3223 {
3224   MagickBooleanType
3225     status;
3226
3227   MagickStatusType
3228     flags;
3229
3230   assert(image != (Image *) NULL);
3231   assert(image->signature == MagickSignature);
3232   if (image->debug != MagickFalse)
3233     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
3234       image->filename);
3235   if (image->properties == (void *) NULL)
3236     image->properties=NewSplayTree(CompareSplayTreeString,
3237       RelinquishMagickMemory,RelinquishMagickMemory);
3238   if ((value == (const char *) NULL) || (*value == '\0'))
3239     return(DeleteImageProperty(image,property));
3240   status=MagickTrue;
3241   switch (*property)
3242   {
3243     case 'B':
3244     case 'b':
3245     {
3246       if (LocaleCompare(property,"bias") == 0)
3247         {
3248           image->bias=StringToDouble(value,QuantumRange);
3249           break;
3250         }
3251       status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
3252         ConstantString(property),ConstantString(value));
3253       break;
3254     }
3255     case 'C':
3256     case 'c':
3257     {
3258       if (LocaleCompare(property,"colorspace") == 0)
3259         {
3260           long
3261             colorspace;
3262
3263           colorspace=ParseMagickOption(MagickColorspaceOptions,MagickFalse,
3264             value);
3265           if (colorspace < 0)
3266             break;
3267           (void) SetImageColorspace(image,(ColorspaceType) colorspace);
3268           break;
3269         }
3270       if (LocaleCompare(property,"compose") == 0)
3271         {
3272           long
3273             compose;
3274
3275           compose=ParseMagickOption(MagickComposeOptions,MagickFalse,value);
3276           if (compose < 0)
3277             break;
3278           image->compose=(CompositeOperator) compose;
3279           break;
3280         }
3281       if (LocaleCompare(property,"compress") == 0)
3282         {
3283           long
3284             compression;
3285
3286           compression=ParseMagickOption(MagickCompressOptions,MagickFalse,
3287             value);
3288           if (compression < 0)
3289             break;
3290           image->compression=(CompressionType) compression;
3291           break;
3292         }
3293       status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
3294         ConstantString(property),ConstantString(value));
3295       break;
3296     }
3297     case 'D':
3298     case 'd':
3299     {
3300       if (LocaleCompare(property,"delay") == 0)
3301         {
3302           GeometryInfo
3303             geometry_info;
3304
3305           flags=ParseGeometry(value,&geometry_info);
3306           if ((flags & GreaterValue) != 0)
3307             {
3308               if (image->delay > (unsigned long) (geometry_info.rho+0.5))
3309                 image->delay=(unsigned long) (geometry_info.rho+0.5);
3310             }
3311           else
3312             if ((flags & LessValue) != 0)
3313               {
3314                 if (image->delay < (unsigned long) (geometry_info.rho+0.5))
3315                   image->ticks_per_second=(long) (geometry_info.sigma+0.5);
3316               }
3317             else
3318               image->delay=(unsigned long) (geometry_info.rho+0.5);
3319           if ((flags & SigmaValue) != 0)
3320             image->ticks_per_second=(long) (geometry_info.sigma+0.5);
3321           break;
3322         }
3323       if (LocaleCompare(property,"depth") == 0)
3324         {
3325           image->depth=(unsigned long) atol(value);
3326           break;
3327         }
3328       if (LocaleCompare(property,"dispose") == 0)
3329         {
3330           long
3331             dispose;
3332
3333           dispose=ParseMagickOption(MagickDisposeOptions,MagickFalse,value);
3334           if (dispose < 0)
3335             break;
3336           image->dispose=(DisposeType) dispose;
3337           break;
3338         }
3339       status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
3340         ConstantString(property),ConstantString(value));
3341       break;
3342     }
3343     case 'G':
3344     case 'g':
3345     {
3346       if (LocaleCompare(property,"gravity") == 0)
3347         {
3348           long
3349             gravity;
3350
3351           gravity=ParseMagickOption(MagickGravityOptions,MagickFalse,value);
3352           if (gravity < 0)
3353             break;
3354           image->gravity=(GravityType) gravity;
3355           break;
3356         }
3357       status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
3358         ConstantString(property),ConstantString(value));
3359       break;
3360     }
3361     case 'I':
3362     case 'i':
3363     {
3364       if (LocaleCompare(property,"intent") == 0)
3365         {
3366           long
3367             rendering_intent;
3368
3369           rendering_intent=ParseMagickOption(MagickIntentOptions,MagickFalse,
3370             value);
3371           if (rendering_intent < 0)
3372             break;
3373           image->rendering_intent=(RenderingIntent) rendering_intent;
3374           break;
3375         }
3376       if (LocaleCompare(property,"interpolate") == 0)
3377         {
3378           long
3379             interpolate;
3380
3381           interpolate=ParseMagickOption(MagickInterpolateOptions,MagickFalse,
3382             value);
3383           if (interpolate < 0)
3384             break;
3385           image->interpolate=(InterpolatePixelMethod) interpolate;
3386           break;
3387         }
3388       status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
3389         ConstantString(property),ConstantString(value));
3390       break;
3391     }
3392     case 'L':
3393     case 'l':
3394     {
3395       if (LocaleCompare(property,"loop") == 0)
3396         {
3397           image->iterations=(unsigned long) atol(value);
3398           break;
3399         }
3400       status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
3401         ConstantString(property),ConstantString(value));
3402       break;
3403     }
3404     case 'P':
3405     case 'p':
3406     {
3407       if (LocaleCompare(property,"page") == 0)
3408         {
3409           char
3410             *geometry;
3411
3412           geometry=GetPageGeometry(value);
3413           flags=ParseAbsoluteGeometry(geometry,&image->page);
3414           geometry=DestroyString(geometry);
3415           break;
3416         }
3417       status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
3418         ConstantString(property),ConstantString(value));
3419       break;
3420     }
3421     case 'R':
3422     case 'r':
3423     {
3424       if (LocaleCompare(property,"rendering-intent") == 0)
3425         {
3426           long
3427             rendering_intent;
3428
3429           rendering_intent=ParseMagickOption(MagickIntentOptions,MagickFalse,
3430             value);
3431           if (rendering_intent < 0)
3432             break;
3433           image->rendering_intent=(RenderingIntent) rendering_intent;
3434           break;
3435         }
3436       status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
3437         ConstantString(property),ConstantString(value));
3438       break;
3439     }
3440     case 'T':
3441     case 't':
3442     {
3443       if (LocaleCompare(property,"tile-offset") == 0)
3444         {
3445           char
3446             *geometry;
3447
3448           geometry=GetPageGeometry(value);
3449           flags=ParseAbsoluteGeometry(geometry,&image->tile_offset);
3450           geometry=DestroyString(geometry);
3451           break;
3452         }
3453       status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
3454         ConstantString(property),ConstantString(value));
3455       break;
3456     }
3457     default:
3458     {
3459       status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
3460         ConstantString(property),ConstantString(value));
3461       break;
3462     }
3463   }
3464   return(status);
3465 }