1 /* 2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 % % 4 % % 5 % L AAA Y Y EEEEE RRRR % 6 % L A A Y Y E R R % 7 % L AAAAA Y EEE RRRR % 8 % L A A Y E R R % 9 % LLLLL A A Y EEEEE R R % 10 % % 11 % MagickCore Image Layering Methods % 12 % % 13 % Software Design % 14 % Cristy % 15 % Anthony Thyssen % 16 % January 2006 % 17 % % 18 % % 19 % Copyright 1999-2016 ImageMagick Studio LLC, a non-profit organization % 20 % dedicated to making software imaging solutions freely available. % 21 % % 22 % You may not use this file except in compliance with the License. You may % 23 % obtain a copy of the License at % 24 % % 25 % http://www.imagemagick.org/script/license.php % 26 % % 27 % Unless required by applicable law or agreed to in writing, software % 28 % distributed under the License is distributed on an "AS IS" BASIS, % 29 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % 30 % See the License for the specific language governing permissions and % 31 % limitations under the License. % 32 % % 33 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 34 % 35 */ 36 37 /* 39 Include declarations. 40 */ 41 #include "MagickCore/studio.h" 42 #include "MagickCore/artifact.h" 43 #include "MagickCore/cache.h" 44 #include "MagickCore/channel.h" 45 #include "MagickCore/color.h" 46 #include "MagickCore/color-private.h" 47 #include "MagickCore/composite.h" 48 #include "MagickCore/effect.h" 49 #include "MagickCore/exception.h" 50 #include "MagickCore/exception-private.h" 51 #include "MagickCore/geometry.h" 52 #include "MagickCore/image.h" 53 #include "MagickCore/layer.h" 54 #include "MagickCore/list.h" 55 #include "MagickCore/memory_.h" 56 #include "MagickCore/monitor.h" 57 #include "MagickCore/monitor-private.h" 58 #include "MagickCore/option.h" 59 #include "MagickCore/pixel-accessor.h" 60 #include "MagickCore/property.h" 61 #include "MagickCore/profile.h" 62 #include "MagickCore/resource_.h" 63 #include "MagickCore/resize.h" 64 #include "MagickCore/statistic.h" 65 #include "MagickCore/string_.h" 66 #include "MagickCore/transform.h" 67 68 /* 70 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 71 % % 72 % % 73 % % 74 + C l e a r B o u n d s % 75 % % 76 % % 77 % % 78 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 79 % 80 % ClearBounds() Clear the area specified by the bounds in an image to 81 % transparency. This typically used to handle Background Disposal for the 82 % previous frame in an animation sequence. 83 % 84 % Warning: no bounds checks are performed, except for the null or missed 85 % image, for images that don't change. in all other cases bound must fall 86 % within the image. 87 % 88 % The format is: 89 % 90 % void ClearBounds(Image *image,RectangleInfo *bounds, 91 % ExceptionInfo *exception) 92 % 93 % A description of each parameter follows: 94 % 95 % o image: the image to had the area cleared in 96 % 97 % o bounds: the area to be clear within the imag image 98 % 99 % o exception: return any errors or warnings in this structure. 100 % 101 */ 102 static void ClearBounds(Image *image,RectangleInfo *bounds, 103 ExceptionInfo *exception) 104 { 105 ssize_t 106 y; 107 108 if (bounds->x < 0) 109 return; 110 if (image->alpha_trait == UndefinedPixelTrait) 111 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception); 112 for (y=0; y < (ssize_t) bounds->height; y++) 113 { 114 register ssize_t 115 x; 116 117 register Quantum 118 *magick_restrict q; 119 120 q=GetAuthenticPixels(image,bounds->x,bounds->y+y,bounds->width,1,exception); 121 if (q == (Quantum *) NULL) 122 break; 123 for (x=0; x < (ssize_t) bounds->width; x++) 124 { 125 SetPixelAlpha(image,TransparentAlpha,q); 126 q+=GetPixelChannels(image); 127 } 128 if (SyncAuthenticPixels(image,exception) == MagickFalse) 129 break; 130 } 131 } 132 133 /* 135 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 136 % % 137 % % 138 % % 139 + I s B o u n d s C l e a r e d % 140 % % 141 % % 142 % % 143 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 144 % 145 % IsBoundsCleared() tests whether any pixel in the bounds given, gets cleared 146 % when going from the first image to the second image. This typically used 147 % to check if a proposed disposal method will work successfully to generate 148 % the second frame image from the first disposed form of the previous frame. 149 % 150 % Warning: no bounds checks are performed, except for the null or missed 151 % image, for images that don't change. in all other cases bound must fall 152 % within the image. 153 % 154 % The format is: 155 % 156 % MagickBooleanType IsBoundsCleared(const Image *image1, 157 % const Image *image2,RectangleInfo bounds,ExceptionInfo *exception) 158 % 159 % A description of each parameter follows: 160 % 161 % o image1, image 2: the images to check for cleared pixels 162 % 163 % o bounds: the area to be clear within the imag image 164 % 165 % o exception: return any errors or warnings in this structure. 166 % 167 */ 168 static MagickBooleanType IsBoundsCleared(const Image *image1, 169 const Image *image2,RectangleInfo *bounds,ExceptionInfo *exception) 170 { 171 register const Quantum 172 *p, 173 *q; 174 175 register ssize_t 176 x; 177 178 ssize_t 179 y; 180 181 if (bounds->x < 0) 182 return(MagickFalse); 183 for (y=0; y < (ssize_t) bounds->height; y++) 184 { 185 p=GetVirtualPixels(image1,bounds->x,bounds->y+y,bounds->width,1,exception); 186 q=GetVirtualPixels(image2,bounds->x,bounds->y+y,bounds->width,1,exception); 187 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 188 break; 189 for (x=0; x < (ssize_t) bounds->width; x++) 190 { 191 if ((GetPixelAlpha(image1,p) >= (Quantum) (QuantumRange/2)) && 192 (GetPixelAlpha(image2,q) < (Quantum) (QuantumRange/2))) 193 break; 194 p+=GetPixelChannels(image1); 195 q+=GetPixelChannels(image2); 196 } 197 if (x < (ssize_t) bounds->width) 198 break; 199 } 200 return(y < (ssize_t) bounds->height ? MagickTrue : MagickFalse); 201 } 202 203 /* 205 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 206 % % 207 % % 208 % % 209 % C o a l e s c e I m a g e s % 210 % % 211 % % 212 % % 213 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 214 % 215 % CoalesceImages() composites a set of images while respecting any page 216 % offsets and disposal methods. GIF, MIFF, and MNG animation sequences 217 % typically start with an image background and each subsequent image 218 % varies in size and offset. A new image sequence is returned with all 219 % images the same size as the first images virtual canvas and composited 220 % with the next image in the sequence. 221 % 222 % The format of the CoalesceImages method is: 223 % 224 % Image *CoalesceImages(Image *image,ExceptionInfo *exception) 225 % 226 % A description of each parameter follows: 227 % 228 % o image: the image sequence. 229 % 230 % o exception: return any errors or warnings in this structure. 231 % 232 */ 233 MagickExport Image *CoalesceImages(const Image *image,ExceptionInfo *exception) 234 { 235 Image 236 *coalesce_image, 237 *dispose_image, 238 *previous; 239 240 register Image 241 *next; 242 243 RectangleInfo 244 bounds; 245 246 /* 247 Coalesce the image sequence. 248 */ 249 assert(image != (Image *) NULL); 250 assert(image->signature == MagickCoreSignature); 251 if (image->debug != MagickFalse) 252 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 253 assert(exception != (ExceptionInfo *) NULL); 254 assert(exception->signature == MagickCoreSignature); 255 next=GetFirstImageInList(image); 256 bounds=next->page; 257 if (bounds.width == 0) 258 { 259 bounds.width=next->columns; 260 if (bounds.x > 0) 261 bounds.width+=bounds.x; 262 } 263 if (bounds.height == 0) 264 { 265 bounds.height=next->rows; 266 if (bounds.y > 0) 267 bounds.height+=bounds.y; 268 } 269 bounds.x=0; 270 bounds.y=0; 271 coalesce_image=CloneImage(next,bounds.width,bounds.height,MagickTrue, 272 exception); 273 if (coalesce_image == (Image *) NULL) 274 return((Image *) NULL); 275 coalesce_image->background_color.alpha=(Quantum) TransparentAlpha; 276 (void) SetImageBackgroundColor(coalesce_image,exception); 277 coalesce_image->alpha_trait=next->alpha_trait; 278 coalesce_image->page=bounds; 279 coalesce_image->dispose=NoneDispose; 280 /* 281 Coalesce rest of the images. 282 */ 283 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception); 284 (void) CompositeImage(coalesce_image,next,CopyCompositeOp,MagickTrue, 285 next->page.x,next->page.y,exception); 286 next=GetNextImageInList(next); 287 for ( ; next != (Image *) NULL; next=GetNextImageInList(next)) 288 { 289 /* 290 Determine the bounds that was overlaid in the previous image. 291 */ 292 previous=GetPreviousImageInList(next); 293 bounds=previous->page; 294 bounds.width=previous->columns; 295 bounds.height=previous->rows; 296 if (bounds.x < 0) 297 { 298 bounds.width+=bounds.x; 299 bounds.x=0; 300 } 301 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) coalesce_image->columns) 302 bounds.width=coalesce_image->columns-bounds.x; 303 if (bounds.y < 0) 304 { 305 bounds.height+=bounds.y; 306 bounds.y=0; 307 } 308 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) coalesce_image->rows) 309 bounds.height=coalesce_image->rows-bounds.y; 310 /* 311 Replace the dispose image with the new coalesced image. 312 */ 313 if (GetPreviousImageInList(next)->dispose != PreviousDispose) 314 { 315 dispose_image=DestroyImage(dispose_image); 316 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception); 317 if (dispose_image == (Image *) NULL) 318 { 319 coalesce_image=DestroyImageList(coalesce_image); 320 return((Image *) NULL); 321 } 322 } 323 /* 324 Clear the overlaid area of the coalesced bounds for background disposal 325 */ 326 if (next->previous->dispose == BackgroundDispose) 327 ClearBounds(dispose_image,&bounds,exception); 328 /* 329 Next image is the dispose image, overlaid with next frame in sequence. 330 */ 331 coalesce_image->next=CloneImage(dispose_image,0,0,MagickTrue,exception); 332 coalesce_image->next->previous=coalesce_image; 333 previous=coalesce_image; 334 coalesce_image=GetNextImageInList(coalesce_image); 335 (void) CompositeImage(coalesce_image,next, 336 next->alpha_trait != UndefinedPixelTrait ? OverCompositeOp : CopyCompositeOp, 337 MagickTrue,next->page.x,next->page.y,exception); 338 (void) CloneImageProfiles(coalesce_image,next); 339 (void) CloneImageProperties(coalesce_image,next); 340 (void) CloneImageArtifacts(coalesce_image,next); 341 coalesce_image->page=previous->page; 342 /* 343 If a pixel goes opaque to transparent, use background dispose. 344 */ 345 if (IsBoundsCleared(previous,coalesce_image,&bounds,exception) != MagickFalse) 346 coalesce_image->dispose=BackgroundDispose; 347 else 348 coalesce_image->dispose=NoneDispose; 349 previous->dispose=coalesce_image->dispose; 350 } 351 dispose_image=DestroyImage(dispose_image); 352 return(GetFirstImageInList(coalesce_image)); 353 } 354 355 /* 357 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 358 % % 359 % % 360 % % 361 % D i s p o s e I m a g e s % 362 % % 363 % % 364 % % 365 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 366 % 367 % DisposeImages() returns the coalesced frames of a GIF animation as it would 368 % appear after the GIF dispose method of that frame has been applied. That is 369 % it returned the appearance of each frame before the next is overlaid. 370 % 371 % The format of the DisposeImages method is: 372 % 373 % Image *DisposeImages(Image *image,ExceptionInfo *exception) 374 % 375 % A description of each parameter follows: 376 % 377 % o images: the image sequence. 378 % 379 % o exception: return any errors or warnings in this structure. 380 % 381 */ 382 MagickExport Image *DisposeImages(const Image *images,ExceptionInfo *exception) 383 { 384 Image 385 *dispose_image, 386 *dispose_images; 387 388 RectangleInfo 389 bounds; 390 391 register Image 392 *image, 393 *next; 394 395 /* 396 Run the image through the animation sequence 397 */ 398 assert(images != (Image *) NULL); 399 assert(images->signature == MagickCoreSignature); 400 if (images->debug != MagickFalse) 401 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename); 402 assert(exception != (ExceptionInfo *) NULL); 403 assert(exception->signature == MagickCoreSignature); 404 image=GetFirstImageInList(images); 405 dispose_image=CloneImage(image,image->page.width,image->page.height, 406 MagickTrue,exception); 407 if (dispose_image == (Image *) NULL) 408 return((Image *) NULL); 409 dispose_image->page=image->page; 410 dispose_image->page.x=0; 411 dispose_image->page.y=0; 412 dispose_image->dispose=NoneDispose; 413 dispose_image->background_color.alpha=(Quantum) TransparentAlpha; 414 (void) SetImageBackgroundColor(dispose_image,exception); 415 dispose_images=NewImageList(); 416 for (next=image; image != (Image *) NULL; image=GetNextImageInList(image)) 417 { 418 Image 419 *current_image; 420 421 /* 422 Overlay this frame's image over the previous disposal image. 423 */ 424 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception); 425 if (current_image == (Image *) NULL) 426 { 427 dispose_images=DestroyImageList(dispose_images); 428 dispose_image=DestroyImage(dispose_image); 429 return((Image *) NULL); 430 } 431 (void) CompositeImage(current_image,next, 432 next->alpha_trait != UndefinedPixelTrait ? OverCompositeOp : CopyCompositeOp, 433 MagickTrue,next->page.x,next->page.y,exception); 434 /* 435 Handle Background dispose: image is displayed for the delay period. 436 */ 437 if (next->dispose == BackgroundDispose) 438 { 439 bounds=next->page; 440 bounds.width=next->columns; 441 bounds.height=next->rows; 442 if (bounds.x < 0) 443 { 444 bounds.width+=bounds.x; 445 bounds.x=0; 446 } 447 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns) 448 bounds.width=current_image->columns-bounds.x; 449 if (bounds.y < 0) 450 { 451 bounds.height+=bounds.y; 452 bounds.y=0; 453 } 454 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows) 455 bounds.height=current_image->rows-bounds.y; 456 ClearBounds(current_image,&bounds,exception); 457 } 458 /* 459 Select the appropriate previous/disposed image. 460 */ 461 if (next->dispose == PreviousDispose) 462 current_image=DestroyImage(current_image); 463 else 464 { 465 dispose_image=DestroyImage(dispose_image); 466 dispose_image=current_image; 467 current_image=(Image *) NULL; 468 } 469 /* 470 Save the dispose image just calculated for return. 471 */ 472 { 473 Image 474 *dispose; 475 476 dispose=CloneImage(dispose_image,0,0,MagickTrue,exception); 477 if (dispose == (Image *) NULL) 478 { 479 dispose_images=DestroyImageList(dispose_images); 480 dispose_image=DestroyImage(dispose_image); 481 return((Image *) NULL); 482 } 483 (void) CloneImageProfiles(dispose,next); 484 (void) CloneImageProperties(dispose,next); 485 (void) CloneImageArtifacts(dispose,next); 486 dispose->page.x=0; 487 dispose->page.y=0; 488 dispose->dispose=next->dispose; 489 AppendImageToList(&dispose_images,dispose); 490 } 491 } 492 dispose_image=DestroyImage(dispose_image); 493 return(GetFirstImageInList(dispose_images)); 494 } 495 496 /* 498 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 499 % % 500 % % 501 % % 502 + C o m p a r e P i x e l s % 503 % % 504 % % 505 % % 506 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 507 % 508 % ComparePixels() Compare the two pixels and return true if the pixels 509 % differ according to the given LayerType comparision method. 510 % 511 % This currently only used internally by CompareImagesBounds(). It is 512 % doubtful that this sub-routine will be useful outside this module. 513 % 514 % The format of the ComparePixels method is: 515 % 516 % MagickBooleanType *ComparePixels(const LayerMethod method, 517 % const PixelInfo *p,const PixelInfo *q) 518 % 519 % A description of each parameter follows: 520 % 521 % o method: What differences to look for. Must be one of 522 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer. 523 % 524 % o p, q: the pixels to test for appropriate differences. 525 % 526 */ 527 528 static MagickBooleanType ComparePixels(const LayerMethod method, 529 const PixelInfo *p,const PixelInfo *q) 530 { 531 double 532 o1, 533 o2; 534 535 /* 536 Any change in pixel values 537 */ 538 if (method == CompareAnyLayer) 539 return((MagickBooleanType)(IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse)); 540 541 o1 = (p->alpha_trait != UndefinedPixelTrait) ? p->alpha : OpaqueAlpha; 542 o2 = (q->alpha_trait != UndefinedPixelTrait) ? q->alpha : OpaqueAlpha; 543 /* 544 Pixel goes from opaque to transprency. 545 */ 546 if (method == CompareClearLayer) 547 return((MagickBooleanType) ( (o1 <= ((double) QuantumRange/2.0)) && 548 (o2 > ((double) QuantumRange/2.0)) ) ); 549 /* 550 Overlay would change first pixel by second. 551 */ 552 if (method == CompareOverlayLayer) 553 { 554 if (o2 > ((double) QuantumRange/2.0)) 555 return MagickFalse; 556 return((MagickBooleanType) (IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse)); 557 } 558 return(MagickFalse); 559 } 560 561 562 /* 564 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 565 % % 566 % % 567 % % 568 + C o m p a r e I m a g e B o u n d s % 569 % % 570 % % 571 % % 572 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 573 % 574 % CompareImagesBounds() Given two images return the smallest rectangular area 575 % by which the two images differ, accourding to the given 'Compare...' 576 % layer method. 577 % 578 % This currently only used internally in this module, but may eventually 579 % be used by other modules. 580 % 581 % The format of the CompareImagesBounds method is: 582 % 583 % RectangleInfo *CompareImagesBounds(const LayerMethod method, 584 % const Image *image1, const Image *image2, ExceptionInfo *exception) 585 % 586 % A description of each parameter follows: 587 % 588 % o method: What differences to look for. Must be one of CompareAnyLayer, 589 % CompareClearLayer, CompareOverlayLayer. 590 % 591 % o image1, image2: the two images to compare. 592 % 593 % o exception: return any errors or warnings in this structure. 594 % 595 */ 596 597 static RectangleInfo CompareImagesBounds(const Image *image1, 598 const Image *image2,const LayerMethod method,ExceptionInfo *exception) 599 { 600 RectangleInfo 601 bounds; 602 603 PixelInfo 604 pixel1, 605 pixel2; 606 607 register const Quantum 608 *p, 609 *q; 610 611 register ssize_t 612 x; 613 614 ssize_t 615 y; 616 617 /* 618 Set bounding box of the differences between images. 619 */ 620 GetPixelInfo(image1,&pixel1); 621 GetPixelInfo(image2,&pixel2); 622 for (x=0; x < (ssize_t) image1->columns; x++) 623 { 624 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception); 625 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception); 626 if ((p == (const Quantum *) NULL) || 627 (q == (Quantum *) NULL)) 628 break; 629 for (y=0; y < (ssize_t) image1->rows; y++) 630 { 631 GetPixelInfoPixel(image1,p,&pixel1); 632 GetPixelInfoPixel(image2,q,&pixel2); 633 if (ComparePixels(method,&pixel1,&pixel2)) 634 break; 635 p+=GetPixelChannels(image1); 636 q+=GetPixelChannels(image2); 637 } 638 if (y < (ssize_t) image1->rows) 639 break; 640 } 641 if (x >= (ssize_t) image1->columns) 642 { 643 /* 644 Images are identical, return a null image. 645 */ 646 bounds.x=-1; 647 bounds.y=-1; 648 bounds.width=1; 649 bounds.height=1; 650 return(bounds); 651 } 652 bounds.x=x; 653 for (x=(ssize_t) image1->columns-1; x >= 0; x--) 654 { 655 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception); 656 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception); 657 if ((p == (const Quantum *) NULL) || 658 (q == (Quantum *) NULL)) 659 break; 660 for (y=0; y < (ssize_t) image1->rows; y++) 661 { 662 GetPixelInfoPixel(image1,p,&pixel1); 663 GetPixelInfoPixel(image2,q,&pixel2); 664 if (ComparePixels(method,&pixel1,&pixel2)) 665 break; 666 p+=GetPixelChannels(image1); 667 q+=GetPixelChannels(image2); 668 } 669 if (y < (ssize_t) image1->rows) 670 break; 671 } 672 bounds.width=(size_t) (x-bounds.x+1); 673 for (y=0; y < (ssize_t) image1->rows; y++) 674 { 675 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception); 676 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception); 677 if ((p == (const Quantum *) NULL) || 678 (q == (Quantum *) NULL)) 679 break; 680 for (x=0; x < (ssize_t) image1->columns; x++) 681 { 682 GetPixelInfoPixel(image1,p,&pixel1); 683 GetPixelInfoPixel(image2,q,&pixel2); 684 if (ComparePixels(method,&pixel1,&pixel2)) 685 break; 686 p+=GetPixelChannels(image1); 687 q+=GetPixelChannels(image2); 688 } 689 if (x < (ssize_t) image1->columns) 690 break; 691 } 692 bounds.y=y; 693 for (y=(ssize_t) image1->rows-1; y >= 0; y--) 694 { 695 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception); 696 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception); 697 if ((p == (const Quantum *) NULL) || 698 (q == (Quantum *) NULL)) 699 break; 700 for (x=0; x < (ssize_t) image1->columns; x++) 701 { 702 GetPixelInfoPixel(image1,p,&pixel1); 703 GetPixelInfoPixel(image2,q,&pixel2); 704 if (ComparePixels(method,&pixel1,&pixel2)) 705 break; 706 p+=GetPixelChannels(image1); 707 q+=GetPixelChannels(image2); 708 } 709 if (x < (ssize_t) image1->columns) 710 break; 711 } 712 bounds.height=(size_t) (y-bounds.y+1); 713 return(bounds); 714 } 715 716 /* 718 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 719 % % 720 % % 721 % % 722 % C o m p a r e I m a g e L a y e r s % 723 % % 724 % % 725 % % 726 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 727 % 728 % CompareImagesLayers() compares each image with the next in a sequence and 729 % returns the minimum bounding region of all the pixel differences (of the 730 % LayerMethod specified) it discovers. 731 % 732 % Images do NOT have to be the same size, though it is best that all the 733 % images are 'coalesced' (images are all the same size, on a flattened 734 % canvas, so as to represent exactly how an specific frame should look). 735 % 736 % No GIF dispose methods are applied, so GIF animations must be coalesced 737 % before applying this image operator to find differences to them. 738 % 739 % The format of the CompareImagesLayers method is: 740 % 741 % Image *CompareImagesLayers(const Image *images, 742 % const LayerMethod method,ExceptionInfo *exception) 743 % 744 % A description of each parameter follows: 745 % 746 % o image: the image. 747 % 748 % o method: the layers type to compare images with. Must be one of... 749 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer. 750 % 751 % o exception: return any errors or warnings in this structure. 752 % 753 */ 754 755 MagickExport Image *CompareImagesLayers(const Image *image, 756 const LayerMethod method, ExceptionInfo *exception) 757 { 758 Image 759 *image_a, 760 *image_b, 761 *layers; 762 763 RectangleInfo 764 *bounds; 765 766 register const Image 767 *next; 768 769 register ssize_t 770 i; 771 772 assert(image != (const Image *) NULL); 773 assert(image->signature == MagickCoreSignature); 774 if (image->debug != MagickFalse) 775 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 776 assert(exception != (ExceptionInfo *) NULL); 777 assert(exception->signature == MagickCoreSignature); 778 assert((method == CompareAnyLayer) || 779 (method == CompareClearLayer) || 780 (method == CompareOverlayLayer)); 781 /* 782 Allocate bounds memory. 783 */ 784 next=GetFirstImageInList(image); 785 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t) 786 GetImageListLength(next),sizeof(*bounds)); 787 if (bounds == (RectangleInfo *) NULL) 788 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 789 /* 790 Set up first comparision images. 791 */ 792 image_a=CloneImage(next,next->page.width,next->page.height, 793 MagickTrue,exception); 794 if (image_a == (Image *) NULL) 795 { 796 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 797 return((Image *) NULL); 798 } 799 image_a->background_color.alpha=(Quantum) TransparentAlpha; 800 (void) SetImageBackgroundColor(image_a,exception); 801 image_a->page=next->page; 802 image_a->page.x=0; 803 image_a->page.y=0; 804 (void) CompositeImage(image_a,next,CopyCompositeOp,MagickTrue,next->page.x, 805 next->page.y,exception); 806 /* 807 Compute the bounding box of changes for the later images 808 */ 809 i=0; 810 next=GetNextImageInList(next); 811 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next)) 812 { 813 image_b=CloneImage(image_a,0,0,MagickTrue,exception); 814 if (image_b == (Image *) NULL) 815 { 816 image_a=DestroyImage(image_a); 817 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 818 return((Image *) NULL); 819 } 820 (void) CompositeImage(image_a,next,CopyCompositeOp,MagickTrue,next->page.x, 821 next->page.y,exception); 822 bounds[i]=CompareImagesBounds(image_b,image_a,method,exception); 823 image_b=DestroyImage(image_b); 824 i++; 825 } 826 image_a=DestroyImage(image_a); 827 /* 828 Clone first image in sequence. 829 */ 830 next=GetFirstImageInList(image); 831 layers=CloneImage(next,0,0,MagickTrue,exception); 832 if (layers == (Image *) NULL) 833 { 834 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 835 return((Image *) NULL); 836 } 837 /* 838 Deconstruct the image sequence. 839 */ 840 i=0; 841 next=GetNextImageInList(next); 842 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next)) 843 { 844 if ((bounds[i].x == -1) && (bounds[i].y == -1) && 845 (bounds[i].width == 1) && (bounds[i].height == 1)) 846 { 847 /* 848 An empty frame is returned from CompareImageBounds(), which means the 849 current frame is identical to the previous frame. 850 */ 851 i++; 852 continue; 853 } 854 image_a=CloneImage(next,0,0,MagickTrue,exception); 855 if (image_a == (Image *) NULL) 856 break; 857 image_b=CropImage(image_a,&bounds[i],exception); 858 image_a=DestroyImage(image_a); 859 if (image_b == (Image *) NULL) 860 break; 861 AppendImageToList(&layers,image_b); 862 i++; 863 } 864 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 865 if (next != (Image *) NULL) 866 { 867 layers=DestroyImageList(layers); 868 return((Image *) NULL); 869 } 870 return(GetFirstImageInList(layers)); 871 } 872 873 /* 875 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 876 % % 877 % % 878 % % 879 + O p t i m i z e L a y e r F r a m e s % 880 % % 881 % % 882 % % 883 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 884 % 885 % OptimizeLayerFrames() takes a coalesced GIF animation, and compares each 886 % frame against the three different 'disposal' forms of the previous frame. 887 % From this it then attempts to select the smallest cropped image and 888 % disposal method needed to reproduce the resulting image. 889 % 890 % Note that this not easy, and may require the expansion of the bounds 891 % of previous frame, simply clear pixels for the next animation frame to 892 % transparency according to the selected dispose method. 893 % 894 % The format of the OptimizeLayerFrames method is: 895 % 896 % Image *OptimizeLayerFrames(const Image *image, 897 % const LayerMethod method, ExceptionInfo *exception) 898 % 899 % A description of each parameter follows: 900 % 901 % o image: the image. 902 % 903 % o method: the layers technique to optimize with. Must be one of... 904 % OptimizeImageLayer, or OptimizePlusLayer. The Plus form allows 905 % the addition of extra 'zero delay' frames to clear pixels from 906 % the previous frame, and the removal of frames that done change, 907 % merging the delay times together. 908 % 909 % o exception: return any errors or warnings in this structure. 910 % 911 */ 912 /* 913 Define a 'fake' dispose method where the frame is duplicated, (for 914 OptimizePlusLayer) with a extra zero time delay frame which does a 915 BackgroundDisposal to clear the pixels that need to be cleared. 916 */ 917 #define DupDispose ((DisposeType)9) 918 /* 919 Another 'fake' dispose method used to removed frames that don't change. 920 */ 921 #define DelDispose ((DisposeType)8) 922 923 #define DEBUG_OPT_FRAME 0 924 925 static Image *OptimizeLayerFrames(const Image *image, 926 const LayerMethod method, ExceptionInfo *exception) 927 { 928 ExceptionInfo 929 *sans_exception; 930 931 Image 932 *prev_image, 933 *dup_image, 934 *bgnd_image, 935 *optimized_image; 936 937 RectangleInfo 938 try_bounds, 939 bgnd_bounds, 940 dup_bounds, 941 *bounds; 942 943 MagickBooleanType 944 add_frames, 945 try_cleared, 946 cleared; 947 948 DisposeType 949 *disposals; 950 951 register const Image 952 *curr; 953 954 register ssize_t 955 i; 956 957 assert(image != (const Image *) NULL); 958 assert(image->signature == MagickCoreSignature); 959 if (image->debug != MagickFalse) 960 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 961 assert(exception != (ExceptionInfo *) NULL); 962 assert(exception->signature == MagickCoreSignature); 963 assert(method == OptimizeLayer || 964 method == OptimizeImageLayer || 965 method == OptimizePlusLayer); 966 /* 967 Are we allowed to add/remove frames from animation? 968 */ 969 add_frames=method == OptimizePlusLayer ? MagickTrue : MagickFalse; 970 /* 971 Ensure all the images are the same size. 972 */ 973 curr=GetFirstImageInList(image); 974 for (; curr != (Image *) NULL; curr=GetNextImageInList(curr)) 975 { 976 if ((curr->columns != image->columns) || (curr->rows != image->rows)) 977 ThrowImageException(OptionError,"ImagesAreNotTheSameSize"); 978 /* 979 FUTURE: also check that image is also fully coalesced (full page) 980 Though as long as they are the same size it should not matter. 981 */ 982 } 983 /* 984 Allocate memory (times 2 if we allow the use of frame duplications) 985 */ 986 curr=GetFirstImageInList(image); 987 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t) 988 GetImageListLength(curr),(add_frames != MagickFalse ? 2UL : 1UL)* 989 sizeof(*bounds)); 990 if (bounds == (RectangleInfo *) NULL) 991 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 992 disposals=(DisposeType *) AcquireQuantumMemory((size_t) 993 GetImageListLength(image),(add_frames != MagickFalse ? 2UL : 1UL)* 994 sizeof(*disposals)); 995 if (disposals == (DisposeType *) NULL) 996 { 997 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 998 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 999 } 1000 /* 1001 Initialise Previous Image as fully transparent 1002 */ 1003 prev_image=CloneImage(curr,curr->page.width,curr->page.height, 1004 MagickTrue,exception); 1005 if (prev_image == (Image *) NULL) 1006 { 1007 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 1008 disposals=(DisposeType *) RelinquishMagickMemory(disposals); 1009 return((Image *) NULL); 1010 } 1011 prev_image->page=curr->page; /* ERROR: <-- should not be need, but is! */ 1012 prev_image->page.x=0; 1013 prev_image->page.y=0; 1014 prev_image->dispose=NoneDispose; 1015 prev_image->background_color.alpha_trait=BlendPixelTrait; 1016 prev_image->background_color.alpha=(Quantum) TransparentAlpha; 1017 (void) SetImageBackgroundColor(prev_image,exception); 1018 /* 1019 Figure out the area of overlay of the first frame 1020 No pixel could be cleared as all pixels are already cleared. 1021 */ 1022 #if DEBUG_OPT_FRAME 1023 i=0; 1024 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i); 1025 #endif 1026 disposals[0]=NoneDispose; 1027 bounds[0]=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception); 1028 #if DEBUG_OPT_FRAME 1029 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g\n\n", 1030 (double) bounds[i].width,(double) bounds[i].height, 1031 (double) bounds[i].x,(double) bounds[i].y ); 1032 #endif 1033 /* 1034 Compute the bounding box of changes for each pair of images. 1035 */ 1036 i=1; 1037 bgnd_image=(Image *) NULL; 1038 dup_image=(Image *) NULL; 1039 dup_bounds.width=0; 1040 dup_bounds.height=0; 1041 dup_bounds.x=0; 1042 dup_bounds.y=0; 1043 curr=GetNextImageInList(curr); 1044 for ( ; curr != (const Image *) NULL; curr=GetNextImageInList(curr)) 1045 { 1046 #if DEBUG_OPT_FRAME 1047 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i); 1048 #endif 1049 /* 1050 Assume none disposal is the best 1051 */ 1052 bounds[i]=CompareImagesBounds(curr->previous,curr,CompareAnyLayer,exception); 1053 cleared=IsBoundsCleared(curr->previous,curr,&bounds[i],exception); 1054 disposals[i-1]=NoneDispose; 1055 #if DEBUG_OPT_FRAME 1056 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g%s%s\n", 1057 (double) bounds[i].width,(double) bounds[i].height, 1058 (double) bounds[i].x,(double) bounds[i].y, 1059 bounds[i].x < 0?" (unchanged)":"", 1060 cleared?" (pixels cleared)":""); 1061 #endif 1062 if ( bounds[i].x < 0 ) { 1063 /* 1064 Image frame is exactly the same as the previous frame! 1065 If not adding frames leave it to be cropped down to a null image. 1066 Otherwise mark previous image for deleted, transfering its crop bounds 1067 to the current image. 1068 */ 1069 if ( add_frames && i>=2 ) { 1070 disposals[i-1]=DelDispose; 1071 disposals[i]=NoneDispose; 1072 bounds[i]=bounds[i-1]; 1073 i++; 1074 continue; 1075 } 1076 } 1077 else 1078 { 1079 /* 1080 Compare a none disposal against a previous disposal 1081 */ 1082 try_bounds=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception); 1083 try_cleared=IsBoundsCleared(prev_image,curr,&try_bounds,exception); 1084 #if DEBUG_OPT_FRAME 1085 (void) FormatLocaleFile(stderr, "test_prev: %.20gx%.20g%+.20g%+.20g%s\n", 1086 (double) try_bounds.width,(double) try_bounds.height, 1087 (double) try_bounds.x,(double) try_bounds.y, 1088 try_cleared?" (pixels were cleared)":""); 1089 #endif 1090 if ( (!try_cleared && cleared ) || 1091 try_bounds.width * try_bounds.height 1092 < bounds[i].width * bounds[i].height ) 1093 { 1094 cleared=try_cleared; 1095 bounds[i]=try_bounds; 1096 disposals[i-1]=PreviousDispose; 1097 #if DEBUG_OPT_FRAME 1098 (void) FormatLocaleFile(stderr, "previous: accepted\n"); 1099 } else { 1100 (void) FormatLocaleFile(stderr, "previous: rejected\n"); 1101 #endif 1102 } 1103 1104 /* 1105 If we are allowed lets try a complex frame duplication. 1106 It is useless if the previous image already clears pixels correctly. 1107 This method will always clear all the pixels that need to be cleared. 1108 */ 1109 dup_bounds.width=dup_bounds.height=0; /* no dup, no pixel added */ 1110 if ( add_frames ) 1111 { 1112 dup_image=CloneImage(curr->previous,curr->previous->page.width, 1113 curr->previous->page.height,MagickTrue,exception); 1114 if (dup_image == (Image *) NULL) 1115 { 1116 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 1117 disposals=(DisposeType *) RelinquishMagickMemory(disposals); 1118 prev_image=DestroyImage(prev_image); 1119 return((Image *) NULL); 1120 } 1121 dup_bounds=CompareImagesBounds(dup_image,curr,CompareClearLayer,exception); 1122 ClearBounds(dup_image,&dup_bounds,exception); 1123 try_bounds=CompareImagesBounds(dup_image,curr,CompareAnyLayer,exception); 1124 if ( cleared || 1125 dup_bounds.width*dup_bounds.height 1126 +try_bounds.width*try_bounds.height 1127 < bounds[i].width * bounds[i].height ) 1128 { 1129 cleared=MagickFalse; 1130 bounds[i]=try_bounds; 1131 disposals[i-1]=DupDispose; 1132 /* to be finalised later, if found to be optimial */ 1133 } 1134 else 1135 dup_bounds.width=dup_bounds.height=0; 1136 } 1137 /* 1138 Now compare against a simple background disposal 1139 */ 1140 bgnd_image=CloneImage(curr->previous,curr->previous->page.width, 1141 curr->previous->page.height,MagickTrue,exception); 1142 if (bgnd_image == (Image *) NULL) 1143 { 1144 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 1145 disposals=(DisposeType *) RelinquishMagickMemory(disposals); 1146 prev_image=DestroyImage(prev_image); 1147 if ( dup_image != (Image *) NULL) 1148 dup_image=DestroyImage(dup_image); 1149 return((Image *) NULL); 1150 } 1151 bgnd_bounds=bounds[i-1]; /* interum bounds of the previous image */ 1152 ClearBounds(bgnd_image,&bgnd_bounds,exception); 1153 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception); 1154 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception); 1155 #if DEBUG_OPT_FRAME 1156 (void) FormatLocaleFile(stderr, "background: %s\n", 1157 try_cleared?"(pixels cleared)":""); 1158 #endif 1159 if ( try_cleared ) 1160 { 1161 /* 1162 Straight background disposal failed to clear pixels needed! 1163 Lets try expanding the disposal area of the previous frame, to 1164 include the pixels that are cleared. This guaranteed 1165 to work, though may not be the most optimized solution. 1166 */ 1167 try_bounds=CompareImagesBounds(curr->previous,curr,CompareClearLayer,exception); 1168 #if DEBUG_OPT_FRAME 1169 (void) FormatLocaleFile(stderr, "expand_clear: %.20gx%.20g%+.20g%+.20g%s\n", 1170 (double) try_bounds.width,(double) try_bounds.height, 1171 (double) try_bounds.x,(double) try_bounds.y, 1172 try_bounds.x<0?" (no expand nessary)":""); 1173 #endif 1174 if ( bgnd_bounds.x < 0 ) 1175 bgnd_bounds = try_bounds; 1176 else 1177 { 1178 #if DEBUG_OPT_FRAME 1179 (void) FormatLocaleFile(stderr, "expand_bgnd: %.20gx%.20g%+.20g%+.20g\n", 1180 (double) bgnd_bounds.width,(double) bgnd_bounds.height, 1181 (double) bgnd_bounds.x,(double) bgnd_bounds.y ); 1182 #endif 1183 if ( try_bounds.x < bgnd_bounds.x ) 1184 { 1185 bgnd_bounds.width+= bgnd_bounds.x-try_bounds.x; 1186 if ( bgnd_bounds.width < try_bounds.width ) 1187 bgnd_bounds.width = try_bounds.width; 1188 bgnd_bounds.x = try_bounds.x; 1189 } 1190 else 1191 { 1192 try_bounds.width += try_bounds.x - bgnd_bounds.x; 1193 if ( bgnd_bounds.width < try_bounds.width ) 1194 bgnd_bounds.width = try_bounds.width; 1195 } 1196 if ( try_bounds.y < bgnd_bounds.y ) 1197 { 1198 bgnd_bounds.height += bgnd_bounds.y - try_bounds.y; 1199 if ( bgnd_bounds.height < try_bounds.height ) 1200 bgnd_bounds.height = try_bounds.height; 1201 bgnd_bounds.y = try_bounds.y; 1202 } 1203 else 1204 { 1205 try_bounds.height += try_bounds.y - bgnd_bounds.y; 1206 if ( bgnd_bounds.height < try_bounds.height ) 1207 bgnd_bounds.height = try_bounds.height; 1208 } 1209 #if DEBUG_OPT_FRAME 1210 (void) FormatLocaleFile(stderr, " to : %.20gx%.20g%+.20g%+.20g\n", 1211 (double) bgnd_bounds.width,(double) bgnd_bounds.height, 1212 (double) bgnd_bounds.x,(double) bgnd_bounds.y ); 1213 #endif 1214 } 1215 ClearBounds(bgnd_image,&bgnd_bounds,exception); 1216 #if DEBUG_OPT_FRAME 1217 /* Something strange is happening with a specific animation 1218 * CompareAnyLayers (normal method) and CompareClearLayers returns the whole 1219 * image, which is not posibly correct! As verified by previous tests. 1220 * Something changed beyond the bgnd_bounds clearing. But without being able 1221 * to see, or writet he image at this point it is hard to tell what is wrong! 1222 * Only CompareOverlay seemed to return something sensible. 1223 */ 1224 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareClearLayer,exception); 1225 (void) FormatLocaleFile(stderr, "expand_ctst: %.20gx%.20g%+.20g%+.20g\n", 1226 (double) try_bounds.width,(double) try_bounds.height, 1227 (double) try_bounds.x,(double) try_bounds.y ); 1228 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception); 1229 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception); 1230 (void) FormatLocaleFile(stderr, "expand_any : %.20gx%.20g%+.20g%+.20g%s\n", 1231 (double) try_bounds.width,(double) try_bounds.height, 1232 (double) try_bounds.x,(double) try_bounds.y, 1233 try_cleared?" (pixels cleared)":""); 1234 #endif 1235 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareOverlayLayer,exception); 1236 #if DEBUG_OPT_FRAME 1237 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception); 1238 (void) FormatLocaleFile(stderr, "expand_test: %.20gx%.20g%+.20g%+.20g%s\n", 1239 (double) try_bounds.width,(double) try_bounds.height, 1240 (double) try_bounds.x,(double) try_bounds.y, 1241 try_cleared?" (pixels cleared)":""); 1242 #endif 1243 } 1244 /* 1245 Test if this background dispose is smaller than any of the 1246 other methods we tryed before this (including duplicated frame) 1247 */ 1248 if ( cleared || 1249 bgnd_bounds.width*bgnd_bounds.height 1250 +try_bounds.width*try_bounds.height 1251 < bounds[i-1].width*bounds[i-1].height 1252 +dup_bounds.width*dup_bounds.height 1253 +bounds[i].width*bounds[i].height ) 1254 { 1255 cleared=MagickFalse; 1256 bounds[i-1]=bgnd_bounds; 1257 bounds[i]=try_bounds; 1258 if ( disposals[i-1] == DupDispose ) 1259 dup_image=DestroyImage(dup_image); 1260 disposals[i-1]=BackgroundDispose; 1261 #if DEBUG_OPT_FRAME 1262 (void) FormatLocaleFile(stderr, "expand_bgnd: accepted\n"); 1263 } else { 1264 (void) FormatLocaleFile(stderr, "expand_bgnd: reject\n"); 1265 #endif 1266 } 1267 } 1268 /* 1269 Finalise choice of dispose, set new prev_image, 1270 and junk any extra images as appropriate, 1271 */ 1272 if ( disposals[i-1] == DupDispose ) 1273 { 1274 if (bgnd_image != (Image *) NULL) 1275 bgnd_image=DestroyImage(bgnd_image); 1276 prev_image=DestroyImage(prev_image); 1277 prev_image=dup_image, dup_image=(Image *) NULL; 1278 bounds[i+1]=bounds[i]; 1279 bounds[i]=dup_bounds; 1280 disposals[i-1]=DupDispose; 1281 disposals[i]=BackgroundDispose; 1282 i++; 1283 } 1284 else 1285 { 1286 if ( dup_image != (Image *) NULL) 1287 dup_image=DestroyImage(dup_image); 1288 if ( disposals[i-1] != PreviousDispose ) 1289 prev_image=DestroyImage(prev_image); 1290 if ( disposals[i-1] == BackgroundDispose ) 1291 prev_image=bgnd_image, bgnd_image=(Image *) NULL; 1292 if (bgnd_image != (Image *) NULL) 1293 bgnd_image=DestroyImage(bgnd_image); 1294 if ( disposals[i-1] == NoneDispose ) 1295 { 1296 prev_image=CloneImage(curr->previous,curr->previous->page.width, 1297 curr->previous->page.height,MagickTrue,exception); 1298 if (prev_image == (Image *) NULL) 1299 { 1300 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 1301 disposals=(DisposeType *) RelinquishMagickMemory(disposals); 1302 return((Image *) NULL); 1303 } 1304 } 1305 1306 } 1307 assert(prev_image != (Image *) NULL); 1308 disposals[i]=disposals[i-1]; 1309 #if DEBUG_OPT_FRAME 1310 (void) FormatLocaleFile(stderr, "final %.20g : %s %.20gx%.20g%+.20g%+.20g\n", 1311 (double) i-1, 1312 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i-1]), 1313 (double) bounds[i-1].width, (double) bounds[i-1].height, 1314 (double) bounds[i-1].x, (double) bounds[i-1].y ); 1315 #endif 1316 #if DEBUG_OPT_FRAME 1317 (void) FormatLocaleFile(stderr, "interum %.20g : %s %.20gx%.20g%+.20g%+.20g\n", 1318 (double) i, 1319 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i]), 1320 (double) bounds[i].width, (double) bounds[i].height, 1321 (double) bounds[i].x, (double) bounds[i].y ); 1322 (void) FormatLocaleFile(stderr, "\n"); 1323 #endif 1324 i++; 1325 } 1326 prev_image=DestroyImage(prev_image); 1327 /* 1328 Optimize all images in sequence. 1329 */ 1330 sans_exception=AcquireExceptionInfo(); 1331 i=0; 1332 curr=GetFirstImageInList(image); 1333 optimized_image=NewImageList(); 1334 while ( curr != (const Image *) NULL ) 1335 { 1336 prev_image=CloneImage(curr,0,0,MagickTrue,exception); 1337 if (prev_image == (Image *) NULL) 1338 break; 1339 if ( disposals[i] == DelDispose ) { 1340 size_t time = 0; 1341 while ( disposals[i] == DelDispose ) { 1342 time += curr->delay*1000/curr->ticks_per_second; 1343 curr=GetNextImageInList(curr); 1344 i++; 1345 } 1346 time += curr->delay*1000/curr->ticks_per_second; 1347 prev_image->ticks_per_second = 100L; 1348 prev_image->delay = time*prev_image->ticks_per_second/1000; 1349 } 1350 bgnd_image=CropImage(prev_image,&bounds[i],sans_exception); 1351 prev_image=DestroyImage(prev_image); 1352 if (bgnd_image == (Image *) NULL) 1353 break; 1354 bgnd_image->dispose=disposals[i]; 1355 if ( disposals[i] == DupDispose ) { 1356 bgnd_image->delay=0; 1357 bgnd_image->dispose=NoneDispose; 1358 } 1359 else 1360 curr=GetNextImageInList(curr); 1361 AppendImageToList(&optimized_image,bgnd_image); 1362 i++; 1363 } 1364 sans_exception=DestroyExceptionInfo(sans_exception); 1365 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 1366 disposals=(DisposeType *) RelinquishMagickMemory(disposals); 1367 if (curr != (Image *) NULL) 1368 { 1369 optimized_image=DestroyImageList(optimized_image); 1370 return((Image *) NULL); 1371 } 1372 return(GetFirstImageInList(optimized_image)); 1373 } 1374 1375 /* 1377 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1378 % % 1379 % % 1380 % % 1381 % O p t i m i z e I m a g e L a y e r s % 1382 % % 1383 % % 1384 % % 1385 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1386 % 1387 % OptimizeImageLayers() compares each image the GIF disposed forms of the 1388 % previous image in the sequence. From this it attempts to select the 1389 % smallest cropped image to replace each frame, while preserving the results 1390 % of the GIF animation. 1391 % 1392 % The format of the OptimizeImageLayers method is: 1393 % 1394 % Image *OptimizeImageLayers(const Image *image, 1395 % ExceptionInfo *exception) 1396 % 1397 % A description of each parameter follows: 1398 % 1399 % o image: the image. 1400 % 1401 % o exception: return any errors or warnings in this structure. 1402 % 1403 */ 1404 MagickExport Image *OptimizeImageLayers(const Image *image, 1405 ExceptionInfo *exception) 1406 { 1407 return(OptimizeLayerFrames(image,OptimizeImageLayer,exception)); 1408 } 1409 1410 /* 1412 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1413 % % 1414 % % 1415 % % 1416 % O p t i m i z e P l u s I m a g e L a y e r s % 1417 % % 1418 % % 1419 % % 1420 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1421 % 1422 % OptimizeImagePlusLayers() is exactly as OptimizeImageLayers(), but may 1423 % also add or even remove extra frames in the animation, if it improves 1424 % the total number of pixels in the resulting GIF animation. 1425 % 1426 % The format of the OptimizePlusImageLayers method is: 1427 % 1428 % Image *OptimizePlusImageLayers(const Image *image, 1429 % ExceptionInfo *exception) 1430 % 1431 % A description of each parameter follows: 1432 % 1433 % o image: the image. 1434 % 1435 % o exception: return any errors or warnings in this structure. 1436 % 1437 */ 1438 MagickExport Image *OptimizePlusImageLayers(const Image *image, 1439 ExceptionInfo *exception) 1440 { 1441 return OptimizeLayerFrames(image, OptimizePlusLayer, exception); 1442 } 1443 1444 /* 1446 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1447 % % 1448 % % 1449 % % 1450 % O p t i m i z e I m a g e T r a n s p a r e n c y % 1451 % % 1452 % % 1453 % % 1454 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1455 % 1456 % OptimizeImageTransparency() takes a frame optimized GIF animation, and 1457 % compares the overlayed pixels against the disposal image resulting from all 1458 % the previous frames in the animation. Any pixel that does not change the 1459 % disposal image (and thus does not effect the outcome of an overlay) is made 1460 % transparent. 1461 % 1462 % WARNING: This modifies the current images directly, rather than generate 1463 % a new image sequence. 1464 % 1465 % The format of the OptimizeImageTransperency method is: 1466 % 1467 % void OptimizeImageTransperency(Image *image,ExceptionInfo *exception) 1468 % 1469 % A description of each parameter follows: 1470 % 1471 % o image: the image sequence 1472 % 1473 % o exception: return any errors or warnings in this structure. 1474 % 1475 */ 1476 MagickExport void OptimizeImageTransparency(const Image *image, 1477 ExceptionInfo *exception) 1478 { 1479 Image 1480 *dispose_image; 1481 1482 register Image 1483 *next; 1484 1485 /* 1486 Run the image through the animation sequence 1487 */ 1488 assert(image != (Image *) NULL); 1489 assert(image->signature == MagickCoreSignature); 1490 if (image->debug != MagickFalse) 1491 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 1492 assert(exception != (ExceptionInfo *) NULL); 1493 assert(exception->signature == MagickCoreSignature); 1494 next=GetFirstImageInList(image); 1495 dispose_image=CloneImage(next,next->page.width,next->page.height, 1496 MagickTrue,exception); 1497 if (dispose_image == (Image *) NULL) 1498 return; 1499 dispose_image->page=next->page; 1500 dispose_image->page.x=0; 1501 dispose_image->page.y=0; 1502 dispose_image->dispose=NoneDispose; 1503 dispose_image->background_color.alpha_trait=BlendPixelTrait; 1504 dispose_image->background_color.alpha=(Quantum) TransparentAlpha; 1505 (void) SetImageBackgroundColor(dispose_image,exception); 1506 1507 while ( next != (Image *) NULL ) 1508 { 1509 Image 1510 *current_image; 1511 1512 /* 1513 Overlay this frame's image over the previous disposal image 1514 */ 1515 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception); 1516 if (current_image == (Image *) NULL) 1517 { 1518 dispose_image=DestroyImage(dispose_image); 1519 return; 1520 } 1521 (void) CompositeImage(current_image,next,next->alpha_trait != UndefinedPixelTrait ? 1522 OverCompositeOp : CopyCompositeOp,MagickTrue,next->page.x,next->page.y, 1523 exception); 1524 /* 1525 At this point the image would be displayed, for the delay period 1526 ** 1527 Work out the disposal of the previous image 1528 */ 1529 if (next->dispose == BackgroundDispose) 1530 { 1531 RectangleInfo 1532 bounds=next->page; 1533 1534 bounds.width=next->columns; 1535 bounds.height=next->rows; 1536 if (bounds.x < 0) 1537 { 1538 bounds.width+=bounds.x; 1539 bounds.x=0; 1540 } 1541 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns) 1542 bounds.width=current_image->columns-bounds.x; 1543 if (bounds.y < 0) 1544 { 1545 bounds.height+=bounds.y; 1546 bounds.y=0; 1547 } 1548 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows) 1549 bounds.height=current_image->rows-bounds.y; 1550 ClearBounds(current_image, &bounds,exception); 1551 } 1552 if (next->dispose != PreviousDispose) 1553 { 1554 dispose_image=DestroyImage(dispose_image); 1555 dispose_image=current_image; 1556 } 1557 else 1558 current_image=DestroyImage(current_image); 1559 1560 /* 1561 Optimize Transparency of the next frame (if present) 1562 */ 1563 next=GetNextImageInList(next); 1564 if (next != (Image *) NULL) { 1565 (void) CompositeImage(next,dispose_image,ChangeMaskCompositeOp, 1566 MagickTrue,-(next->page.x),-(next->page.y),exception); 1567 } 1568 } 1569 dispose_image=DestroyImage(dispose_image); 1570 return; 1571 } 1572 1573 /* 1575 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1576 % % 1577 % % 1578 % % 1579 % R e m o v e D u p l i c a t e L a y e r s % 1580 % % 1581 % % 1582 % % 1583 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1584 % 1585 % RemoveDuplicateLayers() removes any image that is exactly the same as the 1586 % next image in the given image list. Image size and virtual canvas offset 1587 % must also match, though not the virtual canvas size itself. 1588 % 1589 % No check is made with regards to image disposal setting, though it is the 1590 % dispose setting of later image that is kept. Also any time delays are also 1591 % added together. As such coalesced image animations should still produce the 1592 % same result, though with duplicte frames merged into a single frame. 1593 % 1594 % The format of the RemoveDuplicateLayers method is: 1595 % 1596 % void RemoveDuplicateLayers(Image **image, ExceptionInfo *exception) 1597 % 1598 % A description of each parameter follows: 1599 % 1600 % o images: the image list 1601 % 1602 % o exception: return any errors or warnings in this structure. 1603 % 1604 */ 1605 MagickExport void RemoveDuplicateLayers(Image **images, 1606 ExceptionInfo *exception) 1607 { 1608 register Image 1609 *curr, 1610 *next; 1611 1612 RectangleInfo 1613 bounds; 1614 1615 assert((*images) != (const Image *) NULL); 1616 assert((*images)->signature == MagickCoreSignature); 1617 if ((*images)->debug != MagickFalse) 1618 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename); 1619 assert(exception != (ExceptionInfo *) NULL); 1620 assert(exception->signature == MagickCoreSignature); 1621 1622 curr=GetFirstImageInList(*images); 1623 for (; (next=GetNextImageInList(curr)) != (Image *) NULL; curr=next) 1624 { 1625 if ( curr->columns != next->columns || curr->rows != next->rows 1626 || curr->page.x != next->page.x || curr->page.y != next->page.y ) 1627 continue; 1628 bounds=CompareImagesBounds(curr,next,CompareAnyLayer,exception); 1629 if ( bounds.x < 0 ) { 1630 /* 1631 the two images are the same, merge time delays and delete one. 1632 */ 1633 size_t time; 1634 time = curr->delay*1000/curr->ticks_per_second; 1635 time += next->delay*1000/next->ticks_per_second; 1636 next->ticks_per_second = 100L; 1637 next->delay = time*curr->ticks_per_second/1000; 1638 next->iterations = curr->iterations; 1639 *images = curr; 1640 (void) DeleteImageFromList(images); 1641 } 1642 } 1643 *images = GetFirstImageInList(*images); 1644 } 1645 1646 /* 1648 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1649 % % 1650 % % 1651 % % 1652 % R e m o v e Z e r o D e l a y L a y e r s % 1653 % % 1654 % % 1655 % % 1656 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1657 % 1658 % RemoveZeroDelayLayers() removes any image that as a zero delay time. Such 1659 % images generally represent intermediate or partial updates in GIF 1660 % animations used for file optimization. They are not ment to be displayed 1661 % to users of the animation. Viewable images in an animation should have a 1662 % time delay of 3 or more centi-seconds (hundredths of a second). 1663 % 1664 % However if all the frames have a zero time delay, then either the animation 1665 % is as yet incomplete, or it is not a GIF animation. This a non-sensible 1666 % situation, so no image will be removed and a 'Zero Time Animation' warning 1667 % (exception) given. 1668 % 1669 % No warning will be given if no image was removed because all images had an 1670 % appropriate non-zero time delay set. 1671 % 1672 % Due to the special requirements of GIF disposal handling, GIF animations 1673 % should be coalesced first, before calling this function, though that is not 1674 % a requirement. 1675 % 1676 % The format of the RemoveZeroDelayLayers method is: 1677 % 1678 % void RemoveZeroDelayLayers(Image **image, ExceptionInfo *exception) 1679 % 1680 % A description of each parameter follows: 1681 % 1682 % o images: the image list 1683 % 1684 % o exception: return any errors or warnings in this structure. 1685 % 1686 */ 1687 MagickExport void RemoveZeroDelayLayers(Image **images, 1688 ExceptionInfo *exception) 1689 { 1690 Image 1691 *i; 1692 1693 assert((*images) != (const Image *) NULL); 1694 assert((*images)->signature == MagickCoreSignature); 1695 if ((*images)->debug != MagickFalse) 1696 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename); 1697 assert(exception != (ExceptionInfo *) NULL); 1698 assert(exception->signature == MagickCoreSignature); 1699 1700 i=GetFirstImageInList(*images); 1701 for ( ; i != (Image *) NULL; i=GetNextImageInList(i)) 1702 if ( i->delay != 0L ) break; 1703 if ( i == (Image *) NULL ) { 1704 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning, 1705 "ZeroTimeAnimation","`%s'",GetFirstImageInList(*images)->filename); 1706 return; 1707 } 1708 i=GetFirstImageInList(*images); 1709 while ( i != (Image *) NULL ) 1710 { 1711 if ( i->delay == 0L ) { 1712 (void) DeleteImageFromList(&i); 1713 *images=i; 1714 } 1715 else 1716 i=GetNextImageInList(i); 1717 } 1718 *images=GetFirstImageInList(*images); 1719 } 1720 1721 /* 1723 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1724 % % 1725 % % 1726 % % 1727 % C o m p o s i t e L a y e r s % 1728 % % 1729 % % 1730 % % 1731 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1732 % 1733 % CompositeLayers() compose the source image sequence over the destination 1734 % image sequence, starting with the current image in both lists. 1735 % 1736 % Each layer from the two image lists are composted together until the end of 1737 % one of the image lists is reached. The offset of each composition is also 1738 % adjusted to match the virtual canvas offsets of each layer. As such the 1739 % given offset is relative to the virtual canvas, and not the actual image. 1740 % 1741 % Composition uses given x and y offsets, as the 'origin' location of the 1742 % source images virtual canvas (not the real image) allowing you to compose a 1743 % list of 'layer images' into the destiantioni images. This makes it well 1744 % sutiable for directly composing 'Clears Frame Animations' or 'Coaleased 1745 % Animations' onto a static or other 'Coaleased Animation' destination image 1746 % list. GIF disposal handling is not looked at. 1747 % 1748 % Special case:- If one of the image sequences is the last image (just a 1749 % single image remaining), that image is repeatally composed with all the 1750 % images in the other image list. Either the source or destination lists may 1751 % be the single image, for this situation. 1752 % 1753 % In the case of a single destination image (or last image given), that image 1754 % will ve cloned to match the number of images remaining in the source image 1755 % list. 1756 % 1757 % This is equivelent to the "-layer Composite" Shell API operator. 1758 % 1759 % 1760 % The format of the CompositeLayers method is: 1761 % 1762 % void CompositeLayers(Image *destination, const CompositeOperator 1763 % compose, Image *source, const ssize_t x_offset, const ssize_t y_offset, 1764 % ExceptionInfo *exception); 1765 % 1766 % A description of each parameter follows: 1767 % 1768 % o destination: the destination images and results 1769 % 1770 % o source: source image(s) for the layer composition 1771 % 1772 % o compose, x_offset, y_offset: arguments passed on to CompositeImages() 1773 % 1774 % o exception: return any errors or warnings in this structure. 1775 % 1776 */ 1777 1778 static inline void CompositeCanvas(Image *destination, 1779 const CompositeOperator compose,Image *source,ssize_t x_offset, 1780 ssize_t y_offset,ExceptionInfo *exception) 1781 { 1782 x_offset+=source->page.x-destination->page.x; 1783 y_offset+=source->page.y-destination->page.y; 1784 (void) CompositeImage(destination,source,compose,MagickTrue,x_offset, 1785 y_offset,exception); 1786 } 1787 1788 MagickExport void CompositeLayers(Image *destination, 1789 const CompositeOperator compose, Image *source,const ssize_t x_offset, 1790 const ssize_t y_offset,ExceptionInfo *exception) 1791 { 1792 assert(destination != (Image *) NULL); 1793 assert(destination->signature == MagickCoreSignature); 1794 assert(source != (Image *) NULL); 1795 assert(source->signature == MagickCoreSignature); 1796 assert(exception != (ExceptionInfo *) NULL); 1797 assert(exception->signature == MagickCoreSignature); 1798 if (source->debug != MagickFalse || destination->debug != MagickFalse) 1799 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s - %s", 1800 source->filename, destination->filename); 1801 1802 /* 1803 Overlay single source image over destation image/list 1804 */ 1805 if ( source->next == (Image *) NULL ) 1806 while ( destination != (Image *) NULL ) 1807 { 1808 CompositeCanvas(destination, compose, source, x_offset, y_offset, 1809 exception); 1810 destination=GetNextImageInList(destination); 1811 } 1812 1813 /* 1814 Overlay source image list over single destination. 1815 Multiple clones of destination image are created to match source list. 1816 Original Destination image becomes first image of generated list. 1817 As such the image list pointer does not require any change in caller. 1818 Some animation attributes however also needs coping in this case. 1819 */ 1820 else if ( destination->next == (Image *) NULL ) 1821 { 1822 Image *dest = CloneImage(destination,0,0,MagickTrue,exception); 1823 1824 CompositeCanvas(destination, compose, source, x_offset, y_offset, 1825 exception); 1826 /* copy source image attributes ? */ 1827 if ( source->next != (Image *) NULL ) 1828 { 1829 destination->delay = source->delay; 1830 destination->iterations = source->iterations; 1831 } 1832 source=GetNextImageInList(source); 1833 1834 while ( source != (Image *) NULL ) 1835 { 1836 AppendImageToList(&destination, 1837 CloneImage(dest,0,0,MagickTrue,exception)); 1838 destination=GetLastImageInList(destination); 1839 1840 CompositeCanvas(destination, compose, source, x_offset, y_offset, 1841 exception); 1842 destination->delay = source->delay; 1843 destination->iterations = source->iterations; 1844 source=GetNextImageInList(source); 1845 } 1846 dest=DestroyImage(dest); 1847 } 1848 1849 /* 1850 Overlay a source image list over a destination image list 1851 until either list runs out of images. (Does not repeat) 1852 */ 1853 else 1854 while ( source != (Image *) NULL && destination != (Image *) NULL ) 1855 { 1856 CompositeCanvas(destination, compose, source, x_offset, y_offset, 1857 exception); 1858 source=GetNextImageInList(source); 1859 destination=GetNextImageInList(destination); 1860 } 1861 } 1862 1863 /* 1865 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1866 % % 1867 % % 1868 % % 1869 % M e r g e I m a g e L a y e r s % 1870 % % 1871 % % 1872 % % 1873 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1874 % 1875 % MergeImageLayers() composes all the image layers from the current given 1876 % image onward to produce a single image of the merged layers. 1877 % 1878 % The inital canvas's size depends on the given LayerMethod, and is 1879 % initialized using the first images background color. The images 1880 % are then compositied onto that image in sequence using the given 1881 % composition that has been assigned to each individual image. 1882 % 1883 % The format of the MergeImageLayers is: 1884 % 1885 % Image *MergeImageLayers(const Image *image, 1886 % const LayerMethod method, ExceptionInfo *exception) 1887 % 1888 % A description of each parameter follows: 1889 % 1890 % o image: the image list to be composited together 1891 % 1892 % o method: the method of selecting the size of the initial canvas. 1893 % 1894 % MergeLayer: Merge all layers onto a canvas just large enough 1895 % to hold all the actual images. The virtual canvas of the 1896 % first image is preserved but otherwise ignored. 1897 % 1898 % FlattenLayer: Use the virtual canvas size of first image. 1899 % Images which fall outside this canvas is clipped. 1900 % This can be used to 'fill out' a given virtual canvas. 1901 % 1902 % MosaicLayer: Start with the virtual canvas of the first image, 1903 % enlarging left and right edges to contain all images. 1904 % Images with negative offsets will be clipped. 1905 % 1906 % TrimBoundsLayer: Determine the overall bounds of all the image 1907 % layers just as in "MergeLayer", then adjust the the canvas 1908 % and offsets to be relative to those bounds, without overlaying 1909 % the images. 1910 % 1911 % WARNING: a new image is not returned, the original image 1912 % sequence page data is modified instead. 1913 % 1914 % o exception: return any errors or warnings in this structure. 1915 % 1916 */ 1917 MagickExport Image *MergeImageLayers(Image *image,const LayerMethod method, 1918 ExceptionInfo *exception) 1919 { 1920 #define MergeLayersTag "Merge/Layers" 1921 1922 Image 1923 *canvas; 1924 1925 MagickBooleanType 1926 proceed; 1927 1928 RectangleInfo 1929 page; 1930 1931 register const Image 1932 *next; 1933 1934 size_t 1935 number_images, 1936 height, 1937 width; 1938 1939 ssize_t 1940 scene; 1941 1942 assert(image != (Image *) NULL); 1943 assert(image->signature == MagickCoreSignature); 1944 if (image->debug != MagickFalse) 1945 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 1946 assert(exception != (ExceptionInfo *) NULL); 1947 assert(exception->signature == MagickCoreSignature); 1948 /* 1949 Determine canvas image size, and its virtual canvas size and offset 1950 */ 1951 page=image->page; 1952 width=image->columns; 1953 height=image->rows; 1954 switch (method) 1955 { 1956 case TrimBoundsLayer: 1957 case MergeLayer: 1958 default: 1959 { 1960 next=GetNextImageInList(image); 1961 for ( ; next != (Image *) NULL; next=GetNextImageInList(next)) 1962 { 1963 if (page.x > next->page.x) 1964 { 1965 width+=page.x-next->page.x; 1966 page.x=next->page.x; 1967 } 1968 if (page.y > next->page.y) 1969 { 1970 height+=page.y-next->page.y; 1971 page.y=next->page.y; 1972 } 1973 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns-page.x)) 1974 width=(size_t) next->page.x+(ssize_t) next->columns-page.x; 1975 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows-page.y)) 1976 height=(size_t) next->page.y+(ssize_t) next->rows-page.y; 1977 } 1978 break; 1979 } 1980 case FlattenLayer: 1981 { 1982 if (page.width > 0) 1983 width=page.width; 1984 if (page.height > 0) 1985 height=page.height; 1986 page.x=0; 1987 page.y=0; 1988 break; 1989 } 1990 case MosaicLayer: 1991 { 1992 if (page.width > 0) 1993 width=page.width; 1994 if (page.height > 0) 1995 height=page.height; 1996 for (next=image; next != (Image *) NULL; next=GetNextImageInList(next)) 1997 { 1998 if (method == MosaicLayer) 1999 { 2000 page.x=next->page.x; 2001 page.y=next->page.y; 2002 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns)) 2003 width=(size_t) next->page.x+next->columns; 2004 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows)) 2005 height=(size_t) next->page.y+next->rows; 2006 } 2007 } 2008 page.width=width; 2009 page.height=height; 2010 page.x=0; 2011 page.y=0; 2012 } 2013 break; 2014 } 2015 /* 2016 Set virtual canvas size if not defined. 2017 */ 2018 if (page.width == 0) 2019 page.width=page.x < 0 ? width : width+page.x; 2020 if (page.height == 0) 2021 page.height=page.y < 0 ? height : height+page.y; 2022 /* 2023 Handle "TrimBoundsLayer" method separately to normal 'layer merge'. 2024 */ 2025 if (method == TrimBoundsLayer) 2026 { 2027 number_images=GetImageListLength(image); 2028 for (scene=0; scene < (ssize_t) number_images; scene++) 2029 { 2030 image->page.x-=page.x; 2031 image->page.y-=page.y; 2032 image->page.width=width; 2033 image->page.height=height; 2034 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene, 2035 number_images); 2036 if (proceed == MagickFalse) 2037 break; 2038 image=GetNextImageInList(image); 2039 if (image == (Image *) NULL) 2040 break; 2041 } 2042 return((Image *) NULL); 2043 } 2044 /* 2045 Create canvas size of width and height, and background color. 2046 */ 2047 canvas=CloneImage(image,width,height,MagickTrue,exception); 2048 if (canvas == (Image *) NULL) 2049 return((Image *) NULL); 2050 (void) SetImageBackgroundColor(canvas,exception); 2051 canvas->page=page; 2052 canvas->dispose=UndefinedDispose; 2053 /* 2054 Compose images onto canvas, with progress monitor 2055 */ 2056 number_images=GetImageListLength(image); 2057 for (scene=0; scene < (ssize_t) number_images; scene++) 2058 { 2059 (void) CompositeImage(canvas,image,image->compose,MagickTrue,image->page.x- 2060 canvas->page.x,image->page.y-canvas->page.y,exception); 2061 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene, 2062 number_images); 2063 if (proceed == MagickFalse) 2064 break; 2065 image=GetNextImageInList(image); 2066 if (image == (Image *) NULL) 2067 break; 2068 } 2069 return(canvas); 2070 } 2071 2072