source: ImageMagick/branches/ImageMagick-6/magick/property.c @ 7311

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