2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5 % L AAA Y Y EEEEE RRRR %
9 % LLLLL A A Y EEEEE R R %
11 % MagickCore Image Layering Methods %
19 % Copyright 1999-2013 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 for the
79 % previous frame in an animation sequence.
81 % Warning: no bounds checks are performed, except for the null or missed
82 % image, for images that don't change. in all other cases bound must fall
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->alpha_trait != BlendPixelTrait)
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.
146 % Warning: no bounds checks are performed, except for the null or missed
147 % image, for images that don't change. in all other cases bound must fall
152 % MagickBooleanType IsBoundsCleared(const Image *image1,
153 % const Image *image2,RectangleInfo bounds,ExceptionInfo *exception)
155 % A description of each parameter follows:
157 % o image1, image 2: the images to check for cleared pixels
159 % o bounds: the area to be clear within the imag image
161 % o exception: return any errors or warnings in this structure.
164 static MagickBooleanType IsBoundsCleared(const Image *image1,
165 const Image *image2,RectangleInfo *bounds,ExceptionInfo *exception)
167 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,exception);
182 q=GetVirtualPixels(image2,bounds->x,bounds->y+y,bounds->width,1,exception);
183 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
185 for (x=0; x < (ssize_t) bounds->width; x++)
187 if ((GetPixelAlpha(image1,p) <= (Quantum) (QuantumRange/2)) &&
188 (GetPixelAlpha(image1,q) > (Quantum) (QuantumRange/2)))
190 p+=GetPixelChannels(image1);
193 if (x < (ssize_t) bounds->width)
196 return(y < (ssize_t) bounds->height ? MagickTrue : MagickFalse);
200 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
204 % C o a l e s c e I m a g e s %
208 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
210 % CoalesceImages() composites a set of images while respecting any page
211 % offsets and disposal methods. GIF, MIFF, and MNG animation sequences
212 % typically start with an image background and each subsequent image
213 % varies in size and offset. A new image sequence is returned with all
214 % images the same size as the first images virtual canvas and composited
215 % with the next image in the sequence.
217 % The format of the CoalesceImages method is:
219 % Image *CoalesceImages(Image *image,ExceptionInfo *exception)
221 % A description of each parameter follows:
223 % o image: the image sequence.
225 % o exception: return any errors or warnings in this structure.
228 MagickExport Image *CoalesceImages(const Image *image,ExceptionInfo *exception)
242 Coalesce the image sequence.
244 assert(image != (Image *) NULL);
245 assert(image->signature == MagickSignature);
246 if (image->debug != MagickFalse)
247 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
248 assert(exception != (ExceptionInfo *) NULL);
249 assert(exception->signature == MagickSignature);
250 next=GetFirstImageInList(image);
252 if (bounds.width == 0)
254 bounds.width=next->columns;
256 bounds.width+=bounds.x;
258 if (bounds.height == 0)
260 bounds.height=next->rows;
262 bounds.height+=bounds.y;
266 coalesce_image=CloneImage(next,bounds.width,bounds.height,MagickTrue,
268 if (coalesce_image == (Image *) NULL)
269 return((Image *) NULL);
270 (void) SetImageBackgroundColor(coalesce_image,exception);
271 coalesce_image->alpha_trait=next->alpha_trait;
272 coalesce_image->page=bounds;
273 coalesce_image->dispose=NoneDispose;
275 Coalesce rest of the images.
277 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
278 (void) CompositeImage(coalesce_image,next,CopyCompositeOp,MagickTrue,
279 next->page.x,next->page.y,exception);
280 next=GetNextImageInList(next);
281 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
284 Determine the bounds that was overlaid in the previous image.
286 previous=GetPreviousImageInList(next);
287 bounds=previous->page;
288 bounds.width=previous->columns;
289 bounds.height=previous->rows;
292 bounds.width+=bounds.x;
295 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) coalesce_image->columns)
296 bounds.width=coalesce_image->columns-bounds.x;
299 bounds.height+=bounds.y;
302 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) coalesce_image->rows)
303 bounds.height=coalesce_image->rows-bounds.y;
305 Replace the dispose image with the new coalesced image.
307 if (GetPreviousImageInList(next)->dispose != PreviousDispose)
309 dispose_image=DestroyImage(dispose_image);
310 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
311 if (dispose_image == (Image *) NULL)
313 coalesce_image=DestroyImageList(coalesce_image);
314 return((Image *) NULL);
318 Clear the overlaid area of the coalesced bounds for background disposal
320 if (next->previous->dispose == BackgroundDispose)
321 ClearBounds(dispose_image,&bounds,exception);
323 Next image is the dispose image, overlaid with next frame in sequence.
325 coalesce_image->next=CloneImage(dispose_image,0,0,MagickTrue,exception);
326 coalesce_image->next->previous=coalesce_image;
327 previous=coalesce_image;
328 coalesce_image=GetNextImageInList(coalesce_image);
329 (void) CompositeImage(coalesce_image,next,
330 next->alpha_trait == BlendPixelTrait ? OverCompositeOp : CopyCompositeOp,
331 MagickTrue,next->page.x,next->page.y,exception);
332 (void) CloneImageProfiles(coalesce_image,next);
333 (void) CloneImageProperties(coalesce_image,next);
334 (void) CloneImageArtifacts(coalesce_image,next);
335 coalesce_image->page=previous->page;
337 If a pixel goes opaque to transparent, use background dispose.
339 if (IsBoundsCleared(previous,coalesce_image,&bounds,exception) != MagickFalse)
340 coalesce_image->dispose=BackgroundDispose;
342 coalesce_image->dispose=NoneDispose;
343 previous->dispose=coalesce_image->dispose;
345 dispose_image=DestroyImage(dispose_image);
346 return(GetFirstImageInList(coalesce_image));
350 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
354 % D i s p o s e I m a g e s %
358 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
360 % DisposeImages() returns the coalesced frames of a GIF animation as it would
361 % appear after the GIF dispose method of that frame has been applied. That is
362 % it returned the appearance of each frame before the next is overlaid.
364 % The format of the DisposeImages method is:
366 % Image *DisposeImages(Image *image,ExceptionInfo *exception)
368 % A description of each parameter follows:
370 % o images: the image sequence.
372 % o exception: return any errors or warnings in this structure.
375 MagickExport Image *DisposeImages(const Image *images,ExceptionInfo *exception)
389 Run the image through the animation sequence
391 assert(images != (Image *) NULL);
392 assert(images->signature == MagickSignature);
393 if (images->debug != MagickFalse)
394 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
395 assert(exception != (ExceptionInfo *) NULL);
396 assert(exception->signature == MagickSignature);
397 image=GetFirstImageInList(images);
398 dispose_image=CloneImage(image,image->page.width,image->page.height,
399 MagickTrue,exception);
400 if (dispose_image == (Image *) NULL)
401 return((Image *) NULL);
402 dispose_image->page=image->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 (next=image; image != (Image *) NULL; image=GetNextImageInList(image))
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,next,
425 next->alpha_trait == BlendPixelTrait ? OverCompositeOp : CopyCompositeOp,
426 MagickTrue,next->page.x,next->page.y,exception);
428 Handle Background dispose: image is displayed for the delay period.
430 if (next->dispose == BackgroundDispose)
433 bounds.width=next->columns;
434 bounds.height=next->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 (next->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,next);
477 (void) CloneImageProperties(dispose,next);
478 (void) CloneImageArtifacts(dispose,next);
481 dispose->dispose=next->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 LayerMethod 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 LayerMethod 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->alpha_trait == BlendPixelTrait) ? p->alpha : OpaqueAlpha;
534 o2 = (q->alpha_trait == BlendPixelTrait) ? q->alpha : OpaqueAlpha;
536 Pixel goes from opaque to transprency.
538 if (method == CompareClearLayer)
539 return((MagickBooleanType) ( (o1 <= ((double) QuantumRange/2.0)) &&
540 (o2 > ((double) QuantumRange/2.0)) ) );
542 Overlay would change first pixel by second.
544 if (method == CompareOverlayLayer)
546 if (o2 > ((double) QuantumRange/2.0))
548 return((MagickBooleanType) (IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
555 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
559 + C o m p a r e I m a g e B o u n d s %
563 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
565 % CompareImagesBounds() Given two images return the smallest rectangular area
566 % by which the two images differ, accourding to the given 'Compare...'
569 % This currently only used internally in this module, but may eventually
570 % be used by other modules.
572 % The format of the CompareImagesBounds method is:
574 % RectangleInfo *CompareImagesBounds(const LayerMethod method,
575 % const Image *image1, const Image *image2, ExceptionInfo *exception)
577 % A description of each parameter follows:
579 % o method: What differences to look for. Must be one of CompareAnyLayer,
580 % CompareClearLayer, CompareOverlayLayer.
582 % o image1, image2: the two images to compare.
584 % o exception: return any errors or warnings in this structure.
588 static RectangleInfo CompareImagesBounds(const Image *image1,
589 const Image *image2,const LayerMethod method,ExceptionInfo *exception)
598 register const Quantum
609 Set bounding box of the differences between images.
611 GetPixelInfo(image1,&pixel1);
612 GetPixelInfo(image2,&pixel2);
613 for (x=0; x < (ssize_t) image1->columns; x++)
615 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
616 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
617 if ((p == (const Quantum *) NULL) ||
618 (q == (Quantum *) NULL))
620 for (y=0; y < (ssize_t) image1->rows; y++)
622 GetPixelInfoPixel(image1,p,&pixel1);
623 GetPixelInfoPixel(image2,q,&pixel2);
624 if (ComparePixels(method,&pixel1,&pixel2))
626 p+=GetPixelChannels(image1);
629 if (y < (ssize_t) image1->rows)
632 if (x >= (ssize_t) image1->columns)
635 Images are identical, return a null image.
644 for (x=(ssize_t) image1->columns-1; x >= 0; x--)
646 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
647 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
648 if ((p == (const Quantum *) NULL) ||
649 (q == (Quantum *) NULL))
651 for (y=0; y < (ssize_t) image1->rows; y++)
653 GetPixelInfoPixel(image1,p,&pixel1);
654 GetPixelInfoPixel(image2,q,&pixel2);
655 if (ComparePixels(method,&pixel1,&pixel2))
657 p+=GetPixelChannels(image1);
660 if (y < (ssize_t) image1->rows)
663 bounds.width=(size_t) (x-bounds.x+1);
664 for (y=0; y < (ssize_t) image1->rows; y++)
666 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
667 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
668 if ((p == (const Quantum *) NULL) ||
669 (q == (Quantum *) NULL))
671 for (x=0; x < (ssize_t) image1->columns; x++)
673 GetPixelInfoPixel(image1,p,&pixel1);
674 GetPixelInfoPixel(image2,q,&pixel2);
675 if (ComparePixels(method,&pixel1,&pixel2))
677 p+=GetPixelChannels(image1);
680 if (x < (ssize_t) image1->columns)
684 for (y=(ssize_t) image1->rows-1; y >= 0; y--)
686 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
687 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
688 if ((p == (const Quantum *) NULL) ||
689 (q == (Quantum *) NULL))
691 for (x=0; x < (ssize_t) image1->columns; x++)
693 GetPixelInfoPixel(image1,p,&pixel1);
694 GetPixelInfoPixel(image2,q,&pixel2);
695 if (ComparePixels(method,&pixel1,&pixel2))
697 p+=GetPixelChannels(image1);
700 if (x < (ssize_t) image1->columns)
703 bounds.height=(size_t) (y-bounds.y+1);
708 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
712 % C o m p a r e I m a g e L a y e r s %
716 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
718 % CompareImagesLayers() compares each image with the next in a sequence and
719 % returns the minimum bounding region of all the pixel differences (of the
720 % LayerMethod specified) it discovers.
722 % Images do NOT have to be the same size, though it is best that all the
723 % images are 'coalesced' (images are all the same size, on a flattened
724 % canvas, so as to represent exactly how an specific frame should look).
726 % No GIF dispose methods are applied, so GIF animations must be coalesced
727 % before applying this image operator to find differences to them.
729 % The format of the CompareImagesLayers method is:
731 % Image *CompareImagesLayers(const Image *images,
732 % const LayerMethod method,ExceptionInfo *exception)
734 % A description of each parameter follows:
736 % o image: the image.
738 % o method: the layers type to compare images with. Must be one of...
739 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
741 % o exception: return any errors or warnings in this structure.
745 MagickExport Image *CompareImagesLayers(const Image *image,
746 const LayerMethod method, ExceptionInfo *exception)
762 assert(image != (const Image *) NULL);
763 assert(image->signature == MagickSignature);
764 if (image->debug != MagickFalse)
765 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
766 assert(exception != (ExceptionInfo *) NULL);
767 assert(exception->signature == MagickSignature);
768 assert((method == CompareAnyLayer) ||
769 (method == CompareClearLayer) ||
770 (method == CompareOverlayLayer));
772 Allocate bounds memory.
774 next=GetFirstImageInList(image);
775 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
776 GetImageListLength(next),sizeof(*bounds));
777 if (bounds == (RectangleInfo *) NULL)
778 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
780 Set up first comparision images.
782 image_a=CloneImage(next,next->page.width,next->page.height,
783 MagickTrue,exception);
784 if (image_a == (Image *) NULL)
786 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
787 return((Image *) NULL);
789 image_a->background_color.alpha=(Quantum) TransparentAlpha;
790 (void) SetImageBackgroundColor(image_a,exception);
791 image_a->page=next->page;
794 (void) CompositeImage(image_a,next,CopyCompositeOp,MagickTrue,next->page.x,
795 next->page.y,exception);
797 Compute the bounding box of changes for the later images
800 next=GetNextImageInList(next);
801 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
803 image_b=CloneImage(image_a,0,0,MagickTrue,exception);
804 if (image_b == (Image *) NULL)
806 image_a=DestroyImage(image_a);
807 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
808 return((Image *) NULL);
810 (void) CompositeImage(image_a,next,CopyCompositeOp,MagickTrue,next->page.x,
811 next->page.y,exception);
812 bounds[i]=CompareImagesBounds(image_b,image_a,method,exception);
813 image_b=DestroyImage(image_b);
816 image_a=DestroyImage(image_a);
818 Clone first image in sequence.
820 next=GetFirstImageInList(image);
821 layers=CloneImage(next,0,0,MagickTrue,exception);
822 if (layers == (Image *) NULL)
824 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
825 return((Image *) NULL);
828 Deconstruct the image sequence.
831 next=GetNextImageInList(next);
832 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
834 image_a=CloneImage(next,0,0,MagickTrue,exception);
835 if (image_a == (Image *) NULL)
837 image_b=CropImage(image_a,&bounds[i],exception);
838 image_a=DestroyImage(image_a);
839 if (image_b == (Image *) NULL)
841 AppendImageToList(&layers,image_b);
844 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
845 if (next != (Image *) NULL)
847 layers=DestroyImageList(layers);
848 return((Image *) NULL);
850 return(GetFirstImageInList(layers));
854 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
858 + O p t i m i z e L a y e r F r a m e s %
862 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
864 % OptimizeLayerFrames() takes a coalesced GIF animation, and compares each
865 % frame against the three different 'disposal' forms of the previous frame.
866 % From this it then attempts to select the smallest cropped image and
867 % disposal method needed to reproduce the resulting image.
869 % Note that this not easy, and may require the expansion of the bounds
870 % of previous frame, simply clear pixels for the next animation frame to
871 % transparency according to the selected dispose method.
873 % The format of the OptimizeLayerFrames method is:
875 % Image *OptimizeLayerFrames(const Image *image,
876 % const LayerMethod method, ExceptionInfo *exception)
878 % A description of each parameter follows:
880 % o image: the image.
882 % o method: the layers technique to optimize with. Must be one of...
883 % OptimizeImageLayer, or OptimizePlusLayer. The Plus form allows
884 % the addition of extra 'zero delay' frames to clear pixels from
885 % the previous frame, and the removal of frames that done change,
886 % merging the delay times together.
888 % o exception: return any errors or warnings in this structure.
892 Define a 'fake' dispose method where the frame is duplicated, (for
893 OptimizePlusLayer) with a extra zero time delay frame which does a
894 BackgroundDisposal to clear the pixels that need to be cleared.
896 #define DupDispose ((DisposeType)9)
898 Another 'fake' dispose method used to removed frames that don't change.
900 #define DelDispose ((DisposeType)8)
902 #define DEBUG_OPT_FRAME 0
904 static Image *OptimizeLayerFrames(const Image *image,
905 const LayerMethod method, ExceptionInfo *exception)
936 assert(image != (const Image *) NULL);
937 assert(image->signature == MagickSignature);
938 if (image->debug != MagickFalse)
939 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
940 assert(exception != (ExceptionInfo *) NULL);
941 assert(exception->signature == MagickSignature);
942 assert(method == OptimizeLayer ||
943 method == OptimizeImageLayer ||
944 method == OptimizePlusLayer);
946 Are we allowed to add/remove frames from animation?
948 add_frames=method == OptimizePlusLayer ? MagickTrue : MagickFalse;
950 Ensure all the images are the same size.
952 curr=GetFirstImageInList(image);
953 for (; curr != (Image *) NULL; curr=GetNextImageInList(curr))
955 if ((curr->columns != image->columns) || (curr->rows != image->rows))
956 ThrowImageException(OptionError,"ImagesAreNotTheSameSize");
958 FUTURE: also check that image is also fully coalesced (full page)
959 Though as long as they are the same size it should not matter.
963 Allocate memory (times 2 if we allow the use of frame duplications)
965 curr=GetFirstImageInList(image);
966 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
967 GetImageListLength(curr),(add_frames != MagickFalse ? 2UL : 1UL)*
969 if (bounds == (RectangleInfo *) NULL)
970 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
971 disposals=(DisposeType *) AcquireQuantumMemory((size_t)
972 GetImageListLength(image),(add_frames != MagickFalse ? 2UL : 1UL)*
974 if (disposals == (DisposeType *) NULL)
976 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
977 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
980 Initialise Previous Image as fully transparent
982 prev_image=CloneImage(curr,curr->page.width,curr->page.height,
983 MagickTrue,exception);
984 if (prev_image == (Image *) NULL)
986 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
987 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
988 return((Image *) NULL);
990 prev_image->page=curr->page; /* ERROR: <-- should not be need, but is! */
991 prev_image->page.x=0;
992 prev_image->page.y=0;
993 prev_image->dispose=NoneDispose;
995 prev_image->background_color.alpha=(Quantum) TransparentAlpha;
996 (void) SetImageBackgroundColor(prev_image,exception);
998 Figure out the area of overlay of the first frame
999 No pixel could be cleared as all pixels are already cleared.
1003 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
1005 disposals[0]=NoneDispose;
1006 bounds[0]=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
1008 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g\n\n",
1009 (double) bounds[i].width,(double) bounds[i].height,
1010 (double) bounds[i].x,(double) bounds[i].y );
1013 Compute the bounding box of changes for each pair of images.
1016 bgnd_image=(Image *)NULL;
1017 dup_image=(Image *)NULL;
1019 dup_bounds.height=0;
1022 curr=GetNextImageInList(curr);
1023 for ( ; curr != (const Image *) NULL; curr=GetNextImageInList(curr))
1026 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
1029 Assume none disposal is the best
1031 bounds[i]=CompareImagesBounds(curr->previous,curr,CompareAnyLayer,exception);
1032 cleared=IsBoundsCleared(curr->previous,curr,&bounds[i],exception);
1033 disposals[i-1]=NoneDispose;
1035 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g%s%s\n",
1036 (double) bounds[i].width,(double) bounds[i].height,
1037 (double) bounds[i].x,(double) bounds[i].y,
1038 bounds[i].x < 0?" (unchanged)":"",
1039 cleared?" (pixels cleared)":"");
1041 if ( bounds[i].x < 0 ) {
1043 Image frame is exactly the same as the previous frame!
1044 If not adding frames leave it to be cropped down to a null image.
1045 Otherwise mark previous image for deleted, transfering its crop bounds
1046 to the current image.
1048 if ( add_frames && i>=2 ) {
1049 disposals[i-1]=DelDispose;
1050 disposals[i]=NoneDispose;
1051 bounds[i]=bounds[i-1];
1059 Compare a none disposal against a previous disposal
1061 try_bounds=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
1062 try_cleared=IsBoundsCleared(prev_image,curr,&try_bounds,exception);
1064 (void) FormatLocaleFile(stderr, "test_prev: %.20gx%.20g%+.20g%+.20g%s\n",
1065 (double) try_bounds.width,(double) try_bounds.height,
1066 (double) try_bounds.x,(double) try_bounds.y,
1067 try_cleared?" (pixels were cleared)":"");
1069 if ( (!try_cleared && cleared ) ||
1070 try_bounds.width * try_bounds.height
1071 < bounds[i].width * bounds[i].height )
1073 cleared=try_cleared;
1074 bounds[i]=try_bounds;
1075 disposals[i-1]=PreviousDispose;
1077 (void) FormatLocaleFile(stderr, "previous: accepted\n");
1079 (void) FormatLocaleFile(stderr, "previous: rejected\n");
1084 If we are allowed lets try a complex frame duplication.
1085 It is useless if the previous image already clears pixels correctly.
1086 This method will always clear all the pixels that need to be cleared.
1088 dup_bounds.width=dup_bounds.height=0; /* no dup, no pixel added */
1091 dup_image=CloneImage(curr->previous,curr->previous->page.width,
1092 curr->previous->page.height,MagickTrue,exception);
1093 if (dup_image == (Image *) NULL)
1095 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1096 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1097 prev_image=DestroyImage(prev_image);
1098 return((Image *) NULL);
1100 dup_bounds=CompareImagesBounds(dup_image,curr,CompareClearLayer,exception);
1101 ClearBounds(dup_image,&dup_bounds,exception);
1102 try_bounds=CompareImagesBounds(dup_image,curr,CompareAnyLayer,exception);
1104 dup_bounds.width*dup_bounds.height
1105 +try_bounds.width*try_bounds.height
1106 < bounds[i].width * bounds[i].height )
1108 cleared=MagickFalse;
1109 bounds[i]=try_bounds;
1110 disposals[i-1]=DupDispose;
1111 /* to be finalised later, if found to be optimial */
1114 dup_bounds.width=dup_bounds.height=0;
1117 Now compare against a simple background disposal
1119 bgnd_image=CloneImage(curr->previous,curr->previous->page.width,
1120 curr->previous->page.height,MagickTrue,exception);
1121 if (bgnd_image == (Image *) NULL)
1123 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1124 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1125 prev_image=DestroyImage(prev_image);
1126 if ( dup_image != (Image *) NULL)
1127 dup_image=DestroyImage(dup_image);
1128 return((Image *) NULL);
1130 bgnd_bounds=bounds[i-1]; /* interum bounds of the previous image */
1131 ClearBounds(bgnd_image,&bgnd_bounds,exception);
1132 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
1133 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1135 (void) FormatLocaleFile(stderr, "background: %s\n",
1136 try_cleared?"(pixels cleared)":"");
1141 Straight background disposal failed to clear pixels needed!
1142 Lets try expanding the disposal area of the previous frame, to
1143 include the pixels that are cleared. This guaranteed
1144 to work, though may not be the most optimized solution.
1146 try_bounds=CompareImagesBounds(curr->previous,curr,CompareClearLayer,exception);
1148 (void) FormatLocaleFile(stderr, "expand_clear: %.20gx%.20g%+.20g%+.20g%s\n",
1149 (double) try_bounds.width,(double) try_bounds.height,
1150 (double) try_bounds.x,(double) try_bounds.y,
1151 try_bounds.x<0?" (no expand nessary)":"");
1153 if ( bgnd_bounds.x < 0 )
1154 bgnd_bounds = try_bounds;
1158 (void) FormatLocaleFile(stderr, "expand_bgnd: %.20gx%.20g%+.20g%+.20g\n",
1159 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1160 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1162 if ( try_bounds.x < bgnd_bounds.x )
1164 bgnd_bounds.width+= bgnd_bounds.x-try_bounds.x;
1165 if ( bgnd_bounds.width < try_bounds.width )
1166 bgnd_bounds.width = try_bounds.width;
1167 bgnd_bounds.x = try_bounds.x;
1171 try_bounds.width += try_bounds.x - bgnd_bounds.x;
1172 if ( bgnd_bounds.width < try_bounds.width )
1173 bgnd_bounds.width = try_bounds.width;
1175 if ( try_bounds.y < bgnd_bounds.y )
1177 bgnd_bounds.height += bgnd_bounds.y - try_bounds.y;
1178 if ( bgnd_bounds.height < try_bounds.height )
1179 bgnd_bounds.height = try_bounds.height;
1180 bgnd_bounds.y = try_bounds.y;
1184 try_bounds.height += try_bounds.y - bgnd_bounds.y;
1185 if ( bgnd_bounds.height < try_bounds.height )
1186 bgnd_bounds.height = try_bounds.height;
1189 (void) FormatLocaleFile(stderr, " to : %.20gx%.20g%+.20g%+.20g\n",
1190 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1191 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1194 ClearBounds(bgnd_image,&bgnd_bounds,exception);
1196 /* Something strange is happening with a specific animation
1197 * CompareAnyLayers (normal method) and CompareClearLayers returns the whole
1198 * image, which is not posibly correct! As verified by previous tests.
1199 * Something changed beyond the bgnd_bounds clearing. But without being able
1200 * to see, or writet he image at this point it is hard to tell what is wrong!
1201 * Only CompareOverlay seemed to return something sensible.
1203 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareClearLayer,exception);
1204 (void) FormatLocaleFile(stderr, "expand_ctst: %.20gx%.20g%+.20g%+.20g\n",
1205 (double) try_bounds.width,(double) try_bounds.height,
1206 (double) try_bounds.x,(double) try_bounds.y );
1207 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
1208 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1209 (void) FormatLocaleFile(stderr, "expand_any : %.20gx%.20g%+.20g%+.20g%s\n",
1210 (double) try_bounds.width,(double) try_bounds.height,
1211 (double) try_bounds.x,(double) try_bounds.y,
1212 try_cleared?" (pixels cleared)":"");
1214 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareOverlayLayer,exception);
1216 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1217 (void) FormatLocaleFile(stderr, "expand_test: %.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)":"");
1224 Test if this background dispose is smaller than any of the
1225 other methods we tryed before this (including duplicated frame)
1228 bgnd_bounds.width*bgnd_bounds.height
1229 +try_bounds.width*try_bounds.height
1230 < bounds[i-1].width*bounds[i-1].height
1231 +dup_bounds.width*dup_bounds.height
1232 +bounds[i].width*bounds[i].height )
1234 cleared=MagickFalse;
1235 bounds[i-1]=bgnd_bounds;
1236 bounds[i]=try_bounds;
1237 if ( disposals[i-1] == DupDispose )
1238 dup_image=DestroyImage(dup_image);
1239 disposals[i-1]=BackgroundDispose;
1241 (void) FormatLocaleFile(stderr, "expand_bgnd: accepted\n");
1243 (void) FormatLocaleFile(stderr, "expand_bgnd: reject\n");
1248 Finalise choice of dispose, set new prev_image,
1249 and junk any extra images as appropriate,
1251 if ( disposals[i-1] == DupDispose )
1253 if (bgnd_image != (Image *) NULL)
1254 bgnd_image=DestroyImage(bgnd_image);
1255 prev_image=DestroyImage(prev_image);
1256 prev_image=dup_image, dup_image=(Image *) NULL;
1257 bounds[i+1]=bounds[i];
1258 bounds[i]=dup_bounds;
1259 disposals[i-1]=DupDispose;
1260 disposals[i]=BackgroundDispose;
1265 if ( dup_image != (Image *) NULL)
1266 dup_image=DestroyImage(dup_image);
1267 if ( disposals[i-1] != PreviousDispose )
1268 prev_image=DestroyImage(prev_image);
1269 if ( disposals[i-1] == BackgroundDispose )
1270 prev_image=bgnd_image, bgnd_image=(Image *)NULL;
1271 if (bgnd_image != (Image *) NULL)
1272 bgnd_image=DestroyImage(bgnd_image);
1273 if ( disposals[i-1] == NoneDispose )
1275 prev_image=CloneImage(curr->previous,curr->previous->page.width,
1276 curr->previous->page.height,MagickTrue,exception);
1277 if (prev_image == (Image *) NULL)
1279 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1280 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1281 return((Image *) NULL);
1286 assert(prev_image != (Image *) NULL);
1287 disposals[i]=disposals[i-1];
1289 (void) FormatLocaleFile(stderr, "final %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1291 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i-1]),
1292 (double) bounds[i-1].width, (double) bounds[i-1].height,
1293 (double) bounds[i-1].x, (double) bounds[i-1].y );
1296 (void) FormatLocaleFile(stderr, "interum %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1298 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i]),
1299 (double) bounds[i].width, (double) bounds[i].height,
1300 (double) bounds[i].x, (double) bounds[i].y );
1301 (void) FormatLocaleFile(stderr, "\n");
1305 prev_image=DestroyImage(prev_image);
1307 Optimize all images in sequence.
1309 sans_exception=AcquireExceptionInfo();
1311 curr=GetFirstImageInList(image);
1312 optimized_image=NewImageList();
1313 while ( curr != (const Image *) NULL )
1315 prev_image=CloneImage(curr,0,0,MagickTrue,exception);
1316 if (prev_image == (Image *) NULL)
1318 if ( disposals[i] == DelDispose ) {
1320 while ( disposals[i] == DelDispose ) {
1321 time += curr->delay*1000/curr->ticks_per_second;
1322 curr=GetNextImageInList(curr);
1325 time += curr->delay*1000/curr->ticks_per_second;
1326 prev_image->ticks_per_second = 100L;
1327 prev_image->delay = time*prev_image->ticks_per_second/1000;
1329 bgnd_image=CropImage(prev_image,&bounds[i],sans_exception);
1330 prev_image=DestroyImage(prev_image);
1331 if (bgnd_image == (Image *) NULL)
1333 bgnd_image->dispose=disposals[i];
1334 if ( disposals[i] == DupDispose ) {
1335 bgnd_image->delay=0;
1336 bgnd_image->dispose=NoneDispose;
1339 curr=GetNextImageInList(curr);
1340 AppendImageToList(&optimized_image,bgnd_image);
1343 sans_exception=DestroyExceptionInfo(sans_exception);
1344 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1345 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1346 if (curr != (Image *) NULL)
1348 optimized_image=DestroyImageList(optimized_image);
1349 return((Image *) NULL);
1351 return(GetFirstImageInList(optimized_image));
1355 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1359 % O p t i m i z e I m a g e L a y e r s %
1363 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1365 % OptimizeImageLayers() compares each image the GIF disposed forms of the
1366 % previous image in the sequence. From this it attempts to select the
1367 % smallest cropped image to replace each frame, while preserving the results
1368 % of the GIF animation.
1370 % The format of the OptimizeImageLayers method is:
1372 % Image *OptimizeImageLayers(const Image *image,
1373 % ExceptionInfo *exception)
1375 % A description of each parameter follows:
1377 % o image: the image.
1379 % o exception: return any errors or warnings in this structure.
1382 MagickExport Image *OptimizeImageLayers(const Image *image,
1383 ExceptionInfo *exception)
1385 return(OptimizeLayerFrames(image,OptimizeImageLayer,exception));
1389 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1393 % O p t i m i z e P l u s I m a g e L a y e r s %
1397 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1399 % OptimizeImagePlusLayers() is exactly as OptimizeImageLayers(), but may
1400 % also add or even remove extra frames in the animation, if it improves
1401 % the total number of pixels in the resulting GIF animation.
1403 % The format of the OptimizePlusImageLayers method is:
1405 % Image *OptimizePlusImageLayers(const Image *image,
1406 % ExceptionInfo *exception)
1408 % A description of each parameter follows:
1410 % o image: the image.
1412 % o exception: return any errors or warnings in this structure.
1415 MagickExport Image *OptimizePlusImageLayers(const Image *image,
1416 ExceptionInfo *exception)
1418 return OptimizeLayerFrames(image, OptimizePlusLayer, exception);
1422 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1426 % 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 %
1430 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1432 % OptimizeImageTransparency() takes a frame optimized GIF animation, and
1433 % compares the overlayed pixels against the disposal image resulting from all
1434 % the previous frames in the animation. Any pixel that does not change the
1435 % disposal image (and thus does not effect the outcome of an overlay) is made
1438 % WARNING: This modifies the current images directly, rather than generate
1439 % a new image sequence.
1441 % The format of the OptimizeImageTransperency method is:
1443 % void OptimizeImageTransperency(Image *image,ExceptionInfo *exception)
1445 % A description of each parameter follows:
1447 % o image: the image sequence
1449 % o exception: return any errors or warnings in this structure.
1452 MagickExport void OptimizeImageTransparency(const Image *image,
1453 ExceptionInfo *exception)
1462 Run the image through the animation sequence
1464 assert(image != (Image *) NULL);
1465 assert(image->signature == MagickSignature);
1466 if (image->debug != MagickFalse)
1467 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1468 assert(exception != (ExceptionInfo *) NULL);
1469 assert(exception->signature == MagickSignature);
1470 next=GetFirstImageInList(image);
1471 dispose_image=CloneImage(next,next->page.width,next->page.height,
1472 MagickTrue,exception);
1473 if (dispose_image == (Image *) NULL)
1475 dispose_image->page=next->page;
1476 dispose_image->page.x=0;
1477 dispose_image->page.y=0;
1478 dispose_image->dispose=NoneDispose;
1479 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
1480 (void) SetImageBackgroundColor(dispose_image,exception);
1482 while ( next != (Image *) NULL )
1488 Overlay this frame's image over the previous disposal image
1490 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
1491 if (current_image == (Image *) NULL)
1493 dispose_image=DestroyImage(dispose_image);
1496 (void) CompositeImage(current_image,next,next->alpha_trait == BlendPixelTrait ?
1497 OverCompositeOp : CopyCompositeOp,MagickTrue,next->page.x,next->page.y,
1500 At this point the image would be displayed, for the delay period
1502 Work out the disposal of the previous image
1504 if (next->dispose == BackgroundDispose)
1509 bounds.width=next->columns;
1510 bounds.height=next->rows;
1513 bounds.width+=bounds.x;
1516 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
1517 bounds.width=current_image->columns-bounds.x;
1520 bounds.height+=bounds.y;
1523 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
1524 bounds.height=current_image->rows-bounds.y;
1525 ClearBounds(current_image, &bounds,exception);
1527 if (next->dispose != PreviousDispose)
1529 dispose_image=DestroyImage(dispose_image);
1530 dispose_image=current_image;
1533 current_image=DestroyImage(current_image);
1536 Optimize Transparency of the next frame (if present)
1538 next=GetNextImageInList(next);
1539 if (next != (Image *) NULL) {
1540 (void) CompositeImage(next,dispose_image,ChangeMaskCompositeOp,
1541 MagickTrue,-(next->page.x),-(next->page.y),exception);
1544 dispose_image=DestroyImage(dispose_image);
1549 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1553 % R e m o v e D u p l i c a t e L a y e r s %
1557 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1559 % RemoveDuplicateLayers() removes any image that is exactly the same as the
1560 % next image in the given image list. Image size and virtual canvas offset
1561 % must also match, though not the virtual canvas size itself.
1563 % No check is made with regards to image disposal setting, though it is the
1564 % dispose setting of later image that is kept. Also any time delays are also
1565 % added together. As such coalesced image animations should still produce the
1566 % same result, though with duplicte frames merged into a single frame.
1568 % The format of the RemoveDuplicateLayers method is:
1570 % void RemoveDuplicateLayers(Image **image, ExceptionInfo *exception)
1572 % A description of each parameter follows:
1574 % o images: the image list
1576 % o exception: return any errors or warnings in this structure.
1579 MagickExport void RemoveDuplicateLayers(Image **images,
1580 ExceptionInfo *exception)
1589 assert((*images) != (const Image *) NULL);
1590 assert((*images)->signature == MagickSignature);
1591 if ((*images)->debug != MagickFalse)
1592 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1593 assert(exception != (ExceptionInfo *) NULL);
1594 assert(exception->signature == MagickSignature);
1596 curr=GetFirstImageInList(*images);
1597 for (; (next=GetNextImageInList(curr)) != (Image *) NULL; curr=next)
1599 if ( curr->columns != next->columns || curr->rows != next->rows
1600 || curr->page.x != next->page.x || curr->page.y != next->page.y )
1602 bounds=CompareImagesBounds(curr,next,CompareAnyLayer,exception);
1603 if ( bounds.x < 0 ) {
1605 the two images are the same, merge time delays and delete one.
1608 time = curr->delay*1000/curr->ticks_per_second;
1609 time += next->delay*1000/next->ticks_per_second;
1610 next->ticks_per_second = 100L;
1611 next->delay = time*curr->ticks_per_second/1000;
1612 next->iterations = curr->iterations;
1614 (void) DeleteImageFromList(images);
1617 *images = GetFirstImageInList(*images);
1621 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1625 % R e m o v e Z e r o D e l a y L a y e r s %
1629 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1631 % RemoveZeroDelayLayers() removes any image that as a zero delay time. Such
1632 % images generally represent intermediate or partial updates in GIF
1633 % animations used for file optimization. They are not ment to be displayed
1634 % to users of the animation. Viewable images in an animation should have a
1635 % time delay of 3 or more centi-seconds (hundredths of a second).
1637 % However if all the frames have a zero time delay, then either the animation
1638 % is as yet incomplete, or it is not a GIF animation. This a non-sensible
1639 % situation, so no image will be removed and a 'Zero Time Animation' warning
1640 % (exception) given.
1642 % No warning will be given if no image was removed because all images had an
1643 % appropriate non-zero time delay set.
1645 % Due to the special requirements of GIF disposal handling, GIF animations
1646 % should be coalesced first, before calling this function, though that is not
1649 % The format of the RemoveZeroDelayLayers method is:
1651 % void RemoveZeroDelayLayers(Image **image, ExceptionInfo *exception)
1653 % A description of each parameter follows:
1655 % o images: the image list
1657 % o exception: return any errors or warnings in this structure.
1660 MagickExport void RemoveZeroDelayLayers(Image **images,
1661 ExceptionInfo *exception)
1666 assert((*images) != (const Image *) NULL);
1667 assert((*images)->signature == MagickSignature);
1668 if ((*images)->debug != MagickFalse)
1669 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1670 assert(exception != (ExceptionInfo *) NULL);
1671 assert(exception->signature == MagickSignature);
1673 i=GetFirstImageInList(*images);
1674 for ( ; i != (Image *) NULL; i=GetNextImageInList(i))
1675 if ( i->delay != 0L ) break;
1676 if ( i == (Image *) NULL ) {
1677 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
1678 "ZeroTimeAnimation","`%s'",GetFirstImageInList(*images)->filename);
1681 i=GetFirstImageInList(*images);
1682 while ( i != (Image *) NULL )
1684 if ( i->delay == 0L ) {
1685 (void) DeleteImageFromList(&i);
1689 i=GetNextImageInList(i);
1691 *images=GetFirstImageInList(*images);
1695 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1699 % C o m p o s i t e L a y e r s %
1703 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1705 % CompositeLayers() compose the source image sequence over the destination
1706 % image sequence, starting with the current image in both lists.
1708 % Each layer from the two image lists are composted together until the end of
1709 % one of the image lists is reached. The offset of each composition is also
1710 % adjusted to match the virtual canvas offsets of each layer. As such the
1711 % given offset is relative to the virtual canvas, and not the actual image.
1713 % Composition uses given x and y offsets, as the 'origin' location of the
1714 % source images virtual canvas (not the real image) allowing you to compose a
1715 % list of 'layer images' into the destiantioni images. This makes it well
1716 % sutiable for directly composing 'Clears Frame Animations' or 'Coaleased
1717 % Animations' onto a static or other 'Coaleased Animation' destination image
1718 % list. GIF disposal handling is not looked at.
1720 % Special case:- If one of the image sequences is the last image (just a
1721 % single image remaining), that image is repeatally composed with all the
1722 % images in the other image list. Either the source or destination lists may
1723 % be the single image, for this situation.
1725 % In the case of a single destination image (or last image given), that image
1726 % will ve cloned to match the number of images remaining in the source image
1729 % This is equivelent to the "-layer Composite" Shell API operator.
1732 % The format of the CompositeLayers method is:
1734 % void CompositeLayers(Image *destination, const CompositeOperator
1735 % compose, Image *source, 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,source,compose,MagickTrue,x_offset,
1757 y_offset,exception);
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->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 Multiple clones of destination image are created 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->next == (Image *) NULL )
1794 Image *dest = CloneImage(destination,0,0,MagickTrue,exception);
1796 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1798 /* copy source image attributes ? */
1799 if ( source->next != (Image *) NULL )
1801 destination->delay = source->delay;
1802 destination->iterations = source->iterations;
1804 source=GetNextImageInList(source);
1806 while ( source != (Image *) NULL )
1808 AppendImageToList(&destination,
1809 CloneImage(dest,0,0,MagickTrue,exception));
1810 destination=GetLastImageInList(destination);
1812 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1814 destination->delay = source->delay;
1815 destination->iterations = source->iterations;
1816 source=GetNextImageInList(source);
1818 dest=DestroyImage(dest);
1822 Overlay a source image list over a destination image list
1823 until either list runs out of images. (Does not repeat)
1826 while ( source != (Image *) NULL && destination != (Image *) NULL )
1828 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1830 source=GetNextImageInList(source);
1831 destination=GetNextImageInList(destination);
1836 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1840 % M e r g e I m a g e L a y e r s %
1844 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1846 % MergeImageLayers() composes all the image layers from the current given
1847 % image onward to produce a single image of the merged layers.
1849 % The inital canvas's size depends on the given LayerMethod, and is
1850 % initialized using the first images background color. The images
1851 % are then compositied onto that image in sequence using the given
1852 % composition that has been assigned to each individual image.
1854 % The format of the MergeImageLayers is:
1856 % Image *MergeImageLayers(const Image *image,
1857 % const LayerMethod method, ExceptionInfo *exception)
1859 % A description of each parameter follows:
1861 % o image: the image list to be composited together
1863 % o method: the method of selecting the size of the initial canvas.
1865 % MergeLayer: Merge all layers onto a canvas just large enough
1866 % to hold all the actual images. The virtual canvas of the
1867 % first image is preserved but otherwise ignored.
1869 % FlattenLayer: Use the virtual canvas size of first image.
1870 % Images which fall outside this canvas is clipped.
1871 % This can be used to 'fill out' a given virtual canvas.
1873 % MosaicLayer: Start with the virtual canvas of the first image,
1874 % enlarging left and right edges to contain all images.
1875 % Images with negative offsets will be clipped.
1877 % TrimBoundsLayer: Determine the overall bounds of all the image
1878 % layers just as in "MergeLayer", then adjust the the canvas
1879 % and offsets to be relative to those bounds, without overlaying
1882 % WARNING: a new image is not returned, the original image
1883 % sequence page data is modified instead.
1885 % o exception: return any errors or warnings in this structure.
1888 MagickExport Image *MergeImageLayers(Image *image,const LayerMethod method,
1889 ExceptionInfo *exception)
1891 #define MergeLayersTag "Merge/Layers"
1902 register const Image
1913 assert(image != (Image *) NULL);
1914 assert(image->signature == MagickSignature);
1915 if (image->debug != MagickFalse)
1916 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1917 assert(exception != (ExceptionInfo *) NULL);
1918 assert(exception->signature == MagickSignature);
1920 Determine canvas image size, and its virtual canvas size and offset
1923 width=image->columns;
1927 case TrimBoundsLayer:
1931 next=GetNextImageInList(image);
1932 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
1934 if (page.x > next->page.x)
1936 width+=page.x-next->page.x;
1937 page.x=next->page.x;
1939 if (page.y > next->page.y)
1941 height+=page.y-next->page.y;
1942 page.y=next->page.y;
1944 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns-page.x))
1945 width=(size_t) next->page.x+(ssize_t)next->columns-page.x;
1946 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows-page.y))
1947 height=(size_t) next->page.y+(ssize_t) next->rows-page.y;
1955 if (page.height > 0)
1965 if (page.height > 0)
1967 for (next=image; next != (Image *) NULL; next=GetNextImageInList(next))
1969 if (method == MosaicLayer)
1971 page.x=next->page.x;
1972 page.y=next->page.y;
1973 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns))
1974 width=(size_t) next->page.x+next->columns;
1975 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows))
1976 height=(size_t) next->page.y+next->rows;
1987 Set virtual canvas size if not defined.
1989 if (page.width == 0)
1990 page.width=page.x < 0 ? width : width+page.x;
1991 if (page.height == 0)
1992 page.height=page.y < 0 ? height : height+page.y;
1994 Handle "TrimBoundsLayer" method separately to normal 'layer merge'.
1996 if (method == TrimBoundsLayer)
1998 number_images=GetImageListLength(image);
1999 for (scene=0; scene < (ssize_t) number_images; scene++)
2001 image->page.x-=page.x;
2002 image->page.y-=page.y;
2003 image->page.width=width;
2004 image->page.height=height;
2005 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2007 if (proceed == MagickFalse)
2009 image=GetNextImageInList(image);
2010 if (image == (Image *) NULL)
2013 return((Image *) NULL);
2016 Create canvas size of width and height, and background color.
2018 canvas=CloneImage(image,width,height,MagickTrue,exception);
2019 if (canvas == (Image *) NULL)
2020 return((Image *) NULL);
2021 (void) SetImageBackgroundColor(canvas,exception);
2023 canvas->dispose=UndefinedDispose;
2025 Compose images onto canvas, with progress monitor
2027 number_images=GetImageListLength(image);
2028 for (scene=0; scene < (ssize_t) number_images; scene++)
2030 (void) CompositeImage(canvas,image,image->compose,MagickTrue,image->page.x-
2031 canvas->page.x,image->page.y-canvas->page.y,exception);
2032 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2034 if (proceed == MagickFalse)
2036 image=GetNextImageInList(image);
2037 if (image == (Image *) NULL)