2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5 % L AAA Y Y EEEEE RRRR %
9 % LLLLL A A Y EEEEE R R %
11 % MagickCore Image Layering Methods %
19 % Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
20 % dedicated to making software imaging solutions freely available. %
22 % You may not use this file except in compliance with the License. You may %
23 % obtain a copy of the License at %
25 % http://www.imagemagick.org/script/license.php %
27 % Unless required by applicable law or agreed to in writing, software %
28 % distributed under the License is distributed on an "AS IS" BASIS, %
29 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
30 % See the License for the specific language governing permissions and %
31 % limitations under the License. %
33 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
40 #include "MagickCore/studio.h"
41 #include "MagickCore/artifact.h"
42 #include "MagickCore/cache.h"
43 #include "MagickCore/color.h"
44 #include "MagickCore/color-private.h"
45 #include "MagickCore/composite.h"
46 #include "MagickCore/effect.h"
47 #include "MagickCore/exception.h"
48 #include "MagickCore/exception-private.h"
49 #include "MagickCore/geometry.h"
50 #include "MagickCore/image.h"
51 #include "MagickCore/layer.h"
52 #include "MagickCore/list.h"
53 #include "MagickCore/memory_.h"
54 #include "MagickCore/monitor.h"
55 #include "MagickCore/monitor-private.h"
56 #include "MagickCore/option.h"
57 #include "MagickCore/pixel-accessor.h"
58 #include "MagickCore/property.h"
59 #include "MagickCore/profile.h"
60 #include "MagickCore/resource_.h"
61 #include "MagickCore/resize.h"
62 #include "MagickCore/statistic.h"
63 #include "MagickCore/string_.h"
64 #include "MagickCore/transform.h"
67 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
71 + C l e a r B o u n d s %
75 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
77 % ClearBounds() Clear the area specified by the bounds in an image to
78 % transparency. This typically used to handle Background Disposal
79 % for the previous frame in an animation sequence.
81 % WARNING: no bounds checks are performed, except for the null or
82 % missed image, for images that don't change. in all other cases
83 % bound must fall within the image.
87 % void ClearBounds(Image *image,RectangleInfo *bounds)
89 % A description of each parameter follows:
91 % o image: the image to had the area cleared in
93 % o bounds: the area to be clear within the imag image
96 static void ClearBounds(Image *image,RectangleInfo *bounds)
106 exception=(&image->exception);
107 if (image->matte == MagickFalse)
108 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
109 for (y=0; y < (ssize_t) bounds->height; y++)
117 q=GetAuthenticPixels(image,bounds->x,bounds->y+y,bounds->width,1,exception);
118 if (q == (Quantum *) NULL)
120 for (x=0; x < (ssize_t) bounds->width; x++)
122 SetPixelAlpha(image,TransparentAlpha,q);
123 q+=GetPixelChannels(image);
125 if (SyncAuthenticPixels(image,exception) == MagickFalse)
131 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
135 + I s B o u n d s C l e a r e d %
139 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
141 % IsBoundsCleared() tests whether any pixel in the bounds given, gets cleared
142 % when going from the first image to the second image. This typically used
143 % to check if a proposed disposal method will work successfully to generate
144 % the second frame image from the first disposed form of the previous frame.
148 % MagickBooleanType IsBoundsCleared(const Image *image1,
149 % const Image *image2,RectangleInfo bounds,ExceptionInfo *exception)
151 % A description of each parameter follows:
153 % o image1, image 2: the images to check for cleared pixels
155 % o bounds: the area to be clear within the imag image
157 % o exception: return any errors or warnings in this structure.
159 % WARNING: no bounds checks are performed, except for the null or
160 % missed image, for images that don't change. in all other cases
161 % bound must fall within the image.
164 static MagickBooleanType IsBoundsCleared(const Image *image1,
165 const Image *image2,RectangleInfo *bounds,ExceptionInfo *exception)
170 register const Quantum
179 for (y=0; y < (ssize_t) bounds->height; y++)
181 p=GetVirtualPixels(image1,bounds->x,bounds->y+y,bounds->width,1,
183 q=GetVirtualPixels(image2,bounds->x,bounds->y+y,bounds->width,1,
185 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
187 for (x=0; x < (ssize_t) bounds->width; x++)
189 if ((GetPixelAlpha(image1,p) <= (Quantum) (QuantumRange/2)) &&
190 (GetPixelAlpha(image1,q) > (Quantum) (QuantumRange/2)))
192 p+=GetPixelChannels(image1);
195 if (x < (ssize_t) bounds->width)
198 return(y < (ssize_t) bounds->height ? MagickTrue : MagickFalse);
202 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
206 % C o a l e s c e I m a g e s %
210 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
212 % CoalesceImages() composites a set of images while respecting any page
213 % offsets and disposal methods. GIF, MIFF, and MNG animation sequences
214 % typically start with an image background and each subsequent image
215 % varies in size and offset. A new image sequence is returned with all
216 % images the same size as the first images virtual canvas and composited
217 % with the next image in the sequence.
219 % The format of the CoalesceImages method is:
221 % Image *CoalesceImages(Image *image,ExceptionInfo *exception)
223 % A description of each parameter follows:
225 % o image: the image sequence.
227 % o exception: return any errors or warnings in this structure.
230 MagickExport Image *CoalesceImages(const Image *image,ExceptionInfo *exception)
244 Coalesce the image sequence.
246 assert(image != (Image *) NULL);
247 assert(image->signature == MagickSignature);
248 if (image->debug != MagickFalse)
249 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
250 assert(exception != (ExceptionInfo *) NULL);
251 assert(exception->signature == MagickSignature);
253 /* initialise first image */
254 next=GetFirstImageInList(image);
256 if (bounds.width == 0)
258 bounds.width=next->columns;
260 bounds.width+=bounds.x;
262 if (bounds.height == 0)
264 bounds.height=next->rows;
266 bounds.height+=bounds.y;
270 coalesce_image=CloneImage(next,bounds.width,bounds.height,MagickTrue,
272 if (coalesce_image == (Image *) NULL)
273 return((Image *) NULL);
274 coalesce_image->page=bounds;
275 coalesce_image->dispose=NoneDispose;
276 coalesce_image->background_color.alpha=(Quantum) TransparentAlpha;
277 (void) SetImageBackgroundColor(coalesce_image,exception);
279 Coalesce rest of the images.
281 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
282 (void) CompositeImage(coalesce_image,CopyCompositeOp,next,next->page.x,
283 next->page.y,exception);
284 next=GetNextImageInList(next);
285 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
288 Determine the bounds that was overlaid in the previous image.
290 previous=GetPreviousImageInList(next);
291 bounds=previous->page;
292 bounds.width=previous->columns;
293 bounds.height=previous->rows;
296 bounds.width+=bounds.x;
299 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) coalesce_image->columns)
300 bounds.width=coalesce_image->columns-bounds.x;
303 bounds.height+=bounds.y;
306 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) coalesce_image->rows)
307 bounds.height=coalesce_image->rows-bounds.y;
309 Replace the dispose image with the new coalesced image.
311 if (GetPreviousImageInList(next)->dispose != PreviousDispose)
313 dispose_image=DestroyImage(dispose_image);
314 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
315 if (dispose_image == (Image *) NULL)
317 coalesce_image=DestroyImageList(coalesce_image);
318 return((Image *) NULL);
322 Clear the overlaid area of the coalesced bounds for background disposal
324 if (next->previous->dispose == BackgroundDispose)
325 ClearBounds(dispose_image, &bounds);
327 Next image is the dispose image, overlaid with next frame in sequence.
329 coalesce_image->next=CloneImage(dispose_image,0,0,MagickTrue,exception);
330 coalesce_image->next->previous=coalesce_image;
331 previous=coalesce_image;
332 coalesce_image=GetNextImageInList(coalesce_image);
333 coalesce_image->matte=MagickTrue;
334 (void) CompositeImage(coalesce_image,next->matte != MagickFalse ?
335 OverCompositeOp : CopyCompositeOp,next,next->page.x,next->page.y,
337 (void) CloneImageProfiles(coalesce_image,next);
338 (void) CloneImageProperties(coalesce_image,next);
339 (void) CloneImageArtifacts(coalesce_image,next);
340 coalesce_image->page=previous->page;
342 If a pixel goes opaque to transparent, use background dispose.
344 if (IsBoundsCleared(previous,coalesce_image,&bounds,exception))
345 coalesce_image->dispose=BackgroundDispose;
347 coalesce_image->dispose=NoneDispose;
348 previous->dispose=coalesce_image->dispose;
350 dispose_image=DestroyImage(dispose_image);
351 return(GetFirstImageInList(coalesce_image));
355 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
359 % D i s p o s e I m a g e s %
363 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
365 % DisposeImages() returns the coalesced frames of a GIF animation as it would
366 % appear after the GIF dispose method of that frame has been applied. That
367 % is it returned the appearance of each frame before the next is overlaid.
369 % The format of the DisposeImages method is:
371 % Image *DisposeImages(Image *image,ExceptionInfo *exception)
373 % A description of each parameter follows:
375 % o image: the image sequence.
377 % o exception: return any errors or warnings in this structure.
380 MagickExport Image *DisposeImages(const Image *image,ExceptionInfo *exception)
393 Run the image through the animation sequence
395 assert(image != (Image *) NULL);
396 assert(image->signature == MagickSignature);
397 if (image->debug != MagickFalse)
398 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
399 assert(exception != (ExceptionInfo *) NULL);
400 assert(exception->signature == MagickSignature);
401 curr=GetFirstImageInList(image);
402 dispose_image=CloneImage(curr,curr->page.width,curr->page.height,MagickTrue,
404 if (dispose_image == (Image *) NULL)
405 return((Image *) NULL);
406 dispose_image->page=curr->page;
407 dispose_image->page.x=0;
408 dispose_image->page.y=0;
409 dispose_image->dispose=NoneDispose;
410 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
411 (void) SetImageBackgroundColor(dispose_image,exception);
412 dispose_images=NewImageList();
413 for ( ; curr != (Image *) NULL; curr=GetNextImageInList(curr))
419 Overlay this frame's image over the previous disposal image.
421 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
422 if (current_image == (Image *) NULL)
424 dispose_images=DestroyImageList(dispose_images);
425 dispose_image=DestroyImage(dispose_image);
426 return((Image *) NULL);
428 (void) CompositeImage(current_image,curr->matte != MagickFalse ?
429 OverCompositeOp : CopyCompositeOp,curr,curr->page.x,curr->page.y,
433 Handle Background dispose: image is displayed for the delay period.
435 if (curr->dispose == BackgroundDispose)
438 bounds.width=curr->columns;
439 bounds.height=curr->rows;
442 bounds.width+=bounds.x;
445 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
446 bounds.width=current_image->columns-bounds.x;
449 bounds.height+=bounds.y;
452 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
453 bounds.height=current_image->rows-bounds.y;
454 ClearBounds(current_image,&bounds);
457 Select the appropriate previous/disposed image.
459 if (curr->dispose == PreviousDispose)
460 current_image=DestroyImage(current_image);
463 dispose_image=DestroyImage(dispose_image);
464 dispose_image=current_image;
465 current_image=(Image *)NULL;
468 Save the dispose image just calculated for return.
474 dispose=CloneImage(dispose_image,0,0,MagickTrue,exception);
475 if (dispose == (Image *) NULL)
477 dispose_images=DestroyImageList(dispose_images);
478 dispose_image=DestroyImage(dispose_image);
479 return((Image *) NULL);
481 (void) CloneImageProfiles(dispose,curr);
482 (void) CloneImageProperties(dispose,curr);
483 (void) CloneImageArtifacts(dispose,curr);
486 dispose->dispose=curr->dispose;
487 AppendImageToList(&dispose_images,dispose);
490 dispose_image=DestroyImage(dispose_image);
491 return(GetFirstImageInList(dispose_images));
495 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
499 + C o m p a r e P i x e l s %
503 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
505 % ComparePixels() Compare the two pixels and return true if the pixels
506 % differ according to the given LayerType comparision method.
508 % This currently only used internally by CompareImagesBounds(). It is
509 % doubtful that this sub-routine will be useful outside this module.
511 % The format of the ComparePixels method is:
513 % MagickBooleanType *ComparePixels(const ImageLayerMethod method,
514 % const PixelInfo *p,const PixelInfo *q)
516 % A description of each parameter follows:
518 % o method: What differences to look for. Must be one of
519 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
521 % o p, q: the pixels to test for appropriate differences.
525 static MagickBooleanType ComparePixels(const ImageLayerMethod method,
526 const PixelInfo *p,const PixelInfo *q)
533 Any change in pixel values
535 if (method == CompareAnyLayer)
536 return((MagickBooleanType)(IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
538 o1 = (p->matte != MagickFalse) ? p->alpha : OpaqueAlpha;
539 o2 = (q->matte != MagickFalse) ? q->alpha : OpaqueAlpha;
542 Pixel goes from opaque to transprency
544 if (method == CompareClearLayer)
545 return((MagickBooleanType) ( (o1 <= ((MagickRealType) QuantumRange/2.0)) &&
546 (o2 > ((MagickRealType) QuantumRange/2.0)) ) );
549 overlay would change first pixel by second
551 if (method == CompareOverlayLayer)
553 if (o2 > ((MagickRealType) QuantumRange/2.0))
555 return((MagickBooleanType) (IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
562 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
566 + C o m p a r e I m a g e B o u n d s %
570 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
572 % CompareImagesBounds() Given two images return the smallest rectangular area
573 % by which the two images differ, accourding to the given 'Compare...'
576 % This currently only used internally in this module, but may eventually
577 % be used by other modules.
579 % The format of the CompareImagesBounds method is:
581 % RectangleInfo *CompareImagesBounds(const ImageLayerMethod method,
582 % const Image *image1, const Image *image2, ExceptionInfo *exception)
584 % A description of each parameter follows:
586 % o method: What differences to look for. Must be one of CompareAnyLayer,
587 % CompareClearLayer, CompareOverlayLayer.
589 % o image1, image2: the two images to compare.
591 % o exception: return any errors or warnings in this structure.
595 static RectangleInfo CompareImagesBounds(const Image *image1,const Image *image2,
596 const ImageLayerMethod method,ExceptionInfo *exception)
605 register const Quantum
616 Set bounding box of the differences between images.
618 GetPixelInfo(image1,&pixel1);
619 GetPixelInfo(image2,&pixel2);
620 for (x=0; x < (ssize_t) image1->columns; x++)
622 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
623 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
624 if ((p == (const Quantum *) NULL) ||
625 (q == (Quantum *) NULL))
627 for (y=0; y < (ssize_t) image1->rows; y++)
629 SetPixelInfo(image1,p,&pixel1);
630 SetPixelInfo(image2,q,&pixel2);
631 if (ComparePixels(method,&pixel1,&pixel2))
633 p+=GetPixelChannels(image1);
636 if (y < (ssize_t) image1->rows)
639 if (x >= (ssize_t) image1->columns)
642 Images are identical, return a null image.
651 for (x=(ssize_t) image1->columns-1; x >= 0; x--)
653 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
654 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
655 if ((p == (const Quantum *) NULL) ||
656 (q == (Quantum *) NULL))
658 for (y=0; y < (ssize_t) image1->rows; y++)
660 SetPixelInfo(image1,p,&pixel1);
661 SetPixelInfo(image2,q,&pixel2);
662 if (ComparePixels(method,&pixel1,&pixel2))
664 p+=GetPixelChannels(image1);
667 if (y < (ssize_t) image1->rows)
670 bounds.width=(size_t) (x-bounds.x+1);
671 for (y=0; y < (ssize_t) image1->rows; y++)
673 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
674 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
675 if ((p == (const Quantum *) NULL) ||
676 (q == (Quantum *) NULL))
678 for (x=0; x < (ssize_t) image1->columns; x++)
680 SetPixelInfo(image1,p,&pixel1);
681 SetPixelInfo(image2,q,&pixel2);
682 if (ComparePixels(method,&pixel1,&pixel2))
684 p+=GetPixelChannels(image1);
687 if (x < (ssize_t) image1->columns)
691 for (y=(ssize_t) image1->rows-1; y >= 0; y--)
693 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
694 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
695 if ((p == (const Quantum *) NULL) ||
696 (q == (Quantum *) NULL))
698 for (x=0; x < (ssize_t) image1->columns; x++)
700 SetPixelInfo(image1,p,&pixel1);
701 SetPixelInfo(image2,q,&pixel2);
702 if (ComparePixels(method,&pixel1,&pixel2))
704 p+=GetPixelChannels(image1);
707 if (x < (ssize_t) image1->columns)
710 bounds.height=(size_t) (y-bounds.y+1);
715 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
719 % C o m p a r e I m a g e L a y e r s %
723 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
725 % CompareImagesLayers() compares each image with the next in a sequence and
726 % returns the minimum bounding region of all the pixel differences (of the
727 % ImageLayerMethod specified) it discovers.
729 % Images do NOT have to be the same size, though it is best that all the
730 % images are 'coalesced' (images are all the same size, on a flattened
731 % canvas, so as to represent exactly how an specific frame should look).
733 % No GIF dispose methods are applied, so GIF animations must be coalesced
734 % before applying this image operator to find differences to them.
736 % The format of the CompareImagesLayers method is:
738 % Image *CompareImagesLayers(const Image *images,
739 % const ImageLayerMethod method,ExceptionInfo *exception)
741 % A description of each parameter follows:
743 % o image: the image.
745 % o method: the layers type to compare images with. Must be one of...
746 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
748 % o exception: return any errors or warnings in this structure.
752 MagickExport Image *CompareImagesLayers(const Image *image,
753 const ImageLayerMethod method, ExceptionInfo *exception)
769 assert(image != (const Image *) NULL);
770 assert(image->signature == MagickSignature);
771 if (image->debug != MagickFalse)
772 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
773 assert(exception != (ExceptionInfo *) NULL);
774 assert(exception->signature == MagickSignature);
775 assert((method == CompareAnyLayer) ||
776 (method == CompareClearLayer) ||
777 (method == CompareOverlayLayer));
779 Allocate bounds memory.
781 next=GetFirstImageInList(image);
782 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
783 GetImageListLength(next),sizeof(*bounds));
784 if (bounds == (RectangleInfo *) NULL)
785 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
787 Set up first comparision images.
789 image_a=CloneImage(next,next->page.width,next->page.height,
790 MagickTrue,exception);
791 if (image_a == (Image *) NULL)
793 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
794 return((Image *) NULL);
796 image_a->background_color.alpha=(Quantum) TransparentAlpha;
797 (void) SetImageBackgroundColor(image_a,exception);
798 image_a->page=next->page;
801 (void) CompositeImage(image_a,CopyCompositeOp,next,next->page.x,next->page.y,
804 Compute the bounding box of changes for the later images
807 next=GetNextImageInList(next);
808 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
810 image_b=CloneImage(image_a,0,0,MagickTrue,exception);
811 if (image_b == (Image *) NULL)
813 image_a=DestroyImage(image_a);
814 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
815 return((Image *) NULL);
817 (void) CompositeImage(image_a,CopyCompositeOp,next,next->page.x,
818 next->page.y,exception);
819 bounds[i]=CompareImagesBounds(image_b,image_a,method,exception);
821 image_b=DestroyImage(image_b);
824 image_a=DestroyImage(image_a);
826 Clone first image in sequence.
828 next=GetFirstImageInList(image);
829 layers=CloneImage(next,0,0,MagickTrue,exception);
830 if (layers == (Image *) NULL)
832 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
833 return((Image *) NULL);
836 Deconstruct the image sequence.
839 next=GetNextImageInList(next);
840 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
842 image_a=CloneImage(next,0,0,MagickTrue,exception);
843 if (image_a == (Image *) NULL)
845 image_b=CropImage(image_a,&bounds[i],exception);
846 image_a=DestroyImage(image_a);
847 if (image_b == (Image *) NULL)
849 AppendImageToList(&layers,image_b);
852 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
853 if (next != (Image *) NULL)
855 layers=DestroyImageList(layers);
856 return((Image *) NULL);
858 return(GetFirstImageInList(layers));
862 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
866 + O p t i m i z e L a y e r F r a m e s %
870 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
872 % OptimizeLayerFrames() takes a coalesced GIF animation, and compares each
873 % frame against the three different 'disposal' forms of the previous frame.
874 % From this it then attempts to select the smallest cropped image and
875 % disposal method needed to reproduce the resulting image.
877 % Note that this not easy, and may require the expansion of the bounds
878 % of previous frame, simply clear pixels for the next animation frame to
879 % transparency according to the selected dispose method.
881 % The format of the OptimizeLayerFrames method is:
883 % Image *OptimizeLayerFrames(const Image *image,
884 % const ImageLayerMethod method, ExceptionInfo *exception)
886 % A description of each parameter follows:
888 % o image: the image.
890 % o method: the layers technique to optimize with. Must be one of...
891 % OptimizeImageLayer, or OptimizePlusLayer. The Plus form allows
892 % the addition of extra 'zero delay' frames to clear pixels from
893 % the previous frame, and the removal of frames that done change,
894 % merging the delay times together.
896 % o exception: return any errors or warnings in this structure.
900 Define a 'fake' dispose method where the frame is duplicated, (for
901 OptimizePlusLayer) with a extra zero time delay frame which does a
902 BackgroundDisposal to clear the pixels that need to be cleared.
904 #define DupDispose ((DisposeType)9)
906 Another 'fake' dispose method used to removed frames that don't change.
908 #define DelDispose ((DisposeType)8)
910 #define DEBUG_OPT_FRAME 0
912 static Image *OptimizeLayerFrames(const Image *image,
913 const ImageLayerMethod method, ExceptionInfo *exception)
944 assert(image != (const Image *) NULL);
945 assert(image->signature == MagickSignature);
946 if (image->debug != MagickFalse)
947 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
948 assert(exception != (ExceptionInfo *) NULL);
949 assert(exception->signature == MagickSignature);
950 assert(method == OptimizeLayer ||
951 method == OptimizeImageLayer ||
952 method == OptimizePlusLayer);
955 Are we allowed to add/remove frames from animation
957 add_frames=method == OptimizePlusLayer ? MagickTrue : MagickFalse;
959 Ensure all the images are the same size
961 curr=GetFirstImageInList(image);
962 for (; curr != (Image *) NULL; curr=GetNextImageInList(curr))
964 if ((curr->columns != image->columns) || (curr->rows != image->rows))
965 ThrowImageException(OptionError,"ImagesAreNotTheSameSize");
967 FUTURE: also check that image is also fully coalesced (full page)
968 Though as long as they are the same size it should not matter.
972 Allocate memory (times 2 if we allow the use of frame duplications)
974 curr=GetFirstImageInList(image);
975 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
976 GetImageListLength(curr),(add_frames != MagickFalse ? 2UL : 1UL)*
978 if (bounds == (RectangleInfo *) NULL)
979 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
980 disposals=(DisposeType *) AcquireQuantumMemory((size_t)
981 GetImageListLength(image),(add_frames != MagickFalse ? 2UL : 1UL)*
983 if (disposals == (DisposeType *) NULL)
985 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
986 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
989 Initialise Previous Image as fully transparent
991 prev_image=CloneImage(curr,curr->page.width,curr->page.height,
992 MagickTrue,exception);
993 if (prev_image == (Image *) NULL)
995 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
996 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
997 return((Image *) NULL);
999 prev_image->page=curr->page; /* ERROR: <-- should not be need, but is! */
1000 prev_image->page.x=0;
1001 prev_image->page.y=0;
1002 prev_image->dispose=NoneDispose;
1004 prev_image->background_color.alpha=(Quantum) TransparentAlpha;
1005 (void) SetImageBackgroundColor(prev_image,exception);
1007 Figure out the area of overlay of the first frame
1008 No pixel could be cleared as all pixels are already cleared.
1012 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
1014 disposals[0]=NoneDispose;
1015 bounds[0]=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
1017 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g\n\n",
1018 (double) bounds[i].width,(double) bounds[i].height,
1019 (double) bounds[i].x,(double) bounds[i].y );
1022 Compute the bounding box of changes for each pair of images.
1025 bgnd_image=(Image *)NULL;
1026 dup_image=(Image *)NULL;
1028 dup_bounds.height=0;
1031 curr=GetNextImageInList(curr);
1032 for ( ; curr != (const Image *) NULL; curr=GetNextImageInList(curr))
1035 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
1038 Assume none disposal is the best
1040 bounds[i]=CompareImagesBounds(curr->previous,curr,CompareAnyLayer,exception);
1041 cleared=IsBoundsCleared(curr->previous,curr,&bounds[i],exception);
1042 disposals[i-1]=NoneDispose;
1044 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g%s%s\n",
1045 (double) bounds[i].width,(double) bounds[i].height,
1046 (double) bounds[i].x,(double) bounds[i].y,
1047 bounds[i].x < 0?" (unchanged)":"",
1048 cleared?" (pixels cleared)":"");
1050 if ( bounds[i].x < 0 ) {
1052 Image frame is exactly the same as the previous frame!
1053 If not adding frames leave it to be cropped down to a null image.
1054 Otherwise mark previous image for deleted, transfering its crop bounds
1055 to the current image.
1057 if ( add_frames && i>=2 ) {
1058 disposals[i-1]=DelDispose;
1059 disposals[i]=NoneDispose;
1060 bounds[i]=bounds[i-1];
1068 Compare a none disposal against a previous disposal
1070 try_bounds=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
1071 try_cleared=IsBoundsCleared(prev_image,curr,&try_bounds,exception);
1073 (void) FormatLocaleFile(stderr, "test_prev: %.20gx%.20g%+.20g%+.20g%s\n",
1074 (double) try_bounds.width,(double) try_bounds.height,
1075 (double) try_bounds.x,(double) try_bounds.y,
1076 try_cleared?" (pixels were cleared)":"");
1078 if ( (!try_cleared && cleared ) ||
1079 try_bounds.width * try_bounds.height
1080 < bounds[i].width * bounds[i].height )
1082 cleared=try_cleared;
1083 bounds[i]=try_bounds;
1084 disposals[i-1]=PreviousDispose;
1086 (void) FormatLocaleFile(stderr, "previous: accepted\n");
1088 (void) FormatLocaleFile(stderr, "previous: rejected\n");
1093 If we are allowed lets try a complex frame duplication.
1094 It is useless if the previous image already clears pixels correctly.
1095 This method will always clear all the pixels that need to be cleared.
1097 dup_bounds.width=dup_bounds.height=0; /* no dup, no pixel added */
1100 dup_image=CloneImage(curr->previous,curr->previous->page.width,
1101 curr->previous->page.height,MagickTrue,exception);
1102 if (dup_image == (Image *) NULL)
1104 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1105 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1106 prev_image=DestroyImage(prev_image);
1107 return((Image *) NULL);
1109 dup_bounds=CompareImagesBounds(dup_image,curr,CompareClearLayer,exception);
1110 ClearBounds(dup_image,&dup_bounds);
1111 try_bounds=CompareImagesBounds(dup_image,curr,CompareAnyLayer,exception);
1113 dup_bounds.width*dup_bounds.height
1114 +try_bounds.width*try_bounds.height
1115 < bounds[i].width * bounds[i].height )
1117 cleared=MagickFalse;
1118 bounds[i]=try_bounds;
1119 disposals[i-1]=DupDispose;
1120 /* to be finalised later, if found to be optimial */
1123 dup_bounds.width=dup_bounds.height=0;
1126 Now compare against a simple background disposal
1128 bgnd_image=CloneImage(curr->previous,curr->previous->page.width,
1129 curr->previous->page.height,MagickTrue,exception);
1130 if (bgnd_image == (Image *) NULL)
1132 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1133 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1134 prev_image=DestroyImage(prev_image);
1135 if ( dup_image != (Image *) NULL)
1136 dup_image=DestroyImage(dup_image);
1137 return((Image *) NULL);
1139 bgnd_bounds=bounds[i-1]; /* interum bounds of the previous image */
1140 ClearBounds(bgnd_image,&bgnd_bounds);
1141 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
1142 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1144 (void) FormatLocaleFile(stderr, "background: %s\n",
1145 try_cleared?"(pixels cleared)":"");
1150 Straight background disposal failed to clear pixels needed!
1151 Lets try expanding the disposal area of the previous frame, to
1152 include the pixels that are cleared. This guaranteed
1153 to work, though may not be the most optimized solution.
1155 try_bounds=CompareImagesBounds(curr->previous,curr,CompareClearLayer,exception);
1157 (void) FormatLocaleFile(stderr, "expand_clear: %.20gx%.20g%+.20g%+.20g%s\n",
1158 (double) try_bounds.width,(double) try_bounds.height,
1159 (double) try_bounds.x,(double) try_bounds.y,
1160 try_bounds.x<0?" (no expand nessary)":"");
1162 if ( bgnd_bounds.x < 0 )
1163 bgnd_bounds = try_bounds;
1167 (void) FormatLocaleFile(stderr, "expand_bgnd: %.20gx%.20g%+.20g%+.20g\n",
1168 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1169 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1171 if ( try_bounds.x < bgnd_bounds.x )
1173 bgnd_bounds.width+= bgnd_bounds.x-try_bounds.x;
1174 if ( bgnd_bounds.width < try_bounds.width )
1175 bgnd_bounds.width = try_bounds.width;
1176 bgnd_bounds.x = try_bounds.x;
1180 try_bounds.width += try_bounds.x - bgnd_bounds.x;
1181 if ( bgnd_bounds.width < try_bounds.width )
1182 bgnd_bounds.width = try_bounds.width;
1184 if ( try_bounds.y < bgnd_bounds.y )
1186 bgnd_bounds.height += bgnd_bounds.y - try_bounds.y;
1187 if ( bgnd_bounds.height < try_bounds.height )
1188 bgnd_bounds.height = try_bounds.height;
1189 bgnd_bounds.y = try_bounds.y;
1193 try_bounds.height += try_bounds.y - bgnd_bounds.y;
1194 if ( bgnd_bounds.height < try_bounds.height )
1195 bgnd_bounds.height = try_bounds.height;
1198 (void) FormatLocaleFile(stderr, " to : %.20gx%.20g%+.20g%+.20g\n",
1199 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1200 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1203 ClearBounds(bgnd_image,&bgnd_bounds);
1205 /* Something strange is happening with a specific animation
1206 * CompareAnyLayers (normal method) and CompareClearLayers returns the whole
1207 * image, which is not posibly correct! As verified by previous tests.
1208 * Something changed beyond the bgnd_bounds clearing. But without being able
1209 * to see, or writet he image at this point it is hard to tell what is wrong!
1210 * Only CompareOverlay seemed to return something sensible.
1212 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareClearLayer,exception);
1213 (void) FormatLocaleFile(stderr, "expand_ctst: %.20gx%.20g%+.20g%+.20g\n",
1214 (double) try_bounds.width,(double) try_bounds.height,
1215 (double) try_bounds.x,(double) try_bounds.y );
1216 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
1217 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1218 (void) FormatLocaleFile(stderr, "expand_any : %.20gx%.20g%+.20g%+.20g%s\n",
1219 (double) try_bounds.width,(double) try_bounds.height,
1220 (double) try_bounds.x,(double) try_bounds.y,
1221 try_cleared?" (pixels cleared)":"");
1223 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareOverlayLayer,exception);
1225 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1226 (void) FormatLocaleFile(stderr, "expand_test: %.20gx%.20g%+.20g%+.20g%s\n",
1227 (double) try_bounds.width,(double) try_bounds.height,
1228 (double) try_bounds.x,(double) try_bounds.y,
1229 try_cleared?" (pixels cleared)":"");
1233 Test if this background dispose is smaller than any of the
1234 other methods we tryed before this (including duplicated frame)
1237 bgnd_bounds.width*bgnd_bounds.height
1238 +try_bounds.width*try_bounds.height
1239 < bounds[i-1].width*bounds[i-1].height
1240 +dup_bounds.width*dup_bounds.height
1241 +bounds[i].width*bounds[i].height )
1243 cleared=MagickFalse;
1244 bounds[i-1]=bgnd_bounds;
1245 bounds[i]=try_bounds;
1246 if ( disposals[i-1] == DupDispose )
1247 dup_image=DestroyImage(dup_image);
1248 disposals[i-1]=BackgroundDispose;
1250 (void) FormatLocaleFile(stderr, "expand_bgnd: accepted\n");
1252 (void) FormatLocaleFile(stderr, "expand_bgnd: reject\n");
1257 Finalise choice of dispose, set new prev_image,
1258 and junk any extra images as appropriate,
1260 if ( disposals[i-1] == DupDispose )
1262 if (bgnd_image != (Image *) NULL)
1263 bgnd_image=DestroyImage(bgnd_image);
1264 prev_image=DestroyImage(prev_image);
1265 prev_image=dup_image, dup_image=(Image *) NULL;
1266 bounds[i+1]=bounds[i];
1267 bounds[i]=dup_bounds;
1268 disposals[i-1]=DupDispose;
1269 disposals[i]=BackgroundDispose;
1274 if ( dup_image != (Image *) NULL)
1275 dup_image=DestroyImage(dup_image);
1276 if ( disposals[i-1] != PreviousDispose )
1277 prev_image=DestroyImage(prev_image);
1278 if ( disposals[i-1] == BackgroundDispose )
1279 prev_image=bgnd_image, bgnd_image=(Image *)NULL;
1280 if (bgnd_image != (Image *) NULL)
1281 bgnd_image=DestroyImage(bgnd_image);
1282 if ( disposals[i-1] == NoneDispose )
1284 prev_image=CloneImage(curr->previous,curr->previous->page.width,
1285 curr->previous->page.height,MagickTrue,exception);
1286 if (prev_image == (Image *) NULL)
1288 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1289 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1290 return((Image *) NULL);
1295 assert(prev_image != (Image *) NULL);
1296 disposals[i]=disposals[i-1];
1298 (void) FormatLocaleFile(stderr, "final %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1300 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i-1]),
1301 (double) bounds[i-1].width, (double) bounds[i-1].height,
1302 (double) bounds[i-1].x, (double) bounds[i-1].y );
1305 (void) FormatLocaleFile(stderr, "interum %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1307 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i]),
1308 (double) bounds[i].width, (double) bounds[i].height,
1309 (double) bounds[i].x, (double) bounds[i].y );
1310 (void) FormatLocaleFile(stderr, "\n");
1314 prev_image=DestroyImage(prev_image);
1316 Optimize all images in sequence.
1318 sans_exception=AcquireExceptionInfo();
1320 curr=GetFirstImageInList(image);
1321 optimized_image=NewImageList();
1322 while ( curr != (const Image *) NULL )
1324 prev_image=CloneImage(curr,0,0,MagickTrue,exception);
1325 if (prev_image == (Image *) NULL)
1327 if ( disposals[i] == DelDispose ) {
1329 while ( disposals[i] == DelDispose ) {
1330 time += curr->delay*1000/curr->ticks_per_second;
1331 curr=GetNextImageInList(curr);
1334 time += curr->delay*1000/curr->ticks_per_second;
1335 prev_image->ticks_per_second = 100L;
1336 prev_image->delay = time*prev_image->ticks_per_second/1000;
1338 bgnd_image=CropImage(prev_image,&bounds[i],sans_exception);
1339 prev_image=DestroyImage(prev_image);
1340 if (bgnd_image == (Image *) NULL)
1342 bgnd_image->dispose=disposals[i];
1343 if ( disposals[i] == DupDispose ) {
1344 bgnd_image->delay=0;
1345 bgnd_image->dispose=NoneDispose;
1348 curr=GetNextImageInList(curr);
1349 AppendImageToList(&optimized_image,bgnd_image);
1352 sans_exception=DestroyExceptionInfo(sans_exception);
1353 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1354 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1355 if (curr != (Image *) NULL)
1357 optimized_image=DestroyImageList(optimized_image);
1358 return((Image *) NULL);
1360 return(GetFirstImageInList(optimized_image));
1364 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1368 % O p t i m i z e I m a g e L a y e r s %
1372 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1374 % OptimizeImageLayers() compares each image the GIF disposed forms of the
1375 % previous image in the sequence. From this it attempts to select the
1376 % smallest cropped image to replace each frame, while preserving the results
1377 % of the GIF animation.
1379 % The format of the OptimizeImageLayers method is:
1381 % Image *OptimizeImageLayers(const Image *image,
1382 % ExceptionInfo *exception)
1384 % A description of each parameter follows:
1386 % o image: the image.
1388 % o exception: return any errors or warnings in this structure.
1391 MagickExport Image *OptimizeImageLayers(const Image *image,
1392 ExceptionInfo *exception)
1394 return(OptimizeLayerFrames(image,OptimizeImageLayer,exception));
1398 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1402 % O p t i m i z e P l u s I m a g e L a y e r s %
1406 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1408 % OptimizeImagePlusLayers() is exactly as OptimizeImageLayers(), but may
1409 % also add or even remove extra frames in the animation, if it improves
1410 % the total number of pixels in the resulting GIF animation.
1412 % The format of the OptimizePlusImageLayers method is:
1414 % Image *OptimizePlusImageLayers(const Image *image,
1415 % ExceptionInfo *exception)
1417 % A description of each parameter follows:
1419 % o image: the image.
1421 % o exception: return any errors or warnings in this structure.
1424 MagickExport Image *OptimizePlusImageLayers(const Image *image,
1425 ExceptionInfo *exception)
1427 return OptimizeLayerFrames(image, OptimizePlusLayer, exception);
1431 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1435 % 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 %
1439 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1441 % OptimizeImageTransparency() takes a frame optimized GIF animation, and
1442 % compares the overlayed pixels against the disposal image resulting from all
1443 % the previous frames in the animation. Any pixel that does not change the
1444 % disposal image (and thus does not effect the outcome of an overlay) is made
1447 % WARNING: This modifies the current images directly, rather than generate
1448 % a new image sequence.
1450 % The format of the OptimizeImageTransperency method is:
1452 % void OptimizeImageTransperency(Image *image,ExceptionInfo *exception)
1454 % A description of each parameter follows:
1456 % o image: the image sequence
1458 % o exception: return any errors or warnings in this structure.
1461 MagickExport void OptimizeImageTransparency(const Image *image,
1462 ExceptionInfo *exception)
1471 Run the image through the animation sequence
1473 assert(image != (Image *) NULL);
1474 assert(image->signature == MagickSignature);
1475 if (image->debug != MagickFalse)
1476 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1477 assert(exception != (ExceptionInfo *) NULL);
1478 assert(exception->signature == MagickSignature);
1479 next=GetFirstImageInList(image);
1480 dispose_image=CloneImage(next,next->page.width,next->page.height,
1481 MagickTrue,exception);
1482 if (dispose_image == (Image *) NULL)
1484 dispose_image->page=next->page;
1485 dispose_image->page.x=0;
1486 dispose_image->page.y=0;
1487 dispose_image->dispose=NoneDispose;
1488 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
1489 (void) SetImageBackgroundColor(dispose_image,exception);
1491 while ( next != (Image *) NULL )
1497 Overlay this frame's image over the previous disposal image
1499 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
1500 if (current_image == (Image *) NULL)
1502 dispose_image=DestroyImage(dispose_image);
1505 (void) CompositeImage(current_image,next->matte != MagickFalse ?
1506 OverCompositeOp : CopyCompositeOp, next,next->page.x,next->page.y,
1509 At this point the image would be displayed, for the delay period
1511 Work out the disposal of the previous image
1513 if (next->dispose == BackgroundDispose)
1518 bounds.width=next->columns;
1519 bounds.height=next->rows;
1522 bounds.width+=bounds.x;
1525 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
1526 bounds.width=current_image->columns-bounds.x;
1529 bounds.height+=bounds.y;
1532 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
1533 bounds.height=current_image->rows-bounds.y;
1534 ClearBounds(current_image, &bounds);
1536 if (next->dispose != PreviousDispose)
1538 dispose_image=DestroyImage(dispose_image);
1539 dispose_image=current_image;
1542 current_image=DestroyImage(current_image);
1545 Optimize Transparency of the next frame (if present)
1547 next=GetNextImageInList(next);
1548 if ( next != (Image *) NULL ) {
1549 (void) CompositeImage(next, ChangeMaskCompositeOp,
1550 dispose_image, -(next->page.x), -(next->page.y), exception );
1553 dispose_image=DestroyImage(dispose_image);
1558 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1562 % R e m o v e D u p l i c a t e L a y e r s %
1566 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1568 % RemoveDuplicateLayers() removes any image that is exactly the same as the
1569 % next image in the given image list. Image size and virtual canvas offset
1570 % must also match, though not the virtual canvas size itself.
1572 % No check is made with regards to image disposal setting, though it is the
1573 % dispose setting of later image that is kept. Also any time delays are also
1574 % added together. As such coalesced image animations should still produce the
1575 % same result, though with duplicte frames merged into a single frame.
1577 % The format of the RemoveDuplicateLayers method is:
1579 % void RemoveDuplicateLayers(Image **image, ExceptionInfo *exception)
1581 % A description of each parameter follows:
1583 % o images: the image list
1585 % o exception: return any errors or warnings in this structure.
1588 MagickExport void RemoveDuplicateLayers(Image **images,
1589 ExceptionInfo *exception)
1598 assert((*images) != (const Image *) NULL);
1599 assert((*images)->signature == MagickSignature);
1600 if ((*images)->debug != MagickFalse)
1601 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1602 assert(exception != (ExceptionInfo *) NULL);
1603 assert(exception->signature == MagickSignature);
1605 curr=GetFirstImageInList(*images);
1606 for (; (next=GetNextImageInList(curr)) != (Image *) NULL; curr=next)
1608 if ( curr->columns != next->columns || curr->rows != next->rows
1609 || curr->page.x != next->page.x || curr->page.y != next->page.y )
1611 bounds=CompareImagesBounds(curr,next,CompareAnyLayer,exception);
1612 if ( bounds.x < 0 ) {
1614 the two images are the same, merge time delays and delete one.
1617 time = curr->delay*1000/curr->ticks_per_second;
1618 time += next->delay*1000/next->ticks_per_second;
1619 next->ticks_per_second = 100L;
1620 next->delay = time*curr->ticks_per_second/1000;
1621 next->iterations = curr->iterations;
1623 (void) DeleteImageFromList(images);
1626 *images = GetFirstImageInList(*images);
1630 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1634 % R e m o v e Z e r o D e l a y L a y e r s %
1638 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1640 % RemoveZeroDelayLayers() removes any image that as a zero delay time. Such
1641 % images generally represent intermediate or partial updates in GIF
1642 % animations used for file optimization. They are not ment to be displayed
1643 % to users of the animation. Viewable images in an animation should have a
1644 % time delay of 3 or more centi-seconds (hundredths of a second).
1646 % However if all the frames have a zero time delay, then either the animation
1647 % is as yet incomplete, or it is not a GIF animation. This a non-sensible
1648 % situation, so no image will be removed and a 'Zero Time Animation' warning
1649 % (exception) given.
1651 % No warning will be given if no image was removed because all images had an
1652 % appropriate non-zero time delay set.
1654 % Due to the special requirements of GIF disposal handling, GIF animations
1655 % should be coalesced first, before calling this function, though that is not
1658 % The format of the RemoveZeroDelayLayers method is:
1660 % void RemoveZeroDelayLayers(Image **image, ExceptionInfo *exception)
1662 % A description of each parameter follows:
1664 % o images: the image list
1666 % o exception: return any errors or warnings in this structure.
1669 MagickExport void RemoveZeroDelayLayers(Image **images,
1670 ExceptionInfo *exception)
1675 assert((*images) != (const Image *) NULL);
1676 assert((*images)->signature == MagickSignature);
1677 if ((*images)->debug != MagickFalse)
1678 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1679 assert(exception != (ExceptionInfo *) NULL);
1680 assert(exception->signature == MagickSignature);
1682 i=GetFirstImageInList(*images);
1683 for ( ; i != (Image *) NULL; i=GetNextImageInList(i))
1684 if ( i->delay != 0L ) break;
1685 if ( i == (Image *) NULL ) {
1686 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
1687 "ZeroTimeAnimation","`%s'",GetFirstImageInList(*images)->filename);
1690 i=GetFirstImageInList(*images);
1691 while ( i != (Image *) NULL )
1693 if ( i->delay == 0L ) {
1694 (void) DeleteImageFromList(&i);
1698 i=GetNextImageInList(i);
1700 *images=GetFirstImageInList(*images);
1704 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1708 % C o m p o s i t e L a y e r s %
1712 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1714 % CompositeLayers() compose first image sequence (source) over the second
1715 % image sequence (destination), using the given compose method and offsets.
1717 % The pointers to the image list does not have to be the start of that image
1718 % list, but may start somewhere in the middle. Each layer from the two image
1719 % lists are composted together until the end of one of the image lists is
1720 % reached. The offset of each composition is also adjusted to match the
1721 % virtual canvas offsets of each layer. As such the given offset is relative
1722 % to the virtual canvas, and not the actual image.
1724 % No GIF disposal handling is performed, so GIF animations should be
1725 % coalesced before use. However this not a requirement, and individual
1726 % layer images may have any size or offset, for special compositions.
1728 % Special case:- If one of the image sequences is just a single image that
1729 % image is repeatally composed with all the images in the other image list.
1730 % Either the source or destination lists may be the single image, for this
1733 % The destination list will be expanded as needed to match number of source
1734 % image overlaid (from current position to end of list).
1736 % The format of the CompositeLayers method is:
1738 % void CompositeLayers(Image *destination,
1739 % const CompositeOperator compose, Image *source,
1740 % const ssize_t x_offset, const ssize_t y_offset,
1741 % ExceptionInfo *exception);
1743 % A description of each parameter follows:
1745 % o destination: the destination images and results
1747 % o source: source image(s) for the layer composition
1749 % o compose, x_offset, y_offset: arguments passed on to CompositeImages()
1751 % o exception: return any errors or warnings in this structure.
1755 static inline void CompositeCanvas(Image *destination,
1756 const CompositeOperator compose,Image *source,ssize_t x_offset,
1757 ssize_t y_offset,ExceptionInfo *exception)
1759 x_offset+=source->page.x-destination->page.x;
1760 y_offset+=source->page.y-destination->page.y;
1761 (void) CompositeImage(destination,compose,source,x_offset,y_offset,
1765 MagickExport void CompositeLayers(Image *destination,
1766 const CompositeOperator compose, Image *source,const ssize_t x_offset,
1767 const ssize_t y_offset,ExceptionInfo *exception)
1769 assert(destination != (Image *) NULL);
1770 assert(destination->signature == MagickSignature);
1771 assert(source != (Image *) NULL);
1772 assert(source->signature == MagickSignature);
1773 assert(exception != (ExceptionInfo *) NULL);
1774 assert(exception->signature == MagickSignature);
1775 if (source->debug != MagickFalse || destination->debug != MagickFalse)
1776 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s - %s",
1777 source->filename, destination->filename);
1780 Overlay single source image over destation image/list
1782 if ( source->previous == (Image *) NULL && source->next == (Image *) NULL )
1783 while ( destination != (Image *) NULL )
1785 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1787 destination=GetNextImageInList(destination);
1791 Overlay source image list over single destination
1792 Generating multiple clones of destination image to match source list.
1793 Original Destination image becomes first image of generated list.
1794 As such the image list pointer does not require any change in caller.
1795 Some animation attributes however also needs coping in this case.
1797 else if ( destination->previous == (Image *) NULL &&
1798 destination->next == (Image *) NULL )
1800 Image *dest = CloneImage(destination,0,0,MagickTrue,exception);
1802 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1804 /* copy source image attributes ? */
1805 if ( source->next != (Image *) NULL )
1807 destination->delay = source->delay;
1808 destination->iterations = source->iterations;
1810 source=GetNextImageInList(source);
1812 while ( source != (Image *) NULL )
1814 AppendImageToList(&destination,
1815 CloneImage(dest,0,0,MagickTrue,exception));
1816 destination=GetLastImageInList(destination);
1818 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1820 destination->delay = source->delay;
1821 destination->iterations = source->iterations;
1822 source=GetNextImageInList(source);
1824 dest=DestroyImage(dest);
1828 Overlay a source image list over a destination image list
1829 until either list runs out of images. (Does not repeat)
1832 while ( source != (Image *) NULL && destination != (Image *) NULL )
1834 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1836 source=GetNextImageInList(source);
1837 destination=GetNextImageInList(destination);
1842 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1846 % M e r g e I m a g e L a y e r s %
1850 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1852 % MergeImageLayers() composes all the image layers from the current given
1853 % image onward to produce a single image of the merged layers.
1855 % The inital canvas's size depends on the given ImageLayerMethod, and is
1856 % initialized using the first images background color. The images
1857 % are then compositied onto that image in sequence using the given
1858 % composition that has been assigned to each individual image.
1860 % The format of the MergeImageLayers is:
1862 % Image *MergeImageLayers(const Image *image,
1863 % const ImageLayerMethod method, ExceptionInfo *exception)
1865 % A description of each parameter follows:
1867 % o image: the image list to be composited together
1869 % o method: the method of selecting the size of the initial canvas.
1871 % MergeLayer: Merge all layers onto a canvas just large enough
1872 % to hold all the actual images. The virtual canvas of the
1873 % first image is preserved but otherwise ignored.
1875 % FlattenLayer: Use the virtual canvas size of first image.
1876 % Images which fall outside this canvas is clipped.
1877 % This can be used to 'fill out' a given virtual canvas.
1879 % MosaicLayer: Start with the virtual canvas of the first image,
1880 % enlarging left and right edges to contain all images.
1881 % Images with negative offsets will be clipped.
1883 % TrimBoundsLayer: Determine the overall bounds of all the image
1884 % layers just as in "MergeLayer", then adjust the the canvas
1885 % and offsets to be relative to those bounds, without overlaying
1888 % WARNING: a new image is not returned, the original image
1889 % sequence page data is modified instead.
1891 % o exception: return any errors or warnings in this structure.
1894 MagickExport Image *MergeImageLayers(Image *image,const ImageLayerMethod method,
1895 ExceptionInfo *exception)
1897 #define MergeLayersTag "Merge/Layers"
1908 register const Image
1919 assert(image != (Image *) NULL);
1920 assert(image->signature == MagickSignature);
1921 if (image->debug != MagickFalse)
1922 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1923 assert(exception != (ExceptionInfo *) NULL);
1924 assert(exception->signature == MagickSignature);
1926 Determine canvas image size, and its virtual canvas size and offset
1929 width=image->columns;
1933 case TrimBoundsLayer:
1937 next = GetNextImageInList(image);
1938 for ( ; next != (Image *) NULL; next=GetNextImageInList(next)) {
1939 if ( page.x > next->page.x ) {
1940 width += page.x-next->page.x;
1941 page.x = next->page.x;
1943 if ( page.y > next->page.y ) {
1944 height += page.y-next->page.y;
1945 page.y = next->page.y;
1947 if ( (ssize_t) width < (next->page.x + (ssize_t)next->columns - page.x) )
1948 width = (size_t) next->page.x + (ssize_t)next->columns - page.x;
1949 if ( (ssize_t) height < (next->page.y + (ssize_t)next->rows - page.y) )
1950 height = (size_t) next->page.y + (ssize_t)next->rows - page.y;
1956 if ( page.width > 0 )
1958 if ( page.height > 0 )
1966 if ( page.width > 0 )
1968 if ( page.height > 0 )
1970 for (next=image; next != (Image *) NULL; next=GetNextImageInList(next)) {
1971 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;
1987 /* set virtual canvas size if not defined */
1988 if ( page.width == 0 )
1989 page.width = (page.x < 0) ? width : width+page.x;
1990 if ( page.height == 0 )
1991 page.height = (page.y < 0) ? height : height+page.y;
1994 Handle "TrimBoundsLayer" method separately to normal 'layer merge'
1996 if ( method == TrimBoundsLayer ) {
1997 number_images=GetImageListLength(image);
1998 for (scene=0; scene < (ssize_t) number_images; scene++)
2000 image->page.x -= page.x;
2001 image->page.y -= page.y;
2002 image->page.width = width;
2003 image->page.height = height;
2004 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2006 if (proceed == MagickFalse)
2008 image=GetNextImageInList(image);
2010 return((Image *) NULL);
2014 Create canvas size of width and height, and background color.
2016 canvas=CloneImage(image,width,height,MagickTrue,exception);
2017 if (canvas == (Image *) NULL)
2018 return((Image *) NULL);
2019 (void) SetImageBackgroundColor(canvas,exception);
2021 canvas->dispose=UndefinedDispose;
2024 Compose images onto canvas, with progress monitor
2026 number_images=GetImageListLength(image);
2027 for (scene=0; scene < (ssize_t) number_images; scene++)
2029 (void) CompositeImage(canvas,image->compose,image,image->page.x-
2030 canvas->page.x,image->page.y-canvas->page.y,exception);
2031 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2033 if (proceed == MagickFalse)
2035 image=GetNextImageInList(image);