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