2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5 % L AAA Y Y EEEEE RRRR %
9 % LLLLL A A Y EEEEE R R %
11 % MagickCore Image Layering Methods %
19 % Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
20 % dedicated to making software imaging solutions freely available. %
22 % You may not use this file except in compliance with the License. You may %
23 % obtain a copy of the License at %
25 % http://www.imagemagick.org/script/license.php %
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. %
33 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
40 #include "MagickCore/studio.h"
41 #include "MagickCore/artifact.h"
42 #include "MagickCore/cache.h"
43 #include "MagickCore/color.h"
44 #include "MagickCore/color-private.h"
45 #include "MagickCore/composite.h"
46 #include "MagickCore/effect.h"
47 #include "MagickCore/exception.h"
48 #include "MagickCore/exception-private.h"
49 #include "MagickCore/geometry.h"
50 #include "MagickCore/image.h"
51 #include "MagickCore/layer.h"
52 #include "MagickCore/list.h"
53 #include "MagickCore/memory_.h"
54 #include "MagickCore/monitor.h"
55 #include "MagickCore/monitor-private.h"
56 #include "MagickCore/option.h"
57 #include "MagickCore/pixel-accessor.h"
58 #include "MagickCore/property.h"
59 #include "MagickCore/profile.h"
60 #include "MagickCore/resource_.h"
61 #include "MagickCore/resize.h"
62 #include "MagickCore/statistic.h"
63 #include "MagickCore/string_.h"
64 #include "MagickCore/transform.h"
67 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
71 + C l e a r B o u n d s %
75 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
77 % ClearBounds() Clear the area specified by the bounds in an image to
78 % transparency. This typically used to handle Background Disposal
79 % for the previous frame in an animation sequence.
81 % WARNING: no bounds checks are performed, except for the null or
82 % missed image, for images that don't change. in all other cases
83 % bound must fall within the image.
87 % void ClearBounds(Image *image,RectangleInfo *bounds
88 % ExceptionInfo *exception)
90 % A description of each parameter follows:
92 % o image: the image to had the area cleared in
94 % o bounds: the area to be clear within the imag image
96 % o exception: return any errors or warnings in this structure.
99 static void ClearBounds(Image *image,RectangleInfo *bounds,
100 ExceptionInfo *exception)
107 if (image->matte == MagickFalse)
108 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
109 for (y=0; y < (ssize_t) bounds->height; y++)
117 q=GetAuthenticPixels(image,bounds->x,bounds->y+y,bounds->width,1,exception);
118 if (q == (Quantum *) NULL)
120 for (x=0; x < (ssize_t) bounds->width; x++)
122 SetPixelAlpha(image,TransparentAlpha,q);
123 q+=GetPixelChannels(image);
125 if (SyncAuthenticPixels(image,exception) == MagickFalse)
131 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
135 + I s B o u n d s C l e a r e d %
139 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
141 % IsBoundsCleared() tests whether any pixel in the bounds given, gets cleared
142 % when going from the first image to the second image. This typically used
143 % to check if a proposed disposal method will work successfully to generate
144 % the second frame image from the first disposed form of the previous frame.
148 % MagickBooleanType IsBoundsCleared(const Image *image1,
149 % const Image *image2,RectangleInfo bounds,ExceptionInfo *exception)
151 % A description of each parameter follows:
153 % o image1, image 2: the images to check for cleared pixels
155 % o bounds: the area to be clear within the imag image
157 % o exception: return any errors or warnings in this structure.
159 % WARNING: no bounds checks are performed, except for the null or
160 % missed image, for images that don't change. in all other cases
161 % bound must fall within the image.
164 static MagickBooleanType IsBoundsCleared(const Image *image1,
165 const Image *image2,RectangleInfo *bounds,ExceptionInfo *exception)
170 register const Quantum
179 for (y=0; y < (ssize_t) bounds->height; y++)
181 p=GetVirtualPixels(image1,bounds->x,bounds->y+y,bounds->width,1,
183 q=GetVirtualPixels(image2,bounds->x,bounds->y+y,bounds->width,1,
185 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
187 for (x=0; x < (ssize_t) bounds->width; x++)
189 if ((GetPixelAlpha(image1,p) <= (Quantum) (QuantumRange/2)) &&
190 (GetPixelAlpha(image1,q) > (Quantum) (QuantumRange/2)))
192 p+=GetPixelChannels(image1);
195 if (x < (ssize_t) bounds->width)
198 return(y < (ssize_t) bounds->height ? MagickTrue : MagickFalse);
202 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
206 % C o a l e s c e I m a g e s %
210 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
212 % CoalesceImages() composites a set of images while respecting any page
213 % offsets and disposal methods. GIF, MIFF, and MNG animation sequences
214 % typically start with an image background and each subsequent image
215 % varies in size and offset. A new image sequence is returned with all
216 % images the same size as the first images virtual canvas and composited
217 % with the next image in the sequence.
219 % The format of the CoalesceImages method is:
221 % Image *CoalesceImages(Image *image,ExceptionInfo *exception)
223 % A description of each parameter follows:
225 % o image: the image sequence.
227 % o exception: return any errors or warnings in this structure.
230 MagickExport Image *CoalesceImages(const Image *image,ExceptionInfo *exception)
244 Coalesce the image sequence.
246 assert(image != (Image *) NULL);
247 assert(image->signature == MagickSignature);
248 if (image->debug != MagickFalse)
249 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
250 assert(exception != (ExceptionInfo *) NULL);
251 assert(exception->signature == MagickSignature);
253 /* initialise first image */
254 next=GetFirstImageInList(image);
256 if (bounds.width == 0)
258 bounds.width=next->columns;
260 bounds.width+=bounds.x;
262 if (bounds.height == 0)
264 bounds.height=next->rows;
266 bounds.height+=bounds.y;
270 coalesce_image=CloneImage(next,bounds.width,bounds.height,MagickTrue,
272 if (coalesce_image == (Image *) NULL)
273 return((Image *) NULL);
274 coalesce_image->page=bounds;
275 coalesce_image->dispose=NoneDispose;
276 coalesce_image->background_color.alpha=(Quantum) TransparentAlpha;
277 (void) SetImageBackgroundColor(coalesce_image,exception);
279 Coalesce rest of the images.
281 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
282 (void) CompositeImage(coalesce_image,CopyCompositeOp,next,next->page.x,
283 next->page.y,exception);
284 next=GetNextImageInList(next);
285 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
288 Determine the bounds that was overlaid in the previous image.
290 previous=GetPreviousImageInList(next);
291 bounds=previous->page;
292 bounds.width=previous->columns;
293 bounds.height=previous->rows;
296 bounds.width+=bounds.x;
299 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) coalesce_image->columns)
300 bounds.width=coalesce_image->columns-bounds.x;
303 bounds.height+=bounds.y;
306 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) coalesce_image->rows)
307 bounds.height=coalesce_image->rows-bounds.y;
309 Replace the dispose image with the new coalesced image.
311 if (GetPreviousImageInList(next)->dispose != PreviousDispose)
313 dispose_image=DestroyImage(dispose_image);
314 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
315 if (dispose_image == (Image *) NULL)
317 coalesce_image=DestroyImageList(coalesce_image);
318 return((Image *) NULL);
322 Clear the overlaid area of the coalesced bounds for background disposal
324 if (next->previous->dispose == BackgroundDispose)
325 ClearBounds(dispose_image,&bounds,exception);
327 Next image is the dispose image, overlaid with next frame in sequence.
329 coalesce_image->next=CloneImage(dispose_image,0,0,MagickTrue,exception);
330 coalesce_image->next->previous=coalesce_image;
331 previous=coalesce_image;
332 coalesce_image=GetNextImageInList(coalesce_image);
333 coalesce_image->matte=MagickTrue;
334 (void) CompositeImage(coalesce_image,next->matte != MagickFalse ?
335 OverCompositeOp : CopyCompositeOp,next,next->page.x,next->page.y,
337 (void) CloneImageProfiles(coalesce_image,next);
338 (void) CloneImageProperties(coalesce_image,next);
339 (void) CloneImageArtifacts(coalesce_image,next);
340 coalesce_image->page=previous->page;
342 If a pixel goes opaque to transparent, use background dispose.
344 if (IsBoundsCleared(previous,coalesce_image,&bounds,exception))
345 coalesce_image->dispose=BackgroundDispose;
347 coalesce_image->dispose=NoneDispose;
348 previous->dispose=coalesce_image->dispose;
350 dispose_image=DestroyImage(dispose_image);
351 return(GetFirstImageInList(coalesce_image));
355 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
359 % D i s p o s e I m a g e s %
363 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
365 % DisposeImages() returns the coalesced frames of a GIF animation as it would
366 % appear after the GIF dispose method of that frame has been applied. That
367 % is it returned the appearance of each frame before the next is overlaid.
369 % The format of the DisposeImages method is:
371 % Image *DisposeImages(Image *image,ExceptionInfo *exception)
373 % A description of each parameter follows:
375 % o image: the image sequence.
377 % o exception: return any errors or warnings in this structure.
380 MagickExport Image *DisposeImages(const Image *image,ExceptionInfo *exception)
393 Run the image through the animation sequence
395 assert(image != (Image *) NULL);
396 assert(image->signature == MagickSignature);
397 if (image->debug != MagickFalse)
398 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
399 assert(exception != (ExceptionInfo *) NULL);
400 assert(exception->signature == MagickSignature);
401 curr=GetFirstImageInList(image);
402 dispose_image=CloneImage(curr,curr->page.width,curr->page.height,MagickTrue,
404 if (dispose_image == (Image *) NULL)
405 return((Image *) NULL);
406 dispose_image->page=curr->page;
407 dispose_image->page.x=0;
408 dispose_image->page.y=0;
409 dispose_image->dispose=NoneDispose;
410 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
411 (void) SetImageBackgroundColor(dispose_image,exception);
412 dispose_images=NewImageList();
413 for ( ; curr != (Image *) NULL; curr=GetNextImageInList(curr))
419 Overlay this frame's image over the previous disposal image.
421 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
422 if (current_image == (Image *) NULL)
424 dispose_images=DestroyImageList(dispose_images);
425 dispose_image=DestroyImage(dispose_image);
426 return((Image *) NULL);
428 (void) CompositeImage(current_image,curr->matte != MagickFalse ?
429 OverCompositeOp : CopyCompositeOp,curr,curr->page.x,curr->page.y,
432 Handle Background dispose: image is displayed for the delay period.
434 if (curr->dispose == BackgroundDispose)
437 bounds.width=curr->columns;
438 bounds.height=curr->rows;
441 bounds.width+=bounds.x;
444 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
445 bounds.width=current_image->columns-bounds.x;
448 bounds.height+=bounds.y;
451 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
452 bounds.height=current_image->rows-bounds.y;
453 ClearBounds(current_image,&bounds,exception);
456 Select the appropriate previous/disposed image.
458 if (curr->dispose == PreviousDispose)
459 current_image=DestroyImage(current_image);
462 dispose_image=DestroyImage(dispose_image);
463 dispose_image=current_image;
464 current_image=(Image *)NULL;
467 Save the dispose image just calculated for return.
473 dispose=CloneImage(dispose_image,0,0,MagickTrue,exception);
474 if (dispose == (Image *) NULL)
476 dispose_images=DestroyImageList(dispose_images);
477 dispose_image=DestroyImage(dispose_image);
478 return((Image *) NULL);
480 (void) CloneImageProfiles(dispose,curr);
481 (void) CloneImageProperties(dispose,curr);
482 (void) CloneImageArtifacts(dispose,curr);
485 dispose->dispose=curr->dispose;
486 AppendImageToList(&dispose_images,dispose);
489 dispose_image=DestroyImage(dispose_image);
490 return(GetFirstImageInList(dispose_images));
494 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
498 + C o m p a r e P i x e l s %
502 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
504 % ComparePixels() Compare the two pixels and return true if the pixels
505 % differ according to the given LayerType comparision method.
507 % This currently only used internally by CompareImagesBounds(). It is
508 % doubtful that this sub-routine will be useful outside this module.
510 % The format of the ComparePixels method is:
512 % MagickBooleanType *ComparePixels(const ImageLayerMethod method,
513 % const PixelInfo *p,const PixelInfo *q)
515 % A description of each parameter follows:
517 % o method: What differences to look for. Must be one of
518 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
520 % o p, q: the pixels to test for appropriate differences.
524 static MagickBooleanType ComparePixels(const ImageLayerMethod method,
525 const PixelInfo *p,const PixelInfo *q)
532 Any change in pixel values
534 if (method == CompareAnyLayer)
535 return((MagickBooleanType)(IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
537 o1 = (p->matte != MagickFalse) ? p->alpha : OpaqueAlpha;
538 o2 = (q->matte != MagickFalse) ? q->alpha : OpaqueAlpha;
541 Pixel goes from opaque to transprency
543 if (method == CompareClearLayer)
544 return((MagickBooleanType) ( (o1 <= ((MagickRealType) QuantumRange/2.0)) &&
545 (o2 > ((MagickRealType) QuantumRange/2.0)) ) );
548 overlay would change first pixel by second
550 if (method == CompareOverlayLayer)
552 if (o2 > ((MagickRealType) QuantumRange/2.0))
554 return((MagickBooleanType) (IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
561 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
565 + C o m p a r e I m a g e B o u n d s %
569 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
571 % CompareImagesBounds() Given two images return the smallest rectangular area
572 % by which the two images differ, accourding to the given 'Compare...'
575 % This currently only used internally in this module, but may eventually
576 % be used by other modules.
578 % The format of the CompareImagesBounds method is:
580 % RectangleInfo *CompareImagesBounds(const ImageLayerMethod method,
581 % const Image *image1, const Image *image2, ExceptionInfo *exception)
583 % A description of each parameter follows:
585 % o method: What differences to look for. Must be one of CompareAnyLayer,
586 % CompareClearLayer, CompareOverlayLayer.
588 % o image1, image2: the two images to compare.
590 % o exception: return any errors or warnings in this structure.
594 static RectangleInfo CompareImagesBounds(const Image *image1,const Image *image2,
595 const ImageLayerMethod method,ExceptionInfo *exception)
604 register const Quantum
615 Set bounding box of the differences between images.
617 GetPixelInfo(image1,&pixel1);
618 GetPixelInfo(image2,&pixel2);
619 for (x=0; x < (ssize_t) image1->columns; x++)
621 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
622 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
623 if ((p == (const Quantum *) NULL) ||
624 (q == (Quantum *) NULL))
626 for (y=0; y < (ssize_t) image1->rows; y++)
628 GetPixelInfoPixel(image1,p,&pixel1);
629 GetPixelInfoPixel(image2,q,&pixel2);
630 if (ComparePixels(method,&pixel1,&pixel2))
632 p+=GetPixelChannels(image1);
635 if (y < (ssize_t) image1->rows)
638 if (x >= (ssize_t) image1->columns)
641 Images are identical, return a null image.
650 for (x=(ssize_t) image1->columns-1; x >= 0; x--)
652 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
653 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
654 if ((p == (const Quantum *) NULL) ||
655 (q == (Quantum *) NULL))
657 for (y=0; y < (ssize_t) image1->rows; y++)
659 GetPixelInfoPixel(image1,p,&pixel1);
660 GetPixelInfoPixel(image2,q,&pixel2);
661 if (ComparePixels(method,&pixel1,&pixel2))
663 p+=GetPixelChannels(image1);
666 if (y < (ssize_t) image1->rows)
669 bounds.width=(size_t) (x-bounds.x+1);
670 for (y=0; y < (ssize_t) image1->rows; y++)
672 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
673 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
674 if ((p == (const Quantum *) NULL) ||
675 (q == (Quantum *) NULL))
677 for (x=0; x < (ssize_t) image1->columns; x++)
679 GetPixelInfoPixel(image1,p,&pixel1);
680 GetPixelInfoPixel(image2,q,&pixel2);
681 if (ComparePixels(method,&pixel1,&pixel2))
683 p+=GetPixelChannels(image1);
686 if (x < (ssize_t) image1->columns)
690 for (y=(ssize_t) image1->rows-1; y >= 0; y--)
692 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
693 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
694 if ((p == (const Quantum *) NULL) ||
695 (q == (Quantum *) NULL))
697 for (x=0; x < (ssize_t) image1->columns; x++)
699 GetPixelInfoPixel(image1,p,&pixel1);
700 GetPixelInfoPixel(image2,q,&pixel2);
701 if (ComparePixels(method,&pixel1,&pixel2))
703 p+=GetPixelChannels(image1);
706 if (x < (ssize_t) image1->columns)
709 bounds.height=(size_t) (y-bounds.y+1);
714 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
718 % C o m p a r e I m a g e L a y e r s %
722 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
724 % CompareImagesLayers() compares each image with the next in a sequence and
725 % returns the minimum bounding region of all the pixel differences (of the
726 % ImageLayerMethod specified) it discovers.
728 % Images do NOT have to be the same size, though it is best that all the
729 % images are 'coalesced' (images are all the same size, on a flattened
730 % canvas, so as to represent exactly how an specific frame should look).
732 % No GIF dispose methods are applied, so GIF animations must be coalesced
733 % before applying this image operator to find differences to them.
735 % The format of the CompareImagesLayers method is:
737 % Image *CompareImagesLayers(const Image *images,
738 % const ImageLayerMethod method,ExceptionInfo *exception)
740 % A description of each parameter follows:
742 % o image: the image.
744 % o method: the layers type to compare images with. Must be one of...
745 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
747 % o exception: return any errors or warnings in this structure.
751 MagickExport Image *CompareImagesLayers(const Image *image,
752 const ImageLayerMethod method, ExceptionInfo *exception)
768 assert(image != (const Image *) NULL);
769 assert(image->signature == MagickSignature);
770 if (image->debug != MagickFalse)
771 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
772 assert(exception != (ExceptionInfo *) NULL);
773 assert(exception->signature == MagickSignature);
774 assert((method == CompareAnyLayer) ||
775 (method == CompareClearLayer) ||
776 (method == CompareOverlayLayer));
778 Allocate bounds memory.
780 next=GetFirstImageInList(image);
781 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
782 GetImageListLength(next),sizeof(*bounds));
783 if (bounds == (RectangleInfo *) NULL)
784 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
786 Set up first comparision images.
788 image_a=CloneImage(next,next->page.width,next->page.height,
789 MagickTrue,exception);
790 if (image_a == (Image *) NULL)
792 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
793 return((Image *) NULL);
795 image_a->background_color.alpha=(Quantum) TransparentAlpha;
796 (void) SetImageBackgroundColor(image_a,exception);
797 image_a->page=next->page;
800 (void) CompositeImage(image_a,CopyCompositeOp,next,next->page.x,next->page.y,
803 Compute the bounding box of changes for the later images
806 next=GetNextImageInList(next);
807 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
809 image_b=CloneImage(image_a,0,0,MagickTrue,exception);
810 if (image_b == (Image *) NULL)
812 image_a=DestroyImage(image_a);
813 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
814 return((Image *) NULL);
816 (void) CompositeImage(image_a,CopyCompositeOp,next,next->page.x,
817 next->page.y,exception);
818 bounds[i]=CompareImagesBounds(image_b,image_a,method,exception);
820 image_b=DestroyImage(image_b);
823 image_a=DestroyImage(image_a);
825 Clone first image in sequence.
827 next=GetFirstImageInList(image);
828 layers=CloneImage(next,0,0,MagickTrue,exception);
829 if (layers == (Image *) NULL)
831 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
832 return((Image *) NULL);
835 Deconstruct the image sequence.
838 next=GetNextImageInList(next);
839 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
841 image_a=CloneImage(next,0,0,MagickTrue,exception);
842 if (image_a == (Image *) NULL)
844 image_b=CropImage(image_a,&bounds[i],exception);
845 image_a=DestroyImage(image_a);
846 if (image_b == (Image *) NULL)
848 AppendImageToList(&layers,image_b);
851 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
852 if (next != (Image *) NULL)
854 layers=DestroyImageList(layers);
855 return((Image *) NULL);
857 return(GetFirstImageInList(layers));
861 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
865 + O p t i m i z e L a y e r F r a m e s %
869 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
871 % OptimizeLayerFrames() takes a coalesced GIF animation, and compares each
872 % frame against the three different 'disposal' forms of the previous frame.
873 % From this it then attempts to select the smallest cropped image and
874 % disposal method needed to reproduce the resulting image.
876 % Note that this not easy, and may require the expansion of the bounds
877 % of previous frame, simply clear pixels for the next animation frame to
878 % transparency according to the selected dispose method.
880 % The format of the OptimizeLayerFrames method is:
882 % Image *OptimizeLayerFrames(const Image *image,
883 % const ImageLayerMethod method, ExceptionInfo *exception)
885 % A description of each parameter follows:
887 % o image: the image.
889 % o method: the layers technique to optimize with. Must be one of...
890 % OptimizeImageLayer, or OptimizePlusLayer. The Plus form allows
891 % the addition of extra 'zero delay' frames to clear pixels from
892 % the previous frame, and the removal of frames that done change,
893 % merging the delay times together.
895 % o exception: return any errors or warnings in this structure.
899 Define a 'fake' dispose method where the frame is duplicated, (for
900 OptimizePlusLayer) with a extra zero time delay frame which does a
901 BackgroundDisposal to clear the pixels that need to be cleared.
903 #define DupDispose ((DisposeType)9)
905 Another 'fake' dispose method used to removed frames that don't change.
907 #define DelDispose ((DisposeType)8)
909 #define DEBUG_OPT_FRAME 0
911 static Image *OptimizeLayerFrames(const Image *image,
912 const ImageLayerMethod method, ExceptionInfo *exception)
943 assert(image != (const Image *) NULL);
944 assert(image->signature == MagickSignature);
945 if (image->debug != MagickFalse)
946 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
947 assert(exception != (ExceptionInfo *) NULL);
948 assert(exception->signature == MagickSignature);
949 assert(method == OptimizeLayer ||
950 method == OptimizeImageLayer ||
951 method == OptimizePlusLayer);
954 Are we allowed to add/remove frames from animation
956 add_frames=method == OptimizePlusLayer ? MagickTrue : MagickFalse;
958 Ensure all the images are the same size
960 curr=GetFirstImageInList(image);
961 for (; curr != (Image *) NULL; curr=GetNextImageInList(curr))
963 if ((curr->columns != image->columns) || (curr->rows != image->rows))
964 ThrowImageException(OptionError,"ImagesAreNotTheSameSize");
966 FUTURE: also check that image is also fully coalesced (full page)
967 Though as long as they are the same size it should not matter.
971 Allocate memory (times 2 if we allow the use of frame duplications)
973 curr=GetFirstImageInList(image);
974 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
975 GetImageListLength(curr),(add_frames != MagickFalse ? 2UL : 1UL)*
977 if (bounds == (RectangleInfo *) NULL)
978 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
979 disposals=(DisposeType *) AcquireQuantumMemory((size_t)
980 GetImageListLength(image),(add_frames != MagickFalse ? 2UL : 1UL)*
982 if (disposals == (DisposeType *) NULL)
984 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
985 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
988 Initialise Previous Image as fully transparent
990 prev_image=CloneImage(curr,curr->page.width,curr->page.height,
991 MagickTrue,exception);
992 if (prev_image == (Image *) NULL)
994 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
995 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
996 return((Image *) NULL);
998 prev_image->page=curr->page; /* ERROR: <-- should not be need, but is! */
999 prev_image->page.x=0;
1000 prev_image->page.y=0;
1001 prev_image->dispose=NoneDispose;
1003 prev_image->background_color.alpha=(Quantum) TransparentAlpha;
1004 (void) SetImageBackgroundColor(prev_image,exception);
1006 Figure out the area of overlay of the first frame
1007 No pixel could be cleared as all pixels are already cleared.
1011 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
1013 disposals[0]=NoneDispose;
1014 bounds[0]=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
1016 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g\n\n",
1017 (double) bounds[i].width,(double) bounds[i].height,
1018 (double) bounds[i].x,(double) bounds[i].y );
1021 Compute the bounding box of changes for each pair of images.
1024 bgnd_image=(Image *)NULL;
1025 dup_image=(Image *)NULL;
1027 dup_bounds.height=0;
1030 curr=GetNextImageInList(curr);
1031 for ( ; curr != (const Image *) NULL; curr=GetNextImageInList(curr))
1034 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
1037 Assume none disposal is the best
1039 bounds[i]=CompareImagesBounds(curr->previous,curr,CompareAnyLayer,exception);
1040 cleared=IsBoundsCleared(curr->previous,curr,&bounds[i],exception);
1041 disposals[i-1]=NoneDispose;
1043 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g%s%s\n",
1044 (double) bounds[i].width,(double) bounds[i].height,
1045 (double) bounds[i].x,(double) bounds[i].y,
1046 bounds[i].x < 0?" (unchanged)":"",
1047 cleared?" (pixels cleared)":"");
1049 if ( bounds[i].x < 0 ) {
1051 Image frame is exactly the same as the previous frame!
1052 If not adding frames leave it to be cropped down to a null image.
1053 Otherwise mark previous image for deleted, transfering its crop bounds
1054 to the current image.
1056 if ( add_frames && i>=2 ) {
1057 disposals[i-1]=DelDispose;
1058 disposals[i]=NoneDispose;
1059 bounds[i]=bounds[i-1];
1067 Compare a none disposal against a previous disposal
1069 try_bounds=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
1070 try_cleared=IsBoundsCleared(prev_image,curr,&try_bounds,exception);
1072 (void) FormatLocaleFile(stderr, "test_prev: %.20gx%.20g%+.20g%+.20g%s\n",
1073 (double) try_bounds.width,(double) try_bounds.height,
1074 (double) try_bounds.x,(double) try_bounds.y,
1075 try_cleared?" (pixels were cleared)":"");
1077 if ( (!try_cleared && cleared ) ||
1078 try_bounds.width * try_bounds.height
1079 < bounds[i].width * bounds[i].height )
1081 cleared=try_cleared;
1082 bounds[i]=try_bounds;
1083 disposals[i-1]=PreviousDispose;
1085 (void) FormatLocaleFile(stderr, "previous: accepted\n");
1087 (void) FormatLocaleFile(stderr, "previous: rejected\n");
1092 If we are allowed lets try a complex frame duplication.
1093 It is useless if the previous image already clears pixels correctly.
1094 This method will always clear all the pixels that need to be cleared.
1096 dup_bounds.width=dup_bounds.height=0; /* no dup, no pixel added */
1099 dup_image=CloneImage(curr->previous,curr->previous->page.width,
1100 curr->previous->page.height,MagickTrue,exception);
1101 if (dup_image == (Image *) NULL)
1103 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1104 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1105 prev_image=DestroyImage(prev_image);
1106 return((Image *) NULL);
1108 dup_bounds=CompareImagesBounds(dup_image,curr,CompareClearLayer,exception);
1109 ClearBounds(dup_image,&dup_bounds,exception);
1110 try_bounds=CompareImagesBounds(dup_image,curr,CompareAnyLayer,exception);
1112 dup_bounds.width*dup_bounds.height
1113 +try_bounds.width*try_bounds.height
1114 < bounds[i].width * bounds[i].height )
1116 cleared=MagickFalse;
1117 bounds[i]=try_bounds;
1118 disposals[i-1]=DupDispose;
1119 /* to be finalised later, if found to be optimial */
1122 dup_bounds.width=dup_bounds.height=0;
1125 Now compare against a simple background disposal
1127 bgnd_image=CloneImage(curr->previous,curr->previous->page.width,
1128 curr->previous->page.height,MagickTrue,exception);
1129 if (bgnd_image == (Image *) NULL)
1131 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1132 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1133 prev_image=DestroyImage(prev_image);
1134 if ( dup_image != (Image *) NULL)
1135 dup_image=DestroyImage(dup_image);
1136 return((Image *) NULL);
1138 bgnd_bounds=bounds[i-1]; /* interum bounds of the previous image */
1139 ClearBounds(bgnd_image,&bgnd_bounds,exception);
1140 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
1141 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1143 (void) FormatLocaleFile(stderr, "background: %s\n",
1144 try_cleared?"(pixels cleared)":"");
1149 Straight background disposal failed to clear pixels needed!
1150 Lets try expanding the disposal area of the previous frame, to
1151 include the pixels that are cleared. This guaranteed
1152 to work, though may not be the most optimized solution.
1154 try_bounds=CompareImagesBounds(curr->previous,curr,CompareClearLayer,exception);
1156 (void) FormatLocaleFile(stderr, "expand_clear: %.20gx%.20g%+.20g%+.20g%s\n",
1157 (double) try_bounds.width,(double) try_bounds.height,
1158 (double) try_bounds.x,(double) try_bounds.y,
1159 try_bounds.x<0?" (no expand nessary)":"");
1161 if ( bgnd_bounds.x < 0 )
1162 bgnd_bounds = try_bounds;
1166 (void) FormatLocaleFile(stderr, "expand_bgnd: %.20gx%.20g%+.20g%+.20g\n",
1167 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1168 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1170 if ( try_bounds.x < bgnd_bounds.x )
1172 bgnd_bounds.width+= bgnd_bounds.x-try_bounds.x;
1173 if ( bgnd_bounds.width < try_bounds.width )
1174 bgnd_bounds.width = try_bounds.width;
1175 bgnd_bounds.x = try_bounds.x;
1179 try_bounds.width += try_bounds.x - bgnd_bounds.x;
1180 if ( bgnd_bounds.width < try_bounds.width )
1181 bgnd_bounds.width = try_bounds.width;
1183 if ( try_bounds.y < bgnd_bounds.y )
1185 bgnd_bounds.height += bgnd_bounds.y - try_bounds.y;
1186 if ( bgnd_bounds.height < try_bounds.height )
1187 bgnd_bounds.height = try_bounds.height;
1188 bgnd_bounds.y = try_bounds.y;
1192 try_bounds.height += try_bounds.y - bgnd_bounds.y;
1193 if ( bgnd_bounds.height < try_bounds.height )
1194 bgnd_bounds.height = try_bounds.height;
1197 (void) FormatLocaleFile(stderr, " to : %.20gx%.20g%+.20g%+.20g\n",
1198 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1199 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1202 ClearBounds(bgnd_image,&bgnd_bounds,exception);
1204 /* Something strange is happening with a specific animation
1205 * CompareAnyLayers (normal method) and CompareClearLayers returns the whole
1206 * image, which is not posibly correct! As verified by previous tests.
1207 * Something changed beyond the bgnd_bounds clearing. But without being able
1208 * to see, or writet he image at this point it is hard to tell what is wrong!
1209 * Only CompareOverlay seemed to return something sensible.
1211 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareClearLayer,exception);
1212 (void) FormatLocaleFile(stderr, "expand_ctst: %.20gx%.20g%+.20g%+.20g\n",
1213 (double) try_bounds.width,(double) try_bounds.height,
1214 (double) try_bounds.x,(double) try_bounds.y );
1215 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
1216 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1217 (void) FormatLocaleFile(stderr, "expand_any : %.20gx%.20g%+.20g%+.20g%s\n",
1218 (double) try_bounds.width,(double) try_bounds.height,
1219 (double) try_bounds.x,(double) try_bounds.y,
1220 try_cleared?" (pixels cleared)":"");
1222 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareOverlayLayer,exception);
1224 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1225 (void) FormatLocaleFile(stderr, "expand_test: %.20gx%.20g%+.20g%+.20g%s\n",
1226 (double) try_bounds.width,(double) try_bounds.height,
1227 (double) try_bounds.x,(double) try_bounds.y,
1228 try_cleared?" (pixels cleared)":"");
1232 Test if this background dispose is smaller than any of the
1233 other methods we tryed before this (including duplicated frame)
1236 bgnd_bounds.width*bgnd_bounds.height
1237 +try_bounds.width*try_bounds.height
1238 < bounds[i-1].width*bounds[i-1].height
1239 +dup_bounds.width*dup_bounds.height
1240 +bounds[i].width*bounds[i].height )
1242 cleared=MagickFalse;
1243 bounds[i-1]=bgnd_bounds;
1244 bounds[i]=try_bounds;
1245 if ( disposals[i-1] == DupDispose )
1246 dup_image=DestroyImage(dup_image);
1247 disposals[i-1]=BackgroundDispose;
1249 (void) FormatLocaleFile(stderr, "expand_bgnd: accepted\n");
1251 (void) FormatLocaleFile(stderr, "expand_bgnd: reject\n");
1256 Finalise choice of dispose, set new prev_image,
1257 and junk any extra images as appropriate,
1259 if ( disposals[i-1] == DupDispose )
1261 if (bgnd_image != (Image *) NULL)
1262 bgnd_image=DestroyImage(bgnd_image);
1263 prev_image=DestroyImage(prev_image);
1264 prev_image=dup_image, dup_image=(Image *) NULL;
1265 bounds[i+1]=bounds[i];
1266 bounds[i]=dup_bounds;
1267 disposals[i-1]=DupDispose;
1268 disposals[i]=BackgroundDispose;
1273 if ( dup_image != (Image *) NULL)
1274 dup_image=DestroyImage(dup_image);
1275 if ( disposals[i-1] != PreviousDispose )
1276 prev_image=DestroyImage(prev_image);
1277 if ( disposals[i-1] == BackgroundDispose )
1278 prev_image=bgnd_image, bgnd_image=(Image *)NULL;
1279 if (bgnd_image != (Image *) NULL)
1280 bgnd_image=DestroyImage(bgnd_image);
1281 if ( disposals[i-1] == NoneDispose )
1283 prev_image=CloneImage(curr->previous,curr->previous->page.width,
1284 curr->previous->page.height,MagickTrue,exception);
1285 if (prev_image == (Image *) NULL)
1287 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1288 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1289 return((Image *) NULL);
1294 assert(prev_image != (Image *) NULL);
1295 disposals[i]=disposals[i-1];
1297 (void) FormatLocaleFile(stderr, "final %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1299 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i-1]),
1300 (double) bounds[i-1].width, (double) bounds[i-1].height,
1301 (double) bounds[i-1].x, (double) bounds[i-1].y );
1304 (void) FormatLocaleFile(stderr, "interum %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1306 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i]),
1307 (double) bounds[i].width, (double) bounds[i].height,
1308 (double) bounds[i].x, (double) bounds[i].y );
1309 (void) FormatLocaleFile(stderr, "\n");
1313 prev_image=DestroyImage(prev_image);
1315 Optimize all images in sequence.
1317 sans_exception=AcquireExceptionInfo();
1319 curr=GetFirstImageInList(image);
1320 optimized_image=NewImageList();
1321 while ( curr != (const Image *) NULL )
1323 prev_image=CloneImage(curr,0,0,MagickTrue,exception);
1324 if (prev_image == (Image *) NULL)
1326 if ( disposals[i] == DelDispose ) {
1328 while ( disposals[i] == DelDispose ) {
1329 time += curr->delay*1000/curr->ticks_per_second;
1330 curr=GetNextImageInList(curr);
1333 time += curr->delay*1000/curr->ticks_per_second;
1334 prev_image->ticks_per_second = 100L;
1335 prev_image->delay = time*prev_image->ticks_per_second/1000;
1337 bgnd_image=CropImage(prev_image,&bounds[i],sans_exception);
1338 prev_image=DestroyImage(prev_image);
1339 if (bgnd_image == (Image *) NULL)
1341 bgnd_image->dispose=disposals[i];
1342 if ( disposals[i] == DupDispose ) {
1343 bgnd_image->delay=0;
1344 bgnd_image->dispose=NoneDispose;
1347 curr=GetNextImageInList(curr);
1348 AppendImageToList(&optimized_image,bgnd_image);
1351 sans_exception=DestroyExceptionInfo(sans_exception);
1352 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1353 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1354 if (curr != (Image *) NULL)
1356 optimized_image=DestroyImageList(optimized_image);
1357 return((Image *) NULL);
1359 return(GetFirstImageInList(optimized_image));
1363 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1367 % O p t i m i z e I m a g e L a y e r s %
1371 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1373 % OptimizeImageLayers() compares each image the GIF disposed forms of the
1374 % previous image in the sequence. From this it attempts to select the
1375 % smallest cropped image to replace each frame, while preserving the results
1376 % of the GIF animation.
1378 % The format of the OptimizeImageLayers method is:
1380 % Image *OptimizeImageLayers(const Image *image,
1381 % ExceptionInfo *exception)
1383 % A description of each parameter follows:
1385 % o image: the image.
1387 % o exception: return any errors or warnings in this structure.
1390 MagickExport Image *OptimizeImageLayers(const Image *image,
1391 ExceptionInfo *exception)
1393 return(OptimizeLayerFrames(image,OptimizeImageLayer,exception));
1397 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1401 % O p t i m i z e P l u s I m a g e L a y e r s %
1405 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1407 % OptimizeImagePlusLayers() is exactly as OptimizeImageLayers(), but may
1408 % also add or even remove extra frames in the animation, if it improves
1409 % the total number of pixels in the resulting GIF animation.
1411 % The format of the OptimizePlusImageLayers method is:
1413 % Image *OptimizePlusImageLayers(const Image *image,
1414 % ExceptionInfo *exception)
1416 % A description of each parameter follows:
1418 % o image: the image.
1420 % o exception: return any errors or warnings in this structure.
1423 MagickExport Image *OptimizePlusImageLayers(const Image *image,
1424 ExceptionInfo *exception)
1426 return OptimizeLayerFrames(image, OptimizePlusLayer, exception);
1430 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1434 % 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 %
1438 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1440 % OptimizeImageTransparency() takes a frame optimized GIF animation, and
1441 % compares the overlayed pixels against the disposal image resulting from all
1442 % the previous frames in the animation. Any pixel that does not change the
1443 % disposal image (and thus does not effect the outcome of an overlay) is made
1446 % WARNING: This modifies the current images directly, rather than generate
1447 % a new image sequence.
1449 % The format of the OptimizeImageTransperency method is:
1451 % void OptimizeImageTransperency(Image *image,ExceptionInfo *exception)
1453 % A description of each parameter follows:
1455 % o image: the image sequence
1457 % o exception: return any errors or warnings in this structure.
1460 MagickExport void OptimizeImageTransparency(const Image *image,
1461 ExceptionInfo *exception)
1470 Run the image through the animation sequence
1472 assert(image != (Image *) NULL);
1473 assert(image->signature == MagickSignature);
1474 if (image->debug != MagickFalse)
1475 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1476 assert(exception != (ExceptionInfo *) NULL);
1477 assert(exception->signature == MagickSignature);
1478 next=GetFirstImageInList(image);
1479 dispose_image=CloneImage(next,next->page.width,next->page.height,
1480 MagickTrue,exception);
1481 if (dispose_image == (Image *) NULL)
1483 dispose_image->page=next->page;
1484 dispose_image->page.x=0;
1485 dispose_image->page.y=0;
1486 dispose_image->dispose=NoneDispose;
1487 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
1488 (void) SetImageBackgroundColor(dispose_image,exception);
1490 while ( next != (Image *) NULL )
1496 Overlay this frame's image over the previous disposal image
1498 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
1499 if (current_image == (Image *) NULL)
1501 dispose_image=DestroyImage(dispose_image);
1504 (void) CompositeImage(current_image,next->matte != MagickFalse ?
1505 OverCompositeOp : CopyCompositeOp, next,next->page.x,next->page.y,
1508 At this point the image would be displayed, for the delay period
1510 Work out the disposal of the previous image
1512 if (next->dispose == BackgroundDispose)
1517 bounds.width=next->columns;
1518 bounds.height=next->rows;
1521 bounds.width+=bounds.x;
1524 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
1525 bounds.width=current_image->columns-bounds.x;
1528 bounds.height+=bounds.y;
1531 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
1532 bounds.height=current_image->rows-bounds.y;
1533 ClearBounds(current_image, &bounds,exception);
1535 if (next->dispose != PreviousDispose)
1537 dispose_image=DestroyImage(dispose_image);
1538 dispose_image=current_image;
1541 current_image=DestroyImage(current_image);
1544 Optimize Transparency of the next frame (if present)
1546 next=GetNextImageInList(next);
1547 if ( next != (Image *) NULL ) {
1548 (void) CompositeImage(next, ChangeMaskCompositeOp,
1549 dispose_image, -(next->page.x), -(next->page.y), exception );
1552 dispose_image=DestroyImage(dispose_image);
1557 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1561 % R e m o v e D u p l i c a t e L a y e r s %
1565 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1567 % RemoveDuplicateLayers() removes any image that is exactly the same as the
1568 % next image in the given image list. Image size and virtual canvas offset
1569 % must also match, though not the virtual canvas size itself.
1571 % No check is made with regards to image disposal setting, though it is the
1572 % dispose setting of later image that is kept. Also any time delays are also
1573 % added together. As such coalesced image animations should still produce the
1574 % same result, though with duplicte frames merged into a single frame.
1576 % The format of the RemoveDuplicateLayers method is:
1578 % void RemoveDuplicateLayers(Image **image, ExceptionInfo *exception)
1580 % A description of each parameter follows:
1582 % o images: the image list
1584 % o exception: return any errors or warnings in this structure.
1587 MagickExport void RemoveDuplicateLayers(Image **images,
1588 ExceptionInfo *exception)
1597 assert((*images) != (const Image *) NULL);
1598 assert((*images)->signature == MagickSignature);
1599 if ((*images)->debug != MagickFalse)
1600 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1601 assert(exception != (ExceptionInfo *) NULL);
1602 assert(exception->signature == MagickSignature);
1604 curr=GetFirstImageInList(*images);
1605 for (; (next=GetNextImageInList(curr)) != (Image *) NULL; curr=next)
1607 if ( curr->columns != next->columns || curr->rows != next->rows
1608 || curr->page.x != next->page.x || curr->page.y != next->page.y )
1610 bounds=CompareImagesBounds(curr,next,CompareAnyLayer,exception);
1611 if ( bounds.x < 0 ) {
1613 the two images are the same, merge time delays and delete one.
1616 time = curr->delay*1000/curr->ticks_per_second;
1617 time += next->delay*1000/next->ticks_per_second;
1618 next->ticks_per_second = 100L;
1619 next->delay = time*curr->ticks_per_second/1000;
1620 next->iterations = curr->iterations;
1622 (void) DeleteImageFromList(images);
1625 *images = GetFirstImageInList(*images);
1629 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1633 % R e m o v e Z e r o D e l a y L a y e r s %
1637 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1639 % RemoveZeroDelayLayers() removes any image that as a zero delay time. Such
1640 % images generally represent intermediate or partial updates in GIF
1641 % animations used for file optimization. They are not ment to be displayed
1642 % to users of the animation. Viewable images in an animation should have a
1643 % time delay of 3 or more centi-seconds (hundredths of a second).
1645 % However if all the frames have a zero time delay, then either the animation
1646 % is as yet incomplete, or it is not a GIF animation. This a non-sensible
1647 % situation, so no image will be removed and a 'Zero Time Animation' warning
1648 % (exception) given.
1650 % No warning will be given if no image was removed because all images had an
1651 % appropriate non-zero time delay set.
1653 % Due to the special requirements of GIF disposal handling, GIF animations
1654 % should be coalesced first, before calling this function, though that is not
1657 % The format of the RemoveZeroDelayLayers method is:
1659 % void RemoveZeroDelayLayers(Image **image, ExceptionInfo *exception)
1661 % A description of each parameter follows:
1663 % o images: the image list
1665 % o exception: return any errors or warnings in this structure.
1668 MagickExport void RemoveZeroDelayLayers(Image **images,
1669 ExceptionInfo *exception)
1674 assert((*images) != (const Image *) NULL);
1675 assert((*images)->signature == MagickSignature);
1676 if ((*images)->debug != MagickFalse)
1677 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1678 assert(exception != (ExceptionInfo *) NULL);
1679 assert(exception->signature == MagickSignature);
1681 i=GetFirstImageInList(*images);
1682 for ( ; i != (Image *) NULL; i=GetNextImageInList(i))
1683 if ( i->delay != 0L ) break;
1684 if ( i == (Image *) NULL ) {
1685 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
1686 "ZeroTimeAnimation","`%s'",GetFirstImageInList(*images)->filename);
1689 i=GetFirstImageInList(*images);
1690 while ( i != (Image *) NULL )
1692 if ( i->delay == 0L ) {
1693 (void) DeleteImageFromList(&i);
1697 i=GetNextImageInList(i);
1699 *images=GetFirstImageInList(*images);
1703 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1707 % C o m p o s i t e L a y e r s %
1711 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1713 % CompositeLayers() compose first image sequence (source) over the second
1714 % image sequence (destination), using the given compose method and offsets.
1716 % The pointers to the image list does not have to be the start of that image
1717 % list, but may start somewhere in the middle. Each layer from the two image
1718 % lists are composted together until the end of one of the image lists is
1719 % reached. The offset of each composition is also adjusted to match the
1720 % virtual canvas offsets of each layer. As such the given offset is relative
1721 % to the virtual canvas, and not the actual image.
1723 % No GIF disposal handling is performed, so GIF animations should be
1724 % coalesced before use. However this not a requirement, and individual
1725 % layer images may have any size or offset, for special compositions.
1727 % Special case:- If one of the image sequences is just a single image that
1728 % image is repeatally composed with all the images in the other image list.
1729 % Either the source or destination lists may be the single image, for this
1732 % The destination list will be expanded as needed to match number of source
1733 % image overlaid (from current position to end of list).
1735 % The format of the CompositeLayers method is:
1737 % void CompositeLayers(Image *destination,
1738 % const CompositeOperator compose, Image *source,
1739 % const ssize_t x_offset, const ssize_t y_offset,
1740 % ExceptionInfo *exception);
1742 % A description of each parameter follows:
1744 % o destination: the destination images and results
1746 % o source: source image(s) for the layer composition
1748 % o compose, x_offset, y_offset: arguments passed on to CompositeImages()
1750 % o exception: return any errors or warnings in this structure.
1754 static inline void CompositeCanvas(Image *destination,
1755 const CompositeOperator compose,Image *source,ssize_t x_offset,
1756 ssize_t y_offset,ExceptionInfo *exception)
1758 x_offset+=source->page.x-destination->page.x;
1759 y_offset+=source->page.y-destination->page.y;
1760 (void) CompositeImage(destination,compose,source,x_offset,y_offset,
1764 MagickExport void CompositeLayers(Image *destination,
1765 const CompositeOperator compose, Image *source,const ssize_t x_offset,
1766 const ssize_t y_offset,ExceptionInfo *exception)
1768 assert(destination != (Image *) NULL);
1769 assert(destination->signature == MagickSignature);
1770 assert(source != (Image *) NULL);
1771 assert(source->signature == MagickSignature);
1772 assert(exception != (ExceptionInfo *) NULL);
1773 assert(exception->signature == MagickSignature);
1774 if (source->debug != MagickFalse || destination->debug != MagickFalse)
1775 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s - %s",
1776 source->filename, destination->filename);
1779 Overlay single source image over destation image/list
1781 if ( source->previous == (Image *) NULL && source->next == (Image *) NULL )
1782 while ( destination != (Image *) NULL )
1784 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1786 destination=GetNextImageInList(destination);
1790 Overlay source image list over single destination
1791 Generating multiple clones of destination image to match source list.
1792 Original Destination image becomes first image of generated list.
1793 As such the image list pointer does not require any change in caller.
1794 Some animation attributes however also needs coping in this case.
1796 else if ( destination->previous == (Image *) NULL &&
1797 destination->next == (Image *) NULL )
1799 Image *dest = CloneImage(destination,0,0,MagickTrue,exception);
1801 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1803 /* copy source image attributes ? */
1804 if ( source->next != (Image *) NULL )
1806 destination->delay = source->delay;
1807 destination->iterations = source->iterations;
1809 source=GetNextImageInList(source);
1811 while ( source != (Image *) NULL )
1813 AppendImageToList(&destination,
1814 CloneImage(dest,0,0,MagickTrue,exception));
1815 destination=GetLastImageInList(destination);
1817 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1819 destination->delay = source->delay;
1820 destination->iterations = source->iterations;
1821 source=GetNextImageInList(source);
1823 dest=DestroyImage(dest);
1827 Overlay a source image list over a destination image list
1828 until either list runs out of images. (Does not repeat)
1831 while ( source != (Image *) NULL && destination != (Image *) NULL )
1833 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1835 source=GetNextImageInList(source);
1836 destination=GetNextImageInList(destination);
1841 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1845 % M e r g e I m a g e L a y e r s %
1849 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1851 % MergeImageLayers() composes all the image layers from the current given
1852 % image onward to produce a single image of the merged layers.
1854 % The inital canvas's size depends on the given ImageLayerMethod, and is
1855 % initialized using the first images background color. The images
1856 % are then compositied onto that image in sequence using the given
1857 % composition that has been assigned to each individual image.
1859 % The format of the MergeImageLayers is:
1861 % Image *MergeImageLayers(const Image *image,
1862 % const ImageLayerMethod method, ExceptionInfo *exception)
1864 % A description of each parameter follows:
1866 % o image: the image list to be composited together
1868 % o method: the method of selecting the size of the initial canvas.
1870 % MergeLayer: Merge all layers onto a canvas just large enough
1871 % to hold all the actual images. The virtual canvas of the
1872 % first image is preserved but otherwise ignored.
1874 % FlattenLayer: Use the virtual canvas size of first image.
1875 % Images which fall outside this canvas is clipped.
1876 % This can be used to 'fill out' a given virtual canvas.
1878 % MosaicLayer: Start with the virtual canvas of the first image,
1879 % enlarging left and right edges to contain all images.
1880 % Images with negative offsets will be clipped.
1882 % TrimBoundsLayer: Determine the overall bounds of all the image
1883 % layers just as in "MergeLayer", then adjust the the canvas
1884 % and offsets to be relative to those bounds, without overlaying
1887 % WARNING: a new image is not returned, the original image
1888 % sequence page data is modified instead.
1890 % o exception: return any errors or warnings in this structure.
1893 MagickExport Image *MergeImageLayers(Image *image,const ImageLayerMethod method,
1894 ExceptionInfo *exception)
1896 #define MergeLayersTag "Merge/Layers"
1907 register const Image
1918 assert(image != (Image *) NULL);
1919 assert(image->signature == MagickSignature);
1920 if (image->debug != MagickFalse)
1921 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1922 assert(exception != (ExceptionInfo *) NULL);
1923 assert(exception->signature == MagickSignature);
1925 Determine canvas image size, and its virtual canvas size and offset
1928 width=image->columns;
1932 case TrimBoundsLayer:
1936 next=GetNextImageInList(image);
1937 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
1939 if (page.x > next->page.x)
1941 width+=page.x-next->page.x;
1942 page.x=next->page.x;
1944 if (page.y > next->page.y)
1946 height+=page.y-next->page.y;
1947 page.y=next->page.y;
1949 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns-page.x))
1950 width=(size_t) next->page.x+(ssize_t)next->columns-page.x;
1951 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows-page.y))
1952 height=(size_t) next->page.y+(ssize_t) next->rows-page.y;
1960 if (page.height > 0)
1970 if (page.height > 0)
1972 for (next=image; next != (Image *) NULL; next=GetNextImageInList(next))
1974 if (method == MosaicLayer)
1976 page.x=next->page.x;
1977 page.y=next->page.y;
1978 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns))
1979 width=(size_t) next->page.x+next->columns;
1980 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows))
1981 height=(size_t) next->page.y+next->rows;
1992 Set virtual canvas size if not defined.
1994 if (page.width == 0)
1995 page.width=page.x < 0 ? width : width+page.x;
1996 if (page.height == 0)
1997 page.height=page.y < 0 ? height : height+page.y;
1999 Handle "TrimBoundsLayer" method separately to normal 'layer merge'.
2001 if (method == TrimBoundsLayer)
2003 number_images=GetImageListLength(image);
2004 for (scene=0; scene < (ssize_t) number_images; scene++)
2006 image->page.x-=page.x;
2007 image->page.y-=page.y;
2008 image->page.width=width;
2009 image->page.height=height;
2010 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2012 if (proceed == MagickFalse)
2014 image=GetNextImageInList(image);
2015 if (image == (Image *) NULL)
2018 return((Image *) NULL);
2021 Create canvas size of width and height, and background color.
2023 canvas=CloneImage(image,width,height,MagickTrue,exception);
2024 if (canvas == (Image *) NULL)
2025 return((Image *) NULL);
2026 (void) SetImageBackgroundColor(canvas,exception);
2028 canvas->dispose=UndefinedDispose;
2030 Compose images onto canvas, with progress monitor
2032 number_images=GetImageListLength(image);
2033 for (scene=0; scene < (ssize_t) number_images; scene++)
2035 (void) CompositeImage(canvas,image->compose,image,image->page.x-
2036 canvas->page.x,image->page.y-canvas->page.y,exception);
2037 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2039 if (proceed == MagickFalse)
2041 image=GetNextImageInList(image);
2042 if (image == (Image *) NULL)