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);
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,
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);
336 (void) CloneImageProfiles(coalesce_image,next);
337 (void) CloneImageProperties(coalesce_image,next);
338 (void) CloneImageArtifacts(coalesce_image,next);
339 coalesce_image->page=previous->page;
341 If a pixel goes opaque to transparent, use background dispose.
343 if (IsBoundsCleared(previous,coalesce_image,&bounds,exception))
344 coalesce_image->dispose=BackgroundDispose;
346 coalesce_image->dispose=NoneDispose;
347 previous->dispose=coalesce_image->dispose;
349 dispose_image=DestroyImage(dispose_image);
350 return(GetFirstImageInList(coalesce_image));
354 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
358 % D i s p o s e I m a g e s %
362 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
364 % DisposeImages() returns the coalesced frames of a GIF animation as it would
365 % appear after the GIF dispose method of that frame has been applied. That
366 % is it returned the appearance of each frame before the next is overlaid.
368 % The format of the DisposeImages method is:
370 % Image *DisposeImages(Image *image,ExceptionInfo *exception)
372 % A description of each parameter follows:
374 % o image: the image sequence.
376 % o exception: return any errors or warnings in this structure.
379 MagickExport Image *DisposeImages(const Image *image,ExceptionInfo *exception)
392 Run the image through the animation sequence
394 assert(image != (Image *) NULL);
395 assert(image->signature == MagickSignature);
396 if (image->debug != MagickFalse)
397 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
398 assert(exception != (ExceptionInfo *) NULL);
399 assert(exception->signature == MagickSignature);
400 curr=GetFirstImageInList(image);
401 dispose_image=CloneImage(curr,curr->page.width,curr->page.height,MagickTrue,
403 if (dispose_image == (Image *) NULL)
404 return((Image *) NULL);
405 dispose_image->page=curr->page;
406 dispose_image->page.x=0;
407 dispose_image->page.y=0;
408 dispose_image->dispose=NoneDispose;
409 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
410 (void) SetImageBackgroundColor(dispose_image);
411 dispose_images=NewImageList();
412 for ( ; curr != (Image *) NULL; curr=GetNextImageInList(curr))
418 Overlay this frame's image over the previous disposal image.
420 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
421 if (current_image == (Image *) NULL)
423 dispose_images=DestroyImageList(dispose_images);
424 dispose_image=DestroyImage(dispose_image);
425 return((Image *) NULL);
427 (void) CompositeImage(current_image,curr->matte != MagickFalse ?
428 OverCompositeOp : CopyCompositeOp,curr,curr->page.x,curr->page.y);
431 Handle Background dispose: image is displayed for the delay period.
433 if (curr->dispose == BackgroundDispose)
436 bounds.width=curr->columns;
437 bounds.height=curr->rows;
440 bounds.width+=bounds.x;
443 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
444 bounds.width=current_image->columns-bounds.x;
447 bounds.height+=bounds.y;
450 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
451 bounds.height=current_image->rows-bounds.y;
452 ClearBounds(current_image,&bounds);
455 Select the appropriate previous/disposed image.
457 if (curr->dispose == PreviousDispose)
458 current_image=DestroyImage(current_image);
461 dispose_image=DestroyImage(dispose_image);
462 dispose_image=current_image;
463 current_image=(Image *)NULL;
466 Save the dispose image just calculated for return.
472 dispose=CloneImage(dispose_image,0,0,MagickTrue,exception);
473 if (dispose == (Image *) NULL)
475 dispose_images=DestroyImageList(dispose_images);
476 dispose_image=DestroyImage(dispose_image);
477 return((Image *) NULL);
479 (void) CloneImageProfiles(dispose,curr);
480 (void) CloneImageProperties(dispose,curr);
481 (void) CloneImageArtifacts(dispose,curr);
484 dispose->dispose=curr->dispose;
485 AppendImageToList(&dispose_images,dispose);
488 dispose_image=DestroyImage(dispose_image);
489 return(GetFirstImageInList(dispose_images));
493 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
497 + C o m p a r e P i x e l s %
501 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
503 % ComparePixels() Compare the two pixels and return true if the pixels
504 % differ according to the given LayerType comparision method.
506 % This currently only used internally by CompareImagesBounds(). It is
507 % doubtful that this sub-routine will be useful outside this module.
509 % The format of the ComparePixels method is:
511 % MagickBooleanType *ComparePixels(const ImageLayerMethod method,
512 % const PixelInfo *p,const PixelInfo *q)
514 % A description of each parameter follows:
516 % o method: What differences to look for. Must be one of
517 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
519 % o p, q: the pixels to test for appropriate differences.
523 static MagickBooleanType ComparePixels(const ImageLayerMethod method,
524 const PixelInfo *p,const PixelInfo *q)
531 Any change in pixel values
533 if (method == CompareAnyLayer)
534 return((MagickBooleanType)(IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
536 o1 = (p->matte != MagickFalse) ? p->alpha : OpaqueAlpha;
537 o2 = (q->matte != MagickFalse) ? q->alpha : OpaqueAlpha;
540 Pixel goes from opaque to transprency
542 if (method == CompareClearLayer)
543 return((MagickBooleanType) ( (o1 <= ((MagickRealType) QuantumRange/2.0)) &&
544 (o2 > ((MagickRealType) QuantumRange/2.0)) ) );
547 overlay would change first pixel by second
549 if (method == CompareOverlayLayer)
551 if (o2 > ((MagickRealType) QuantumRange/2.0))
553 return((MagickBooleanType) (IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
560 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
564 + C o m p a r e I m a g e B o u n d s %
568 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
570 % CompareImagesBounds() Given two images return the smallest rectangular area
571 % by which the two images differ, accourding to the given 'Compare...'
574 % This currently only used internally in this module, but may eventually
575 % be used by other modules.
577 % The format of the CompareImagesBounds method is:
579 % RectangleInfo *CompareImagesBounds(const ImageLayerMethod method,
580 % const Image *image1, const Image *image2, ExceptionInfo *exception)
582 % A description of each parameter follows:
584 % o method: What differences to look for. Must be one of CompareAnyLayer,
585 % CompareClearLayer, CompareOverlayLayer.
587 % o image1, image2: the two images to compare.
589 % o exception: return any errors or warnings in this structure.
593 static RectangleInfo CompareImagesBounds(const Image *image1,const Image *image2,
594 const ImageLayerMethod method,ExceptionInfo *exception)
603 register const Quantum
614 Set bounding box of the differences between images.
616 GetPixelInfo(image1,&pixel1);
617 GetPixelInfo(image2,&pixel2);
618 for (x=0; x < (ssize_t) image1->columns; x++)
620 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
621 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
622 if ((p == (const Quantum *) NULL) ||
623 (q == (Quantum *) NULL))
625 for (y=0; y < (ssize_t) image1->rows; y++)
627 SetPixelInfo(image1,p,&pixel1);
628 SetPixelInfo(image2,q,&pixel2);
629 if (ComparePixels(method,&pixel1,&pixel2))
631 p+=GetPixelChannels(image1);
634 if (y < (ssize_t) image1->rows)
637 if (x >= (ssize_t) image1->columns)
640 Images are identical, return a null image.
649 for (x=(ssize_t) image1->columns-1; x >= 0; x--)
651 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
652 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
653 if ((p == (const Quantum *) NULL) ||
654 (q == (Quantum *) NULL))
656 for (y=0; y < (ssize_t) image1->rows; y++)
658 SetPixelInfo(image1,p,&pixel1);
659 SetPixelInfo(image2,q,&pixel2);
660 if (ComparePixels(method,&pixel1,&pixel2))
662 p+=GetPixelChannels(image1);
665 if (y < (ssize_t) image1->rows)
668 bounds.width=(size_t) (x-bounds.x+1);
669 for (y=0; y < (ssize_t) image1->rows; y++)
671 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
672 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
673 if ((p == (const Quantum *) NULL) ||
674 (q == (Quantum *) NULL))
676 for (x=0; x < (ssize_t) image1->columns; x++)
678 SetPixelInfo(image1,p,&pixel1);
679 SetPixelInfo(image2,q,&pixel2);
680 if (ComparePixels(method,&pixel1,&pixel2))
682 p+=GetPixelChannels(image1);
685 if (x < (ssize_t) image1->columns)
689 for (y=(ssize_t) image1->rows-1; y >= 0; y--)
691 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
692 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
693 if ((p == (const Quantum *) NULL) ||
694 (q == (Quantum *) NULL))
696 for (x=0; x < (ssize_t) image1->columns; x++)
698 SetPixelInfo(image1,p,&pixel1);
699 SetPixelInfo(image2,q,&pixel2);
700 if (ComparePixels(method,&pixel1,&pixel2))
702 p+=GetPixelChannels(image1);
705 if (x < (ssize_t) image1->columns)
708 bounds.height=(size_t) (y-bounds.y+1);
713 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
717 % C o m p a r e I m a g e L a y e r s %
721 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
723 % CompareImagesLayers() compares each image with the next in a sequence and
724 % returns the minimum bounding region of all the pixel differences (of the
725 % ImageLayerMethod specified) it discovers.
727 % Images do NOT have to be the same size, though it is best that all the
728 % images are 'coalesced' (images are all the same size, on a flattened
729 % canvas, so as to represent exactly how an specific frame should look).
731 % No GIF dispose methods are applied, so GIF animations must be coalesced
732 % before applying this image operator to find differences to them.
734 % The format of the CompareImagesLayers method is:
736 % Image *CompareImagesLayers(const Image *images,
737 % const ImageLayerMethod method,ExceptionInfo *exception)
739 % A description of each parameter follows:
741 % o image: the image.
743 % o method: the layers type to compare images with. Must be one of...
744 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
746 % o exception: return any errors or warnings in this structure.
750 MagickExport Image *CompareImagesLayers(const Image *image,
751 const ImageLayerMethod method, ExceptionInfo *exception)
767 assert(image != (const Image *) NULL);
768 assert(image->signature == MagickSignature);
769 if (image->debug != MagickFalse)
770 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
771 assert(exception != (ExceptionInfo *) NULL);
772 assert(exception->signature == MagickSignature);
773 assert((method == CompareAnyLayer) ||
774 (method == CompareClearLayer) ||
775 (method == CompareOverlayLayer));
777 Allocate bounds memory.
779 next=GetFirstImageInList(image);
780 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
781 GetImageListLength(next),sizeof(*bounds));
782 if (bounds == (RectangleInfo *) NULL)
783 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
785 Set up first comparision images.
787 image_a=CloneImage(next,next->page.width,next->page.height,
788 MagickTrue,exception);
789 if (image_a == (Image *) NULL)
791 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
792 return((Image *) NULL);
794 image_a->background_color.alpha=(Quantum) TransparentAlpha;
795 (void) SetImageBackgroundColor(image_a);
796 image_a->page=next->page;
799 (void) CompositeImage(image_a,CopyCompositeOp,next,next->page.x,next->page.y);
801 Compute the bounding box of changes for the later images
804 next=GetNextImageInList(next);
805 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
807 image_b=CloneImage(image_a,0,0,MagickTrue,exception);
808 if (image_b == (Image *) NULL)
810 image_a=DestroyImage(image_a);
811 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
812 return((Image *) NULL);
814 (void) CompositeImage(image_a,CopyCompositeOp,next,next->page.x,
816 bounds[i]=CompareImagesBounds(image_b,image_a,method,exception);
818 image_b=DestroyImage(image_b);
821 image_a=DestroyImage(image_a);
823 Clone first image in sequence.
825 next=GetFirstImageInList(image);
826 layers=CloneImage(next,0,0,MagickTrue,exception);
827 if (layers == (Image *) NULL)
829 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
830 return((Image *) NULL);
833 Deconstruct the image sequence.
836 next=GetNextImageInList(next);
837 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
839 image_a=CloneImage(next,0,0,MagickTrue,exception);
840 if (image_a == (Image *) NULL)
842 image_b=CropImage(image_a,&bounds[i],exception);
843 image_a=DestroyImage(image_a);
844 if (image_b == (Image *) NULL)
846 AppendImageToList(&layers,image_b);
849 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
850 if (next != (Image *) NULL)
852 layers=DestroyImageList(layers);
853 return((Image *) NULL);
855 return(GetFirstImageInList(layers));
859 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
863 + O p t i m i z e L a y e r F r a m e s %
867 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
869 % OptimizeLayerFrames() takes a coalesced GIF animation, and compares each
870 % frame against the three different 'disposal' forms of the previous frame.
871 % From this it then attempts to select the smallest cropped image and
872 % disposal method needed to reproduce the resulting image.
874 % Note that this not easy, and may require the expansion of the bounds
875 % of previous frame, simply clear pixels for the next animation frame to
876 % transparency according to the selected dispose method.
878 % The format of the OptimizeLayerFrames method is:
880 % Image *OptimizeLayerFrames(const Image *image,
881 % const ImageLayerMethod method, ExceptionInfo *exception)
883 % A description of each parameter follows:
885 % o image: the image.
887 % o method: the layers technique to optimize with. Must be one of...
888 % OptimizeImageLayer, or OptimizePlusLayer. The Plus form allows
889 % the addition of extra 'zero delay' frames to clear pixels from
890 % the previous frame, and the removal of frames that done change,
891 % merging the delay times together.
893 % o exception: return any errors or warnings in this structure.
897 Define a 'fake' dispose method where the frame is duplicated, (for
898 OptimizePlusLayer) with a extra zero time delay frame which does a
899 BackgroundDisposal to clear the pixels that need to be cleared.
901 #define DupDispose ((DisposeType)9)
903 Another 'fake' dispose method used to removed frames that don't change.
905 #define DelDispose ((DisposeType)8)
907 #define DEBUG_OPT_FRAME 0
909 static Image *OptimizeLayerFrames(const Image *image,
910 const ImageLayerMethod method, ExceptionInfo *exception)
941 assert(image != (const Image *) NULL);
942 assert(image->signature == MagickSignature);
943 if (image->debug != MagickFalse)
944 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
945 assert(exception != (ExceptionInfo *) NULL);
946 assert(exception->signature == MagickSignature);
947 assert(method == OptimizeLayer ||
948 method == OptimizeImageLayer ||
949 method == OptimizePlusLayer);
952 Are we allowed to add/remove frames from animation
954 add_frames=method == OptimizePlusLayer ? MagickTrue : MagickFalse;
956 Ensure all the images are the same size
958 curr=GetFirstImageInList(image);
959 for (; curr != (Image *) NULL; curr=GetNextImageInList(curr))
961 if ((curr->columns != image->columns) || (curr->rows != image->rows))
962 ThrowImageException(OptionError,"ImagesAreNotTheSameSize");
964 FUTURE: also check that image is also fully coalesced (full page)
965 Though as long as they are the same size it should not matter.
969 Allocate memory (times 2 if we allow the use of frame duplications)
971 curr=GetFirstImageInList(image);
972 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
973 GetImageListLength(curr),(add_frames != MagickFalse ? 2UL : 1UL)*
975 if (bounds == (RectangleInfo *) NULL)
976 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
977 disposals=(DisposeType *) AcquireQuantumMemory((size_t)
978 GetImageListLength(image),(add_frames != MagickFalse ? 2UL : 1UL)*
980 if (disposals == (DisposeType *) NULL)
982 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
983 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
986 Initialise Previous Image as fully transparent
988 prev_image=CloneImage(curr,curr->page.width,curr->page.height,
989 MagickTrue,exception);
990 if (prev_image == (Image *) NULL)
992 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
993 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
994 return((Image *) NULL);
996 prev_image->page=curr->page; /* ERROR: <-- should not be need, but is! */
997 prev_image->page.x=0;
998 prev_image->page.y=0;
999 prev_image->dispose=NoneDispose;
1001 prev_image->background_color.alpha=(Quantum) TransparentAlpha;
1002 (void) SetImageBackgroundColor(prev_image);
1004 Figure out the area of overlay of the first frame
1005 No pixel could be cleared as all pixels are already cleared.
1009 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
1011 disposals[0]=NoneDispose;
1012 bounds[0]=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
1014 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g\n\n",
1015 (double) bounds[i].width,(double) bounds[i].height,
1016 (double) bounds[i].x,(double) bounds[i].y );
1019 Compute the bounding box of changes for each pair of images.
1022 bgnd_image=(Image *)NULL;
1023 dup_image=(Image *)NULL;
1025 dup_bounds.height=0;
1028 curr=GetNextImageInList(curr);
1029 for ( ; curr != (const Image *) NULL; curr=GetNextImageInList(curr))
1032 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
1035 Assume none disposal is the best
1037 bounds[i]=CompareImagesBounds(curr->previous,curr,CompareAnyLayer,exception);
1038 cleared=IsBoundsCleared(curr->previous,curr,&bounds[i],exception);
1039 disposals[i-1]=NoneDispose;
1041 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g%s%s\n",
1042 (double) bounds[i].width,(double) bounds[i].height,
1043 (double) bounds[i].x,(double) bounds[i].y,
1044 bounds[i].x < 0?" (unchanged)":"",
1045 cleared?" (pixels cleared)":"");
1047 if ( bounds[i].x < 0 ) {
1049 Image frame is exactly the same as the previous frame!
1050 If not adding frames leave it to be cropped down to a null image.
1051 Otherwise mark previous image for deleted, transfering its crop bounds
1052 to the current image.
1054 if ( add_frames && i>=2 ) {
1055 disposals[i-1]=DelDispose;
1056 disposals[i]=NoneDispose;
1057 bounds[i]=bounds[i-1];
1065 Compare a none disposal against a previous disposal
1067 try_bounds=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
1068 try_cleared=IsBoundsCleared(prev_image,curr,&try_bounds,exception);
1070 (void) FormatLocaleFile(stderr, "test_prev: %.20gx%.20g%+.20g%+.20g%s\n",
1071 (double) try_bounds.width,(double) try_bounds.height,
1072 (double) try_bounds.x,(double) try_bounds.y,
1073 try_cleared?" (pixels were cleared)":"");
1075 if ( (!try_cleared && cleared ) ||
1076 try_bounds.width * try_bounds.height
1077 < bounds[i].width * bounds[i].height )
1079 cleared=try_cleared;
1080 bounds[i]=try_bounds;
1081 disposals[i-1]=PreviousDispose;
1083 (void) FormatLocaleFile(stderr, "previous: accepted\n");
1085 (void) FormatLocaleFile(stderr, "previous: rejected\n");
1090 If we are allowed lets try a complex frame duplication.
1091 It is useless if the previous image already clears pixels correctly.
1092 This method will always clear all the pixels that need to be cleared.
1094 dup_bounds.width=dup_bounds.height=0; /* no dup, no pixel added */
1097 dup_image=CloneImage(curr->previous,curr->previous->page.width,
1098 curr->previous->page.height,MagickTrue,exception);
1099 if (dup_image == (Image *) NULL)
1101 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1102 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1103 prev_image=DestroyImage(prev_image);
1104 return((Image *) NULL);
1106 dup_bounds=CompareImagesBounds(dup_image,curr,CompareClearLayer,exception);
1107 ClearBounds(dup_image,&dup_bounds);
1108 try_bounds=CompareImagesBounds(dup_image,curr,CompareAnyLayer,exception);
1110 dup_bounds.width*dup_bounds.height
1111 +try_bounds.width*try_bounds.height
1112 < bounds[i].width * bounds[i].height )
1114 cleared=MagickFalse;
1115 bounds[i]=try_bounds;
1116 disposals[i-1]=DupDispose;
1117 /* to be finalised later, if found to be optimial */
1120 dup_bounds.width=dup_bounds.height=0;
1123 Now compare against a simple background disposal
1125 bgnd_image=CloneImage(curr->previous,curr->previous->page.width,
1126 curr->previous->page.height,MagickTrue,exception);
1127 if (bgnd_image == (Image *) NULL)
1129 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1130 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1131 prev_image=DestroyImage(prev_image);
1132 if ( dup_image != (Image *) NULL)
1133 dup_image=DestroyImage(dup_image);
1134 return((Image *) NULL);
1136 bgnd_bounds=bounds[i-1]; /* interum bounds of the previous image */
1137 ClearBounds(bgnd_image,&bgnd_bounds);
1138 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
1139 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1141 (void) FormatLocaleFile(stderr, "background: %s\n",
1142 try_cleared?"(pixels cleared)":"");
1147 Straight background disposal failed to clear pixels needed!
1148 Lets try expanding the disposal area of the previous frame, to
1149 include the pixels that are cleared. This guaranteed
1150 to work, though may not be the most optimized solution.
1152 try_bounds=CompareImagesBounds(curr->previous,curr,CompareClearLayer,exception);
1154 (void) FormatLocaleFile(stderr, "expand_clear: %.20gx%.20g%+.20g%+.20g%s\n",
1155 (double) try_bounds.width,(double) try_bounds.height,
1156 (double) try_bounds.x,(double) try_bounds.y,
1157 try_bounds.x<0?" (no expand nessary)":"");
1159 if ( bgnd_bounds.x < 0 )
1160 bgnd_bounds = try_bounds;
1164 (void) FormatLocaleFile(stderr, "expand_bgnd: %.20gx%.20g%+.20g%+.20g\n",
1165 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1166 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1168 if ( try_bounds.x < bgnd_bounds.x )
1170 bgnd_bounds.width+= bgnd_bounds.x-try_bounds.x;
1171 if ( bgnd_bounds.width < try_bounds.width )
1172 bgnd_bounds.width = try_bounds.width;
1173 bgnd_bounds.x = try_bounds.x;
1177 try_bounds.width += try_bounds.x - bgnd_bounds.x;
1178 if ( bgnd_bounds.width < try_bounds.width )
1179 bgnd_bounds.width = try_bounds.width;
1181 if ( try_bounds.y < bgnd_bounds.y )
1183 bgnd_bounds.height += bgnd_bounds.y - try_bounds.y;
1184 if ( bgnd_bounds.height < try_bounds.height )
1185 bgnd_bounds.height = try_bounds.height;
1186 bgnd_bounds.y = try_bounds.y;
1190 try_bounds.height += try_bounds.y - bgnd_bounds.y;
1191 if ( bgnd_bounds.height < try_bounds.height )
1192 bgnd_bounds.height = try_bounds.height;
1195 (void) FormatLocaleFile(stderr, " to : %.20gx%.20g%+.20g%+.20g\n",
1196 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1197 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1200 ClearBounds(bgnd_image,&bgnd_bounds);
1202 /* Something strange is happening with a specific animation
1203 * CompareAnyLayers (normal method) and CompareClearLayers returns the whole
1204 * image, which is not posibly correct! As verified by previous tests.
1205 * Something changed beyond the bgnd_bounds clearing. But without being able
1206 * to see, or writet he image at this point it is hard to tell what is wrong!
1207 * Only CompareOverlay seemed to return something sensible.
1209 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareClearLayer,exception);
1210 (void) FormatLocaleFile(stderr, "expand_ctst: %.20gx%.20g%+.20g%+.20g\n",
1211 (double) try_bounds.width,(double) try_bounds.height,
1212 (double) try_bounds.x,(double) try_bounds.y );
1213 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
1214 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1215 (void) FormatLocaleFile(stderr, "expand_any : %.20gx%.20g%+.20g%+.20g%s\n",
1216 (double) try_bounds.width,(double) try_bounds.height,
1217 (double) try_bounds.x,(double) try_bounds.y,
1218 try_cleared?" (pixels cleared)":"");
1220 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareOverlayLayer,exception);
1222 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1223 (void) FormatLocaleFile(stderr, "expand_test: %.20gx%.20g%+.20g%+.20g%s\n",
1224 (double) try_bounds.width,(double) try_bounds.height,
1225 (double) try_bounds.x,(double) try_bounds.y,
1226 try_cleared?" (pixels cleared)":"");
1230 Test if this background dispose is smaller than any of the
1231 other methods we tryed before this (including duplicated frame)
1234 bgnd_bounds.width*bgnd_bounds.height
1235 +try_bounds.width*try_bounds.height
1236 < bounds[i-1].width*bounds[i-1].height
1237 +dup_bounds.width*dup_bounds.height
1238 +bounds[i].width*bounds[i].height )
1240 cleared=MagickFalse;
1241 bounds[i-1]=bgnd_bounds;
1242 bounds[i]=try_bounds;
1243 if ( disposals[i-1] == DupDispose )
1244 dup_image=DestroyImage(dup_image);
1245 disposals[i-1]=BackgroundDispose;
1247 (void) FormatLocaleFile(stderr, "expand_bgnd: accepted\n");
1249 (void) FormatLocaleFile(stderr, "expand_bgnd: reject\n");
1254 Finalise choice of dispose, set new prev_image,
1255 and junk any extra images as appropriate,
1257 if ( disposals[i-1] == DupDispose )
1259 if (bgnd_image != (Image *) NULL)
1260 bgnd_image=DestroyImage(bgnd_image);
1261 prev_image=DestroyImage(prev_image);
1262 prev_image=dup_image, dup_image=(Image *) NULL;
1263 bounds[i+1]=bounds[i];
1264 bounds[i]=dup_bounds;
1265 disposals[i-1]=DupDispose;
1266 disposals[i]=BackgroundDispose;
1271 if ( dup_image != (Image *) NULL)
1272 dup_image=DestroyImage(dup_image);
1273 if ( disposals[i-1] != PreviousDispose )
1274 prev_image=DestroyImage(prev_image);
1275 if ( disposals[i-1] == BackgroundDispose )
1276 prev_image=bgnd_image, bgnd_image=(Image *)NULL;
1277 if (bgnd_image != (Image *) NULL)
1278 bgnd_image=DestroyImage(bgnd_image);
1279 if ( disposals[i-1] == NoneDispose )
1281 prev_image=CloneImage(curr->previous,curr->previous->page.width,
1282 curr->previous->page.height,MagickTrue,exception);
1283 if (prev_image == (Image *) NULL)
1285 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1286 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1287 return((Image *) NULL);
1292 assert(prev_image != (Image *) NULL);
1293 disposals[i]=disposals[i-1];
1295 (void) FormatLocaleFile(stderr, "final %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1297 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i-1]),
1298 (double) bounds[i-1].width, (double) bounds[i-1].height,
1299 (double) bounds[i-1].x, (double) bounds[i-1].y );
1302 (void) FormatLocaleFile(stderr, "interum %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1304 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i]),
1305 (double) bounds[i].width, (double) bounds[i].height,
1306 (double) bounds[i].x, (double) bounds[i].y );
1307 (void) FormatLocaleFile(stderr, "\n");
1311 prev_image=DestroyImage(prev_image);
1313 Optimize all images in sequence.
1315 sans_exception=AcquireExceptionInfo();
1317 curr=GetFirstImageInList(image);
1318 optimized_image=NewImageList();
1319 while ( curr != (const Image *) NULL )
1321 prev_image=CloneImage(curr,0,0,MagickTrue,exception);
1322 if (prev_image == (Image *) NULL)
1324 if ( disposals[i] == DelDispose ) {
1326 while ( disposals[i] == DelDispose ) {
1327 time += curr->delay*1000/curr->ticks_per_second;
1328 curr=GetNextImageInList(curr);
1331 time += curr->delay*1000/curr->ticks_per_second;
1332 prev_image->ticks_per_second = 100L;
1333 prev_image->delay = time*prev_image->ticks_per_second/1000;
1335 bgnd_image=CropImage(prev_image,&bounds[i],sans_exception);
1336 prev_image=DestroyImage(prev_image);
1337 if (bgnd_image == (Image *) NULL)
1339 bgnd_image->dispose=disposals[i];
1340 if ( disposals[i] == DupDispose ) {
1341 bgnd_image->delay=0;
1342 bgnd_image->dispose=NoneDispose;
1345 curr=GetNextImageInList(curr);
1346 AppendImageToList(&optimized_image,bgnd_image);
1349 sans_exception=DestroyExceptionInfo(sans_exception);
1350 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1351 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1352 if (curr != (Image *) NULL)
1354 optimized_image=DestroyImageList(optimized_image);
1355 return((Image *) NULL);
1357 return(GetFirstImageInList(optimized_image));
1361 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1365 % O p t i m i z e I m a g e L a y e r s %
1369 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1371 % OptimizeImageLayers() compares each image the GIF disposed forms of the
1372 % previous image in the sequence. From this it attempts to select the
1373 % smallest cropped image to replace each frame, while preserving the results
1374 % of the GIF animation.
1376 % The format of the OptimizeImageLayers method is:
1378 % Image *OptimizeImageLayers(const Image *image,
1379 % ExceptionInfo *exception)
1381 % A description of each parameter follows:
1383 % o image: the image.
1385 % o exception: return any errors or warnings in this structure.
1388 MagickExport Image *OptimizeImageLayers(const Image *image,
1389 ExceptionInfo *exception)
1391 return(OptimizeLayerFrames(image,OptimizeImageLayer,exception));
1395 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1399 % O p t i m i z e P l u s I m a g e L a y e r s %
1403 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1405 % OptimizeImagePlusLayers() is exactly as OptimizeImageLayers(), but may
1406 % also add or even remove extra frames in the animation, if it improves
1407 % the total number of pixels in the resulting GIF animation.
1409 % The format of the OptimizePlusImageLayers method is:
1411 % Image *OptimizePlusImageLayers(const Image *image,
1412 % ExceptionInfo *exception)
1414 % A description of each parameter follows:
1416 % o image: the image.
1418 % o exception: return any errors or warnings in this structure.
1421 MagickExport Image *OptimizePlusImageLayers(const Image *image,
1422 ExceptionInfo *exception)
1424 return OptimizeLayerFrames(image, OptimizePlusLayer, exception);
1428 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1432 % 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 %
1436 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1438 % OptimizeImageTransparency() takes a frame optimized GIF animation, and
1439 % compares the overlayed pixels against the disposal image resulting from all
1440 % the previous frames in the animation. Any pixel that does not change the
1441 % disposal image (and thus does not effect the outcome of an overlay) is made
1444 % WARNING: This modifies the current images directly, rather than generate
1445 % a new image sequence.
1447 % The format of the OptimizeImageTransperency method is:
1449 % void OptimizeImageTransperency(Image *image,ExceptionInfo *exception)
1451 % A description of each parameter follows:
1453 % o image: the image sequence
1455 % o exception: return any errors or warnings in this structure.
1458 MagickExport void OptimizeImageTransparency(const Image *image,
1459 ExceptionInfo *exception)
1468 Run the image through the animation sequence
1470 assert(image != (Image *) NULL);
1471 assert(image->signature == MagickSignature);
1472 if (image->debug != MagickFalse)
1473 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1474 assert(exception != (ExceptionInfo *) NULL);
1475 assert(exception->signature == MagickSignature);
1476 next=GetFirstImageInList(image);
1477 dispose_image=CloneImage(next,next->page.width,next->page.height,
1478 MagickTrue,exception);
1479 if (dispose_image == (Image *) NULL)
1481 dispose_image->page=next->page;
1482 dispose_image->page.x=0;
1483 dispose_image->page.y=0;
1484 dispose_image->dispose=NoneDispose;
1485 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
1486 (void) SetImageBackgroundColor(dispose_image);
1488 while ( next != (Image *) NULL )
1494 Overlay this frame's image over the previous disposal image
1496 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
1497 if (current_image == (Image *) NULL)
1499 dispose_image=DestroyImage(dispose_image);
1502 (void) CompositeImage(current_image,next->matte != MagickFalse ?
1503 OverCompositeOp : CopyCompositeOp, next,next->page.x,next->page.y);
1505 At this point the image would be displayed, for the delay period
1507 Work out the disposal of the previous image
1509 if (next->dispose == BackgroundDispose)
1514 bounds.width=next->columns;
1515 bounds.height=next->rows;
1518 bounds.width+=bounds.x;
1521 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
1522 bounds.width=current_image->columns-bounds.x;
1525 bounds.height+=bounds.y;
1528 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
1529 bounds.height=current_image->rows-bounds.y;
1530 ClearBounds(current_image, &bounds);
1532 if (next->dispose != PreviousDispose)
1534 dispose_image=DestroyImage(dispose_image);
1535 dispose_image=current_image;
1538 current_image=DestroyImage(current_image);
1541 Optimize Transparency of the next frame (if present)
1543 next=GetNextImageInList(next);
1544 if ( next != (Image *) NULL ) {
1545 (void) CompositeImage(next, ChangeMaskCompositeOp,
1546 dispose_image, -(next->page.x), -(next->page.y) );
1549 dispose_image=DestroyImage(dispose_image);
1554 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1558 % R e m o v e D u p l i c a t e L a y e r s %
1562 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1564 % RemoveDuplicateLayers() removes any image that is exactly the same as the
1565 % next image in the given image list. Image size and virtual canvas offset
1566 % must also match, though not the virtual canvas size itself.
1568 % No check is made with regards to image disposal setting, though it is the
1569 % dispose setting of later image that is kept. Also any time delays are also
1570 % added together. As such coalesced image animations should still produce the
1571 % same result, though with duplicte frames merged into a single frame.
1573 % The format of the RemoveDuplicateLayers method is:
1575 % void RemoveDuplicateLayers(Image **image, ExceptionInfo *exception)
1577 % A description of each parameter follows:
1579 % o images: the image list
1581 % o exception: return any errors or warnings in this structure.
1584 MagickExport void RemoveDuplicateLayers(Image **images,
1585 ExceptionInfo *exception)
1594 assert((*images) != (const Image *) NULL);
1595 assert((*images)->signature == MagickSignature);
1596 if ((*images)->debug != MagickFalse)
1597 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1598 assert(exception != (ExceptionInfo *) NULL);
1599 assert(exception->signature == MagickSignature);
1601 curr=GetFirstImageInList(*images);
1602 for (; (next=GetNextImageInList(curr)) != (Image *) NULL; curr=next)
1604 if ( curr->columns != next->columns || curr->rows != next->rows
1605 || curr->page.x != next->page.x || curr->page.y != next->page.y )
1607 bounds=CompareImagesBounds(curr,next,CompareAnyLayer,exception);
1608 if ( bounds.x < 0 ) {
1610 the two images are the same, merge time delays and delete one.
1613 time = curr->delay*1000/curr->ticks_per_second;
1614 time += next->delay*1000/next->ticks_per_second;
1615 next->ticks_per_second = 100L;
1616 next->delay = time*curr->ticks_per_second/1000;
1617 next->iterations = curr->iterations;
1619 (void) DeleteImageFromList(images);
1622 *images = GetFirstImageInList(*images);
1626 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1630 % R e m o v e Z e r o D e l a y L a y e r s %
1634 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1636 % RemoveZeroDelayLayers() removes any image that as a zero delay time. Such
1637 % images generally represent intermediate or partial updates in GIF
1638 % animations used for file optimization. They are not ment to be displayed
1639 % to users of the animation. Viewable images in an animation should have a
1640 % time delay of 3 or more centi-seconds (hundredths of a second).
1642 % However if all the frames have a zero time delay, then either the animation
1643 % is as yet incomplete, or it is not a GIF animation. This a non-sensible
1644 % situation, so no image will be removed and a 'Zero Time Animation' warning
1645 % (exception) given.
1647 % No warning will be given if no image was removed because all images had an
1648 % appropriate non-zero time delay set.
1650 % Due to the special requirements of GIF disposal handling, GIF animations
1651 % should be coalesced first, before calling this function, though that is not
1654 % The format of the RemoveZeroDelayLayers method is:
1656 % void RemoveZeroDelayLayers(Image **image, ExceptionInfo *exception)
1658 % A description of each parameter follows:
1660 % o images: the image list
1662 % o exception: return any errors or warnings in this structure.
1665 MagickExport void RemoveZeroDelayLayers(Image **images,
1666 ExceptionInfo *exception)
1671 assert((*images) != (const Image *) NULL);
1672 assert((*images)->signature == MagickSignature);
1673 if ((*images)->debug != MagickFalse)
1674 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1675 assert(exception != (ExceptionInfo *) NULL);
1676 assert(exception->signature == MagickSignature);
1678 i=GetFirstImageInList(*images);
1679 for ( ; i != (Image *) NULL; i=GetNextImageInList(i))
1680 if ( i->delay != 0L ) break;
1681 if ( i == (Image *) NULL ) {
1682 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
1683 "ZeroTimeAnimation","`%s'",GetFirstImageInList(*images)->filename);
1686 i=GetFirstImageInList(*images);
1687 while ( i != (Image *) NULL )
1689 if ( i->delay == 0L ) {
1690 (void) DeleteImageFromList(&i);
1694 i=GetNextImageInList(i);
1696 *images=GetFirstImageInList(*images);
1700 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1704 % C o m p o s i t e L a y e r s %
1708 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1710 % CompositeLayers() compose first image sequence (source) over the second
1711 % image sequence (destination), using the given compose method and offsets.
1713 % The pointers to the image list does not have to be the start of that image
1714 % list, but may start somewhere in the middle. Each layer from the two image
1715 % lists are composted together until the end of one of the image lists is
1716 % reached. The offset of each composition is also adjusted to match the
1717 % virtual canvas offsets of each layer. As such the given offset is relative
1718 % to the virtual canvas, and not the actual image.
1720 % No GIF disposal handling is performed, so GIF animations should be
1721 % coalesced before use. However this not a requirement, and individual
1722 % layer images may have any size or offset, for special compositions.
1724 % Special case:- If one of the image sequences is just a single image that
1725 % image is repeatally composed with all the images in the other image list.
1726 % Either the source or destination lists may be the single image, for this
1729 % The destination list will be expanded as needed to match number of source
1730 % image overlaid (from current position to end of list).
1732 % The format of the CompositeLayers method is:
1734 % void CompositeLayers(Image *destination,
1735 % const CompositeOperator compose, Image *source,
1736 % const ssize_t x_offset, const ssize_t y_offset,
1737 % ExceptionInfo *exception);
1739 % A description of each parameter follows:
1741 % o destination: the destination images and results
1743 % o source: source image(s) for the layer composition
1745 % o compose, x_offset, y_offset: arguments passed on to CompositeImages()
1747 % 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,
1754 x_offset+=source->page.x-destination->page.x;
1755 y_offset+=source->page.y-destination->page.y;
1756 (void) CompositeImage(destination,compose,source,x_offset,y_offset);
1759 MagickExport void CompositeLayers(Image *destination,
1760 const CompositeOperator compose, Image *source,const ssize_t x_offset,
1761 const ssize_t y_offset,ExceptionInfo *exception)
1763 assert(destination != (Image *) NULL);
1764 assert(destination->signature == MagickSignature);
1765 assert(source != (Image *) NULL);
1766 assert(source->signature == MagickSignature);
1767 assert(exception != (ExceptionInfo *) NULL);
1768 assert(exception->signature == MagickSignature);
1769 if (source->debug != MagickFalse || destination->debug != MagickFalse)
1770 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s - %s",
1771 source->filename, destination->filename);
1774 Overlay single source image over destation image/list
1776 if ( source->previous == (Image *) NULL && source->next == (Image *) NULL )
1777 while ( destination != (Image *) NULL )
1779 CompositeCanvas(destination, compose, source, x_offset, y_offset);
1780 destination=GetNextImageInList(destination);
1784 Overlay source image list over single destination
1785 Generating multiple clones of destination image to match source list.
1786 Original Destination image becomes first image of generated list.
1787 As such the image list pointer does not require any change in caller.
1788 Some animation attributes however also needs coping in this case.
1790 else if ( destination->previous == (Image *) NULL &&
1791 destination->next == (Image *) NULL )
1793 Image *dest = CloneImage(destination,0,0,MagickTrue,exception);
1795 CompositeCanvas(destination, compose, source, x_offset, y_offset);
1796 /* copy source image attributes ? */
1797 if ( source->next != (Image *) NULL )
1799 destination->delay = source->delay;
1800 destination->iterations = source->iterations;
1802 source=GetNextImageInList(source);
1804 while ( source != (Image *) NULL )
1806 AppendImageToList(&destination,
1807 CloneImage(dest,0,0,MagickTrue,exception));
1808 destination=GetLastImageInList(destination);
1810 CompositeCanvas(destination, compose, source, x_offset, y_offset);
1811 destination->delay = source->delay;
1812 destination->iterations = source->iterations;
1813 source=GetNextImageInList(source);
1815 dest=DestroyImage(dest);
1819 Overlay a source image list over a destination image list
1820 until either list runs out of images. (Does not repeat)
1823 while ( source != (Image *) NULL && destination != (Image *) NULL )
1825 CompositeCanvas(destination, compose, source, x_offset, y_offset);
1826 source=GetNextImageInList(source);
1827 destination=GetNextImageInList(destination);
1832 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1836 % M e r g e I m a g e L a y e r s %
1840 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1842 % MergeImageLayers() composes all the image layers from the current given
1843 % image onward to produce a single image of the merged layers.
1845 % The inital canvas's size depends on the given ImageLayerMethod, and is
1846 % initialized using the first images background color. The images
1847 % are then compositied onto that image in sequence using the given
1848 % composition that has been assigned to each individual image.
1850 % The format of the MergeImageLayers is:
1852 % Image *MergeImageLayers(const Image *image,
1853 % const ImageLayerMethod method, ExceptionInfo *exception)
1855 % A description of each parameter follows:
1857 % o image: the image list to be composited together
1859 % o method: the method of selecting the size of the initial canvas.
1861 % MergeLayer: Merge all layers onto a canvas just large enough
1862 % to hold all the actual images. The virtual canvas of the
1863 % first image is preserved but otherwise ignored.
1865 % FlattenLayer: Use the virtual canvas size of first image.
1866 % Images which fall outside this canvas is clipped.
1867 % This can be used to 'fill out' a given virtual canvas.
1869 % MosaicLayer: Start with the virtual canvas of the first image,
1870 % enlarging left and right edges to contain all images.
1871 % Images with negative offsets will be clipped.
1873 % TrimBoundsLayer: Determine the overall bounds of all the image
1874 % layers just as in "MergeLayer", then adjust the the canvas
1875 % and offsets to be relative to those bounds, without overlaying
1878 % WARNING: a new image is not returned, the original image
1879 % sequence page data is modified instead.
1881 % o exception: return any errors or warnings in this structure.
1884 MagickExport Image *MergeImageLayers(Image *image,
1885 const ImageLayerMethod method,ExceptionInfo *exception)
1887 #define MergeLayersTag "Merge/Layers"
1898 register const Image
1909 assert(image != (Image *) NULL);
1910 assert(image->signature == MagickSignature);
1911 if (image->debug != MagickFalse)
1912 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1913 assert(exception != (ExceptionInfo *) NULL);
1914 assert(exception->signature == MagickSignature);
1916 Determine canvas image size, and its virtual canvas size and offset
1919 width=image->columns;
1923 case TrimBoundsLayer:
1927 next = GetNextImageInList(image);
1928 for ( ; next != (Image *) NULL; next=GetNextImageInList(next)) {
1929 if ( page.x > next->page.x ) {
1930 width += page.x-next->page.x;
1931 page.x = next->page.x;
1933 if ( page.y > next->page.y ) {
1934 height += page.y-next->page.y;
1935 page.y = next->page.y;
1937 if ( (ssize_t) width < (next->page.x + (ssize_t)next->columns - page.x) )
1938 width = (size_t) next->page.x + (ssize_t)next->columns - page.x;
1939 if ( (ssize_t) height < (next->page.y + (ssize_t)next->rows - page.y) )
1940 height = (size_t) next->page.y + (ssize_t)next->rows - page.y;
1946 if ( page.width > 0 )
1948 if ( page.height > 0 )
1956 if ( page.width > 0 )
1958 if ( page.height > 0 )
1960 for (next=image; next != (Image *) NULL; next=GetNextImageInList(next)) {
1961 if (method == MosaicLayer) {
1962 page.x=next->page.x;
1963 page.y=next->page.y;
1964 if ( (ssize_t) width < (next->page.x + (ssize_t)next->columns) )
1965 width = (size_t) next->page.x + next->columns;
1966 if ( (ssize_t) height < (next->page.y + (ssize_t)next->rows) )
1967 height = (size_t) next->page.y + next->rows;
1977 /* set virtual canvas size if not defined */
1978 if ( page.width == 0 )
1979 page.width = (page.x < 0) ? width : width+page.x;
1980 if ( page.height == 0 )
1981 page.height = (page.y < 0) ? height : height+page.y;
1984 Handle "TrimBoundsLayer" method separately to normal 'layer merge'
1986 if ( method == TrimBoundsLayer ) {
1987 number_images=GetImageListLength(image);
1988 for (scene=0; scene < (ssize_t) number_images; scene++)
1990 image->page.x -= page.x;
1991 image->page.y -= page.y;
1992 image->page.width = width;
1993 image->page.height = height;
1994 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
1996 if (proceed == MagickFalse)
1998 image=GetNextImageInList(image);
2000 return((Image *) NULL);
2004 Create canvas size of width and height, and background color.
2006 canvas=CloneImage(image,width,height,MagickTrue,exception);
2007 if (canvas == (Image *) NULL)
2008 return((Image *) NULL);
2009 (void) SetImageBackgroundColor(canvas);
2011 canvas->dispose=UndefinedDispose;
2014 Compose images onto canvas, with progress monitor
2016 number_images=GetImageListLength(image);
2017 for (scene=0; scene < (ssize_t) number_images; scene++)
2019 (void) CompositeImage(canvas,image->compose,image,image->page.x-
2020 canvas->page.x,image->page.y-canvas->page.y);
2021 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2023 if (proceed == MagickFalse)
2025 image=GetNextImageInList(image);