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

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