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