|
MagickCore
6.7.5
|
00001 /* 00002 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00003 % % 00004 % % 00005 % L AAA Y Y EEEEE RRRR % 00006 % L A A Y Y E R R % 00007 % L AAAAA Y EEE RRRR % 00008 % L A A Y E R R % 00009 % LLLLL A A Y EEEEE R R % 00010 % % 00011 % MagickCore Image Layering Methods % 00012 % % 00013 % Software Design % 00014 % John Cristy % 00015 % Anthony Thyssen % 00016 % January 2006 % 00017 % % 00018 % % 00019 % Copyright 1999-2012 ImageMagick Studio LLC, a non-profit organization % 00020 % dedicated to making software imaging solutions freely available. % 00021 % % 00022 % You may not use this file except in compliance with the License. You may % 00023 % obtain a copy of the License at % 00024 % % 00025 % http://www.imagemagick.org/script/license.php % 00026 % % 00027 % Unless required by applicable law or agreed to in writing, software % 00028 % distributed under the License is distributed on an "AS IS" BASIS, % 00029 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % 00030 % See the License for the specific language governing permissions and % 00031 % limitations under the License. % 00032 % % 00033 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00034 % 00035 */ 00036 00037 /* 00038 Include declarations. 00039 */ 00040 #include "MagickCore/studio.h" 00041 #include "MagickCore/artifact.h" 00042 #include "MagickCore/cache.h" 00043 #include "MagickCore/color.h" 00044 #include "MagickCore/color-private.h" 00045 #include "MagickCore/composite.h" 00046 #include "MagickCore/effect.h" 00047 #include "MagickCore/exception.h" 00048 #include "MagickCore/exception-private.h" 00049 #include "MagickCore/geometry.h" 00050 #include "MagickCore/image.h" 00051 #include "MagickCore/layer.h" 00052 #include "MagickCore/list.h" 00053 #include "MagickCore/memory_.h" 00054 #include "MagickCore/monitor.h" 00055 #include "MagickCore/monitor-private.h" 00056 #include "MagickCore/option.h" 00057 #include "MagickCore/pixel-accessor.h" 00058 #include "MagickCore/property.h" 00059 #include "MagickCore/profile.h" 00060 #include "MagickCore/resource_.h" 00061 #include "MagickCore/resize.h" 00062 #include "MagickCore/statistic.h" 00063 #include "MagickCore/string_.h" 00064 #include "MagickCore/transform.h" 00065 00066 /* 00067 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00068 % % 00069 % % 00070 % % 00071 + C l e a r B o u n d s % 00072 % % 00073 % % 00074 % % 00075 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00076 % 00077 % ClearBounds() Clear the area specified by the bounds in an image to 00078 % transparency. This typically used to handle Background Disposal 00079 % for the previous frame in an animation sequence. 00080 % 00081 % WARNING: no bounds checks are performed, except for the null or 00082 % missed image, for images that don't change. in all other cases 00083 % bound must fall within the image. 00084 % 00085 % The format is: 00086 % 00087 % void ClearBounds(Image *image,RectangleInfo *bounds 00088 % ExceptionInfo *exception) 00089 % 00090 % A description of each parameter follows: 00091 % 00092 % o image: the image to had the area cleared in 00093 % 00094 % o bounds: the area to be clear within the imag image 00095 % 00096 % o exception: return any errors or warnings in this structure. 00097 % 00098 */ 00099 static void ClearBounds(Image *image,RectangleInfo *bounds, 00100 ExceptionInfo *exception) 00101 { 00102 ssize_t 00103 y; 00104 00105 if (bounds->x < 0) 00106 return; 00107 if (image->matte == MagickFalse) 00108 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception); 00109 for (y=0; y < (ssize_t) bounds->height; y++) 00110 { 00111 register ssize_t 00112 x; 00113 00114 register Quantum 00115 *restrict q; 00116 00117 q=GetAuthenticPixels(image,bounds->x,bounds->y+y,bounds->width,1,exception); 00118 if (q == (Quantum *) NULL) 00119 break; 00120 for (x=0; x < (ssize_t) bounds->width; x++) 00121 { 00122 SetPixelAlpha(image,TransparentAlpha,q); 00123 q+=GetPixelChannels(image); 00124 } 00125 if (SyncAuthenticPixels(image,exception) == MagickFalse) 00126 break; 00127 } 00128 } 00129 00130 /* 00131 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00132 % % 00133 % % 00134 % % 00135 + I s B o u n d s C l e a r e d % 00136 % % 00137 % % 00138 % % 00139 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00140 % 00141 % IsBoundsCleared() tests whether any pixel in the bounds given, gets cleared 00142 % when going from the first image to the second image. This typically used 00143 % to check if a proposed disposal method will work successfully to generate 00144 % the second frame image from the first disposed form of the previous frame. 00145 % 00146 % The format is: 00147 % 00148 % MagickBooleanType IsBoundsCleared(const Image *image1, 00149 % const Image *image2,RectangleInfo bounds,ExceptionInfo *exception) 00150 % 00151 % A description of each parameter follows: 00152 % 00153 % o image1, image 2: the images to check for cleared pixels 00154 % 00155 % o bounds: the area to be clear within the imag image 00156 % 00157 % o exception: return any errors or warnings in this structure. 00158 % 00159 % WARNING: no bounds checks are performed, except for the null or 00160 % missed image, for images that don't change. in all other cases 00161 % bound must fall within the image. 00162 % 00163 */ 00164 static MagickBooleanType IsBoundsCleared(const Image *image1, 00165 const Image *image2,RectangleInfo *bounds,ExceptionInfo *exception) 00166 { 00167 register ssize_t 00168 x; 00169 00170 register const Quantum 00171 *p, 00172 *q; 00173 00174 ssize_t 00175 y; 00176 00177 if (bounds->x < 0) 00178 return(MagickFalse); 00179 for (y=0; y < (ssize_t) bounds->height; y++) 00180 { 00181 p=GetVirtualPixels(image1,bounds->x,bounds->y+y,bounds->width,1, 00182 exception); 00183 q=GetVirtualPixels(image2,bounds->x,bounds->y+y,bounds->width,1, 00184 exception); 00185 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 00186 break; 00187 for (x=0; x < (ssize_t) bounds->width; x++) 00188 { 00189 if ((GetPixelAlpha(image1,p) <= (Quantum) (QuantumRange/2)) && 00190 (GetPixelAlpha(image1,q) > (Quantum) (QuantumRange/2))) 00191 break; 00192 p+=GetPixelChannels(image1); 00193 q++; 00194 } 00195 if (x < (ssize_t) bounds->width) 00196 break; 00197 } 00198 return(y < (ssize_t) bounds->height ? MagickTrue : MagickFalse); 00199 } 00200 00201 /* 00202 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00203 % % 00204 % % 00205 % % 00206 % C o a l e s c e I m a g e s % 00207 % % 00208 % % 00209 % % 00210 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00211 % 00212 % CoalesceImages() composites a set of images while respecting any page 00213 % offsets and disposal methods. GIF, MIFF, and MNG animation sequences 00214 % typically start with an image background and each subsequent image 00215 % varies in size and offset. A new image sequence is returned with all 00216 % images the same size as the first images virtual canvas and composited 00217 % with the next image in the sequence. 00218 % 00219 % The format of the CoalesceImages method is: 00220 % 00221 % Image *CoalesceImages(Image *image,ExceptionInfo *exception) 00222 % 00223 % A description of each parameter follows: 00224 % 00225 % o image: the image sequence. 00226 % 00227 % o exception: return any errors or warnings in this structure. 00228 % 00229 */ 00230 MagickExport Image *CoalesceImages(const Image *image,ExceptionInfo *exception) 00231 { 00232 Image 00233 *coalesce_image, 00234 *dispose_image, 00235 *previous; 00236 00237 register Image 00238 *next; 00239 00240 RectangleInfo 00241 bounds; 00242 00243 /* 00244 Coalesce the image sequence. 00245 */ 00246 assert(image != (Image *) NULL); 00247 assert(image->signature == MagickSignature); 00248 if (image->debug != MagickFalse) 00249 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 00250 assert(exception != (ExceptionInfo *) NULL); 00251 assert(exception->signature == MagickSignature); 00252 next=GetFirstImageInList(image); 00253 bounds=next->page; 00254 if (bounds.width == 0) 00255 { 00256 bounds.width=next->columns; 00257 if (bounds.x > 0) 00258 bounds.width+=bounds.x; 00259 } 00260 if (bounds.height == 0) 00261 { 00262 bounds.height=next->rows; 00263 if (bounds.y > 0) 00264 bounds.height+=bounds.y; 00265 } 00266 bounds.x=0; 00267 bounds.y=0; 00268 coalesce_image=CloneImage(next,bounds.width,bounds.height,MagickTrue, 00269 exception); 00270 if (coalesce_image == (Image *) NULL) 00271 return((Image *) NULL); 00272 coalesce_image->page=bounds; 00273 coalesce_image->dispose=NoneDispose; 00274 (void) SetImageBackgroundColor(coalesce_image,exception); 00275 /* 00276 Coalesce rest of the images. 00277 */ 00278 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception); 00279 (void) CompositeImage(coalesce_image,CopyCompositeOp,next,next->page.x, 00280 next->page.y,exception); 00281 next=GetNextImageInList(next); 00282 for ( ; next != (Image *) NULL; next=GetNextImageInList(next)) 00283 { 00284 /* 00285 Determine the bounds that was overlaid in the previous image. 00286 */ 00287 previous=GetPreviousImageInList(next); 00288 bounds=previous->page; 00289 bounds.width=previous->columns; 00290 bounds.height=previous->rows; 00291 if (bounds.x < 0) 00292 { 00293 bounds.width+=bounds.x; 00294 bounds.x=0; 00295 } 00296 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) coalesce_image->columns) 00297 bounds.width=coalesce_image->columns-bounds.x; 00298 if (bounds.y < 0) 00299 { 00300 bounds.height+=bounds.y; 00301 bounds.y=0; 00302 } 00303 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) coalesce_image->rows) 00304 bounds.height=coalesce_image->rows-bounds.y; 00305 /* 00306 Replace the dispose image with the new coalesced image. 00307 */ 00308 if (GetPreviousImageInList(next)->dispose != PreviousDispose) 00309 { 00310 dispose_image=DestroyImage(dispose_image); 00311 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception); 00312 if (dispose_image == (Image *) NULL) 00313 { 00314 coalesce_image=DestroyImageList(coalesce_image); 00315 return((Image *) NULL); 00316 } 00317 } 00318 /* 00319 Clear the overlaid area of the coalesced bounds for background disposal 00320 */ 00321 if (next->previous->dispose == BackgroundDispose) 00322 ClearBounds(dispose_image,&bounds,exception); 00323 /* 00324 Next image is the dispose image, overlaid with next frame in sequence. 00325 */ 00326 coalesce_image->next=CloneImage(dispose_image,0,0,MagickTrue,exception); 00327 coalesce_image->next->previous=coalesce_image; 00328 previous=coalesce_image; 00329 coalesce_image=GetNextImageInList(coalesce_image); 00330 coalesce_image->matte=MagickTrue; 00331 (void) CompositeImage(coalesce_image,next->matte != MagickFalse ? 00332 OverCompositeOp : CopyCompositeOp,next,next->page.x,next->page.y, 00333 exception); 00334 (void) CloneImageProfiles(coalesce_image,next); 00335 (void) CloneImageProperties(coalesce_image,next); 00336 (void) CloneImageArtifacts(coalesce_image,next); 00337 coalesce_image->page=previous->page; 00338 /* 00339 If a pixel goes opaque to transparent, use background dispose. 00340 */ 00341 if (IsBoundsCleared(previous,coalesce_image,&bounds,exception)) 00342 coalesce_image->dispose=BackgroundDispose; 00343 else 00344 coalesce_image->dispose=NoneDispose; 00345 previous->dispose=coalesce_image->dispose; 00346 } 00347 dispose_image=DestroyImage(dispose_image); 00348 return(GetFirstImageInList(coalesce_image)); 00349 } 00350 00351 /* 00352 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00353 % % 00354 % % 00355 % % 00356 % D i s p o s e I m a g e s % 00357 % % 00358 % % 00359 % % 00360 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00361 % 00362 % DisposeImages() returns the coalesced frames of a GIF animation as it would 00363 % appear after the GIF dispose method of that frame has been applied. That 00364 % is it returned the appearance of each frame before the next is overlaid. 00365 % 00366 % The format of the DisposeImages method is: 00367 % 00368 % Image *DisposeImages(Image *image,ExceptionInfo *exception) 00369 % 00370 % A description of each parameter follows: 00371 % 00372 % o image: the image sequence. 00373 % 00374 % o exception: return any errors or warnings in this structure. 00375 % 00376 */ 00377 MagickExport Image *DisposeImages(const Image *image,ExceptionInfo *exception) 00378 { 00379 Image 00380 *dispose_image, 00381 *dispose_images; 00382 00383 register Image 00384 *curr; 00385 00386 RectangleInfo 00387 bounds; 00388 00389 /* 00390 Run the image through the animation sequence 00391 */ 00392 assert(image != (Image *) NULL); 00393 assert(image->signature == MagickSignature); 00394 if (image->debug != MagickFalse) 00395 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 00396 assert(exception != (ExceptionInfo *) NULL); 00397 assert(exception->signature == MagickSignature); 00398 curr=GetFirstImageInList(image); 00399 dispose_image=CloneImage(curr,curr->page.width,curr->page.height,MagickTrue, 00400 exception); 00401 if (dispose_image == (Image *) NULL) 00402 return((Image *) NULL); 00403 dispose_image->page=curr->page; 00404 dispose_image->page.x=0; 00405 dispose_image->page.y=0; 00406 dispose_image->dispose=NoneDispose; 00407 dispose_image->background_color.alpha=(Quantum) TransparentAlpha; 00408 (void) SetImageBackgroundColor(dispose_image,exception); 00409 dispose_images=NewImageList(); 00410 for ( ; curr != (Image *) NULL; curr=GetNextImageInList(curr)) 00411 { 00412 Image 00413 *current_image; 00414 00415 /* 00416 Overlay this frame's image over the previous disposal image. 00417 */ 00418 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception); 00419 if (current_image == (Image *) NULL) 00420 { 00421 dispose_images=DestroyImageList(dispose_images); 00422 dispose_image=DestroyImage(dispose_image); 00423 return((Image *) NULL); 00424 } 00425 (void) CompositeImage(current_image,curr->matte != MagickFalse ? 00426 OverCompositeOp : CopyCompositeOp,curr,curr->page.x,curr->page.y, 00427 exception); 00428 /* 00429 Handle Background dispose: image is displayed for the delay period. 00430 */ 00431 if (curr->dispose == BackgroundDispose) 00432 { 00433 bounds=curr->page; 00434 bounds.width=curr->columns; 00435 bounds.height=curr->rows; 00436 if (bounds.x < 0) 00437 { 00438 bounds.width+=bounds.x; 00439 bounds.x=0; 00440 } 00441 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns) 00442 bounds.width=current_image->columns-bounds.x; 00443 if (bounds.y < 0) 00444 { 00445 bounds.height+=bounds.y; 00446 bounds.y=0; 00447 } 00448 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows) 00449 bounds.height=current_image->rows-bounds.y; 00450 ClearBounds(current_image,&bounds,exception); 00451 } 00452 /* 00453 Select the appropriate previous/disposed image. 00454 */ 00455 if (curr->dispose == PreviousDispose) 00456 current_image=DestroyImage(current_image); 00457 else 00458 { 00459 dispose_image=DestroyImage(dispose_image); 00460 dispose_image=current_image; 00461 current_image=(Image *)NULL; 00462 } 00463 /* 00464 Save the dispose image just calculated for return. 00465 */ 00466 { 00467 Image 00468 *dispose; 00469 00470 dispose=CloneImage(dispose_image,0,0,MagickTrue,exception); 00471 if (dispose == (Image *) NULL) 00472 { 00473 dispose_images=DestroyImageList(dispose_images); 00474 dispose_image=DestroyImage(dispose_image); 00475 return((Image *) NULL); 00476 } 00477 (void) CloneImageProfiles(dispose,curr); 00478 (void) CloneImageProperties(dispose,curr); 00479 (void) CloneImageArtifacts(dispose,curr); 00480 dispose->page.x=0; 00481 dispose->page.y=0; 00482 dispose->dispose=curr->dispose; 00483 AppendImageToList(&dispose_images,dispose); 00484 } 00485 } 00486 dispose_image=DestroyImage(dispose_image); 00487 return(GetFirstImageInList(dispose_images)); 00488 } 00489 00490 /* 00491 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00492 % % 00493 % % 00494 % % 00495 + C o m p a r e P i x e l s % 00496 % % 00497 % % 00498 % % 00499 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00500 % 00501 % ComparePixels() Compare the two pixels and return true if the pixels 00502 % differ according to the given LayerType comparision method. 00503 % 00504 % This currently only used internally by CompareImagesBounds(). It is 00505 % doubtful that this sub-routine will be useful outside this module. 00506 % 00507 % The format of the ComparePixels method is: 00508 % 00509 % MagickBooleanType *ComparePixels(const ImageLayerMethod method, 00510 % const PixelInfo *p,const PixelInfo *q) 00511 % 00512 % A description of each parameter follows: 00513 % 00514 % o method: What differences to look for. Must be one of 00515 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer. 00516 % 00517 % o p, q: the pixels to test for appropriate differences. 00518 % 00519 */ 00520 00521 static MagickBooleanType ComparePixels(const ImageLayerMethod method, 00522 const PixelInfo *p,const PixelInfo *q) 00523 { 00524 MagickRealType 00525 o1, 00526 o2; 00527 00528 /* 00529 Any change in pixel values 00530 */ 00531 if (method == CompareAnyLayer) 00532 return((MagickBooleanType)(IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse)); 00533 00534 o1 = (p->matte != MagickFalse) ? p->alpha : OpaqueAlpha; 00535 o2 = (q->matte != MagickFalse) ? q->alpha : OpaqueAlpha; 00536 00537 /* 00538 Pixel goes from opaque to transprency 00539 */ 00540 if (method == CompareClearLayer) 00541 return((MagickBooleanType) ( (o1 <= ((MagickRealType) QuantumRange/2.0)) && 00542 (o2 > ((MagickRealType) QuantumRange/2.0)) ) ); 00543 00544 /* 00545 overlay would change first pixel by second 00546 */ 00547 if (method == CompareOverlayLayer) 00548 { 00549 if (o2 > ((MagickRealType) QuantumRange/2.0)) 00550 return MagickFalse; 00551 return((MagickBooleanType) (IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse)); 00552 } 00553 return(MagickFalse); 00554 } 00555 00556 00557 /* 00558 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00559 % % 00560 % % 00561 % % 00562 + C o m p a r e I m a g e B o u n d s % 00563 % % 00564 % % 00565 % % 00566 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00567 % 00568 % CompareImagesBounds() Given two images return the smallest rectangular area 00569 % by which the two images differ, accourding to the given 'Compare...' 00570 % layer method. 00571 % 00572 % This currently only used internally in this module, but may eventually 00573 % be used by other modules. 00574 % 00575 % The format of the CompareImagesBounds method is: 00576 % 00577 % RectangleInfo *CompareImagesBounds(const ImageLayerMethod method, 00578 % const Image *image1, const Image *image2, ExceptionInfo *exception) 00579 % 00580 % A description of each parameter follows: 00581 % 00582 % o method: What differences to look for. Must be one of CompareAnyLayer, 00583 % CompareClearLayer, CompareOverlayLayer. 00584 % 00585 % o image1, image2: the two images to compare. 00586 % 00587 % o exception: return any errors or warnings in this structure. 00588 % 00589 */ 00590 00591 static RectangleInfo CompareImagesBounds(const Image *image1,const Image *image2, 00592 const ImageLayerMethod method,ExceptionInfo *exception) 00593 { 00594 RectangleInfo 00595 bounds; 00596 00597 PixelInfo 00598 pixel1, 00599 pixel2; 00600 00601 register const Quantum 00602 *p, 00603 *q; 00604 00605 register ssize_t 00606 x; 00607 00608 ssize_t 00609 y; 00610 00611 /* 00612 Set bounding box of the differences between images. 00613 */ 00614 GetPixelInfo(image1,&pixel1); 00615 GetPixelInfo(image2,&pixel2); 00616 for (x=0; x < (ssize_t) image1->columns; x++) 00617 { 00618 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception); 00619 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception); 00620 if ((p == (const Quantum *) NULL) || 00621 (q == (Quantum *) NULL)) 00622 break; 00623 for (y=0; y < (ssize_t) image1->rows; y++) 00624 { 00625 GetPixelInfoPixel(image1,p,&pixel1); 00626 GetPixelInfoPixel(image2,q,&pixel2); 00627 if (ComparePixels(method,&pixel1,&pixel2)) 00628 break; 00629 p+=GetPixelChannels(image1); 00630 q++; 00631 } 00632 if (y < (ssize_t) image1->rows) 00633 break; 00634 } 00635 if (x >= (ssize_t) image1->columns) 00636 { 00637 /* 00638 Images are identical, return a null image. 00639 */ 00640 bounds.x=-1; 00641 bounds.y=-1; 00642 bounds.width=1; 00643 bounds.height=1; 00644 return(bounds); 00645 } 00646 bounds.x=x; 00647 for (x=(ssize_t) image1->columns-1; x >= 0; x--) 00648 { 00649 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception); 00650 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception); 00651 if ((p == (const Quantum *) NULL) || 00652 (q == (Quantum *) NULL)) 00653 break; 00654 for (y=0; y < (ssize_t) image1->rows; y++) 00655 { 00656 GetPixelInfoPixel(image1,p,&pixel1); 00657 GetPixelInfoPixel(image2,q,&pixel2); 00658 if (ComparePixels(method,&pixel1,&pixel2)) 00659 break; 00660 p+=GetPixelChannels(image1); 00661 q++; 00662 } 00663 if (y < (ssize_t) image1->rows) 00664 break; 00665 } 00666 bounds.width=(size_t) (x-bounds.x+1); 00667 for (y=0; y < (ssize_t) image1->rows; y++) 00668 { 00669 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception); 00670 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception); 00671 if ((p == (const Quantum *) NULL) || 00672 (q == (Quantum *) NULL)) 00673 break; 00674 for (x=0; x < (ssize_t) image1->columns; x++) 00675 { 00676 GetPixelInfoPixel(image1,p,&pixel1); 00677 GetPixelInfoPixel(image2,q,&pixel2); 00678 if (ComparePixels(method,&pixel1,&pixel2)) 00679 break; 00680 p+=GetPixelChannels(image1); 00681 q++; 00682 } 00683 if (x < (ssize_t) image1->columns) 00684 break; 00685 } 00686 bounds.y=y; 00687 for (y=(ssize_t) image1->rows-1; y >= 0; y--) 00688 { 00689 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception); 00690 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception); 00691 if ((p == (const Quantum *) NULL) || 00692 (q == (Quantum *) NULL)) 00693 break; 00694 for (x=0; x < (ssize_t) image1->columns; x++) 00695 { 00696 GetPixelInfoPixel(image1,p,&pixel1); 00697 GetPixelInfoPixel(image2,q,&pixel2); 00698 if (ComparePixels(method,&pixel1,&pixel2)) 00699 break; 00700 p+=GetPixelChannels(image1); 00701 q++; 00702 } 00703 if (x < (ssize_t) image1->columns) 00704 break; 00705 } 00706 bounds.height=(size_t) (y-bounds.y+1); 00707 return(bounds); 00708 } 00709 00710 /* 00711 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00712 % % 00713 % % 00714 % % 00715 % C o m p a r e I m a g e L a y e r s % 00716 % % 00717 % % 00718 % % 00719 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00720 % 00721 % CompareImagesLayers() compares each image with the next in a sequence and 00722 % returns the minimum bounding region of all the pixel differences (of the 00723 % ImageLayerMethod specified) it discovers. 00724 % 00725 % Images do NOT have to be the same size, though it is best that all the 00726 % images are 'coalesced' (images are all the same size, on a flattened 00727 % canvas, so as to represent exactly how an specific frame should look). 00728 % 00729 % No GIF dispose methods are applied, so GIF animations must be coalesced 00730 % before applying this image operator to find differences to them. 00731 % 00732 % The format of the CompareImagesLayers method is: 00733 % 00734 % Image *CompareImagesLayers(const Image *images, 00735 % const ImageLayerMethod method,ExceptionInfo *exception) 00736 % 00737 % A description of each parameter follows: 00738 % 00739 % o image: the image. 00740 % 00741 % o method: the layers type to compare images with. Must be one of... 00742 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer. 00743 % 00744 % o exception: return any errors or warnings in this structure. 00745 % 00746 */ 00747 00748 MagickExport Image *CompareImagesLayers(const Image *image, 00749 const ImageLayerMethod method, ExceptionInfo *exception) 00750 { 00751 Image 00752 *image_a, 00753 *image_b, 00754 *layers; 00755 00756 RectangleInfo 00757 *bounds; 00758 00759 register const Image 00760 *next; 00761 00762 register ssize_t 00763 i; 00764 00765 assert(image != (const Image *) NULL); 00766 assert(image->signature == MagickSignature); 00767 if (image->debug != MagickFalse) 00768 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 00769 assert(exception != (ExceptionInfo *) NULL); 00770 assert(exception->signature == MagickSignature); 00771 assert((method == CompareAnyLayer) || 00772 (method == CompareClearLayer) || 00773 (method == CompareOverlayLayer)); 00774 /* 00775 Allocate bounds memory. 00776 */ 00777 next=GetFirstImageInList(image); 00778 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t) 00779 GetImageListLength(next),sizeof(*bounds)); 00780 if (bounds == (RectangleInfo *) NULL) 00781 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 00782 /* 00783 Set up first comparision images. 00784 */ 00785 image_a=CloneImage(next,next->page.width,next->page.height, 00786 MagickTrue,exception); 00787 if (image_a == (Image *) NULL) 00788 { 00789 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 00790 return((Image *) NULL); 00791 } 00792 image_a->background_color.alpha=(Quantum) TransparentAlpha; 00793 (void) SetImageBackgroundColor(image_a,exception); 00794 image_a->page=next->page; 00795 image_a->page.x=0; 00796 image_a->page.y=0; 00797 (void) CompositeImage(image_a,CopyCompositeOp,next,next->page.x,next->page.y, 00798 exception); 00799 /* 00800 Compute the bounding box of changes for the later images 00801 */ 00802 i=0; 00803 next=GetNextImageInList(next); 00804 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next)) 00805 { 00806 image_b=CloneImage(image_a,0,0,MagickTrue,exception); 00807 if (image_b == (Image *) NULL) 00808 { 00809 image_a=DestroyImage(image_a); 00810 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 00811 return((Image *) NULL); 00812 } 00813 (void) CompositeImage(image_a,CopyCompositeOp,next,next->page.x, 00814 next->page.y,exception); 00815 bounds[i]=CompareImagesBounds(image_b,image_a,method,exception); 00816 00817 image_b=DestroyImage(image_b); 00818 i++; 00819 } 00820 image_a=DestroyImage(image_a); 00821 /* 00822 Clone first image in sequence. 00823 */ 00824 next=GetFirstImageInList(image); 00825 layers=CloneImage(next,0,0,MagickTrue,exception); 00826 if (layers == (Image *) NULL) 00827 { 00828 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 00829 return((Image *) NULL); 00830 } 00831 /* 00832 Deconstruct the image sequence. 00833 */ 00834 i=0; 00835 next=GetNextImageInList(next); 00836 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next)) 00837 { 00838 image_a=CloneImage(next,0,0,MagickTrue,exception); 00839 if (image_a == (Image *) NULL) 00840 break; 00841 image_b=CropImage(image_a,&bounds[i],exception); 00842 image_a=DestroyImage(image_a); 00843 if (image_b == (Image *) NULL) 00844 break; 00845 AppendImageToList(&layers,image_b); 00846 i++; 00847 } 00848 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 00849 if (next != (Image *) NULL) 00850 { 00851 layers=DestroyImageList(layers); 00852 return((Image *) NULL); 00853 } 00854 return(GetFirstImageInList(layers)); 00855 } 00856 00857 /* 00858 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00859 % % 00860 % % 00861 % % 00862 + O p t i m i z e L a y e r F r a m e s % 00863 % % 00864 % % 00865 % % 00866 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00867 % 00868 % OptimizeLayerFrames() takes a coalesced GIF animation, and compares each 00869 % frame against the three different 'disposal' forms of the previous frame. 00870 % From this it then attempts to select the smallest cropped image and 00871 % disposal method needed to reproduce the resulting image. 00872 % 00873 % Note that this not easy, and may require the expansion of the bounds 00874 % of previous frame, simply clear pixels for the next animation frame to 00875 % transparency according to the selected dispose method. 00876 % 00877 % The format of the OptimizeLayerFrames method is: 00878 % 00879 % Image *OptimizeLayerFrames(const Image *image, 00880 % const ImageLayerMethod method, ExceptionInfo *exception) 00881 % 00882 % A description of each parameter follows: 00883 % 00884 % o image: the image. 00885 % 00886 % o method: the layers technique to optimize with. Must be one of... 00887 % OptimizeImageLayer, or OptimizePlusLayer. The Plus form allows 00888 % the addition of extra 'zero delay' frames to clear pixels from 00889 % the previous frame, and the removal of frames that done change, 00890 % merging the delay times together. 00891 % 00892 % o exception: return any errors or warnings in this structure. 00893 % 00894 */ 00895 /* 00896 Define a 'fake' dispose method where the frame is duplicated, (for 00897 OptimizePlusLayer) with a extra zero time delay frame which does a 00898 BackgroundDisposal to clear the pixels that need to be cleared. 00899 */ 00900 #define DupDispose ((DisposeType)9) 00901 /* 00902 Another 'fake' dispose method used to removed frames that don't change. 00903 */ 00904 #define DelDispose ((DisposeType)8) 00905 00906 #define DEBUG_OPT_FRAME 0 00907 00908 static Image *OptimizeLayerFrames(const Image *image, 00909 const ImageLayerMethod method, ExceptionInfo *exception) 00910 { 00911 ExceptionInfo 00912 *sans_exception; 00913 00914 Image 00915 *prev_image, 00916 *dup_image, 00917 *bgnd_image, 00918 *optimized_image; 00919 00920 RectangleInfo 00921 try_bounds, 00922 bgnd_bounds, 00923 dup_bounds, 00924 *bounds; 00925 00926 MagickBooleanType 00927 add_frames, 00928 try_cleared, 00929 cleared; 00930 00931 DisposeType 00932 *disposals; 00933 00934 register const Image 00935 *curr; 00936 00937 register ssize_t 00938 i; 00939 00940 assert(image != (const Image *) NULL); 00941 assert(image->signature == MagickSignature); 00942 if (image->debug != MagickFalse) 00943 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 00944 assert(exception != (ExceptionInfo *) NULL); 00945 assert(exception->signature == MagickSignature); 00946 assert(method == OptimizeLayer || 00947 method == OptimizeImageLayer || 00948 method == OptimizePlusLayer); 00949 00950 /* 00951 Are we allowed to add/remove frames from animation 00952 */ 00953 add_frames=method == OptimizePlusLayer ? MagickTrue : MagickFalse; 00954 /* 00955 Ensure all the images are the same size 00956 */ 00957 curr=GetFirstImageInList(image); 00958 for (; curr != (Image *) NULL; curr=GetNextImageInList(curr)) 00959 { 00960 if ((curr->columns != image->columns) || (curr->rows != image->rows)) 00961 ThrowImageException(OptionError,"ImagesAreNotTheSameSize"); 00962 /* 00963 FUTURE: also check that image is also fully coalesced (full page) 00964 Though as long as they are the same size it should not matter. 00965 */ 00966 } 00967 /* 00968 Allocate memory (times 2 if we allow the use of frame duplications) 00969 */ 00970 curr=GetFirstImageInList(image); 00971 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t) 00972 GetImageListLength(curr),(add_frames != MagickFalse ? 2UL : 1UL)* 00973 sizeof(*bounds)); 00974 if (bounds == (RectangleInfo *) NULL) 00975 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 00976 disposals=(DisposeType *) AcquireQuantumMemory((size_t) 00977 GetImageListLength(image),(add_frames != MagickFalse ? 2UL : 1UL)* 00978 sizeof(*disposals)); 00979 if (disposals == (DisposeType *) NULL) 00980 { 00981 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 00982 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 00983 } 00984 /* 00985 Initialise Previous Image as fully transparent 00986 */ 00987 prev_image=CloneImage(curr,curr->page.width,curr->page.height, 00988 MagickTrue,exception); 00989 if (prev_image == (Image *) NULL) 00990 { 00991 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 00992 disposals=(DisposeType *) RelinquishMagickMemory(disposals); 00993 return((Image *) NULL); 00994 } 00995 prev_image->page=curr->page; /* ERROR: <-- should not be need, but is! */ 00996 prev_image->page.x=0; 00997 prev_image->page.y=0; 00998 prev_image->dispose=NoneDispose; 00999 01000 prev_image->background_color.alpha=(Quantum) TransparentAlpha; 01001 (void) SetImageBackgroundColor(prev_image,exception); 01002 /* 01003 Figure out the area of overlay of the first frame 01004 No pixel could be cleared as all pixels are already cleared. 01005 */ 01006 #if DEBUG_OPT_FRAME 01007 i=0; 01008 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i); 01009 #endif 01010 disposals[0]=NoneDispose; 01011 bounds[0]=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception); 01012 #if DEBUG_OPT_FRAME 01013 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g\n\n", 01014 (double) bounds[i].width,(double) bounds[i].height, 01015 (double) bounds[i].x,(double) bounds[i].y ); 01016 #endif 01017 /* 01018 Compute the bounding box of changes for each pair of images. 01019 */ 01020 i=1; 01021 bgnd_image=(Image *)NULL; 01022 dup_image=(Image *)NULL; 01023 dup_bounds.width=0; 01024 dup_bounds.height=0; 01025 dup_bounds.x=0; 01026 dup_bounds.y=0; 01027 curr=GetNextImageInList(curr); 01028 for ( ; curr != (const Image *) NULL; curr=GetNextImageInList(curr)) 01029 { 01030 #if DEBUG_OPT_FRAME 01031 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i); 01032 #endif 01033 /* 01034 Assume none disposal is the best 01035 */ 01036 bounds[i]=CompareImagesBounds(curr->previous,curr,CompareAnyLayer,exception); 01037 cleared=IsBoundsCleared(curr->previous,curr,&bounds[i],exception); 01038 disposals[i-1]=NoneDispose; 01039 #if DEBUG_OPT_FRAME 01040 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g%s%s\n", 01041 (double) bounds[i].width,(double) bounds[i].height, 01042 (double) bounds[i].x,(double) bounds[i].y, 01043 bounds[i].x < 0?" (unchanged)":"", 01044 cleared?" (pixels cleared)":""); 01045 #endif 01046 if ( bounds[i].x < 0 ) { 01047 /* 01048 Image frame is exactly the same as the previous frame! 01049 If not adding frames leave it to be cropped down to a null image. 01050 Otherwise mark previous image for deleted, transfering its crop bounds 01051 to the current image. 01052 */ 01053 if ( add_frames && i>=2 ) { 01054 disposals[i-1]=DelDispose; 01055 disposals[i]=NoneDispose; 01056 bounds[i]=bounds[i-1]; 01057 i++; 01058 continue; 01059 } 01060 } 01061 else 01062 { 01063 /* 01064 Compare a none disposal against a previous disposal 01065 */ 01066 try_bounds=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception); 01067 try_cleared=IsBoundsCleared(prev_image,curr,&try_bounds,exception); 01068 #if DEBUG_OPT_FRAME 01069 (void) FormatLocaleFile(stderr, "test_prev: %.20gx%.20g%+.20g%+.20g%s\n", 01070 (double) try_bounds.width,(double) try_bounds.height, 01071 (double) try_bounds.x,(double) try_bounds.y, 01072 try_cleared?" (pixels were cleared)":""); 01073 #endif 01074 if ( (!try_cleared && cleared ) || 01075 try_bounds.width * try_bounds.height 01076 < bounds[i].width * bounds[i].height ) 01077 { 01078 cleared=try_cleared; 01079 bounds[i]=try_bounds; 01080 disposals[i-1]=PreviousDispose; 01081 #if DEBUG_OPT_FRAME 01082 (void) FormatLocaleFile(stderr, "previous: accepted\n"); 01083 } else { 01084 (void) FormatLocaleFile(stderr, "previous: rejected\n"); 01085 #endif 01086 } 01087 01088 /* 01089 If we are allowed lets try a complex frame duplication. 01090 It is useless if the previous image already clears pixels correctly. 01091 This method will always clear all the pixels that need to be cleared. 01092 */ 01093 dup_bounds.width=dup_bounds.height=0; /* no dup, no pixel added */ 01094 if ( add_frames ) 01095 { 01096 dup_image=CloneImage(curr->previous,curr->previous->page.width, 01097 curr->previous->page.height,MagickTrue,exception); 01098 if (dup_image == (Image *) NULL) 01099 { 01100 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 01101 disposals=(DisposeType *) RelinquishMagickMemory(disposals); 01102 prev_image=DestroyImage(prev_image); 01103 return((Image *) NULL); 01104 } 01105 dup_bounds=CompareImagesBounds(dup_image,curr,CompareClearLayer,exception); 01106 ClearBounds(dup_image,&dup_bounds,exception); 01107 try_bounds=CompareImagesBounds(dup_image,curr,CompareAnyLayer,exception); 01108 if ( cleared || 01109 dup_bounds.width*dup_bounds.height 01110 +try_bounds.width*try_bounds.height 01111 < bounds[i].width * bounds[i].height ) 01112 { 01113 cleared=MagickFalse; 01114 bounds[i]=try_bounds; 01115 disposals[i-1]=DupDispose; 01116 /* to be finalised later, if found to be optimial */ 01117 } 01118 else 01119 dup_bounds.width=dup_bounds.height=0; 01120 } 01121 /* 01122 Now compare against a simple background disposal 01123 */ 01124 bgnd_image=CloneImage(curr->previous,curr->previous->page.width, 01125 curr->previous->page.height,MagickTrue,exception); 01126 if (bgnd_image == (Image *) NULL) 01127 { 01128 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 01129 disposals=(DisposeType *) RelinquishMagickMemory(disposals); 01130 prev_image=DestroyImage(prev_image); 01131 if ( dup_image != (Image *) NULL) 01132 dup_image=DestroyImage(dup_image); 01133 return((Image *) NULL); 01134 } 01135 bgnd_bounds=bounds[i-1]; /* interum bounds of the previous image */ 01136 ClearBounds(bgnd_image,&bgnd_bounds,exception); 01137 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception); 01138 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception); 01139 #if DEBUG_OPT_FRAME 01140 (void) FormatLocaleFile(stderr, "background: %s\n", 01141 try_cleared?"(pixels cleared)":""); 01142 #endif 01143 if ( try_cleared ) 01144 { 01145 /* 01146 Straight background disposal failed to clear pixels needed! 01147 Lets try expanding the disposal area of the previous frame, to 01148 include the pixels that are cleared. This guaranteed 01149 to work, though may not be the most optimized solution. 01150 */ 01151 try_bounds=CompareImagesBounds(curr->previous,curr,CompareClearLayer,exception); 01152 #if DEBUG_OPT_FRAME 01153 (void) FormatLocaleFile(stderr, "expand_clear: %.20gx%.20g%+.20g%+.20g%s\n", 01154 (double) try_bounds.width,(double) try_bounds.height, 01155 (double) try_bounds.x,(double) try_bounds.y, 01156 try_bounds.x<0?" (no expand nessary)":""); 01157 #endif 01158 if ( bgnd_bounds.x < 0 ) 01159 bgnd_bounds = try_bounds; 01160 else 01161 { 01162 #if DEBUG_OPT_FRAME 01163 (void) FormatLocaleFile(stderr, "expand_bgnd: %.20gx%.20g%+.20g%+.20g\n", 01164 (double) bgnd_bounds.width,(double) bgnd_bounds.height, 01165 (double) bgnd_bounds.x,(double) bgnd_bounds.y ); 01166 #endif 01167 if ( try_bounds.x < bgnd_bounds.x ) 01168 { 01169 bgnd_bounds.width+= bgnd_bounds.x-try_bounds.x; 01170 if ( bgnd_bounds.width < try_bounds.width ) 01171 bgnd_bounds.width = try_bounds.width; 01172 bgnd_bounds.x = try_bounds.x; 01173 } 01174 else 01175 { 01176 try_bounds.width += try_bounds.x - bgnd_bounds.x; 01177 if ( bgnd_bounds.width < try_bounds.width ) 01178 bgnd_bounds.width = try_bounds.width; 01179 } 01180 if ( try_bounds.y < bgnd_bounds.y ) 01181 { 01182 bgnd_bounds.height += bgnd_bounds.y - try_bounds.y; 01183 if ( bgnd_bounds.height < try_bounds.height ) 01184 bgnd_bounds.height = try_bounds.height; 01185 bgnd_bounds.y = try_bounds.y; 01186 } 01187 else 01188 { 01189 try_bounds.height += try_bounds.y - bgnd_bounds.y; 01190 if ( bgnd_bounds.height < try_bounds.height ) 01191 bgnd_bounds.height = try_bounds.height; 01192 } 01193 #if DEBUG_OPT_FRAME 01194 (void) FormatLocaleFile(stderr, " to : %.20gx%.20g%+.20g%+.20g\n", 01195 (double) bgnd_bounds.width,(double) bgnd_bounds.height, 01196 (double) bgnd_bounds.x,(double) bgnd_bounds.y ); 01197 #endif 01198 } 01199 ClearBounds(bgnd_image,&bgnd_bounds,exception); 01200 #if DEBUG_OPT_FRAME 01201 /* Something strange is happening with a specific animation 01202 * CompareAnyLayers (normal method) and CompareClearLayers returns the whole 01203 * image, which is not posibly correct! As verified by previous tests. 01204 * Something changed beyond the bgnd_bounds clearing. But without being able 01205 * to see, or writet he image at this point it is hard to tell what is wrong! 01206 * Only CompareOverlay seemed to return something sensible. 01207 */ 01208 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareClearLayer,exception); 01209 (void) FormatLocaleFile(stderr, "expand_ctst: %.20gx%.20g%+.20g%+.20g\n", 01210 (double) try_bounds.width,(double) try_bounds.height, 01211 (double) try_bounds.x,(double) try_bounds.y ); 01212 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception); 01213 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception); 01214 (void) FormatLocaleFile(stderr, "expand_any : %.20gx%.20g%+.20g%+.20g%s\n", 01215 (double) try_bounds.width,(double) try_bounds.height, 01216 (double) try_bounds.x,(double) try_bounds.y, 01217 try_cleared?" (pixels cleared)":""); 01218 #endif 01219 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareOverlayLayer,exception); 01220 #if DEBUG_OPT_FRAME 01221 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception); 01222 (void) FormatLocaleFile(stderr, "expand_test: %.20gx%.20g%+.20g%+.20g%s\n", 01223 (double) try_bounds.width,(double) try_bounds.height, 01224 (double) try_bounds.x,(double) try_bounds.y, 01225 try_cleared?" (pixels cleared)":""); 01226 #endif 01227 } 01228 /* 01229 Test if this background dispose is smaller than any of the 01230 other methods we tryed before this (including duplicated frame) 01231 */ 01232 if ( cleared || 01233 bgnd_bounds.width*bgnd_bounds.height 01234 +try_bounds.width*try_bounds.height 01235 < bounds[i-1].width*bounds[i-1].height 01236 +dup_bounds.width*dup_bounds.height 01237 +bounds[i].width*bounds[i].height ) 01238 { 01239 cleared=MagickFalse; 01240 bounds[i-1]=bgnd_bounds; 01241 bounds[i]=try_bounds; 01242 if ( disposals[i-1] == DupDispose ) 01243 dup_image=DestroyImage(dup_image); 01244 disposals[i-1]=BackgroundDispose; 01245 #if DEBUG_OPT_FRAME 01246 (void) FormatLocaleFile(stderr, "expand_bgnd: accepted\n"); 01247 } else { 01248 (void) FormatLocaleFile(stderr, "expand_bgnd: reject\n"); 01249 #endif 01250 } 01251 } 01252 /* 01253 Finalise choice of dispose, set new prev_image, 01254 and junk any extra images as appropriate, 01255 */ 01256 if ( disposals[i-1] == DupDispose ) 01257 { 01258 if (bgnd_image != (Image *) NULL) 01259 bgnd_image=DestroyImage(bgnd_image); 01260 prev_image=DestroyImage(prev_image); 01261 prev_image=dup_image, dup_image=(Image *) NULL; 01262 bounds[i+1]=bounds[i]; 01263 bounds[i]=dup_bounds; 01264 disposals[i-1]=DupDispose; 01265 disposals[i]=BackgroundDispose; 01266 i++; 01267 } 01268 else 01269 { 01270 if ( dup_image != (Image *) NULL) 01271 dup_image=DestroyImage(dup_image); 01272 if ( disposals[i-1] != PreviousDispose ) 01273 prev_image=DestroyImage(prev_image); 01274 if ( disposals[i-1] == BackgroundDispose ) 01275 prev_image=bgnd_image, bgnd_image=(Image *)NULL; 01276 if (bgnd_image != (Image *) NULL) 01277 bgnd_image=DestroyImage(bgnd_image); 01278 if ( disposals[i-1] == NoneDispose ) 01279 { 01280 prev_image=CloneImage(curr->previous,curr->previous->page.width, 01281 curr->previous->page.height,MagickTrue,exception); 01282 if (prev_image == (Image *) NULL) 01283 { 01284 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 01285 disposals=(DisposeType *) RelinquishMagickMemory(disposals); 01286 return((Image *) NULL); 01287 } 01288 } 01289 01290 } 01291 assert(prev_image != (Image *) NULL); 01292 disposals[i]=disposals[i-1]; 01293 #if DEBUG_OPT_FRAME 01294 (void) FormatLocaleFile(stderr, "final %.20g : %s %.20gx%.20g%+.20g%+.20g\n", 01295 (double) i-1, 01296 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i-1]), 01297 (double) bounds[i-1].width, (double) bounds[i-1].height, 01298 (double) bounds[i-1].x, (double) bounds[i-1].y ); 01299 #endif 01300 #if DEBUG_OPT_FRAME 01301 (void) FormatLocaleFile(stderr, "interum %.20g : %s %.20gx%.20g%+.20g%+.20g\n", 01302 (double) i, 01303 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i]), 01304 (double) bounds[i].width, (double) bounds[i].height, 01305 (double) bounds[i].x, (double) bounds[i].y ); 01306 (void) FormatLocaleFile(stderr, "\n"); 01307 #endif 01308 i++; 01309 } 01310 prev_image=DestroyImage(prev_image); 01311 /* 01312 Optimize all images in sequence. 01313 */ 01314 sans_exception=AcquireExceptionInfo(); 01315 i=0; 01316 curr=GetFirstImageInList(image); 01317 optimized_image=NewImageList(); 01318 while ( curr != (const Image *) NULL ) 01319 { 01320 prev_image=CloneImage(curr,0,0,MagickTrue,exception); 01321 if (prev_image == (Image *) NULL) 01322 break; 01323 if ( disposals[i] == DelDispose ) { 01324 size_t time = 0; 01325 while ( disposals[i] == DelDispose ) { 01326 time += curr->delay*1000/curr->ticks_per_second; 01327 curr=GetNextImageInList(curr); 01328 i++; 01329 } 01330 time += curr->delay*1000/curr->ticks_per_second; 01331 prev_image->ticks_per_second = 100L; 01332 prev_image->delay = time*prev_image->ticks_per_second/1000; 01333 } 01334 bgnd_image=CropImage(prev_image,&bounds[i],sans_exception); 01335 prev_image=DestroyImage(prev_image); 01336 if (bgnd_image == (Image *) NULL) 01337 break; 01338 bgnd_image->dispose=disposals[i]; 01339 if ( disposals[i] == DupDispose ) { 01340 bgnd_image->delay=0; 01341 bgnd_image->dispose=NoneDispose; 01342 } 01343 else 01344 curr=GetNextImageInList(curr); 01345 AppendImageToList(&optimized_image,bgnd_image); 01346 i++; 01347 } 01348 sans_exception=DestroyExceptionInfo(sans_exception); 01349 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds); 01350 disposals=(DisposeType *) RelinquishMagickMemory(disposals); 01351 if (curr != (Image *) NULL) 01352 { 01353 optimized_image=DestroyImageList(optimized_image); 01354 return((Image *) NULL); 01355 } 01356 return(GetFirstImageInList(optimized_image)); 01357 } 01358 01359 /* 01360 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01361 % % 01362 % % 01363 % % 01364 % O p t i m i z e I m a g e L a y e r s % 01365 % % 01366 % % 01367 % % 01368 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01369 % 01370 % OptimizeImageLayers() compares each image the GIF disposed forms of the 01371 % previous image in the sequence. From this it attempts to select the 01372 % smallest cropped image to replace each frame, while preserving the results 01373 % of the GIF animation. 01374 % 01375 % The format of the OptimizeImageLayers method is: 01376 % 01377 % Image *OptimizeImageLayers(const Image *image, 01378 % ExceptionInfo *exception) 01379 % 01380 % A description of each parameter follows: 01381 % 01382 % o image: the image. 01383 % 01384 % o exception: return any errors or warnings in this structure. 01385 % 01386 */ 01387 MagickExport Image *OptimizeImageLayers(const Image *image, 01388 ExceptionInfo *exception) 01389 { 01390 return(OptimizeLayerFrames(image,OptimizeImageLayer,exception)); 01391 } 01392 01393 /* 01394 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01395 % % 01396 % % 01397 % % 01398 % O p t i m i z e P l u s I m a g e L a y e r s % 01399 % % 01400 % % 01401 % % 01402 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01403 % 01404 % OptimizeImagePlusLayers() is exactly as OptimizeImageLayers(), but may 01405 % also add or even remove extra frames in the animation, if it improves 01406 % the total number of pixels in the resulting GIF animation. 01407 % 01408 % The format of the OptimizePlusImageLayers method is: 01409 % 01410 % Image *OptimizePlusImageLayers(const Image *image, 01411 % ExceptionInfo *exception) 01412 % 01413 % A description of each parameter follows: 01414 % 01415 % o image: the image. 01416 % 01417 % o exception: return any errors or warnings in this structure. 01418 % 01419 */ 01420 MagickExport Image *OptimizePlusImageLayers(const Image *image, 01421 ExceptionInfo *exception) 01422 { 01423 return OptimizeLayerFrames(image, OptimizePlusLayer, exception); 01424 } 01425 01426 /* 01427 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01428 % % 01429 % % 01430 % % 01431 % 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 % 01432 % % 01433 % % 01434 % % 01435 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01436 % 01437 % OptimizeImageTransparency() takes a frame optimized GIF animation, and 01438 % compares the overlayed pixels against the disposal image resulting from all 01439 % the previous frames in the animation. Any pixel that does not change the 01440 % disposal image (and thus does not effect the outcome of an overlay) is made 01441 % transparent. 01442 % 01443 % WARNING: This modifies the current images directly, rather than generate 01444 % a new image sequence. 01445 % 01446 % The format of the OptimizeImageTransperency method is: 01447 % 01448 % void OptimizeImageTransperency(Image *image,ExceptionInfo *exception) 01449 % 01450 % A description of each parameter follows: 01451 % 01452 % o image: the image sequence 01453 % 01454 % o exception: return any errors or warnings in this structure. 01455 % 01456 */ 01457 MagickExport void OptimizeImageTransparency(const Image *image, 01458 ExceptionInfo *exception) 01459 { 01460 Image 01461 *dispose_image; 01462 01463 register Image 01464 *next; 01465 01466 /* 01467 Run the image through the animation sequence 01468 */ 01469 assert(image != (Image *) NULL); 01470 assert(image->signature == MagickSignature); 01471 if (image->debug != MagickFalse) 01472 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 01473 assert(exception != (ExceptionInfo *) NULL); 01474 assert(exception->signature == MagickSignature); 01475 next=GetFirstImageInList(image); 01476 dispose_image=CloneImage(next,next->page.width,next->page.height, 01477 MagickTrue,exception); 01478 if (dispose_image == (Image *) NULL) 01479 return; 01480 dispose_image->page=next->page; 01481 dispose_image->page.x=0; 01482 dispose_image->page.y=0; 01483 dispose_image->dispose=NoneDispose; 01484 dispose_image->background_color.alpha=(Quantum) TransparentAlpha; 01485 (void) SetImageBackgroundColor(dispose_image,exception); 01486 01487 while ( next != (Image *) NULL ) 01488 { 01489 Image 01490 *current_image; 01491 01492 /* 01493 Overlay this frame's image over the previous disposal image 01494 */ 01495 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception); 01496 if (current_image == (Image *) NULL) 01497 { 01498 dispose_image=DestroyImage(dispose_image); 01499 return; 01500 } 01501 (void) CompositeImage(current_image,next->matte != MagickFalse ? 01502 OverCompositeOp : CopyCompositeOp, next,next->page.x,next->page.y, 01503 exception); 01504 /* 01505 At this point the image would be displayed, for the delay period 01506 ** 01507 Work out the disposal of the previous image 01508 */ 01509 if (next->dispose == BackgroundDispose) 01510 { 01511 RectangleInfo 01512 bounds=next->page; 01513 01514 bounds.width=next->columns; 01515 bounds.height=next->rows; 01516 if (bounds.x < 0) 01517 { 01518 bounds.width+=bounds.x; 01519 bounds.x=0; 01520 } 01521 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns) 01522 bounds.width=current_image->columns-bounds.x; 01523 if (bounds.y < 0) 01524 { 01525 bounds.height+=bounds.y; 01526 bounds.y=0; 01527 } 01528 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows) 01529 bounds.height=current_image->rows-bounds.y; 01530 ClearBounds(current_image, &bounds,exception); 01531 } 01532 if (next->dispose != PreviousDispose) 01533 { 01534 dispose_image=DestroyImage(dispose_image); 01535 dispose_image=current_image; 01536 } 01537 else 01538 current_image=DestroyImage(current_image); 01539 01540 /* 01541 Optimize Transparency of the next frame (if present) 01542 */ 01543 next=GetNextImageInList(next); 01544 if ( next != (Image *) NULL ) { 01545 (void) CompositeImage(next, ChangeMaskCompositeOp, 01546 dispose_image, -(next->page.x), -(next->page.y), exception ); 01547 } 01548 } 01549 dispose_image=DestroyImage(dispose_image); 01550 return; 01551 } 01552 01553 /* 01554 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01555 % % 01556 % % 01557 % % 01558 % R e m o v e D u p l i c a t e L a y e r s % 01559 % % 01560 % % 01561 % % 01562 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01563 % 01564 % RemoveDuplicateLayers() removes any image that is exactly the same as the 01565 % next image in the given image list. Image size and virtual canvas offset 01566 % must also match, though not the virtual canvas size itself. 01567 % 01568 % No check is made with regards to image disposal setting, though it is the 01569 % dispose setting of later image that is kept. Also any time delays are also 01570 % added together. As such coalesced image animations should still produce the 01571 % same result, though with duplicte frames merged into a single frame. 01572 % 01573 % The format of the RemoveDuplicateLayers method is: 01574 % 01575 % void RemoveDuplicateLayers(Image **image, ExceptionInfo *exception) 01576 % 01577 % A description of each parameter follows: 01578 % 01579 % o images: the image list 01580 % 01581 % o exception: return any errors or warnings in this structure. 01582 % 01583 */ 01584 MagickExport void RemoveDuplicateLayers(Image **images, 01585 ExceptionInfo *exception) 01586 { 01587 register Image 01588 *curr, 01589 *next; 01590 01591 RectangleInfo 01592 bounds; 01593 01594 assert((*images) != (const Image *) NULL); 01595 assert((*images)->signature == MagickSignature); 01596 if ((*images)->debug != MagickFalse) 01597 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename); 01598 assert(exception != (ExceptionInfo *) NULL); 01599 assert(exception->signature == MagickSignature); 01600 01601 curr=GetFirstImageInList(*images); 01602 for (; (next=GetNextImageInList(curr)) != (Image *) NULL; curr=next) 01603 { 01604 if ( curr->columns != next->columns || curr->rows != next->rows 01605 || curr->page.x != next->page.x || curr->page.y != next->page.y ) 01606 continue; 01607 bounds=CompareImagesBounds(curr,next,CompareAnyLayer,exception); 01608 if ( bounds.x < 0 ) { 01609 /* 01610 the two images are the same, merge time delays and delete one. 01611 */ 01612 size_t time; 01613 time = curr->delay*1000/curr->ticks_per_second; 01614 time += next->delay*1000/next->ticks_per_second; 01615 next->ticks_per_second = 100L; 01616 next->delay = time*curr->ticks_per_second/1000; 01617 next->iterations = curr->iterations; 01618 *images = curr; 01619 (void) DeleteImageFromList(images); 01620 } 01621 } 01622 *images = GetFirstImageInList(*images); 01623 } 01624 01625 /* 01626 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01627 % % 01628 % % 01629 % % 01630 % R e m o v e Z e r o D e l a y L a y e r s % 01631 % % 01632 % % 01633 % % 01634 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01635 % 01636 % RemoveZeroDelayLayers() removes any image that as a zero delay time. Such 01637 % images generally represent intermediate or partial updates in GIF 01638 % animations used for file optimization. They are not ment to be displayed 01639 % to users of the animation. Viewable images in an animation should have a 01640 % time delay of 3 or more centi-seconds (hundredths of a second). 01641 % 01642 % However if all the frames have a zero time delay, then either the animation 01643 % is as yet incomplete, or it is not a GIF animation. This a non-sensible 01644 % situation, so no image will be removed and a 'Zero Time Animation' warning 01645 % (exception) given. 01646 % 01647 % No warning will be given if no image was removed because all images had an 01648 % appropriate non-zero time delay set. 01649 % 01650 % Due to the special requirements of GIF disposal handling, GIF animations 01651 % should be coalesced first, before calling this function, though that is not 01652 % a requirement. 01653 % 01654 % The format of the RemoveZeroDelayLayers method is: 01655 % 01656 % void RemoveZeroDelayLayers(Image **image, ExceptionInfo *exception) 01657 % 01658 % A description of each parameter follows: 01659 % 01660 % o images: the image list 01661 % 01662 % o exception: return any errors or warnings in this structure. 01663 % 01664 */ 01665 MagickExport void RemoveZeroDelayLayers(Image **images, 01666 ExceptionInfo *exception) 01667 { 01668 Image 01669 *i; 01670 01671 assert((*images) != (const Image *) NULL); 01672 assert((*images)->signature == MagickSignature); 01673 if ((*images)->debug != MagickFalse) 01674 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename); 01675 assert(exception != (ExceptionInfo *) NULL); 01676 assert(exception->signature == MagickSignature); 01677 01678 i=GetFirstImageInList(*images); 01679 for ( ; i != (Image *) NULL; i=GetNextImageInList(i)) 01680 if ( i->delay != 0L ) break; 01681 if ( i == (Image *) NULL ) { 01682 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning, 01683 "ZeroTimeAnimation","`%s'",GetFirstImageInList(*images)->filename); 01684 return; 01685 } 01686 i=GetFirstImageInList(*images); 01687 while ( i != (Image *) NULL ) 01688 { 01689 if ( i->delay == 0L ) { 01690 (void) DeleteImageFromList(&i); 01691 *images=i; 01692 } 01693 else 01694 i=GetNextImageInList(i); 01695 } 01696 *images=GetFirstImageInList(*images); 01697 } 01698 01699 /* 01700 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01701 % % 01702 % % 01703 % % 01704 % C o m p o s i t e L a y e r s % 01705 % % 01706 % % 01707 % % 01708 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01709 % 01710 % CompositeLayers() compose first image sequence (source) over the second 01711 % image sequence (destination), using the given compose method and offsets. 01712 % 01713 % The pointers to the image list does not have to be the start of that image 01714 % list, but may start somewhere in the middle. Each layer from the two image 01715 % lists are composted together until the end of one of the image lists is 01716 % reached. The offset of each composition is also adjusted to match the 01717 % virtual canvas offsets of each layer. As such the given offset is relative 01718 % to the virtual canvas, and not the actual image. 01719 % 01720 % No GIF disposal handling is performed, so GIF animations should be 01721 % coalesced before use. However this not a requirement, and individual 01722 % layer images may have any size or offset, for special compositions. 01723 % 01724 % Special case:- If one of the image sequences is just a single image that 01725 % image is repeatally composed with all the images in the other image list. 01726 % Either the source or destination lists may be the single image, for this 01727 % situation. 01728 % 01729 % The destination list will be expanded as needed to match number of source 01730 % image overlaid (from current position to end of list). 01731 % 01732 % The format of the CompositeLayers method is: 01733 % 01734 % void CompositeLayers(Image *destination, 01735 % const CompositeOperator compose, Image *source, 01736 % const ssize_t x_offset, const ssize_t y_offset, 01737 % ExceptionInfo *exception); 01738 % 01739 % A description of each parameter follows: 01740 % 01741 % o destination: the destination images and results 01742 % 01743 % o source: source image(s) for the layer composition 01744 % 01745 % o compose, x_offset, y_offset: arguments passed on to CompositeImages() 01746 % 01747 % o exception: return any errors or warnings in this structure. 01748 % 01749 */ 01750 01751 static inline void CompositeCanvas(Image *destination, 01752 const CompositeOperator compose,Image *source,ssize_t x_offset, 01753 ssize_t y_offset,ExceptionInfo *exception) 01754 { 01755 x_offset+=source->page.x-destination->page.x; 01756 y_offset+=source->page.y-destination->page.y; 01757 (void) CompositeImage(destination,compose,source,x_offset,y_offset, 01758 exception); 01759 } 01760 01761 MagickExport void CompositeLayers(Image *destination, 01762 const CompositeOperator compose, Image *source,const ssize_t x_offset, 01763 const ssize_t y_offset,ExceptionInfo *exception) 01764 { 01765 assert(destination != (Image *) NULL); 01766 assert(destination->signature == MagickSignature); 01767 assert(source != (Image *) NULL); 01768 assert(source->signature == MagickSignature); 01769 assert(exception != (ExceptionInfo *) NULL); 01770 assert(exception->signature == MagickSignature); 01771 if (source->debug != MagickFalse || destination->debug != MagickFalse) 01772 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s - %s", 01773 source->filename, destination->filename); 01774 01775 /* 01776 Overlay single source image over destation image/list 01777 */ 01778 if ( source->previous == (Image *) NULL && source->next == (Image *) NULL ) 01779 while ( destination != (Image *) NULL ) 01780 { 01781 CompositeCanvas(destination, compose, source, x_offset, y_offset, 01782 exception); 01783 destination=GetNextImageInList(destination); 01784 } 01785 01786 /* 01787 Overlay source image list over single destination 01788 Generating multiple clones of destination image to match source list. 01789 Original Destination image becomes first image of generated list. 01790 As such the image list pointer does not require any change in caller. 01791 Some animation attributes however also needs coping in this case. 01792 */ 01793 else if ( destination->previous == (Image *) NULL && 01794 destination->next == (Image *) NULL ) 01795 { 01796 Image *dest = CloneImage(destination,0,0,MagickTrue,exception); 01797 01798 CompositeCanvas(destination, compose, source, x_offset, y_offset, 01799 exception); 01800 /* copy source image attributes ? */ 01801 if ( source->next != (Image *) NULL ) 01802 { 01803 destination->delay = source->delay; 01804 destination->iterations = source->iterations; 01805 } 01806 source=GetNextImageInList(source); 01807 01808 while ( source != (Image *) NULL ) 01809 { 01810 AppendImageToList(&destination, 01811 CloneImage(dest,0,0,MagickTrue,exception)); 01812 destination=GetLastImageInList(destination); 01813 01814 CompositeCanvas(destination, compose, source, x_offset, y_offset, 01815 exception); 01816 destination->delay = source->delay; 01817 destination->iterations = source->iterations; 01818 source=GetNextImageInList(source); 01819 } 01820 dest=DestroyImage(dest); 01821 } 01822 01823 /* 01824 Overlay a source image list over a destination image list 01825 until either list runs out of images. (Does not repeat) 01826 */ 01827 else 01828 while ( source != (Image *) NULL && destination != (Image *) NULL ) 01829 { 01830 CompositeCanvas(destination, compose, source, x_offset, y_offset, 01831 exception); 01832 source=GetNextImageInList(source); 01833 destination=GetNextImageInList(destination); 01834 } 01835 } 01836 01837 /* 01838 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01839 % % 01840 % % 01841 % % 01842 % M e r g e I m a g e L a y e r s % 01843 % % 01844 % % 01845 % % 01846 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01847 % 01848 % MergeImageLayers() composes all the image layers from the current given 01849 % image onward to produce a single image of the merged layers. 01850 % 01851 % The inital canvas's size depends on the given ImageLayerMethod, and is 01852 % initialized using the first images background color. The images 01853 % are then compositied onto that image in sequence using the given 01854 % composition that has been assigned to each individual image. 01855 % 01856 % The format of the MergeImageLayers is: 01857 % 01858 % Image *MergeImageLayers(const Image *image, 01859 % const ImageLayerMethod method, ExceptionInfo *exception) 01860 % 01861 % A description of each parameter follows: 01862 % 01863 % o image: the image list to be composited together 01864 % 01865 % o method: the method of selecting the size of the initial canvas. 01866 % 01867 % MergeLayer: Merge all layers onto a canvas just large enough 01868 % to hold all the actual images. The virtual canvas of the 01869 % first image is preserved but otherwise ignored. 01870 % 01871 % FlattenLayer: Use the virtual canvas size of first image. 01872 % Images which fall outside this canvas is clipped. 01873 % This can be used to 'fill out' a given virtual canvas. 01874 % 01875 % MosaicLayer: Start with the virtual canvas of the first image, 01876 % enlarging left and right edges to contain all images. 01877 % Images with negative offsets will be clipped. 01878 % 01879 % TrimBoundsLayer: Determine the overall bounds of all the image 01880 % layers just as in "MergeLayer", then adjust the the canvas 01881 % and offsets to be relative to those bounds, without overlaying 01882 % the images. 01883 % 01884 % WARNING: a new image is not returned, the original image 01885 % sequence page data is modified instead. 01886 % 01887 % o exception: return any errors or warnings in this structure. 01888 % 01889 */ 01890 MagickExport Image *MergeImageLayers(Image *image,const ImageLayerMethod method, 01891 ExceptionInfo *exception) 01892 { 01893 #define MergeLayersTag "Merge/Layers" 01894 01895 Image 01896 *canvas; 01897 01898 MagickBooleanType 01899 proceed; 01900 01901 RectangleInfo 01902 page; 01903 01904 register const Image 01905 *next; 01906 01907 size_t 01908 number_images, 01909 height, 01910 width; 01911 01912 ssize_t 01913 scene; 01914 01915 assert(image != (Image *) NULL); 01916 assert(image->signature == MagickSignature); 01917 if (image->debug != MagickFalse) 01918 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 01919 assert(exception != (ExceptionInfo *) NULL); 01920 assert(exception->signature == MagickSignature); 01921 /* 01922 Determine canvas image size, and its virtual canvas size and offset 01923 */ 01924 page=image->page; 01925 width=image->columns; 01926 height=image->rows; 01927 switch (method) 01928 { 01929 case TrimBoundsLayer: 01930 case MergeLayer: 01931 default: 01932 { 01933 next=GetNextImageInList(image); 01934 for ( ; next != (Image *) NULL; next=GetNextImageInList(next)) 01935 { 01936 if (page.x > next->page.x) 01937 { 01938 width+=page.x-next->page.x; 01939 page.x=next->page.x; 01940 } 01941 if (page.y > next->page.y) 01942 { 01943 height+=page.y-next->page.y; 01944 page.y=next->page.y; 01945 } 01946 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns-page.x)) 01947 width=(size_t) next->page.x+(ssize_t)next->columns-page.x; 01948 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows-page.y)) 01949 height=(size_t) next->page.y+(ssize_t) next->rows-page.y; 01950 } 01951 break; 01952 } 01953 case FlattenLayer: 01954 { 01955 if (page.width > 0) 01956 width=page.width; 01957 if (page.height > 0) 01958 height=page.height; 01959 page.x=0; 01960 page.y=0; 01961 break; 01962 } 01963 case MosaicLayer: 01964 { 01965 if (page.width > 0) 01966 width=page.width; 01967 if (page.height > 0) 01968 height=page.height; 01969 for (next=image; next != (Image *) NULL; next=GetNextImageInList(next)) 01970 { 01971 if (method == MosaicLayer) 01972 { 01973 page.x=next->page.x; 01974 page.y=next->page.y; 01975 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns)) 01976 width=(size_t) next->page.x+next->columns; 01977 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows)) 01978 height=(size_t) next->page.y+next->rows; 01979 } 01980 } 01981 page.width=width; 01982 page.height=height; 01983 page.x=0; 01984 page.y=0; 01985 } 01986 break; 01987 } 01988 /* 01989 Set virtual canvas size if not defined. 01990 */ 01991 if (page.width == 0) 01992 page.width=page.x < 0 ? width : width+page.x; 01993 if (page.height == 0) 01994 page.height=page.y < 0 ? height : height+page.y; 01995 /* 01996 Handle "TrimBoundsLayer" method separately to normal 'layer merge'. 01997 */ 01998 if (method == TrimBoundsLayer) 01999 { 02000 number_images=GetImageListLength(image); 02001 for (scene=0; scene < (ssize_t) number_images; scene++) 02002 { 02003 image->page.x-=page.x; 02004 image->page.y-=page.y; 02005 image->page.width=width; 02006 image->page.height=height; 02007 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene, 02008 number_images); 02009 if (proceed == MagickFalse) 02010 break; 02011 image=GetNextImageInList(image); 02012 if (image == (Image *) NULL) 02013 break; 02014 } 02015 return((Image *) NULL); 02016 } 02017 /* 02018 Create canvas size of width and height, and background color. 02019 */ 02020 canvas=CloneImage(image,width,height,MagickTrue,exception); 02021 if (canvas == (Image *) NULL) 02022 return((Image *) NULL); 02023 (void) SetImageBackgroundColor(canvas,exception); 02024 canvas->page=page; 02025 canvas->dispose=UndefinedDispose; 02026 /* 02027 Compose images onto canvas, with progress monitor 02028 */ 02029 number_images=GetImageListLength(image); 02030 for (scene=0; scene < (ssize_t) number_images; scene++) 02031 { 02032 (void) CompositeImage(canvas,image->compose,image,image->page.x- 02033 canvas->page.x,image->page.y-canvas->page.y,exception); 02034 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene, 02035 number_images); 02036 if (proceed == MagickFalse) 02037 break; 02038 image=GetNextImageInList(image); 02039 if (image == (Image *) NULL) 02040 break; 02041 } 02042 return(canvas); 02043 } 02044