MagickCore  6.9.12-67
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  if (*value == '\0')
2747  string="";
2748  break;
2749  }
2750  case 'u':
2751  {
2752  /*
2753  Unique filename.
2754  */
2755  WarnNoImageInfoReturn("\"%%%c\"",letter);
2756  string=image_info->unique;
2757  break;
2758  }
2759  case 'w':
2760  {
2761  /*
2762  Image width (current).
2763  */
2764  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2765  (image->columns != 0 ? image->columns : image->magick_columns));
2766  break;
2767  }
2768  case 'x':
2769  {
2770  /*
2771  Image horizontal resolution.
2772  */
2773  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",
2774  fabs(image->x_resolution) > MagickEpsilon ? image->x_resolution :
2775  image->units == PixelsPerCentimeterResolution ? DefaultResolution/2.54 :
2776  DefaultResolution);
2777  break;
2778  }
2779  case 'y':
2780  {
2781  /*
2782  Image vertical resolution.
2783  */
2784  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",
2785  fabs(image->y_resolution) > MagickEpsilon ? image->y_resolution :
2786  image->units == PixelsPerCentimeterResolution ? DefaultResolution/2.54 :
2787  DefaultResolution);
2788  break;
2789  }
2790  case 'z':
2791  {
2792  /*
2793  Image depth.
2794  */
2795  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2796  image->depth);
2797  break;
2798  }
2799  case 'A':
2800  {
2801  /*
2802  Image alpha channel.
2803  */
2804  (void) FormatLocaleString(value,MaxTextExtent,"%s",
2805  CommandOptionToMnemonic(MagickBooleanOptions,(ssize_t) image->matte));
2806  break;
2807  }
2808  case 'B':
2809  {
2810  /*
2811  Image size read in - in bytes.
2812  */
2813  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2814  image->extent);
2815  if (image->extent == 0)
2816  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2817  GetBlobSize(image));
2818  break;
2819  }
2820  case 'C':
2821  {
2822  /*
2823  Image compression method.
2824  */
2825  (void) FormatLocaleString(value,MaxTextExtent,"%s",
2826  CommandOptionToMnemonic(MagickCompressOptions,(ssize_t)
2827  image->compression));
2828  break;
2829  }
2830  case 'D':
2831  {
2832  /*
2833  Image dispose method.
2834  */
2835  (void) FormatLocaleString(value,MaxTextExtent,"%s",
2836  CommandOptionToMnemonic(MagickDisposeOptions,(ssize_t) image->dispose));
2837  break;
2838  }
2839  case 'F':
2840  {
2841  const char
2842  *q;
2843 
2844  char
2845  *p;
2846 
2847  static const char
2848  allowlist[] =
2849  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 "
2850  "$-_.+!*'(),{}|\\^~[]`\"><#%;/?:@&=";
2851 
2852  /*
2853  Magick filename (sanitized) - filename given incl. coder & read mods.
2854  */
2855  (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
2856  p=value;
2857  q=value+strlen(value);
2858  for (p+=strspn(p,allowlist); p != q; p+=strspn(p,allowlist))
2859  *p='_';
2860  break;
2861  }
2862  case 'G':
2863  {
2864  /*
2865  Image size as geometry = "%wx%h".
2866  */
2867  (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g",(double)
2868  image->magick_columns,(double) image->magick_rows);
2869  break;
2870  }
2871  case 'H':
2872  {
2873  /*
2874  Layer canvas height.
2875  */
2876  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2877  image->page.height);
2878  break;
2879  }
2880  case 'M':
2881  {
2882  /*
2883  Magick filename - filename given incl. coder & read mods.
2884  */
2885  string=image->magick_filename;
2886  break;
2887  }
2888  case 'N': /* Number of images in the list. */
2889  {
2890  if ((image != (Image *) NULL) && (image->next == (Image *) NULL))
2891  (void) FormatLocaleString(value,MagickPathExtent,"%.20g\n",(double)
2892  GetImageListLength(image));
2893  else
2894  string="";
2895  break;
2896  }
2897  case 'O':
2898  {
2899  /*
2900  Layer canvas offset with sign = "+%X+%Y".
2901  */
2902  (void) FormatLocaleString(value,MaxTextExtent,"%+ld%+ld",(long)
2903  image->page.x,(long) image->page.y);
2904  break;
2905  }
2906  case 'P':
2907  {
2908  /*
2909  Layer canvas page size = "%Wx%H".
2910  */
2911  (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g",(double)
2912  image->page.width,(double) image->page.height);
2913  break;
2914  }
2915  case 'Q':
2916  {
2917  /*
2918  Image compression quality.
2919  */
2920  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2921  (image->quality == 0 ? 92 : image->quality));
2922  break;
2923  }
2924  case 'S':
2925  {
2926  /*
2927  Image scenes.
2928  */
2929  WarnNoImageInfoReturn("\"%%%c\"",letter);
2930  if (image_info->number_scenes == 0)
2931  string="2147483647";
2932  else
2933  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2934  image_info->scene+image_info->number_scenes);
2935  break;
2936  }
2937  case 'T':
2938  {
2939  /*
2940  Image time delay for animations.
2941  */
2942  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2943  image->delay);
2944  break;
2945  }
2946  case 'U':
2947  {
2948  /*
2949  Image resolution units.
2950  */
2951  (void) FormatLocaleString(value,MaxTextExtent,"%s",
2952  CommandOptionToMnemonic(MagickResolutionOptions,(ssize_t)
2953  image->units));
2954  break;
2955  }
2956  case 'W':
2957  {
2958  /*
2959  Layer canvas width.
2960  */
2961  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2962  image->page.width);
2963  break;
2964  }
2965  case 'X':
2966  {
2967  /*
2968  Layer canvas X offset.
2969  */
2970  (void) FormatLocaleString(value,MaxTextExtent,"%+.20g",(double)
2971  image->page.x);
2972  break;
2973  }
2974  case 'Y':
2975  {
2976  /*
2977  Layer canvas Y offset.
2978  */
2979  (void) FormatLocaleString(value,MaxTextExtent,"%+.20g",(double)
2980  image->page.y);
2981  break;
2982  }
2983  case 'Z':
2984  {
2985  /*
2986  Zero filename.
2987  */
2988  WarnNoImageInfoReturn("\"%%%c\"",letter);
2989  string=image_info->zero;
2990  break;
2991  }
2992  case '@':
2993  {
2995  page;
2996 
2997  /*
2998  Image bounding box.
2999  */
3000  page=GetImageBoundingBox(image,&image->exception);
3001  (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g%+.20g%+.20g",
3002  (double) page.width,(double) page.height,(double) page.x,(double)
3003  page.y);
3004  break;
3005  }
3006  case '#':
3007  {
3008  /*
3009  Image signature.
3010  */
3011  if ((image->columns != 0) && (image->rows != 0))
3012  (void) SignatureImage(image);
3013  string=GetImageProperty(image,"signature");
3014  break;
3015  }
3016  case '%':
3017  {
3018  /*
3019  Percent escaped.
3020  */
3021  string="%";
3022  break;
3023  }
3024  }
3025  if (*value != '\0')
3026  string=value;
3027  if (string != (char *) NULL)
3028  {
3029  (void) SetImageArtifact(image,"get-property",string);
3030  return(GetImageArtifact(image,"get-property"));
3031  }
3032  return((char *) NULL);
3033 }
3034 
3035 MagickExport const char *GetMagickProperty(const ImageInfo *image_info,
3036  Image *image,const char *property)
3037 {
3038  char
3039  value[MaxTextExtent];
3040 
3041  const char
3042  *string;
3043 
3044  assert(property[0] != '\0');
3045  if (property[1] == '\0') /* single letter property request */
3046  return(GetMagickPropertyLetter(image_info,image,*property));
3047  *value='\0'; /* formatted string */
3048  string=(char *) NULL; /* constant string reference */
3049  switch (*property)
3050  {
3051  case 'b':
3052  {
3053  if ((LocaleCompare("base",property) == 0) ||
3054  (LocaleCompare("basename",property) == 0) )
3055  {
3056  GetPathComponent(image->magick_filename,BasePath,value);
3057  break;
3058  }
3059  if (LocaleCompare("bit-depth",property) == 0)
3060  {
3061  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3062  GetImageDepth(image,&image->exception));
3063  break;
3064  }
3065  if (LocaleCompare("bounding-box",property) == 0)
3066  {
3068  geometry;
3069 
3070  geometry=GetImageBoundingBox(image,&image->exception);
3071  (void) FormatLocaleString(value,MagickPathExtent,"%g,%g %g,%g\n",
3072  (double) geometry.x,(double) geometry.y,
3073  (double) geometry.x+geometry.width,
3074  (double) geometry.y+geometry.height);
3075  break;
3076  }
3077  break;
3078  }
3079  case 'c':
3080  {
3081  if (LocaleCompare("channels",property) == 0)
3082  {
3083  /*
3084  Image channels.
3085  */
3086  (void) FormatLocaleString(value,MaxTextExtent,"%s",
3087  CommandOptionToMnemonic(MagickColorspaceOptions,(ssize_t)
3088  image->colorspace));
3089  LocaleLower(value);
3090  if (image->matte != MagickFalse)
3091  (void) ConcatenateMagickString(value,"a",MaxTextExtent);
3092  break;
3093  }
3094  if (LocaleCompare("colors",property) == 0)
3095  {
3096  image->colors=GetNumberColors(image,(FILE *) NULL,&image->exception);
3097  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3098  image->colors);
3099  break;
3100  }
3101  if (LocaleCompare("colorspace",property) == 0)
3102  {
3103  string=CommandOptionToMnemonic(MagickColorspaceOptions,(ssize_t)
3104  image->colorspace);
3105  break;
3106  }
3107  if (LocaleCompare("compose",property) == 0)
3108  {
3109  string=CommandOptionToMnemonic(MagickComposeOptions,(ssize_t)
3110  image->compose);
3111  break;
3112  }
3113  if (LocaleCompare("compression",property) == 0)
3114  {
3115  string=CommandOptionToMnemonic(MagickCompressOptions,(ssize_t)
3116  image->compression);
3117  break;
3118  }
3119  if (LocaleCompare("copyright",property) == 0)
3120  {
3121  (void) CopyMagickString(value,GetMagickCopyright(),MaxTextExtent);
3122  break;
3123  }
3124  break;
3125  }
3126  case 'd':
3127  {
3128  if (LocaleCompare("depth",property) == 0)
3129  {
3130  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3131  image->depth);
3132  break;
3133  }
3134  if (LocaleCompare("directory",property) == 0)
3135  {
3136  GetPathComponent(image->magick_filename,HeadPath,value);
3137  break;
3138  }
3139  break;
3140  }
3141  case 'e':
3142  {
3143  if (LocaleCompare("entropy",property) == 0)
3144  {
3145  double
3146  entropy;
3147 
3148  (void) GetImageChannelEntropy(image,image_info->channel,&entropy,
3149  &image->exception);
3150  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3151  GetMagickPrecision(),entropy);
3152  break;
3153  }
3154  if (LocaleCompare("extension",property) == 0)
3155  {
3156  GetPathComponent(image->magick_filename,ExtensionPath,value);
3157  break;
3158  }
3159  break;
3160  }
3161  case 'g':
3162  {
3163  if (LocaleCompare("gamma",property) == 0)
3164  {
3165  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3166  GetMagickPrecision(),image->gamma);
3167  break;
3168  }
3169  if ((image_info != (ImageInfo *) NULL) &&
3170  (LocaleCompare("group",property) == 0))
3171  {
3172  (void) FormatLocaleString(value,MaxTextExtent,"0x%lx",(unsigned long)
3173  image_info->group);
3174  break;
3175  }
3176  break;
3177  }
3178  case 'h':
3179  {
3180  if (LocaleCompare("height",property) == 0)
3181  {
3182  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",
3183  image->magick_rows != 0 ? (double) image->magick_rows : 256.0);
3184  break;
3185  }
3186  break;
3187  }
3188  case 'i':
3189  {
3190  if (LocaleCompare("input",property) == 0)
3191  {
3192  string=image->filename;
3193  break;
3194  }
3195  if (LocaleCompare("interlace",property) == 0)
3196  {
3197  string=CommandOptionToMnemonic(MagickInterlaceOptions,(ssize_t)
3198  image->interlace);
3199  break;
3200  }
3201  break;
3202  }
3203  case 'k':
3204  {
3205  if (LocaleCompare("kurtosis",property) == 0)
3206  {
3207  double
3208  kurtosis,
3209  skewness;
3210 
3211  (void) GetImageChannelKurtosis(image,image_info->channel,&kurtosis,
3212  &skewness,&image->exception);
3213  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3214  GetMagickPrecision(),kurtosis);
3215  break;
3216  }
3217  break;
3218  }
3219  case 'm':
3220  {
3221  if (LocaleCompare("magick",property) == 0)
3222  {
3223  string=image->magick;
3224  break;
3225  }
3226  if ((LocaleCompare("max",property) == 0) ||
3227  (LocaleCompare("maxima",property) == 0))
3228  {
3229  double
3230  maximum,
3231  minimum;
3232 
3233  (void) GetImageChannelRange(image,image_info->channel,&minimum,
3234  &maximum,&image->exception);
3235  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3236  GetMagickPrecision(),maximum);
3237  break;
3238  }
3239  if (LocaleCompare("mean",property) == 0)
3240  {
3241  double
3242  mean,
3243  standard_deviation;
3244 
3245  (void) GetImageChannelMean(image,image_info->channel,&mean,
3246  &standard_deviation,&image->exception);
3247  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3248  GetMagickPrecision(),mean);
3249  break;
3250  }
3251  if ((LocaleCompare("min",property) == 0) ||
3252  (LocaleCompare("minima",property) == 0))
3253  {
3254  double
3255  maximum,
3256  minimum;
3257 
3258  (void) GetImageChannelRange(image,image_info->channel,&minimum,
3259  &maximum,&image->exception);
3260  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3261  GetMagickPrecision(),minimum);
3262  break;
3263  }
3264  break;
3265  }
3266  case 'o':
3267  {
3268  if (LocaleCompare("opaque",property) == 0)
3269  {
3270  MagickBooleanType
3271  opaque;
3272 
3273  opaque=IsOpaqueImage(image,&image->exception);
3274  (void) CopyMagickString(value,opaque != MagickFalse ? "true" :
3275  "false",MaxTextExtent);
3276  break;
3277  }
3278  if (LocaleCompare("orientation",property) == 0)
3279  {
3280  string=CommandOptionToMnemonic(MagickOrientationOptions,(ssize_t)
3281  image->orientation);
3282  break;
3283  }
3284  if ((image_info != (ImageInfo *) NULL) &&
3285  (LocaleCompare("output",property) == 0))
3286  {
3287  (void) CopyMagickString(value,image_info->filename,MaxTextExtent);
3288  break;
3289  }
3290  break;
3291  }
3292  case 'p':
3293  {
3294  if (LocaleCompare("page",property) == 0)
3295  {
3296  (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g",(double)
3297  image->page.width,(double) image->page.height);
3298  break;
3299  }
3300  if (LocaleNCompare("papersize:",property,10) == 0)
3301  {
3302  char
3303  *papersize;
3304 
3305  *value='\0';
3306  papersize=GetPageGeometry(property+10);
3307  if (papersize != (const char *) NULL)
3308  {
3310  page = { 0, 0, 0, 0 };
3311 
3312  (void) ParseAbsoluteGeometry(papersize,&page);
3313  (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g",
3314  (double) page.width,(double) page.height);
3315  papersize=DestroyString(papersize);
3316  }
3317  break;
3318  }
3319 #if defined(MAGICKCORE_LCMS_DELEGATE)
3320  if (LocaleCompare("profile:icc",property) == 0 ||
3321  LocaleCompare("profile:icm",property) == 0)
3322  {
3323 #if !defined(LCMS_VERSION) || (LCMS_VERSION < 2000)
3324 #define cmsUInt32Number DWORD
3325 #endif
3326 
3327  const StringInfo
3328  *profile;
3329 
3330  cmsHPROFILE
3331  icc_profile;
3332 
3333  profile=GetImageProfile(image,property+8);
3334  if (profile == (StringInfo *) NULL)
3335  break;
3336 
3337  icc_profile=cmsOpenProfileFromMem(GetStringInfoDatum(profile),
3338  (cmsUInt32Number) GetStringInfoLength(profile));
3339  if (icc_profile != (cmsHPROFILE *) NULL)
3340  {
3341 #if defined(LCMS_VERSION) && (LCMS_VERSION < 2000)
3342  string=cmsTakeProductName(icc_profile);
3343 #else
3344  (void) cmsGetProfileInfoASCII(icc_profile,cmsInfoDescription,
3345  "en","US",value,MaxTextExtent);
3346 #endif
3347  (void) cmsCloseProfile(icc_profile);
3348  }
3349  }
3350 #endif
3351  if (LocaleCompare("printsize.x",property) == 0)
3352  {
3353  (void) FormatLocaleString(value,MagickPathExtent,"%.*g",
3354  GetMagickPrecision(),PerceptibleReciprocal(image->x_resolution)*
3355  image->columns);
3356  break;
3357  }
3358  if (LocaleCompare("printsize.y",property) == 0)
3359  {
3360  (void) FormatLocaleString(value,MagickPathExtent,"%.*g",
3361  GetMagickPrecision(),PerceptibleReciprocal(image->y_resolution)*
3362  image->rows);
3363  break;
3364  }
3365  if (LocaleCompare("profiles",property) == 0)
3366  {
3367  const char
3368  *name;
3369 
3370  ResetImageProfileIterator(image);
3371  name=GetNextImageProfile(image);
3372  if (name != (char *) NULL)
3373  {
3374  (void) CopyMagickString(value,name,MaxTextExtent);
3375  name=GetNextImageProfile(image);
3376  while (name != (char *) NULL)
3377  {
3378  ConcatenateMagickString(value,",",MaxTextExtent);
3379  ConcatenateMagickString(value,name,MaxTextExtent);
3380  name=GetNextImageProfile(image);
3381  }
3382  }
3383  break;
3384  }
3385  break;
3386  }
3387  case 'q':
3388  {
3389  if (LocaleCompare("quality",property) == 0)
3390  {
3391  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3392  image->quality);
3393  break;
3394  }
3395  break;
3396  }
3397  case 'r':
3398  {
3399  if (LocaleCompare("rendering-intent",property) == 0)
3400  {
3401  string=CommandOptionToMnemonic(MagickIntentOptions,(ssize_t)
3402  image->rendering_intent);
3403  break;
3404  }
3405  if (LocaleCompare("resolution.x",property) == 0)
3406  {
3407  (void) FormatLocaleString(value,MaxTextExtent,"%g",
3408  image->x_resolution);
3409  break;
3410  }
3411  if (LocaleCompare("resolution.y",property) == 0)
3412  {
3413  (void) FormatLocaleString(value,MaxTextExtent,"%g",
3414  image->y_resolution);
3415  break;
3416  }
3417  break;
3418  }
3419  case 's':
3420  {
3421  if (LocaleCompare("scene",property) == 0)
3422  {
3423  if ((image_info != (ImageInfo *) NULL) &&
3424  (image_info->number_scenes != 0))
3425  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3426  image_info->scene);
3427  else
3428  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3429  image->scene);
3430  break;
3431  }
3432  if (LocaleCompare("scenes",property) == 0)
3433  {
3434  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3435  GetImageListLength(image));
3436  break;
3437  }
3438  if (LocaleCompare("size",property) == 0)
3439  {
3440  (void) FormatMagickSize(GetBlobSize(image),MagickFalse,value);
3441  break;
3442  }
3443  if (LocaleCompare("skewness",property) == 0)
3444  {
3445  double
3446  kurtosis,
3447  skewness;
3448 
3449  (void) GetImageChannelKurtosis(image,image_info->channel,&kurtosis,
3450  &skewness,&image->exception);
3451  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3452  GetMagickPrecision(),skewness);
3453  break;
3454  }
3455  if ((LocaleCompare("standard-deviation",property) == 0) ||
3456  (LocaleCompare("standard_deviation",property) == 0))
3457  {
3458  double
3459  mean,
3460  standard_deviation;
3461 
3462  (void) GetImageChannelMean(image,image_info->channel,&mean,
3463  &standard_deviation,&image->exception);
3464  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3465  GetMagickPrecision(),standard_deviation);
3466  break;
3467  }
3468  break;
3469  }
3470  case 't':
3471  {
3472  if (LocaleCompare("type",property) == 0)
3473  {
3474  string=CommandOptionToMnemonic(MagickTypeOptions,(ssize_t)
3475  IdentifyImageType(image,&image->exception));
3476  break;
3477  }
3478  break;
3479  }
3480  case 'u':
3481  {
3482  if ((image_info != (ImageInfo *) NULL) &&
3483  (LocaleCompare("unique",property) == 0))
3484  {
3485  string=image_info->unique;
3486  break;
3487  }
3488  if (LocaleCompare("units",property) == 0)
3489  {
3490  /*
3491  Image resolution units.
3492  */
3493  string=CommandOptionToMnemonic(MagickResolutionOptions,(ssize_t)
3494  image->units);
3495  break;
3496  }
3497  break;
3498  }
3499  case 'v':
3500  {
3501  if (LocaleCompare("version",property) == 0)
3502  {
3503  string=GetMagickVersion((size_t *) NULL);
3504  break;
3505  }
3506  break;
3507  }
3508  case 'w':
3509  {
3510  if (LocaleCompare("width",property) == 0)
3511  {
3512  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3513  (image->magick_columns != 0 ? image->magick_columns : 256));
3514  break;
3515  }
3516  break;
3517  }
3518  case 'x': /* FUTURE: Obsolete X resolution */
3519  {
3520  if ((LocaleCompare("xresolution",property) == 0) ||
3521  (LocaleCompare("x-resolution",property) == 0) )
3522  {
3523  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",
3524  image->x_resolution);
3525  break;
3526  }
3527  break;
3528  }
3529  case 'y': /* FUTURE: Obsolete Y resolution */
3530  {
3531  if ((LocaleCompare("yresolution",property) == 0) ||
3532  (LocaleCompare("y-resolution",property) == 0) )
3533  {
3534  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",
3535  image->y_resolution);
3536  break;
3537  }
3538  break;
3539  }
3540  case 'z':
3541  {
3542  if ((image_info != (ImageInfo *) NULL) &&
3543  (LocaleCompare("zero",property) == 0))
3544  {
3545  string=image_info->zero;
3546  break;
3547  }
3548  break;
3549  }
3550  }
3551  if (*value != '\0')
3552  string=value;
3553  if (string != (char *) NULL)
3554  {
3555  (void) SetImageArtifact(image,"get-property", string);
3556  return(GetImageArtifact(image,"get-property"));
3557  }
3558  return((char *) NULL);
3559 }
3560 
3561 /*
3562 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3563 % %
3564 % %
3565 % %
3566 % G e t N e x t I m a g e P r o p e r t y %
3567 % %
3568 % %
3569 % %
3570 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3571 %
3572 % GetNextImageProperty() gets the next free-form string property name.
3573 %
3574 % The format of the GetNextImageProperty method is:
3575 %
3576 % char *GetNextImageProperty(const Image *image)
3577 %
3578 % A description of each parameter follows:
3579 %
3580 % o image: the image.
3581 %
3582 */
3583 MagickExport char *GetNextImageProperty(const Image *image)
3584 {
3585  assert(image != (Image *) NULL);
3586  assert(image->signature == MagickCoreSignature);
3587  if (IsEventLogging() != MagickFalse)
3588  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
3589  image->filename);
3590  if (image->properties == (void *) NULL)
3591  return((char *) NULL);
3592  return((char *) GetNextKeyInSplayTree((SplayTreeInfo *) image->properties));
3593 }
3594 
3595 /*
3596 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3597 % %
3598 % %
3599 % %
3600 % I n t e r p r e t I m a g e P r o p e r t i e s %
3601 % %
3602 % %
3603 % %
3604 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3605 %
3606 % InterpretImageProperties() replaces any embedded formatting characters with
3607 % the appropriate image property and returns the interpreted text.
3608 %
3609 % This searches for and replaces
3610 % \n \r \% replaced by newline, return, and percent resp.
3611 % &lt; &gt; &amp; replaced by '<', '>', '&' resp.
3612 % %% replaced by percent
3613 %
3614 % %x %[x] where 'x' is a single letter properity, case sensitive).
3615 % %[type:name] where 'type' a is special and known prefix.
3616 % %[name] where 'name' is a specifically known attribute, calculated
3617 % value, or a per-image property string name, or a per-image
3618 % 'artifact' (as generated from a global option).
3619 % It may contain ':' as long as the prefix is not special.
3620 %
3621 % Single letter % substitutions will only happen if the character before the
3622 % percent is NOT a number. But braced substitutions will always be performed.
3623 % This prevents the typical usage of percent in a interpreted geometry
3624 % argument from being substituted when the percent is a geometry flag.
3625 %
3626 % If 'glob-expresions' ('*' or '?' characters) is used for 'name' it may be
3627 % used as a search pattern to print multiple lines of "name=value\n" pairs of
3628 % the associacted set of properities.
3629 %
3630 % The returned string must be freed using DestoryString() by the caller.
3631 %
3632 % The format of the InterpretImageProperties method is:
3633 %
3634 % char *InterpretImageProperties(const ImageInfo *image_info,Image *image,
3635 % const char *embed_text)
3636 %
3637 % A description of each parameter follows:
3638 %
3639 % o image_info: the image info.
3640 %
3641 % o image: the image.
3642 %
3643 % o embed_text: the address of a character string containing the embedded
3644 % formatting characters.
3645 %
3646 */
3647 MagickExport char *InterpretImageProperties(const ImageInfo *image_info,
3648  Image *image,const char *embed_text)
3649 {
3650 #define ExtendInterpretText(string_length) \
3651 { \
3652  size_t length=(string_length); \
3653  if ((size_t) (q-interpret_text+length+1) >= extent) \
3654  { \
3655  extent+=length; \
3656  interpret_text=(char *) ResizeQuantumMemory(interpret_text,extent+ \
3657  MaxTextExtent,sizeof(*interpret_text)); \
3658  if (interpret_text == (char *) NULL) \
3659  { \
3660  if (property_info != image_info) \
3661  property_info=DestroyImageInfo(property_info); \
3662  return((char *) NULL); \
3663  } \
3664  q=interpret_text+strlen(interpret_text); \
3665  } \
3666 }
3667 
3668 #define AppendKeyValue2Text(key,value)\
3669 { \
3670  size_t length=strlen(key)+strlen(value)+2; \
3671  if ((size_t) (q-interpret_text+length+1) >= extent) \
3672  { \
3673  extent+=length; \
3674  interpret_text=(char *) ResizeQuantumMemory(interpret_text,extent+ \
3675  MaxTextExtent,sizeof(*interpret_text)); \
3676  if (interpret_text == (char *) NULL) \
3677  { \
3678  if (property_info != image_info) \
3679  property_info=DestroyImageInfo(property_info); \
3680  return((char *) NULL); \
3681  } \
3682  q=interpret_text+strlen(interpret_text); \
3683  } \
3684  q+=FormatLocaleString(q,extent,"%s=%s\n",(key),(value)); \
3685 }
3686 
3687 #define AppendString2Text(string) \
3688 { \
3689  size_t length=strlen((string)); \
3690  if ((size_t) (q-interpret_text+length+1) >= extent) \
3691  { \
3692  extent+=length; \
3693  interpret_text=(char *) ResizeQuantumMemory(interpret_text,extent+ \
3694  MaxTextExtent,sizeof(*interpret_text)); \
3695  if (interpret_text == (char *) NULL) \
3696  { \
3697  if (property_info != image_info) \
3698  property_info=DestroyImageInfo(property_info); \
3699  return((char *) NULL); \
3700  } \
3701  q=interpret_text+strlen(interpret_text); \
3702  } \
3703  (void) CopyMagickString(q,(string),extent); \
3704  q+=length; \
3705 }
3706 
3707  char
3708  *interpret_text;
3709 
3710  ImageInfo
3711  *property_info;
3712 
3713  char
3714  *q; /* current position in interpret_text */
3715 
3716  const char
3717  *p; /* position in embed_text string being expanded */
3718 
3719  size_t
3720  extent; /* allocated length of interpret_text */
3721 
3722  MagickBooleanType
3723  number;
3724 
3725  assert(image != (Image *) NULL);
3726  assert(image->signature == MagickCoreSignature);
3727  if (IsEventLogging() != MagickFalse)
3728  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3729  if (embed_text == (const char *) NULL)
3730  return(ConstantString(""));
3731  p=embed_text;
3732  while ((isspace((int) ((unsigned char) *p)) != 0) && (*p != '\0'))
3733  p++;
3734  if (*p == '\0')
3735  return(ConstantString(""));
3736  if ((*p == '@') && (IsPathAccessible(p+1) != MagickFalse))
3737  {
3738  /*
3739  Replace string from file.
3740  */
3741  if (IsRightsAuthorized(PathPolicyDomain,ReadPolicyRights,p) == MagickFalse)
3742  {
3743  errno=EPERM;
3744  (void) ThrowMagickException(&image->exception,GetMagickModule(),
3745  PolicyError,"NotAuthorized","`%s'",p);
3746  return(ConstantString(""));
3747  }
3748  interpret_text=FileToString(p+1,~0UL,&image->exception);
3749  if (interpret_text != (char *) NULL)
3750  return(interpret_text);
3751  }
3752  /*
3753  Translate any embedded format characters.
3754  */
3755  if (image_info != (ImageInfo *) NULL)
3756  property_info=(ImageInfo *) image_info;
3757  else
3758  property_info=CloneImageInfo(image_info);
3759  interpret_text=AcquireString(embed_text); /* new string with extra space */
3760  extent=MaxTextExtent; /* how many extra space */
3761  number=MagickFalse; /* is last char a number? */
3762  for (q=interpret_text; *p!='\0';
3763  number=(isdigit((int) ((unsigned char) *p))) ? MagickTrue : MagickFalse,p++)
3764  {
3765  /*
3766  Look for the various escapes, (and handle other specials).
3767  */
3768  *q='\0';
3769  ExtendInterpretText(MaxTextExtent);
3770  switch (*p)
3771  {
3772  case '\\':
3773  {
3774  switch (*(p+1))
3775  {
3776  case '\0':
3777  continue;
3778  case 'r': /* convert to RETURN */
3779  {
3780  *q++='\r';
3781  p++;
3782  continue;
3783  }
3784  case 'n': /* convert to NEWLINE */
3785  {
3786  *q++='\n';
3787  p++;
3788  continue;
3789  }
3790  case '\n': /* EOL removal UNIX,MacOSX */
3791  {
3792  p++;
3793  continue;
3794  }
3795  case '\r': /* EOL removal DOS,Windows */
3796  {
3797  p++;
3798  if (*p == '\n') /* return-newline EOL */
3799  p++;
3800  continue;
3801  }
3802  default:
3803  {
3804  p++;
3805  *q++=(*p);
3806  }
3807  }
3808  continue;
3809  }
3810  case '&':
3811  {
3812  if (LocaleNCompare("&lt;",p,4) == 0)
3813  {
3814  *q++='<';
3815  p+=3;
3816  }
3817  else
3818  if (LocaleNCompare("&gt;",p,4) == 0)
3819  {
3820  *q++='>';
3821  p+=3;
3822  }
3823  else
3824  if (LocaleNCompare("&amp;",p,5) == 0)
3825  {
3826  *q++='&';
3827  p+=4;
3828  }
3829  else
3830  *q++=(*p);
3831  continue;
3832  }
3833  case '%':
3834  break; /* continue to next set of handlers */
3835  default:
3836  {
3837  *q++=(*p); /* any thing else is 'as normal' */
3838  continue;
3839  }
3840  }
3841  p++; /* advance beyond the percent */
3842  /*
3843  Doubled percent - or percent at end of string.
3844  */
3845  if ((*p == '\0') || (*p == '\'') || (*p == '"'))
3846  p--;
3847  if (*p == '%')
3848  {
3849  *q++='%';
3850  continue;
3851  }
3852  /*
3853  Single letter escapes %c.
3854  */
3855  if (*p != '[')
3856  {
3857  const char
3858  *value;
3859 
3860  /* But only if not preceeded by a number! */
3861  if (number != MagickFalse)
3862  {
3863  *q++='%'; /* do NOT substitute the percent */
3864  p--; /* back up one */
3865  continue;
3866  }
3867  value=GetMagickPropertyLetter(property_info,image,*p);
3868  if (value != (char *) NULL)
3869  {
3870  AppendString2Text(value);
3871  continue;
3872  }
3873  (void) ThrowMagickException(&image->exception,GetMagickModule(),
3874  OptionWarning,"UnknownImageProperty","\"%%%c\"",*p);
3875  continue;
3876  }
3877  {
3878  char
3879  pattern[2*MaxTextExtent];
3880 
3881  const char
3882  *key,
3883  *value;
3884 
3885  ssize_t
3886  len;
3887 
3888  ssize_t
3889  depth;
3890 
3891  /*
3892  Braced Percent Escape %[...]
3893  */
3894  p++; /* advance p to just inside the opening brace */
3895  depth=1;
3896  if ( *p == ']' )
3897  {
3898  (void) ThrowMagickException(&image->exception,GetMagickModule(),
3899  OptionWarning,"UnknownImageProperty","\"%%[]\"");
3900  break;
3901  }
3902  for (len=0; len<(MaxTextExtent-1L) && (*p != '\0');)
3903  {
3904  if ((*p == '\\') && (*(p+1) != '\0'))
3905  {
3906  /*
3907  Skip escaped braces within braced pattern.
3908  */
3909  pattern[len++]=(*p++);
3910  pattern[len++]=(*p++);
3911  continue;
3912  }
3913  if (*p == '[')
3914  depth++;
3915  if (*p == ']')
3916  depth--;
3917  if (depth <= 0)
3918  break;
3919  pattern[len++]=(*p++);
3920  }
3921  pattern[len]='\0';
3922  if (depth != 0)
3923  {
3924  /*
3925  Check for unmatched final ']' for "%[...]".
3926  */
3927  if (len >= 64)
3928  {
3929  pattern[61] = '.'; /* truncate string for error message */
3930  pattern[62] = '.';
3931  pattern[63] = '.';
3932  pattern[64] = '\0';
3933  }
3934  (void) ThrowMagickException(&image->exception,GetMagickModule(),
3935  OptionError,"UnbalancedBraces","\"%%[%s\"",pattern);
3936  interpret_text=DestroyString(interpret_text);
3937  if (property_info != image_info)
3938  property_info=DestroyImageInfo(property_info);
3939  return((char *) NULL);
3940  }
3941  /*
3942  Special Lookup Prefixes %[prefix:...]
3943  */
3944  if (LocaleNCompare("fx:",pattern,3) == 0)
3945  {
3946  double
3947  value;
3948 
3949  FxInfo
3950  *fx_info;
3951 
3952  MagickBooleanType
3953  status;
3954 
3955  /*
3956  FX - value calculator.
3957  */
3958  fx_info=AcquireFxInfo(image,pattern+3);
3959  status=FxEvaluateChannelExpression(fx_info,property_info->channel,0,0,
3960  &value,&image->exception);
3961  fx_info=DestroyFxInfo(fx_info);
3962  if (status != MagickFalse)
3963  {
3964  char
3965  result[MagickPathExtent];
3966 
3967  (void) FormatLocaleString(result,MagickPathExtent,"%.*g",
3968  GetMagickPrecision(),(double) value);
3969  AppendString2Text(result);
3970  }
3971  continue;
3972  }
3973  if (LocaleNCompare("option:",pattern,7) == 0)
3974  {
3975  /*
3976  Option - direct global option lookup (with globbing).
3977  */
3978  if (IsGlob(pattern+7) != MagickFalse)
3979  {
3980  ResetImageOptionIterator(property_info);
3981  while ((key=GetNextImageOption(property_info)) != (const char *) NULL)
3982  if (GlobExpression(key,pattern+7,MagickTrue) != MagickFalse)
3983  {
3984  value=GetImageOption(property_info,key);
3985  if (value != (const char *) NULL)
3986  AppendKeyValue2Text(key,value);
3987  /* else - assertion failure? key but no value! */
3988  }
3989  continue;
3990  }
3991  value=GetImageOption(property_info,pattern+7);
3992  if (value != (char *) NULL)
3993  AppendString2Text(value);
3994  /* else - no global option of this specifc name */
3995  continue;
3996  }
3997  if (LocaleNCompare("artifact:",pattern,9) == 0)
3998  {
3999  /*
4000  Artifact - direct image artifact lookup (with glob).
4001  */
4002  if (IsGlob(pattern+9) != MagickFalse)
4003  {
4004  ResetImageArtifactIterator(image);
4005  while ((key=GetNextImageArtifact(image)) != (const char *) NULL)
4006  if (GlobExpression(key,pattern+9,MagickTrue) != MagickFalse)
4007  {
4008  value=GetImageArtifact(image,key);
4009  if (value != (const char *) NULL)
4010  AppendKeyValue2Text(key,value);
4011  /* else - assertion failure? key but no value! */
4012  }
4013  continue;
4014  }
4015  value=GetImageArtifact(image,pattern+9);
4016  if (value != (char *) NULL)
4017  AppendString2Text(value);
4018  /* else - no artifact of this specifc name */
4019  continue;
4020  }
4021  /*
4022  Handle special image properties, for example:
4023  %[exif:...] %[fx:...] %[pixel:...].
4024 
4025  FUTURE: handle %[property:...] prefix - abort other lookups.
4026  */
4027  value=GetImageProperty(image,pattern);
4028  if (value != (const char *) NULL)
4029  {
4030  AppendString2Text(value);
4031  continue;
4032  }
4033  /*
4034  Handle property 'glob' patterns such as:
4035  %[*] %[user:array_??] %[filename:e*]
4036  */
4037  if (IsGlob(pattern) != MagickFalse)
4038  {
4039  ResetImagePropertyIterator(image);
4040  while ((key=GetNextImageProperty(image)) != (const char *) NULL)
4041  if (GlobExpression(key,pattern,MagickTrue) != MagickFalse)
4042  {
4043  value=GetImageProperty(image,key);
4044  if (value != (const char *) NULL)
4045  AppendKeyValue2Text(key,value);
4046  /* else - assertion failure? */
4047  }
4048  continue;
4049  }
4050  /*
4051  Look for a known property or image attribute such as
4052  %[basename] %[denisty] %[delay]. Also handles a braced single
4053  letter: %[b] %[G] %[g].
4054  */
4055  value=GetMagickProperty(property_info,image,pattern);
4056  if (value != (const char *) NULL)
4057  {
4058  AppendString2Text(value);
4059  continue;
4060  }
4061  /*
4062  Look for a per-image Artifact (user option, post-interpreted)
4063  */
4064  value=GetImageArtifact(image,pattern);
4065  if (value != (char *) NULL)
4066  {
4067  AppendString2Text(value);
4068  continue;
4069  }
4070  /*
4071  Look for user option of this name (should never match in CLI usage).
4072  */
4073  value=GetImageOption(property_info,pattern);
4074  if (value != (char *) NULL)
4075  {
4076  AppendString2Text(value);
4077  continue;
4078  }
4079  /*
4080  Failed to find any match anywhere!
4081  */
4082  if (len >= 64)
4083  {
4084  pattern[61] = '.'; /* truncate string for error message */
4085  pattern[62] = '.';
4086  pattern[63] = '.';
4087  pattern[64] = '\0';
4088  }
4089  (void) ThrowMagickException(&image->exception,GetMagickModule(),
4090  OptionWarning,"UnknownImageProperty","\"%%[%s]\"",pattern);
4091  /* continue */
4092  } /* Braced Percent Escape */
4093  } /* for each char in 'embed_text' */
4094  *q='\0';
4095  if (property_info != image_info)
4096  property_info=DestroyImageInfo(property_info);
4097  return(interpret_text);
4098 }
4099 
4100 /*
4101 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4102 % %
4103 % %
4104 % %
4105 % R e m o v e I m a g e P r o p e r t y %
4106 % %
4107 % %
4108 % %
4109 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4110 %
4111 % RemoveImageProperty() removes a property from the image and returns its
4112 % value.
4113 %
4114 % In this case the ConstantString() value returned should be freed by the
4115 % caller when finished.
4116 %
4117 % The format of the RemoveImageProperty method is:
4118 %
4119 % char *RemoveImageProperty(Image *image,const char *property)
4120 %
4121 % A description of each parameter follows:
4122 %
4123 % o image: the image.
4124 %
4125 % o property: the image property.
4126 %
4127 */
4128 MagickExport char *RemoveImageProperty(Image *image,const char *property)
4129 {
4130  char
4131  *value;
4132 
4133  assert(image != (Image *) NULL);
4134  assert(image->signature == MagickCoreSignature);
4135  if (IsEventLogging() != MagickFalse)
4136  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
4137  image->filename);
4138  if (image->properties == (void *) NULL)
4139  return((char *) NULL);
4140  value=(char *) RemoveNodeFromSplayTree((SplayTreeInfo *) image->properties,
4141  property);
4142  return(value);
4143 }
4144 
4145 /*
4146 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4147 % %
4148 % %
4149 % %
4150 % 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 %
4151 % %
4152 % %
4153 % %
4154 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4155 %
4156 % ResetImagePropertyIterator() resets the image properties iterator. Use it
4157 % in conjunction with GetNextImageProperty() to iterate over all the values
4158 % associated with an image property.
4159 %
4160 % The format of the ResetImagePropertyIterator method is:
4161 %
4162 % ResetImagePropertyIterator(Image *image)
4163 %
4164 % A description of each parameter follows:
4165 %
4166 % o image: the image.
4167 %
4168 */
4169 MagickExport void ResetImagePropertyIterator(const Image *image)
4170 {
4171  assert(image != (Image *) NULL);
4172  assert(image->signature == MagickCoreSignature);
4173  if (IsEventLogging() != MagickFalse)
4174  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
4175  image->filename);
4176  if (image->properties == (void *) NULL)
4177  return;
4178  ResetSplayTreeIterator((SplayTreeInfo *) image->properties);
4179 }
4180 
4181 /*
4182 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4183 % %
4184 % %
4185 % %
4186 % S e t I m a g e P r o p e r t y %
4187 % %
4188 % %
4189 % %
4190 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4191 %
4192 % SetImageProperty() saves the given string value either to specific known
4193 % attribute or to a freeform property string.
4194 %
4195 % The format of the SetImageProperty method is:
4196 %
4197 % MagickBooleanType SetImageProperty(Image *image,const char *property,
4198 % const char *value)
4199 %
4200 % A description of each parameter follows:
4201 %
4202 % o image: the image.
4203 %
4204 % o property: the image property.
4205 %
4206 % o values: the image property values.
4207 %
4208 */
4209 MagickExport MagickBooleanType SetImageProperty(Image *image,
4210  const char *property,const char *value)
4211 {
4213  *exception;
4214 
4215  MagickBooleanType
4216  status;
4217 
4218  MagickStatusType
4219  flags;
4220 
4221  size_t
4222  property_length;
4223 
4224 
4225  assert(image != (Image *) NULL);
4226  assert(image->signature == MagickCoreSignature);
4227  if (IsEventLogging() != MagickFalse)
4228  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
4229  image->filename);
4230  if (image->properties == (void *) NULL)
4231  image->properties=NewSplayTree(CompareSplayTreeString,
4232  RelinquishMagickMemory,RelinquishMagickMemory); /* create splay-tree */
4233  if (value == (const char *) NULL)
4234  return(DeleteImageProperty(image,property)); /* delete if NULL */
4235  exception=(&image->exception);
4236  property_length=strlen(property);
4237  if ((property_length > 2) && (*(property+(property_length-2)) == ':') &&
4238  (*(property+(property_length-1)) == '*'))
4239  {
4240  (void) ThrowMagickException(exception,GetMagickModule(),
4241  OptionWarning,"SetReadOnlyProperty","`%s'",property);
4242  return(MagickFalse);
4243  }
4244  /*
4245  FUTURE: These should produce 'illegal settings'
4246  * binary chars in p[roperty key
4247  * first letter must be a alphabetic
4248  * single letter property keys (read only)
4249  * known special prefix (read only, they don't get saved!)
4250  */
4251  status=MagickTrue;
4252  switch (*property)
4253  {
4254  case 'B':
4255  case 'b':
4256  {
4257  if (LocaleCompare("background",property) == 0)
4258  {
4259  (void) QueryColorDatabase(value,&image->background_color,exception);
4260  break;
4261  }
4262  if (LocaleCompare("bias",property) == 0)
4263  {
4264  image->bias=StringToDoubleInterval(value,(double) QuantumRange+1.0);
4265  break;
4266  }
4267  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4268  ConstantString(property),ConstantString(value));
4269  break;
4270  }
4271  case 'C':
4272  case 'c':
4273  {
4274  if (LocaleCompare("colorspace",property) == 0)
4275  {
4276  ssize_t
4277  colorspace;
4278 
4279  colorspace=ParseCommandOption(MagickColorspaceOptions,MagickFalse,
4280  value);
4281  if (colorspace < 0)
4282  break;
4283  status=SetImageColorspace(image,(ColorspaceType) colorspace);
4284  break;
4285  }
4286  if (LocaleCompare("compose",property) == 0)
4287  {
4288  ssize_t
4289  compose;
4290 
4291  compose=ParseCommandOption(MagickComposeOptions,MagickFalse,value);
4292  if (compose < 0)
4293  break;
4294  image->compose=(CompositeOperator) compose;
4295  break;
4296  }
4297  if (LocaleCompare("compress",property) == 0)
4298  {
4299  ssize_t
4300  compression;
4301 
4302  compression=ParseCommandOption(MagickCompressOptions,MagickFalse,
4303  value);
4304  if (compression < 0)
4305  break;
4306  image->compression=(CompressionType) compression;
4307  break;
4308  }
4309  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4310  ConstantString(property),ConstantString(value));
4311  break;
4312  }
4313  case 'D':
4314  case 'd':
4315  {
4316  if (LocaleCompare("delay",property) == 0)
4317  {
4318  GeometryInfo
4319  geometry_info;
4320 
4321  flags=ParseGeometry(value,&geometry_info);
4322  if ((flags & GreaterValue) != 0)
4323  {
4324  if (image->delay > (size_t) floor(geometry_info.rho+0.5))
4325  image->delay=(size_t) floor(geometry_info.rho+0.5);
4326  }
4327  else
4328  if ((flags & LessValue) != 0)
4329  {
4330  if ((double) image->delay < floor(geometry_info.rho+0.5))
4331  image->ticks_per_second=CastDoubleToLong(
4332  floor(geometry_info.sigma+0.5));
4333  }
4334  else
4335  image->delay=(size_t) floor(geometry_info.rho+0.5);
4336  if ((flags & SigmaValue) != 0)
4337  image->ticks_per_second=CastDoubleToLong(floor(
4338  geometry_info.sigma+0.5));
4339  break;
4340  }
4341  if (LocaleCompare("density",property) == 0)
4342  {
4343  GeometryInfo
4344  geometry_info;
4345 
4346  flags=ParseGeometry(value,&geometry_info);
4347  if ((flags & RhoValue) != 0)
4348  image->x_resolution=geometry_info.rho;
4349  image->y_resolution=image->x_resolution;
4350  if ((flags & SigmaValue) != 0)
4351  image->y_resolution=geometry_info.sigma;
4352  }
4353  if (LocaleCompare("depth",property) == 0)
4354  {
4355  image->depth=StringToUnsignedLong(value);
4356  break;
4357  }
4358  if (LocaleCompare("dispose",property) == 0)
4359  {
4360  ssize_t
4361  dispose;
4362 
4363  dispose=ParseCommandOption(MagickDisposeOptions,MagickFalse,value);
4364  if (dispose < 0)
4365  break;
4366  image->dispose=(DisposeType) dispose;
4367  break;
4368  }
4369  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4370  ConstantString(property),ConstantString(value));
4371  break;
4372  }
4373  case 'G':
4374  case 'g':
4375  {
4376  if (LocaleCompare("gamma",property) == 0)
4377  {
4378  image->gamma=StringToDouble(value,(char **) NULL);
4379  break;
4380  }
4381  if (LocaleCompare("gravity",property) == 0)
4382  {
4383  ssize_t
4384  gravity;
4385 
4386  gravity=ParseCommandOption(MagickGravityOptions,MagickFalse,value);
4387  if (gravity < 0)
4388  break;
4389  image->gravity=(GravityType) gravity;
4390  break;
4391  }
4392  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4393  ConstantString(property),ConstantString(value));
4394  break;
4395  }
4396  case 'I':
4397  case 'i':
4398  {
4399  if (LocaleCompare("intensity",property) == 0)
4400  {
4401  ssize_t
4402  intensity;
4403 
4404  intensity=ParseCommandOption(MagickPixelIntensityOptions,MagickFalse,
4405  value);
4406  if (intensity < 0)
4407  break;
4408  image->intensity=(PixelIntensityMethod) intensity;
4409  break;
4410  }
4411  if (LocaleCompare("interpolate",property) == 0)
4412  {
4413  ssize_t
4414  interpolate;
4415 
4416  interpolate=ParseCommandOption(MagickInterpolateOptions,MagickFalse,
4417  value);
4418  if (interpolate < 0)
4419  break;
4420  image->interpolate=(InterpolatePixelMethod) interpolate;
4421  break;
4422  }
4423  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4424  ConstantString(property),ConstantString(value));
4425  break;
4426  }
4427  case 'L':
4428  case 'l':
4429  {
4430  if (LocaleCompare("loop",property) == 0)
4431  {
4432  image->iterations=StringToUnsignedLong(value);
4433  break;
4434  }
4435  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4436  ConstantString(property),ConstantString(value));
4437  break;
4438  }
4439  case 'P':
4440  case 'p':
4441  {
4442  if (LocaleCompare("page",property) == 0)
4443  {
4444  char
4445  *geometry;
4446 
4447  geometry=GetPageGeometry(value);
4448  flags=ParseAbsoluteGeometry(geometry,&image->page);
4449  geometry=DestroyString(geometry);
4450  break;
4451  }
4452  if (LocaleCompare("profile",property) == 0)
4453  {
4454  ImageInfo
4455  *image_info;
4456 
4457  StringInfo
4458  *profile = (StringInfo *) NULL;
4459 
4460  image_info=AcquireImageInfo();
4461  (void) CopyMagickString(image_info->filename,value,MaxTextExtent);
4462  (void) SetImageInfo(image_info,1,exception);
4463  if (LocaleCompare(image_info->filename,"-") != 0)
4464  profile=FileToStringInfo(image_info->filename,~0UL,exception);
4465  if (profile != (StringInfo *) NULL)
4466  {
4467  status=SetImageProfile(image,image_info->magick,profile);
4468  profile=DestroyStringInfo(profile);
4469  }
4470  image_info=DestroyImageInfo(image_info);
4471  break;
4472  }
4473  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4474  ConstantString(property),ConstantString(value));
4475  break;
4476  }
4477  case 'R':
4478  case 'r':
4479  {
4480  if (LocaleCompare("rendering-intent",property) == 0)
4481  {
4482  ssize_t
4483  rendering_intent;
4484 
4485  rendering_intent=ParseCommandOption(MagickIntentOptions,MagickFalse,
4486  value);
4487  if (rendering_intent < 0)
4488  break;
4489  image->rendering_intent=(RenderingIntent) rendering_intent;
4490  break;
4491  }
4492  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4493  ConstantString(property),ConstantString(value));
4494  break;
4495  }
4496  case 'T':
4497  case 't':
4498  {
4499  if (LocaleCompare("tile-offset",property) == 0)
4500  {
4501  char
4502  *geometry;
4503 
4504  geometry=GetPageGeometry(value);
4505  flags=ParseAbsoluteGeometry(geometry,&image->tile_offset);
4506  geometry=DestroyString(geometry);
4507  break;
4508  }
4509  if (LocaleCompare("type",property) == 0)
4510  {
4511  ssize_t
4512  type;
4513 
4514  type=ParseCommandOption(MagickTypeOptions,MagickFalse,value);
4515  if (type < 0)
4516  return(MagickFalse);
4517  image->type=(ImageType) type;
4518  break;
4519  }
4520  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4521  ConstantString(property),ConstantString(value));
4522  break;
4523  }
4524  case 'U':
4525  case 'u':
4526  {
4527  if (LocaleCompare("units",property) == 0)
4528  {
4529  ssize_t
4530  units;
4531 
4532  units=ParseCommandOption(MagickResolutionOptions,MagickFalse,value);
4533  if (units < 0)
4534  break;
4535  image->units=(ResolutionType) units;
4536  break;
4537  }
4538  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4539  ConstantString(property),ConstantString(value));
4540  break;
4541  }
4542  default:
4543  {
4544  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4545  ConstantString(property),ConstantString(value));
4546  break;
4547  }
4548  }
4549  return(status);
4550 }
Definition: image.h:152
Definition: fx.c:129