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