2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5 % L AAA Y Y EEEEE RRRR %
9 % LLLLL A A Y EEEEE R R %
11 % MagickCore Image Layering Methods %
19 % Copyright 1999-2012 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);
252 next=GetFirstImageInList(image);
254 if (bounds.width == 0)
256 bounds.width=next->columns;
258 bounds.width+=bounds.x;
260 if (bounds.height == 0)
262 bounds.height=next->rows;
264 bounds.height+=bounds.y;
268 coalesce_image=CloneImage(next,bounds.width,bounds.height,MagickTrue,
270 if (coalesce_image == (Image *) NULL)
271 return((Image *) NULL);
272 coalesce_image->page=bounds;
273 coalesce_image->dispose=NoneDispose;
274 (void) SetImageBackgroundColor(coalesce_image,exception);
276 Coalesce rest of the images.
278 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
279 (void) CompositeImage(coalesce_image,CopyCompositeOp,next,next->page.x,
280 next->page.y,exception);
281 next=GetNextImageInList(next);
282 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
285 Determine the bounds that was overlaid in the previous image.
287 previous=GetPreviousImageInList(next);
288 bounds=previous->page;
289 bounds.width=previous->columns;
290 bounds.height=previous->rows;
293 bounds.width+=bounds.x;
296 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) coalesce_image->columns)
297 bounds.width=coalesce_image->columns-bounds.x;
300 bounds.height+=bounds.y;
303 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) coalesce_image->rows)
304 bounds.height=coalesce_image->rows-bounds.y;
306 Replace the dispose image with the new coalesced image.
308 if (GetPreviousImageInList(next)->dispose != PreviousDispose)
310 dispose_image=DestroyImage(dispose_image);
311 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
312 if (dispose_image == (Image *) NULL)
314 coalesce_image=DestroyImageList(coalesce_image);
315 return((Image *) NULL);
319 Clear the overlaid area of the coalesced bounds for background disposal
321 if (next->previous->dispose == BackgroundDispose)
322 ClearBounds(dispose_image,&bounds,exception);
324 Next image is the dispose image, overlaid with next frame in sequence.
326 coalesce_image->next=CloneImage(dispose_image,0,0,MagickTrue,exception);
327 coalesce_image->next->previous=coalesce_image;
328 previous=coalesce_image;
329 coalesce_image=GetNextImageInList(coalesce_image);
330 (void) CompositeImage(coalesce_image,next->matte != MagickFalse ?
331 OverCompositeOp : CopyCompositeOp,next,next->page.x,next->page.y,
333 (void) CloneImageProfiles(coalesce_image,next);
334 (void) CloneImageProperties(coalesce_image,next);
335 (void) CloneImageArtifacts(coalesce_image,next);
336 coalesce_image->page=previous->page;
338 If a pixel goes opaque to transparent, use background dispose.
340 if (IsBoundsCleared(previous,coalesce_image,&bounds,exception))
341 coalesce_image->dispose=BackgroundDispose;
343 coalesce_image->dispose=NoneDispose;
344 previous->dispose=coalesce_image->dispose;
346 dispose_image=DestroyImage(dispose_image);
347 return(GetFirstImageInList(coalesce_image));
351 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
355 % D i s p o s e I m a g e s %
359 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
361 % DisposeImages() returns the coalesced frames of a GIF animation as it would
362 % appear after the GIF dispose method of that frame has been applied. That
363 % is it returned the appearance of each frame before the next is overlaid.
365 % The format of the DisposeImages method is:
367 % Image *DisposeImages(Image *image,ExceptionInfo *exception)
369 % A description of each parameter follows:
371 % o image: the image sequence.
373 % o exception: return any errors or warnings in this structure.
376 MagickExport Image *DisposeImages(const Image *image,ExceptionInfo *exception)
389 Run the image through the animation sequence
391 assert(image != (Image *) NULL);
392 assert(image->signature == MagickSignature);
393 if (image->debug != MagickFalse)
394 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
395 assert(exception != (ExceptionInfo *) NULL);
396 assert(exception->signature == MagickSignature);
397 curr=GetFirstImageInList(image);
398 dispose_image=CloneImage(curr,curr->page.width,curr->page.height,MagickTrue,
400 if (dispose_image == (Image *) NULL)
401 return((Image *) NULL);
402 dispose_image->page=curr->page;
403 dispose_image->page.x=0;
404 dispose_image->page.y=0;
405 dispose_image->dispose=NoneDispose;
406 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
407 (void) SetImageBackgroundColor(dispose_image,exception);
408 dispose_images=NewImageList();
409 for ( ; curr != (Image *) NULL; curr=GetNextImageInList(curr))
415 Overlay this frame's image over the previous disposal image.
417 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
418 if (current_image == (Image *) NULL)
420 dispose_images=DestroyImageList(dispose_images);
421 dispose_image=DestroyImage(dispose_image);
422 return((Image *) NULL);
424 (void) CompositeImage(current_image,curr->matte != MagickFalse ?
425 OverCompositeOp : CopyCompositeOp,curr,curr->page.x,curr->page.y,
428 Handle Background dispose: image is displayed for the delay period.
430 if (curr->dispose == BackgroundDispose)
433 bounds.width=curr->columns;
434 bounds.height=curr->rows;
437 bounds.width+=bounds.x;
440 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
441 bounds.width=current_image->columns-bounds.x;
444 bounds.height+=bounds.y;
447 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
448 bounds.height=current_image->rows-bounds.y;
449 ClearBounds(current_image,&bounds,exception);
452 Select the appropriate previous/disposed image.
454 if (curr->dispose == PreviousDispose)
455 current_image=DestroyImage(current_image);
458 dispose_image=DestroyImage(dispose_image);
459 dispose_image=current_image;
460 current_image=(Image *)NULL;
463 Save the dispose image just calculated for return.
469 dispose=CloneImage(dispose_image,0,0,MagickTrue,exception);
470 if (dispose == (Image *) NULL)
472 dispose_images=DestroyImageList(dispose_images);
473 dispose_image=DestroyImage(dispose_image);
474 return((Image *) NULL);
476 (void) CloneImageProfiles(dispose,curr);
477 (void) CloneImageProperties(dispose,curr);
478 (void) CloneImageArtifacts(dispose,curr);
481 dispose->dispose=curr->dispose;
482 AppendImageToList(&dispose_images,dispose);
485 dispose_image=DestroyImage(dispose_image);
486 return(GetFirstImageInList(dispose_images));
490 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
494 + C o m p a r e P i x e l s %
498 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
500 % ComparePixels() Compare the two pixels and return true if the pixels
501 % differ according to the given LayerType comparision method.
503 % This currently only used internally by CompareImagesBounds(). It is
504 % doubtful that this sub-routine will be useful outside this module.
506 % The format of the ComparePixels method is:
508 % MagickBooleanType *ComparePixels(const ImageLayerMethod method,
509 % const PixelInfo *p,const PixelInfo *q)
511 % A description of each parameter follows:
513 % o method: What differences to look for. Must be one of
514 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
516 % o p, q: the pixels to test for appropriate differences.
520 static MagickBooleanType ComparePixels(const ImageLayerMethod method,
521 const PixelInfo *p,const PixelInfo *q)
528 Any change in pixel values
530 if (method == CompareAnyLayer)
531 return((MagickBooleanType)(IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
533 o1 = (p->matte != MagickFalse) ? p->alpha : OpaqueAlpha;
534 o2 = (q->matte != MagickFalse) ? q->alpha : OpaqueAlpha;
537 Pixel goes from opaque to transprency
539 if (method == CompareClearLayer)
540 return((MagickBooleanType) ( (o1 <= ((MagickRealType) QuantumRange/2.0)) &&
541 (o2 > ((MagickRealType) QuantumRange/2.0)) ) );
544 overlay would change first pixel by second
546 if (method == CompareOverlayLayer)
548 if (o2 > ((MagickRealType) QuantumRange/2.0))
550 return((MagickBooleanType) (IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
557 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
561 + C o m p a r e I m a g e B o u n d s %
565 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
567 % CompareImagesBounds() Given two images return the smallest rectangular area
568 % by which the two images differ, accourding to the given 'Compare...'
571 % This currently only used internally in this module, but may eventually
572 % be used by other modules.
574 % The format of the CompareImagesBounds method is:
576 % RectangleInfo *CompareImagesBounds(const ImageLayerMethod method,
577 % const Image *image1, const Image *image2, ExceptionInfo *exception)
579 % A description of each parameter follows:
581 % o method: What differences to look for. Must be one of CompareAnyLayer,
582 % CompareClearLayer, CompareOverlayLayer.
584 % o image1, image2: the two images to compare.
586 % o exception: return any errors or warnings in this structure.
590 static RectangleInfo CompareImagesBounds(const Image *image1,const Image *image2,
591 const ImageLayerMethod method,ExceptionInfo *exception)
600 register const Quantum
611 Set bounding box of the differences between images.
613 GetPixelInfo(image1,&pixel1);
614 GetPixelInfo(image2,&pixel2);
615 for (x=0; x < (ssize_t) image1->columns; x++)
617 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
618 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
619 if ((p == (const Quantum *) NULL) ||
620 (q == (Quantum *) NULL))
622 for (y=0; y < (ssize_t) image1->rows; y++)
624 GetPixelInfoPixel(image1,p,&pixel1);
625 GetPixelInfoPixel(image2,q,&pixel2);
626 if (ComparePixels(method,&pixel1,&pixel2))
628 p+=GetPixelChannels(image1);
631 if (y < (ssize_t) image1->rows)
634 if (x >= (ssize_t) image1->columns)
637 Images are identical, return a null image.
646 for (x=(ssize_t) image1->columns-1; x >= 0; x--)
648 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
649 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
650 if ((p == (const Quantum *) NULL) ||
651 (q == (Quantum *) NULL))
653 for (y=0; y < (ssize_t) image1->rows; y++)
655 GetPixelInfoPixel(image1,p,&pixel1);
656 GetPixelInfoPixel(image2,q,&pixel2);
657 if (ComparePixels(method,&pixel1,&pixel2))
659 p+=GetPixelChannels(image1);
662 if (y < (ssize_t) image1->rows)
665 bounds.width=(size_t) (x-bounds.x+1);
666 for (y=0; y < (ssize_t) image1->rows; y++)
668 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
669 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
670 if ((p == (const Quantum *) NULL) ||
671 (q == (Quantum *) NULL))
673 for (x=0; x < (ssize_t) image1->columns; x++)
675 GetPixelInfoPixel(image1,p,&pixel1);
676 GetPixelInfoPixel(image2,q,&pixel2);
677 if (ComparePixels(method,&pixel1,&pixel2))
679 p+=GetPixelChannels(image1);
682 if (x < (ssize_t) image1->columns)
686 for (y=(ssize_t) image1->rows-1; y >= 0; y--)
688 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
689 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
690 if ((p == (const Quantum *) NULL) ||
691 (q == (Quantum *) NULL))
693 for (x=0; x < (ssize_t) image1->columns; x++)
695 GetPixelInfoPixel(image1,p,&pixel1);
696 GetPixelInfoPixel(image2,q,&pixel2);
697 if (ComparePixels(method,&pixel1,&pixel2))
699 p+=GetPixelChannels(image1);
702 if (x < (ssize_t) image1->columns)
705 bounds.height=(size_t) (y-bounds.y+1);
710 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
714 % C o m p a r e I m a g e L a y e r s %
718 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
720 % CompareImagesLayers() compares each image with the next in a sequence and
721 % returns the minimum bounding region of all the pixel differences (of the
722 % ImageLayerMethod specified) it discovers.
724 % Images do NOT have to be the same size, though it is best that all the
725 % images are 'coalesced' (images are all the same size, on a flattened
726 % canvas, so as to represent exactly how an specific frame should look).
728 % No GIF dispose methods are applied, so GIF animations must be coalesced
729 % before applying this image operator to find differences to them.
731 % The format of the CompareImagesLayers method is:
733 % Image *CompareImagesLayers(const Image *images,
734 % const ImageLayerMethod method,ExceptionInfo *exception)
736 % A description of each parameter follows:
738 % o image: the image.
740 % o method: the layers type to compare images with. Must be one of...
741 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
743 % o exception: return any errors or warnings in this structure.
747 MagickExport Image *CompareImagesLayers(const Image *image,
748 const ImageLayerMethod method, ExceptionInfo *exception)
764 assert(image != (const Image *) NULL);
765 assert(image->signature == MagickSignature);
766 if (image->debug != MagickFalse)
767 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
768 assert(exception != (ExceptionInfo *) NULL);
769 assert(exception->signature == MagickSignature);
770 assert((method == CompareAnyLayer) ||
771 (method == CompareClearLayer) ||
772 (method == CompareOverlayLayer));
774 Allocate bounds memory.
776 next=GetFirstImageInList(image);
777 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
778 GetImageListLength(next),sizeof(*bounds));
779 if (bounds == (RectangleInfo *) NULL)
780 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
782 Set up first comparision images.
784 image_a=CloneImage(next,next->page.width,next->page.height,
785 MagickTrue,exception);
786 if (image_a == (Image *) NULL)
788 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
789 return((Image *) NULL);
791 image_a->background_color.alpha=(Quantum) TransparentAlpha;
792 (void) SetImageBackgroundColor(image_a,exception);
793 image_a->page=next->page;
796 (void) CompositeImage(image_a,CopyCompositeOp,next,next->page.x,next->page.y,
799 Compute the bounding box of changes for the later images
802 next=GetNextImageInList(next);
803 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
805 image_b=CloneImage(image_a,0,0,MagickTrue,exception);
806 if (image_b == (Image *) NULL)
808 image_a=DestroyImage(image_a);
809 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
810 return((Image *) NULL);
812 (void) CompositeImage(image_a,CopyCompositeOp,next,next->page.x,
813 next->page.y,exception);
814 bounds[i]=CompareImagesBounds(image_b,image_a,method,exception);
816 image_b=DestroyImage(image_b);
819 image_a=DestroyImage(image_a);
821 Clone first image in sequence.
823 next=GetFirstImageInList(image);
824 layers=CloneImage(next,0,0,MagickTrue,exception);
825 if (layers == (Image *) NULL)
827 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
828 return((Image *) NULL);
831 Deconstruct the image sequence.
834 next=GetNextImageInList(next);
835 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
837 image_a=CloneImage(next,0,0,MagickTrue,exception);
838 if (image_a == (Image *) NULL)
840 image_b=CropImage(image_a,&bounds[i],exception);
841 image_a=DestroyImage(image_a);
842 if (image_b == (Image *) NULL)
844 AppendImageToList(&layers,image_b);
847 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
848 if (next != (Image *) NULL)
850 layers=DestroyImageList(layers);
851 return((Image *) NULL);
853 return(GetFirstImageInList(layers));
857 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
861 + O p t i m i z e L a y e r F r a m e s %
865 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
867 % OptimizeLayerFrames() takes a coalesced GIF animation, and compares each
868 % frame against the three different 'disposal' forms of the previous frame.
869 % From this it then attempts to select the smallest cropped image and
870 % disposal method needed to reproduce the resulting image.
872 % Note that this not easy, and may require the expansion of the bounds
873 % of previous frame, simply clear pixels for the next animation frame to
874 % transparency according to the selected dispose method.
876 % The format of the OptimizeLayerFrames method is:
878 % Image *OptimizeLayerFrames(const Image *image,
879 % const ImageLayerMethod method, ExceptionInfo *exception)
881 % A description of each parameter follows:
883 % o image: the image.
885 % o method: the layers technique to optimize with. Must be one of...
886 % OptimizeImageLayer, or OptimizePlusLayer. The Plus form allows
887 % the addition of extra 'zero delay' frames to clear pixels from
888 % the previous frame, and the removal of frames that done change,
889 % merging the delay times together.
891 % o exception: return any errors or warnings in this structure.
895 Define a 'fake' dispose method where the frame is duplicated, (for
896 OptimizePlusLayer) with a extra zero time delay frame which does a
897 BackgroundDisposal to clear the pixels that need to be cleared.
899 #define DupDispose ((DisposeType)9)
901 Another 'fake' dispose method used to removed frames that don't change.
903 #define DelDispose ((DisposeType)8)
905 #define DEBUG_OPT_FRAME 0
907 static Image *OptimizeLayerFrames(const Image *image,
908 const ImageLayerMethod method, ExceptionInfo *exception)
939 assert(image != (const Image *) NULL);
940 assert(image->signature == MagickSignature);
941 if (image->debug != MagickFalse)
942 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
943 assert(exception != (ExceptionInfo *) NULL);
944 assert(exception->signature == MagickSignature);
945 assert(method == OptimizeLayer ||
946 method == OptimizeImageLayer ||
947 method == OptimizePlusLayer);
950 Are we allowed to add/remove frames from animation
952 add_frames=method == OptimizePlusLayer ? MagickTrue : MagickFalse;
954 Ensure all the images are the same size
956 curr=GetFirstImageInList(image);
957 for (; curr != (Image *) NULL; curr=GetNextImageInList(curr))
959 if ((curr->columns != image->columns) || (curr->rows != image->rows))
960 ThrowImageException(OptionError,"ImagesAreNotTheSameSize");
962 FUTURE: also check that image is also fully coalesced (full page)
963 Though as long as they are the same size it should not matter.
967 Allocate memory (times 2 if we allow the use of frame duplications)
969 curr=GetFirstImageInList(image);
970 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
971 GetImageListLength(curr),(add_frames != MagickFalse ? 2UL : 1UL)*
973 if (bounds == (RectangleInfo *) NULL)
974 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
975 disposals=(DisposeType *) AcquireQuantumMemory((size_t)
976 GetImageListLength(image),(add_frames != MagickFalse ? 2UL : 1UL)*
978 if (disposals == (DisposeType *) NULL)
980 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
981 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
984 Initialise Previous Image as fully transparent
986 prev_image=CloneImage(curr,curr->page.width,curr->page.height,
987 MagickTrue,exception);
988 if (prev_image == (Image *) NULL)
990 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
991 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
992 return((Image *) NULL);
994 prev_image->page=curr->page; /* ERROR: <-- should not be need, but is! */
995 prev_image->page.x=0;
996 prev_image->page.y=0;
997 prev_image->dispose=NoneDispose;
999 prev_image->background_color.alpha=(Quantum) TransparentAlpha;
1000 (void) SetImageBackgroundColor(prev_image,exception);
1002 Figure out the area of overlay of the first frame
1003 No pixel could be cleared as all pixels are already cleared.
1007 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
1009 disposals[0]=NoneDispose;
1010 bounds[0]=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
1012 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g\n\n",
1013 (double) bounds[i].width,(double) bounds[i].height,
1014 (double) bounds[i].x,(double) bounds[i].y );
1017 Compute the bounding box of changes for each pair of images.
1020 bgnd_image=(Image *)NULL;
1021 dup_image=(Image *)NULL;
1023 dup_bounds.height=0;
1026 curr=GetNextImageInList(curr);
1027 for ( ; curr != (const Image *) NULL; curr=GetNextImageInList(curr))
1030 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
1033 Assume none disposal is the best
1035 bounds[i]=CompareImagesBounds(curr->previous,curr,CompareAnyLayer,exception);
1036 cleared=IsBoundsCleared(curr->previous,curr,&bounds[i],exception);
1037 disposals[i-1]=NoneDispose;
1039 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g%s%s\n",
1040 (double) bounds[i].width,(double) bounds[i].height,
1041 (double) bounds[i].x,(double) bounds[i].y,
1042 bounds[i].x < 0?" (unchanged)":"",
1043 cleared?" (pixels cleared)":"");
1045 if ( bounds[i].x < 0 ) {
1047 Image frame is exactly the same as the previous frame!
1048 If not adding frames leave it to be cropped down to a null image.
1049 Otherwise mark previous image for deleted, transfering its crop bounds
1050 to the current image.
1052 if ( add_frames && i>=2 ) {
1053 disposals[i-1]=DelDispose;
1054 disposals[i]=NoneDispose;
1055 bounds[i]=bounds[i-1];
1063 Compare a none disposal against a previous disposal
1065 try_bounds=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
1066 try_cleared=IsBoundsCleared(prev_image,curr,&try_bounds,exception);
1068 (void) FormatLocaleFile(stderr, "test_prev: %.20gx%.20g%+.20g%+.20g%s\n",
1069 (double) try_bounds.width,(double) try_bounds.height,
1070 (double) try_bounds.x,(double) try_bounds.y,
1071 try_cleared?" (pixels were cleared)":"");
1073 if ( (!try_cleared && cleared ) ||
1074 try_bounds.width * try_bounds.height
1075 < bounds[i].width * bounds[i].height )
1077 cleared=try_cleared;
1078 bounds[i]=try_bounds;
1079 disposals[i-1]=PreviousDispose;
1081 (void) FormatLocaleFile(stderr, "previous: accepted\n");
1083 (void) FormatLocaleFile(stderr, "previous: rejected\n");
1088 If we are allowed lets try a complex frame duplication.
1089 It is useless if the previous image already clears pixels correctly.
1090 This method will always clear all the pixels that need to be cleared.
1092 dup_bounds.width=dup_bounds.height=0; /* no dup, no pixel added */
1095 dup_image=CloneImage(curr->previous,curr->previous->page.width,
1096 curr->previous->page.height,MagickTrue,exception);
1097 if (dup_image == (Image *) NULL)
1099 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1100 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1101 prev_image=DestroyImage(prev_image);
1102 return((Image *) NULL);
1104 dup_bounds=CompareImagesBounds(dup_image,curr,CompareClearLayer,exception);
1105 ClearBounds(dup_image,&dup_bounds,exception);
1106 try_bounds=CompareImagesBounds(dup_image,curr,CompareAnyLayer,exception);
1108 dup_bounds.width*dup_bounds.height
1109 +try_bounds.width*try_bounds.height
1110 < bounds[i].width * bounds[i].height )
1112 cleared=MagickFalse;
1113 bounds[i]=try_bounds;
1114 disposals[i-1]=DupDispose;
1115 /* to be finalised later, if found to be optimial */
1118 dup_bounds.width=dup_bounds.height=0;
1121 Now compare against a simple background disposal
1123 bgnd_image=CloneImage(curr->previous,curr->previous->page.width,
1124 curr->previous->page.height,MagickTrue,exception);
1125 if (bgnd_image == (Image *) NULL)
1127 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1128 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1129 prev_image=DestroyImage(prev_image);
1130 if ( dup_image != (Image *) NULL)
1131 dup_image=DestroyImage(dup_image);
1132 return((Image *) NULL);
1134 bgnd_bounds=bounds[i-1]; /* interum bounds of the previous image */
1135 ClearBounds(bgnd_image,&bgnd_bounds,exception);
1136 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
1137 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1139 (void) FormatLocaleFile(stderr, "background: %s\n",
1140 try_cleared?"(pixels cleared)":"");
1145 Straight background disposal failed to clear pixels needed!
1146 Lets try expanding the disposal area of the previous frame, to
1147 include the pixels that are cleared. This guaranteed
1148 to work, though may not be the most optimized solution.
1150 try_bounds=CompareImagesBounds(curr->previous,curr,CompareClearLayer,exception);
1152 (void) FormatLocaleFile(stderr, "expand_clear: %.20gx%.20g%+.20g%+.20g%s\n",
1153 (double) try_bounds.width,(double) try_bounds.height,
1154 (double) try_bounds.x,(double) try_bounds.y,
1155 try_bounds.x<0?" (no expand nessary)":"");
1157 if ( bgnd_bounds.x < 0 )
1158 bgnd_bounds = try_bounds;
1162 (void) FormatLocaleFile(stderr, "expand_bgnd: %.20gx%.20g%+.20g%+.20g\n",
1163 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1164 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1166 if ( try_bounds.x < bgnd_bounds.x )
1168 bgnd_bounds.width+= bgnd_bounds.x-try_bounds.x;
1169 if ( bgnd_bounds.width < try_bounds.width )
1170 bgnd_bounds.width = try_bounds.width;
1171 bgnd_bounds.x = try_bounds.x;
1175 try_bounds.width += try_bounds.x - bgnd_bounds.x;
1176 if ( bgnd_bounds.width < try_bounds.width )
1177 bgnd_bounds.width = try_bounds.width;
1179 if ( try_bounds.y < bgnd_bounds.y )
1181 bgnd_bounds.height += bgnd_bounds.y - try_bounds.y;
1182 if ( bgnd_bounds.height < try_bounds.height )
1183 bgnd_bounds.height = try_bounds.height;
1184 bgnd_bounds.y = try_bounds.y;
1188 try_bounds.height += try_bounds.y - bgnd_bounds.y;
1189 if ( bgnd_bounds.height < try_bounds.height )
1190 bgnd_bounds.height = try_bounds.height;
1193 (void) FormatLocaleFile(stderr, " to : %.20gx%.20g%+.20g%+.20g\n",
1194 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1195 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1198 ClearBounds(bgnd_image,&bgnd_bounds,exception);
1200 /* Something strange is happening with a specific animation
1201 * CompareAnyLayers (normal method) and CompareClearLayers returns the whole
1202 * image, which is not posibly correct! As verified by previous tests.
1203 * Something changed beyond the bgnd_bounds clearing. But without being able
1204 * to see, or writet he image at this point it is hard to tell what is wrong!
1205 * Only CompareOverlay seemed to return something sensible.
1207 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareClearLayer,exception);
1208 (void) FormatLocaleFile(stderr, "expand_ctst: %.20gx%.20g%+.20g%+.20g\n",
1209 (double) try_bounds.width,(double) try_bounds.height,
1210 (double) try_bounds.x,(double) try_bounds.y );
1211 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
1212 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1213 (void) FormatLocaleFile(stderr, "expand_any : %.20gx%.20g%+.20g%+.20g%s\n",
1214 (double) try_bounds.width,(double) try_bounds.height,
1215 (double) try_bounds.x,(double) try_bounds.y,
1216 try_cleared?" (pixels cleared)":"");
1218 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareOverlayLayer,exception);
1220 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1221 (void) FormatLocaleFile(stderr, "expand_test: %.20gx%.20g%+.20g%+.20g%s\n",
1222 (double) try_bounds.width,(double) try_bounds.height,
1223 (double) try_bounds.x,(double) try_bounds.y,
1224 try_cleared?" (pixels cleared)":"");
1228 Test if this background dispose is smaller than any of the
1229 other methods we tryed before this (including duplicated frame)
1232 bgnd_bounds.width*bgnd_bounds.height
1233 +try_bounds.width*try_bounds.height
1234 < bounds[i-1].width*bounds[i-1].height
1235 +dup_bounds.width*dup_bounds.height
1236 +bounds[i].width*bounds[i].height )
1238 cleared=MagickFalse;
1239 bounds[i-1]=bgnd_bounds;
1240 bounds[i]=try_bounds;
1241 if ( disposals[i-1] == DupDispose )
1242 dup_image=DestroyImage(dup_image);
1243 disposals[i-1]=BackgroundDispose;
1245 (void) FormatLocaleFile(stderr, "expand_bgnd: accepted\n");
1247 (void) FormatLocaleFile(stderr, "expand_bgnd: reject\n");
1252 Finalise choice of dispose, set new prev_image,
1253 and junk any extra images as appropriate,
1255 if ( disposals[i-1] == DupDispose )
1257 if (bgnd_image != (Image *) NULL)
1258 bgnd_image=DestroyImage(bgnd_image);
1259 prev_image=DestroyImage(prev_image);
1260 prev_image=dup_image, dup_image=(Image *) NULL;
1261 bounds[i+1]=bounds[i];
1262 bounds[i]=dup_bounds;
1263 disposals[i-1]=DupDispose;
1264 disposals[i]=BackgroundDispose;
1269 if ( dup_image != (Image *) NULL)
1270 dup_image=DestroyImage(dup_image);
1271 if ( disposals[i-1] != PreviousDispose )
1272 prev_image=DestroyImage(prev_image);
1273 if ( disposals[i-1] == BackgroundDispose )
1274 prev_image=bgnd_image, bgnd_image=(Image *)NULL;
1275 if (bgnd_image != (Image *) NULL)
1276 bgnd_image=DestroyImage(bgnd_image);
1277 if ( disposals[i-1] == NoneDispose )
1279 prev_image=CloneImage(curr->previous,curr->previous->page.width,
1280 curr->previous->page.height,MagickTrue,exception);
1281 if (prev_image == (Image *) NULL)
1283 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1284 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1285 return((Image *) NULL);
1290 assert(prev_image != (Image *) NULL);
1291 disposals[i]=disposals[i-1];
1293 (void) FormatLocaleFile(stderr, "final %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1295 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i-1]),
1296 (double) bounds[i-1].width, (double) bounds[i-1].height,
1297 (double) bounds[i-1].x, (double) bounds[i-1].y );
1300 (void) FormatLocaleFile(stderr, "interum %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1302 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i]),
1303 (double) bounds[i].width, (double) bounds[i].height,
1304 (double) bounds[i].x, (double) bounds[i].y );
1305 (void) FormatLocaleFile(stderr, "\n");
1309 prev_image=DestroyImage(prev_image);
1311 Optimize all images in sequence.
1313 sans_exception=AcquireExceptionInfo();
1315 curr=GetFirstImageInList(image);
1316 optimized_image=NewImageList();
1317 while ( curr != (const Image *) NULL )
1319 prev_image=CloneImage(curr,0,0,MagickTrue,exception);
1320 if (prev_image == (Image *) NULL)
1322 if ( disposals[i] == DelDispose ) {
1324 while ( disposals[i] == DelDispose ) {
1325 time += curr->delay*1000/curr->ticks_per_second;
1326 curr=GetNextImageInList(curr);
1329 time += curr->delay*1000/curr->ticks_per_second;
1330 prev_image->ticks_per_second = 100L;
1331 prev_image->delay = time*prev_image->ticks_per_second/1000;
1333 bgnd_image=CropImage(prev_image,&bounds[i],sans_exception);
1334 prev_image=DestroyImage(prev_image);
1335 if (bgnd_image == (Image *) NULL)
1337 bgnd_image->dispose=disposals[i];
1338 if ( disposals[i] == DupDispose ) {
1339 bgnd_image->delay=0;
1340 bgnd_image->dispose=NoneDispose;
1343 curr=GetNextImageInList(curr);
1344 AppendImageToList(&optimized_image,bgnd_image);
1347 sans_exception=DestroyExceptionInfo(sans_exception);
1348 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1349 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1350 if (curr != (Image *) NULL)
1352 optimized_image=DestroyImageList(optimized_image);
1353 return((Image *) NULL);
1355 return(GetFirstImageInList(optimized_image));
1359 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1363 % O p t i m i z e I m a g e L a y e r s %
1367 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1369 % OptimizeImageLayers() compares each image the GIF disposed forms of the
1370 % previous image in the sequence. From this it attempts to select the
1371 % smallest cropped image to replace each frame, while preserving the results
1372 % of the GIF animation.
1374 % The format of the OptimizeImageLayers method is:
1376 % Image *OptimizeImageLayers(const Image *image,
1377 % ExceptionInfo *exception)
1379 % A description of each parameter follows:
1381 % o image: the image.
1383 % o exception: return any errors or warnings in this structure.
1386 MagickExport Image *OptimizeImageLayers(const Image *image,
1387 ExceptionInfo *exception)
1389 return(OptimizeLayerFrames(image,OptimizeImageLayer,exception));
1393 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1397 % O p t i m i z e P l u s I m a g e L a y e r s %
1401 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1403 % OptimizeImagePlusLayers() is exactly as OptimizeImageLayers(), but may
1404 % also add or even remove extra frames in the animation, if it improves
1405 % the total number of pixels in the resulting GIF animation.
1407 % The format of the OptimizePlusImageLayers method is:
1409 % Image *OptimizePlusImageLayers(const Image *image,
1410 % ExceptionInfo *exception)
1412 % A description of each parameter follows:
1414 % o image: the image.
1416 % o exception: return any errors or warnings in this structure.
1419 MagickExport Image *OptimizePlusImageLayers(const Image *image,
1420 ExceptionInfo *exception)
1422 return OptimizeLayerFrames(image, OptimizePlusLayer, exception);
1426 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1430 % 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 %
1434 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1436 % OptimizeImageTransparency() takes a frame optimized GIF animation, and
1437 % compares the overlayed pixels against the disposal image resulting from all
1438 % the previous frames in the animation. Any pixel that does not change the
1439 % disposal image (and thus does not effect the outcome of an overlay) is made
1442 % WARNING: This modifies the current images directly, rather than generate
1443 % a new image sequence.
1445 % The format of the OptimizeImageTransperency method is:
1447 % void OptimizeImageTransperency(Image *image,ExceptionInfo *exception)
1449 % A description of each parameter follows:
1451 % o image: the image sequence
1453 % o exception: return any errors or warnings in this structure.
1456 MagickExport void OptimizeImageTransparency(const Image *image,
1457 ExceptionInfo *exception)
1466 Run the image through the animation sequence
1468 assert(image != (Image *) NULL);
1469 assert(image->signature == MagickSignature);
1470 if (image->debug != MagickFalse)
1471 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1472 assert(exception != (ExceptionInfo *) NULL);
1473 assert(exception->signature == MagickSignature);
1474 next=GetFirstImageInList(image);
1475 dispose_image=CloneImage(next,next->page.width,next->page.height,
1476 MagickTrue,exception);
1477 if (dispose_image == (Image *) NULL)
1479 dispose_image->page=next->page;
1480 dispose_image->page.x=0;
1481 dispose_image->page.y=0;
1482 dispose_image->dispose=NoneDispose;
1483 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
1484 (void) SetImageBackgroundColor(dispose_image,exception);
1486 while ( next != (Image *) NULL )
1492 Overlay this frame's image over the previous disposal image
1494 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
1495 if (current_image == (Image *) NULL)
1497 dispose_image=DestroyImage(dispose_image);
1500 (void) CompositeImage(current_image,next->matte != MagickFalse ?
1501 OverCompositeOp : CopyCompositeOp, next,next->page.x,next->page.y,
1504 At this point the image would be displayed, for the delay period
1506 Work out the disposal of the previous image
1508 if (next->dispose == BackgroundDispose)
1513 bounds.width=next->columns;
1514 bounds.height=next->rows;
1517 bounds.width+=bounds.x;
1520 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
1521 bounds.width=current_image->columns-bounds.x;
1524 bounds.height+=bounds.y;
1527 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
1528 bounds.height=current_image->rows-bounds.y;
1529 ClearBounds(current_image, &bounds,exception);
1531 if (next->dispose != PreviousDispose)
1533 dispose_image=DestroyImage(dispose_image);
1534 dispose_image=current_image;
1537 current_image=DestroyImage(current_image);
1540 Optimize Transparency of the next frame (if present)
1542 next=GetNextImageInList(next);
1543 if ( next != (Image *) NULL ) {
1544 (void) CompositeImage(next, ChangeMaskCompositeOp,
1545 dispose_image, -(next->page.x), -(next->page.y), exception );
1548 dispose_image=DestroyImage(dispose_image);
1553 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1557 % R e m o v e D u p l i c a t e L a y e r s %
1561 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1563 % RemoveDuplicateLayers() removes any image that is exactly the same as the
1564 % next image in the given image list. Image size and virtual canvas offset
1565 % must also match, though not the virtual canvas size itself.
1567 % No check is made with regards to image disposal setting, though it is the
1568 % dispose setting of later image that is kept. Also any time delays are also
1569 % added together. As such coalesced image animations should still produce the
1570 % same result, though with duplicte frames merged into a single frame.
1572 % The format of the RemoveDuplicateLayers method is:
1574 % void RemoveDuplicateLayers(Image **image, ExceptionInfo *exception)
1576 % A description of each parameter follows:
1578 % o images: the image list
1580 % o exception: return any errors or warnings in this structure.
1583 MagickExport void RemoveDuplicateLayers(Image **images,
1584 ExceptionInfo *exception)
1593 assert((*images) != (const Image *) NULL);
1594 assert((*images)->signature == MagickSignature);
1595 if ((*images)->debug != MagickFalse)
1596 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1597 assert(exception != (ExceptionInfo *) NULL);
1598 assert(exception->signature == MagickSignature);
1600 curr=GetFirstImageInList(*images);
1601 for (; (next=GetNextImageInList(curr)) != (Image *) NULL; curr=next)
1603 if ( curr->columns != next->columns || curr->rows != next->rows
1604 || curr->page.x != next->page.x || curr->page.y != next->page.y )
1606 bounds=CompareImagesBounds(curr,next,CompareAnyLayer,exception);
1607 if ( bounds.x < 0 ) {
1609 the two images are the same, merge time delays and delete one.
1612 time = curr->delay*1000/curr->ticks_per_second;
1613 time += next->delay*1000/next->ticks_per_second;
1614 next->ticks_per_second = 100L;
1615 next->delay = time*curr->ticks_per_second/1000;
1616 next->iterations = curr->iterations;
1618 (void) DeleteImageFromList(images);
1621 *images = GetFirstImageInList(*images);
1625 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1629 % R e m o v e Z e r o D e l a y L a y e r s %
1633 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1635 % RemoveZeroDelayLayers() removes any image that as a zero delay time. Such
1636 % images generally represent intermediate or partial updates in GIF
1637 % animations used for file optimization. They are not ment to be displayed
1638 % to users of the animation. Viewable images in an animation should have a
1639 % time delay of 3 or more centi-seconds (hundredths of a second).
1641 % However if all the frames have a zero time delay, then either the animation
1642 % is as yet incomplete, or it is not a GIF animation. This a non-sensible
1643 % situation, so no image will be removed and a 'Zero Time Animation' warning
1644 % (exception) given.
1646 % No warning will be given if no image was removed because all images had an
1647 % appropriate non-zero time delay set.
1649 % Due to the special requirements of GIF disposal handling, GIF animations
1650 % should be coalesced first, before calling this function, though that is not
1653 % The format of the RemoveZeroDelayLayers method is:
1655 % void RemoveZeroDelayLayers(Image **image, ExceptionInfo *exception)
1657 % A description of each parameter follows:
1659 % o images: the image list
1661 % o exception: return any errors or warnings in this structure.
1664 MagickExport void RemoveZeroDelayLayers(Image **images,
1665 ExceptionInfo *exception)
1670 assert((*images) != (const Image *) NULL);
1671 assert((*images)->signature == MagickSignature);
1672 if ((*images)->debug != MagickFalse)
1673 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1674 assert(exception != (ExceptionInfo *) NULL);
1675 assert(exception->signature == MagickSignature);
1677 i=GetFirstImageInList(*images);
1678 for ( ; i != (Image *) NULL; i=GetNextImageInList(i))
1679 if ( i->delay != 0L ) break;
1680 if ( i == (Image *) NULL ) {
1681 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
1682 "ZeroTimeAnimation","`%s'",GetFirstImageInList(*images)->filename);
1685 i=GetFirstImageInList(*images);
1686 while ( i != (Image *) NULL )
1688 if ( i->delay == 0L ) {
1689 (void) DeleteImageFromList(&i);
1693 i=GetNextImageInList(i);
1695 *images=GetFirstImageInList(*images);
1699 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1703 % C o m p o s i t e L a y e r s %
1707 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1709 % CompositeLayers() compose first image sequence (source) over the second
1710 % image sequence (destination), using the given compose method and offsets.
1712 % The pointers to the image list does not have to be the start of that image
1713 % list, but may start somewhere in the middle. Each layer from the two image
1714 % lists are composted together until the end of one of the image lists is
1715 % reached. The offset of each composition is also adjusted to match the
1716 % virtual canvas offsets of each layer. As such the given offset is relative
1717 % to the virtual canvas, and not the actual image.
1719 % No GIF disposal handling is performed, so GIF animations should be
1720 % coalesced before use. However this not a requirement, and individual
1721 % layer images may have any size or offset, for special compositions.
1723 % Special case:- If one of the image sequences is just a single image that
1724 % image is repeatally composed with all the images in the other image list.
1725 % Either the source or destination lists may be the single image, for this
1728 % The destination list will be expanded as needed to match number of source
1729 % image overlaid (from current position to end of list).
1731 % The format of the CompositeLayers method is:
1733 % void CompositeLayers(Image *destination,
1734 % const CompositeOperator compose, Image *source,
1735 % const ssize_t x_offset, const ssize_t y_offset,
1736 % ExceptionInfo *exception);
1738 % A description of each parameter follows:
1740 % o destination: the destination images and results
1742 % o source: source image(s) for the layer composition
1744 % o compose, x_offset, y_offset: arguments passed on to CompositeImages()
1746 % o exception: return any errors or warnings in this structure.
1750 static inline void CompositeCanvas(Image *destination,
1751 const CompositeOperator compose,Image *source,ssize_t x_offset,
1752 ssize_t y_offset,ExceptionInfo *exception)
1754 x_offset+=source->page.x-destination->page.x;
1755 y_offset+=source->page.y-destination->page.y;
1756 (void) CompositeImage(destination,compose,source,x_offset,y_offset,
1760 MagickExport void CompositeLayers(Image *destination,
1761 const CompositeOperator compose, Image *source,const ssize_t x_offset,
1762 const ssize_t y_offset,ExceptionInfo *exception)
1764 assert(destination != (Image *) NULL);
1765 assert(destination->signature == MagickSignature);
1766 assert(source != (Image *) NULL);
1767 assert(source->signature == MagickSignature);
1768 assert(exception != (ExceptionInfo *) NULL);
1769 assert(exception->signature == MagickSignature);
1770 if (source->debug != MagickFalse || destination->debug != MagickFalse)
1771 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s - %s",
1772 source->filename, destination->filename);
1775 Overlay single source image over destation image/list
1777 if ( source->previous == (Image *) NULL && source->next == (Image *) NULL )
1778 while ( destination != (Image *) NULL )
1780 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1782 destination=GetNextImageInList(destination);
1786 Overlay source image list over single destination
1787 Generating multiple clones of destination image to match source list.
1788 Original Destination image becomes first image of generated list.
1789 As such the image list pointer does not require any change in caller.
1790 Some animation attributes however also needs coping in this case.
1792 else if ( destination->previous == (Image *) NULL &&
1793 destination->next == (Image *) NULL )
1795 Image *dest = CloneImage(destination,0,0,MagickTrue,exception);
1797 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1799 /* copy source image attributes ? */
1800 if ( source->next != (Image *) NULL )
1802 destination->delay = source->delay;
1803 destination->iterations = source->iterations;
1805 source=GetNextImageInList(source);
1807 while ( source != (Image *) NULL )
1809 AppendImageToList(&destination,
1810 CloneImage(dest,0,0,MagickTrue,exception));
1811 destination=GetLastImageInList(destination);
1813 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1815 destination->delay = source->delay;
1816 destination->iterations = source->iterations;
1817 source=GetNextImageInList(source);
1819 dest=DestroyImage(dest);
1823 Overlay a source image list over a destination image list
1824 until either list runs out of images. (Does not repeat)
1827 while ( source != (Image *) NULL && destination != (Image *) NULL )
1829 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1831 source=GetNextImageInList(source);
1832 destination=GetNextImageInList(destination);
1837 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1841 % M e r g e I m a g e L a y e r s %
1845 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1847 % MergeImageLayers() composes all the image layers from the current given
1848 % image onward to produce a single image of the merged layers.
1850 % The inital canvas's size depends on the given ImageLayerMethod, and is
1851 % initialized using the first images background color. The images
1852 % are then compositied onto that image in sequence using the given
1853 % composition that has been assigned to each individual image.
1855 % The format of the MergeImageLayers is:
1857 % Image *MergeImageLayers(const Image *image,
1858 % const ImageLayerMethod method, ExceptionInfo *exception)
1860 % A description of each parameter follows:
1862 % o image: the image list to be composited together
1864 % o method: the method of selecting the size of the initial canvas.
1866 % MergeLayer: Merge all layers onto a canvas just large enough
1867 % to hold all the actual images. The virtual canvas of the
1868 % first image is preserved but otherwise ignored.
1870 % FlattenLayer: Use the virtual canvas size of first image.
1871 % Images which fall outside this canvas is clipped.
1872 % This can be used to 'fill out' a given virtual canvas.
1874 % MosaicLayer: Start with the virtual canvas of the first image,
1875 % enlarging left and right edges to contain all images.
1876 % Images with negative offsets will be clipped.
1878 % TrimBoundsLayer: Determine the overall bounds of all the image
1879 % layers just as in "MergeLayer", then adjust the the canvas
1880 % and offsets to be relative to those bounds, without overlaying
1883 % WARNING: a new image is not returned, the original image
1884 % sequence page data is modified instead.
1886 % o exception: return any errors or warnings in this structure.
1889 MagickExport Image *MergeImageLayers(Image *image,const ImageLayerMethod method,
1890 ExceptionInfo *exception)
1892 #define MergeLayersTag "Merge/Layers"
1903 register const Image
1914 assert(image != (Image *) NULL);
1915 assert(image->signature == MagickSignature);
1916 if (image->debug != MagickFalse)
1917 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1918 assert(exception != (ExceptionInfo *) NULL);
1919 assert(exception->signature == MagickSignature);
1921 Determine canvas image size, and its virtual canvas size and offset
1924 width=image->columns;
1928 case TrimBoundsLayer:
1932 next=GetNextImageInList(image);
1933 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
1935 if (page.x > next->page.x)
1937 width+=page.x-next->page.x;
1938 page.x=next->page.x;
1940 if (page.y > next->page.y)
1942 height+=page.y-next->page.y;
1943 page.y=next->page.y;
1945 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns-page.x))
1946 width=(size_t) next->page.x+(ssize_t)next->columns-page.x;
1947 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows-page.y))
1948 height=(size_t) next->page.y+(ssize_t) next->rows-page.y;
1956 if (page.height > 0)
1966 if (page.height > 0)
1968 for (next=image; next != (Image *) NULL; next=GetNextImageInList(next))
1970 if (method == MosaicLayer)
1972 page.x=next->page.x;
1973 page.y=next->page.y;
1974 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns))
1975 width=(size_t) next->page.x+next->columns;
1976 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows))
1977 height=(size_t) next->page.y+next->rows;
1988 Set virtual canvas size if not defined.
1990 if (page.width == 0)
1991 page.width=page.x < 0 ? width : width+page.x;
1992 if (page.height == 0)
1993 page.height=page.y < 0 ? height : height+page.y;
1995 Handle "TrimBoundsLayer" method separately to normal 'layer merge'.
1997 if (method == TrimBoundsLayer)
1999 number_images=GetImageListLength(image);
2000 for (scene=0; scene < (ssize_t) number_images; scene++)
2002 image->page.x-=page.x;
2003 image->page.y-=page.y;
2004 image->page.width=width;
2005 image->page.height=height;
2006 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2008 if (proceed == MagickFalse)
2010 image=GetNextImageInList(image);
2011 if (image == (Image *) NULL)
2014 return((Image *) NULL);
2017 Create canvas size of width and height, and background color.
2019 canvas=CloneImage(image,width,height,MagickTrue,exception);
2020 if (canvas == (Image *) NULL)
2021 return((Image *) NULL);
2022 (void) SetImageBackgroundColor(canvas,exception);
2024 canvas->dispose=UndefinedDispose;
2026 Compose images onto canvas, with progress monitor
2028 number_images=GetImageListLength(image);
2029 for (scene=0; scene < (ssize_t) number_images; scene++)
2031 (void) CompositeImage(canvas,image->compose,image,image->page.x-
2032 canvas->page.x,image->page.y-canvas->page.y,exception);
2033 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2035 if (proceed == MagickFalse)
2037 image=GetNextImageInList(image);
2038 if (image == (Image *) NULL)