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