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