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