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