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