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