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