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