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