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