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