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