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