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