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