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