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