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