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