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