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