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