2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5 % L AAA Y Y EEEEE RRRR %
9 % LLLLL A A Y EEEEE R R %
11 % MagickCore Image Layering Methods %
19 % Copyright 1999-2014 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/channel.h"
44 #include "MagickCore/color.h"
45 #include "MagickCore/color-private.h"
46 #include "MagickCore/composite.h"
47 #include "MagickCore/effect.h"
48 #include "MagickCore/exception.h"
49 #include "MagickCore/exception-private.h"
50 #include "MagickCore/geometry.h"
51 #include "MagickCore/image.h"
52 #include "MagickCore/layer.h"
53 #include "MagickCore/list.h"
54 #include "MagickCore/memory_.h"
55 #include "MagickCore/monitor.h"
56 #include "MagickCore/monitor-private.h"
57 #include "MagickCore/option.h"
58 #include "MagickCore/pixel-accessor.h"
59 #include "MagickCore/property.h"
60 #include "MagickCore/profile.h"
61 #include "MagickCore/resource_.h"
62 #include "MagickCore/resize.h"
63 #include "MagickCore/statistic.h"
64 #include "MagickCore/string_.h"
65 #include "MagickCore/transform.h"
68 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
72 + C l e a r B o u n d s %
76 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
78 % ClearBounds() Clear the area specified by the bounds in an image to
79 % transparency. This typically used to handle Background Disposal for the
80 % previous frame in an animation sequence.
82 % Warning: no bounds checks are performed, except for the null or missed
83 % image, for images that don't change. in all other cases bound must fall
88 % void ClearBounds(Image *image,RectangleInfo *bounds,
89 % ExceptionInfo *exception)
91 % A description of each parameter follows:
93 % o image: the image to had the area cleared in
95 % o bounds: the area to be clear within the imag image
97 % o exception: return any errors or warnings in this structure.
100 static void ClearBounds(Image *image,RectangleInfo *bounds,
101 ExceptionInfo *exception)
108 if (image->alpha_trait != BlendPixelTrait)
109 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
110 for (y=0; y < (ssize_t) bounds->height; y++)
118 q=GetAuthenticPixels(image,bounds->x,bounds->y+y,bounds->width,1,exception);
119 if (q == (Quantum *) NULL)
121 for (x=0; x < (ssize_t) bounds->width; x++)
123 SetPixelAlpha(image,TransparentAlpha,q);
124 q+=GetPixelChannels(image);
126 if (SyncAuthenticPixels(image,exception) == MagickFalse)
132 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
136 + I s B o u n d s C l e a r e d %
140 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
142 % IsBoundsCleared() tests whether any pixel in the bounds given, gets cleared
143 % when going from the first image to the second image. This typically used
144 % to check if a proposed disposal method will work successfully to generate
145 % the second frame image from the first disposed form of the previous frame.
147 % Warning: no bounds checks are performed, except for the null or missed
148 % image, for images that don't change. in all other cases bound must fall
153 % MagickBooleanType IsBoundsCleared(const Image *image1,
154 % const Image *image2,RectangleInfo bounds,ExceptionInfo *exception)
156 % A description of each parameter follows:
158 % o image1, image 2: the images to check for cleared pixels
160 % o bounds: the area to be clear within the imag image
162 % o exception: return any errors or warnings in this structure.
165 static MagickBooleanType IsBoundsCleared(const Image *image1,
166 const Image *image2,RectangleInfo *bounds,ExceptionInfo *exception)
168 register const Quantum
180 for (y=0; y < (ssize_t) bounds->height; y++)
182 p=GetVirtualPixels(image1,bounds->x,bounds->y+y,bounds->width,1,exception);
183 q=GetVirtualPixels(image2,bounds->x,bounds->y+y,bounds->width,1,exception);
184 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
186 for (x=0; x < (ssize_t) bounds->width; x++)
188 if ((GetPixelAlpha(image1,p) <= (Quantum) (QuantumRange/2)) &&
189 (GetPixelAlpha(image1,q) > (Quantum) (QuantumRange/2)))
191 p+=GetPixelChannels(image1);
194 if (x < (ssize_t) bounds->width)
197 return(y < (ssize_t) bounds->height ? MagickTrue : MagickFalse);
201 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
205 % C o a l e s c e I m a g e s %
209 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
211 % CoalesceImages() composites a set of images while respecting any page
212 % offsets and disposal methods. GIF, MIFF, and MNG animation sequences
213 % typically start with an image background and each subsequent image
214 % varies in size and offset. A new image sequence is returned with all
215 % images the same size as the first images virtual canvas and composited
216 % with the next image in the sequence.
218 % The format of the CoalesceImages method is:
220 % Image *CoalesceImages(Image *image,ExceptionInfo *exception)
222 % A description of each parameter follows:
224 % o image: the image sequence.
226 % o exception: return any errors or warnings in this structure.
229 MagickExport Image *CoalesceImages(const Image *image,ExceptionInfo *exception)
243 Coalesce the image sequence.
245 assert(image != (Image *) NULL);
246 assert(image->signature == MagickSignature);
247 if (image->debug != MagickFalse)
248 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
249 assert(exception != (ExceptionInfo *) NULL);
250 assert(exception->signature == MagickSignature);
251 next=GetFirstImageInList(image);
253 if (bounds.width == 0)
255 bounds.width=next->columns;
257 bounds.width+=bounds.x;
259 if (bounds.height == 0)
261 bounds.height=next->rows;
263 bounds.height+=bounds.y;
267 coalesce_image=CloneImage(next,bounds.width,bounds.height,MagickTrue,
269 if (coalesce_image == (Image *) NULL)
270 return((Image *) NULL);
271 (void) SetImageBackgroundColor(coalesce_image,exception);
272 coalesce_image->alpha_trait=next->alpha_trait;
273 coalesce_image->page=bounds;
274 coalesce_image->dispose=NoneDispose;
276 Coalesce rest of the images.
278 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
279 (void) CompositeImage(coalesce_image,next,CopyCompositeOp,MagickTrue,
280 next->page.x,next->page.y,exception);
281 next=GetNextImageInList(next);
282 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
285 Determine the bounds that was overlaid in the previous image.
287 previous=GetPreviousImageInList(next);
288 bounds=previous->page;
289 bounds.width=previous->columns;
290 bounds.height=previous->rows;
293 bounds.width+=bounds.x;
296 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) coalesce_image->columns)
297 bounds.width=coalesce_image->columns-bounds.x;
300 bounds.height+=bounds.y;
303 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) coalesce_image->rows)
304 bounds.height=coalesce_image->rows-bounds.y;
306 Replace the dispose image with the new coalesced image.
308 if (GetPreviousImageInList(next)->dispose != PreviousDispose)
310 dispose_image=DestroyImage(dispose_image);
311 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
312 if (dispose_image == (Image *) NULL)
314 coalesce_image=DestroyImageList(coalesce_image);
315 return((Image *) NULL);
319 Clear the overlaid area of the coalesced bounds for background disposal
321 if (next->previous->dispose == BackgroundDispose)
322 ClearBounds(dispose_image,&bounds,exception);
324 Next image is the dispose image, overlaid with next frame in sequence.
326 coalesce_image->next=CloneImage(dispose_image,0,0,MagickTrue,exception);
327 coalesce_image->next->previous=coalesce_image;
328 previous=coalesce_image;
329 coalesce_image=GetNextImageInList(coalesce_image);
330 (void) CompositeImage(coalesce_image,next,
331 next->alpha_trait == BlendPixelTrait ? OverCompositeOp : CopyCompositeOp,
332 MagickTrue,next->page.x,next->page.y,exception);
333 (void) CloneImageProfiles(coalesce_image,next);
334 (void) CloneImageProperties(coalesce_image,next);
335 (void) CloneImageArtifacts(coalesce_image,next);
336 coalesce_image->page=previous->page;
338 If a pixel goes opaque to transparent, use background dispose.
340 if (IsBoundsCleared(previous,coalesce_image,&bounds,exception) != MagickFalse)
341 coalesce_image->dispose=BackgroundDispose;
343 coalesce_image->dispose=NoneDispose;
344 previous->dispose=coalesce_image->dispose;
346 dispose_image=DestroyImage(dispose_image);
347 return(GetFirstImageInList(coalesce_image));
351 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
355 % D i s p o s e I m a g e s %
359 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
361 % DisposeImages() returns the coalesced frames of a GIF animation as it would
362 % appear after the GIF dispose method of that frame has been applied. That is
363 % it returned the appearance of each frame before the next is overlaid.
365 % The format of the DisposeImages method is:
367 % Image *DisposeImages(Image *image,ExceptionInfo *exception)
369 % A description of each parameter follows:
371 % o images: the image sequence.
373 % o exception: return any errors or warnings in this structure.
376 MagickExport Image *DisposeImages(const Image *images,ExceptionInfo *exception)
390 Run the image through the animation sequence
392 assert(images != (Image *) NULL);
393 assert(images->signature == MagickSignature);
394 if (images->debug != MagickFalse)
395 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
396 assert(exception != (ExceptionInfo *) NULL);
397 assert(exception->signature == MagickSignature);
398 image=GetFirstImageInList(images);
399 dispose_image=CloneImage(image,image->page.width,image->page.height,
400 MagickTrue,exception);
401 if (dispose_image == (Image *) NULL)
402 return((Image *) NULL);
403 dispose_image->page=image->page;
404 dispose_image->page.x=0;
405 dispose_image->page.y=0;
406 dispose_image->dispose=NoneDispose;
407 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
408 (void) SetImageBackgroundColor(dispose_image,exception);
409 dispose_images=NewImageList();
410 for (next=image; image != (Image *) NULL; image=GetNextImageInList(image))
416 Overlay this frame's image over the previous disposal image.
418 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
419 if (current_image == (Image *) NULL)
421 dispose_images=DestroyImageList(dispose_images);
422 dispose_image=DestroyImage(dispose_image);
423 return((Image *) NULL);
425 (void) CompositeImage(current_image,next,
426 next->alpha_trait == BlendPixelTrait ? OverCompositeOp : CopyCompositeOp,
427 MagickTrue,next->page.x,next->page.y,exception);
429 Handle Background dispose: image is displayed for the delay period.
431 if (next->dispose == BackgroundDispose)
434 bounds.width=next->columns;
435 bounds.height=next->rows;
438 bounds.width+=bounds.x;
441 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
442 bounds.width=current_image->columns-bounds.x;
445 bounds.height+=bounds.y;
448 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
449 bounds.height=current_image->rows-bounds.y;
450 ClearBounds(current_image,&bounds,exception);
453 Select the appropriate previous/disposed image.
455 if (next->dispose == PreviousDispose)
456 current_image=DestroyImage(current_image);
459 dispose_image=DestroyImage(dispose_image);
460 dispose_image=current_image;
461 current_image=(Image *) NULL;
464 Save the dispose image just calculated for return.
470 dispose=CloneImage(dispose_image,0,0,MagickTrue,exception);
471 if (dispose == (Image *) NULL)
473 dispose_images=DestroyImageList(dispose_images);
474 dispose_image=DestroyImage(dispose_image);
475 return((Image *) NULL);
477 (void) CloneImageProfiles(dispose,next);
478 (void) CloneImageProperties(dispose,next);
479 (void) CloneImageArtifacts(dispose,next);
482 dispose->dispose=next->dispose;
483 AppendImageToList(&dispose_images,dispose);
486 dispose_image=DestroyImage(dispose_image);
487 return(GetFirstImageInList(dispose_images));
491 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
495 + C o m p a r e P i x e l s %
499 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
501 % ComparePixels() Compare the two pixels and return true if the pixels
502 % differ according to the given LayerType comparision method.
504 % This currently only used internally by CompareImagesBounds(). It is
505 % doubtful that this sub-routine will be useful outside this module.
507 % The format of the ComparePixels method is:
509 % MagickBooleanType *ComparePixels(const LayerMethod method,
510 % const PixelInfo *p,const PixelInfo *q)
512 % A description of each parameter follows:
514 % o method: What differences to look for. Must be one of
515 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
517 % o p, q: the pixels to test for appropriate differences.
521 static MagickBooleanType ComparePixels(const LayerMethod method,
522 const PixelInfo *p,const PixelInfo *q)
529 Any change in pixel values
531 if (method == CompareAnyLayer)
532 return((MagickBooleanType)(IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
534 o1 = (p->alpha_trait == BlendPixelTrait) ? p->alpha : OpaqueAlpha;
535 o2 = (q->alpha_trait == BlendPixelTrait) ? q->alpha : OpaqueAlpha;
537 Pixel goes from opaque to transprency.
539 if (method == CompareClearLayer)
540 return((MagickBooleanType) ( (o1 <= ((double) QuantumRange/2.0)) &&
541 (o2 > ((double) QuantumRange/2.0)) ) );
543 Overlay would change first pixel by second.
545 if (method == CompareOverlayLayer)
547 if (o2 > ((double) QuantumRange/2.0))
549 return((MagickBooleanType) (IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
556 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
560 + C o m p a r e I m a g e B o u n d s %
564 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
566 % CompareImagesBounds() Given two images return the smallest rectangular area
567 % by which the two images differ, accourding to the given 'Compare...'
570 % This currently only used internally in this module, but may eventually
571 % be used by other modules.
573 % The format of the CompareImagesBounds method is:
575 % RectangleInfo *CompareImagesBounds(const LayerMethod method,
576 % const Image *image1, const Image *image2, ExceptionInfo *exception)
578 % A description of each parameter follows:
580 % o method: What differences to look for. Must be one of CompareAnyLayer,
581 % CompareClearLayer, CompareOverlayLayer.
583 % o image1, image2: the two images to compare.
585 % o exception: return any errors or warnings in this structure.
589 static RectangleInfo CompareImagesBounds(const Image *image1,
590 const Image *image2,const LayerMethod method,ExceptionInfo *exception)
599 register const Quantum
610 Set bounding box of the differences between images.
612 GetPixelInfo(image1,&pixel1);
613 GetPixelInfo(image2,&pixel2);
614 for (x=0; x < (ssize_t) image1->columns; x++)
616 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
617 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
618 if ((p == (const Quantum *) NULL) ||
619 (q == (Quantum *) NULL))
621 for (y=0; y < (ssize_t) image1->rows; y++)
623 GetPixelInfoPixel(image1,p,&pixel1);
624 GetPixelInfoPixel(image2,q,&pixel2);
625 if (ComparePixels(method,&pixel1,&pixel2))
627 p+=GetPixelChannels(image1);
630 if (y < (ssize_t) image1->rows)
633 if (x >= (ssize_t) image1->columns)
636 Images are identical, return a null image.
645 for (x=(ssize_t) image1->columns-1; x >= 0; x--)
647 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
648 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
649 if ((p == (const Quantum *) NULL) ||
650 (q == (Quantum *) NULL))
652 for (y=0; y < (ssize_t) image1->rows; y++)
654 GetPixelInfoPixel(image1,p,&pixel1);
655 GetPixelInfoPixel(image2,q,&pixel2);
656 if (ComparePixels(method,&pixel1,&pixel2))
658 p+=GetPixelChannels(image1);
661 if (y < (ssize_t) image1->rows)
664 bounds.width=(size_t) (x-bounds.x+1);
665 for (y=0; y < (ssize_t) image1->rows; y++)
667 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
668 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
669 if ((p == (const Quantum *) NULL) ||
670 (q == (Quantum *) NULL))
672 for (x=0; x < (ssize_t) image1->columns; x++)
674 GetPixelInfoPixel(image1,p,&pixel1);
675 GetPixelInfoPixel(image2,q,&pixel2);
676 if (ComparePixels(method,&pixel1,&pixel2))
678 p+=GetPixelChannels(image1);
681 if (x < (ssize_t) image1->columns)
685 for (y=(ssize_t) image1->rows-1; y >= 0; y--)
687 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
688 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
689 if ((p == (const Quantum *) NULL) ||
690 (q == (Quantum *) NULL))
692 for (x=0; x < (ssize_t) image1->columns; x++)
694 GetPixelInfoPixel(image1,p,&pixel1);
695 GetPixelInfoPixel(image2,q,&pixel2);
696 if (ComparePixels(method,&pixel1,&pixel2))
698 p+=GetPixelChannels(image1);
701 if (x < (ssize_t) image1->columns)
704 bounds.height=(size_t) (y-bounds.y+1);
709 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
713 % C o m p a r e I m a g e L a y e r s %
717 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
719 % CompareImagesLayers() compares each image with the next in a sequence and
720 % returns the minimum bounding region of all the pixel differences (of the
721 % LayerMethod specified) it discovers.
723 % Images do NOT have to be the same size, though it is best that all the
724 % images are 'coalesced' (images are all the same size, on a flattened
725 % canvas, so as to represent exactly how an specific frame should look).
727 % No GIF dispose methods are applied, so GIF animations must be coalesced
728 % before applying this image operator to find differences to them.
730 % The format of the CompareImagesLayers method is:
732 % Image *CompareImagesLayers(const Image *images,
733 % const LayerMethod method,ExceptionInfo *exception)
735 % A description of each parameter follows:
737 % o image: the image.
739 % o method: the layers type to compare images with. Must be one of...
740 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
742 % o exception: return any errors or warnings in this structure.
746 MagickExport Image *CompareImagesLayers(const Image *image,
747 const LayerMethod method, ExceptionInfo *exception)
763 assert(image != (const Image *) NULL);
764 assert(image->signature == MagickSignature);
765 if (image->debug != MagickFalse)
766 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
767 assert(exception != (ExceptionInfo *) NULL);
768 assert(exception->signature == MagickSignature);
769 assert((method == CompareAnyLayer) ||
770 (method == CompareClearLayer) ||
771 (method == CompareOverlayLayer));
773 Allocate bounds memory.
775 next=GetFirstImageInList(image);
776 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
777 GetImageListLength(next),sizeof(*bounds));
778 if (bounds == (RectangleInfo *) NULL)
779 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
781 Set up first comparision images.
783 image_a=CloneImage(next,next->page.width,next->page.height,
784 MagickTrue,exception);
785 if (image_a == (Image *) NULL)
787 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
788 return((Image *) NULL);
790 image_a->background_color.alpha=(Quantum) TransparentAlpha;
791 (void) SetImageBackgroundColor(image_a,exception);
792 image_a->page=next->page;
795 (void) CompositeImage(image_a,next,CopyCompositeOp,MagickTrue,next->page.x,
796 next->page.y,exception);
798 Compute the bounding box of changes for the later images
801 next=GetNextImageInList(next);
802 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
804 image_b=CloneImage(image_a,0,0,MagickTrue,exception);
805 if (image_b == (Image *) NULL)
807 image_a=DestroyImage(image_a);
808 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
809 return((Image *) NULL);
811 (void) CompositeImage(image_a,next,CopyCompositeOp,MagickTrue,next->page.x,
812 next->page.y,exception);
813 bounds[i]=CompareImagesBounds(image_b,image_a,method,exception);
814 image_b=DestroyImage(image_b);
817 image_a=DestroyImage(image_a);
819 Clone first image in sequence.
821 next=GetFirstImageInList(image);
822 layers=CloneImage(next,0,0,MagickTrue,exception);
823 if (layers == (Image *) NULL)
825 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
826 return((Image *) NULL);
829 Deconstruct the image sequence.
832 next=GetNextImageInList(next);
833 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
835 image_a=CloneImage(next,0,0,MagickTrue,exception);
836 if (image_a == (Image *) NULL)
838 image_b=CropImage(image_a,&bounds[i],exception);
839 image_a=DestroyImage(image_a);
840 if (image_b == (Image *) NULL)
842 AppendImageToList(&layers,image_b);
845 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
846 if (next != (Image *) NULL)
848 layers=DestroyImageList(layers);
849 return((Image *) NULL);
851 return(GetFirstImageInList(layers));
855 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
859 + O p t i m i z e L a y e r F r a m e s %
863 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
865 % OptimizeLayerFrames() takes a coalesced GIF animation, and compares each
866 % frame against the three different 'disposal' forms of the previous frame.
867 % From this it then attempts to select the smallest cropped image and
868 % disposal method needed to reproduce the resulting image.
870 % Note that this not easy, and may require the expansion of the bounds
871 % of previous frame, simply clear pixels for the next animation frame to
872 % transparency according to the selected dispose method.
874 % The format of the OptimizeLayerFrames method is:
876 % Image *OptimizeLayerFrames(const Image *image,
877 % const LayerMethod method, ExceptionInfo *exception)
879 % A description of each parameter follows:
881 % o image: the image.
883 % o method: the layers technique to optimize with. Must be one of...
884 % OptimizeImageLayer, or OptimizePlusLayer. The Plus form allows
885 % the addition of extra 'zero delay' frames to clear pixels from
886 % the previous frame, and the removal of frames that done change,
887 % merging the delay times together.
889 % o exception: return any errors or warnings in this structure.
893 Define a 'fake' dispose method where the frame is duplicated, (for
894 OptimizePlusLayer) with a extra zero time delay frame which does a
895 BackgroundDisposal to clear the pixels that need to be cleared.
897 #define DupDispose ((DisposeType)9)
899 Another 'fake' dispose method used to removed frames that don't change.
901 #define DelDispose ((DisposeType)8)
903 #define DEBUG_OPT_FRAME 0
905 static Image *OptimizeLayerFrames(const Image *image,
906 const LayerMethod method, ExceptionInfo *exception)
937 assert(image != (const Image *) NULL);
938 assert(image->signature == MagickSignature);
939 if (image->debug != MagickFalse)
940 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
941 assert(exception != (ExceptionInfo *) NULL);
942 assert(exception->signature == MagickSignature);
943 assert(method == OptimizeLayer ||
944 method == OptimizeImageLayer ||
945 method == OptimizePlusLayer);
947 Are we allowed to add/remove frames from animation?
949 add_frames=method == OptimizePlusLayer ? MagickTrue : MagickFalse;
951 Ensure all the images are the same size.
953 curr=GetFirstImageInList(image);
954 for (; curr != (Image *) NULL; curr=GetNextImageInList(curr))
956 if ((curr->columns != image->columns) || (curr->rows != image->rows))
957 ThrowImageException(OptionError,"ImagesAreNotTheSameSize");
959 FUTURE: also check that image is also fully coalesced (full page)
960 Though as long as they are the same size it should not matter.
964 Allocate memory (times 2 if we allow the use of frame duplications)
966 curr=GetFirstImageInList(image);
967 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
968 GetImageListLength(curr),(add_frames != MagickFalse ? 2UL : 1UL)*
970 if (bounds == (RectangleInfo *) NULL)
971 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
972 disposals=(DisposeType *) AcquireQuantumMemory((size_t)
973 GetImageListLength(image),(add_frames != MagickFalse ? 2UL : 1UL)*
975 if (disposals == (DisposeType *) NULL)
977 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
978 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
981 Initialise Previous Image as fully transparent
983 prev_image=CloneImage(curr,curr->page.width,curr->page.height,
984 MagickTrue,exception);
985 if (prev_image == (Image *) NULL)
987 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
988 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
989 return((Image *) NULL);
991 prev_image->page=curr->page; /* ERROR: <-- should not be need, but is! */
992 prev_image->page.x=0;
993 prev_image->page.y=0;
994 prev_image->dispose=NoneDispose;
996 prev_image->background_color.alpha=(Quantum) TransparentAlpha;
997 (void) SetImageBackgroundColor(prev_image,exception);
999 Figure out the area of overlay of the first frame
1000 No pixel could be cleared as all pixels are already cleared.
1004 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
1006 disposals[0]=NoneDispose;
1007 bounds[0]=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
1009 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g\n\n",
1010 (double) bounds[i].width,(double) bounds[i].height,
1011 (double) bounds[i].x,(double) bounds[i].y );
1014 Compute the bounding box of changes for each pair of images.
1017 bgnd_image=(Image *)NULL;
1018 dup_image=(Image *)NULL;
1020 dup_bounds.height=0;
1023 curr=GetNextImageInList(curr);
1024 for ( ; curr != (const Image *) NULL; curr=GetNextImageInList(curr))
1027 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
1030 Assume none disposal is the best
1032 bounds[i]=CompareImagesBounds(curr->previous,curr,CompareAnyLayer,exception);
1033 cleared=IsBoundsCleared(curr->previous,curr,&bounds[i],exception);
1034 disposals[i-1]=NoneDispose;
1036 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g%s%s\n",
1037 (double) bounds[i].width,(double) bounds[i].height,
1038 (double) bounds[i].x,(double) bounds[i].y,
1039 bounds[i].x < 0?" (unchanged)":"",
1040 cleared?" (pixels cleared)":"");
1042 if ( bounds[i].x < 0 ) {
1044 Image frame is exactly the same as the previous frame!
1045 If not adding frames leave it to be cropped down to a null image.
1046 Otherwise mark previous image for deleted, transfering its crop bounds
1047 to the current image.
1049 if ( add_frames && i>=2 ) {
1050 disposals[i-1]=DelDispose;
1051 disposals[i]=NoneDispose;
1052 bounds[i]=bounds[i-1];
1060 Compare a none disposal against a previous disposal
1062 try_bounds=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
1063 try_cleared=IsBoundsCleared(prev_image,curr,&try_bounds,exception);
1065 (void) FormatLocaleFile(stderr, "test_prev: %.20gx%.20g%+.20g%+.20g%s\n",
1066 (double) try_bounds.width,(double) try_bounds.height,
1067 (double) try_bounds.x,(double) try_bounds.y,
1068 try_cleared?" (pixels were cleared)":"");
1070 if ( (!try_cleared && cleared ) ||
1071 try_bounds.width * try_bounds.height
1072 < bounds[i].width * bounds[i].height )
1074 cleared=try_cleared;
1075 bounds[i]=try_bounds;
1076 disposals[i-1]=PreviousDispose;
1078 (void) FormatLocaleFile(stderr, "previous: accepted\n");
1080 (void) FormatLocaleFile(stderr, "previous: rejected\n");
1085 If we are allowed lets try a complex frame duplication.
1086 It is useless if the previous image already clears pixels correctly.
1087 This method will always clear all the pixels that need to be cleared.
1089 dup_bounds.width=dup_bounds.height=0; /* no dup, no pixel added */
1092 dup_image=CloneImage(curr->previous,curr->previous->page.width,
1093 curr->previous->page.height,MagickTrue,exception);
1094 if (dup_image == (Image *) NULL)
1096 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1097 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1098 prev_image=DestroyImage(prev_image);
1099 return((Image *) NULL);
1101 dup_bounds=CompareImagesBounds(dup_image,curr,CompareClearLayer,exception);
1102 ClearBounds(dup_image,&dup_bounds,exception);
1103 try_bounds=CompareImagesBounds(dup_image,curr,CompareAnyLayer,exception);
1105 dup_bounds.width*dup_bounds.height
1106 +try_bounds.width*try_bounds.height
1107 < bounds[i].width * bounds[i].height )
1109 cleared=MagickFalse;
1110 bounds[i]=try_bounds;
1111 disposals[i-1]=DupDispose;
1112 /* to be finalised later, if found to be optimial */
1115 dup_bounds.width=dup_bounds.height=0;
1118 Now compare against a simple background disposal
1120 bgnd_image=CloneImage(curr->previous,curr->previous->page.width,
1121 curr->previous->page.height,MagickTrue,exception);
1122 if (bgnd_image == (Image *) NULL)
1124 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1125 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1126 prev_image=DestroyImage(prev_image);
1127 if ( dup_image != (Image *) NULL)
1128 dup_image=DestroyImage(dup_image);
1129 return((Image *) NULL);
1131 bgnd_bounds=bounds[i-1]; /* interum bounds of the previous image */
1132 ClearBounds(bgnd_image,&bgnd_bounds,exception);
1133 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
1134 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1136 (void) FormatLocaleFile(stderr, "background: %s\n",
1137 try_cleared?"(pixels cleared)":"");
1142 Straight background disposal failed to clear pixels needed!
1143 Lets try expanding the disposal area of the previous frame, to
1144 include the pixels that are cleared. This guaranteed
1145 to work, though may not be the most optimized solution.
1147 try_bounds=CompareImagesBounds(curr->previous,curr,CompareClearLayer,exception);
1149 (void) FormatLocaleFile(stderr, "expand_clear: %.20gx%.20g%+.20g%+.20g%s\n",
1150 (double) try_bounds.width,(double) try_bounds.height,
1151 (double) try_bounds.x,(double) try_bounds.y,
1152 try_bounds.x<0?" (no expand nessary)":"");
1154 if ( bgnd_bounds.x < 0 )
1155 bgnd_bounds = try_bounds;
1159 (void) FormatLocaleFile(stderr, "expand_bgnd: %.20gx%.20g%+.20g%+.20g\n",
1160 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1161 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1163 if ( try_bounds.x < bgnd_bounds.x )
1165 bgnd_bounds.width+= bgnd_bounds.x-try_bounds.x;
1166 if ( bgnd_bounds.width < try_bounds.width )
1167 bgnd_bounds.width = try_bounds.width;
1168 bgnd_bounds.x = try_bounds.x;
1172 try_bounds.width += try_bounds.x - bgnd_bounds.x;
1173 if ( bgnd_bounds.width < try_bounds.width )
1174 bgnd_bounds.width = try_bounds.width;
1176 if ( try_bounds.y < bgnd_bounds.y )
1178 bgnd_bounds.height += bgnd_bounds.y - try_bounds.y;
1179 if ( bgnd_bounds.height < try_bounds.height )
1180 bgnd_bounds.height = try_bounds.height;
1181 bgnd_bounds.y = try_bounds.y;
1185 try_bounds.height += try_bounds.y - bgnd_bounds.y;
1186 if ( bgnd_bounds.height < try_bounds.height )
1187 bgnd_bounds.height = try_bounds.height;
1190 (void) FormatLocaleFile(stderr, " to : %.20gx%.20g%+.20g%+.20g\n",
1191 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1192 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1195 ClearBounds(bgnd_image,&bgnd_bounds,exception);
1197 /* Something strange is happening with a specific animation
1198 * CompareAnyLayers (normal method) and CompareClearLayers returns the whole
1199 * image, which is not posibly correct! As verified by previous tests.
1200 * Something changed beyond the bgnd_bounds clearing. But without being able
1201 * to see, or writet he image at this point it is hard to tell what is wrong!
1202 * Only CompareOverlay seemed to return something sensible.
1204 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareClearLayer,exception);
1205 (void) FormatLocaleFile(stderr, "expand_ctst: %.20gx%.20g%+.20g%+.20g\n",
1206 (double) try_bounds.width,(double) try_bounds.height,
1207 (double) try_bounds.x,(double) try_bounds.y );
1208 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
1209 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1210 (void) FormatLocaleFile(stderr, "expand_any : %.20gx%.20g%+.20g%+.20g%s\n",
1211 (double) try_bounds.width,(double) try_bounds.height,
1212 (double) try_bounds.x,(double) try_bounds.y,
1213 try_cleared?" (pixels cleared)":"");
1215 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareOverlayLayer,exception);
1217 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1218 (void) FormatLocaleFile(stderr, "expand_test: %.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)":"");
1225 Test if this background dispose is smaller than any of the
1226 other methods we tryed before this (including duplicated frame)
1229 bgnd_bounds.width*bgnd_bounds.height
1230 +try_bounds.width*try_bounds.height
1231 < bounds[i-1].width*bounds[i-1].height
1232 +dup_bounds.width*dup_bounds.height
1233 +bounds[i].width*bounds[i].height )
1235 cleared=MagickFalse;
1236 bounds[i-1]=bgnd_bounds;
1237 bounds[i]=try_bounds;
1238 if ( disposals[i-1] == DupDispose )
1239 dup_image=DestroyImage(dup_image);
1240 disposals[i-1]=BackgroundDispose;
1242 (void) FormatLocaleFile(stderr, "expand_bgnd: accepted\n");
1244 (void) FormatLocaleFile(stderr, "expand_bgnd: reject\n");
1249 Finalise choice of dispose, set new prev_image,
1250 and junk any extra images as appropriate,
1252 if ( disposals[i-1] == DupDispose )
1254 if (bgnd_image != (Image *) NULL)
1255 bgnd_image=DestroyImage(bgnd_image);
1256 prev_image=DestroyImage(prev_image);
1257 prev_image=dup_image, dup_image=(Image *) NULL;
1258 bounds[i+1]=bounds[i];
1259 bounds[i]=dup_bounds;
1260 disposals[i-1]=DupDispose;
1261 disposals[i]=BackgroundDispose;
1266 if ( dup_image != (Image *) NULL)
1267 dup_image=DestroyImage(dup_image);
1268 if ( disposals[i-1] != PreviousDispose )
1269 prev_image=DestroyImage(prev_image);
1270 if ( disposals[i-1] == BackgroundDispose )
1271 prev_image=bgnd_image, bgnd_image=(Image *)NULL;
1272 if (bgnd_image != (Image *) NULL)
1273 bgnd_image=DestroyImage(bgnd_image);
1274 if ( disposals[i-1] == NoneDispose )
1276 prev_image=CloneImage(curr->previous,curr->previous->page.width,
1277 curr->previous->page.height,MagickTrue,exception);
1278 if (prev_image == (Image *) NULL)
1280 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1281 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1282 return((Image *) NULL);
1287 assert(prev_image != (Image *) NULL);
1288 disposals[i]=disposals[i-1];
1290 (void) FormatLocaleFile(stderr, "final %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1292 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i-1]),
1293 (double) bounds[i-1].width, (double) bounds[i-1].height,
1294 (double) bounds[i-1].x, (double) bounds[i-1].y );
1297 (void) FormatLocaleFile(stderr, "interum %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1299 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i]),
1300 (double) bounds[i].width, (double) bounds[i].height,
1301 (double) bounds[i].x, (double) bounds[i].y );
1302 (void) FormatLocaleFile(stderr, "\n");
1306 prev_image=DestroyImage(prev_image);
1308 Optimize all images in sequence.
1310 sans_exception=AcquireExceptionInfo();
1312 curr=GetFirstImageInList(image);
1313 optimized_image=NewImageList();
1314 while ( curr != (const Image *) NULL )
1316 prev_image=CloneImage(curr,0,0,MagickTrue,exception);
1317 if (prev_image == (Image *) NULL)
1319 if ( disposals[i] == DelDispose ) {
1321 while ( disposals[i] == DelDispose ) {
1322 time += curr->delay*1000/curr->ticks_per_second;
1323 curr=GetNextImageInList(curr);
1326 time += curr->delay*1000/curr->ticks_per_second;
1327 prev_image->ticks_per_second = 100L;
1328 prev_image->delay = time*prev_image->ticks_per_second/1000;
1330 bgnd_image=CropImage(prev_image,&bounds[i],sans_exception);
1331 prev_image=DestroyImage(prev_image);
1332 if (bgnd_image == (Image *) NULL)
1334 bgnd_image->dispose=disposals[i];
1335 if ( disposals[i] == DupDispose ) {
1336 bgnd_image->delay=0;
1337 bgnd_image->dispose=NoneDispose;
1340 curr=GetNextImageInList(curr);
1341 AppendImageToList(&optimized_image,bgnd_image);
1344 sans_exception=DestroyExceptionInfo(sans_exception);
1345 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1346 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1347 if (curr != (Image *) NULL)
1349 optimized_image=DestroyImageList(optimized_image);
1350 return((Image *) NULL);
1352 return(GetFirstImageInList(optimized_image));
1356 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1360 % O p t i m i z e I m a g e L a y e r s %
1364 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1366 % OptimizeImageLayers() compares each image the GIF disposed forms of the
1367 % previous image in the sequence. From this it attempts to select the
1368 % smallest cropped image to replace each frame, while preserving the results
1369 % of the GIF animation.
1371 % The format of the OptimizeImageLayers method is:
1373 % Image *OptimizeImageLayers(const Image *image,
1374 % ExceptionInfo *exception)
1376 % A description of each parameter follows:
1378 % o image: the image.
1380 % o exception: return any errors or warnings in this structure.
1383 MagickExport Image *OptimizeImageLayers(const Image *image,
1384 ExceptionInfo *exception)
1386 return(OptimizeLayerFrames(image,OptimizeImageLayer,exception));
1390 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1394 % O p t i m i z e P l u s I m a g e L a y e r s %
1398 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1400 % OptimizeImagePlusLayers() is exactly as OptimizeImageLayers(), but may
1401 % also add or even remove extra frames in the animation, if it improves
1402 % the total number of pixels in the resulting GIF animation.
1404 % The format of the OptimizePlusImageLayers method is:
1406 % Image *OptimizePlusImageLayers(const Image *image,
1407 % ExceptionInfo *exception)
1409 % A description of each parameter follows:
1411 % o image: the image.
1413 % o exception: return any errors or warnings in this structure.
1416 MagickExport Image *OptimizePlusImageLayers(const Image *image,
1417 ExceptionInfo *exception)
1419 return OptimizeLayerFrames(image, OptimizePlusLayer, exception);
1423 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1427 % 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 %
1431 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1433 % OptimizeImageTransparency() takes a frame optimized GIF animation, and
1434 % compares the overlayed pixels against the disposal image resulting from all
1435 % the previous frames in the animation. Any pixel that does not change the
1436 % disposal image (and thus does not effect the outcome of an overlay) is made
1439 % WARNING: This modifies the current images directly, rather than generate
1440 % a new image sequence.
1442 % The format of the OptimizeImageTransperency method is:
1444 % void OptimizeImageTransperency(Image *image,ExceptionInfo *exception)
1446 % A description of each parameter follows:
1448 % o image: the image sequence
1450 % o exception: return any errors or warnings in this structure.
1453 MagickExport void OptimizeImageTransparency(const Image *image,
1454 ExceptionInfo *exception)
1463 Run the image through the animation sequence
1465 assert(image != (Image *) NULL);
1466 assert(image->signature == MagickSignature);
1467 if (image->debug != MagickFalse)
1468 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1469 assert(exception != (ExceptionInfo *) NULL);
1470 assert(exception->signature == MagickSignature);
1471 next=GetFirstImageInList(image);
1472 dispose_image=CloneImage(next,next->page.width,next->page.height,
1473 MagickTrue,exception);
1474 if (dispose_image == (Image *) NULL)
1476 dispose_image->page=next->page;
1477 dispose_image->page.x=0;
1478 dispose_image->page.y=0;
1479 dispose_image->dispose=NoneDispose;
1480 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
1481 (void) SetImageBackgroundColor(dispose_image,exception);
1483 while ( next != (Image *) NULL )
1489 Overlay this frame's image over the previous disposal image
1491 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
1492 if (current_image == (Image *) NULL)
1494 dispose_image=DestroyImage(dispose_image);
1497 (void) CompositeImage(current_image,next,next->alpha_trait == BlendPixelTrait ?
1498 OverCompositeOp : CopyCompositeOp,MagickTrue,next->page.x,next->page.y,
1501 At this point the image would be displayed, for the delay period
1503 Work out the disposal of the previous image
1505 if (next->dispose == BackgroundDispose)
1510 bounds.width=next->columns;
1511 bounds.height=next->rows;
1514 bounds.width+=bounds.x;
1517 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
1518 bounds.width=current_image->columns-bounds.x;
1521 bounds.height+=bounds.y;
1524 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
1525 bounds.height=current_image->rows-bounds.y;
1526 ClearBounds(current_image, &bounds,exception);
1528 if (next->dispose != PreviousDispose)
1530 dispose_image=DestroyImage(dispose_image);
1531 dispose_image=current_image;
1534 current_image=DestroyImage(current_image);
1537 Optimize Transparency of the next frame (if present)
1539 next=GetNextImageInList(next);
1540 if (next != (Image *) NULL) {
1541 (void) CompositeImage(next,dispose_image,ChangeMaskCompositeOp,
1542 MagickTrue,-(next->page.x),-(next->page.y),exception);
1545 dispose_image=DestroyImage(dispose_image);
1550 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1554 % R e m o v e D u p l i c a t e L a y e r s %
1558 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1560 % RemoveDuplicateLayers() removes any image that is exactly the same as the
1561 % next image in the given image list. Image size and virtual canvas offset
1562 % must also match, though not the virtual canvas size itself.
1564 % No check is made with regards to image disposal setting, though it is the
1565 % dispose setting of later image that is kept. Also any time delays are also
1566 % added together. As such coalesced image animations should still produce the
1567 % same result, though with duplicte frames merged into a single frame.
1569 % The format of the RemoveDuplicateLayers method is:
1571 % void RemoveDuplicateLayers(Image **image, ExceptionInfo *exception)
1573 % A description of each parameter follows:
1575 % o images: the image list
1577 % o exception: return any errors or warnings in this structure.
1580 MagickExport void RemoveDuplicateLayers(Image **images,
1581 ExceptionInfo *exception)
1590 assert((*images) != (const Image *) NULL);
1591 assert((*images)->signature == MagickSignature);
1592 if ((*images)->debug != MagickFalse)
1593 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1594 assert(exception != (ExceptionInfo *) NULL);
1595 assert(exception->signature == MagickSignature);
1597 curr=GetFirstImageInList(*images);
1598 for (; (next=GetNextImageInList(curr)) != (Image *) NULL; curr=next)
1600 if ( curr->columns != next->columns || curr->rows != next->rows
1601 || curr->page.x != next->page.x || curr->page.y != next->page.y )
1603 bounds=CompareImagesBounds(curr,next,CompareAnyLayer,exception);
1604 if ( bounds.x < 0 ) {
1606 the two images are the same, merge time delays and delete one.
1609 time = curr->delay*1000/curr->ticks_per_second;
1610 time += next->delay*1000/next->ticks_per_second;
1611 next->ticks_per_second = 100L;
1612 next->delay = time*curr->ticks_per_second/1000;
1613 next->iterations = curr->iterations;
1615 (void) DeleteImageFromList(images);
1618 *images = GetFirstImageInList(*images);
1622 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1626 % R e m o v e Z e r o D e l a y L a y e r s %
1630 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1632 % RemoveZeroDelayLayers() removes any image that as a zero delay time. Such
1633 % images generally represent intermediate or partial updates in GIF
1634 % animations used for file optimization. They are not ment to be displayed
1635 % to users of the animation. Viewable images in an animation should have a
1636 % time delay of 3 or more centi-seconds (hundredths of a second).
1638 % However if all the frames have a zero time delay, then either the animation
1639 % is as yet incomplete, or it is not a GIF animation. This a non-sensible
1640 % situation, so no image will be removed and a 'Zero Time Animation' warning
1641 % (exception) given.
1643 % No warning will be given if no image was removed because all images had an
1644 % appropriate non-zero time delay set.
1646 % Due to the special requirements of GIF disposal handling, GIF animations
1647 % should be coalesced first, before calling this function, though that is not
1650 % The format of the RemoveZeroDelayLayers method is:
1652 % void RemoveZeroDelayLayers(Image **image, ExceptionInfo *exception)
1654 % A description of each parameter follows:
1656 % o images: the image list
1658 % o exception: return any errors or warnings in this structure.
1661 MagickExport void RemoveZeroDelayLayers(Image **images,
1662 ExceptionInfo *exception)
1667 assert((*images) != (const Image *) NULL);
1668 assert((*images)->signature == MagickSignature);
1669 if ((*images)->debug != MagickFalse)
1670 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1671 assert(exception != (ExceptionInfo *) NULL);
1672 assert(exception->signature == MagickSignature);
1674 i=GetFirstImageInList(*images);
1675 for ( ; i != (Image *) NULL; i=GetNextImageInList(i))
1676 if ( i->delay != 0L ) break;
1677 if ( i == (Image *) NULL ) {
1678 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
1679 "ZeroTimeAnimation","`%s'",GetFirstImageInList(*images)->filename);
1682 i=GetFirstImageInList(*images);
1683 while ( i != (Image *) NULL )
1685 if ( i->delay == 0L ) {
1686 (void) DeleteImageFromList(&i);
1690 i=GetNextImageInList(i);
1692 *images=GetFirstImageInList(*images);
1696 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1700 % C o m p o s i t e L a y e r s %
1704 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1706 % CompositeLayers() compose the source image sequence over the destination
1707 % image sequence, starting with the current image in both lists.
1709 % Each layer from the two image lists are composted together until the end of
1710 % one of the image lists is reached. The offset of each composition is also
1711 % adjusted to match the virtual canvas offsets of each layer. As such the
1712 % given offset is relative to the virtual canvas, and not the actual image.
1714 % Composition uses given x and y offsets, as the 'origin' location of the
1715 % source images virtual canvas (not the real image) allowing you to compose a
1716 % list of 'layer images' into the destiantioni images. This makes it well
1717 % sutiable for directly composing 'Clears Frame Animations' or 'Coaleased
1718 % Animations' onto a static or other 'Coaleased Animation' destination image
1719 % list. GIF disposal handling is not looked at.
1721 % Special case:- If one of the image sequences is the last image (just a
1722 % single image remaining), that image is repeatally composed with all the
1723 % images in the other image list. Either the source or destination lists may
1724 % be the single image, for this situation.
1726 % In the case of a single destination image (or last image given), that image
1727 % will ve cloned to match the number of images remaining in the source image
1730 % This is equivelent to the "-layer Composite" Shell API operator.
1733 % The format of the CompositeLayers method is:
1735 % void CompositeLayers(Image *destination, const CompositeOperator
1736 % compose, Image *source, 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.
1751 static inline void CompositeCanvas(Image *destination,
1752 const CompositeOperator compose,Image *source,ssize_t x_offset,
1753 ssize_t y_offset,ExceptionInfo *exception)
1755 x_offset+=source->page.x-destination->page.x;
1756 y_offset+=source->page.y-destination->page.y;
1757 (void) CompositeImage(destination,source,compose,MagickTrue,x_offset,
1758 y_offset,exception);
1761 MagickExport void CompositeLayers(Image *destination,
1762 const CompositeOperator compose, Image *source,const ssize_t x_offset,
1763 const ssize_t y_offset,ExceptionInfo *exception)
1765 assert(destination != (Image *) NULL);
1766 assert(destination->signature == MagickSignature);
1767 assert(source != (Image *) NULL);
1768 assert(source->signature == MagickSignature);
1769 assert(exception != (ExceptionInfo *) NULL);
1770 assert(exception->signature == MagickSignature);
1771 if (source->debug != MagickFalse || destination->debug != MagickFalse)
1772 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s - %s",
1773 source->filename, destination->filename);
1776 Overlay single source image over destation image/list
1778 if ( source->next == (Image *) NULL )
1779 while ( destination != (Image *) NULL )
1781 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1783 destination=GetNextImageInList(destination);
1787 Overlay source image list over single destination.
1788 Multiple clones of destination image are created to match source list.
1789 Original Destination image becomes first image of generated list.
1790 As such the image list pointer does not require any change in caller.
1791 Some animation attributes however also needs coping in this case.
1793 else if ( destination->next == (Image *) NULL )
1795 Image *dest = CloneImage(destination,0,0,MagickTrue,exception);
1797 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1799 /* copy source image attributes ? */
1800 if ( source->next != (Image *) NULL )
1802 destination->delay = source->delay;
1803 destination->iterations = source->iterations;
1805 source=GetNextImageInList(source);
1807 while ( source != (Image *) NULL )
1809 AppendImageToList(&destination,
1810 CloneImage(dest,0,0,MagickTrue,exception));
1811 destination=GetLastImageInList(destination);
1813 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1815 destination->delay = source->delay;
1816 destination->iterations = source->iterations;
1817 source=GetNextImageInList(source);
1819 dest=DestroyImage(dest);
1823 Overlay a source image list over a destination image list
1824 until either list runs out of images. (Does not repeat)
1827 while ( source != (Image *) NULL && destination != (Image *) NULL )
1829 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1831 source=GetNextImageInList(source);
1832 destination=GetNextImageInList(destination);
1837 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1841 % M e r g e I m a g e L a y e r s %
1845 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1847 % MergeImageLayers() composes all the image layers from the current given
1848 % image onward to produce a single image of the merged layers.
1850 % The inital canvas's size depends on the given LayerMethod, and is
1851 % initialized using the first images background color. The images
1852 % are then compositied onto that image in sequence using the given
1853 % composition that has been assigned to each individual image.
1855 % The format of the MergeImageLayers is:
1857 % Image *MergeImageLayers(const Image *image,
1858 % const LayerMethod method, ExceptionInfo *exception)
1860 % A description of each parameter follows:
1862 % o image: the image list to be composited together
1864 % o method: the method of selecting the size of the initial canvas.
1866 % MergeLayer: Merge all layers onto a canvas just large enough
1867 % to hold all the actual images. The virtual canvas of the
1868 % first image is preserved but otherwise ignored.
1870 % FlattenLayer: Use the virtual canvas size of first image.
1871 % Images which fall outside this canvas is clipped.
1872 % This can be used to 'fill out' a given virtual canvas.
1874 % MosaicLayer: Start with the virtual canvas of the first image,
1875 % enlarging left and right edges to contain all images.
1876 % Images with negative offsets will be clipped.
1878 % TrimBoundsLayer: Determine the overall bounds of all the image
1879 % layers just as in "MergeLayer", then adjust the the canvas
1880 % and offsets to be relative to those bounds, without overlaying
1883 % WARNING: a new image is not returned, the original image
1884 % sequence page data is modified instead.
1886 % o exception: return any errors or warnings in this structure.
1889 MagickExport Image *MergeImageLayers(Image *image,const LayerMethod method,
1890 ExceptionInfo *exception)
1892 #define MergeLayersTag "Merge/Layers"
1903 register const Image
1914 assert(image != (Image *) NULL);
1915 assert(image->signature == MagickSignature);
1916 if (image->debug != MagickFalse)
1917 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1918 assert(exception != (ExceptionInfo *) NULL);
1919 assert(exception->signature == MagickSignature);
1921 Determine canvas image size, and its virtual canvas size and offset
1924 width=image->columns;
1928 case TrimBoundsLayer:
1932 next=GetNextImageInList(image);
1933 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
1935 if (page.x > next->page.x)
1937 width+=page.x-next->page.x;
1938 page.x=next->page.x;
1940 if (page.y > next->page.y)
1942 height+=page.y-next->page.y;
1943 page.y=next->page.y;
1945 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns-page.x))
1946 width=(size_t) next->page.x+(ssize_t)next->columns-page.x;
1947 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows-page.y))
1948 height=(size_t) next->page.y+(ssize_t) next->rows-page.y;
1956 if (page.height > 0)
1966 if (page.height > 0)
1968 for (next=image; next != (Image *) NULL; next=GetNextImageInList(next))
1970 if (method == MosaicLayer)
1972 page.x=next->page.x;
1973 page.y=next->page.y;
1974 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns))
1975 width=(size_t) next->page.x+next->columns;
1976 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows))
1977 height=(size_t) next->page.y+next->rows;
1988 Set virtual canvas size if not defined.
1990 if (page.width == 0)
1991 page.width=page.x < 0 ? width : width+page.x;
1992 if (page.height == 0)
1993 page.height=page.y < 0 ? height : height+page.y;
1995 Handle "TrimBoundsLayer" method separately to normal 'layer merge'.
1997 if (method == TrimBoundsLayer)
1999 number_images=GetImageListLength(image);
2000 for (scene=0; scene < (ssize_t) number_images; scene++)
2002 image->page.x-=page.x;
2003 image->page.y-=page.y;
2004 image->page.width=width;
2005 image->page.height=height;
2006 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2008 if (proceed == MagickFalse)
2010 image=GetNextImageInList(image);
2011 if (image == (Image *) NULL)
2014 return((Image *) NULL);
2017 Create canvas size of width and height, and background color.
2019 canvas=CloneImage(image,width,height,MagickTrue,exception);
2020 if (canvas == (Image *) NULL)
2021 return((Image *) NULL);
2022 (void) SetImageBackgroundColor(canvas,exception);
2024 canvas->dispose=UndefinedDispose;
2026 Compose images onto canvas, with progress monitor
2028 number_images=GetImageListLength(image);
2029 for (scene=0; scene < (ssize_t) number_images; scene++)
2031 (void) CompositeImage(canvas,image,image->compose,MagickTrue,image->page.x-
2032 canvas->page.x,image->page.y-canvas->page.y,exception);
2033 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2035 if (proceed == MagickFalse)
2037 image=GetNextImageInList(image);
2038 if (image == (Image *) NULL)