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