source: ImageMagick/branches/ImageMagick-6.7.5/magick/property.c @ 6676

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