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