MagickCore  6.9.12-93
Convert, Edit, Or Compose Bitmap Images
property.c
1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 % %
4 % %
5 % %
6 % PPPP RRRR OOO PPPP EEEEE RRRR TTTTT Y Y %
7 % P P R R O O P P E R R T Y Y %
8 % PPPP RRRR O O PPPP EEE RRRR T Y %
9 % P R R O O P E R R T Y %
10 % P R R OOO P EEEEE R R T Y %
11 % %
12 % %
13 % MagickCore Property Methods %
14 % %
15 % Software Design %
16 % Cristy %
17 % March 2000 %
18 % %
19 % %
20 % Copyright 1999 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
22 % %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
25 % %
26 % https://imagemagick.org/script/license.php %
27 % %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
33 % %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35 %
36 %
37 %
38 */
39 
40 /*
41  Include declarations.
42 */
43 #include "magick/studio.h"
44 #include "magick/artifact.h"
45 #include "magick/attribute.h"
46 #include "magick/cache.h"
47 #include "magick/cache-private.h"
48 #include "magick/color.h"
49 #include "magick/colorspace-private.h"
50 #include "magick/compare.h"
51 #include "magick/constitute.h"
52 #include "magick/draw.h"
53 #include "magick/effect.h"
54 #include "magick/exception.h"
55 #include "magick/exception-private.h"
56 #include "magick/fx.h"
57 #include "magick/fx-private.h"
58 #include "magick/gem.h"
59 #include "magick/geometry.h"
60 #include "magick/histogram.h"
61 #include "magick/image.h"
62 #include "magick/image.h"
63 #include "magick/layer.h"
64 #include "magick/list.h"
65 #include "magick/magick.h"
66 #include "magick/memory_.h"
67 #include "magick/monitor.h"
68 #include "magick/montage.h"
69 #include "magick/option.h"
70 #include "magick/policy.h"
71 #include "magick/profile.h"
72 #include "magick/property.h"
73 #include "magick/quantum.h"
74 #include "magick/resource_.h"
75 #include "magick/splay-tree.h"
76 #include "magick/signature-private.h"
77 #include "magick/statistic.h"
78 #include "magick/string_.h"
79 #include "magick/string-private.h"
80 #include "magick/token.h"
81 #include "magick/utility.h"
82 #include "magick/version.h"
83 #include "magick/xml-tree.h"
84 #if defined(MAGICKCORE_LCMS_DELEGATE)
85 #if defined(MAGICKCORE_HAVE_LCMS2_LCMS2_H)
86 #include <lcms2/lcms2.h>
87 #elif defined(MAGICKCORE_HAVE_LCMS2_H)
88 #include "lcms2.h"
89 #elif defined(MAGICKCORE_HAVE_LCMS_LCMS_H)
90 #include <lcms/lcms.h>
91 #else
92 #include "lcms.h"
93 #endif
94 #endif
95 ␌
96 /*
97  Define declarations.
98 */
99 #if defined(MAGICKCORE_LCMS_DELEGATE)
100 #if defined(LCMS_VERSION) && (LCMS_VERSION < 2000)
101 #define cmsUInt32Number DWORD
102 #endif
103 #endif
104 ␌
105 /*
106 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
107 % %
108 % %
109 % %
110 % C l o n e I m a g e P r o p e r t i e s %
111 % %
112 % %
113 % %
114 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
115 %
116 % CloneImageProperties() clones all the image properties to another image.
117 %
118 % The format of the CloneImageProperties method is:
119 %
120 % MagickBooleanType CloneImageProperties(Image *image,
121 % const Image *clone_image)
122 %
123 % A description of each parameter follows:
124 %
125 % o image: the image.
126 %
127 % o clone_image: the clone image.
128 %
129 */
130 MagickExport MagickBooleanType CloneImageProperties(Image *image,
131  const Image *clone_image)
132 {
133  assert(image != (Image *) NULL);
134  assert(image->signature == MagickCoreSignature);
135  assert(clone_image != (const Image *) NULL);
136  assert(clone_image->signature == MagickCoreSignature);
137  if (IsEventLogging() != MagickFalse)
138  {
139  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
140  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
141  clone_image->filename);
142  }
143  (void) CopyMagickString(image->filename,clone_image->filename,MaxTextExtent);
144  (void) CopyMagickString(image->magick_filename,clone_image->magick_filename,
145  MaxTextExtent);
146  image->compression=clone_image->compression;
147  image->quality=clone_image->quality;
148  image->depth=clone_image->depth;
149  image->background_color=clone_image->background_color;
150  image->border_color=clone_image->border_color;
151  image->matte_color=clone_image->matte_color;
152  image->transparent_color=clone_image->transparent_color;
153  image->gamma=clone_image->gamma;
154  image->chromaticity=clone_image->chromaticity;
155  image->rendering_intent=clone_image->rendering_intent;
156  image->black_point_compensation=clone_image->black_point_compensation;
157  image->units=clone_image->units;
158  image->montage=(char *) NULL;
159  image->directory=(char *) NULL;
160  (void) CloneString(&image->geometry,clone_image->geometry);
161  image->offset=clone_image->offset;
162  image->x_resolution=clone_image->x_resolution;
163  image->y_resolution=clone_image->y_resolution;
164  image->page=clone_image->page;
165  image->tile_offset=clone_image->tile_offset;
166  image->extract_info=clone_image->extract_info;
167  image->bias=clone_image->bias;
168  image->filter=clone_image->filter;
169  image->blur=clone_image->blur;
170  image->fuzz=clone_image->fuzz;
171  image->intensity=clone_image->intensity;
172  image->interlace=clone_image->interlace;
173  image->interpolate=clone_image->interpolate;
174  image->endian=clone_image->endian;
175  image->gravity=clone_image->gravity;
176  image->compose=clone_image->compose;
177  image->orientation=clone_image->orientation;
178  image->scene=clone_image->scene;
179  image->dispose=clone_image->dispose;
180  image->delay=clone_image->delay;
181  image->ticks_per_second=clone_image->ticks_per_second;
182  image->iterations=clone_image->iterations;
183  image->total_colors=clone_image->total_colors;
184  image->taint=clone_image->taint;
185  image->progress_monitor=clone_image->progress_monitor;
186  image->client_data=clone_image->client_data;
187  image->start_loop=clone_image->start_loop;
188  image->error=clone_image->error;
189  image->signature=clone_image->signature;
190  if (clone_image->properties != (void *) NULL)
191  {
192  if (image->properties != (void *) NULL)
193  DestroyImageProperties(image);
194  image->properties=CloneSplayTree((SplayTreeInfo *)
195  clone_image->properties,(void *(*)(void *)) ConstantString,
196  (void *(*)(void *)) ConstantString);
197  }
198  return(MagickTrue);
199 }
200 ␌
201 /*
202 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
203 % %
204 % %
205 % %
206 % D e f i n e I m a g e P r o p e r t y %
207 % %
208 % %
209 % %
210 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
211 %
212 % DefineImageProperty() associates an assignment string of the form
213 % "key=value" with an artifact or options. It is equivalent to
214 % SetImageProperty().
215 %
216 % The format of the DefineImageProperty method is:
217 %
218 % MagickBooleanType DefineImageProperty(Image *image,
219 % const char *property)
220 %
221 % A description of each parameter follows:
222 %
223 % o image: the image.
224 %
225 % o property: the image property.
226 %
227 */
228 MagickExport MagickBooleanType DefineImageProperty(Image *image,
229  const char *property)
230 {
231  char
232  key[MaxTextExtent],
233  value[MaxTextExtent];
234 
235  char
236  *p;
237 
238  assert(image != (Image *) NULL);
239  assert(property != (const char *) NULL);
240  (void) CopyMagickString(key,property,MaxTextExtent-1);
241  for (p=key; *p != '\0'; p++)
242  if (*p == '=')
243  break;
244  *value='\0';
245  if (*p == '=')
246  (void) CopyMagickString(value,p+1,MaxTextExtent);
247  *p='\0';
248  return(SetImageProperty(image,key,value));
249 }
250 ␌
251 /*
252 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
253 % %
254 % %
255 % %
256 % D e l e t e I m a g e P r o p e r t y %
257 % %
258 % %
259 % %
260 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
261 %
262 % DeleteImageProperty() deletes an image property.
263 %
264 % The format of the DeleteImageProperty method is:
265 %
266 % MagickBooleanType DeleteImageProperty(Image *image,const char *property)
267 %
268 % A description of each parameter follows:
269 %
270 % o image: the image.
271 %
272 % o property: the image property.
273 %
274 */
275 MagickExport MagickBooleanType DeleteImageProperty(Image *image,
276  const char *property)
277 {
278  assert(image != (Image *) NULL);
279  assert(image->signature == MagickCoreSignature);
280  if (IsEventLogging() != MagickFalse)
281  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
282  if (image->properties == (void *) NULL)
283  return(MagickFalse);
284  return(DeleteNodeFromSplayTree((SplayTreeInfo *) image->properties,property));
285 }
286 ␌
287 /*
288 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
289 % %
290 % %
291 % %
292 % D e s t r o y I m a g e P r o p e r t i e s %
293 % %
294 % %
295 % %
296 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
297 %
298 % DestroyImageProperties() destroys all properties and associated memory
299 % attached to the given image.
300 %
301 % The format of the DestroyDefines method is:
302 %
303 % void DestroyImageProperties(Image *image)
304 %
305 % A description of each parameter follows:
306 %
307 % o image: the image.
308 %
309 */
310 MagickExport void DestroyImageProperties(Image *image)
311 {
312  assert(image != (Image *) NULL);
313  assert(image->signature == MagickCoreSignature);
314  if (IsEventLogging() != MagickFalse)
315  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
316  if (image->properties != (void *) NULL)
317  image->properties=(void *) DestroySplayTree((SplayTreeInfo *)
318  image->properties);
319 }
320 ␌
321 /*
322 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
323 % %
324 % %
325 % %
326 % F o r m a t I m a g e P r o p e r t y %
327 % %
328 % %
329 % %
330 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
331 %
332 % FormatImageProperty() permits formatted property/value pairs to be saved as
333 % an image property.
334 %
335 % The format of the FormatImageProperty method is:
336 %
337 % MagickBooleanType FormatImageProperty(Image *image,const char *property,
338 % const char *format,...)
339 %
340 % A description of each parameter follows.
341 %
342 % o image: The image.
343 %
344 % o property: The attribute property.
345 %
346 % o format: A string describing the format to use to write the remaining
347 % arguments.
348 %
349 */
350 MagickExport MagickBooleanType FormatImageProperty(Image *image,
351  const char *property,const char *format,...)
352 {
353  char
354  value[MaxTextExtent];
355 
356  ssize_t
357  n;
358 
359  va_list
360  operands;
361 
362  va_start(operands,format);
363  n=FormatLocaleStringList(value,MaxTextExtent,format,operands);
364  (void) n;
365  va_end(operands);
366  return(SetImageProperty(image,property,value));
367 }
368 ␌
369 /*
370 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
371 % %
372 % %
373 % %
374 % G e t I m a g e P r o p e r t y %
375 % %
376 % %
377 % %
378 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
379 %
380 % GetImageProperty() gets a value associated with an image property.
381 %
382 % This includes, profile prefixes, such as "exif:", "iptc:" and "8bim:"
383 % It does not handle non-profile prefixes, such as "fx:", "option:", or
384 % "artifact:".
385 %
386 % The returned string is stored as a prosperity of the same name for faster
387 % lookup later. It should NOT be freed by the caller.
388 %
389 % The format of the GetImageProperty method is:
390 %
391 % const char *GetImageProperty(const Image *image,const char *key)
392 %
393 % A description of each parameter follows:
394 %
395 % o image: the image.
396 %
397 % o key: the key.
398 %
399 */
400 
401 static char
402  *TracePSClippath(const unsigned char *,size_t,const size_t,
403  const size_t),
404  *TraceSVGClippath(const unsigned char *,size_t,const size_t,
405  const size_t);
406 
407 static MagickBooleanType GetIPTCProperty(const Image *image,const char *key)
408 {
409  char
410  *attribute,
411  *message;
412 
413  const StringInfo
414  *profile;
415 
416  long
417  count,
418  dataset,
419  record;
420 
421  ssize_t
422  i;
423 
424  size_t
425  length;
426 
427  profile=GetImageProfile(image,"iptc");
428  if (profile == (StringInfo *) NULL)
429  profile=GetImageProfile(image,"8bim");
430  if (profile == (StringInfo *) NULL)
431  return(MagickFalse);
432  count=sscanf(key,"IPTC:%ld:%ld",&dataset,&record);
433  if (count != 2)
434  return(MagickFalse);
435  attribute=(char *) NULL;
436  for (i=0; i < (ssize_t) GetStringInfoLength(profile); i+=(ssize_t) length)
437  {
438  length=1;
439  if ((ssize_t) GetStringInfoDatum(profile)[i] != 0x1c)
440  continue;
441  length=(size_t) (GetStringInfoDatum(profile)[i+3] << 8);
442  length|=GetStringInfoDatum(profile)[i+4];
443  if (((long) GetStringInfoDatum(profile)[i+1] == dataset) &&
444  ((long) GetStringInfoDatum(profile)[i+2] == record))
445  {
446  message=(char *) NULL;
447  if (~length >= 1)
448  message=(char *) AcquireQuantumMemory(length+1UL,sizeof(*message));
449  if (message != (char *) NULL)
450  {
451  (void) CopyMagickString(message,(char *) GetStringInfoDatum(
452  profile)+i+5,length+1);
453  (void) ConcatenateString(&attribute,message);
454  (void) ConcatenateString(&attribute,";");
455  message=DestroyString(message);
456  }
457  }
458  i+=5;
459  }
460  if ((attribute == (char *) NULL) || (*attribute == ';'))
461  {
462  if (attribute != (char *) NULL)
463  attribute=DestroyString(attribute);
464  return(MagickFalse);
465  }
466  attribute[strlen(attribute)-1]='\0';
467  (void) SetImageProperty((Image *) image,key,(const char *) attribute);
468  attribute=DestroyString(attribute);
469  return(MagickTrue);
470 }
471 
472 static inline int ReadPropertyByte(const unsigned char **p,size_t *length)
473 {
474  int
475  c;
476 
477  if (*length < 1)
478  return(EOF);
479  c=(int) (*(*p)++);
480  (*length)--;
481  return(c);
482 }
483 
484 static inline signed int ReadPropertyMSBLong(const unsigned char **p,
485  size_t *length)
486 {
487  union
488  {
489  unsigned int
490  unsigned_value;
491 
492  signed int
493  signed_value;
494  } quantum;
495 
496  int
497  c;
498 
499  ssize_t
500  i;
501 
502  unsigned char
503  buffer[4];
504 
505  unsigned int
506  value;
507 
508  if (*length < 4)
509  return(-1);
510  for (i=0; i < 4; i++)
511  {
512  c=(int) (*(*p)++);
513  (*length)--;
514  buffer[i]=(unsigned char) c;
515  }
516  value=(unsigned int) buffer[0] << 24;
517  value|=(unsigned int) buffer[1] << 16;
518  value|=(unsigned int) buffer[2] << 8;
519  value|=(unsigned int) buffer[3];
520  quantum.unsigned_value=value & 0xffffffff;
521  return(quantum.signed_value);
522 }
523 
524 static inline signed short ReadPropertyMSBShort(const unsigned char **p,
525  size_t *length)
526 {
527  union
528  {
529  unsigned short
530  unsigned_value;
531 
532  signed short
533  signed_value;
534  } quantum;
535 
536  int
537  c;
538 
539  ssize_t
540  i;
541 
542  unsigned char
543  buffer[2];
544 
545  unsigned short
546  value;
547 
548  if (*length < 2)
549  return((unsigned short) ~0);
550  for (i=0; i < 2; i++)
551  {
552  c=(int) (*(*p)++);
553  (*length)--;
554  buffer[i]=(unsigned char) c;
555  }
556  value=(unsigned short) buffer[0] << 8;
557  value|=(unsigned short) buffer[1];
558  quantum.unsigned_value=value & 0xffff;
559  return(quantum.signed_value);
560 }
561 
562 static MagickBooleanType Get8BIMProperty(const Image *image,const char *key)
563 {
564  char
565  *attribute,
566  format[MaxTextExtent],
567  name[MaxTextExtent],
568  *resource;
569 
570  const StringInfo
571  *profile;
572 
573  const unsigned char
574  *info;
575 
576  long
577  start,
578  stop;
579 
580  MagickBooleanType
581  status;
582 
583  ssize_t
584  i;
585 
586  size_t
587  length;
588 
589  ssize_t
590  count,
591  id,
592  sub_number;
593 
594  /*
595  There are no newlines in path names, so it's safe as terminator.
596  */
597  profile=GetImageProfile(image,"8bim");
598  if (profile == (StringInfo *) NULL)
599  return(MagickFalse);
600  count=(ssize_t) sscanf(key,"8BIM:%ld,%ld:%1024[^\n]\n%1024[^\n]",&start,&stop,
601  name,format);
602  if ((count != 2) && (count != 3) && (count != 4))
603  return(MagickFalse);
604  if (count < 4)
605  (void) CopyMagickString(format,"SVG",MaxTextExtent);
606  if (count < 3)
607  *name='\0';
608  sub_number=1;
609  if (*name == '#')
610  sub_number=(ssize_t) StringToLong(&name[1]);
611  sub_number=MagickMax(sub_number,1L);
612  resource=(char *) NULL;
613  status=MagickFalse;
614  length=GetStringInfoLength(profile);
615  info=GetStringInfoDatum(profile);
616  while ((length > 0) && (status == MagickFalse))
617  {
618  if (ReadPropertyByte(&info,&length) != (unsigned char) '8')
619  continue;
620  if (ReadPropertyByte(&info,&length) != (unsigned char) 'B')
621  continue;
622  if (ReadPropertyByte(&info,&length) != (unsigned char) 'I')
623  continue;
624  if (ReadPropertyByte(&info,&length) != (unsigned char) 'M')
625  continue;
626  id=(ssize_t) ReadPropertyMSBShort(&info,&length);
627  if (id < (ssize_t) start)
628  continue;
629  if (id > (ssize_t) stop)
630  continue;
631  if (resource != (char *) NULL)
632  resource=DestroyString(resource);
633  count=(ssize_t) ReadPropertyByte(&info,&length);
634  if ((count != 0) && ((size_t) count <= length))
635  {
636  resource=(char *) NULL;
637  if (~((size_t) count) >= (MaxTextExtent-1))
638  resource=(char *) AcquireQuantumMemory((size_t) count+MaxTextExtent,
639  sizeof(*resource));
640  if (resource != (char *) NULL)
641  {
642  for (i=0; i < (ssize_t) count; i++)
643  resource[i]=(char) ReadPropertyByte(&info,&length);
644  resource[count]='\0';
645  }
646  }
647  if ((count & 0x01) == 0)
648  (void) ReadPropertyByte(&info,&length);
649  count=(ssize_t) ReadPropertyMSBLong(&info,&length);
650  if ((count < 0) || ((size_t) count > length))
651  {
652  length=0;
653  continue;
654  }
655  if ((*name != '\0') && (*name != '#'))
656  if ((resource == (char *) NULL) || (LocaleCompare(name,resource) != 0))
657  {
658  /*
659  No name match, scroll forward and try next.
660  */
661  info+=count;
662  length-=MagickMin(count,(ssize_t) length);
663  continue;
664  }
665  if ((*name == '#') && (sub_number != 1))
666  {
667  /*
668  No numbered match, scroll forward and try next.
669  */
670  sub_number--;
671  info+=count;
672  length-=MagickMin(count,(ssize_t) length);
673  continue;
674  }
675  /*
676  We have the resource of interest.
677  */
678  attribute=(char *) NULL;
679  if (~((size_t) count) >= (MaxTextExtent-1))
680  attribute=(char *) AcquireQuantumMemory((size_t) count+MaxTextExtent,
681  sizeof(*attribute));
682  if (attribute != (char *) NULL)
683  {
684  (void) memcpy(attribute,(char *) info,(size_t) count);
685  attribute[count]='\0';
686  info+=count;
687  length-=MagickMin(count,(ssize_t) length);
688  if ((id <= 1999) || (id >= 2999))
689  (void) SetImageProperty((Image *) image,key,(const char *) attribute);
690  else
691  {
692  char
693  *path;
694 
695  if (LocaleCompare(format,"svg") == 0)
696  path=TraceSVGClippath((unsigned char *) attribute,(size_t) count,
697  image->columns,image->rows);
698  else
699  path=TracePSClippath((unsigned char *) attribute,(size_t) count,
700  image->columns,image->rows);
701  (void) SetImageProperty((Image *) image,key,(const char *) path);
702  path=DestroyString(path);
703  }
704  attribute=DestroyString(attribute);
705  status=MagickTrue;
706  }
707  }
708  if (resource != (char *) NULL)
709  resource=DestroyString(resource);
710  return(status);
711 }
712 
713 static inline signed int ReadPropertySignedLong(const EndianType endian,
714  const unsigned char *buffer)
715 {
716  union
717  {
718  unsigned int
719  unsigned_value;
720 
721  signed int
722  signed_value;
723  } quantum;
724 
725  unsigned int
726  value;
727 
728  if (endian == LSBEndian)
729  {
730  value=(unsigned int) buffer[3] << 24;
731  value|=(unsigned int) buffer[2] << 16;
732  value|=(unsigned int) buffer[1] << 8;
733  value|=(unsigned int) buffer[0];
734  quantum.unsigned_value=value & 0xffffffff;
735  return(quantum.signed_value);
736  }
737  value=(unsigned int) buffer[0] << 24;
738  value|=(unsigned int) buffer[1] << 16;
739  value|=(unsigned int) buffer[2] << 8;
740  value|=(unsigned int) buffer[3];
741  quantum.unsigned_value=value & 0xffffffff;
742  return(quantum.signed_value);
743 }
744 
745 static inline unsigned int ReadPropertyUnsignedLong(const EndianType endian,
746  const unsigned char *buffer)
747 {
748  unsigned int
749  value;
750 
751  if (endian == LSBEndian)
752  {
753  value=(unsigned int) buffer[3] << 24;
754  value|=(unsigned int) buffer[2] << 16;
755  value|=(unsigned int) buffer[1] << 8;
756  value|=(unsigned int) buffer[0];
757  return(value & 0xffffffff);
758  }
759  value=(unsigned int) buffer[0] << 24;
760  value|=(unsigned int) buffer[1] << 16;
761  value|=(unsigned int) buffer[2] << 8;
762  value|=(unsigned int) buffer[3];
763  return(value & 0xffffffff);
764 }
765 
766 static inline signed short ReadPropertySignedShort(const EndianType endian,
767  const unsigned char *buffer)
768 {
769  union
770  {
771  unsigned short
772  unsigned_value;
773 
774  signed short
775  signed_value;
776  } quantum;
777 
778  unsigned short
779  value;
780 
781  if (endian == LSBEndian)
782  {
783  value=(unsigned short) buffer[1] << 8;
784  value|=(unsigned short) buffer[0];
785  quantum.unsigned_value=value & 0xffff;
786  return(quantum.signed_value);
787  }
788  value=(unsigned short) buffer[0] << 8;
789  value|=(unsigned short) buffer[1];
790  quantum.unsigned_value=value & 0xffff;
791  return(quantum.signed_value);
792 }
793 
794 static inline unsigned short ReadPropertyUnsignedShort(const EndianType endian,
795  const unsigned char *buffer)
796 {
797  unsigned short
798  value;
799 
800  if (endian == LSBEndian)
801  {
802  value=(unsigned short) buffer[1] << 8;
803  value|=(unsigned short) buffer[0];
804  return(value & 0xffff);
805  }
806  value=(unsigned short) buffer[0] << 8;
807  value|=(unsigned short) buffer[1];
808  return(value & 0xffff);
809 }
810 
811 static MagickBooleanType GetEXIFProperty(const Image *image,
812  const char *property)
813 {
814 #define MaxDirectoryStack 16
815 #define EXIF_DELIMITER "\n"
816 #define EXIF_NUM_FORMATS 12
817 #define EXIF_FMT_BYTE 1
818 #define EXIF_FMT_STRING 2
819 #define EXIF_FMT_USHORT 3
820 #define EXIF_FMT_ULONG 4
821 #define EXIF_FMT_URATIONAL 5
822 #define EXIF_FMT_SBYTE 6
823 #define EXIF_FMT_UNDEFINED 7
824 #define EXIF_FMT_SSHORT 8
825 #define EXIF_FMT_SLONG 9
826 #define EXIF_FMT_SRATIONAL 10
827 #define EXIF_FMT_SINGLE 11
828 #define EXIF_FMT_DOUBLE 12
829 #define TAG_EXIF_OFFSET 0x8769
830 #define TAG_GPS_OFFSET 0x8825
831 #define TAG_INTEROP_OFFSET 0xa005
832 
833 #define EXIFMultipleValues(size,format,arg) \
834 { \
835  ssize_t \
836  component; \
837  \
838  size_t \
839  length; \
840  \
841  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, 0, 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  {
1470  value=(char *) NULL;
1471  if (~((size_t) number_bytes) >= 1)
1472  value=(char *) AcquireQuantumMemory((size_t) number_bytes+1UL,
1473  sizeof(*value));
1474  if (value != (char *) NULL)
1475  {
1476  for (i=0; i < (ssize_t) number_bytes; i++)
1477  {
1478  value[i]='.';
1479  if (isprint((int) p[i]) != 0)
1480  value[i]=(char) p[i];
1481  }
1482  value[i]='\0';
1483  }
1484  break;
1485  }
1486  case EXIF_FMT_SBYTE:
1487  {
1488  EXIFMultipleValues(1,"%.20g",(double) (*(signed char *) p1));
1489  break;
1490  }
1491  case EXIF_FMT_SSHORT:
1492  {
1493  EXIFMultipleValues(2,"%hd",ReadPropertySignedShort(endian,p1));
1494  break;
1495  }
1496  case EXIF_FMT_USHORT:
1497  {
1498  EXIFMultipleValues(2,"%hu",ReadPropertyUnsignedShort(endian,p1));
1499  break;
1500  }
1501  case EXIF_FMT_ULONG:
1502  {
1503  EXIFMultipleValues(4,"%.20g",(double)
1504  ReadPropertyUnsignedLong(endian,p1));
1505  break;
1506  }
1507  case EXIF_FMT_SLONG:
1508  {
1509  EXIFMultipleValues(4,"%.20g",(double)
1510  ReadPropertySignedLong(endian,p1));
1511  break;
1512  }
1513  case EXIF_FMT_URATIONAL:
1514  {
1515  EXIFMultipleFractions(8,"%.20g/%.20g",(double)
1516  ReadPropertyUnsignedLong(endian,p1),(double)
1517  ReadPropertyUnsignedLong(endian,p1+4));
1518  break;
1519  }
1520  case EXIF_FMT_SRATIONAL:
1521  {
1522  EXIFMultipleFractions(8,"%.20g/%.20g",(double)
1523  ReadPropertySignedLong(endian,p1),(double)
1524  ReadPropertySignedLong(endian,p1+4));
1525  break;
1526  }
1527  case EXIF_FMT_SINGLE:
1528  {
1529  EXIFMultipleValues(4,"%.20g",(double)
1530  ReadPropertySignedLong(endian,p1));
1531  break;
1532  }
1533  case EXIF_FMT_DOUBLE:
1534  {
1535  EXIFMultipleValues(8,"%.20g",(double)
1536  ReadPropertySignedLong(endian,p1));
1537  break;
1538  }
1539  case EXIF_FMT_STRING:
1540  case EXIF_FMT_UNDEFINED:
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)
1684 {
1685  const StringInfo
1686  *profile;
1687 
1688  /*
1689  Return ICC profile property.
1690  */
1691  profile=GetImageProfile(image,"icc");
1692  if (profile == (StringInfo *) NULL)
1693  profile=GetImageProfile(image,"icm");
1694  if (profile == (StringInfo *) NULL)
1695  return(MagickFalse);
1696  if (GetStringInfoLength(profile) < 128)
1697  return(MagickFalse); /* minimum ICC profile length */
1698 #if defined(MAGICKCORE_LCMS_DELEGATE)
1699  {
1700  cmsHPROFILE
1701  icc_profile;
1702 
1703  icc_profile=cmsOpenProfileFromMem(GetStringInfoDatum(profile),
1704  (cmsUInt32Number) GetStringInfoLength(profile));
1705  if (icc_profile != (cmsHPROFILE *) NULL)
1706  {
1707 #if defined(LCMS_VERSION) && (LCMS_VERSION < 2000)
1708  const char
1709  *name;
1710 
1711  name=cmsTakeProductName(icc_profile);
1712  if (name != (const char *) NULL)
1713  (void) SetImageProperty((Image *) image,"icc:name",name);
1714 #else
1715  StringInfo
1716  *info;
1717 
1718  unsigned int
1719  extent;
1720 
1721  info=AcquireStringInfo(0);
1722  extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoDescription,"en","US",
1723  NULL,0);
1724  if (extent != 0)
1725  {
1726  SetStringInfoLength(info,extent+1);
1727  extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoDescription,"en",
1728  "US",(char *) GetStringInfoDatum(info),extent);
1729  if (extent != 0)
1730  (void) SetImageProperty((Image *) image,"icc:description",
1731  (char *) GetStringInfoDatum(info));
1732  }
1733  extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoManufacturer,"en","US",
1734  NULL,0);
1735  if (extent != 0)
1736  {
1737  SetStringInfoLength(info,extent+1);
1738  extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoManufacturer,"en",
1739  "US",(char *) GetStringInfoDatum(info),extent);
1740  if (extent != 0)
1741  (void) SetImageProperty((Image *) image,"icc:manufacturer",
1742  (char *) GetStringInfoDatum(info));
1743  }
1744  extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoModel,"en","US",
1745  NULL,0);
1746  if (extent != 0)
1747  {
1748  SetStringInfoLength(info,extent+1);
1749  extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoModel,"en","US",
1750  (char *) GetStringInfoDatum(info),extent);
1751  if (extent != 0)
1752  (void) SetImageProperty((Image *) image,"icc:model",
1753  (char *) GetStringInfoDatum(info));
1754  }
1755  extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoCopyright,"en","US",
1756  NULL,0);
1757  if (extent != 0)
1758  {
1759  SetStringInfoLength(info,extent+1);
1760  extent=cmsGetProfileInfoASCII(icc_profile,cmsInfoCopyright,"en",
1761  "US",(char *) GetStringInfoDatum(info),extent);
1762  if (extent != 0)
1763  (void) SetImageProperty((Image *) image,"icc:copyright",
1764  (char *) GetStringInfoDatum(info));
1765  }
1766  info=DestroyStringInfo(info);
1767 #endif
1768  (void) cmsCloseProfile(icc_profile);
1769  }
1770  }
1771 #endif
1772  return(MagickTrue);
1773 }
1774 
1775 static MagickBooleanType SkipXMPValue(const char *value)
1776 {
1777  if (value == (const char*) NULL)
1778  return(MagickTrue);
1779  while (*value != '\0')
1780  {
1781  if (isspace((int) ((unsigned char) *value)) == 0)
1782  return(MagickFalse);
1783  value++;
1784  }
1785  return(MagickTrue);
1786 }
1787 
1788 static MagickBooleanType GetXMPProperty(const Image *image,const char *property)
1789 {
1790  char
1791  *xmp_profile;
1792 
1793  const char
1794  *content;
1795 
1796  const StringInfo
1797  *profile;
1798 
1800  *exception;
1801 
1802  MagickBooleanType
1803  status;
1804 
1805  const char
1806  *p;
1807 
1808  XMLTreeInfo
1809  *child,
1810  *description,
1811  *node,
1812  *rdf,
1813  *xmp;
1814 
1815  profile=GetImageProfile(image,"xmp");
1816  if (profile == (StringInfo *) NULL)
1817  return(MagickFalse);
1818  if (GetStringInfoLength(profile) < 17)
1819  return(MagickFalse);
1820  if ((property == (const char *) NULL) || (*property == '\0'))
1821  return(MagickFalse);
1822  xmp_profile=StringInfoToString(profile);
1823  if (xmp_profile == (char *) NULL)
1824  return(MagickFalse);
1825  for (p=xmp_profile; *p != '\0'; p++)
1826  if ((*p == '<') && (*(p+1) == 'x'))
1827  break;
1828  exception=AcquireExceptionInfo();
1829  xmp=NewXMLTree((char *) p,exception);
1830  xmp_profile=DestroyString(xmp_profile);
1831  exception=DestroyExceptionInfo(exception);
1832  if (xmp == (XMLTreeInfo *) NULL)
1833  return(MagickFalse);
1834  status=MagickFalse;
1835  rdf=GetXMLTreeChild(xmp,"rdf:RDF");
1836  if (rdf != (XMLTreeInfo *) NULL)
1837  {
1838  if (image->properties == (void *) NULL)
1839  ((Image *) image)->properties=NewSplayTree(CompareSplayTreeString,
1840  RelinquishMagickMemory,RelinquishMagickMemory);
1841  description=GetXMLTreeChild(rdf,"rdf:Description");
1842  while (description != (XMLTreeInfo *) NULL)
1843  {
1844  node=GetXMLTreeChild(description,(const char *) NULL);
1845  while (node != (XMLTreeInfo *) NULL)
1846  {
1847  char
1848  *xmp_namespace;
1849 
1850  child=GetXMLTreeChild(node,(const char *) NULL);
1851  content=GetXMLTreeContent(node);
1852  if ((child == (XMLTreeInfo *) NULL) &&
1853  (SkipXMPValue(content) == MagickFalse))
1854  {
1855  xmp_namespace=ConstantString(GetXMLTreeTag(node));
1856  (void) SubstituteString(&xmp_namespace,"exif:","xmp:");
1857  (void) AddValueToSplayTree((SplayTreeInfo *) image->properties,
1858  xmp_namespace,ConstantString(content));
1859  }
1860  while (child != (XMLTreeInfo *) NULL)
1861  {
1862  content=GetXMLTreeContent(child);
1863  if (SkipXMPValue(content) == MagickFalse)
1864  {
1865  xmp_namespace=ConstantString(GetXMLTreeTag(node));
1866  (void) SubstituteString(&xmp_namespace,"exif:","xmp:");
1867  (void) AddValueToSplayTree((SplayTreeInfo *) image->properties,
1868  xmp_namespace,ConstantString(content));
1869  }
1870  child=GetXMLTreeSibling(child);
1871  }
1872  node=GetXMLTreeSibling(node);
1873  }
1874  description=GetNextXMLTreeTag(description);
1875  }
1876  }
1877  xmp=DestroyXMLTree(xmp);
1878  return(status);
1879 }
1880 
1881 static char *TracePSClippath(const unsigned char *blob,size_t length,
1882  const size_t magick_unused(columns),const size_t magick_unused(rows))
1883 {
1884  char
1885  *path,
1886  *message;
1887 
1888  MagickBooleanType
1889  in_subpath;
1890 
1891  PointInfo
1892  first[3],
1893  last[3],
1894  point[3];
1895 
1896  ssize_t
1897  i,
1898  x;
1899 
1900  ssize_t
1901  knot_count,
1902  selector,
1903  y;
1904 
1905  magick_unreferenced(columns);
1906  magick_unreferenced(rows);
1907 
1908  path=AcquireString((char *) NULL);
1909  if (path == (char *) NULL)
1910  return((char *) NULL);
1911  message=AcquireString((char *) NULL);
1912  (void) FormatLocaleString(message,MaxTextExtent,"/ClipImage\n");
1913  (void) ConcatenateString(&path,message);
1914  (void) FormatLocaleString(message,MaxTextExtent,"{\n");
1915  (void) ConcatenateString(&path,message);
1916  (void) FormatLocaleString(message,MaxTextExtent," /c {curveto} bind def\n");
1917  (void) ConcatenateString(&path,message);
1918  (void) FormatLocaleString(message,MaxTextExtent," /l {lineto} bind def\n");
1919  (void) ConcatenateString(&path,message);
1920  (void) FormatLocaleString(message,MaxTextExtent," /m {moveto} bind def\n");
1921  (void) ConcatenateString(&path,message);
1922  (void) FormatLocaleString(message,MaxTextExtent,
1923  " /v {currentpoint 6 2 roll curveto} bind def\n");
1924  (void) ConcatenateString(&path,message);
1925  (void) FormatLocaleString(message,MaxTextExtent,
1926  " /y {2 copy curveto} bind def\n");
1927  (void) ConcatenateString(&path,message);
1928  (void) FormatLocaleString(message,MaxTextExtent,
1929  " /z {closepath} bind def\n");
1930  (void) ConcatenateString(&path,message);
1931  (void) FormatLocaleString(message,MaxTextExtent," newpath\n");
1932  (void) ConcatenateString(&path,message);
1933  /*
1934  The clipping path format is defined in "Adobe Photoshop File
1935  Formats Specification" version 6.0 downloadable from adobe.com.
1936  */
1937  (void) memset(point,0,sizeof(point));
1938  (void) memset(first,0,sizeof(first));
1939  (void) memset(last,0,sizeof(last));
1940  knot_count=0;
1941  in_subpath=MagickFalse;
1942  while (length > 0)
1943  {
1944  selector=(ssize_t) ReadPropertyMSBShort(&blob,&length);
1945  switch (selector)
1946  {
1947  case 0:
1948  case 3:
1949  {
1950  if (knot_count != 0)
1951  {
1952  blob+=24;
1953  length-=MagickMin(24,(ssize_t) length);
1954  break;
1955  }
1956  /*
1957  Expected subpath length record.
1958  */
1959  knot_count=(ssize_t) ReadPropertyMSBShort(&blob,&length);
1960  blob+=22;
1961  length-=MagickMin(22,(ssize_t) length);
1962  break;
1963  }
1964  case 1:
1965  case 2:
1966  case 4:
1967  case 5:
1968  {
1969  if (knot_count == 0)
1970  {
1971  /*
1972  Unexpected subpath knot
1973  */
1974  blob+=24;
1975  length-=MagickMin(24,(ssize_t) length);
1976  break;
1977  }
1978  /*
1979  Add sub-path knot
1980  */
1981  for (i=0; i < 3; i++)
1982  {
1983  y=(size_t) ReadPropertyMSBLong(&blob,&length);
1984  x=(size_t) ReadPropertyMSBLong(&blob,&length);
1985  point[i].x=(double) x/4096.0/4096.0;
1986  point[i].y=1.0-(double) y/4096.0/4096.0;
1987  }
1988  if (in_subpath == MagickFalse)
1989  {
1990  (void) FormatLocaleString(message,MaxTextExtent," %g %g m\n",
1991  point[1].x,point[1].y);
1992  for (i=0; i < 3; i++)
1993  {
1994  first[i]=point[i];
1995  last[i]=point[i];
1996  }
1997  }
1998  else
1999  {
2000  /*
2001  Handle special cases when Bezier curves are used to describe
2002  corners and straight lines.
2003  */
2004  if ((last[1].x == last[2].x) && (last[1].y == last[2].y) &&
2005  (point[0].x == point[1].x) && (point[0].y == point[1].y))
2006  (void) FormatLocaleString(message,MaxTextExtent,
2007  " %g %g l\n",point[1].x,point[1].y);
2008  else
2009  if ((last[1].x == last[2].x) && (last[1].y == last[2].y))
2010  (void) FormatLocaleString(message,MaxTextExtent,
2011  " %g %g %g %g v\n",point[0].x,point[0].y,
2012  point[1].x,point[1].y);
2013  else
2014  if ((point[0].x == point[1].x) && (point[0].y == point[1].y))
2015  (void) FormatLocaleString(message,MaxTextExtent,
2016  " %g %g %g %g y\n",last[2].x,last[2].y,
2017  point[1].x,point[1].y);
2018  else
2019  (void) FormatLocaleString(message,MaxTextExtent,
2020  " %g %g %g %g %g %g c\n",last[2].x,
2021  last[2].y,point[0].x,point[0].y,point[1].x,point[1].y);
2022  for (i=0; i < 3; i++)
2023  last[i]=point[i];
2024  }
2025  (void) ConcatenateString(&path,message);
2026  in_subpath=MagickTrue;
2027  knot_count--;
2028  /*
2029  Close the subpath if there are no more knots.
2030  */
2031  if (knot_count == 0)
2032  {
2033  /*
2034  Same special handling as above except we compare to the
2035  first point in the path and close the path.
2036  */
2037  if ((last[1].x == last[2].x) && (last[1].y == last[2].y) &&
2038  (first[0].x == first[1].x) && (first[0].y == first[1].y))
2039  (void) FormatLocaleString(message,MaxTextExtent,
2040  " %g %g l z\n",first[1].x,first[1].y);
2041  else
2042  if ((last[1].x == last[2].x) && (last[1].y == last[2].y))
2043  (void) FormatLocaleString(message,MaxTextExtent,
2044  " %g %g %g %g v z\n",first[0].x,first[0].y,
2045  first[1].x,first[1].y);
2046  else
2047  if ((first[0].x == first[1].x) && (first[0].y == first[1].y))
2048  (void) FormatLocaleString(message,MaxTextExtent,
2049  " %g %g %g %g y z\n",last[2].x,last[2].y,
2050  first[1].x,first[1].y);
2051  else
2052  (void) FormatLocaleString(message,MaxTextExtent,
2053  " %g %g %g %g %g %g c z\n",last[2].x,
2054  last[2].y,first[0].x,first[0].y,first[1].x,first[1].y);
2055  (void) ConcatenateString(&path,message);
2056  in_subpath=MagickFalse;
2057  }
2058  break;
2059  }
2060  case 6:
2061  case 7:
2062  case 8:
2063  default:
2064  {
2065  blob+=24;
2066  length-=MagickMin(24,(ssize_t) length);
2067  break;
2068  }
2069  }
2070  }
2071  /*
2072  Returns an empty PS path if the path has no knots.
2073  */
2074  (void) FormatLocaleString(message,MaxTextExtent," eoclip\n");
2075  (void) ConcatenateString(&path,message);
2076  (void) FormatLocaleString(message,MaxTextExtent,"} bind def");
2077  (void) ConcatenateString(&path,message);
2078  message=DestroyString(message);
2079  return(path);
2080 }
2081 
2082 static inline void TraceBezierCurve(char *message,PointInfo *last,
2083  PointInfo *point)
2084 {
2085  /*
2086  Handle special cases when Bezier curves are used to describe
2087  corners and straight lines.
2088  */
2089  if (((last+1)->x == (last+2)->x) && ((last+1)->y == (last+2)->y) &&
2090  (point->x == (point+1)->x) && (point->y == (point+1)->y))
2091  (void) FormatLocaleString(message,MagickPathExtent,
2092  "L %g %g\n",point[1].x,point[1].y);
2093  else
2094  (void) FormatLocaleString(message,MagickPathExtent,"C %g %g %g %g %g %g\n",
2095  (last+2)->x,(last+2)->y,point->x,point->y,(point+1)->x,(point+1)->y);
2096 }
2097 
2098 static char *TraceSVGClippath(const unsigned char *blob,size_t length,
2099  const size_t columns,const size_t rows)
2100 {
2101  char
2102  *path,
2103  *message;
2104 
2105  MagickBooleanType
2106  in_subpath;
2107 
2108  PointInfo
2109  first[3],
2110  last[3],
2111  point[3];
2112 
2113  ssize_t
2114  i;
2115 
2116  ssize_t
2117  knot_count,
2118  selector,
2119  x,
2120  y;
2121 
2122  path=AcquireString((char *) NULL);
2123  if (path == (char *) NULL)
2124  return((char *) NULL);
2125  message=AcquireString((char *) NULL);
2126  (void) FormatLocaleString(message,MaxTextExtent,(
2127  "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
2128  "<svg xmlns=\"http://www.w3.org/2000/svg\""
2129  " width=\"%.20g\" height=\"%.20g\">\n"
2130  "<g>\n"
2131  "<path fill-rule=\"evenodd\" style=\"fill:#000000;stroke:#000000;"
2132  "stroke-width:0;stroke-antialiasing:false\" d=\"\n"),(double) columns,
2133  (double) rows);
2134  (void) ConcatenateString(&path,message);
2135  (void) memset(point,0,sizeof(point));
2136  (void) memset(first,0,sizeof(first));
2137  (void) memset(last,0,sizeof(last));
2138  knot_count=0;
2139  in_subpath=MagickFalse;
2140  while (length != 0)
2141  {
2142  selector=(ssize_t) ReadPropertyMSBShort(&blob,&length);
2143  switch (selector)
2144  {
2145  case 0:
2146  case 3:
2147  {
2148  if (knot_count != 0)
2149  {
2150  blob+=24;
2151  length-=MagickMin(24,(ssize_t) length);
2152  break;
2153  }
2154  /*
2155  Expected subpath length record.
2156  */
2157  knot_count=(ssize_t) ReadPropertyMSBShort(&blob,&length);
2158  blob+=22;
2159  length-=MagickMin(22,(ssize_t) length);
2160  break;
2161  }
2162  case 1:
2163  case 2:
2164  case 4:
2165  case 5:
2166  {
2167  if (knot_count == 0)
2168  {
2169  /*
2170  Unexpected subpath knot.
2171  */
2172  blob+=24;
2173  length-=MagickMin(24,(ssize_t) length);
2174  break;
2175  }
2176  /*
2177  Add sub-path knot.
2178  */
2179  for (i=0; i < 3; i++)
2180  {
2181  y=(ssize_t) ReadPropertyMSBLong(&blob,&length);
2182  x=(ssize_t) ReadPropertyMSBLong(&blob,&length);
2183  point[i].x=(double) x*columns/4096.0/4096.0;
2184  point[i].y=(double) y*rows/4096.0/4096.0;
2185  }
2186  if (in_subpath == MagickFalse)
2187  {
2188  (void) FormatLocaleString(message,MaxTextExtent,"M %g %g\n",
2189  point[1].x,point[1].y);
2190  for (i=0; i < 3; i++)
2191  {
2192  first[i]=point[i];
2193  last[i]=point[i];
2194  }
2195  }
2196  else
2197  {
2198  TraceBezierCurve(message,last,point);
2199  for (i=0; i < 3; i++)
2200  last[i]=point[i];
2201  }
2202  (void) ConcatenateString(&path,message);
2203  in_subpath=MagickTrue;
2204  knot_count--;
2205  /*
2206  Close the subpath if there are no more knots.
2207  */
2208  if (knot_count == 0)
2209  {
2210  TraceBezierCurve(message,last,first);
2211  (void) ConcatenateString(&path,message);
2212  in_subpath=MagickFalse;
2213  }
2214  break;
2215  }
2216  case 6:
2217  case 7:
2218  case 8:
2219  default:
2220  {
2221  blob+=24;
2222  length-=MagickMin(24,(ssize_t) length);
2223  break;
2224  }
2225  }
2226  }
2227  /*
2228  Return an empty SVG image if the path does not have knots.
2229  */
2230  (void) ConcatenateString(&path,"\"/>\n</g>\n</svg>\n");
2231  message=DestroyString(message);
2232  return(path);
2233 }
2234 
2235 MagickExport const char *GetImageProperty(const Image *image,
2236  const char *property)
2237 {
2238  double
2239  alpha;
2240 
2242  *exception;
2243 
2244  FxInfo
2245  *fx_info;
2246 
2247  MagickStatusType
2248  status;
2249 
2250  const char
2251  *p;
2252 
2253  assert(image != (Image *) NULL);
2254  assert(image->signature == MagickCoreSignature);
2255  if (IsEventLogging() != MagickFalse)
2256  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2257  p=(const char *) NULL;
2258  if (image->properties != (void *) NULL)
2259  {
2260  if (property == (const char *) NULL)
2261  {
2262  ResetSplayTreeIterator((SplayTreeInfo *) image->properties);
2263  p=(const char *) GetNextValueInSplayTree((SplayTreeInfo *)
2264  image->properties);
2265  return(p);
2266  }
2267  if (LocaleNCompare("fx:",property,3) != 0) /* NOT fx: !!!! */
2268  {
2269  p=(const char *) GetValueFromSplayTree((SplayTreeInfo *)
2270  image->properties,property);
2271  if (p != (const char *) NULL)
2272  return(p);
2273  }
2274  }
2275  if ((property == (const char *) NULL) ||
2276  (strchr(property,':') == (char *) NULL))
2277  return(p);
2278  exception=(&((Image *) image)->exception);
2279  switch (*property)
2280  {
2281  case '8':
2282  {
2283  if (LocaleNCompare("8bim:",property,5) == 0)
2284  {
2285  (void) Get8BIMProperty(image,property);
2286  break;
2287  }
2288  break;
2289  }
2290  case 'E':
2291  case 'e':
2292  {
2293  if (LocaleNCompare("exif:",property,5) == 0)
2294  {
2295  (void) GetEXIFProperty(image,property);
2296  break;
2297  }
2298  break;
2299  }
2300  case 'F':
2301  case 'f':
2302  {
2303  if (LocaleNCompare("fx:",property,3) == 0)
2304  {
2305  if ((image->columns == 0) || (image->rows == 0))
2306  break;
2307  fx_info=AcquireFxInfo(image,property+3);
2308  status=FxEvaluateChannelExpression(fx_info,DefaultChannels,0,0,&alpha,
2309  exception);
2310  fx_info=DestroyFxInfo(fx_info);
2311  if (status != MagickFalse)
2312  {
2313  char
2314  value[MaxTextExtent];
2315 
2316  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
2317  GetMagickPrecision(),(double) alpha);
2318  (void) SetImageProperty((Image *) image,property,value);
2319  }
2320  break;
2321  }
2322  break;
2323  }
2324  case 'H':
2325  case 'h':
2326  {
2327  if (LocaleNCompare("hex:",property,4) == 0)
2328  {
2330  pixel;
2331 
2332  if ((image->columns == 0) || (image->rows == 0))
2333  break;
2334  GetMagickPixelPacket(image,&pixel);
2335  fx_info=AcquireFxInfo(image,property+4);
2336  status=FxEvaluateChannelExpression(fx_info,RedChannel,0,0,&alpha,
2337  exception);
2338  pixel.red=(MagickRealType) QuantumRange*alpha;
2339  status&=FxEvaluateChannelExpression(fx_info,GreenChannel,0,0,&alpha,
2340  exception);
2341  pixel.green=(MagickRealType) QuantumRange*alpha;
2342  status&=FxEvaluateChannelExpression(fx_info,BlueChannel,0,0,&alpha,
2343  exception);
2344  pixel.blue=(MagickRealType) QuantumRange*alpha;
2345  status&=FxEvaluateChannelExpression(fx_info,OpacityChannel,0,0,&alpha,
2346  exception);
2347  pixel.opacity=(MagickRealType) QuantumRange*(1.0-alpha);
2348  if (image->colorspace == CMYKColorspace)
2349  {
2350  status&=FxEvaluateChannelExpression(fx_info,BlackChannel,0,0,
2351  &alpha,exception);
2352  pixel.index=(MagickRealType) QuantumRange*alpha;
2353  }
2354  fx_info=DestroyFxInfo(fx_info);
2355  if (status != MagickFalse)
2356  {
2357  char
2358  hex[MaxTextExtent];
2359 
2360  GetColorTuple(&pixel,MagickTrue,hex);
2361  (void) SetImageProperty((Image *) image,property,hex+1);
2362  }
2363  break;
2364  }
2365  break;
2366  }
2367  case 'I':
2368  case 'i':
2369  {
2370  if ((LocaleNCompare("icc:",property,4) == 0) ||
2371  (LocaleNCompare("icm:",property,4) == 0))
2372  {
2373  (void) GetICCProperty(image);
2374  break;
2375  }
2376  if (LocaleNCompare("iptc:",property,5) == 0)
2377  {
2378  (void) GetIPTCProperty(image,property);
2379  break;
2380  }
2381  break;
2382  }
2383  case 'P':
2384  case 'p':
2385  {
2386  if (LocaleNCompare("pixel:",property,6) == 0)
2387  {
2389  pixel;
2390 
2391  GetMagickPixelPacket(image,&pixel);
2392  fx_info=AcquireFxInfo(image,property+6);
2393  status=FxEvaluateChannelExpression(fx_info,RedChannel,0,0,&alpha,
2394  exception);
2395  pixel.red=(MagickRealType) QuantumRange*alpha;
2396  status&=FxEvaluateChannelExpression(fx_info,GreenChannel,0,0,&alpha,
2397  exception);
2398  pixel.green=(MagickRealType) QuantumRange*alpha;
2399  status&=FxEvaluateChannelExpression(fx_info,BlueChannel,0,0,&alpha,
2400  exception);
2401  pixel.blue=(MagickRealType) QuantumRange*alpha;
2402  status&=FxEvaluateChannelExpression(fx_info,OpacityChannel,0,0,&alpha,
2403  exception);
2404  pixel.opacity=(MagickRealType) QuantumRange*(1.0-alpha);
2405  if (image->colorspace == CMYKColorspace)
2406  {
2407  status&=FxEvaluateChannelExpression(fx_info,BlackChannel,0,0,
2408  &alpha,exception);
2409  pixel.index=(MagickRealType) QuantumRange*alpha;
2410  }
2411  fx_info=DestroyFxInfo(fx_info);
2412  if (status != MagickFalse)
2413  {
2414  char
2415  name[MaxTextExtent];
2416 
2417  const char
2418  *value;
2419 
2420  GetColorTuple(&pixel,MagickFalse,name);
2421  value=GetImageArtifact(image,"pixel:compliance");
2422  if (value != (char *) NULL)
2423  {
2424  ComplianceType compliance=(ComplianceType) ParseCommandOption(
2425  MagickComplianceOptions,MagickFalse,value);
2426  (void) QueryMagickColorname(image,&pixel,compliance,name,
2427  exception);
2428  }
2429  (void) SetImageProperty((Image *) image,property,name);
2430  }
2431  break;
2432  }
2433  break;
2434  }
2435  case 'X':
2436  case 'x':
2437  {
2438  if (LocaleNCompare("xmp:",property,4) == 0)
2439  {
2440  (void) GetXMPProperty(image,property);
2441  break;
2442  }
2443  break;
2444  }
2445  default:
2446  break;
2447  }
2448  if (image->properties != (void *) NULL)
2449  {
2450  p=(const char *) GetValueFromSplayTree((SplayTreeInfo *)
2451  image->properties,property);
2452  return(p);
2453  }
2454  return((const char *) NULL);
2455 }
2456 ␌
2457 /*
2458 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2459 % %
2460 % %
2461 % %
2462 + G e t M a g i c k P r o p e r t y %
2463 % %
2464 % %
2465 % %
2466 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2467 %
2468 % GetMagickProperty() gets attributes or calculated values that is associated
2469 % with a fixed known property name, or single letter property:
2470 %
2471 % \n newline
2472 % \r carriage return
2473 % < less-than character.
2474 % > greater-than character.
2475 % & ampersand character.
2476 % %% a percent sign
2477 % %b file size of image read in
2478 % %c comment meta-data property
2479 % %d directory component of path
2480 % %e filename extension or suffix
2481 % %f filename (including suffix)
2482 % %g layer canvas page geometry (equivalent to "%Wx%H%X%Y")
2483 % %h current image height in pixels
2484 % %i image filename (note: becomes output filename for "info:")
2485 % %k CALCULATED: number of unique colors
2486 % %l label meta-data property
2487 % %m image file format (file magic)
2488 % %n number of images in current image sequence
2489 % %o output filename (used for delegates)
2490 % %p index of image in current image list
2491 % %q quantum depth (compile-time constant)
2492 % %r image class and colorspace
2493 % %s scene number (from input unless re-assigned)
2494 % %t filename without directory or extension (suffix)
2495 % %u unique temporary filename (used for delegates)
2496 % %w current width in pixels
2497 % %x x resolution (density)
2498 % %y y resolution (density)
2499 % %z image depth (as read in unless modified, image save depth)
2500 % %A image transparency channel enabled (true/false)
2501 % %B file size of image in bytes
2502 % %C image compression type
2503 % %D image GIF dispose method
2504 % %G original image size (%wx%h; before any resizes)
2505 % %H page (canvas) height
2506 % %M Magick filename (original file exactly as given, including read mods)
2507 % %O page (canvas) offset ( = %X%Y )
2508 % %P page (canvas) size ( = %Wx%H )
2509 % %Q image compression quality ( 0 = default )
2510 % %S ?? scenes ??
2511 % %T image time delay (in centi-seconds)
2512 % %U image resolution units
2513 % %W page (canvas) width
2514 % %X page (canvas) x offset (including sign)
2515 % %Y page (canvas) y offset (including sign)
2516 % %Z unique filename (used for delegates)
2517 % %@ CALCULATED: trim bounding box (without actually trimming)
2518 % %# CALCULATED: 'signature' hash of image values
2519 %
2520 % This does not return, special profile or property expressions. Nor does it
2521 % return free-form property strings, unless referenced by a single letter
2522 % property name.
2523 %
2524 % The returned string is stored as the image artifact 'get-property' (not as
2525 % another property), and as such should not be freed. Later calls however
2526 % will overwrite this value so if needed for a longer period a copy should be
2527 % made. This artifact can be deleted when no longer required.
2528 %
2529 % The format of the GetMagickProperty method is:
2530 %
2531 % const char *GetMagickProperty(const ImageInfo *image_info,Image *image,
2532 % const char *property)
2533 %
2534 % A description of each parameter follows:
2535 %
2536 % o image_info: the image info.
2537 %
2538 % o image: the image.
2539 %
2540 % o key: the key.
2541 %
2542 */
2543 static const char *GetMagickPropertyLetter(const ImageInfo *image_info,
2544  Image *image,const char letter)
2545 {
2546 #define WarnNoImageInfoReturn(format,arg) \
2547  if (image_info == (ImageInfo *) NULL ) { \
2548  (void) ThrowMagickException(&image->exception,GetMagickModule(), \
2549  OptionWarning,"NoImageInfoForProperty",format,arg); \
2550  return((const char *) NULL); \
2551  }
2552 
2553  char
2554  value[MaxTextExtent];
2555 
2556  const char
2557  *string;
2558 
2559  assert(image != (Image *) NULL);
2560  assert(image->signature == MagickCoreSignature);
2561  if (IsEventLogging() != MagickFalse)
2562  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2563  *value='\0';
2564  string=(char *) NULL;
2565  switch (letter)
2566  {
2567  case 'b':
2568  {
2569  /*
2570  Image size read in - in bytes.
2571  */
2572  (void) FormatMagickSize(image->extent,MagickFalse,value);
2573  if (image->extent == 0)
2574  (void) FormatMagickSize(GetBlobSize(image),MagickFalse,value);
2575  break;
2576  }
2577  case 'c':
2578  {
2579  /*
2580  Image comment property - empty string by default.
2581  */
2582  string=GetImageProperty(image,"comment");
2583  if (string == (const char *) NULL)
2584  string="";
2585  break;
2586  }
2587  case 'd':
2588  {
2589  /*
2590  Directory component of filename.
2591  */
2592  GetPathComponent(image->magick_filename,HeadPath,value);
2593  if (*value == '\0')
2594  string="";
2595  break;
2596  }
2597  case 'e':
2598  {
2599  /*
2600  Filename extension (suffix) of image file.
2601  */
2602  GetPathComponent(image->magick_filename,ExtensionPath,value);
2603  if (*value == '\0')
2604  string="";
2605  break;
2606  }
2607  case 'f':
2608  {
2609  /*
2610  Filename without directory component.
2611  */
2612  GetPathComponent(image->magick_filename,TailPath,value);
2613  if (*value == '\0')
2614  string="";
2615  break;
2616  }
2617  case 'g':
2618  {
2619  /*
2620  Image geometry, canvas and offset %Wx%H+%X+%Y.
2621  */
2622  (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g%+.20g%+.20g",
2623  (double) image->page.width,(double) image->page.height,
2624  (double) image->page.x,(double) image->page.y);
2625  break;
2626  }
2627  case 'h':
2628  {
2629  /*
2630  Image height (current).
2631  */
2632  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2633  (image->rows != 0 ? image->rows : image->magick_rows));
2634  break;
2635  }
2636  case 'i':
2637  {
2638  /*
2639  Filename last used for image (read or write).
2640  */
2641  string=image->filename;
2642  break;
2643  }
2644  case 'k':
2645  {
2646  /*
2647  Number of unique colors.
2648  */
2649  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2650  GetNumberColors(image,(FILE *) NULL,&image->exception));
2651  break;
2652  }
2653  case 'l':
2654  {
2655  /*
2656  Image label property - empty string by default.
2657  */
2658  string=GetImageProperty(image,"label");
2659  if (string == (const char *) NULL)
2660  string="";
2661  break;
2662  }
2663  case 'm':
2664  {
2665  /*
2666  Image format (file magick).
2667  */
2668  string=image->magick;
2669  break;
2670  }
2671  case 'n':
2672  {
2673  /*
2674  Number of images in the list.
2675  */
2676  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2677  GetImageListLength(image));
2678  break;
2679  }
2680  case 'o':
2681  {
2682  /*
2683  Output Filename - for delegate use only
2684  */
2685  WarnNoImageInfoReturn("\"%%%c\"",letter);
2686  string=image_info->filename;
2687  break;
2688  }
2689  case 'p':
2690  {
2691  /*
2692  Image index in current image list -- As 'n' OBSOLETE.
2693  */
2694  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2695  GetImageIndexInList(image));
2696  break;
2697  }
2698  case 'q':
2699  {
2700  /*
2701  Quantum depth of image in memory.
2702  */
2703  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2704  MAGICKCORE_QUANTUM_DEPTH);
2705  break;
2706  }
2707  case 'r':
2708  {
2709  ColorspaceType
2710  colorspace;
2711 
2712  /*
2713  Image storage class and colorspace.
2714  */
2715  colorspace=image->colorspace;
2716  if ((image->columns != 0) && (image->rows != 0) &&
2717  (SetImageGray(image,&image->exception) != MagickFalse))
2718  colorspace=GRAYColorspace;
2719  (void) FormatLocaleString(value,MaxTextExtent,"%s %s %s",
2720  CommandOptionToMnemonic(MagickClassOptions,(ssize_t)
2721  image->storage_class),CommandOptionToMnemonic(MagickColorspaceOptions,
2722  (ssize_t) colorspace),image->matte != MagickFalse ? "Matte" : "" );
2723  break;
2724  }
2725  case 's':
2726  {
2727  /*
2728  Image scene number.
2729  */
2730  WarnNoImageInfoReturn("\"%%%c\"",letter);
2731  if (image_info->number_scenes != 0)
2732  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2733  image_info->scene);
2734  else
2735  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2736  image->scene);
2737  break;
2738  }
2739  case 't':
2740  {
2741  /*
2742  Base filename without directory or extension.
2743  */
2744  GetPathComponent(image->magick_filename,BasePath,value);
2745  if (*value == '\0')
2746  string="";
2747  break;
2748  }
2749  case 'u':
2750  {
2751  /*
2752  Unique filename.
2753  */
2754  WarnNoImageInfoReturn("\"%%%c\"",letter);
2755  string=image_info->unique;
2756  break;
2757  }
2758  case 'w':
2759  {
2760  /*
2761  Image width (current).
2762  */
2763  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2764  (image->columns != 0 ? image->columns : image->magick_columns));
2765  break;
2766  }
2767  case 'x':
2768  {
2769  /*
2770  Image horizontal resolution.
2771  */
2772  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",
2773  fabs(image->x_resolution) > MagickEpsilon ? image->x_resolution :
2774  image->units == PixelsPerCentimeterResolution ? DefaultResolution/2.54 :
2775  DefaultResolution);
2776  break;
2777  }
2778  case 'y':
2779  {
2780  /*
2781  Image vertical resolution.
2782  */
2783  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",
2784  fabs(image->y_resolution) > MagickEpsilon ? image->y_resolution :
2785  image->units == PixelsPerCentimeterResolution ? DefaultResolution/2.54 :
2786  DefaultResolution);
2787  break;
2788  }
2789  case 'z':
2790  {
2791  /*
2792  Image depth.
2793  */
2794  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2795  image->depth);
2796  break;
2797  }
2798  case 'A':
2799  {
2800  /*
2801  Image alpha channel.
2802  */
2803  (void) FormatLocaleString(value,MaxTextExtent,"%s",
2804  CommandOptionToMnemonic(MagickBooleanOptions,(ssize_t) image->matte));
2805  break;
2806  }
2807  case 'B':
2808  {
2809  /*
2810  Image size read in - in bytes.
2811  */
2812  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2813  image->extent);
2814  if (image->extent == 0)
2815  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2816  GetBlobSize(image));
2817  break;
2818  }
2819  case 'C':
2820  {
2821  /*
2822  Image compression method.
2823  */
2824  (void) FormatLocaleString(value,MaxTextExtent,"%s",
2825  CommandOptionToMnemonic(MagickCompressOptions,(ssize_t)
2826  image->compression));
2827  break;
2828  }
2829  case 'D':
2830  {
2831  /*
2832  Image dispose method.
2833  */
2834  (void) FormatLocaleString(value,MaxTextExtent,"%s",
2835  CommandOptionToMnemonic(MagickDisposeOptions,(ssize_t) image->dispose));
2836  break;
2837  }
2838  case 'F':
2839  {
2840  const char
2841  *q;
2842 
2843  char
2844  *p;
2845 
2846  static const char
2847  allowlist[] =
2848  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 "
2849  "$-_.+!*'(),{}|\\^~[]`\"><#%;/?:@&=";
2850 
2851  /*
2852  Magick filename (sanitized) - filename given incl. coder & read mods.
2853  */
2854  (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
2855  p=value;
2856  q=value+strlen(value);
2857  for (p+=strspn(p,allowlist); p != q; p+=strspn(p,allowlist))
2858  *p='_';
2859  break;
2860  }
2861  case 'G':
2862  {
2863  /*
2864  Image size as geometry = "%wx%h".
2865  */
2866  (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g",(double)
2867  image->magick_columns,(double) image->magick_rows);
2868  break;
2869  }
2870  case 'H':
2871  {
2872  /*
2873  Layer canvas height.
2874  */
2875  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2876  image->page.height);
2877  break;
2878  }
2879  case 'M':
2880  {
2881  /*
2882  Magick filename - filename given incl. coder & read mods.
2883  */
2884  string=image->magick_filename;
2885  break;
2886  }
2887  case 'N': /* Number of images in the list. */
2888  {
2889  if ((image != (Image *) NULL) && (image->next == (Image *) NULL))
2890  (void) FormatLocaleString(value,MagickPathExtent,"%.20g\n",(double)
2891  GetImageListLength(image));
2892  else
2893  string="";
2894  break;
2895  }
2896  case 'O':
2897  {
2898  /*
2899  Layer canvas offset with sign = "+%X+%Y".
2900  */
2901  (void) FormatLocaleString(value,MaxTextExtent,"%+ld%+ld",(long)
2902  image->page.x,(long) image->page.y);
2903  break;
2904  }
2905  case 'P':
2906  {
2907  /*
2908  Layer canvas page size = "%Wx%H".
2909  */
2910  (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g",(double)
2911  image->page.width,(double) image->page.height);
2912  break;
2913  }
2914  case 'Q':
2915  {
2916  /*
2917  Image compression quality.
2918  */
2919  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2920  (image->quality == 0 ? 92 : image->quality));
2921  break;
2922  }
2923  case 'S':
2924  {
2925  /*
2926  Image scenes.
2927  */
2928  WarnNoImageInfoReturn("\"%%%c\"",letter);
2929  if (image_info->number_scenes == 0)
2930  string="2147483647";
2931  else
2932  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2933  image_info->scene+image_info->number_scenes);
2934  break;
2935  }
2936  case 'T':
2937  {
2938  /*
2939  Image time delay for animations.
2940  */
2941  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2942  image->delay);
2943  break;
2944  }
2945  case 'U':
2946  {
2947  /*
2948  Image resolution units.
2949  */
2950  (void) FormatLocaleString(value,MaxTextExtent,"%s",
2951  CommandOptionToMnemonic(MagickResolutionOptions,(ssize_t)
2952  image->units));
2953  break;
2954  }
2955  case 'W':
2956  {
2957  /*
2958  Layer canvas width.
2959  */
2960  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
2961  image->page.width);
2962  break;
2963  }
2964  case 'X':
2965  {
2966  /*
2967  Layer canvas X offset.
2968  */
2969  (void) FormatLocaleString(value,MaxTextExtent,"%+.20g",(double)
2970  image->page.x);
2971  break;
2972  }
2973  case 'Y':
2974  {
2975  /*
2976  Layer canvas Y offset.
2977  */
2978  (void) FormatLocaleString(value,MaxTextExtent,"%+.20g",(double)
2979  image->page.y);
2980  break;
2981  }
2982  case 'Z':
2983  {
2984  /*
2985  Zero filename.
2986  */
2987  WarnNoImageInfoReturn("\"%%%c\"",letter);
2988  string=image_info->zero;
2989  break;
2990  }
2991  case '@':
2992  {
2994  page;
2995 
2996  /*
2997  Image bounding box.
2998  */
2999  page=GetImageBoundingBox(image,&image->exception);
3000  (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g%+.20g%+.20g",
3001  (double) page.width,(double) page.height,(double) page.x,(double)
3002  page.y);
3003  break;
3004  }
3005  case '#':
3006  {
3007  /*
3008  Image signature.
3009  */
3010  if ((image->columns != 0) && (image->rows != 0))
3011  (void) SignatureImage(image);
3012  string=GetImageProperty(image,"signature");
3013  break;
3014  }
3015  case '%':
3016  {
3017  /*
3018  Percent escaped.
3019  */
3020  string="%";
3021  break;
3022  }
3023  }
3024  if (*value != '\0')
3025  string=value;
3026  if (string != (char *) NULL)
3027  {
3028  (void) SetImageArtifact(image,"get-property",string);
3029  return(GetImageArtifact(image,"get-property"));
3030  }
3031  return((char *) NULL);
3032 }
3033 
3034 MagickExport const char *GetMagickProperty(const ImageInfo *image_info,
3035  Image *image,const char *property)
3036 {
3037  char
3038  value[MaxTextExtent];
3039 
3040  const char
3041  *string;
3042 
3043  assert(property[0] != '\0');
3044  if (property[1] == '\0') /* single letter property request */
3045  return(GetMagickPropertyLetter(image_info,image,*property));
3046  *value='\0'; /* formatted string */
3047  string=(char *) NULL; /* constant string reference */
3048  switch (*property)
3049  {
3050  case 'b':
3051  {
3052  if ((LocaleCompare("base",property) == 0) ||
3053  (LocaleCompare("basename",property) == 0) )
3054  {
3055  GetPathComponent(image->magick_filename,BasePath,value);
3056  break;
3057  }
3058  if (LocaleCompare("bit-depth",property) == 0)
3059  {
3060  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3061  GetImageDepth(image,&image->exception));
3062  break;
3063  }
3064  if (LocaleCompare("bounding-box",property) == 0)
3065  {
3067  geometry;
3068 
3069  geometry=GetImageBoundingBox(image,&image->exception);
3070  (void) FormatLocaleString(value,MagickPathExtent,"%g,%g %g,%g\n",
3071  (double) geometry.x,(double) geometry.y,
3072  (double) geometry.x+geometry.width,
3073  (double) geometry.y+geometry.height);
3074  break;
3075  }
3076  break;
3077  }
3078  case 'c':
3079  {
3080  if (LocaleCompare("channels",property) == 0)
3081  {
3082  /*
3083  Image channels.
3084  */
3085  (void) FormatLocaleString(value,MaxTextExtent,"%s",
3086  CommandOptionToMnemonic(MagickColorspaceOptions,(ssize_t)
3087  image->colorspace));
3088  LocaleLower(value);
3089  if (image->matte != MagickFalse)
3090  (void) ConcatenateMagickString(value,"a",MaxTextExtent);
3091  break;
3092  }
3093  if (LocaleCompare("colors",property) == 0)
3094  {
3095  image->colors=GetNumberColors(image,(FILE *) NULL,&image->exception);
3096  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3097  image->colors);
3098  break;
3099  }
3100  if (LocaleCompare("colorspace",property) == 0)
3101  {
3102  string=CommandOptionToMnemonic(MagickColorspaceOptions,(ssize_t)
3103  image->colorspace);
3104  break;
3105  }
3106  if (LocaleCompare("compose",property) == 0)
3107  {
3108  string=CommandOptionToMnemonic(MagickComposeOptions,(ssize_t)
3109  image->compose);
3110  break;
3111  }
3112  if (LocaleCompare("compression",property) == 0)
3113  {
3114  string=CommandOptionToMnemonic(MagickCompressOptions,(ssize_t)
3115  image->compression);
3116  break;
3117  }
3118  if (LocaleCompare("copyright",property) == 0)
3119  {
3120  (void) CopyMagickString(value,GetMagickCopyright(),MaxTextExtent);
3121  break;
3122  }
3123  break;
3124  }
3125  case 'd':
3126  {
3127  if (LocaleCompare("depth",property) == 0)
3128  {
3129  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3130  image->depth);
3131  break;
3132  }
3133  if (LocaleCompare("directory",property) == 0)
3134  {
3135  GetPathComponent(image->magick_filename,HeadPath,value);
3136  break;
3137  }
3138  break;
3139  }
3140  case 'e':
3141  {
3142  if (LocaleCompare("entropy",property) == 0)
3143  {
3144  double
3145  entropy;
3146 
3147  (void) GetImageChannelEntropy(image,image_info->channel,&entropy,
3148  &image->exception);
3149  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3150  GetMagickPrecision(),entropy);
3151  break;
3152  }
3153  if (LocaleCompare("extension",property) == 0)
3154  {
3155  GetPathComponent(image->magick_filename,ExtensionPath,value);
3156  break;
3157  }
3158  break;
3159  }
3160  case 'g':
3161  {
3162  if (LocaleCompare("gamma",property) == 0)
3163  {
3164  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3165  GetMagickPrecision(),image->gamma);
3166  break;
3167  }
3168  if ((image_info != (ImageInfo *) NULL) &&
3169  (LocaleCompare("group",property) == 0))
3170  {
3171  (void) FormatLocaleString(value,MaxTextExtent,"0x%lx",(unsigned long)
3172  image_info->group);
3173  break;
3174  }
3175  break;
3176  }
3177  case 'h':
3178  {
3179  if (LocaleCompare("height",property) == 0)
3180  {
3181  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",
3182  image->magick_rows != 0 ? (double) image->magick_rows : 256.0);
3183  break;
3184  }
3185  break;
3186  }
3187  case 'i':
3188  {
3189  if (LocaleCompare("input",property) == 0)
3190  {
3191  string=image->filename;
3192  break;
3193  }
3194  if (LocaleCompare("interlace",property) == 0)
3195  {
3196  string=CommandOptionToMnemonic(MagickInterlaceOptions,(ssize_t)
3197  image->interlace);
3198  break;
3199  }
3200  break;
3201  }
3202  case 'k':
3203  {
3204  if (LocaleCompare("kurtosis",property) == 0)
3205  {
3206  double
3207  kurtosis,
3208  skewness;
3209 
3210  (void) GetImageChannelKurtosis(image,image_info->channel,&kurtosis,
3211  &skewness,&image->exception);
3212  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3213  GetMagickPrecision(),kurtosis);
3214  break;
3215  }
3216  break;
3217  }
3218  case 'm':
3219  {
3220  if (LocaleCompare("magick",property) == 0)
3221  {
3222  string=image->magick;
3223  break;
3224  }
3225  if ((LocaleCompare("max",property) == 0) ||
3226  (LocaleCompare("maxima",property) == 0))
3227  {
3228  double
3229  maximum,
3230  minimum;
3231 
3232  (void) GetImageChannelRange(image,image_info->channel,&minimum,
3233  &maximum,&image->exception);
3234  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3235  GetMagickPrecision(),maximum);
3236  break;
3237  }
3238  if (LocaleCompare("mean",property) == 0)
3239  {
3240  double
3241  mean,
3242  standard_deviation;
3243 
3244  (void) GetImageChannelMean(image,image_info->channel,&mean,
3245  &standard_deviation,&image->exception);
3246  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3247  GetMagickPrecision(),mean);
3248  break;
3249  }
3250  if ((LocaleCompare("min",property) == 0) ||
3251  (LocaleCompare("minima",property) == 0))
3252  {
3253  double
3254  maximum,
3255  minimum;
3256 
3257  (void) GetImageChannelRange(image,image_info->channel,&minimum,
3258  &maximum,&image->exception);
3259  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3260  GetMagickPrecision(),minimum);
3261  break;
3262  }
3263  break;
3264  }
3265  case 'o':
3266  {
3267  if (LocaleCompare("opaque",property) == 0)
3268  {
3269  MagickBooleanType
3270  opaque;
3271 
3272  opaque=IsOpaqueImage(image,&image->exception);
3273  (void) CopyMagickString(value,opaque != MagickFalse ? "true" :
3274  "false",MaxTextExtent);
3275  break;
3276  }
3277  if (LocaleCompare("orientation",property) == 0)
3278  {
3279  string=CommandOptionToMnemonic(MagickOrientationOptions,(ssize_t)
3280  image->orientation);
3281  break;
3282  }
3283  if ((image_info != (ImageInfo *) NULL) &&
3284  (LocaleCompare("output",property) == 0))
3285  {
3286  (void) CopyMagickString(value,image_info->filename,MaxTextExtent);
3287  break;
3288  }
3289  break;
3290  }
3291  case 'p':
3292  {
3293  if (LocaleCompare("page",property) == 0)
3294  {
3295  (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g",(double)
3296  image->page.width,(double) image->page.height);
3297  break;
3298  }
3299  if (LocaleNCompare("papersize:",property,10) == 0)
3300  {
3301  char
3302  *papersize;
3303 
3304  *value='\0';
3305  papersize=GetPageGeometry(property+10);
3306  if (papersize != (const char *) NULL)
3307  {
3309  page = { 0, 0, 0, 0 };
3310 
3311  (void) ParseAbsoluteGeometry(papersize,&page);
3312  (void) FormatLocaleString(value,MaxTextExtent,"%.20gx%.20g",
3313  (double) page.width,(double) page.height);
3314  papersize=DestroyString(papersize);
3315  }
3316  break;
3317  }
3318 #if defined(MAGICKCORE_LCMS_DELEGATE)
3319  if (LocaleCompare("profile:icc",property) == 0 ||
3320  LocaleCompare("profile:icm",property) == 0)
3321  {
3322 #if !defined(LCMS_VERSION) || (LCMS_VERSION < 2000)
3323 #define cmsUInt32Number DWORD
3324 #endif
3325 
3326  const StringInfo
3327  *profile;
3328 
3329  cmsHPROFILE
3330  icc_profile;
3331 
3332  profile=GetImageProfile(image,property+8);
3333  if (profile == (StringInfo *) NULL)
3334  break;
3335 
3336  icc_profile=cmsOpenProfileFromMem(GetStringInfoDatum(profile),
3337  (cmsUInt32Number) GetStringInfoLength(profile));
3338  if (icc_profile != (cmsHPROFILE *) NULL)
3339  {
3340 #if defined(LCMS_VERSION) && (LCMS_VERSION < 2000)
3341  string=cmsTakeProductName(icc_profile);
3342 #else
3343  (void) cmsGetProfileInfoASCII(icc_profile,cmsInfoDescription,
3344  "en","US",value,MaxTextExtent);
3345 #endif
3346  (void) cmsCloseProfile(icc_profile);
3347  }
3348  }
3349 #endif
3350  if (LocaleCompare("printsize.x",property) == 0)
3351  {
3352  (void) FormatLocaleString(value,MagickPathExtent,"%.*g",
3353  GetMagickPrecision(),PerceptibleReciprocal(image->x_resolution)*
3354  image->columns);
3355  break;
3356  }
3357  if (LocaleCompare("printsize.y",property) == 0)
3358  {
3359  (void) FormatLocaleString(value,MagickPathExtent,"%.*g",
3360  GetMagickPrecision(),PerceptibleReciprocal(image->y_resolution)*
3361  image->rows);
3362  break;
3363  }
3364  if (LocaleCompare("profiles",property) == 0)
3365  {
3366  const char
3367  *name;
3368 
3369  ResetImageProfileIterator(image);
3370  name=GetNextImageProfile(image);
3371  if (name != (char *) NULL)
3372  {
3373  (void) CopyMagickString(value,name,MaxTextExtent);
3374  name=GetNextImageProfile(image);
3375  while (name != (char *) NULL)
3376  {
3377  ConcatenateMagickString(value,",",MaxTextExtent);
3378  ConcatenateMagickString(value,name,MaxTextExtent);
3379  name=GetNextImageProfile(image);
3380  }
3381  }
3382  break;
3383  }
3384  break;
3385  }
3386  case 'q':
3387  {
3388  if (LocaleCompare("quality",property) == 0)
3389  {
3390  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3391  image->quality);
3392  break;
3393  }
3394  break;
3395  }
3396  case 'r':
3397  {
3398  if (LocaleCompare("rendering-intent",property) == 0)
3399  {
3400  string=CommandOptionToMnemonic(MagickIntentOptions,(ssize_t)
3401  image->rendering_intent);
3402  break;
3403  }
3404  if (LocaleCompare("resolution.x",property) == 0)
3405  {
3406  (void) FormatLocaleString(value,MaxTextExtent,"%g",
3407  image->x_resolution);
3408  break;
3409  }
3410  if (LocaleCompare("resolution.y",property) == 0)
3411  {
3412  (void) FormatLocaleString(value,MaxTextExtent,"%g",
3413  image->y_resolution);
3414  break;
3415  }
3416  break;
3417  }
3418  case 's':
3419  {
3420  if (LocaleCompare("scene",property) == 0)
3421  {
3422  if ((image_info != (ImageInfo *) NULL) &&
3423  (image_info->number_scenes != 0))
3424  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3425  image_info->scene);
3426  else
3427  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3428  image->scene);
3429  break;
3430  }
3431  if (LocaleCompare("scenes",property) == 0)
3432  {
3433  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3434  GetImageListLength(image));
3435  break;
3436  }
3437  if (LocaleCompare("size",property) == 0)
3438  {
3439  (void) FormatMagickSize(GetBlobSize(image),MagickFalse,value);
3440  break;
3441  }
3442  if (LocaleCompare("skewness",property) == 0)
3443  {
3444  double
3445  kurtosis,
3446  skewness;
3447 
3448  (void) GetImageChannelKurtosis(image,image_info->channel,&kurtosis,
3449  &skewness,&image->exception);
3450  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3451  GetMagickPrecision(),skewness);
3452  break;
3453  }
3454  if ((LocaleCompare("standard-deviation",property) == 0) ||
3455  (LocaleCompare("standard_deviation",property) == 0))
3456  {
3457  double
3458  mean,
3459  standard_deviation;
3460 
3461  (void) GetImageChannelMean(image,image_info->channel,&mean,
3462  &standard_deviation,&image->exception);
3463  (void) FormatLocaleString(value,MaxTextExtent,"%.*g",
3464  GetMagickPrecision(),standard_deviation);
3465  break;
3466  }
3467  break;
3468  }
3469  case 't':
3470  {
3471  if (LocaleCompare("type",property) == 0)
3472  {
3473  string=CommandOptionToMnemonic(MagickTypeOptions,(ssize_t)
3474  IdentifyImageType(image,&image->exception));
3475  break;
3476  }
3477  break;
3478  }
3479  case 'u':
3480  {
3481  if ((image_info != (ImageInfo *) NULL) &&
3482  (LocaleCompare("unique",property) == 0))
3483  {
3484  string=image_info->unique;
3485  break;
3486  }
3487  if (LocaleCompare("units",property) == 0)
3488  {
3489  /*
3490  Image resolution units.
3491  */
3492  string=CommandOptionToMnemonic(MagickResolutionOptions,(ssize_t)
3493  image->units);
3494  break;
3495  }
3496  break;
3497  }
3498  case 'v':
3499  {
3500  if (LocaleCompare("version",property) == 0)
3501  {
3502  string=GetMagickVersion((size_t *) NULL);
3503  break;
3504  }
3505  break;
3506  }
3507  case 'w':
3508  {
3509  if (LocaleCompare("width",property) == 0)
3510  {
3511  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
3512  (image->magick_columns != 0 ? image->magick_columns : 256));
3513  break;
3514  }
3515  break;
3516  }
3517  case 'x': /* FUTURE: Obsolete X resolution */
3518  {
3519  if ((LocaleCompare("xresolution",property) == 0) ||
3520  (LocaleCompare("x-resolution",property) == 0) )
3521  {
3522  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",
3523  image->x_resolution);
3524  break;
3525  }
3526  break;
3527  }
3528  case 'y': /* FUTURE: Obsolete Y resolution */
3529  {
3530  if ((LocaleCompare("yresolution",property) == 0) ||
3531  (LocaleCompare("y-resolution",property) == 0) )
3532  {
3533  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",
3534  image->y_resolution);
3535  break;
3536  }
3537  break;
3538  }
3539  case 'z':
3540  {
3541  if ((image_info != (ImageInfo *) NULL) &&
3542  (LocaleCompare("zero",property) == 0))
3543  {
3544  string=image_info->zero;
3545  break;
3546  }
3547  break;
3548  }
3549  }
3550  if (*value != '\0')
3551  string=value;
3552  if (string != (char *) NULL)
3553  {
3554  (void) SetImageArtifact(image,"get-property", string);
3555  return(GetImageArtifact(image,"get-property"));
3556  }
3557  return((char *) NULL);
3558 }
3559 ␌
3560 /*
3561 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3562 % %
3563 % %
3564 % %
3565 % G e t N e x t I m a g e P r o p e r t y %
3566 % %
3567 % %
3568 % %
3569 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3570 %
3571 % GetNextImageProperty() gets the next free-form string property name.
3572 %
3573 % The format of the GetNextImageProperty method is:
3574 %
3575 % char *GetNextImageProperty(const Image *image)
3576 %
3577 % A description of each parameter follows:
3578 %
3579 % o image: the image.
3580 %
3581 */
3582 MagickExport char *GetNextImageProperty(const Image *image)
3583 {
3584  assert(image != (Image *) NULL);
3585  assert(image->signature == MagickCoreSignature);
3586  if (IsEventLogging() != MagickFalse)
3587  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
3588  image->filename);
3589  if (image->properties == (void *) NULL)
3590  return((char *) NULL);
3591  return((char *) GetNextKeyInSplayTree((SplayTreeInfo *) image->properties));
3592 }
3593 ␌
3594 /*
3595 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3596 % %
3597 % %
3598 % %
3599 % I n t e r p r e t I m a g e P r o p e r t i e s %
3600 % %
3601 % %
3602 % %
3603 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3604 %
3605 % InterpretImageProperties() replaces any embedded formatting characters with
3606 % the appropriate image property and returns the interpreted text.
3607 %
3608 % This searches for and replaces
3609 % \n \r \% replaced by newline, return, and percent resp.
3610 % &lt; &gt; &amp; replaced by '<', '>', '&' resp.
3611 % %% replaced by percent
3612 %
3613 % %x %[x] where 'x' is a single letter prosperity, case sensitive).
3614 % %[type:name] where 'type' a is special and known prefix.
3615 % %[name] where 'name' is a specifically known attribute, calculated
3616 % value, or a per-image property string name, or a per-image
3617 % 'artifact' (as generated from a global option).
3618 % It may contain ':' as long as the prefix is not special.
3619 %
3620 % Single letter % substitutions will only happen if the character before the
3621 % percent is NOT a number. But braced substitutions will always be performed.
3622 % This prevents the typical usage of percent in a interpreted geometry
3623 % argument from being substituted when the percent is a geometry flag.
3624 %
3625 % If 'glob-expressions' ('*' or '?' characters) is used for 'name' it may be
3626 % used as a search pattern to print multiple lines of "name=value\n" pairs of
3627 % the associacted set of properities.
3628 %
3629 % The returned string must be freed using DestroyString() by the caller.
3630 %
3631 % The format of the InterpretImageProperties method is:
3632 %
3633 % char *InterpretImageProperties(const ImageInfo *image_info,Image *image,
3634 % const char *embed_text)
3635 %
3636 % A description of each parameter follows:
3637 %
3638 % o image_info: the image info.
3639 %
3640 % o image: the image.
3641 %
3642 % o embed_text: the address of a character string containing the embedded
3643 % formatting characters.
3644 %
3645 */
3646 MagickExport char *InterpretImageProperties(const ImageInfo *image_info,
3647  Image *image,const char *embed_text)
3648 {
3649 #define ExtendInterpretText(string_length) \
3650 { \
3651  size_t length=(string_length); \
3652  if ((size_t) (q-interpret_text+length+1) >= extent) \
3653  { \
3654  extent+=length; \
3655  interpret_text=(char *) ResizeQuantumMemory(interpret_text,extent+ \
3656  MaxTextExtent,sizeof(*interpret_text)); \
3657  if (interpret_text == (char *) NULL) \
3658  { \
3659  if (property_info != image_info) \
3660  property_info=DestroyImageInfo(property_info); \
3661  return((char *) NULL); \
3662  } \
3663  q=interpret_text+strlen(interpret_text); \
3664  } \
3665 }
3666 
3667 #define AppendKeyValue2Text(key,value)\
3668 { \
3669  size_t length=strlen(key)+strlen(value)+2; \
3670  if ((size_t) (q-interpret_text+length+1) >= extent) \
3671  { \
3672  extent+=length; \
3673  interpret_text=(char *) ResizeQuantumMemory(interpret_text,extent+ \
3674  MaxTextExtent,sizeof(*interpret_text)); \
3675  if (interpret_text == (char *) NULL) \
3676  { \
3677  if (property_info != image_info) \
3678  property_info=DestroyImageInfo(property_info); \
3679  return((char *) NULL); \
3680  } \
3681  q=interpret_text+strlen(interpret_text); \
3682  } \
3683  q+=FormatLocaleString(q,extent,"%s=%s\n",(key),(value)); \
3684 }
3685 
3686 #define AppendString2Text(string) \
3687 { \
3688  size_t length=strlen((string)); \
3689  if ((size_t) (q-interpret_text+length+1) >= extent) \
3690  { \
3691  extent+=length; \
3692  interpret_text=(char *) ResizeQuantumMemory(interpret_text,extent+ \
3693  MaxTextExtent,sizeof(*interpret_text)); \
3694  if (interpret_text == (char *) NULL) \
3695  { \
3696  if (property_info != image_info) \
3697  property_info=DestroyImageInfo(property_info); \
3698  return((char *) NULL); \
3699  } \
3700  q=interpret_text+strlen(interpret_text); \
3701  } \
3702  (void) CopyMagickString(q,(string),extent); \
3703  q+=length; \
3704 }
3705 
3706  char
3707  *interpret_text;
3708 
3709  ImageInfo
3710  *property_info;
3711 
3712  char
3713  *q; /* current position in interpret_text */
3714 
3715  const char
3716  *p; /* position in embed_text string being expanded */
3717 
3718  size_t
3719  extent; /* allocated length of interpret_text */
3720 
3721  MagickBooleanType
3722  number;
3723 
3724  assert(image != (Image *) NULL);
3725  assert(image->signature == MagickCoreSignature);
3726  if (IsEventLogging() != MagickFalse)
3727  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3728  if (embed_text == (const char *) NULL)
3729  return(ConstantString(""));
3730  p=embed_text;
3731  while ((isspace((int) ((unsigned char) *p)) != 0) && (*p != '\0'))
3732  p++;
3733  if (*p == '\0')
3734  return(ConstantString(""));
3735  if ((*p == '@') && (IsPathAccessible(p+1) != MagickFalse))
3736  {
3737  /*
3738  Replace string from file.
3739  */
3740  if (IsRightsAuthorized(PathPolicyDomain,ReadPolicyRights,p) == MagickFalse)
3741  {
3742  errno=EPERM;
3743  (void) ThrowMagickException(&image->exception,GetMagickModule(),
3744  PolicyError,"NotAuthorized","`%s'",p);
3745  return(ConstantString(""));
3746  }
3747  interpret_text=FileToString(p,~0UL,&image->exception);
3748  if (interpret_text != (char *) NULL)
3749  return(interpret_text);
3750  }
3751  /*
3752  Translate any embedded format characters.
3753  */
3754  if (image_info != (ImageInfo *) NULL)
3755  property_info=(ImageInfo *) image_info;
3756  else
3757  property_info=CloneImageInfo(image_info);
3758  interpret_text=AcquireString(embed_text); /* new string with extra space */
3759  extent=MaxTextExtent; /* how many extra space */
3760  number=MagickFalse; /* is last char a number? */
3761  for (q=interpret_text; *p!='\0';
3762  number=(isdigit((int) ((unsigned char) *p))) ? MagickTrue : MagickFalse,p++)
3763  {
3764  /*
3765  Look for the various escapes, (and handle other specials).
3766  */
3767  *q='\0';
3768  ExtendInterpretText(MaxTextExtent);
3769  switch (*p)
3770  {
3771  case '\\':
3772  {
3773  switch (*(p+1))
3774  {
3775  case '\0':
3776  continue;
3777  case 'r': /* convert to RETURN */
3778  {
3779  *q++='\r';
3780  p++;
3781  continue;
3782  }
3783  case 'n': /* convert to NEWLINE */
3784  {
3785  *q++='\n';
3786  p++;
3787  continue;
3788  }
3789  case '\n': /* EOL removal UNIX,MacOSX */
3790  {
3791  p++;
3792  continue;
3793  }
3794  case '\r': /* EOL removal DOS,Windows */
3795  {
3796  p++;
3797  if (*p == '\n') /* return-newline EOL */
3798  p++;
3799  continue;
3800  }
3801  default:
3802  {
3803  p++;
3804  *q++=(*p);
3805  }
3806  }
3807  continue;
3808  }
3809  case '&':
3810  {
3811  if (LocaleNCompare("&lt;",p,4) == 0)
3812  {
3813  *q++='<';
3814  p+=3;
3815  }
3816  else
3817  if (LocaleNCompare("&gt;",p,4) == 0)
3818  {
3819  *q++='>';
3820  p+=3;
3821  }
3822  else
3823  if (LocaleNCompare("&amp;",p,5) == 0)
3824  {
3825  *q++='&';
3826  p+=4;
3827  }
3828  else
3829  *q++=(*p);
3830  continue;
3831  }
3832  case '%':
3833  break; /* continue to next set of handlers */
3834  default:
3835  {
3836  *q++=(*p); /* any thing else is 'as normal' */
3837  continue;
3838  }
3839  }
3840  p++; /* advance beyond the percent */
3841  /*
3842  Doubled percent - or percent at end of string.
3843  */
3844  if ((*p == '\0') || (*p == '\'') || (*p == '"'))
3845  p--;
3846  if (*p == '%')
3847  {
3848  *q++='%';
3849  continue;
3850  }
3851  /*
3852  Single letter escapes %c.
3853  */
3854  if (*p != '[')
3855  {
3856  const char
3857  *value;
3858 
3859  /* But only if not preceeded by a number! */
3860  if (number != MagickFalse)
3861  {
3862  *q++='%'; /* do NOT substitute the percent */
3863  p--; /* back up one */
3864  continue;
3865  }
3866  value=GetMagickPropertyLetter(property_info,image,*p);
3867  if (value != (char *) NULL)
3868  {
3869  AppendString2Text(value);
3870  continue;
3871  }
3872  (void) ThrowMagickException(&image->exception,GetMagickModule(),
3873  OptionWarning,"UnknownImageProperty","\"%%%c\"",*p);
3874  continue;
3875  }
3876  {
3877  char
3878  pattern[2*MaxTextExtent];
3879 
3880  const char
3881  *key,
3882  *value;
3883 
3884  ssize_t
3885  len;
3886 
3887  ssize_t
3888  depth;
3889 
3890  /*
3891  Braced Percent Escape %[...]
3892  */
3893  p++; /* advance p to just inside the opening brace */
3894  depth=1;
3895  if ( *p == ']' )
3896  {
3897  (void) ThrowMagickException(&image->exception,GetMagickModule(),
3898  OptionWarning,"UnknownImageProperty","\"%%[]\"");
3899  break;
3900  }
3901  for (len=0; len<(MaxTextExtent-1L) && (*p != '\0');)
3902  {
3903  if ((*p == '\\') && (*(p+1) != '\0'))
3904  {
3905  /*
3906  Skip escaped braces within braced pattern.
3907  */
3908  pattern[len++]=(*p++);
3909  pattern[len++]=(*p++);
3910  continue;
3911  }
3912  if (*p == '[')
3913  depth++;
3914  if (*p == ']')
3915  depth--;
3916  if (depth <= 0)
3917  break;
3918  pattern[len++]=(*p++);
3919  }
3920  pattern[len]='\0';
3921  if (depth != 0)
3922  {
3923  /*
3924  Check for unmatched final ']' for "%[...]".
3925  */
3926  if (len >= 64)
3927  {
3928  pattern[61] = '.'; /* truncate string for error message */
3929  pattern[62] = '.';
3930  pattern[63] = '.';
3931  pattern[64] = '\0';
3932  }
3933  (void) ThrowMagickException(&image->exception,GetMagickModule(),
3934  OptionError,"UnbalancedBraces","\"%%[%s\"",pattern);
3935  interpret_text=DestroyString(interpret_text);
3936  if (property_info != image_info)
3937  property_info=DestroyImageInfo(property_info);
3938  return((char *) NULL);
3939  }
3940  /*
3941  Special Lookup Prefixes %[prefix:...]
3942  */
3943  if (LocaleNCompare("fx:",pattern,3) == 0)
3944  {
3945  double
3946  value;
3947 
3948  FxInfo
3949  *fx_info;
3950 
3951  MagickBooleanType
3952  status;
3953 
3954  /*
3955  FX - value calculator.
3956  */
3957  fx_info=AcquireFxInfo(image,pattern+3);
3958  status=FxEvaluateChannelExpression(fx_info,property_info->channel,0,0,
3959  &value,&image->exception);
3960  fx_info=DestroyFxInfo(fx_info);
3961  if (status != MagickFalse)
3962  {
3963  char
3964  result[MagickPathExtent];
3965 
3966  (void) FormatLocaleString(result,MagickPathExtent,"%.*g",
3967  GetMagickPrecision(),(double) value);
3968  AppendString2Text(result);
3969  }
3970  continue;
3971  }
3972  if (LocaleNCompare("option:",pattern,7) == 0)
3973  {
3974  /*
3975  Option - direct global option lookup (with globbing).
3976  */
3977  if (IsGlob(pattern+7) != MagickFalse)
3978  {
3979  ResetImageOptionIterator(property_info);
3980  while ((key=GetNextImageOption(property_info)) != (const char *) NULL)
3981  if (GlobExpression(key,pattern+7,MagickTrue) != MagickFalse)
3982  {
3983  value=GetImageOption(property_info,key);
3984  if (value != (const char *) NULL)
3985  AppendKeyValue2Text(key,value);
3986  /* else - assertion failure? key but no value! */
3987  }
3988  continue;
3989  }
3990  value=GetImageOption(property_info,pattern+7);
3991  if (value != (char *) NULL)
3992  AppendString2Text(value);
3993  /* else - no global option of this specifc name */
3994  continue;
3995  }
3996  if (LocaleNCompare("artifact:",pattern,9) == 0)
3997  {
3998  /*
3999  Artifact - direct image artifact lookup (with glob).
4000  */
4001  if (IsGlob(pattern+9) != MagickFalse)
4002  {
4003  ResetImageArtifactIterator(image);
4004  while ((key=GetNextImageArtifact(image)) != (const char *) NULL)
4005  if (GlobExpression(key,pattern+9,MagickTrue) != MagickFalse)
4006  {
4007  value=GetImageArtifact(image,key);
4008  if (value != (const char *) NULL)
4009  AppendKeyValue2Text(key,value);
4010  /* else - assertion failure? key but no value! */
4011  }
4012  continue;
4013  }
4014  value=GetImageArtifact(image,pattern+9);
4015  if (value != (char *) NULL)
4016  AppendString2Text(value);
4017  /* else - no artifact of this specifc name */
4018  continue;
4019  }
4020  /*
4021  Handle special image properties, for example:
4022  %[exif:...] %[fx:...] %[pixel:...].
4023 
4024  FUTURE: handle %[property:...] prefix - abort other lookups.
4025  */
4026  value=GetImageProperty(image,pattern);
4027  if (value != (const char *) NULL)
4028  {
4029  AppendString2Text(value);
4030  continue;
4031  }
4032  /*
4033  Handle property 'glob' patterns such as:
4034  %[*] %[user:array_??] %[filename:e*]
4035  */
4036  if (IsGlob(pattern) != MagickFalse)
4037  {
4038  ResetImagePropertyIterator(image);
4039  while ((key=GetNextImageProperty(image)) != (const char *) NULL)
4040  if (GlobExpression(key,pattern,MagickTrue) != MagickFalse)
4041  {
4042  value=GetImageProperty(image,key);
4043  if (value != (const char *) NULL)
4044  AppendKeyValue2Text(key,value);
4045  /* else - assertion failure? */
4046  }
4047  continue;
4048  }
4049  /*
4050  Look for a known property or image attribute such as
4051  %[basename] %[density] %[delay]. Also handles a braced single
4052  letter: %[b] %[G] %[g].
4053  */
4054  value=GetMagickProperty(property_info,image,pattern);
4055  if (value != (const char *) NULL)
4056  {
4057  AppendString2Text(value);
4058  continue;
4059  }
4060  /*
4061  Look for a per-image Artifact (user option, post-interpreted)
4062  */
4063  value=GetImageArtifact(image,pattern);
4064  if (value != (char *) NULL)
4065  {
4066  AppendString2Text(value);
4067  continue;
4068  }
4069  /*
4070  Look for user option of this name (should never match in CLI usage).
4071  */
4072  value=GetImageOption(property_info,pattern);
4073  if (value != (char *) NULL)
4074  {
4075  AppendString2Text(value);
4076  continue;
4077  }
4078  /*
4079  Failed to find any match anywhere!
4080  */
4081  if (len >= 64)
4082  {
4083  pattern[61] = '.'; /* truncate string for error message */
4084  pattern[62] = '.';
4085  pattern[63] = '.';
4086  pattern[64] = '\0';
4087  }
4088  (void) ThrowMagickException(&image->exception,GetMagickModule(),
4089  OptionWarning,"UnknownImageProperty","\"%%[%s]\"",pattern);
4090  /* continue */
4091  } /* Braced Percent Escape */
4092  } /* for each char in 'embed_text' */
4093  *q='\0';
4094  if (property_info != image_info)
4095  property_info=DestroyImageInfo(property_info);
4096  return(interpret_text);
4097 }
4098 ␌
4099 /*
4100 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4101 % %
4102 % %
4103 % %
4104 % R e m o v e I m a g e P r o p e r t y %
4105 % %
4106 % %
4107 % %
4108 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4109 %
4110 % RemoveImageProperty() removes a property from the image and returns its
4111 % value.
4112 %
4113 % In this case the ConstantString() value returned should be freed by the
4114 % caller when finished.
4115 %
4116 % The format of the RemoveImageProperty method is:
4117 %
4118 % char *RemoveImageProperty(Image *image,const char *property)
4119 %
4120 % A description of each parameter follows:
4121 %
4122 % o image: the image.
4123 %
4124 % o property: the image property.
4125 %
4126 */
4127 MagickExport char *RemoveImageProperty(Image *image,const char *property)
4128 {
4129  char
4130  *value;
4131 
4132  assert(image != (Image *) NULL);
4133  assert(image->signature == MagickCoreSignature);
4134  if (IsEventLogging() != MagickFalse)
4135  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
4136  image->filename);
4137  if (image->properties == (void *) NULL)
4138  return((char *) NULL);
4139  value=(char *) RemoveNodeFromSplayTree((SplayTreeInfo *) image->properties,
4140  property);
4141  return(value);
4142 }
4143 ␌
4144 /*
4145 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4146 % %
4147 % %
4148 % %
4149 % 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 %
4150 % %
4151 % %
4152 % %
4153 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4154 %
4155 % ResetImagePropertyIterator() resets the image properties iterator. Use it
4156 % in conjunction with GetNextImageProperty() to iterate over all the values
4157 % associated with an image property.
4158 %
4159 % The format of the ResetImagePropertyIterator method is:
4160 %
4161 % ResetImagePropertyIterator(Image *image)
4162 %
4163 % A description of each parameter follows:
4164 %
4165 % o image: the image.
4166 %
4167 */
4168 MagickExport void ResetImagePropertyIterator(const Image *image)
4169 {
4170  assert(image != (Image *) NULL);
4171  assert(image->signature == MagickCoreSignature);
4172  if (IsEventLogging() != MagickFalse)
4173  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
4174  image->filename);
4175  if (image->properties == (void *) NULL)
4176  return;
4177  ResetSplayTreeIterator((SplayTreeInfo *) image->properties);
4178 }
4179 ␌
4180 /*
4181 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4182 % %
4183 % %
4184 % %
4185 % S e t I m a g e P r o p e r t y %
4186 % %
4187 % %
4188 % %
4189 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4190 %
4191 % SetImageProperty() saves the given string value either to specific known
4192 % attribute or to a freeform property string.
4193 %
4194 % The format of the SetImageProperty method is:
4195 %
4196 % MagickBooleanType SetImageProperty(Image *image,const char *property,
4197 % const char *value)
4198 %
4199 % A description of each parameter follows:
4200 %
4201 % o image: the image.
4202 %
4203 % o property: the image property.
4204 %
4205 % o values: the image property values.
4206 %
4207 */
4208 MagickExport MagickBooleanType SetImageProperty(Image *image,
4209  const char *property,const char *value)
4210 {
4212  *exception;
4213 
4214  MagickBooleanType
4215  status;
4216 
4217  MagickStatusType
4218  flags;
4219 
4220  size_t
4221  property_length;
4222 
4223 
4224  assert(image != (Image *) NULL);
4225  assert(image->signature == MagickCoreSignature);
4226  if (IsEventLogging() != MagickFalse)
4227  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
4228  image->filename);
4229  if (image->properties == (void *) NULL)
4230  image->properties=NewSplayTree(CompareSplayTreeString,
4231  RelinquishMagickMemory,RelinquishMagickMemory); /* create splay-tree */
4232  if (value == (const char *) NULL)
4233  return(DeleteImageProperty(image,property)); /* delete if NULL */
4234  exception=(&image->exception);
4235  property_length=strlen(property);
4236  if ((property_length > 2) && (*(property+(property_length-2)) == ':') &&
4237  (*(property+(property_length-1)) == '*'))
4238  {
4239  (void) ThrowMagickException(exception,GetMagickModule(),
4240  OptionWarning,"SetReadOnlyProperty","`%s'",property);
4241  return(MagickFalse);
4242  }
4243  /*
4244  FUTURE: These should produce 'illegal settings'
4245  * binary chars in p[roperty key
4246  * first letter must be a alphabetic
4247  * single letter property keys (read only)
4248  * known special prefix (read only, they don't get saved!)
4249  */
4250  status=MagickTrue;
4251  switch (*property)
4252  {
4253  case 'B':
4254  case 'b':
4255  {
4256  if (LocaleCompare("background",property) == 0)
4257  {
4258  (void) QueryColorDatabase(value,&image->background_color,exception);
4259  break;
4260  }
4261  if (LocaleCompare("bias",property) == 0)
4262  {
4263  image->bias=StringToDoubleInterval(value,(double) QuantumRange+1.0);
4264  break;
4265  }
4266  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4267  ConstantString(property),ConstantString(value));
4268  break;
4269  }
4270  case 'C':
4271  case 'c':
4272  {
4273  if (LocaleCompare("colorspace",property) == 0)
4274  {
4275  ssize_t
4276  colorspace;
4277 
4278  colorspace=ParseCommandOption(MagickColorspaceOptions,MagickFalse,
4279  value);
4280  if (colorspace < 0)
4281  break;
4282  status=SetImageColorspace(image,(ColorspaceType) colorspace);
4283  break;
4284  }
4285  if (LocaleCompare("compose",property) == 0)
4286  {
4287  ssize_t
4288  compose;
4289 
4290  compose=ParseCommandOption(MagickComposeOptions,MagickFalse,value);
4291  if (compose < 0)
4292  break;
4293  image->compose=(CompositeOperator) compose;
4294  break;
4295  }
4296  if (LocaleCompare("compress",property) == 0)
4297  {
4298  ssize_t
4299  compression;
4300 
4301  compression=ParseCommandOption(MagickCompressOptions,MagickFalse,
4302  value);
4303  if (compression < 0)
4304  break;
4305  image->compression=(CompressionType) compression;
4306  break;
4307  }
4308  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4309  ConstantString(property),ConstantString(value));
4310  break;
4311  }
4312  case 'D':
4313  case 'd':
4314  {
4315  if (LocaleCompare("delay",property) == 0)
4316  {
4317  GeometryInfo
4318  geometry_info;
4319 
4320  flags=ParseGeometry(value,&geometry_info);
4321  if ((flags & GreaterValue) != 0)
4322  {
4323  if (image->delay > (size_t) floor(geometry_info.rho+0.5))
4324  image->delay=(size_t) floor(geometry_info.rho+0.5);
4325  }
4326  else
4327  if ((flags & LessValue) != 0)
4328  {
4329  if ((double) image->delay < floor(geometry_info.rho+0.5))
4330  image->ticks_per_second=CastDoubleToLong(
4331  floor(geometry_info.sigma+0.5));
4332  }
4333  else
4334  image->delay=(size_t) floor(geometry_info.rho+0.5);
4335  if ((flags & SigmaValue) != 0)
4336  image->ticks_per_second=CastDoubleToLong(floor(
4337  geometry_info.sigma+0.5));
4338  break;
4339  }
4340  if (LocaleCompare("density",property) == 0)
4341  {
4342  GeometryInfo
4343  geometry_info;
4344 
4345  flags=ParseGeometry(value,&geometry_info);
4346  if ((flags & RhoValue) != 0)
4347  image->x_resolution=geometry_info.rho;
4348  image->y_resolution=image->x_resolution;
4349  if ((flags & SigmaValue) != 0)
4350  image->y_resolution=geometry_info.sigma;
4351  }
4352  if (LocaleCompare("depth",property) == 0)
4353  {
4354  image->depth=StringToUnsignedLong(value);
4355  break;
4356  }
4357  if (LocaleCompare("dispose",property) == 0)
4358  {
4359  ssize_t
4360  dispose;
4361 
4362  dispose=ParseCommandOption(MagickDisposeOptions,MagickFalse,value);
4363  if (dispose < 0)
4364  break;
4365  image->dispose=(DisposeType) dispose;
4366  break;
4367  }
4368  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4369  ConstantString(property),ConstantString(value));
4370  break;
4371  }
4372  case 'G':
4373  case 'g':
4374  {
4375  if (LocaleCompare("gamma",property) == 0)
4376  {
4377  image->gamma=StringToDouble(value,(char **) NULL);
4378  break;
4379  }
4380  if (LocaleCompare("gravity",property) == 0)
4381  {
4382  ssize_t
4383  gravity;
4384 
4385  gravity=ParseCommandOption(MagickGravityOptions,MagickFalse,value);
4386  if (gravity < 0)
4387  break;
4388  image->gravity=(GravityType) gravity;
4389  break;
4390  }
4391  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4392  ConstantString(property),ConstantString(value));
4393  break;
4394  }
4395  case 'I':
4396  case 'i':
4397  {
4398  if (LocaleCompare("intensity",property) == 0)
4399  {
4400  ssize_t
4401  intensity;
4402 
4403  intensity=ParseCommandOption(MagickPixelIntensityOptions,MagickFalse,
4404  value);
4405  if (intensity < 0)
4406  break;
4407  image->intensity=(PixelIntensityMethod) intensity;
4408  break;
4409  }
4410  if (LocaleCompare("interpolate",property) == 0)
4411  {
4412  ssize_t
4413  interpolate;
4414 
4415  interpolate=ParseCommandOption(MagickInterpolateOptions,MagickFalse,
4416  value);
4417  if (interpolate < 0)
4418  break;
4419  image->interpolate=(InterpolatePixelMethod) interpolate;
4420  break;
4421  }
4422  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4423  ConstantString(property),ConstantString(value));
4424  break;
4425  }
4426  case 'L':
4427  case 'l':
4428  {
4429  if (LocaleCompare("loop",property) == 0)
4430  {
4431  image->iterations=StringToUnsignedLong(value);
4432  break;
4433  }
4434  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4435  ConstantString(property),ConstantString(value));
4436  break;
4437  }
4438  case 'P':
4439  case 'p':
4440  {
4441  if (LocaleCompare("page",property) == 0)
4442  {
4443  char
4444  *geometry;
4445 
4446  geometry=GetPageGeometry(value);
4447  flags=ParseAbsoluteGeometry(geometry,&image->page);
4448  geometry=DestroyString(geometry);
4449  break;
4450  }
4451  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4452  ConstantString(property),ConstantString(value));
4453  break;
4454  }
4455  case 'R':
4456  case 'r':
4457  {
4458  if (LocaleCompare("rendering-intent",property) == 0)
4459  {
4460  ssize_t
4461  rendering_intent;
4462 
4463  rendering_intent=ParseCommandOption(MagickIntentOptions,MagickFalse,
4464  value);
4465  if (rendering_intent < 0)
4466  break;
4467  image->rendering_intent=(RenderingIntent) rendering_intent;
4468  break;
4469  }
4470  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4471  ConstantString(property),ConstantString(value));
4472  break;
4473  }
4474  case 'T':
4475  case 't':
4476  {
4477  if (LocaleCompare("tile-offset",property) == 0)
4478  {
4479  char
4480  *geometry;
4481 
4482  geometry=GetPageGeometry(value);
4483  flags=ParseAbsoluteGeometry(geometry,&image->tile_offset);
4484  geometry=DestroyString(geometry);
4485  break;
4486  }
4487  if (LocaleCompare("type",property) == 0)
4488  {
4489  ssize_t
4490  type;
4491 
4492  type=ParseCommandOption(MagickTypeOptions,MagickFalse,value);
4493  if (type < 0)
4494  return(MagickFalse);
4495  image->type=(ImageType) type;
4496  break;
4497  }
4498  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4499  ConstantString(property),ConstantString(value));
4500  break;
4501  }
4502  case 'U':
4503  case 'u':
4504  {
4505  if (LocaleCompare("units",property) == 0)
4506  {
4507  ssize_t
4508  units;
4509 
4510  units=ParseCommandOption(MagickResolutionOptions,MagickFalse,value);
4511  if (units < 0)
4512  break;
4513  image->units=(ResolutionType) units;
4514  break;
4515  }
4516  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4517  ConstantString(property),ConstantString(value));
4518  break;
4519  }
4520  default:
4521  {
4522  status=AddValueToSplayTree((SplayTreeInfo *) image->properties,
4523  ConstantString(property),ConstantString(value));
4524  break;
4525  }
4526  }
4527  return(status);
4528 }
Definition: fx.c:130
Definition: image.h:153