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