2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5 % L AAA Y Y EEEEE RRRR %
9 % LLLLL A A Y EEEEE R R %
11 % MagickCore Image Layering Methods %
19 % Copyright 1999-2019 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 % https://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 == UndefinedPixelTrait)
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(image2,q) < (Quantum) (QuantumRange/2)))
191 p+=GetPixelChannels(image1);
192 q+=GetPixelChannels(image2);
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 == MagickCoreSignature);
247 if (image->debug != MagickFalse)
248 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
249 assert(exception != (ExceptionInfo *) NULL);
250 assert(exception->signature == MagickCoreSignature);
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 coalesce_image->background_color.alpha=(MagickRealType) TransparentAlpha;
272 (void) SetImageBackgroundColor(coalesce_image,exception);
273 coalesce_image->alpha_trait=next->alpha_trait;
274 coalesce_image->page=bounds;
275 coalesce_image->dispose=NoneDispose;
277 Coalesce rest of the images.
279 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
280 (void) CompositeImage(coalesce_image,next,CopyCompositeOp,MagickTrue,
281 next->page.x,next->page.y,exception);
282 next=GetNextImageInList(next);
283 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
286 Determine the bounds that was overlaid in the previous image.
288 previous=GetPreviousImageInList(next);
289 bounds=previous->page;
290 bounds.width=previous->columns;
291 bounds.height=previous->rows;
294 bounds.width+=bounds.x;
297 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) coalesce_image->columns)
298 bounds.width=coalesce_image->columns-bounds.x;
301 bounds.height+=bounds.y;
304 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) coalesce_image->rows)
305 bounds.height=coalesce_image->rows-bounds.y;
307 Replace the dispose image with the new coalesced image.
309 if (GetPreviousImageInList(next)->dispose != PreviousDispose)
311 dispose_image=DestroyImage(dispose_image);
312 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
313 if (dispose_image == (Image *) NULL)
315 coalesce_image=DestroyImageList(coalesce_image);
316 return((Image *) NULL);
320 Clear the overlaid area of the coalesced bounds for background disposal
322 if (next->previous->dispose == BackgroundDispose)
323 ClearBounds(dispose_image,&bounds,exception);
325 Next image is the dispose image, overlaid with next frame in sequence.
327 coalesce_image->next=CloneImage(dispose_image,0,0,MagickTrue,exception);
328 coalesce_image->next->previous=coalesce_image;
329 previous=coalesce_image;
330 coalesce_image=GetNextImageInList(coalesce_image);
331 (void) CompositeImage(coalesce_image,next,
332 next->alpha_trait != UndefinedPixelTrait ? OverCompositeOp : CopyCompositeOp,
333 MagickTrue,next->page.x,next->page.y,exception);
334 (void) CloneImageProfiles(coalesce_image,next);
335 (void) CloneImageProperties(coalesce_image,next);
336 (void) CloneImageArtifacts(coalesce_image,next);
337 coalesce_image->page=previous->page;
339 If a pixel goes opaque to transparent, use background dispose.
341 if (IsBoundsCleared(previous,coalesce_image,&bounds,exception) != MagickFalse)
342 coalesce_image->dispose=BackgroundDispose;
344 coalesce_image->dispose=NoneDispose;
345 previous->dispose=coalesce_image->dispose;
347 dispose_image=DestroyImage(dispose_image);
348 return(GetFirstImageInList(coalesce_image));
352 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
356 % D i s p o s e I m a g e s %
360 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
362 % DisposeImages() returns the coalesced frames of a GIF animation as it would
363 % appear after the GIF dispose method of that frame has been applied. That is
364 % it returned the appearance of each frame before the next is overlaid.
366 % The format of the DisposeImages method is:
368 % Image *DisposeImages(Image *image,ExceptionInfo *exception)
370 % A description of each parameter follows:
372 % o images: the image sequence.
374 % o exception: return any errors or warnings in this structure.
377 MagickExport Image *DisposeImages(const Image *images,ExceptionInfo *exception)
391 Run the image through the animation sequence
393 assert(images != (Image *) NULL);
394 assert(images->signature == MagickCoreSignature);
395 if (images->debug != MagickFalse)
396 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
397 assert(exception != (ExceptionInfo *) NULL);
398 assert(exception->signature == MagickCoreSignature);
399 image=GetFirstImageInList(images);
400 dispose_image=CloneImage(image,image->page.width,image->page.height,
401 MagickTrue,exception);
402 if (dispose_image == (Image *) NULL)
403 return((Image *) NULL);
404 dispose_image->page=image->page;
405 dispose_image->page.x=0;
406 dispose_image->page.y=0;
407 dispose_image->dispose=NoneDispose;
408 dispose_image->background_color.alpha=(MagickRealType) TransparentAlpha;
409 (void) SetImageBackgroundColor(dispose_image,exception);
410 dispose_images=NewImageList();
411 for (next=image; image != (Image *) NULL; image=GetNextImageInList(image))
417 Overlay this frame's image over the previous disposal image.
419 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
420 if (current_image == (Image *) NULL)
422 dispose_images=DestroyImageList(dispose_images);
423 dispose_image=DestroyImage(dispose_image);
424 return((Image *) NULL);
426 (void) CompositeImage(current_image,next,
427 next->alpha_trait != UndefinedPixelTrait ? OverCompositeOp : CopyCompositeOp,
428 MagickTrue,next->page.x,next->page.y,exception);
430 Handle Background dispose: image is displayed for the delay period.
432 if (next->dispose == BackgroundDispose)
435 bounds.width=next->columns;
436 bounds.height=next->rows;
439 bounds.width+=bounds.x;
442 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
443 bounds.width=current_image->columns-bounds.x;
446 bounds.height+=bounds.y;
449 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
450 bounds.height=current_image->rows-bounds.y;
451 ClearBounds(current_image,&bounds,exception);
454 Select the appropriate previous/disposed image.
456 if (next->dispose == PreviousDispose)
457 current_image=DestroyImage(current_image);
460 dispose_image=DestroyImage(dispose_image);
461 dispose_image=current_image;
462 current_image=(Image *) NULL;
465 Save the dispose image just calculated for return.
471 dispose=CloneImage(dispose_image,0,0,MagickTrue,exception);
472 if (dispose == (Image *) NULL)
474 dispose_images=DestroyImageList(dispose_images);
475 dispose_image=DestroyImage(dispose_image);
476 return((Image *) NULL);
478 (void) CloneImageProfiles(dispose,next);
479 (void) CloneImageProperties(dispose,next);
480 (void) CloneImageArtifacts(dispose,next);
483 dispose->dispose=next->dispose;
484 AppendImageToList(&dispose_images,dispose);
487 dispose_image=DestroyImage(dispose_image);
488 return(GetFirstImageInList(dispose_images));
492 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
496 + C o m p a r e P i x e l s %
500 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
502 % ComparePixels() Compare the two pixels and return true if the pixels
503 % differ according to the given LayerType comparision method.
505 % This currently only used internally by CompareImagesBounds(). It is
506 % doubtful that this sub-routine will be useful outside this module.
508 % The format of the ComparePixels method is:
510 % MagickBooleanType *ComparePixels(const LayerMethod method,
511 % const PixelInfo *p,const PixelInfo *q)
513 % A description of each parameter follows:
515 % o method: What differences to look for. Must be one of
516 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
518 % o p, q: the pixels to test for appropriate differences.
522 static MagickBooleanType ComparePixels(const LayerMethod method,
523 const PixelInfo *p,const PixelInfo *q)
530 Any change in pixel values
532 if (method == CompareAnyLayer)
533 return((MagickBooleanType)(IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
535 o1 = (p->alpha_trait != UndefinedPixelTrait) ? p->alpha : OpaqueAlpha;
536 o2 = (q->alpha_trait != UndefinedPixelTrait) ? q->alpha : OpaqueAlpha;
538 Pixel goes from opaque to transprency.
540 if (method == CompareClearLayer)
541 return((MagickBooleanType) ( (o1 >= ((double) QuantumRange/2.0)) &&
542 (o2 < ((double) QuantumRange/2.0)) ) );
544 Overlay would change first pixel by second.
546 if (method == CompareOverlayLayer)
548 if (o2 < ((double) QuantumRange/2.0))
550 return((MagickBooleanType) (IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
557 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
561 + C o m p a r e I m a g e B o u n d s %
565 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
567 % CompareImagesBounds() Given two images return the smallest rectangular area
568 % by which the two images differ, accourding to the given 'Compare...'
571 % This currently only used internally in this module, but may eventually
572 % be used by other modules.
574 % The format of the CompareImagesBounds method is:
576 % RectangleInfo *CompareImagesBounds(const LayerMethod method,
577 % const Image *image1,const Image *image2,ExceptionInfo *exception)
579 % A description of each parameter follows:
581 % o method: What differences to look for. Must be one of CompareAnyLayer,
582 % CompareClearLayer, CompareOverlayLayer.
584 % o image1, image2: the two images to compare.
586 % o exception: return any errors or warnings in this structure.
590 static RectangleInfo CompareImagesBounds(const Image *image1,
591 const Image *image2,const LayerMethod method,ExceptionInfo *exception)
600 register const Quantum
611 Set bounding box of the differences between images.
613 GetPixelInfo(image1,&pixel1);
614 GetPixelInfo(image2,&pixel2);
615 for (x=0; x < (ssize_t) image1->columns; x++)
617 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
618 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
619 if ((p == (const Quantum *) NULL) || (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);
628 q+=GetPixelChannels(image2);
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) || (q == (Quantum *) NULL))
651 for (y=0; y < (ssize_t) image1->rows; y++)
653 GetPixelInfoPixel(image1,p,&pixel1);
654 GetPixelInfoPixel(image2,q,&pixel2);
655 if (ComparePixels(method,&pixel1,&pixel2))
657 p+=GetPixelChannels(image1);
658 q+=GetPixelChannels(image2);
660 if (y < (ssize_t) image1->rows)
663 bounds.width=(size_t) (x-bounds.x+1);
664 for (y=0; y < (ssize_t) image1->rows; y++)
666 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
667 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
668 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
670 for (x=0; x < (ssize_t) image1->columns; x++)
672 GetPixelInfoPixel(image1,p,&pixel1);
673 GetPixelInfoPixel(image2,q,&pixel2);
674 if (ComparePixels(method,&pixel1,&pixel2))
676 p+=GetPixelChannels(image1);
677 q+=GetPixelChannels(image2);
679 if (x < (ssize_t) image1->columns)
683 for (y=(ssize_t) image1->rows-1; y >= 0; y--)
685 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
686 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
687 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
689 for (x=0; x < (ssize_t) image1->columns; x++)
691 GetPixelInfoPixel(image1,p,&pixel1);
692 GetPixelInfoPixel(image2,q,&pixel2);
693 if (ComparePixels(method,&pixel1,&pixel2))
695 p+=GetPixelChannels(image1);
696 q+=GetPixelChannels(image2);
698 if (x < (ssize_t) image1->columns)
701 bounds.height=(size_t) (y-bounds.y+1);
706 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
710 % C o m p a r e I m a g e L a y e r s %
714 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
716 % CompareImagesLayers() compares each image with the next in a sequence and
717 % returns the minimum bounding region of all the pixel differences (of the
718 % LayerMethod specified) it discovers.
720 % Images do NOT have to be the same size, though it is best that all the
721 % images are 'coalesced' (images are all the same size, on a flattened
722 % canvas, so as to represent exactly how an specific frame should look).
724 % No GIF dispose methods are applied, so GIF animations must be coalesced
725 % before applying this image operator to find differences to them.
727 % The format of the CompareImagesLayers method is:
729 % Image *CompareImagesLayers(const Image *images,
730 % const LayerMethod method,ExceptionInfo *exception)
732 % A description of each parameter follows:
734 % o image: the image.
736 % o method: the layers type to compare images with. Must be one of...
737 % CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
739 % o exception: return any errors or warnings in this structure.
743 MagickExport Image *CompareImagesLayers(const Image *image,
744 const LayerMethod method,ExceptionInfo *exception)
760 assert(image != (const Image *) NULL);
761 assert(image->signature == MagickCoreSignature);
762 if (image->debug != MagickFalse)
763 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
764 assert(exception != (ExceptionInfo *) NULL);
765 assert(exception->signature == MagickCoreSignature);
766 assert((method == CompareAnyLayer) ||
767 (method == CompareClearLayer) ||
768 (method == CompareOverlayLayer));
770 Allocate bounds memory.
772 next=GetFirstImageInList(image);
773 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
774 GetImageListLength(next),sizeof(*bounds));
775 if (bounds == (RectangleInfo *) NULL)
776 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
778 Set up first comparision images.
780 image_a=CloneImage(next,next->page.width,next->page.height,
781 MagickTrue,exception);
782 if (image_a == (Image *) NULL)
784 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
785 return((Image *) NULL);
787 image_a->background_color.alpha=(MagickRealType) TransparentAlpha;
788 (void) SetImageBackgroundColor(image_a,exception);
789 image_a->page=next->page;
792 (void) CompositeImage(image_a,next,CopyCompositeOp,MagickTrue,next->page.x,
793 next->page.y,exception);
795 Compute the bounding box of changes for the later images
798 next=GetNextImageInList(next);
799 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
801 image_b=CloneImage(image_a,0,0,MagickTrue,exception);
802 if (image_b == (Image *) NULL)
804 image_a=DestroyImage(image_a);
805 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
806 return((Image *) NULL);
808 (void) CompositeImage(image_a,next,CopyCompositeOp,MagickTrue,next->page.x,
809 next->page.y,exception);
810 bounds[i]=CompareImagesBounds(image_b,image_a,method,exception);
811 image_b=DestroyImage(image_b);
814 image_a=DestroyImage(image_a);
816 Clone first image in sequence.
818 next=GetFirstImageInList(image);
819 layers=CloneImage(next,0,0,MagickTrue,exception);
820 if (layers == (Image *) NULL)
822 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
823 return((Image *) NULL);
826 Deconstruct the image sequence.
829 next=GetNextImageInList(next);
830 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
832 if ((bounds[i].x == -1) && (bounds[i].y == -1) &&
833 (bounds[i].width == 1) && (bounds[i].height == 1))
836 An empty frame is returned from CompareImageBounds(), which means the
837 current frame is identical to the previous frame.
842 image_a=CloneImage(next,0,0,MagickTrue,exception);
843 if (image_a == (Image *) NULL)
845 image_b=CropImage(image_a,&bounds[i],exception);
846 image_a=DestroyImage(image_a);
847 if (image_b == (Image *) NULL)
849 AppendImageToList(&layers,image_b);
852 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
853 if (next != (Image *) NULL)
855 layers=DestroyImageList(layers);
856 return((Image *) NULL);
858 return(GetFirstImageInList(layers));
862 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
866 + O p t i m i z e L a y e r F r a m e s %
870 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
872 % OptimizeLayerFrames() takes a coalesced GIF animation, and compares each
873 % frame against the three different 'disposal' forms of the previous frame.
874 % From this it then attempts to select the smallest cropped image and
875 % disposal method needed to reproduce the resulting image.
877 % Note that this not easy, and may require the expansion of the bounds
878 % of previous frame, simply clear pixels for the next animation frame to
879 % transparency according to the selected dispose method.
881 % The format of the OptimizeLayerFrames method is:
883 % Image *OptimizeLayerFrames(const Image *image,
884 % const LayerMethod method,ExceptionInfo *exception)
886 % A description of each parameter follows:
888 % o image: the image.
890 % o method: the layers technique to optimize with. Must be one of...
891 % OptimizeImageLayer, or OptimizePlusLayer. The Plus form allows
892 % the addition of extra 'zero delay' frames to clear pixels from
893 % the previous frame, and the removal of frames that done change,
894 % merging the delay times together.
896 % o exception: return any errors or warnings in this structure.
900 Define a 'fake' dispose method where the frame is duplicated, (for
901 OptimizePlusLayer) with a extra zero time delay frame which does a
902 BackgroundDisposal to clear the pixels that need to be cleared.
904 #define DupDispose ((DisposeType)9)
906 Another 'fake' dispose method used to removed frames that don't change.
908 #define DelDispose ((DisposeType)8)
910 #define DEBUG_OPT_FRAME 0
912 static Image *OptimizeLayerFrames(const Image *image,const LayerMethod method,
913 ExceptionInfo *exception)
944 assert(image != (const Image *) NULL);
945 assert(image->signature == MagickCoreSignature);
946 if (image->debug != MagickFalse)
947 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
948 assert(exception != (ExceptionInfo *) NULL);
949 assert(exception->signature == MagickCoreSignature);
950 assert(method == OptimizeLayer ||
951 method == OptimizeImageLayer ||
952 method == OptimizePlusLayer);
954 Are we allowed to add/remove frames from animation?
956 add_frames=method == OptimizePlusLayer ? MagickTrue : MagickFalse;
958 Ensure all the images are the same size.
960 curr=GetFirstImageInList(image);
961 for (; curr != (Image *) NULL; curr=GetNextImageInList(curr))
963 if ((curr->columns != image->columns) || (curr->rows != image->rows))
964 ThrowImageException(OptionError,"ImagesAreNotTheSameSize");
966 if ((curr->page.x != 0) || (curr->page.y != 0) ||
967 (curr->page.width != image->page.width) ||
968 (curr->page.height != image->page.height))
969 ThrowImageException(OptionError,"ImagePagesAreNotCoalesced");
972 Allocate memory (times 2 if we allow the use of frame duplications)
974 curr=GetFirstImageInList(image);
975 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
976 GetImageListLength(curr),(add_frames != MagickFalse ? 2UL : 1UL)*
978 if (bounds == (RectangleInfo *) NULL)
979 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
980 disposals=(DisposeType *) AcquireQuantumMemory((size_t)
981 GetImageListLength(image),(add_frames != MagickFalse ? 2UL : 1UL)*
983 if (disposals == (DisposeType *) NULL)
985 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
986 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
989 Initialise Previous Image as fully transparent
991 prev_image=CloneImage(curr,curr->columns,curr->rows,MagickTrue,exception);
992 if (prev_image == (Image *) NULL)
994 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
995 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
996 return((Image *) NULL);
998 prev_image->page=curr->page; /* ERROR: <-- should not be need, but is! */
999 prev_image->page.x=0;
1000 prev_image->page.y=0;
1001 prev_image->dispose=NoneDispose;
1002 prev_image->background_color.alpha_trait=BlendPixelTrait;
1003 prev_image->background_color.alpha=(MagickRealType) TransparentAlpha;
1004 (void) SetImageBackgroundColor(prev_image,exception);
1006 Figure out the area of overlay of the first frame
1007 No pixel could be cleared as all pixels are already cleared.
1011 (void) FormatLocaleFile(stderr,"frame %.20g :-\n",(double) i);
1013 disposals[0]=NoneDispose;
1014 bounds[0]=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
1016 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g\n\n",
1017 (double) bounds[i].width,(double) bounds[i].height,
1018 (double) bounds[i].x,(double) bounds[i].y );
1021 Compute the bounding box of changes for each pair of images.
1024 bgnd_image=(Image *) NULL;
1025 dup_image=(Image *) NULL;
1027 dup_bounds.height=0;
1030 curr=GetNextImageInList(curr);
1031 for ( ; curr != (const Image *) NULL; curr=GetNextImageInList(curr))
1034 (void) FormatLocaleFile(stderr,"frame %.20g :-\n",(double) i);
1037 Assume none disposal is the best
1039 bounds[i]=CompareImagesBounds(curr->previous,curr,CompareAnyLayer,exception);
1040 cleared=IsBoundsCleared(curr->previous,curr,&bounds[i],exception);
1041 disposals[i-1]=NoneDispose;
1043 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g%s%s\n",
1044 (double) bounds[i].width,(double) bounds[i].height,
1045 (double) bounds[i].x,(double) bounds[i].y,
1046 bounds[i].x < 0?" (unchanged)":"",
1047 cleared?" (pixels cleared)":"");
1049 if ( bounds[i].x < 0 ) {
1051 Image frame is exactly the same as the previous frame!
1052 If not adding frames leave it to be cropped down to a null image.
1053 Otherwise mark previous image for deleted, transfering its crop bounds
1054 to the current image.
1056 if ( add_frames && i>=2 ) {
1057 disposals[i-1]=DelDispose;
1058 disposals[i]=NoneDispose;
1059 bounds[i]=bounds[i-1];
1067 Compare a none disposal against a previous disposal
1069 try_bounds=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
1070 try_cleared=IsBoundsCleared(prev_image,curr,&try_bounds,exception);
1072 (void) FormatLocaleFile(stderr, "test_prev: %.20gx%.20g%+.20g%+.20g%s\n",
1073 (double) try_bounds.width,(double) try_bounds.height,
1074 (double) try_bounds.x,(double) try_bounds.y,
1075 try_cleared?" (pixels were cleared)":"");
1077 if ( (!try_cleared && cleared ) ||
1078 try_bounds.width * try_bounds.height
1079 < bounds[i].width * bounds[i].height )
1081 cleared=try_cleared;
1082 bounds[i]=try_bounds;
1083 disposals[i-1]=PreviousDispose;
1085 (void) FormatLocaleFile(stderr,"previous: accepted\n");
1087 (void) FormatLocaleFile(stderr,"previous: rejected\n");
1092 If we are allowed lets try a complex frame duplication.
1093 It is useless if the previous image already clears pixels correctly.
1094 This method will always clear all the pixels that need to be cleared.
1096 dup_bounds.width=dup_bounds.height=0; /* no dup, no pixel added */
1099 dup_image=CloneImage(curr->previous,0,0,MagickTrue,exception);
1100 if (dup_image == (Image *) NULL)
1102 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1103 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1104 prev_image=DestroyImage(prev_image);
1105 return((Image *) NULL);
1107 dup_bounds=CompareImagesBounds(dup_image,curr,CompareClearLayer,exception);
1108 ClearBounds(dup_image,&dup_bounds,exception);
1109 try_bounds=CompareImagesBounds(dup_image,curr,CompareAnyLayer,exception);
1111 dup_bounds.width*dup_bounds.height
1112 +try_bounds.width*try_bounds.height
1113 < bounds[i].width * bounds[i].height )
1115 cleared=MagickFalse;
1116 bounds[i]=try_bounds;
1117 disposals[i-1]=DupDispose;
1118 /* to be finalised later, if found to be optimial */
1121 dup_bounds.width=dup_bounds.height=0;
1124 Now compare against a simple background disposal
1126 bgnd_image=CloneImage(curr->previous,0,0,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,exception);
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,exception);
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=ReferenceImage(curr->previous);
1282 if (prev_image == (Image *) NULL)
1284 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1285 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1286 return((Image *) NULL);
1291 assert(prev_image != (Image *) NULL);
1292 disposals[i]=disposals[i-1];
1294 (void) FormatLocaleFile(stderr, "final %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1296 CommandOptionToMnemonic(MagickDisposeOptions,disposals[i-1]),
1297 (double) bounds[i-1].width,(double) bounds[i-1].height,
1298 (double) bounds[i-1].x,(double) bounds[i-1].y );
1301 (void) FormatLocaleFile(stderr, "interum %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1303 CommandOptionToMnemonic(MagickDisposeOptions,disposals[i]),
1304 (double) bounds[i].width,(double) bounds[i].height,
1305 (double) bounds[i].x,(double) bounds[i].y );
1306 (void) FormatLocaleFile(stderr,"\n");
1310 prev_image=DestroyImage(prev_image);
1312 Optimize all images in sequence.
1314 sans_exception=AcquireExceptionInfo();
1316 curr=GetFirstImageInList(image);
1317 optimized_image=NewImageList();
1318 while ( curr != (const Image *) NULL )
1320 prev_image=CloneImage(curr,0,0,MagickTrue,exception);
1321 if (prev_image == (Image *) NULL)
1323 if (prev_image->alpha_trait == UndefinedPixelTrait)
1324 (void) SetImageAlphaChannel(prev_image,OpaqueAlphaChannel,exception);
1325 if ( disposals[i] == DelDispose ) {
1327 while ( disposals[i] == DelDispose ) {
1328 time += curr->delay*1000/curr->ticks_per_second;
1329 curr=GetNextImageInList(curr);
1332 time += curr->delay*1000/curr->ticks_per_second;
1333 prev_image->ticks_per_second = 100L;
1334 prev_image->delay = time*prev_image->ticks_per_second/1000;
1336 bgnd_image=CropImage(prev_image,&bounds[i],sans_exception);
1337 prev_image=DestroyImage(prev_image);
1338 if (bgnd_image == (Image *) NULL)
1340 bgnd_image->dispose=disposals[i];
1341 if ( disposals[i] == DupDispose ) {
1342 bgnd_image->delay=0;
1343 bgnd_image->dispose=NoneDispose;
1346 curr=GetNextImageInList(curr);
1347 AppendImageToList(&optimized_image,bgnd_image);
1350 sans_exception=DestroyExceptionInfo(sans_exception);
1351 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1352 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1353 if (curr != (Image *) NULL)
1355 optimized_image=DestroyImageList(optimized_image);
1356 return((Image *) NULL);
1358 return(GetFirstImageInList(optimized_image));
1362 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1366 % O p t i m i z e I m a g e L a y e r s %
1370 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1372 % OptimizeImageLayers() compares each image the GIF disposed forms of the
1373 % previous image in the sequence. From this it attempts to select the
1374 % smallest cropped image to replace each frame, while preserving the results
1375 % of the GIF animation.
1377 % The format of the OptimizeImageLayers method is:
1379 % Image *OptimizeImageLayers(const Image *image,
1380 % ExceptionInfo *exception)
1382 % A description of each parameter follows:
1384 % o image: the image.
1386 % o exception: return any errors or warnings in this structure.
1389 MagickExport Image *OptimizeImageLayers(const Image *image,
1390 ExceptionInfo *exception)
1392 return(OptimizeLayerFrames(image,OptimizeImageLayer,exception));
1396 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1400 % O p t i m i z e P l u s I m a g e L a y e r s %
1404 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1406 % OptimizeImagePlusLayers() is exactly as OptimizeImageLayers(), but may
1407 % also add or even remove extra frames in the animation, if it improves
1408 % the total number of pixels in the resulting GIF animation.
1410 % The format of the OptimizePlusImageLayers method is:
1412 % Image *OptimizePlusImageLayers(const Image *image,
1413 % ExceptionInfo *exception)
1415 % A description of each parameter follows:
1417 % o image: the image.
1419 % o exception: return any errors or warnings in this structure.
1422 MagickExport Image *OptimizePlusImageLayers(const Image *image,
1423 ExceptionInfo *exception)
1425 return OptimizeLayerFrames(image,OptimizePlusLayer,exception);
1429 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1433 % 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 %
1437 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1439 % OptimizeImageTransparency() takes a frame optimized GIF animation, and
1440 % compares the overlayed pixels against the disposal image resulting from all
1441 % the previous frames in the animation. Any pixel that does not change the
1442 % disposal image (and thus does not effect the outcome of an overlay) is made
1445 % WARNING: This modifies the current images directly, rather than generate
1446 % a new image sequence.
1448 % The format of the OptimizeImageTransperency method is:
1450 % void OptimizeImageTransperency(Image *image,ExceptionInfo *exception)
1452 % A description of each parameter follows:
1454 % o image: the image sequence
1456 % o exception: return any errors or warnings in this structure.
1459 MagickExport void OptimizeImageTransparency(const Image *image,
1460 ExceptionInfo *exception)
1469 Run the image through the animation sequence
1471 assert(image != (Image *) NULL);
1472 assert(image->signature == MagickCoreSignature);
1473 if (image->debug != MagickFalse)
1474 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1475 assert(exception != (ExceptionInfo *) NULL);
1476 assert(exception->signature == MagickCoreSignature);
1477 next=GetFirstImageInList(image);
1478 dispose_image=CloneImage(next,next->page.width,next->page.height,
1479 MagickTrue,exception);
1480 if (dispose_image == (Image *) NULL)
1482 dispose_image->page=next->page;
1483 dispose_image->page.x=0;
1484 dispose_image->page.y=0;
1485 dispose_image->dispose=NoneDispose;
1486 dispose_image->background_color.alpha_trait=BlendPixelTrait;
1487 dispose_image->background_color.alpha=(MagickRealType) TransparentAlpha;
1488 (void) SetImageBackgroundColor(dispose_image,exception);
1490 while ( next != (Image *) NULL )
1496 Overlay this frame's image over the previous disposal image
1498 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
1499 if (current_image == (Image *) NULL)
1501 dispose_image=DestroyImage(dispose_image);
1504 (void) CompositeImage(current_image,next,next->alpha_trait != UndefinedPixelTrait ?
1505 OverCompositeOp : CopyCompositeOp,MagickTrue,next->page.x,next->page.y,
1508 At this point the image would be displayed, for the delay period
1510 Work out the disposal of the previous image
1512 if (next->dispose == BackgroundDispose)
1517 bounds.width=next->columns;
1518 bounds.height=next->rows;
1521 bounds.width+=bounds.x;
1524 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
1525 bounds.width=current_image->columns-bounds.x;
1528 bounds.height+=bounds.y;
1531 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
1532 bounds.height=current_image->rows-bounds.y;
1533 ClearBounds(current_image,&bounds,exception);
1535 if (next->dispose != PreviousDispose)
1537 dispose_image=DestroyImage(dispose_image);
1538 dispose_image=current_image;
1541 current_image=DestroyImage(current_image);
1544 Optimize Transparency of the next frame (if present)
1546 next=GetNextImageInList(next);
1547 if (next != (Image *) NULL) {
1548 (void) CompositeImage(next,dispose_image,ChangeMaskCompositeOp,
1549 MagickTrue,-(next->page.x),-(next->page.y),exception);
1552 dispose_image=DestroyImage(dispose_image);
1557 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1561 % R e m o v e D u p l i c a t e L a y e r s %
1565 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1567 % RemoveDuplicateLayers() removes any image that is exactly the same as the
1568 % next image in the given image list. Image size and virtual canvas offset
1569 % must also match, though not the virtual canvas size itself.
1571 % No check is made with regards to image disposal setting, though it is the
1572 % dispose setting of later image that is kept. Also any time delays are also
1573 % added together. As such coalesced image animations should still produce the
1574 % same result, though with duplicte frames merged into a single frame.
1576 % The format of the RemoveDuplicateLayers method is:
1578 % void RemoveDuplicateLayers(Image **image,ExceptionInfo *exception)
1580 % A description of each parameter follows:
1582 % o images: the image list
1584 % o exception: return any errors or warnings in this structure.
1587 MagickExport void RemoveDuplicateLayers(Image **images,ExceptionInfo *exception)
1596 assert((*images) != (const Image *) NULL);
1597 assert((*images)->signature == MagickCoreSignature);
1598 if ((*images)->debug != MagickFalse)
1599 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
1600 (*images)->filename);
1601 assert(exception != (ExceptionInfo *) NULL);
1602 assert(exception->signature == MagickCoreSignature);
1603 image=GetFirstImageInList(*images);
1604 for ( ; (next=GetNextImageInList(image)) != (Image *) NULL; image=next)
1606 if ((image->columns != next->columns) || (image->rows != next->rows) ||
1607 (image->page.x != next->page.x) || (image->page.y != next->page.y))
1609 bounds=CompareImagesBounds(image,next,CompareAnyLayer,exception);
1613 Two images are the same, merge time delays and delete one.
1618 time=1000*image->delay*PerceptibleReciprocal(image->ticks_per_second);
1619 time+=1000*next->delay*PerceptibleReciprocal(next->ticks_per_second);
1620 next->ticks_per_second=100L;
1621 next->delay=time*image->ticks_per_second/1000;
1622 next->iterations=image->iterations;
1624 (void) DeleteImageFromList(images);
1627 *images=GetFirstImageInList(*images);
1631 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1635 % R e m o v e Z e r o D e l a y L a y e r s %
1639 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1641 % RemoveZeroDelayLayers() removes any image that as a zero delay time. Such
1642 % images generally represent intermediate or partial updates in GIF
1643 % animations used for file optimization. They are not ment to be displayed
1644 % to users of the animation. Viewable images in an animation should have a
1645 % time delay of 3 or more centi-seconds (hundredths of a second).
1647 % However if all the frames have a zero time delay, then either the animation
1648 % is as yet incomplete, or it is not a GIF animation. This a non-sensible
1649 % situation, so no image will be removed and a 'Zero Time Animation' warning
1650 % (exception) given.
1652 % No warning will be given if no image was removed because all images had an
1653 % appropriate non-zero time delay set.
1655 % Due to the special requirements of GIF disposal handling, GIF animations
1656 % should be coalesced first, before calling this function, though that is not
1659 % The format of the RemoveZeroDelayLayers method is:
1661 % void RemoveZeroDelayLayers(Image **image,ExceptionInfo *exception)
1663 % A description of each parameter follows:
1665 % o images: the image list
1667 % o exception: return any errors or warnings in this structure.
1670 MagickExport void RemoveZeroDelayLayers(Image **images,
1671 ExceptionInfo *exception)
1676 assert((*images) != (const Image *) NULL);
1677 assert((*images)->signature == MagickCoreSignature);
1678 if ((*images)->debug != MagickFalse)
1679 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1680 assert(exception != (ExceptionInfo *) NULL);
1681 assert(exception->signature == MagickCoreSignature);
1683 i=GetFirstImageInList(*images);
1684 for ( ; i != (Image *) NULL; i=GetNextImageInList(i))
1685 if ( i->delay != 0L ) break;
1686 if ( i == (Image *) NULL ) {
1687 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
1688 "ZeroTimeAnimation","`%s'",GetFirstImageInList(*images)->filename);
1691 i=GetFirstImageInList(*images);
1692 while ( i != (Image *) NULL )
1694 if ( i->delay == 0L ) {
1695 (void) DeleteImageFromList(&i);
1699 i=GetNextImageInList(i);
1701 *images=GetFirstImageInList(*images);
1705 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1709 % C o m p o s i t e L a y e r s %
1713 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1715 % CompositeLayers() compose the source image sequence over the destination
1716 % image sequence, starting with the current image in both lists.
1718 % Each layer from the two image lists are composted together until the end of
1719 % one of the image lists is reached. The offset of each composition is also
1720 % adjusted to match the virtual canvas offsets of each layer. As such the
1721 % given offset is relative to the virtual canvas, and not the actual image.
1723 % Composition uses given x and y offsets, as the 'origin' location of the
1724 % source images virtual canvas (not the real image) allowing you to compose a
1725 % list of 'layer images' into the destiantioni images. This makes it well
1726 % sutiable for directly composing 'Clears Frame Animations' or 'Coaleased
1727 % Animations' onto a static or other 'Coaleased Animation' destination image
1728 % list. GIF disposal handling is not looked at.
1730 % Special case:- If one of the image sequences is the last image (just a
1731 % single image remaining), that image is repeatally composed with all the
1732 % images in the other image list. Either the source or destination lists may
1733 % be the single image, for this situation.
1735 % In the case of a single destination image (or last image given), that image
1736 % will ve cloned to match the number of images remaining in the source image
1739 % This is equivelent to the "-layer Composite" Shell API operator.
1742 % The format of the CompositeLayers method is:
1744 % void CompositeLayers(Image *destination, const CompositeOperator
1745 % compose, Image *source, const ssize_t x_offset, const ssize_t y_offset,
1746 % ExceptionInfo *exception);
1748 % A description of each parameter follows:
1750 % o destination: the destination images and results
1752 % o source: source image(s) for the layer composition
1754 % o compose, x_offset, y_offset: arguments passed on to CompositeImages()
1756 % o exception: return any errors or warnings in this structure.
1760 static inline void CompositeCanvas(Image *destination,
1761 const CompositeOperator compose,Image *source,ssize_t x_offset,
1762 ssize_t y_offset,ExceptionInfo *exception)
1767 x_offset+=source->page.x-destination->page.x;
1768 y_offset+=source->page.y-destination->page.y;
1769 value=GetImageArtifact(source,"compose:outside-overlay");
1770 (void) CompositeImage(destination,source,compose,
1771 (value != (const char *) NULL) && (IsStringTrue(value) != MagickFalse) ?
1772 MagickFalse : MagickTrue,x_offset,y_offset,exception);
1775 MagickExport void CompositeLayers(Image *destination,
1776 const CompositeOperator compose, Image *source,const ssize_t x_offset,
1777 const ssize_t y_offset,ExceptionInfo *exception)
1779 assert(destination != (Image *) NULL);
1780 assert(destination->signature == MagickCoreSignature);
1781 assert(source != (Image *) NULL);
1782 assert(source->signature == MagickCoreSignature);
1783 assert(exception != (ExceptionInfo *) NULL);
1784 assert(exception->signature == MagickCoreSignature);
1785 if (source->debug != MagickFalse || destination->debug != MagickFalse)
1786 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s - %s",
1787 source->filename,destination->filename);
1790 Overlay single source image over destation image/list
1792 if ( source->next == (Image *) NULL )
1793 while ( destination != (Image *) NULL )
1795 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1797 destination=GetNextImageInList(destination);
1801 Overlay source image list over single destination.
1802 Multiple clones of destination image are created to match source list.
1803 Original Destination image becomes first image of generated list.
1804 As such the image list pointer does not require any change in caller.
1805 Some animation attributes however also needs coping in this case.
1807 else if ( destination->next == (Image *) NULL )
1809 Image *dest = CloneImage(destination,0,0,MagickTrue,exception);
1811 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1813 /* copy source image attributes ? */
1814 if ( source->next != (Image *) NULL )
1816 destination->delay = source->delay;
1817 destination->iterations = source->iterations;
1819 source=GetNextImageInList(source);
1821 while ( source != (Image *) NULL )
1823 AppendImageToList(&destination,
1824 CloneImage(dest,0,0,MagickTrue,exception));
1825 destination=GetLastImageInList(destination);
1827 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1829 destination->delay = source->delay;
1830 destination->iterations = source->iterations;
1831 source=GetNextImageInList(source);
1833 dest=DestroyImage(dest);
1837 Overlay a source image list over a destination image list
1838 until either list runs out of images. (Does not repeat)
1841 while ( source != (Image *) NULL && destination != (Image *) NULL )
1843 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1845 source=GetNextImageInList(source);
1846 destination=GetNextImageInList(destination);
1851 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1855 % M e r g e I m a g e L a y e r s %
1859 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1861 % MergeImageLayers() composes all the image layers from the current given
1862 % image onward to produce a single image of the merged layers.
1864 % The inital canvas's size depends on the given LayerMethod, and is
1865 % initialized using the first images background color. The images
1866 % are then compositied onto that image in sequence using the given
1867 % composition that has been assigned to each individual image.
1869 % The format of the MergeImageLayers is:
1871 % Image *MergeImageLayers(Image *image,const LayerMethod method,
1872 % ExceptionInfo *exception)
1874 % A description of each parameter follows:
1876 % o image: the image list to be composited together
1878 % o method: the method of selecting the size of the initial canvas.
1880 % MergeLayer: Merge all layers onto a canvas just large enough
1881 % to hold all the actual images. The virtual canvas of the
1882 % first image is preserved but otherwise ignored.
1884 % FlattenLayer: Use the virtual canvas size of first image.
1885 % Images which fall outside this canvas is clipped.
1886 % This can be used to 'fill out' a given virtual canvas.
1888 % MosaicLayer: Start with the virtual canvas of the first image,
1889 % enlarging left and right edges to contain all images.
1890 % Images with negative offsets will be clipped.
1892 % TrimBoundsLayer: Determine the overall bounds of all the image
1893 % layers just as in "MergeLayer", then adjust the the canvas
1894 % and offsets to be relative to those bounds, without overlaying
1897 % WARNING: a new image is not returned, the original image
1898 % sequence page data is modified instead.
1900 % o exception: return any errors or warnings in this structure.
1903 MagickExport Image *MergeImageLayers(Image *image,const LayerMethod method,
1904 ExceptionInfo *exception)
1906 #define MergeLayersTag "Merge/Layers"
1917 register const Image
1928 assert(image != (Image *) NULL);
1929 assert(image->signature == MagickCoreSignature);
1930 if (image->debug != MagickFalse)
1931 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1932 assert(exception != (ExceptionInfo *) NULL);
1933 assert(exception->signature == MagickCoreSignature);
1935 Determine canvas image size, and its virtual canvas size and offset
1938 width=image->columns;
1942 case TrimBoundsLayer:
1946 next=GetNextImageInList(image);
1947 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
1949 if (page.x > next->page.x)
1951 width+=page.x-next->page.x;
1952 page.x=next->page.x;
1954 if (page.y > next->page.y)
1956 height+=page.y-next->page.y;
1957 page.y=next->page.y;
1959 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns-page.x))
1960 width=(size_t) next->page.x+(ssize_t) next->columns-page.x;
1961 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows-page.y))
1962 height=(size_t) next->page.y+(ssize_t) next->rows-page.y;
1970 if (page.height > 0)
1980 if (page.height > 0)
1982 for (next=image; next != (Image *) NULL; next=GetNextImageInList(next))
1984 if (method == MosaicLayer)
1986 page.x=next->page.x;
1987 page.y=next->page.y;
1988 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns))
1989 width=(size_t) next->page.x+next->columns;
1990 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows))
1991 height=(size_t) next->page.y+next->rows;
2002 Set virtual canvas size if not defined.
2004 if (page.width == 0)
2005 page.width=page.x < 0 ? width : width+page.x;
2006 if (page.height == 0)
2007 page.height=page.y < 0 ? height : height+page.y;
2009 Handle "TrimBoundsLayer" method separately to normal 'layer merge'.
2011 if (method == TrimBoundsLayer)
2013 number_images=GetImageListLength(image);
2014 for (scene=0; scene < (ssize_t) number_images; scene++)
2016 image->page.x-=page.x;
2017 image->page.y-=page.y;
2018 image->page.width=width;
2019 image->page.height=height;
2020 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2022 if (proceed == MagickFalse)
2024 image=GetNextImageInList(image);
2025 if (image == (Image *) NULL)
2028 return((Image *) NULL);
2031 Create canvas size of width and height, and background color.
2033 canvas=CloneImage(image,width,height,MagickTrue,exception);
2034 if (canvas == (Image *) NULL)
2035 return((Image *) NULL);
2036 (void) SetImageBackgroundColor(canvas,exception);
2038 canvas->dispose=UndefinedDispose;
2040 Compose images onto canvas, with progress monitor
2042 number_images=GetImageListLength(image);
2043 for (scene=0; scene < (ssize_t) number_images; scene++)
2045 (void) CompositeImage(canvas,image,image->compose,MagickTrue,image->page.x-
2046 canvas->page.x,image->page.y-canvas->page.y,exception);
2047 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2049 if (proceed == MagickFalse)
2051 image=GetNextImageInList(image);
2052 if (image == (Image *) NULL)