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