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