MagickCore 6.9.13
Loading...
Searching...
No Matches
layer.c
1/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% L AAA Y Y EEEEE RRRR %
6% L A A Y Y E R R %
7% L AAAAA Y EEE RRRR %
8% L A A Y E R R %
9% LLLLL A A Y EEEEE R R %
10% %
11% MagickCore Image Layering Methods %
12% %
13% Software Design %
14% Cristy %
15% Anthony Thyssen %
16% January 2006 %
17% %
18% %
19% Copyright 1999 ImageMagick Studio LLC, a non-profit organization %
20% dedicated to making software imaging solutions freely available. %
21% %
22% You may not use this file except in compliance with the License. You may %
23% obtain a copy of the License at %
24% %
25% https://imagemagick.org/script/license.php %
26% %
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. %
32% %
33%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
34%
35*/
36
37/*
38 Include declarations.
39*/
40#include "magick/studio.h"
41#include "magick/artifact.h"
42#include "magick/attribute.h"
43#include "magick/cache.h"
44#include "magick/channel.h"
45#include "magick/color.h"
46#include "magick/color-private.h"
47#include "magick/composite.h"
48#include "magick/effect.h"
49#include "magick/exception.h"
50#include "magick/exception-private.h"
51#include "magick/geometry.h"
52#include "magick/image.h"
53#include "magick/layer.h"
54#include "magick/list.h"
55#include "magick/memory_.h"
56#include "magick/monitor.h"
57#include "magick/monitor-private.h"
58#include "magick/option.h"
59#include "magick/pixel-private.h"
60#include "magick/property.h"
61#include "magick/profile.h"
62#include "magick/resource_.h"
63#include "magick/resize.h"
64#include "magick/statistic.h"
65#include "magick/string_.h"
66#include "magick/transform.h"
67
68/*
69%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
70% %
71% %
72% %
73+ C l e a r B o u n d s %
74% %
75% %
76% %
77%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
78%
79% ClearBounds() Clear the area specified by the bounds in an image to
80% transparency. This typically used to handle Background Disposal for the
81% previous frame in an animation sequence.
82%
83% Warning: no bounds checks are performed, except for the null or missed
84% image, for images that don't change. in all other cases bound must fall
85% within the image.
86%
87% The format is:
88%
89% void ClearBounds(Image *image,RectangleInfo *bounds)
90%
91% A description of each parameter follows:
92%
93% o image: the image to had the area cleared in
94%
95% o bounds: the area to be clear within the imag image
96%
97*/
98static void ClearBounds(Image *image,RectangleInfo *bounds)
99{
101 *exception;
102
103 ssize_t
104 y;
105
106 if (bounds->x < 0)
107 return;
108 if (image->matte == MagickFalse)
109 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
110 exception=(&image->exception);
111 for (y=0; y < (ssize_t) bounds->height; y++)
112 {
113 ssize_t
114 x;
115
117 *magick_restrict q;
118
119 q=GetAuthenticPixels(image,bounds->x,bounds->y+y,bounds->width,1,exception);
120 if (q == (PixelPacket *) NULL)
121 break;
122 for (x=0; x < (ssize_t) bounds->width; x++)
123 {
124 q->opacity=(Quantum) TransparentOpacity;
125 q++;
126 }
127 if (SyncAuthenticPixels(image,exception) == MagickFalse)
128 break;
129 }
130}
131
132/*
133%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
134% %
135% %
136% %
137+ I s B o u n d s C l e a r e d %
138% %
139% %
140% %
141%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
142%
143% IsBoundsCleared() tests whether any pixel in the bounds given, gets cleared
144% when going from the first image to the second image. This typically used
145% to check if a proposed disposal method will work successfully to generate
146% the second frame image from the first disposed form of the previous frame.
147%
148% Warning: no bounds checks are performed, except for the null or missed
149% image, for images that don't change. in all other cases bound must fall
150% within the image.
151%
152% The format is:
153%
154% MagickBooleanType IsBoundsCleared(const Image *image1,
155% const Image *image2,RectangleInfo bounds,ExceptionInfo *exception)
156%
157% A description of each parameter follows:
158%
159% o image1, image 2: the images to check for cleared pixels
160%
161% o bounds: the area to be clear within the imag image
162%
163% o exception: return any errors or warnings in this structure.
164%
165*/
166static MagickBooleanType IsBoundsCleared(const Image *image1,
167 const Image *image2,RectangleInfo *bounds,ExceptionInfo *exception)
168{
169 const PixelPacket
170 *p,
171 *q;
172
173 ssize_t
174 x;
175
176 ssize_t
177 y;
178
179 if (bounds->x < 0)
180 return(MagickFalse);
181 for (y=0; y < (ssize_t) bounds->height; y++)
182 {
183 p=GetVirtualPixels(image1,bounds->x,bounds->y+y,bounds->width,1,
184 exception);
185 q=GetVirtualPixels(image2,bounds->x,bounds->y+y,bounds->width,1,
186 exception);
187 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
188 break;
189 for (x=0; x < (ssize_t) bounds->width; x++)
190 {
191 if ((GetPixelOpacity(p) <= (Quantum) (QuantumRange/2)) &&
192 (GetPixelOpacity(q) > (Quantum) (QuantumRange/2)))
193 break;
194 p++;
195 q++;
196 }
197 if (x < (ssize_t) bounds->width)
198 break;
199 }
200 return(y < (ssize_t) bounds->height ? MagickTrue : MagickFalse);
201}
202
203/*
204%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
205% %
206% %
207% %
208% C o a l e s c e I m a g e s %
209% %
210% %
211% %
212%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
213%
214% CoalesceImages() composites a set of images while respecting any page
215% offsets and disposal methods. GIF, MIFF, and MNG animation sequences
216% typically start with an image background and each subsequent image
217% varies in size and offset. A new image sequence is returned with all
218% images the same size as the first images virtual canvas and composited
219% with the next image in the sequence.
220%
221% The format of the CoalesceImages method is:
222%
223% Image *CoalesceImages(Image *image,ExceptionInfo *exception)
224%
225% A description of each parameter follows:
226%
227% o image: the image sequence.
228%
229% o exception: return any errors or warnings in this structure.
230%
231*/
232MagickExport Image *CoalesceImages(const Image *image,ExceptionInfo *exception)
233{
234 Image
235 *coalesce_image,
236 *dispose_image,
237 *previous;
238
239 Image
240 *next;
241
243 bounds;
244
245 /*
246 Coalesce the image sequence.
247 */
248 assert(image != (Image *) NULL);
249 assert(image->signature == MagickCoreSignature);
250 assert(exception != (ExceptionInfo *) NULL);
251 assert(exception->signature == MagickCoreSignature);
252 if (IsEventLogging() != MagickFalse)
253 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
254 /*
255 Initialise first image.
256 */
257 next=GetFirstImageInList(image);
258 bounds=next->page;
259 if (bounds.width == 0)
260 {
261 bounds.width=next->columns;
262 if (bounds.x > 0)
263 bounds.width+=bounds.x;
264 }
265 if (bounds.height == 0)
266 {
267 bounds.height=next->rows;
268 if (bounds.y > 0)
269 bounds.height+=bounds.y;
270 }
271 bounds.x=0;
272 bounds.y=0;
273 coalesce_image=CloneImage(next,bounds.width,bounds.height,MagickTrue,
274 exception);
275 if (coalesce_image == (Image *) NULL)
276 return((Image *) NULL);
277 coalesce_image->background_color.opacity=(Quantum) TransparentOpacity;
278 (void) SetImageBackgroundColor(coalesce_image);
279 coalesce_image->matte=next->matte;
280 coalesce_image->page=bounds;
281 coalesce_image->dispose=NoneDispose;
282 /*
283 Coalesce rest of the images.
284 */
285 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
286 (void) CompositeImage(coalesce_image,CopyCompositeOp,next,next->page.x,
287 next->page.y);
288 next=GetNextImageInList(next);
289 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
290 {
291 /*
292 Determine the bounds that was overlaid in the previous image.
293 */
294 previous=GetPreviousImageInList(next);
295 bounds=previous->page;
296 bounds.width=previous->columns;
297 bounds.height=previous->rows;
298 if (bounds.x < 0)
299 {
300 bounds.width+=bounds.x;
301 bounds.x=0;
302 }
303 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) coalesce_image->columns)
304 bounds.width=coalesce_image->columns-bounds.x;
305 if (bounds.y < 0)
306 {
307 bounds.height+=bounds.y;
308 bounds.y=0;
309 }
310 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) coalesce_image->rows)
311 bounds.height=coalesce_image->rows-bounds.y;
312 /*
313 Replace the dispose image with the new coalesced image.
314 */
315 if (GetPreviousImageInList(next)->dispose != PreviousDispose)
316 {
317 dispose_image=DestroyImage(dispose_image);
318 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
319 if (dispose_image == (Image *) NULL)
320 {
321 coalesce_image=DestroyImageList(coalesce_image);
322 return((Image *) NULL);
323 }
324 }
325 /*
326 Clear the overlaid area of the coalesced bounds for background disposal
327 */
328 if (next->previous->dispose == BackgroundDispose)
329 ClearBounds(dispose_image,&bounds);
330 /*
331 Next image is the dispose image, overlaid with next frame in sequence.
332 */
333 coalesce_image->next=CloneImage(dispose_image,0,0,MagickTrue,exception);
334 if (coalesce_image->next != NULL)
335 coalesce_image->next->previous=coalesce_image;
336 previous=coalesce_image;
337 coalesce_image=GetNextImageInList(coalesce_image);
338 (void) CompositeImage(coalesce_image,next->matte != MagickFalse ?
339 OverCompositeOp : CopyCompositeOp,next,next->page.x,next->page.y);
340 (void) CloneImageProfiles(coalesce_image,next);
341 (void) CloneImageProperties(coalesce_image,next);
342 (void) CloneImageArtifacts(coalesce_image,next);
343 coalesce_image->page=previous->page;
344 /*
345 If a pixel goes opaque to transparent, use background dispose.
346 */
347 if (IsBoundsCleared(previous,coalesce_image,&bounds,exception) != MagickFalse)
348 coalesce_image->dispose=BackgroundDispose;
349 else
350 coalesce_image->dispose=NoneDispose;
351 previous->dispose=coalesce_image->dispose;
352 }
353 dispose_image=DestroyImage(dispose_image);
354 return(GetFirstImageInList(coalesce_image));
355}
356
357/*
358%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
359% %
360% %
361% %
362% D i s p o s e I m a g e s %
363% %
364% %
365% %
366%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
367%
368% DisposeImages() returns the coalesced frames of a GIF animation as it would
369% appear after the GIF dispose method of that frame has been applied. That is
370% it returned the appearance of each frame before the next is overlaid.
371%
372% The format of the DisposeImages method is:
373%
374% Image *DisposeImages(Image *images,ExceptionInfo *exception)
375%
376% A description of each parameter follows:
377%
378% o image: the image sequence.
379%
380% o exception: return any errors or warnings in this structure.
381%
382*/
383MagickExport Image *DisposeImages(const Image *images,ExceptionInfo *exception)
384{
385 Image
386 *dispose_image,
387 *dispose_images;
388
390 bounds;
391
392 Image
393 *image,
394 *next;
395
396 /*
397 Run the image through the animation sequence
398 */
399 assert(images != (Image *) NULL);
400 assert(images->signature == MagickCoreSignature);
401 assert(exception != (ExceptionInfo *) NULL);
402 assert(exception->signature == MagickCoreSignature);
403 if (IsEventLogging() != MagickFalse)
404 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
405 image=GetFirstImageInList(images);
406 dispose_image=CloneImage(image,image->page.width,image->page.height,
407 MagickTrue,exception);
408 if (dispose_image == (Image *) NULL)
409 return((Image *) NULL);
410 dispose_image->page=image->page;
411 dispose_image->page.x=0;
412 dispose_image->page.y=0;
413 dispose_image->dispose=NoneDispose;
414 dispose_image->background_color.opacity=(Quantum) TransparentOpacity;
415 (void) SetImageBackgroundColor(dispose_image);
416 dispose_images=NewImageList();
417 for (next=image; next != (Image *) NULL; next=GetNextImageInList(next))
418 {
419 Image
420 *current_image;
421
422 /*
423 Overlay this frame's image over the previous disposal image.
424 */
425 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
426 if (current_image == (Image *) NULL)
427 {
428 dispose_images=DestroyImageList(dispose_images);
429 dispose_image=DestroyImage(dispose_image);
430 return((Image *) NULL);
431 }
432 (void) CompositeImage(current_image,next->matte != MagickFalse ?
433 OverCompositeOp : CopyCompositeOp,next,next->page.x,next->page.y);
434 /*
435 Handle Background dispose: image is displayed for the delay period.
436 */
437 if (next->dispose == BackgroundDispose)
438 {
439 bounds=next->page;
440 bounds.width=next->columns;
441 bounds.height=next->rows;
442 if (bounds.x < 0)
443 {
444 bounds.width+=bounds.x;
445 bounds.x=0;
446 }
447 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
448 bounds.width=current_image->columns-bounds.x;
449 if (bounds.y < 0)
450 {
451 bounds.height+=bounds.y;
452 bounds.y=0;
453 }
454 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
455 bounds.height=current_image->rows-bounds.y;
456 ClearBounds(current_image,&bounds);
457 }
458 /*
459 Select the appropriate previous/disposed image.
460 */
461 if (next->dispose == PreviousDispose)
462 current_image=DestroyImage(current_image);
463 else
464 {
465 dispose_image=DestroyImage(dispose_image);
466 dispose_image=current_image;
467 current_image=(Image *) NULL;
468 }
469 /*
470 Save the dispose image just calculated for return.
471 */
472 {
473 Image
474 *dispose;
475
476 dispose=CloneImage(dispose_image,0,0,MagickTrue,exception);
477 if (dispose == (Image *) NULL)
478 {
479 dispose_images=DestroyImageList(dispose_images);
480 dispose_image=DestroyImage(dispose_image);
481 return((Image *) NULL);
482 }
483 (void) CloneImageProfiles(dispose,next);
484 (void) CloneImageProperties(dispose,next);
485 (void) CloneImageArtifacts(dispose,next);
486 dispose->page.x=0;
487 dispose->page.y=0;
488 dispose->dispose=next->dispose;
489 AppendImageToList(&dispose_images,dispose);
490 }
491 }
492 dispose_image=DestroyImage(dispose_image);
493 return(GetFirstImageInList(dispose_images));
494}
495
496/*
497%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
498% %
499% %
500% %
501+ C o m p a r e P i x e l s %
502% %
503% %
504% %
505%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
506%
507% ComparePixels() Compare the two pixels and return true if the pixels
508% differ according to the given LayerType comparison method.
509%
510% This currently only used internally by CompareImageBounds(). It is
511% doubtful that this sub-routine will be useful outside this module.
512%
513% The format of the ComparePixels method is:
514%
515% MagickBooleanType *ComparePixels(const ImageLayerMethod method,
516% const MagickPixelPacket *p,const MagickPixelPacket *q)
517%
518% A description of each parameter follows:
519%
520% o method: What differences to look for. Must be one of
521% CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
522%
523% o p, q: the pixels to test for appropriate differences.
524%
525*/
526
527static MagickBooleanType ComparePixels(const ImageLayerMethod method,
528 const MagickPixelPacket *p,const MagickPixelPacket *q)
529{
530 MagickRealType
531 o1,
532 o2;
533
534 /*
535 Any change in pixel values
536 */
537 if (method == CompareAnyLayer)
538 return((MagickBooleanType)(IsMagickColorSimilar(p,q) == MagickFalse));
539
540 o1 = (p->matte != MagickFalse) ? (MagickRealType) GetPixelOpacity(p) :
541 (MagickRealType) OpaqueOpacity;
542 o2 = (q->matte != MagickFalse) ? (MagickRealType) q->opacity :
543 (MagickRealType) OpaqueOpacity;
544
545 /*
546 Pixel goes from opaque to transprency
547 */
548 if (method == CompareClearLayer)
549 return((MagickBooleanType) ( (o1 <= ((MagickRealType) QuantumRange/2.0)) &&
550 (o2 > ((MagickRealType) QuantumRange/2.0)) ) );
551
552 /*
553 overlay would change first pixel by second
554 */
555 if (method == CompareOverlayLayer)
556 {
557 if (o2 > ((MagickRealType) QuantumRange/2.0))
558 return MagickFalse;
559 return((MagickBooleanType) (IsMagickColorSimilar(p,q) == MagickFalse));
560 }
561 return(MagickFalse);
562}
563
564
565/*
566%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
567% %
568% %
569% %
570+ C o m p a r e I m a g e B o u n d s %
571% %
572% %
573% %
574%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
575%
576% CompareImageBounds() Given two images return the smallest rectangular area
577% by which the two images differ, accourding to the given 'Compare...'
578% layer method.
579%
580% This currently only used internally in this module, but may eventually
581% be used by other modules.
582%
583% The format of the CompareImageBounds method is:
584%
585% RectangleInfo *CompareImageBounds(const ImageLayerMethod method,
586% const Image *image1,const Image *image2,ExceptionInfo *exception)
587%
588% A description of each parameter follows:
589%
590% o method: What differences to look for. Must be one of
591% CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
592%
593% o image1, image2: the two images to compare.
594%
595% o exception: return any errors or warnings in this structure.
596%
597*/
598
599static RectangleInfo CompareImageBounds(const Image *image1,const Image *image2,
600 const ImageLayerMethod method,ExceptionInfo *exception)
601{
603 bounds;
604
606 pixel1,
607 pixel2;
608
609 const IndexPacket
610 *indexes1,
611 *indexes2;
612
613 const PixelPacket
614 *p,
615 *q;
616
617 ssize_t
618 x;
619
620 ssize_t
621 y;
622
623#if 0
624 /* only same sized images can be compared */
625 assert(image1->columns == image2->columns);
626 assert(image1->rows == image2->rows);
627#endif
628
629 /*
630 Set bounding box of the differences between images
631 */
632 GetMagickPixelPacket(image1,&pixel1);
633 GetMagickPixelPacket(image2,&pixel2);
634 for (x=0; x < (ssize_t) image1->columns; x++)
635 {
636 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
637 q=GetVirtualPixels(image2,x,0,1,image1->rows,exception);
638 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
639 break;
640 indexes1=GetVirtualIndexQueue(image1);
641 indexes2=GetVirtualIndexQueue(image2);
642 for (y=0; y < (ssize_t) image1->rows; y++)
643 {
644 SetMagickPixelPacket(image1,p,indexes1+x,&pixel1);
645 SetMagickPixelPacket(image2,q,indexes2+x,&pixel2);
646 if (ComparePixels(method,&pixel1,&pixel2))
647 break;
648 p++;
649 q++;
650 }
651 if (y < (ssize_t) image1->rows)
652 break;
653 }
654 if (x >= (ssize_t) image1->columns)
655 {
656 /*
657 Images are identical, return a null image.
658 */
659 bounds.x=-1;
660 bounds.y=-1;
661 bounds.width=1;
662 bounds.height=1;
663 return(bounds);
664 }
665 bounds.x=x;
666 for (x=(ssize_t) image1->columns-1; x >= 0; x--)
667 {
668 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
669 q=GetVirtualPixels(image2,x,0,1,image1->rows,exception);
670 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
671 break;
672 indexes1=GetVirtualIndexQueue(image1);
673 indexes2=GetVirtualIndexQueue(image2);
674 for (y=0; y < (ssize_t) image1->rows; y++)
675 {
676 SetMagickPixelPacket(image1,p,indexes1+x,&pixel1);
677 SetMagickPixelPacket(image2,q,indexes2+x,&pixel2);
678 if (ComparePixels(method,&pixel1,&pixel2))
679 break;
680 p++;
681 q++;
682 }
683 if (y < (ssize_t) image1->rows)
684 break;
685 }
686 bounds.width=(size_t) (x-bounds.x+1);
687 for (y=0; y < (ssize_t) image1->rows; y++)
688 {
689 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
690 q=GetVirtualPixels(image2,0,y,image1->columns,1,exception);
691 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
692 break;
693 indexes1=GetVirtualIndexQueue(image1);
694 indexes2=GetVirtualIndexQueue(image2);
695 for (x=0; x < (ssize_t) image1->columns; x++)
696 {
697 SetMagickPixelPacket(image1,p,indexes1+x,&pixel1);
698 SetMagickPixelPacket(image2,q,indexes2+x,&pixel2);
699 if (ComparePixels(method,&pixel1,&pixel2))
700 break;
701 p++;
702 q++;
703 }
704 if (x < (ssize_t) image1->columns)
705 break;
706 }
707 bounds.y=y;
708 for (y=(ssize_t) image1->rows-1; y >= 0; y--)
709 {
710 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
711 q=GetVirtualPixels(image2,0,y,image1->columns,1,exception);
712 if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
713 break;
714 indexes1=GetVirtualIndexQueue(image1);
715 indexes2=GetVirtualIndexQueue(image2);
716 for (x=0; x < (ssize_t) image1->columns; x++)
717 {
718 SetMagickPixelPacket(image1,p,indexes1+x,&pixel1);
719 SetMagickPixelPacket(image2,q,indexes2+x,&pixel2);
720 if (ComparePixels(method,&pixel1,&pixel2))
721 break;
722 p++;
723 q++;
724 }
725 if (x < (ssize_t) image1->columns)
726 break;
727 }
728 bounds.height=(size_t) (y-bounds.y+1);
729 return(bounds);
730}
731
732/*
733%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
734% %
735% %
736% %
737% C o m p a r e I m a g e L a y e r s %
738% %
739% %
740% %
741%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
742%
743% CompareImageLayers() compares each image with the next in a sequence and
744% returns the minimum bounding region of all the pixel differences (of the
745% ImageLayerMethod specified) it discovers.
746%
747% Images do NOT have to be the same size, though it is best that all the
748% images are 'coalesced' (images are all the same size, on a flattened
749% canvas, so as to represent exactly how an specific frame should look).
750%
751% No GIF dispose methods are applied, so GIF animations must be coalesced
752% before applying this image operator to find differences to them.
753%
754% The format of the CompareImageLayers method is:
755%
756% Image *CompareImageLayers(const Image *images,
757% const ImageLayerMethod method,ExceptionInfo *exception)
758%
759% A description of each parameter follows:
760%
761% o image: the image.
762%
763% o method: the layers type to compare images with. Must be one of...
764% CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
765%
766% o exception: return any errors or warnings in this structure.
767%
768*/
769
770MagickExport Image *CompareImageLayers(const Image *image,
771 const ImageLayerMethod method,ExceptionInfo *exception)
772{
773 Image
774 *image_a,
775 *image_b,
776 *layers;
777
779 *bounds;
780
781 const Image
782 *next;
783
784 ssize_t
785 i;
786
787 assert(image != (const Image *) NULL);
788 assert(image->signature == MagickCoreSignature);
789 assert(exception != (ExceptionInfo *) NULL);
790 assert(exception->signature == MagickCoreSignature);
791 assert((method == CompareAnyLayer) ||
792 (method == CompareClearLayer) ||
793 (method == CompareOverlayLayer));
794 if (IsEventLogging() != MagickFalse)
795 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
796 /*
797 Allocate bounds memory.
798 */
799 next=GetFirstImageInList(image);
800 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
801 GetImageListLength(next),sizeof(*bounds));
802 if (bounds == (RectangleInfo *) NULL)
803 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
804 /*
805 Set up first comparison images.
806 */
807 image_a=CloneImage(next,next->page.width,next->page.height,
808 MagickTrue,exception);
809 if (image_a == (Image *) NULL)
810 {
811 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
812 return((Image *) NULL);
813 }
814 image_a->background_color.opacity=(Quantum) TransparentOpacity;
815 (void) SetImageBackgroundColor(image_a);
816 image_a->page=next->page;
817 image_a->page.x=0;
818 image_a->page.y=0;
819 (void) CompositeImage(image_a,CopyCompositeOp,next,next->page.x,next->page.y);
820 /*
821 Compute the bounding box of changes for the later images
822 */
823 i=0;
824 next=GetNextImageInList(next);
825 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
826 {
827 image_b=CloneImage(image_a,0,0,MagickTrue,exception);
828 if (image_b == (Image *) NULL)
829 {
830 image_a=DestroyImage(image_a);
831 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
832 return((Image *) NULL);
833 }
834 (void) CompositeImage(image_a,CopyCompositeOp,next,next->page.x,
835 next->page.y);
836 bounds[i]=CompareImageBounds(image_b,image_a,method,exception);
837
838 image_b=DestroyImage(image_b);
839 i++;
840 }
841 image_a=DestroyImage(image_a);
842 /*
843 Clone first image in sequence.
844 */
845 next=GetFirstImageInList(image);
846 layers=CloneImage(next,0,0,MagickTrue,exception);
847 if (layers == (Image *) NULL)
848 {
849 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
850 return((Image *) NULL);
851 }
852 /*
853 Deconstruct the image sequence.
854 */
855 i=0;
856 next=GetNextImageInList(next);
857 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
858 {
859 if ((bounds[i].x == -1) && (bounds[i].y == -1) &&
860 (bounds[i].width == 1) && (bounds[i].height == 1))
861 {
862 /*
863 An empty frame is returned from CompareImageBounds(), which means the
864 current frame is identical to the previous frame.
865 */
866 i++;
867 continue;
868 }
869 image_a=CloneImage(next,0,0,MagickTrue,exception);
870 if (image_a == (Image *) NULL)
871 break;
872 image_b=CropImage(image_a,&bounds[i],exception);
873 image_a=DestroyImage(image_a);
874 if (image_b == (Image *) NULL)
875 break;
876 AppendImageToList(&layers,image_b);
877 i++;
878 }
879 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
880 if (next != (Image *) NULL)
881 {
882 layers=DestroyImageList(layers);
883 return((Image *) NULL);
884 }
885 return(GetFirstImageInList(layers));
886}
887
888/*
889%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
890% %
891% %
892% %
893% D e c o n s t r u c t I m a g e s %
894% %
895% %
896% %
897%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
898%
899% DeconstructImages() compares each image with the next in a sequence and
900% returns the minimum bounding region of all differences from the first image.
901%
902% This function is deprecated in favor of the more universal
903% CompareImageLayers() function.
904%
905% The format of the DeconstructImages method is:
906%
907% Image *DeconstructImages(const Image *images,ExceptionInfo *exception)
908%
909% A description of each parameter follows:
910%
911% o image: the image.
912%
913% o exception: return any errors or warnings in this structure.
914%
915*/
916
917MagickExport Image *DeconstructImages(const Image *images,
918 ExceptionInfo *exception)
919{
920 return(CompareImageLayers(images,CompareAnyLayer,exception));
921}
922
923/*
924%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
925% %
926% %
927% %
928+ O p t i m i z e L a y e r F r a m e s %
929% %
930% %
931% %
932%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
933%
934% OptimizeLayerFrames() takes a coalesced GIF animation, and compares each
935% frame against the three different 'disposal' forms of the previous frame.
936% From this it then attempts to select the smallest cropped image and
937% disposal method needed to reproduce the resulting image.
938%
939% Note that this not easy, and may require the expansion of the bounds
940% of previous frame, simply clear pixels for the next animation frame to
941% transparency according to the selected dispose method.
942%
943% The format of the OptimizeLayerFrames method is:
944%
945% static Image *OptimizeLayerFrames(const Image *image,
946% const ImageLayerMethod method,ExceptionInfo *exception)
947%
948% A description of each parameter follows:
949%
950% o image: the image.
951%
952% o method: the layers technique to optimize with. Must be one of...
953% OptimizeImageLayer, or OptimizePlusLayer. The Plus form allows
954% the addition of extra 'zero delay' frames to clear pixels from
955% the previous frame, and the removal of frames that done change,
956% merging the delay times together.
957%
958% o exception: return any errors or warnings in this structure.
959%
960*/
961/*
962 Define a 'fake' dispose method where the frame is duplicated, (for
963 OptimizePlusLayer) with a extra zero time delay frame which does a
964 BackgroundDisposal to clear the pixels that need to be cleared.
965*/
966#define DupDispose ((DisposeType)9)
967/*
968 Another 'fake' dispose method used to removed frames that don't change.
969*/
970#define DelDispose ((DisposeType)8)
971
972#define DEBUG_OPT_FRAME 0
973
974static Image *OptimizeLayerFrames(const Image *image,
975 const ImageLayerMethod method,ExceptionInfo *exception)
976{
978 *sans_exception;
979
980 Image
981 *prev_image,
982 *dup_image,
983 *bgnd_image,
984 *optimized_image;
985
987 try_bounds,
988 bgnd_bounds,
989 dup_bounds,
990 *bounds;
991
992 MagickBooleanType
993 add_frames,
994 try_cleared,
995 cleared;
996
997 DisposeType
998 *disposals;
999
1000 const Image
1001 *curr;
1002
1003 ssize_t
1004 i;
1005
1006 assert(image != (const Image *) NULL);
1007 assert(image->signature == MagickCoreSignature);
1008 assert(exception != (ExceptionInfo *) NULL);
1009 assert(exception->signature == MagickCoreSignature);
1010 assert(method == OptimizeLayer ||
1011 method == OptimizeImageLayer ||
1012 method == OptimizePlusLayer);
1013 if (IsEventLogging() != MagickFalse)
1014 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1015 /*
1016 Are we allowed to add/remove frames from animation
1017 */
1018 add_frames=method == OptimizePlusLayer ? MagickTrue : MagickFalse;
1019 /*
1020 Ensure all the images are the same size
1021 */
1022 curr=GetFirstImageInList(image);
1023 for (; curr != (Image *) NULL; curr=GetNextImageInList(curr))
1024 {
1025 if ((curr->columns != image->columns) || (curr->rows != image->rows))
1026 ThrowImageException(OptionError,"ImagesAreNotTheSameSize");
1027 if ((curr->page.x != 0) || (curr->page.y != 0) ||
1028 (curr->page.width != image->page.width) ||
1029 (curr->page.height != image->page.height))
1030 ThrowImageException(OptionError,"ImagePagesAreNotCoalesced");
1031 }
1032 /*
1033 Allocate memory (times 2 if we allow the use of frame duplications)
1034 */
1035 curr=GetFirstImageInList(image);
1036 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
1037 GetImageListLength(curr),(add_frames != MagickFalse ? 2UL : 1UL)*
1038 sizeof(*bounds));
1039 if (bounds == (RectangleInfo *) NULL)
1040 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1041 disposals=(DisposeType *) AcquireQuantumMemory((size_t)
1042 GetImageListLength(image),(add_frames != MagickFalse ? 2UL : 1UL)*
1043 sizeof(*disposals));
1044 if (disposals == (DisposeType *) NULL)
1045 {
1046 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1047 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1048 }
1049 /*
1050 Initialise Previous Image as fully transparent
1051 */
1052 prev_image=CloneImage(curr,curr->page.width,curr->page.height,
1053 MagickTrue,exception);
1054 if (prev_image == (Image *) NULL)
1055 {
1056 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1057 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1058 return((Image *) NULL);
1059 }
1060 prev_image->page=curr->page; /* ERROR: <-- should not be need, but is! */
1061 prev_image->page.x=0;
1062 prev_image->page.y=0;
1063 prev_image->dispose=NoneDispose;
1064
1065 prev_image->background_color.opacity=(Quantum) TransparentOpacity;
1066 (void) SetImageBackgroundColor(prev_image);
1067 /*
1068 Figure out the area of overlay of the first frame
1069 No pixel could be cleared as all pixels are already cleared.
1070 */
1071#if DEBUG_OPT_FRAME
1072 i=0;
1073 (void) FormatLocaleFile(stderr,"frame %.20g :-\n",(double) i);
1074#endif
1075 disposals[0]=NoneDispose;
1076 bounds[0]=CompareImageBounds(prev_image,curr,CompareAnyLayer,exception);
1077#if DEBUG_OPT_FRAME
1078 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g\n\n",
1079 (double) bounds[i].width,(double) bounds[i].height,
1080 (double) bounds[i].x,(double) bounds[i].y );
1081#endif
1082 /*
1083 Compute the bounding box of changes for each pair of images.
1084 */
1085 i=1;
1086 bgnd_image=(Image *) NULL;
1087 dup_image=(Image *) NULL;
1088 dup_bounds.width=0;
1089 dup_bounds.height=0;
1090 dup_bounds.x=0;
1091 dup_bounds.y=0;
1092 curr=GetNextImageInList(curr);
1093 for ( ; curr != (const Image *) NULL; curr=GetNextImageInList(curr))
1094 {
1095#if DEBUG_OPT_FRAME
1096 (void) FormatLocaleFile(stderr,"frame %.20g :-\n",(double) i);
1097#endif
1098 /*
1099 Assume none disposal is the best
1100 */
1101 bounds[i]=CompareImageBounds(curr->previous,curr,CompareAnyLayer,exception);
1102 cleared=IsBoundsCleared(curr->previous,curr,&bounds[i],exception);
1103 disposals[i-1]=NoneDispose;
1104#if DEBUG_OPT_FRAME
1105 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g%s%s\n",
1106 (double) bounds[i].width,(double) bounds[i].height,
1107 (double) bounds[i].x,(double) bounds[i].y,
1108 bounds[i].x < 0?" (unchanged)":"",
1109 cleared?" (pixels cleared)":"");
1110#endif
1111 if ( bounds[i].x < 0 ) {
1112 /*
1113 Image frame is exactly the same as the previous frame!
1114 If not adding frames leave it to be cropped down to a null image.
1115 Otherwise mark previous image for deleted, transfering its crop bounds
1116 to the current image.
1117 */
1118 if ( add_frames && i>=2 ) {
1119 disposals[i-1]=DelDispose;
1120 disposals[i]=NoneDispose;
1121 bounds[i]=bounds[i-1];
1122 i++;
1123 continue;
1124 }
1125 }
1126 else
1127 {
1128 /*
1129 Compare a none disposal against a previous disposal
1130 */
1131 try_bounds=CompareImageBounds(prev_image,curr,CompareAnyLayer,exception);
1132 try_cleared=IsBoundsCleared(prev_image,curr,&try_bounds,exception);
1133#if DEBUG_OPT_FRAME
1134 (void) FormatLocaleFile(stderr, "test_prev: %.20gx%.20g%+.20g%+.20g%s\n",
1135 (double) try_bounds.width,(double) try_bounds.height,
1136 (double) try_bounds.x,(double) try_bounds.y,
1137 try_cleared?" (pixels were cleared)":"");
1138#endif
1139 if ( (!try_cleared && cleared ) ||
1140 try_bounds.width * try_bounds.height
1141 < bounds[i].width * bounds[i].height )
1142 {
1143 cleared=try_cleared;
1144 bounds[i]=try_bounds;
1145 disposals[i-1]=PreviousDispose;
1146#if DEBUG_OPT_FRAME
1147 (void) FormatLocaleFile(stderr,"previous: accepted\n");
1148 } else {
1149 (void) FormatLocaleFile(stderr,"previous: rejected\n");
1150#endif
1151 }
1152
1153 /*
1154 If we are allowed lets try a complex frame duplication.
1155 It is useless if the previous image already clears pixels correctly.
1156 This method will always clear all the pixels that need to be cleared.
1157 */
1158 dup_bounds.width=dup_bounds.height=0; /* no dup, no pixel added */
1159 if ( add_frames )
1160 {
1161 dup_image=CloneImage(curr->previous,0,0,MagickTrue,exception);
1162 if (dup_image == (Image *) NULL)
1163 {
1164 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1165 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1166 prev_image=DestroyImage(prev_image);
1167 return((Image *) NULL);
1168 }
1169 dup_bounds=CompareImageBounds(dup_image,curr,CompareClearLayer,exception);
1170 ClearBounds(dup_image,&dup_bounds);
1171 try_bounds=CompareImageBounds(dup_image,curr,CompareAnyLayer,exception);
1172 if ( cleared ||
1173 dup_bounds.width*dup_bounds.height
1174 +try_bounds.width*try_bounds.height
1175 < bounds[i].width * bounds[i].height )
1176 {
1177 cleared=MagickFalse;
1178 bounds[i]=try_bounds;
1179 disposals[i-1]=DupDispose;
1180 /* to be finalised later, if found to be optimal */
1181 }
1182 else
1183 dup_bounds.width=dup_bounds.height=0;
1184 }
1185 /*
1186 Now compare against a simple background disposal
1187 */
1188 bgnd_image=CloneImage(curr->previous,0,0,MagickTrue,exception);
1189 if (bgnd_image == (Image *) NULL)
1190 {
1191 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1192 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1193 prev_image=DestroyImage(prev_image);
1194 if ( dup_image != (Image *) NULL)
1195 dup_image=DestroyImage(dup_image);
1196 return((Image *) NULL);
1197 }
1198 bgnd_bounds=bounds[i-1]; /* interum bounds of the previous image */
1199 ClearBounds(bgnd_image,&bgnd_bounds);
1200 try_bounds=CompareImageBounds(bgnd_image,curr,CompareAnyLayer,exception);
1201 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1202#if DEBUG_OPT_FRAME
1203 (void) FormatLocaleFile(stderr, "background: %s\n",
1204 try_cleared?"(pixels cleared)":"");
1205#endif
1206 if ( try_cleared )
1207 {
1208 /*
1209 Straight background disposal failed to clear pixels needed!
1210 Lets try expanding the disposal area of the previous frame, to
1211 include the pixels that are cleared. This guaranteed
1212 to work, though may not be the most optimized solution.
1213 */
1214 try_bounds=CompareImageBounds(curr->previous,curr,CompareClearLayer,exception);
1215#if DEBUG_OPT_FRAME
1216 (void) FormatLocaleFile(stderr, "expand_clear: %.20gx%.20g%+.20g%+.20g%s\n",
1217 (double) try_bounds.width,(double) try_bounds.height,
1218 (double) try_bounds.x,(double) try_bounds.y,
1219 try_bounds.x<0?" (no expand necessary)":"");
1220#endif
1221 if ( bgnd_bounds.x < 0 )
1222 bgnd_bounds = try_bounds;
1223 else
1224 {
1225#if DEBUG_OPT_FRAME
1226 (void) FormatLocaleFile(stderr, "expand_bgnd: %.20gx%.20g%+.20g%+.20g\n",
1227 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1228 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1229#endif
1230 if ( try_bounds.x < bgnd_bounds.x )
1231 {
1232 bgnd_bounds.width+= bgnd_bounds.x-try_bounds.x;
1233 if ( bgnd_bounds.width < try_bounds.width )
1234 bgnd_bounds.width = try_bounds.width;
1235 bgnd_bounds.x = try_bounds.x;
1236 }
1237 else
1238 {
1239 try_bounds.width += try_bounds.x - bgnd_bounds.x;
1240 if ( bgnd_bounds.width < try_bounds.width )
1241 bgnd_bounds.width = try_bounds.width;
1242 }
1243 if ( try_bounds.y < bgnd_bounds.y )
1244 {
1245 bgnd_bounds.height += bgnd_bounds.y - try_bounds.y;
1246 if ( bgnd_bounds.height < try_bounds.height )
1247 bgnd_bounds.height = try_bounds.height;
1248 bgnd_bounds.y = try_bounds.y;
1249 }
1250 else
1251 {
1252 try_bounds.height += try_bounds.y - bgnd_bounds.y;
1253 if ( bgnd_bounds.height < try_bounds.height )
1254 bgnd_bounds.height = try_bounds.height;
1255 }
1256#if DEBUG_OPT_FRAME
1257 (void) FormatLocaleFile(stderr, " to : %.20gx%.20g%+.20g%+.20g\n",
1258 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1259 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1260#endif
1261 }
1262 ClearBounds(bgnd_image,&bgnd_bounds);
1263#if DEBUG_OPT_FRAME
1264/* Something strange is happening with a specific animation
1265 * CompareAnyLayers (normal method) and CompareClearLayers returns the whole
1266 * image, which is not possibly correct! As verified by previous tests.
1267 * Something changed beyond the bgnd_bounds clearing. But without being able
1268 * to see, or writet he image at this point it is hard to tell what is wrong!
1269 * Only CompareOverlay seemed to return something sensible.
1270 */
1271 try_bounds=CompareImageBounds(bgnd_image,curr,CompareClearLayer,exception);
1272 (void) FormatLocaleFile(stderr, "expand_ctst: %.20gx%.20g%+.20g%+.20g\n",
1273 (double) try_bounds.width,(double) try_bounds.height,
1274 (double) try_bounds.x,(double) try_bounds.y );
1275 try_bounds=CompareImageBounds(bgnd_image,curr,CompareAnyLayer,exception);
1276 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1277 (void) FormatLocaleFile(stderr, "expand_any : %.20gx%.20g%+.20g%+.20g%s\n",
1278 (double) try_bounds.width,(double) try_bounds.height,
1279 (double) try_bounds.x,(double) try_bounds.y,
1280 try_cleared?" (pixels cleared)":"");
1281#endif
1282 try_bounds=CompareImageBounds(bgnd_image,curr,CompareOverlayLayer,exception);
1283#if DEBUG_OPT_FRAME
1284 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
1285 (void) FormatLocaleFile(stderr, "expand_test: %.20gx%.20g%+.20g%+.20g%s\n",
1286 (double) try_bounds.width,(double) try_bounds.height,
1287 (double) try_bounds.x,(double) try_bounds.y,
1288 try_cleared?" (pixels cleared)":"");
1289#endif
1290 }
1291 /*
1292 Test if this background dispose is smaller than any of the
1293 other methods we tried before this (including duplicated frame)
1294 */
1295 if ( cleared ||
1296 bgnd_bounds.width*bgnd_bounds.height
1297 +try_bounds.width*try_bounds.height
1298 < bounds[i-1].width*bounds[i-1].height
1299 +dup_bounds.width*dup_bounds.height
1300 +bounds[i].width*bounds[i].height )
1301 {
1302 cleared=MagickFalse;
1303 bounds[i-1]=bgnd_bounds;
1304 bounds[i]=try_bounds;
1305 if ( disposals[i-1] == DupDispose )
1306 dup_image=DestroyImage(dup_image);
1307 disposals[i-1]=BackgroundDispose;
1308#if DEBUG_OPT_FRAME
1309 (void) FormatLocaleFile(stderr,"expand_bgnd: accepted\n");
1310 } else {
1311 (void) FormatLocaleFile(stderr,"expand_bgnd: reject\n");
1312#endif
1313 }
1314 }
1315 /*
1316 Finalise choice of dispose, set new prev_image,
1317 and junk any extra images as appropriate,
1318 */
1319 if ( disposals[i-1] == DupDispose )
1320 {
1321 if (bgnd_image != (Image *) NULL)
1322 bgnd_image=DestroyImage(bgnd_image);
1323 prev_image=DestroyImage(prev_image);
1324 prev_image=dup_image, dup_image=(Image *) NULL;
1325 bounds[i+1]=bounds[i];
1326 bounds[i]=dup_bounds;
1327 disposals[i-1]=DupDispose;
1328 disposals[i]=BackgroundDispose;
1329 i++;
1330 }
1331 else
1332 {
1333 if ( dup_image != (Image *) NULL)
1334 dup_image=DestroyImage(dup_image);
1335 if ( disposals[i-1] != PreviousDispose )
1336 prev_image=DestroyImage(prev_image);
1337 if ( disposals[i-1] == BackgroundDispose )
1338 prev_image=bgnd_image, bgnd_image=(Image *) NULL;
1339 if (bgnd_image != (Image *) NULL)
1340 bgnd_image=DestroyImage(bgnd_image);
1341 if ( disposals[i-1] == NoneDispose )
1342 {
1343 prev_image=ReferenceImage(curr->previous);
1344 if (prev_image == (Image *) NULL)
1345 {
1346 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1347 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1348 return((Image *) NULL);
1349 }
1350 }
1351
1352 }
1353 assert(prev_image != (Image *) NULL);
1354 disposals[i]=disposals[i-1];
1355#if DEBUG_OPT_FRAME
1356 (void) FormatLocaleFile(stderr, "final %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1357 (double) i-1,
1358 CommandOptionToMnemonic(MagickDisposeOptions,disposals[i-1]),
1359 (double) bounds[i-1].width,(double) bounds[i-1].height,
1360 (double) bounds[i-1].x,(double) bounds[i-1].y );
1361#endif
1362#if DEBUG_OPT_FRAME
1363 (void) FormatLocaleFile(stderr, "interim %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
1364 (double) i,
1365 CommandOptionToMnemonic(MagickDisposeOptions,disposals[i]),
1366 (double) bounds[i].width,(double) bounds[i].height,
1367 (double) bounds[i].x,(double) bounds[i].y );
1368 (void) FormatLocaleFile(stderr,"\n");
1369#endif
1370 i++;
1371 }
1372 prev_image=DestroyImage(prev_image);
1373 /*
1374 Optimize all images in sequence.
1375 */
1376 sans_exception=AcquireExceptionInfo();
1377 i=0;
1378 curr=GetFirstImageInList(image);
1379 optimized_image=NewImageList();
1380 while ( curr != (const Image *) NULL )
1381 {
1382 prev_image=CloneImage(curr,0,0,MagickTrue,exception);
1383 if (prev_image == (Image *) NULL)
1384 break;
1385 if ( disposals[i] == DelDispose ) {
1386 size_t time = 0;
1387 while ( disposals[i] == DelDispose ) {
1388 time += (size_t) (curr->delay*1000*
1389 PerceptibleReciprocal((double) curr->ticks_per_second));
1390 curr=GetNextImageInList(curr);
1391 i++;
1392 }
1393 time += (size_t) (curr->delay*1000*
1394 PerceptibleReciprocal((double) curr->ticks_per_second));
1395 prev_image->ticks_per_second = 100L;
1396 prev_image->delay = time*prev_image->ticks_per_second/1000;
1397 }
1398 bgnd_image=CropImage(prev_image,&bounds[i],sans_exception);
1399 prev_image=DestroyImage(prev_image);
1400 if (bgnd_image == (Image *) NULL)
1401 break;
1402 bgnd_image->dispose=disposals[i];
1403 if ( disposals[i] == DupDispose ) {
1404 bgnd_image->delay=0;
1405 bgnd_image->dispose=NoneDispose;
1406 }
1407 else
1408 curr=GetNextImageInList(curr);
1409 AppendImageToList(&optimized_image,bgnd_image);
1410 i++;
1411 }
1412 sans_exception=DestroyExceptionInfo(sans_exception);
1413 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1414 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1415 if (curr != (Image *) NULL)
1416 {
1417 optimized_image=DestroyImageList(optimized_image);
1418 return((Image *) NULL);
1419 }
1420 return(GetFirstImageInList(optimized_image));
1421}
1422
1423/*
1424%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1425% %
1426% %
1427% %
1428% O p t i m i z e I m a g e L a y e r s %
1429% %
1430% %
1431% %
1432%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1433%
1434% OptimizeImageLayers() compares each image the GIF disposed forms of the
1435% previous image in the sequence. From this it attempts to select the
1436% smallest cropped image to replace each frame, while preserving the results
1437% of the GIF animation.
1438%
1439% The format of the OptimizeImageLayers method is:
1440%
1441% Image *OptimizeImageLayers(const Image *image,
1442% ExceptionInfo *exception)
1443%
1444% A description of each parameter follows:
1445%
1446% o image: the image.
1447%
1448% o exception: return any errors or warnings in this structure.
1449%
1450*/
1451MagickExport Image *OptimizeImageLayers(const Image *image,
1452 ExceptionInfo *exception)
1453{
1454 return(OptimizeLayerFrames(image,OptimizeImageLayer,exception));
1455}
1456
1457/*
1458%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1459% %
1460% %
1461% %
1462% O p t i m i z e P l u s I m a g e L a y e r s %
1463% %
1464% %
1465% %
1466%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1467%
1468% OptimizeImagePlusLayers() is exactly as OptimizeImageLayers(), but may
1469% also add or even remove extra frames in the animation, if it improves
1470% the total number of pixels in the resulting GIF animation.
1471%
1472% The format of the OptimizePlusImageLayers method is:
1473%
1474% Image *OptimizePlusImageLayers(const Image *image,
1475% ExceptionInfo *exception)
1476%
1477% A description of each parameter follows:
1478%
1479% o image: the image.
1480%
1481% o exception: return any errors or warnings in this structure.
1482%
1483*/
1484MagickExport Image *OptimizePlusImageLayers(const Image *image,
1485 ExceptionInfo *exception)
1486{
1487 return OptimizeLayerFrames(image,OptimizePlusLayer,exception);
1488}
1489
1490/*
1491%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1492% %
1493% %
1494% %
1495% 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 %
1496% %
1497% %
1498% %
1499%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1500%
1501% OptimizeImageTransparency() takes a frame optimized GIF animation, and
1502% compares the overlayed pixels against the disposal image resulting from all
1503% the previous frames in the animation. Any pixel that does not change the
1504% disposal image (and thus does not effect the outcome of an overlay) is made
1505% transparent.
1506%
1507% WARNING: This modifies the current images directly, rather than generate
1508% a new image sequence.
1509%
1510% The format of the OptimizeImageTransparency method is:
1511%
1512% void OptimizeImageTransparency(Image *image,ExceptionInfo *exception)
1513%
1514% A description of each parameter follows:
1515%
1516% o image: the image sequence
1517%
1518% o exception: return any errors or warnings in this structure.
1519%
1520*/
1521MagickExport void OptimizeImageTransparency(const Image *image,
1522 ExceptionInfo *exception)
1523{
1524 Image
1525 *dispose_image;
1526
1527 Image
1528 *next;
1529
1530 /*
1531 Run the image through the animation sequence
1532 */
1533 assert(image != (Image *) NULL);
1534 assert(image->signature == MagickCoreSignature);
1535 assert(exception != (ExceptionInfo *) NULL);
1536 assert(exception->signature == MagickCoreSignature);
1537 if (IsEventLogging() != MagickFalse)
1538 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1539 next=GetFirstImageInList(image);
1540 dispose_image=CloneImage(next,next->page.width,next->page.height,
1541 MagickTrue,exception);
1542 if (dispose_image == (Image *) NULL)
1543 return;
1544 dispose_image->page=next->page;
1545 dispose_image->page.x=0;
1546 dispose_image->page.y=0;
1547 dispose_image->dispose=NoneDispose;
1548 dispose_image->background_color.opacity=(Quantum) TransparentOpacity;
1549 (void) SetImageBackgroundColor(dispose_image);
1550
1551 while ( next != (Image *) NULL )
1552 {
1553 Image
1554 *current_image;
1555
1556 /*
1557 Overlay this frame's image over the previous disposal image
1558 */
1559 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
1560 if (current_image == (Image *) NULL)
1561 {
1562 dispose_image=DestroyImage(dispose_image);
1563 return;
1564 }
1565 (void) CompositeImage(current_image,next->matte != MagickFalse ?
1566 OverCompositeOp : CopyCompositeOp,next,next->page.x,next->page.y);
1567 /*
1568 At this point the image would be displayed, for the delay period
1569 **
1570 Work out the disposal of the previous image
1571 */
1572 if (next->dispose == BackgroundDispose)
1573 {
1575 bounds=next->page;
1576
1577 bounds.width=next->columns;
1578 bounds.height=next->rows;
1579 if (bounds.x < 0)
1580 {
1581 bounds.width+=bounds.x;
1582 bounds.x=0;
1583 }
1584 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
1585 bounds.width=current_image->columns-bounds.x;
1586 if (bounds.y < 0)
1587 {
1588 bounds.height+=bounds.y;
1589 bounds.y=0;
1590 }
1591 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
1592 bounds.height=current_image->rows-bounds.y;
1593 ClearBounds(current_image,&bounds);
1594 }
1595 if (next->dispose != PreviousDispose)
1596 {
1597 dispose_image=DestroyImage(dispose_image);
1598 dispose_image=current_image;
1599 }
1600 else
1601 current_image=DestroyImage(current_image);
1602
1603 /*
1604 Optimize Transparency of the next frame (if present)
1605 */
1606 next=GetNextImageInList(next);
1607 if (next != (Image *) NULL)
1608 (void) CompositeImage(next,ChangeMaskCompositeOp,dispose_image,
1609 -(next->page.x),-(next->page.y));
1610 }
1611 dispose_image=DestroyImage(dispose_image);
1612 return;
1613}
1614
1615/*
1616%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1617% %
1618% %
1619% %
1620% R e m o v e D u p l i c a t e L a y e r s %
1621% %
1622% %
1623% %
1624%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1625%
1626% RemoveDuplicateLayers() removes any image that is exactly the same as the
1627% next image in the given image list. Image size and virtual canvas offset
1628% must also match, though not the virtual canvas size itself.
1629%
1630% No check is made with regards to image disposal setting, though it is the
1631% dispose setting of later image that is kept. Also any time delays are also
1632% added together. As such coalesced image animations should still produce the
1633% same result, though with duplicate frames merged into a single frame.
1634%
1635% The format of the RemoveDuplicateLayers method is:
1636%
1637% void RemoveDuplicateLayers(Image **image,ExceptionInfo *exception)
1638%
1639% A description of each parameter follows:
1640%
1641% o images: the image list
1642%
1643% o exception: return any errors or warnings in this structure.
1644%
1645*/
1646MagickExport void RemoveDuplicateLayers(Image **images,ExceptionInfo *exception)
1647{
1649 bounds;
1650
1651 Image
1652 *image,
1653 *next;
1654
1655 assert((*images) != (const Image *) NULL);
1656 assert((*images)->signature == MagickCoreSignature);
1657 assert(exception != (ExceptionInfo *) NULL);
1658 assert(exception->signature == MagickCoreSignature);
1659 if (IsEventLogging() != MagickFalse)
1660 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
1661 (*images)->filename);
1662 image=GetFirstImageInList(*images);
1663 for ( ; (next=GetNextImageInList(image)) != (Image *) NULL; image=next)
1664 {
1665 if ((image->columns != next->columns) || (image->rows != next->rows) ||
1666 (image->page.x != next->page.x) || (image->page.y != next->page.y))
1667 continue;
1668 bounds=CompareImageBounds(image,next,CompareAnyLayer,exception);
1669 if (bounds.x < 0)
1670 {
1671 /*
1672 Two images are the same, merge time delays and delete one.
1673 */
1674 size_t
1675 time;
1676
1677 time=1000*image->delay*PerceptibleReciprocal(image->ticks_per_second);
1678 time+=1000*next->delay*PerceptibleReciprocal(next->ticks_per_second);
1679 next->ticks_per_second=100L;
1680 next->delay=time*image->ticks_per_second/1000;
1681 next->iterations=image->iterations;
1682 *images=image;
1683 (void) DeleteImageFromList(images);
1684 }
1685 }
1686 *images=GetFirstImageInList(*images);
1687}
1688
1689/*
1690%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1691% %
1692% %
1693% %
1694% R e m o v e Z e r o D e l a y L a y e r s %
1695% %
1696% %
1697% %
1698%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1699%
1700% RemoveZeroDelayLayers() removes any image that as a zero delay time. Such
1701% images generally represent intermediate or partial updates in GIF
1702% animations used for file optimization. They are not ment to be displayed
1703% to users of the animation. Viewable images in an animation should have a
1704% time delay of 3 or more centi-seconds (hundredths of a second).
1705%
1706% However if all the frames have a zero time delay, then either the animation
1707% is as yet incomplete, or it is not a GIF animation. This a non-sensible
1708% situation, so no image will be removed and a 'Zero Time Animation' warning
1709% (exception) given.
1710%
1711% No warning will be given if no image was removed because all images had an
1712% appropriate non-zero time delay set.
1713%
1714% Due to the special requirements of GIF disposal handling, GIF animations
1715% should be coalesced first, before calling this function, though that is not
1716% a requirement.
1717%
1718% The format of the RemoveZeroDelayLayers method is:
1719%
1720% void RemoveZeroDelayLayers(Image **image,ExceptionInfo *exception)
1721%
1722% A description of each parameter follows:
1723%
1724% o images: the image list
1725%
1726% o exception: return any errors or warnings in this structure.
1727%
1728*/
1729MagickExport void RemoveZeroDelayLayers(Image **images,
1730 ExceptionInfo *exception)
1731{
1732 Image
1733 *i;
1734
1735 assert((*images) != (const Image *) NULL);
1736 assert((*images)->signature == MagickCoreSignature);
1737 assert(exception != (ExceptionInfo *) NULL);
1738 assert(exception->signature == MagickCoreSignature);
1739 if (IsEventLogging() != MagickFalse)
1740 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
1741 (*images)->filename);
1742 i=GetFirstImageInList(*images);
1743 for ( ; i != (Image *) NULL; i=GetNextImageInList(i))
1744 if ( i->delay != 0L ) break;
1745 if ( i == (Image *) NULL ) {
1746 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
1747 "ZeroTimeAnimation","`%s'",GetFirstImageInList(*images)->filename);
1748 return;
1749 }
1750 i=GetFirstImageInList(*images);
1751 while ( i != (Image *) NULL )
1752 {
1753 if ( i->delay == 0L ) {
1754 (void) DeleteImageFromList(&i);
1755 *images=i;
1756 }
1757 else
1758 i=GetNextImageInList(i);
1759 }
1760 *images=GetFirstImageInList(*images);
1761}
1762
1763/*
1764%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1765% %
1766% %
1767% %
1768% C o m p o s i t e L a y e r s %
1769% %
1770% %
1771% %
1772%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1773%
1774% CompositeLayers() compose the source image sequence over the destination
1775% image sequence, starting with the current image in both lists.
1776%
1777% Each layer from the two image lists are composted together until the end of
1778% one of the image lists is reached. The offset of each composition is also
1779% adjusted to match the virtual canvas offsets of each layer. As such the
1780% given offset is relative to the virtual canvas, and not the actual image.
1781%
1782% Composition uses given x and y offsets, as the 'origin' location of the
1783% source images virtual canvas (not the real image) allowing you to compose a
1784% list of 'layer images' into the destination images. This makes it well
1785% suitable for directly composing 'Clears Frame Animations' or 'Coalesced
1786% Animations' onto a static or other 'Coalesced Animation' destination image
1787% list. GIF disposal handling is not looked at.
1788%
1789% Special case:- If one of the image sequences is the last image (just a
1790% single image remaining), that image is repeatedly composed with all the
1791% images in the other image list. Either the source or destination lists may
1792% be the single image, for this situation.
1793%
1794% In the case of a single destination image (or last image given), that image
1795% will ve cloned to match the number of images remaining in the source image
1796% list.
1797%
1798% This is equivalent to the "-layer Composite" Shell API operator.
1799%
1800%
1801% The format of the CompositeLayers method is:
1802%
1803% void CompositeLayers(Image *destination,
1804% const CompositeOperator compose, Image *source,
1805% const ssize_t x_offset, const ssize_t y_offset,
1806% ExceptionInfo *exception);
1807%
1808% A description of each parameter follows:
1809%
1810% o destination: the destination images and results
1811%
1812% o source: source image(s) for the layer composition
1813%
1814% o compose, x_offset, y_offset: arguments passed on to CompositeImages()
1815%
1816% o exception: return any errors or warnings in this structure.
1817%
1818*/
1819static inline void CompositeCanvas(Image *destination,
1820 const CompositeOperator compose, Image *source,ssize_t x_offset,
1821 ssize_t y_offset )
1822{
1823 x_offset+=source->page.x-destination->page.x;
1824 y_offset+=source->page.y-destination->page.y;
1825 (void) CompositeImage(destination,compose,source,x_offset,y_offset);
1826}
1827
1828MagickExport void CompositeLayers(Image *destination,
1829 const CompositeOperator compose, Image *source,const ssize_t x_offset,
1830 const ssize_t y_offset,ExceptionInfo *exception)
1831{
1832 assert(destination != (Image *) NULL);
1833 assert(destination->signature == MagickCoreSignature);
1834 assert(source != (Image *) NULL);
1835 assert(source->signature == MagickCoreSignature);
1836 assert(exception != (ExceptionInfo *) NULL);
1837 assert(exception->signature == MagickCoreSignature);
1838 if (IsEventLogging() != MagickFalse)
1839 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s - %s",
1840 source->filename,destination->filename);
1841 /*
1842 Overlay single source image over destination image/list
1843 */
1844 if (source->next == (Image *) NULL)
1845 while ( destination != (Image *) NULL )
1846 {
1847 CompositeCanvas(destination,compose,source,x_offset,y_offset);
1848 destination=GetNextImageInList(destination);
1849 }
1850
1851 /*
1852 Overlay source image list over single destination
1853 Generating multiple clones of destination image to match source list.
1854 Original Destination image becomes first image of generated list.
1855 As such the image list pointer does not require any change in caller.
1856 Some animation attributes however also needs coping in this case.
1857 */
1858 else if ( destination->next == (Image *) NULL )
1859 {
1860 Image *dest = CloneImage(destination,0,0,MagickTrue,exception);
1861
1862 CompositeCanvas(destination,compose,source,x_offset,y_offset);
1863 /* copy source image attributes ? */
1864 if ( source->next != (Image *) NULL )
1865 {
1866 destination->delay = source->delay;
1867 destination->iterations = source->iterations;
1868 }
1869 source=GetNextImageInList(source);
1870
1871 while ( source != (Image *) NULL )
1872 {
1873 AppendImageToList(&destination,
1874 CloneImage(dest,0,0,MagickTrue,exception));
1875 destination=GetLastImageInList(destination);
1876
1877 CompositeCanvas(destination,compose,source,x_offset,y_offset);
1878 destination->delay = source->delay;
1879 destination->iterations = source->iterations;
1880 source=GetNextImageInList(source);
1881 }
1882 dest=DestroyImage(dest);
1883 }
1884
1885 /*
1886 Overlay a source image list over a destination image list
1887 until either list runs out of images. (Does not repeat)
1888 */
1889 else
1890 while ( source != (Image *) NULL && destination != (Image *) NULL )
1891 {
1892 CompositeCanvas(destination,compose,source,x_offset,y_offset);
1893 source=GetNextImageInList(source);
1894 destination=GetNextImageInList(destination);
1895 }
1896}
1897
1898/*
1899%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1900% %
1901% %
1902% %
1903% M e r g e I m a g e L a y e r s %
1904% %
1905% %
1906% %
1907%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1908%
1909% MergeImageLayers() composes all the image layers from the current given
1910% image onward to produce a single image of the merged layers.
1911%
1912% The inital canvas's size depends on the given ImageLayerMethod, and is
1913% initialized using the first images background color. The images
1914% are then composited onto that image in sequence using the given
1915% composition that has been assigned to each individual image.
1916%
1917% The format of the MergeImageLayers is:
1918%
1919% Image *MergeImageLayers(const Image *image,
1920% const ImageLayerMethod method,ExceptionInfo *exception)
1921%
1922% A description of each parameter follows:
1923%
1924% o image: the image list to be composited together
1925%
1926% o method: the method of selecting the size of the initial canvas.
1927%
1928% MergeLayer: Merge all layers onto a canvas just large enough
1929% to hold all the actual images. The virtual canvas of the
1930% first image is preserved but otherwise ignored.
1931%
1932% FlattenLayer: Use the virtual canvas size of first image.
1933% Images which fall outside this canvas is clipped.
1934% This can be used to 'fill out' a given virtual canvas.
1935%
1936% MosaicLayer: Start with the virtual canvas of the first image,
1937% enlarging left and right edges to contain all images.
1938% Images with negative offsets will be clipped.
1939%
1940% TrimBoundsLayer: Determine the overall bounds of all the image
1941% layers just as in "MergeLayer", then adjust the canvas
1942% and offsets to be relative to those bounds, without overlaying
1943% the images.
1944%
1945% WARNING: a new image is not returned, the original image
1946% sequence page data is modified instead.
1947%
1948% o exception: return any errors or warnings in this structure.
1949%
1950*/
1951MagickExport Image *MergeImageLayers(Image *image,
1952 const ImageLayerMethod method,ExceptionInfo *exception)
1953{
1954#define MergeLayersTag "Merge/Layers"
1955
1956 Image
1957 *canvas;
1958
1959 MagickBooleanType
1960 proceed;
1961
1963 page;
1964
1965 const Image
1966 *next;
1967
1968 size_t
1969 number_images,
1970 height,
1971 width;
1972
1973 ssize_t
1974 scene;
1975
1976 assert(image != (Image *) NULL);
1977 assert(image->signature == MagickCoreSignature);
1978 assert(exception != (ExceptionInfo *) NULL);
1979 assert(exception->signature == MagickCoreSignature);
1980 if (IsEventLogging() != MagickFalse)
1981 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1982 /*
1983 Determine canvas image size, and its virtual canvas size and offset.
1984 */
1985 page=image->page;
1986 width=image->columns;
1987 height=image->rows;
1988 switch (method)
1989 {
1990 case TrimBoundsLayer:
1991 case MergeLayer:
1992 default:
1993 {
1994 next=GetNextImageInList(image);
1995 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
1996 {
1997 if (page.x > next->page.x)
1998 {
1999 width+=page.x-next->page.x;
2000 page.x=next->page.x;
2001 }
2002 if (page.y > next->page.y)
2003 {
2004 height+=page.y-next->page.y;
2005 page.y=next->page.y;
2006 }
2007 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns-page.x))
2008 width=(size_t) next->page.x+(ssize_t) next->columns-page.x;
2009 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows-page.y))
2010 height=(size_t) next->page.y+(ssize_t) next->rows-page.y;
2011 }
2012 break;
2013 }
2014 case FlattenLayer:
2015 {
2016 if (page.width > 0)
2017 width=page.width;
2018 if (page.height > 0)
2019 height=page.height;
2020 page.x=0;
2021 page.y=0;
2022 break;
2023 }
2024 case MosaicLayer:
2025 {
2026 if (page.width > 0)
2027 width=page.width;
2028 if (page.height > 0)
2029 height=page.height;
2030 for (next=image; next != (Image *) NULL; next=GetNextImageInList(next))
2031 {
2032 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns))
2033 width=(size_t) next->page.x+next->columns;
2034 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows))
2035 height=(size_t) next->page.y+next->rows;
2036 }
2037 page.width=width;
2038 page.height=height;
2039 page.x=0;
2040 page.y=0;
2041 break;
2042 }
2043 }
2044 /*
2045 Set virtual canvas size if not defined.
2046 */
2047 if (page.width == 0)
2048 page.width=page.x < 0 ? width : width+page.x;
2049 if (page.height == 0)
2050 page.height=page.y < 0 ? height : height+page.y;
2051 /*
2052 Handle "TrimBoundsLayer" method separately to normal 'layer merge'.
2053 */
2054 if (method == TrimBoundsLayer)
2055 {
2056 number_images=GetImageListLength(image);
2057 for (scene=0; scene < (ssize_t) number_images; scene++)
2058 {
2059 image->page.x-=page.x;
2060 image->page.y-=page.y;
2061 image->page.width=width;
2062 image->page.height=height;
2063 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2064 number_images);
2065 if (proceed == MagickFalse)
2066 break;
2067 image=GetNextImageInList(image);
2068 if (image == (Image *) NULL)
2069 break;
2070 }
2071 return((Image *) NULL);
2072 }
2073 /*
2074 Create canvas size of width and height, and background color.
2075 */
2076 canvas=CloneImage(image,width,height,MagickTrue,exception);
2077 if (canvas == (Image *) NULL)
2078 return((Image *) NULL);
2079 (void) SetImageBackgroundColor(canvas);
2080 canvas->page=page;
2081 canvas->dispose=UndefinedDispose;
2082 /*
2083 Compose images onto canvas, with progress monitor
2084 */
2085 number_images=GetImageListLength(image);
2086 for (scene=0; scene < (ssize_t) number_images; scene++)
2087 {
2088 (void) CompositeImage(canvas,image->compose,image,image->page.x-
2089 canvas->page.x,image->page.y-canvas->page.y);
2090 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2091 number_images);
2092 if (proceed == MagickFalse)
2093 break;
2094 image=GetNextImageInList(image);
2095 if (image == (Image *) NULL)
2096 break;
2097 }
2098 return(canvas);
2099}