blob: 64d02ccca9cfc962f07b480fe568511b6bb0eca6 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
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 %
cristyde984cd2013-12-01 14:49:27 +000014% Cristy %
cristy3ed852e2009-09-05 21:47:34 +000015% Anthony Thyssen %
16% January 2006 %
17% %
18% %
Cristyf6ff9ea2016-12-05 09:53:35 -050019% Copyright 1999-2017 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000020% 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% %
Cristyf19d4142017-04-24 11:34:30 -040025% https://www.imagemagick.org/script/license.php %
cristy3ed852e2009-09-05 21:47:34 +000026% %
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*/
cristy4c08aed2011-07-01 19:47:50 +000040#include "MagickCore/studio.h"
41#include "MagickCore/artifact.h"
42#include "MagickCore/cache.h"
cristy6a2180c2013-05-27 10:28:36 +000043#include "MagickCore/channel.h"
cristy4c08aed2011-07-01 19:47:50 +000044#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"
cristy3ed852e2009-09-05 21:47:34 +000066
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
cristyf9aa1732013-02-27 19:55:06 +000079% transparency. This typically used to handle Background Disposal for the
80% previous frame in an animation sequence.
cristy3ed852e2009-09-05 21:47:34 +000081%
cristyf9aa1732013-02-27 19:55:06 +000082% 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.
cristy3ed852e2009-09-05 21:47:34 +000085%
86% The format is:
87%
cristyf9aa1732013-02-27 19:55:06 +000088% void ClearBounds(Image *image,RectangleInfo *bounds,
cristy7c3af952011-10-20 16:04:16 +000089% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +000090%
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%
cristy7c3af952011-10-20 16:04:16 +000097% o exception: return any errors or warnings in this structure.
98%
cristy3ed852e2009-09-05 21:47:34 +000099*/
cristy7c3af952011-10-20 16:04:16 +0000100static void ClearBounds(Image *image,RectangleInfo *bounds,
101 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000102{
cristybb503372010-05-27 20:51:26 +0000103 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000104 y;
105
106 if (bounds->x < 0)
107 return;
cristy17f11b02014-12-20 19:37:04 +0000108 if (image->alpha_trait == UndefinedPixelTrait)
cristy63240882011-08-05 19:05:27 +0000109 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristybb503372010-05-27 20:51:26 +0000110 for (y=0; y < (ssize_t) bounds->height; y++)
cristy3ed852e2009-09-05 21:47:34 +0000111 {
cristybb503372010-05-27 20:51:26 +0000112 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000113 x;
114
cristy4c08aed2011-07-01 19:47:50 +0000115 register Quantum
dirk05d2ff72015-11-18 23:13:43 +0100116 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000117
118 q=GetAuthenticPixels(image,bounds->x,bounds->y+y,bounds->width,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000119 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000120 break;
cristybb503372010-05-27 20:51:26 +0000121 for (x=0; x < (ssize_t) bounds->width; x++)
cristy3ed852e2009-09-05 21:47:34 +0000122 {
cristy4c08aed2011-07-01 19:47:50 +0000123 SetPixelAlpha(image,TransparentAlpha,q);
cristyed231572011-07-14 02:18:59 +0000124 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000125 }
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%
cristyf9aa1732013-02-27 19:55:06 +0000147% 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%
cristy3ed852e2009-09-05 21:47:34 +0000151% 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%
cristy3ed852e2009-09-05 21:47:34 +0000164*/
165static MagickBooleanType IsBoundsCleared(const Image *image1,
166 const Image *image2,RectangleInfo *bounds,ExceptionInfo *exception)
167{
cristy4c08aed2011-07-01 19:47:50 +0000168 register const Quantum
cristy3ed852e2009-09-05 21:47:34 +0000169 *p,
170 *q;
171
cristyf9aa1732013-02-27 19:55:06 +0000172 register ssize_t
173 x;
174
cristy9d314ff2011-03-09 01:30:28 +0000175 ssize_t
176 y;
177
cristye23ec9d2011-08-16 18:15:40 +0000178 if (bounds->x < 0)
179 return(MagickFalse);
cristybb503372010-05-27 20:51:26 +0000180 for (y=0; y < (ssize_t) bounds->height; y++)
cristy3ed852e2009-09-05 21:47:34 +0000181 {
cristyed6987e2013-02-19 14:10:05 +0000182 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);
cristy4c08aed2011-07-01 19:47:50 +0000184 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000185 break;
cristybb503372010-05-27 20:51:26 +0000186 for (x=0; x < (ssize_t) bounds->width; x++)
cristy3ed852e2009-09-05 21:47:34 +0000187 {
dirkaf0b10c2014-10-12 19:36:32 +0000188 if ((GetPixelAlpha(image1,p) >= (Quantum) (QuantumRange/2)) &&
189 (GetPixelAlpha(image2,q) < (Quantum) (QuantumRange/2)))
cristy3ed852e2009-09-05 21:47:34 +0000190 break;
cristyed231572011-07-14 02:18:59 +0000191 p+=GetPixelChannels(image1);
dirkaf0b10c2014-10-12 19:36:32 +0000192 q+=GetPixelChannels(image2);
cristy3ed852e2009-09-05 21:47:34 +0000193 }
cristybb503372010-05-27 20:51:26 +0000194 if (x < (ssize_t) bounds->width)
cristy3ed852e2009-09-05 21:47:34 +0000195 break;
196 }
cristybb503372010-05-27 20:51:26 +0000197 return(y < (ssize_t) bounds->height ? MagickTrue : MagickFalse);
cristy3ed852e2009-09-05 21:47:34 +0000198}
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*/
229MagickExport 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);
cristye1c94d92015-06-28 12:16:33 +0000246 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000247 if (image->debug != MagickFalse)
248 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
249 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000250 assert(exception->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000251 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);
Cristy02e481a2015-08-15 15:44:20 -0400271 coalesce_image->background_color.alpha=(Quantum) TransparentAlpha;
cristy3d1c2de2012-06-07 00:50:22 +0000272 (void) SetImageBackgroundColor(coalesce_image,exception);
cristy8a46d822012-08-28 23:32:39 +0000273 coalesce_image->alpha_trait=next->alpha_trait;
cristy3ed852e2009-09-05 21:47:34 +0000274 coalesce_image->page=bounds;
275 coalesce_image->dispose=NoneDispose;
cristy3ed852e2009-09-05 21:47:34 +0000276 /*
277 Coalesce rest of the images.
278 */
279 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
cristy39172402012-03-30 13:04:39 +0000280 (void) CompositeImage(coalesce_image,next,CopyCompositeOp,MagickTrue,
cristyfeb3e962012-03-29 17:25:55 +0000281 next->page.x,next->page.y,exception);
cristy3ed852e2009-09-05 21:47:34 +0000282 next=GetNextImageInList(next);
283 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
284 {
285 /*
286 Determine the bounds that was overlaid in the previous image.
287 */
288 previous=GetPreviousImageInList(next);
289 bounds=previous->page;
290 bounds.width=previous->columns;
291 bounds.height=previous->rows;
292 if (bounds.x < 0)
293 {
294 bounds.width+=bounds.x;
295 bounds.x=0;
296 }
cristybb503372010-05-27 20:51:26 +0000297 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) coalesce_image->columns)
cristy3ed852e2009-09-05 21:47:34 +0000298 bounds.width=coalesce_image->columns-bounds.x;
299 if (bounds.y < 0)
300 {
301 bounds.height+=bounds.y;
302 bounds.y=0;
303 }
cristybb503372010-05-27 20:51:26 +0000304 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) coalesce_image->rows)
cristy3ed852e2009-09-05 21:47:34 +0000305 bounds.height=coalesce_image->rows-bounds.y;
306 /*
307 Replace the dispose image with the new coalesced image.
308 */
309 if (GetPreviousImageInList(next)->dispose != PreviousDispose)
310 {
311 dispose_image=DestroyImage(dispose_image);
312 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
313 if (dispose_image == (Image *) NULL)
314 {
315 coalesce_image=DestroyImageList(coalesce_image);
316 return((Image *) NULL);
317 }
318 }
319 /*
320 Clear the overlaid area of the coalesced bounds for background disposal
321 */
322 if (next->previous->dispose == BackgroundDispose)
cristy7c3af952011-10-20 16:04:16 +0000323 ClearBounds(dispose_image,&bounds,exception);
cristy3ed852e2009-09-05 21:47:34 +0000324 /*
325 Next image is the dispose image, overlaid with next frame in sequence.
326 */
327 coalesce_image->next=CloneImage(dispose_image,0,0,MagickTrue,exception);
328 coalesce_image->next->previous=coalesce_image;
329 previous=coalesce_image;
330 coalesce_image=GetNextImageInList(coalesce_image);
cristyed6987e2013-02-19 14:10:05 +0000331 (void) CompositeImage(coalesce_image,next,
cristy17f11b02014-12-20 19:37:04 +0000332 next->alpha_trait != UndefinedPixelTrait ? OverCompositeOp : CopyCompositeOp,
cristyed6987e2013-02-19 14:10:05 +0000333 MagickTrue,next->page.x,next->page.y,exception);
cristy3ed852e2009-09-05 21:47:34 +0000334 (void) CloneImageProfiles(coalesce_image,next);
335 (void) CloneImageProperties(coalesce_image,next);
336 (void) CloneImageArtifacts(coalesce_image,next);
337 coalesce_image->page=previous->page;
338 /*
339 If a pixel goes opaque to transparent, use background dispose.
340 */
cristyf9aa1732013-02-27 19:55:06 +0000341 if (IsBoundsCleared(previous,coalesce_image,&bounds,exception) != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000342 coalesce_image->dispose=BackgroundDispose;
343 else
344 coalesce_image->dispose=NoneDispose;
345 previous->dispose=coalesce_image->dispose;
346 }
347 dispose_image=DestroyImage(dispose_image);
348 return(GetFirstImageInList(coalesce_image));
349}
350
351/*
352%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
353% %
354% %
355% %
356% D i s p o s e I m a g e s %
357% %
358% %
359% %
360%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
361%
362% DisposeImages() returns the coalesced frames of a GIF animation as it would
cristyf9aa1732013-02-27 19:55:06 +0000363% appear after the GIF dispose method of that frame has been applied. That is
364% it returned the appearance of each frame before the next is overlaid.
cristy3ed852e2009-09-05 21:47:34 +0000365%
366% The format of the DisposeImages method is:
367%
368% Image *DisposeImages(Image *image,ExceptionInfo *exception)
369%
370% A description of each parameter follows:
371%
cristyfeb3e962012-03-29 17:25:55 +0000372% o images: the image sequence.
cristy3ed852e2009-09-05 21:47:34 +0000373%
374% o exception: return any errors or warnings in this structure.
375%
376*/
cristyfeb3e962012-03-29 17:25:55 +0000377MagickExport Image *DisposeImages(const Image *images,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000378{
379 Image
380 *dispose_image,
381 *dispose_images;
382
cristyf9aa1732013-02-27 19:55:06 +0000383 RectangleInfo
384 bounds;
385
cristy3ed852e2009-09-05 21:47:34 +0000386 register Image
cristyfeb3e962012-03-29 17:25:55 +0000387 *image,
388 *next;
anthonyb6d08c52010-09-13 01:17:04 +0000389
cristy3ed852e2009-09-05 21:47:34 +0000390 /*
391 Run the image through the animation sequence
392 */
cristyfeb3e962012-03-29 17:25:55 +0000393 assert(images != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000394 assert(images->signature == MagickCoreSignature);
cristyfeb3e962012-03-29 17:25:55 +0000395 if (images->debug != MagickFalse)
396 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
cristy3ed852e2009-09-05 21:47:34 +0000397 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000398 assert(exception->signature == MagickCoreSignature);
cristyfeb3e962012-03-29 17:25:55 +0000399 image=GetFirstImageInList(images);
400 dispose_image=CloneImage(image,image->page.width,image->page.height,
401 MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000402 if (dispose_image == (Image *) NULL)
403 return((Image *) NULL);
cristyfeb3e962012-03-29 17:25:55 +0000404 dispose_image->page=image->page;
cristy3ed852e2009-09-05 21:47:34 +0000405 dispose_image->page.x=0;
406 dispose_image->page.y=0;
407 dispose_image->dispose=NoneDispose;
cristy4c08aed2011-07-01 19:47:50 +0000408 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
cristyea1a8aa2011-10-20 13:24:06 +0000409 (void) SetImageBackgroundColor(dispose_image,exception);
cristy3ed852e2009-09-05 21:47:34 +0000410 dispose_images=NewImageList();
cristyfeb3e962012-03-29 17:25:55 +0000411 for (next=image; image != (Image *) NULL; image=GetNextImageInList(image))
cristy3ed852e2009-09-05 21:47:34 +0000412 {
413 Image
414 *current_image;
415
416 /*
417 Overlay this frame's image over the previous disposal image.
418 */
419 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
420 if (current_image == (Image *) NULL)
421 {
422 dispose_images=DestroyImageList(dispose_images);
423 dispose_image=DestroyImage(dispose_image);
424 return((Image *) NULL);
425 }
cristyed6987e2013-02-19 14:10:05 +0000426 (void) CompositeImage(current_image,next,
cristy17f11b02014-12-20 19:37:04 +0000427 next->alpha_trait != UndefinedPixelTrait ? OverCompositeOp : CopyCompositeOp,
cristyed6987e2013-02-19 14:10:05 +0000428 MagickTrue,next->page.x,next->page.y,exception);
cristy3ed852e2009-09-05 21:47:34 +0000429 /*
430 Handle Background dispose: image is displayed for the delay period.
431 */
cristyfeb3e962012-03-29 17:25:55 +0000432 if (next->dispose == BackgroundDispose)
cristy3ed852e2009-09-05 21:47:34 +0000433 {
cristyfeb3e962012-03-29 17:25:55 +0000434 bounds=next->page;
435 bounds.width=next->columns;
436 bounds.height=next->rows;
cristy3ed852e2009-09-05 21:47:34 +0000437 if (bounds.x < 0)
438 {
439 bounds.width+=bounds.x;
440 bounds.x=0;
441 }
cristybb503372010-05-27 20:51:26 +0000442 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
cristy3ed852e2009-09-05 21:47:34 +0000443 bounds.width=current_image->columns-bounds.x;
444 if (bounds.y < 0)
445 {
446 bounds.height+=bounds.y;
447 bounds.y=0;
448 }
cristybb503372010-05-27 20:51:26 +0000449 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
cristy3ed852e2009-09-05 21:47:34 +0000450 bounds.height=current_image->rows-bounds.y;
cristy7c3af952011-10-20 16:04:16 +0000451 ClearBounds(current_image,&bounds,exception);
cristy3ed852e2009-09-05 21:47:34 +0000452 }
453 /*
454 Select the appropriate previous/disposed image.
455 */
cristyfeb3e962012-03-29 17:25:55 +0000456 if (next->dispose == PreviousDispose)
cristy3ed852e2009-09-05 21:47:34 +0000457 current_image=DestroyImage(current_image);
458 else
459 {
460 dispose_image=DestroyImage(dispose_image);
461 dispose_image=current_image;
cristyf9aa1732013-02-27 19:55:06 +0000462 current_image=(Image *) NULL;
cristy3ed852e2009-09-05 21:47:34 +0000463 }
anthonyb6d08c52010-09-13 01:17:04 +0000464 /*
465 Save the dispose image just calculated for return.
466 */
cristy3ed852e2009-09-05 21:47:34 +0000467 {
468 Image
469 *dispose;
470
cristy3ed852e2009-09-05 21:47:34 +0000471 dispose=CloneImage(dispose_image,0,0,MagickTrue,exception);
472 if (dispose == (Image *) NULL)
473 {
474 dispose_images=DestroyImageList(dispose_images);
475 dispose_image=DestroyImage(dispose_image);
476 return((Image *) NULL);
477 }
cristyfeb3e962012-03-29 17:25:55 +0000478 (void) CloneImageProfiles(dispose,next);
479 (void) CloneImageProperties(dispose,next);
480 (void) CloneImageArtifacts(dispose,next);
cristy3ed852e2009-09-05 21:47:34 +0000481 dispose->page.x=0;
482 dispose->page.y=0;
cristyfeb3e962012-03-29 17:25:55 +0000483 dispose->dispose=next->dispose;
cristy3ed852e2009-09-05 21:47:34 +0000484 AppendImageToList(&dispose_images,dispose);
485 }
486 }
487 dispose_image=DestroyImage(dispose_image);
488 return(GetFirstImageInList(dispose_images));
489}
490
491/*
492%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
493% %
494% %
495% %
496+ C o m p a r e P i x e l s %
497% %
498% %
499% %
500%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
501%
502% ComparePixels() Compare the two pixels and return true if the pixels
503% differ according to the given LayerType comparision method.
504%
cristy8a9106f2011-07-05 14:39:26 +0000505% This currently only used internally by CompareImagesBounds(). It is
cristy3ed852e2009-09-05 21:47:34 +0000506% doubtful that this sub-routine will be useful outside this module.
507%
508% The format of the ComparePixels method is:
509%
cristya0417062012-09-02 23:34:56 +0000510% MagickBooleanType *ComparePixels(const LayerMethod method,
cristy4c08aed2011-07-01 19:47:50 +0000511% const PixelInfo *p,const PixelInfo *q)
cristy3ed852e2009-09-05 21:47:34 +0000512%
513% A description of each parameter follows:
514%
515% o method: What differences to look for. Must be one of
516% CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
517%
518% o p, q: the pixels to test for appropriate differences.
519%
520*/
521
cristya0417062012-09-02 23:34:56 +0000522static MagickBooleanType ComparePixels(const LayerMethod method,
cristy4c08aed2011-07-01 19:47:50 +0000523 const PixelInfo *p,const PixelInfo *q)
cristy3ed852e2009-09-05 21:47:34 +0000524{
cristya19f1d72012-08-07 18:24:38 +0000525 double
cristy3ed852e2009-09-05 21:47:34 +0000526 o1,
527 o2;
528
529 /*
530 Any change in pixel values
531 */
532 if (method == CompareAnyLayer)
cristy4c08aed2011-07-01 19:47:50 +0000533 return((MagickBooleanType)(IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
cristy3ed852e2009-09-05 21:47:34 +0000534
cristy17f11b02014-12-20 19:37:04 +0000535 o1 = (p->alpha_trait != UndefinedPixelTrait) ? p->alpha : OpaqueAlpha;
536 o2 = (q->alpha_trait != UndefinedPixelTrait) ? q->alpha : OpaqueAlpha;
cristy3ed852e2009-09-05 21:47:34 +0000537 /*
cristyed6987e2013-02-19 14:10:05 +0000538 Pixel goes from opaque to transprency.
cristy3ed852e2009-09-05 21:47:34 +0000539 */
540 if (method == CompareClearLayer)
cristya19f1d72012-08-07 18:24:38 +0000541 return((MagickBooleanType) ( (o1 <= ((double) QuantumRange/2.0)) &&
542 (o2 > ((double) QuantumRange/2.0)) ) );
cristy3ed852e2009-09-05 21:47:34 +0000543 /*
cristyed6987e2013-02-19 14:10:05 +0000544 Overlay would change first pixel by second.
cristy3ed852e2009-09-05 21:47:34 +0000545 */
546 if (method == CompareOverlayLayer)
547 {
cristya19f1d72012-08-07 18:24:38 +0000548 if (o2 > ((double) QuantumRange/2.0))
cristy3ed852e2009-09-05 21:47:34 +0000549 return MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +0000550 return((MagickBooleanType) (IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
cristy3ed852e2009-09-05 21:47:34 +0000551 }
552 return(MagickFalse);
553}
554
555
556/*
557%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
558% %
559% %
560% %
561+ C o m p a r e I m a g e B o u n d s %
562% %
563% %
564% %
565%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
566%
cristy8a9106f2011-07-05 14:39:26 +0000567% CompareImagesBounds() Given two images return the smallest rectangular area
cristy3ed852e2009-09-05 21:47:34 +0000568% by which the two images differ, accourding to the given 'Compare...'
569% layer method.
570%
571% This currently only used internally in this module, but may eventually
572% be used by other modules.
573%
cristy8a9106f2011-07-05 14:39:26 +0000574% The format of the CompareImagesBounds method is:
cristy3ed852e2009-09-05 21:47:34 +0000575%
cristya0417062012-09-02 23:34:56 +0000576% RectangleInfo *CompareImagesBounds(const LayerMethod method,
cristy3ed852e2009-09-05 21:47:34 +0000577% const Image *image1, const Image *image2, ExceptionInfo *exception)
578%
579% A description of each parameter follows:
580%
cristy4c08aed2011-07-01 19:47:50 +0000581% o method: What differences to look for. Must be one of CompareAnyLayer,
582% CompareClearLayer, CompareOverlayLayer.
cristy3ed852e2009-09-05 21:47:34 +0000583%
584% o image1, image2: the two images to compare.
585%
586% o exception: return any errors or warnings in this structure.
587%
588*/
589
cristyed6987e2013-02-19 14:10:05 +0000590static RectangleInfo CompareImagesBounds(const Image *image1,
591 const Image *image2,const LayerMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000592{
593 RectangleInfo
594 bounds;
595
cristy4c08aed2011-07-01 19:47:50 +0000596 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000597 pixel1,
598 pixel2;
599
cristy4c08aed2011-07-01 19:47:50 +0000600 register const Quantum
cristy3ed852e2009-09-05 21:47:34 +0000601 *p,
602 *q;
603
cristybb503372010-05-27 20:51:26 +0000604 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000605 x;
606
cristy9d314ff2011-03-09 01:30:28 +0000607 ssize_t
608 y;
609
cristy3ed852e2009-09-05 21:47:34 +0000610 /*
cristy4c08aed2011-07-01 19:47:50 +0000611 Set bounding box of the differences between images.
cristy3ed852e2009-09-05 21:47:34 +0000612 */
cristy4c08aed2011-07-01 19:47:50 +0000613 GetPixelInfo(image1,&pixel1);
614 GetPixelInfo(image2,&pixel2);
cristybb503372010-05-27 20:51:26 +0000615 for (x=0; x < (ssize_t) image1->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000616 {
617 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
618 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +0000619 if ((p == (const Quantum *) NULL) ||
cristyacd2ed22011-08-30 01:44:23 +0000620 (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000621 break;
cristybb503372010-05-27 20:51:26 +0000622 for (y=0; y < (ssize_t) image1->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000623 {
cristy803640d2011-11-17 02:11:32 +0000624 GetPixelInfoPixel(image1,p,&pixel1);
625 GetPixelInfoPixel(image2,q,&pixel2);
cristy3ed852e2009-09-05 21:47:34 +0000626 if (ComparePixels(method,&pixel1,&pixel2))
627 break;
cristyed231572011-07-14 02:18:59 +0000628 p+=GetPixelChannels(image1);
dirkaf0b10c2014-10-12 19:36:32 +0000629 q+=GetPixelChannels(image2);
cristy3ed852e2009-09-05 21:47:34 +0000630 }
cristybb503372010-05-27 20:51:26 +0000631 if (y < (ssize_t) image1->rows)
cristy3ed852e2009-09-05 21:47:34 +0000632 break;
633 }
cristybb503372010-05-27 20:51:26 +0000634 if (x >= (ssize_t) image1->columns)
cristy3ed852e2009-09-05 21:47:34 +0000635 {
636 /*
637 Images are identical, return a null image.
638 */
639 bounds.x=-1;
640 bounds.y=-1;
641 bounds.width=1;
642 bounds.height=1;
643 return(bounds);
644 }
645 bounds.x=x;
cristybb503372010-05-27 20:51:26 +0000646 for (x=(ssize_t) image1->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +0000647 {
648 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
649 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +0000650 if ((p == (const Quantum *) NULL) ||
cristyacd2ed22011-08-30 01:44:23 +0000651 (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000652 break;
cristybb503372010-05-27 20:51:26 +0000653 for (y=0; y < (ssize_t) image1->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000654 {
cristy803640d2011-11-17 02:11:32 +0000655 GetPixelInfoPixel(image1,p,&pixel1);
656 GetPixelInfoPixel(image2,q,&pixel2);
cristy3ed852e2009-09-05 21:47:34 +0000657 if (ComparePixels(method,&pixel1,&pixel2))
658 break;
cristyed231572011-07-14 02:18:59 +0000659 p+=GetPixelChannels(image1);
dirkaf0b10c2014-10-12 19:36:32 +0000660 q+=GetPixelChannels(image2);
cristy3ed852e2009-09-05 21:47:34 +0000661 }
cristybb503372010-05-27 20:51:26 +0000662 if (y < (ssize_t) image1->rows)
cristy3ed852e2009-09-05 21:47:34 +0000663 break;
664 }
cristybb503372010-05-27 20:51:26 +0000665 bounds.width=(size_t) (x-bounds.x+1);
666 for (y=0; y < (ssize_t) image1->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000667 {
668 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
669 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000670 if ((p == (const Quantum *) NULL) ||
cristyacd2ed22011-08-30 01:44:23 +0000671 (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000672 break;
cristybb503372010-05-27 20:51:26 +0000673 for (x=0; x < (ssize_t) image1->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000674 {
cristy803640d2011-11-17 02:11:32 +0000675 GetPixelInfoPixel(image1,p,&pixel1);
676 GetPixelInfoPixel(image2,q,&pixel2);
cristy3ed852e2009-09-05 21:47:34 +0000677 if (ComparePixels(method,&pixel1,&pixel2))
678 break;
cristyed231572011-07-14 02:18:59 +0000679 p+=GetPixelChannels(image1);
dirkaf0b10c2014-10-12 19:36:32 +0000680 q+=GetPixelChannels(image2);
cristy3ed852e2009-09-05 21:47:34 +0000681 }
cristybb503372010-05-27 20:51:26 +0000682 if (x < (ssize_t) image1->columns)
cristy3ed852e2009-09-05 21:47:34 +0000683 break;
684 }
685 bounds.y=y;
cristybb503372010-05-27 20:51:26 +0000686 for (y=(ssize_t) image1->rows-1; y >= 0; y--)
cristy3ed852e2009-09-05 21:47:34 +0000687 {
688 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
689 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000690 if ((p == (const Quantum *) NULL) ||
cristyacd2ed22011-08-30 01:44:23 +0000691 (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000692 break;
cristybb503372010-05-27 20:51:26 +0000693 for (x=0; x < (ssize_t) image1->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000694 {
cristy803640d2011-11-17 02:11:32 +0000695 GetPixelInfoPixel(image1,p,&pixel1);
696 GetPixelInfoPixel(image2,q,&pixel2);
cristy3ed852e2009-09-05 21:47:34 +0000697 if (ComparePixels(method,&pixel1,&pixel2))
698 break;
cristyed231572011-07-14 02:18:59 +0000699 p+=GetPixelChannels(image1);
dirkaf0b10c2014-10-12 19:36:32 +0000700 q+=GetPixelChannels(image2);
cristy3ed852e2009-09-05 21:47:34 +0000701 }
cristybb503372010-05-27 20:51:26 +0000702 if (x < (ssize_t) image1->columns)
cristy3ed852e2009-09-05 21:47:34 +0000703 break;
704 }
cristybb503372010-05-27 20:51:26 +0000705 bounds.height=(size_t) (y-bounds.y+1);
cristy3ed852e2009-09-05 21:47:34 +0000706 return(bounds);
707}
708
709/*
710%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
711% %
712% %
713% %
714% C o m p a r e I m a g e L a y e r s %
715% %
716% %
717% %
718%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
719%
cristy8a9106f2011-07-05 14:39:26 +0000720% CompareImagesLayers() compares each image with the next in a sequence and
cristy3ed852e2009-09-05 21:47:34 +0000721% returns the minimum bounding region of all the pixel differences (of the
cristya0417062012-09-02 23:34:56 +0000722% LayerMethod specified) it discovers.
cristy3ed852e2009-09-05 21:47:34 +0000723%
724% Images do NOT have to be the same size, though it is best that all the
725% images are 'coalesced' (images are all the same size, on a flattened
726% canvas, so as to represent exactly how an specific frame should look).
727%
728% No GIF dispose methods are applied, so GIF animations must be coalesced
729% before applying this image operator to find differences to them.
730%
cristy8a9106f2011-07-05 14:39:26 +0000731% The format of the CompareImagesLayers method is:
cristy3ed852e2009-09-05 21:47:34 +0000732%
cristy8a9106f2011-07-05 14:39:26 +0000733% Image *CompareImagesLayers(const Image *images,
cristya0417062012-09-02 23:34:56 +0000734% const LayerMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000735%
736% A description of each parameter follows:
737%
738% o image: the image.
739%
740% o method: the layers type to compare images with. Must be one of...
741% CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
742%
743% o exception: return any errors or warnings in this structure.
744%
745*/
746
cristy8a9106f2011-07-05 14:39:26 +0000747MagickExport Image *CompareImagesLayers(const Image *image,
cristya0417062012-09-02 23:34:56 +0000748 const LayerMethod method, ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000749{
750 Image
751 *image_a,
752 *image_b,
753 *layers;
754
755 RectangleInfo
756 *bounds;
757
758 register const Image
759 *next;
760
cristybb503372010-05-27 20:51:26 +0000761 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000762 i;
763
764 assert(image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000765 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000766 if (image->debug != MagickFalse)
767 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
768 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000769 assert(exception->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000770 assert((method == CompareAnyLayer) ||
771 (method == CompareClearLayer) ||
772 (method == CompareOverlayLayer));
773 /*
774 Allocate bounds memory.
775 */
776 next=GetFirstImageInList(image);
777 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
778 GetImageListLength(next),sizeof(*bounds));
779 if (bounds == (RectangleInfo *) NULL)
780 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
781 /*
782 Set up first comparision images.
783 */
784 image_a=CloneImage(next,next->page.width,next->page.height,
785 MagickTrue,exception);
786 if (image_a == (Image *) NULL)
787 {
788 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
789 return((Image *) NULL);
790 }
cristy4c08aed2011-07-01 19:47:50 +0000791 image_a->background_color.alpha=(Quantum) TransparentAlpha;
cristyea1a8aa2011-10-20 13:24:06 +0000792 (void) SetImageBackgroundColor(image_a,exception);
cristy3ed852e2009-09-05 21:47:34 +0000793 image_a->page=next->page;
794 image_a->page.x=0;
795 image_a->page.y=0;
cristy39172402012-03-30 13:04:39 +0000796 (void) CompositeImage(image_a,next,CopyCompositeOp,MagickTrue,next->page.x,
cristyfeb3e962012-03-29 17:25:55 +0000797 next->page.y,exception);
cristy3ed852e2009-09-05 21:47:34 +0000798 /*
799 Compute the bounding box of changes for the later images
800 */
801 i=0;
802 next=GetNextImageInList(next);
803 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
804 {
805 image_b=CloneImage(image_a,0,0,MagickTrue,exception);
806 if (image_b == (Image *) NULL)
807 {
808 image_a=DestroyImage(image_a);
809 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
810 return((Image *) NULL);
811 }
cristy39172402012-03-30 13:04:39 +0000812 (void) CompositeImage(image_a,next,CopyCompositeOp,MagickTrue,next->page.x,
cristye941a752011-10-15 01:52:48 +0000813 next->page.y,exception);
cristy8a9106f2011-07-05 14:39:26 +0000814 bounds[i]=CompareImagesBounds(image_b,image_a,method,exception);
cristy3ed852e2009-09-05 21:47:34 +0000815 image_b=DestroyImage(image_b);
816 i++;
817 }
818 image_a=DestroyImage(image_a);
819 /*
820 Clone first image in sequence.
821 */
822 next=GetFirstImageInList(image);
823 layers=CloneImage(next,0,0,MagickTrue,exception);
824 if (layers == (Image *) NULL)
825 {
826 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
827 return((Image *) NULL);
828 }
829 /*
830 Deconstruct the image sequence.
831 */
832 i=0;
833 next=GetNextImageInList(next);
834 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
835 {
cristy226fb032015-03-20 21:37:53 +0000836 if ((bounds[i].x == -1) && (bounds[i].y == -1) &&
837 (bounds[i].width == 1) && (bounds[i].height == 1))
838 {
839 /*
840 An empty frame is returned from CompareImageBounds(), which means the
841 current frame is identical to the previous frame.
842 */
843 i++;
844 continue;
845 }
cristy3ed852e2009-09-05 21:47:34 +0000846 image_a=CloneImage(next,0,0,MagickTrue,exception);
847 if (image_a == (Image *) NULL)
848 break;
849 image_b=CropImage(image_a,&bounds[i],exception);
850 image_a=DestroyImage(image_a);
851 if (image_b == (Image *) NULL)
852 break;
853 AppendImageToList(&layers,image_b);
854 i++;
855 }
856 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
857 if (next != (Image *) NULL)
858 {
859 layers=DestroyImageList(layers);
860 return((Image *) NULL);
861 }
862 return(GetFirstImageInList(layers));
863}
864
865/*
866%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
867% %
868% %
869% %
cristy3ed852e2009-09-05 21:47:34 +0000870+ O p t i m i z e L a y e r F r a m e s %
871% %
872% %
873% %
874%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
875%
anthony9d570022010-09-11 07:18:35 +0000876% OptimizeLayerFrames() takes a coalesced GIF animation, and compares each
877% frame against the three different 'disposal' forms of the previous frame.
878% From this it then attempts to select the smallest cropped image and
879% disposal method needed to reproduce the resulting image.
cristy3ed852e2009-09-05 21:47:34 +0000880%
glennrp2489f532011-06-25 03:02:43 +0000881% Note that this not easy, and may require the expansion of the bounds
anthony9d570022010-09-11 07:18:35 +0000882% of previous frame, simply clear pixels for the next animation frame to
883% transparency according to the selected dispose method.
cristy3ed852e2009-09-05 21:47:34 +0000884%
885% The format of the OptimizeLayerFrames method is:
886%
cristy4c08aed2011-07-01 19:47:50 +0000887% Image *OptimizeLayerFrames(const Image *image,
cristya0417062012-09-02 23:34:56 +0000888% const LayerMethod method, ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000889%
890% A description of each parameter follows:
891%
892% o image: the image.
893%
anthony9d570022010-09-11 07:18:35 +0000894% o method: the layers technique to optimize with. Must be one of...
cristy4c08aed2011-07-01 19:47:50 +0000895% OptimizeImageLayer, or OptimizePlusLayer. The Plus form allows
896% the addition of extra 'zero delay' frames to clear pixels from
897% the previous frame, and the removal of frames that done change,
898% merging the delay times together.
cristy3ed852e2009-09-05 21:47:34 +0000899%
900% o exception: return any errors or warnings in this structure.
901%
902*/
903/*
anthony9d570022010-09-11 07:18:35 +0000904 Define a 'fake' dispose method where the frame is duplicated, (for
905 OptimizePlusLayer) with a extra zero time delay frame which does a
906 BackgroundDisposal to clear the pixels that need to be cleared.
cristy3ed852e2009-09-05 21:47:34 +0000907*/
908#define DupDispose ((DisposeType)9)
909/*
910 Another 'fake' dispose method used to removed frames that don't change.
911*/
912#define DelDispose ((DisposeType)8)
913
anthony9d570022010-09-11 07:18:35 +0000914#define DEBUG_OPT_FRAME 0
915
cristy3ed852e2009-09-05 21:47:34 +0000916static Image *OptimizeLayerFrames(const Image *image,
cristya0417062012-09-02 23:34:56 +0000917 const LayerMethod method, ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000918{
919 ExceptionInfo
920 *sans_exception;
921
922 Image
923 *prev_image,
924 *dup_image,
925 *bgnd_image,
926 *optimized_image;
927
928 RectangleInfo
929 try_bounds,
930 bgnd_bounds,
931 dup_bounds,
932 *bounds;
933
934 MagickBooleanType
935 add_frames,
936 try_cleared,
937 cleared;
938
939 DisposeType
940 *disposals;
941
942 register const Image
anthonyb6d08c52010-09-13 01:17:04 +0000943 *curr;
cristy3ed852e2009-09-05 21:47:34 +0000944
cristybb503372010-05-27 20:51:26 +0000945 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000946 i;
947
948 assert(image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000949 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000950 if (image->debug != MagickFalse)
951 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
952 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000953 assert(exception->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000954 assert(method == OptimizeLayer ||
955 method == OptimizeImageLayer ||
956 method == OptimizePlusLayer);
cristy3ed852e2009-09-05 21:47:34 +0000957 /*
cristyed6987e2013-02-19 14:10:05 +0000958 Are we allowed to add/remove frames from animation?
cristy3ed852e2009-09-05 21:47:34 +0000959 */
960 add_frames=method == OptimizePlusLayer ? MagickTrue : MagickFalse;
961 /*
cristyed6987e2013-02-19 14:10:05 +0000962 Ensure all the images are the same size.
cristy3ed852e2009-09-05 21:47:34 +0000963 */
anthonyb6d08c52010-09-13 01:17:04 +0000964 curr=GetFirstImageInList(image);
965 for (; curr != (Image *) NULL; curr=GetNextImageInList(curr))
cristy3ed852e2009-09-05 21:47:34 +0000966 {
anthonyb6d08c52010-09-13 01:17:04 +0000967 if ((curr->columns != image->columns) || (curr->rows != image->rows))
cristy3ed852e2009-09-05 21:47:34 +0000968 ThrowImageException(OptionError,"ImagesAreNotTheSameSize");
969 /*
anthony9d570022010-09-11 07:18:35 +0000970 FUTURE: also check that image is also fully coalesced (full page)
971 Though as long as they are the same size it should not matter.
cristy3ed852e2009-09-05 21:47:34 +0000972 */
973 }
974 /*
anthony9d570022010-09-11 07:18:35 +0000975 Allocate memory (times 2 if we allow the use of frame duplications)
cristy3ed852e2009-09-05 21:47:34 +0000976 */
anthonyb6d08c52010-09-13 01:17:04 +0000977 curr=GetFirstImageInList(image);
cristy3ed852e2009-09-05 21:47:34 +0000978 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
anthonyb6d08c52010-09-13 01:17:04 +0000979 GetImageListLength(curr),(add_frames != MagickFalse ? 2UL : 1UL)*
cristy3ed852e2009-09-05 21:47:34 +0000980 sizeof(*bounds));
981 if (bounds == (RectangleInfo *) NULL)
982 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
983 disposals=(DisposeType *) AcquireQuantumMemory((size_t)
984 GetImageListLength(image),(add_frames != MagickFalse ? 2UL : 1UL)*
985 sizeof(*disposals));
986 if (disposals == (DisposeType *) NULL)
987 {
988 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
989 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
990 }
991 /*
992 Initialise Previous Image as fully transparent
993 */
anthonyb6d08c52010-09-13 01:17:04 +0000994 prev_image=CloneImage(curr,curr->page.width,curr->page.height,
cristy3ed852e2009-09-05 21:47:34 +0000995 MagickTrue,exception);
996 if (prev_image == (Image *) NULL)
997 {
998 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
999 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1000 return((Image *) NULL);
1001 }
anthonyb6d08c52010-09-13 01:17:04 +00001002 prev_image->page=curr->page; /* ERROR: <-- should not be need, but is! */
cristy3ed852e2009-09-05 21:47:34 +00001003 prev_image->page.x=0;
1004 prev_image->page.y=0;
1005 prev_image->dispose=NoneDispose;
dirk65f6b912015-11-22 09:27:46 +01001006 prev_image->background_color.alpha_trait=BlendPixelTrait;
cristy4c08aed2011-07-01 19:47:50 +00001007 prev_image->background_color.alpha=(Quantum) TransparentAlpha;
cristyea1a8aa2011-10-20 13:24:06 +00001008 (void) SetImageBackgroundColor(prev_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001009 /*
1010 Figure out the area of overlay of the first frame
1011 No pixel could be cleared as all pixels are already cleared.
1012 */
anthony9d570022010-09-11 07:18:35 +00001013#if DEBUG_OPT_FRAME
1014 i=0;
cristy5acdd942011-05-27 19:45:39 +00001015 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
anthony9d570022010-09-11 07:18:35 +00001016#endif
cristy3ed852e2009-09-05 21:47:34 +00001017 disposals[0]=NoneDispose;
cristy8a9106f2011-07-05 14:39:26 +00001018 bounds[0]=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
anthony9d570022010-09-11 07:18:35 +00001019#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001020 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g\n\n",
cristy1e604812011-05-19 18:07:50 +00001021 (double) bounds[i].width,(double) bounds[i].height,
1022 (double) bounds[i].x,(double) bounds[i].y );
anthony9d570022010-09-11 07:18:35 +00001023#endif
cristy3ed852e2009-09-05 21:47:34 +00001024 /*
1025 Compute the bounding box of changes for each pair of images.
1026 */
1027 i=1;
cristyf432c632014-12-07 15:11:28 +00001028 bgnd_image=(Image *) NULL;
1029 dup_image=(Image *) NULL;
cristy3ed852e2009-09-05 21:47:34 +00001030 dup_bounds.width=0;
1031 dup_bounds.height=0;
1032 dup_bounds.x=0;
1033 dup_bounds.y=0;
anthonyb6d08c52010-09-13 01:17:04 +00001034 curr=GetNextImageInList(curr);
1035 for ( ; curr != (const Image *) NULL; curr=GetNextImageInList(curr))
cristy3ed852e2009-09-05 21:47:34 +00001036 {
anthony9d570022010-09-11 07:18:35 +00001037#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001038 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
anthony9d570022010-09-11 07:18:35 +00001039#endif
cristy3ed852e2009-09-05 21:47:34 +00001040 /*
1041 Assume none disposal is the best
1042 */
cristy8a9106f2011-07-05 14:39:26 +00001043 bounds[i]=CompareImagesBounds(curr->previous,curr,CompareAnyLayer,exception);
anthonyb6d08c52010-09-13 01:17:04 +00001044 cleared=IsBoundsCleared(curr->previous,curr,&bounds[i],exception);
cristy3ed852e2009-09-05 21:47:34 +00001045 disposals[i-1]=NoneDispose;
anthony9d570022010-09-11 07:18:35 +00001046#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001047 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g%s%s\n",
anthony9d570022010-09-11 07:18:35 +00001048 (double) bounds[i].width,(double) bounds[i].height,
1049 (double) bounds[i].x,(double) bounds[i].y,
1050 bounds[i].x < 0?" (unchanged)":"",
1051 cleared?" (pixels cleared)":"");
1052#endif
cristy3ed852e2009-09-05 21:47:34 +00001053 if ( bounds[i].x < 0 ) {
1054 /*
1055 Image frame is exactly the same as the previous frame!
1056 If not adding frames leave it to be cropped down to a null image.
1057 Otherwise mark previous image for deleted, transfering its crop bounds
1058 to the current image.
1059 */
1060 if ( add_frames && i>=2 ) {
1061 disposals[i-1]=DelDispose;
1062 disposals[i]=NoneDispose;
1063 bounds[i]=bounds[i-1];
1064 i++;
1065 continue;
1066 }
1067 }
1068 else
1069 {
1070 /*
1071 Compare a none disposal against a previous disposal
1072 */
cristy8a9106f2011-07-05 14:39:26 +00001073 try_bounds=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
anthonyb6d08c52010-09-13 01:17:04 +00001074 try_cleared=IsBoundsCleared(prev_image,curr,&try_bounds,exception);
anthony9d570022010-09-11 07:18:35 +00001075#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001076 (void) FormatLocaleFile(stderr, "test_prev: %.20gx%.20g%+.20g%+.20g%s\n",
anthony9d570022010-09-11 07:18:35 +00001077 (double) try_bounds.width,(double) try_bounds.height,
1078 (double) try_bounds.x,(double) try_bounds.y,
1079 try_cleared?" (pixels were cleared)":"");
1080#endif
cristy3ed852e2009-09-05 21:47:34 +00001081 if ( (!try_cleared && cleared ) ||
1082 try_bounds.width * try_bounds.height
1083 < bounds[i].width * bounds[i].height )
1084 {
1085 cleared=try_cleared;
1086 bounds[i]=try_bounds;
1087 disposals[i-1]=PreviousDispose;
anthony9d570022010-09-11 07:18:35 +00001088#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001089 (void) FormatLocaleFile(stderr, "previous: accepted\n");
anthony9d570022010-09-11 07:18:35 +00001090 } else {
cristy5acdd942011-05-27 19:45:39 +00001091 (void) FormatLocaleFile(stderr, "previous: rejected\n");
anthony9d570022010-09-11 07:18:35 +00001092#endif
cristy3ed852e2009-09-05 21:47:34 +00001093 }
1094
1095 /*
1096 If we are allowed lets try a complex frame duplication.
1097 It is useless if the previous image already clears pixels correctly.
1098 This method will always clear all the pixels that need to be cleared.
1099 */
anthony9d570022010-09-11 07:18:35 +00001100 dup_bounds.width=dup_bounds.height=0; /* no dup, no pixel added */
cristy3ed852e2009-09-05 21:47:34 +00001101 if ( add_frames )
1102 {
anthonyb6d08c52010-09-13 01:17:04 +00001103 dup_image=CloneImage(curr->previous,curr->previous->page.width,
1104 curr->previous->page.height,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001105 if (dup_image == (Image *) NULL)
1106 {
1107 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1108 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1109 prev_image=DestroyImage(prev_image);
1110 return((Image *) NULL);
1111 }
cristy8a9106f2011-07-05 14:39:26 +00001112 dup_bounds=CompareImagesBounds(dup_image,curr,CompareClearLayer,exception);
cristy7c3af952011-10-20 16:04:16 +00001113 ClearBounds(dup_image,&dup_bounds,exception);
cristy8a9106f2011-07-05 14:39:26 +00001114 try_bounds=CompareImagesBounds(dup_image,curr,CompareAnyLayer,exception);
cristy3ed852e2009-09-05 21:47:34 +00001115 if ( cleared ||
1116 dup_bounds.width*dup_bounds.height
1117 +try_bounds.width*try_bounds.height
1118 < bounds[i].width * bounds[i].height )
1119 {
1120 cleared=MagickFalse;
1121 bounds[i]=try_bounds;
1122 disposals[i-1]=DupDispose;
1123 /* to be finalised later, if found to be optimial */
1124 }
1125 else
1126 dup_bounds.width=dup_bounds.height=0;
1127 }
cristy3ed852e2009-09-05 21:47:34 +00001128 /*
1129 Now compare against a simple background disposal
1130 */
anthonyb6d08c52010-09-13 01:17:04 +00001131 bgnd_image=CloneImage(curr->previous,curr->previous->page.width,
1132 curr->previous->page.height,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001133 if (bgnd_image == (Image *) NULL)
1134 {
1135 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1136 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1137 prev_image=DestroyImage(prev_image);
anthony9d570022010-09-11 07:18:35 +00001138 if ( dup_image != (Image *) NULL)
1139 dup_image=DestroyImage(dup_image);
cristy3ed852e2009-09-05 21:47:34 +00001140 return((Image *) NULL);
1141 }
anthony9d570022010-09-11 07:18:35 +00001142 bgnd_bounds=bounds[i-1]; /* interum bounds of the previous image */
cristy7c3af952011-10-20 16:04:16 +00001143 ClearBounds(bgnd_image,&bgnd_bounds,exception);
cristy8a9106f2011-07-05 14:39:26 +00001144 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
anthonyb6d08c52010-09-13 01:17:04 +00001145 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
anthony9d570022010-09-11 07:18:35 +00001146#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001147 (void) FormatLocaleFile(stderr, "background: %s\n",
anthony9d570022010-09-11 07:18:35 +00001148 try_cleared?"(pixels cleared)":"");
1149#endif
cristy3ed852e2009-09-05 21:47:34 +00001150 if ( try_cleared )
1151 {
1152 /*
1153 Straight background disposal failed to clear pixels needed!
1154 Lets try expanding the disposal area of the previous frame, to
1155 include the pixels that are cleared. This guaranteed
1156 to work, though may not be the most optimized solution.
1157 */
cristy8a9106f2011-07-05 14:39:26 +00001158 try_bounds=CompareImagesBounds(curr->previous,curr,CompareClearLayer,exception);
anthony9d570022010-09-11 07:18:35 +00001159#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001160 (void) FormatLocaleFile(stderr, "expand_clear: %.20gx%.20g%+.20g%+.20g%s\n",
anthony9d570022010-09-11 07:18:35 +00001161 (double) try_bounds.width,(double) try_bounds.height,
1162 (double) try_bounds.x,(double) try_bounds.y,
1163 try_bounds.x<0?" (no expand nessary)":"");
1164#endif
cristy3ed852e2009-09-05 21:47:34 +00001165 if ( bgnd_bounds.x < 0 )
1166 bgnd_bounds = try_bounds;
1167 else
1168 {
anthony9d570022010-09-11 07:18:35 +00001169#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001170 (void) FormatLocaleFile(stderr, "expand_bgnd: %.20gx%.20g%+.20g%+.20g\n",
anthony9d570022010-09-11 07:18:35 +00001171 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1172 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1173#endif
cristy3ed852e2009-09-05 21:47:34 +00001174 if ( try_bounds.x < bgnd_bounds.x )
1175 {
1176 bgnd_bounds.width+= bgnd_bounds.x-try_bounds.x;
1177 if ( bgnd_bounds.width < try_bounds.width )
1178 bgnd_bounds.width = try_bounds.width;
1179 bgnd_bounds.x = try_bounds.x;
1180 }
1181 else
1182 {
1183 try_bounds.width += try_bounds.x - bgnd_bounds.x;
1184 if ( bgnd_bounds.width < try_bounds.width )
1185 bgnd_bounds.width = try_bounds.width;
1186 }
1187 if ( try_bounds.y < bgnd_bounds.y )
1188 {
1189 bgnd_bounds.height += bgnd_bounds.y - try_bounds.y;
1190 if ( bgnd_bounds.height < try_bounds.height )
1191 bgnd_bounds.height = try_bounds.height;
1192 bgnd_bounds.y = try_bounds.y;
1193 }
1194 else
1195 {
1196 try_bounds.height += try_bounds.y - bgnd_bounds.y;
1197 if ( bgnd_bounds.height < try_bounds.height )
1198 bgnd_bounds.height = try_bounds.height;
1199 }
anthony9d570022010-09-11 07:18:35 +00001200#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001201 (void) FormatLocaleFile(stderr, " to : %.20gx%.20g%+.20g%+.20g\n",
anthony9d570022010-09-11 07:18:35 +00001202 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1203 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1204#endif
cristy3ed852e2009-09-05 21:47:34 +00001205 }
cristy7c3af952011-10-20 16:04:16 +00001206 ClearBounds(bgnd_image,&bgnd_bounds,exception);
anthony9d570022010-09-11 07:18:35 +00001207#if DEBUG_OPT_FRAME
1208/* Something strange is happening with a specific animation
1209 * CompareAnyLayers (normal method) and CompareClearLayers returns the whole
1210 * image, which is not posibly correct! As verified by previous tests.
1211 * Something changed beyond the bgnd_bounds clearing. But without being able
1212 * to see, or writet he image at this point it is hard to tell what is wrong!
1213 * Only CompareOverlay seemed to return something sensible.
1214 */
cristy8a9106f2011-07-05 14:39:26 +00001215 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareClearLayer,exception);
cristy5acdd942011-05-27 19:45:39 +00001216 (void) FormatLocaleFile(stderr, "expand_ctst: %.20gx%.20g%+.20g%+.20g\n",
anthony9d570022010-09-11 07:18:35 +00001217 (double) try_bounds.width,(double) try_bounds.height,
1218 (double) try_bounds.x,(double) try_bounds.y );
cristy8a9106f2011-07-05 14:39:26 +00001219 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
anthonyb6d08c52010-09-13 01:17:04 +00001220 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
cristy5acdd942011-05-27 19:45:39 +00001221 (void) FormatLocaleFile(stderr, "expand_any : %.20gx%.20g%+.20g%+.20g%s\n",
anthony9d570022010-09-11 07:18:35 +00001222 (double) try_bounds.width,(double) try_bounds.height,
1223 (double) try_bounds.x,(double) try_bounds.y,
1224 try_cleared?" (pixels cleared)":"");
1225#endif
cristy8a9106f2011-07-05 14:39:26 +00001226 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareOverlayLayer,exception);
anthony9d570022010-09-11 07:18:35 +00001227#if DEBUG_OPT_FRAME
anthonyb6d08c52010-09-13 01:17:04 +00001228 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
cristy5acdd942011-05-27 19:45:39 +00001229 (void) FormatLocaleFile(stderr, "expand_test: %.20gx%.20g%+.20g%+.20g%s\n",
anthony9d570022010-09-11 07:18:35 +00001230 (double) try_bounds.width,(double) try_bounds.height,
1231 (double) try_bounds.x,(double) try_bounds.y,
1232 try_cleared?" (pixels cleared)":"");
1233#endif
cristy3ed852e2009-09-05 21:47:34 +00001234 }
1235 /*
1236 Test if this background dispose is smaller than any of the
1237 other methods we tryed before this (including duplicated frame)
1238 */
1239 if ( cleared ||
1240 bgnd_bounds.width*bgnd_bounds.height
1241 +try_bounds.width*try_bounds.height
1242 < bounds[i-1].width*bounds[i-1].height
1243 +dup_bounds.width*dup_bounds.height
1244 +bounds[i].width*bounds[i].height )
1245 {
1246 cleared=MagickFalse;
1247 bounds[i-1]=bgnd_bounds;
1248 bounds[i]=try_bounds;
1249 if ( disposals[i-1] == DupDispose )
1250 dup_image=DestroyImage(dup_image);
1251 disposals[i-1]=BackgroundDispose;
anthony9d570022010-09-11 07:18:35 +00001252#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001253 (void) FormatLocaleFile(stderr, "expand_bgnd: accepted\n");
anthony9d570022010-09-11 07:18:35 +00001254 } else {
cristy5acdd942011-05-27 19:45:39 +00001255 (void) FormatLocaleFile(stderr, "expand_bgnd: reject\n");
anthony9d570022010-09-11 07:18:35 +00001256#endif
cristy3ed852e2009-09-05 21:47:34 +00001257 }
1258 }
1259 /*
1260 Finalise choice of dispose, set new prev_image,
1261 and junk any extra images as appropriate,
1262 */
1263 if ( disposals[i-1] == DupDispose )
1264 {
1265 if (bgnd_image != (Image *) NULL)
1266 bgnd_image=DestroyImage(bgnd_image);
1267 prev_image=DestroyImage(prev_image);
1268 prev_image=dup_image, dup_image=(Image *) NULL;
1269 bounds[i+1]=bounds[i];
1270 bounds[i]=dup_bounds;
1271 disposals[i-1]=DupDispose;
1272 disposals[i]=BackgroundDispose;
1273 i++;
1274 }
1275 else
1276 {
anthony9d570022010-09-11 07:18:35 +00001277 if ( dup_image != (Image *) NULL)
1278 dup_image=DestroyImage(dup_image);
cristy3ed852e2009-09-05 21:47:34 +00001279 if ( disposals[i-1] != PreviousDispose )
1280 prev_image=DestroyImage(prev_image);
1281 if ( disposals[i-1] == BackgroundDispose )
cristyf432c632014-12-07 15:11:28 +00001282 prev_image=bgnd_image, bgnd_image=(Image *) NULL;
anthony9d570022010-09-11 07:18:35 +00001283 if (bgnd_image != (Image *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001284 bgnd_image=DestroyImage(bgnd_image);
cristy3ed852e2009-09-05 21:47:34 +00001285 if ( disposals[i-1] == NoneDispose )
1286 {
anthonyb6d08c52010-09-13 01:17:04 +00001287 prev_image=CloneImage(curr->previous,curr->previous->page.width,
1288 curr->previous->page.height,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001289 if (prev_image == (Image *) NULL)
1290 {
1291 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1292 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1293 return((Image *) NULL);
1294 }
1295 }
anthony9d570022010-09-11 07:18:35 +00001296
cristy3ed852e2009-09-05 21:47:34 +00001297 }
anthony9d570022010-09-11 07:18:35 +00001298 assert(prev_image != (Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001299 disposals[i]=disposals[i-1];
anthony9d570022010-09-11 07:18:35 +00001300#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001301 (void) FormatLocaleFile(stderr, "final %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
anthony9d570022010-09-11 07:18:35 +00001302 (double) i-1,
cristy042ee782011-04-22 18:48:30 +00001303 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i-1]),
anthony9d570022010-09-11 07:18:35 +00001304 (double) bounds[i-1].width, (double) bounds[i-1].height,
1305 (double) bounds[i-1].x, (double) bounds[i-1].y );
1306#endif
1307#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001308 (void) FormatLocaleFile(stderr, "interum %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
anthony9d570022010-09-11 07:18:35 +00001309 (double) i,
cristy042ee782011-04-22 18:48:30 +00001310 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i]),
anthony9d570022010-09-11 07:18:35 +00001311 (double) bounds[i].width, (double) bounds[i].height,
1312 (double) bounds[i].x, (double) bounds[i].y );
cristy5acdd942011-05-27 19:45:39 +00001313 (void) FormatLocaleFile(stderr, "\n");
anthony9d570022010-09-11 07:18:35 +00001314#endif
cristy3ed852e2009-09-05 21:47:34 +00001315 i++;
1316 }
1317 prev_image=DestroyImage(prev_image);
1318 /*
1319 Optimize all images in sequence.
1320 */
1321 sans_exception=AcquireExceptionInfo();
1322 i=0;
anthonyb6d08c52010-09-13 01:17:04 +00001323 curr=GetFirstImageInList(image);
cristy3ed852e2009-09-05 21:47:34 +00001324 optimized_image=NewImageList();
anthonyb6d08c52010-09-13 01:17:04 +00001325 while ( curr != (const Image *) NULL )
cristy3ed852e2009-09-05 21:47:34 +00001326 {
anthonyb6d08c52010-09-13 01:17:04 +00001327 prev_image=CloneImage(curr,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001328 if (prev_image == (Image *) NULL)
1329 break;
Cristyeadf2e42017-01-09 16:22:51 -05001330 if (prev_image->alpha_trait == UndefinedPixelTrait)
1331 (void) SetImageAlphaChannel(prev_image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +00001332 if ( disposals[i] == DelDispose ) {
cristybb503372010-05-27 20:51:26 +00001333 size_t time = 0;
cristy3ed852e2009-09-05 21:47:34 +00001334 while ( disposals[i] == DelDispose ) {
anthonyb6d08c52010-09-13 01:17:04 +00001335 time += curr->delay*1000/curr->ticks_per_second;
1336 curr=GetNextImageInList(curr);
cristy3ed852e2009-09-05 21:47:34 +00001337 i++;
1338 }
anthonyb6d08c52010-09-13 01:17:04 +00001339 time += curr->delay*1000/curr->ticks_per_second;
cristy3ed852e2009-09-05 21:47:34 +00001340 prev_image->ticks_per_second = 100L;
1341 prev_image->delay = time*prev_image->ticks_per_second/1000;
1342 }
1343 bgnd_image=CropImage(prev_image,&bounds[i],sans_exception);
1344 prev_image=DestroyImage(prev_image);
1345 if (bgnd_image == (Image *) NULL)
1346 break;
1347 bgnd_image->dispose=disposals[i];
1348 if ( disposals[i] == DupDispose ) {
1349 bgnd_image->delay=0;
1350 bgnd_image->dispose=NoneDispose;
1351 }
1352 else
anthonyb6d08c52010-09-13 01:17:04 +00001353 curr=GetNextImageInList(curr);
cristy3ed852e2009-09-05 21:47:34 +00001354 AppendImageToList(&optimized_image,bgnd_image);
1355 i++;
1356 }
1357 sans_exception=DestroyExceptionInfo(sans_exception);
1358 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1359 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
anthonyb6d08c52010-09-13 01:17:04 +00001360 if (curr != (Image *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001361 {
1362 optimized_image=DestroyImageList(optimized_image);
1363 return((Image *) NULL);
1364 }
1365 return(GetFirstImageInList(optimized_image));
1366}
1367
1368/*
1369%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1370% %
1371% %
1372% %
1373% O p t i m i z e I m a g e L a y e r s %
1374% %
1375% %
1376% %
1377%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1378%
1379% OptimizeImageLayers() compares each image the GIF disposed forms of the
1380% previous image in the sequence. From this it attempts to select the
1381% smallest cropped image to replace each frame, while preserving the results
1382% of the GIF animation.
1383%
1384% The format of the OptimizeImageLayers method is:
1385%
1386% Image *OptimizeImageLayers(const Image *image,
1387% ExceptionInfo *exception)
1388%
1389% A description of each parameter follows:
1390%
1391% o image: the image.
1392%
1393% o exception: return any errors or warnings in this structure.
1394%
1395*/
1396MagickExport Image *OptimizeImageLayers(const Image *image,
1397 ExceptionInfo *exception)
1398{
1399 return(OptimizeLayerFrames(image,OptimizeImageLayer,exception));
1400}
1401
1402/*
1403%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1404% %
1405% %
1406% %
1407% O p t i m i z e P l u s I m a g e L a y e r s %
1408% %
1409% %
1410% %
1411%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1412%
1413% OptimizeImagePlusLayers() is exactly as OptimizeImageLayers(), but may
1414% also add or even remove extra frames in the animation, if it improves
1415% the total number of pixels in the resulting GIF animation.
1416%
1417% The format of the OptimizePlusImageLayers method is:
1418%
1419% Image *OptimizePlusImageLayers(const Image *image,
1420% ExceptionInfo *exception)
1421%
1422% A description of each parameter follows:
1423%
1424% o image: the image.
1425%
1426% o exception: return any errors or warnings in this structure.
1427%
1428*/
1429MagickExport Image *OptimizePlusImageLayers(const Image *image,
1430 ExceptionInfo *exception)
1431{
1432 return OptimizeLayerFrames(image, OptimizePlusLayer, exception);
1433}
1434
1435/*
1436%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1437% %
1438% %
1439% %
1440% 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 %
1441% %
1442% %
1443% %
1444%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1445%
1446% OptimizeImageTransparency() takes a frame optimized GIF animation, and
1447% compares the overlayed pixels against the disposal image resulting from all
1448% the previous frames in the animation. Any pixel that does not change the
1449% disposal image (and thus does not effect the outcome of an overlay) is made
1450% transparent.
1451%
1452% WARNING: This modifies the current images directly, rather than generate
1453% a new image sequence.
1454%
1455% The format of the OptimizeImageTransperency method is:
1456%
1457% void OptimizeImageTransperency(Image *image,ExceptionInfo *exception)
1458%
1459% A description of each parameter follows:
1460%
1461% o image: the image sequence
1462%
1463% o exception: return any errors or warnings in this structure.
1464%
1465*/
1466MagickExport void OptimizeImageTransparency(const Image *image,
1467 ExceptionInfo *exception)
1468{
1469 Image
1470 *dispose_image;
1471
1472 register Image
1473 *next;
1474
1475 /*
1476 Run the image through the animation sequence
1477 */
1478 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001479 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001480 if (image->debug != MagickFalse)
1481 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1482 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001483 assert(exception->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001484 next=GetFirstImageInList(image);
1485 dispose_image=CloneImage(next,next->page.width,next->page.height,
1486 MagickTrue,exception);
1487 if (dispose_image == (Image *) NULL)
1488 return;
1489 dispose_image->page=next->page;
1490 dispose_image->page.x=0;
1491 dispose_image->page.y=0;
1492 dispose_image->dispose=NoneDispose;
dirk65f6b912015-11-22 09:27:46 +01001493 dispose_image->background_color.alpha_trait=BlendPixelTrait;
cristy4c08aed2011-07-01 19:47:50 +00001494 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
cristyea1a8aa2011-10-20 13:24:06 +00001495 (void) SetImageBackgroundColor(dispose_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001496
1497 while ( next != (Image *) NULL )
1498 {
1499 Image
1500 *current_image;
1501
1502 /*
1503 Overlay this frame's image over the previous disposal image
1504 */
1505 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
1506 if (current_image == (Image *) NULL)
1507 {
1508 dispose_image=DestroyImage(dispose_image);
1509 return;
1510 }
cristy17f11b02014-12-20 19:37:04 +00001511 (void) CompositeImage(current_image,next,next->alpha_trait != UndefinedPixelTrait ?
cristy39172402012-03-30 13:04:39 +00001512 OverCompositeOp : CopyCompositeOp,MagickTrue,next->page.x,next->page.y,
cristye941a752011-10-15 01:52:48 +00001513 exception);
cristy3ed852e2009-09-05 21:47:34 +00001514 /*
1515 At this point the image would be displayed, for the delay period
1516 **
1517 Work out the disposal of the previous image
1518 */
1519 if (next->dispose == BackgroundDispose)
1520 {
1521 RectangleInfo
1522 bounds=next->page;
1523
1524 bounds.width=next->columns;
1525 bounds.height=next->rows;
1526 if (bounds.x < 0)
1527 {
1528 bounds.width+=bounds.x;
1529 bounds.x=0;
1530 }
cristybb503372010-05-27 20:51:26 +00001531 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
cristy3ed852e2009-09-05 21:47:34 +00001532 bounds.width=current_image->columns-bounds.x;
1533 if (bounds.y < 0)
1534 {
1535 bounds.height+=bounds.y;
1536 bounds.y=0;
1537 }
cristybb503372010-05-27 20:51:26 +00001538 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
cristy3ed852e2009-09-05 21:47:34 +00001539 bounds.height=current_image->rows-bounds.y;
cristy7c3af952011-10-20 16:04:16 +00001540 ClearBounds(current_image, &bounds,exception);
cristy3ed852e2009-09-05 21:47:34 +00001541 }
1542 if (next->dispose != PreviousDispose)
1543 {
1544 dispose_image=DestroyImage(dispose_image);
1545 dispose_image=current_image;
1546 }
1547 else
1548 current_image=DestroyImage(current_image);
1549
1550 /*
1551 Optimize Transparency of the next frame (if present)
1552 */
1553 next=GetNextImageInList(next);
cristyfeb3e962012-03-29 17:25:55 +00001554 if (next != (Image *) NULL) {
1555 (void) CompositeImage(next,dispose_image,ChangeMaskCompositeOp,
cristy39172402012-03-30 13:04:39 +00001556 MagickTrue,-(next->page.x),-(next->page.y),exception);
cristy3ed852e2009-09-05 21:47:34 +00001557 }
1558 }
1559 dispose_image=DestroyImage(dispose_image);
1560 return;
1561}
1562
1563/*
1564%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1565% %
1566% %
1567% %
1568% R e m o v e D u p l i c a t e L a y e r s %
1569% %
1570% %
1571% %
1572%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1573%
1574% RemoveDuplicateLayers() removes any image that is exactly the same as the
1575% next image in the given image list. Image size and virtual canvas offset
1576% must also match, though not the virtual canvas size itself.
1577%
1578% No check is made with regards to image disposal setting, though it is the
1579% dispose setting of later image that is kept. Also any time delays are also
1580% added together. As such coalesced image animations should still produce the
1581% same result, though with duplicte frames merged into a single frame.
1582%
1583% The format of the RemoveDuplicateLayers method is:
1584%
1585% void RemoveDuplicateLayers(Image **image, ExceptionInfo *exception)
1586%
1587% A description of each parameter follows:
1588%
1589% o images: the image list
1590%
1591% o exception: return any errors or warnings in this structure.
1592%
1593*/
1594MagickExport void RemoveDuplicateLayers(Image **images,
1595 ExceptionInfo *exception)
1596{
1597 register Image
1598 *curr,
1599 *next;
1600
1601 RectangleInfo
1602 bounds;
1603
1604 assert((*images) != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001605 assert((*images)->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001606 if ((*images)->debug != MagickFalse)
1607 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1608 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001609 assert(exception->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001610
1611 curr=GetFirstImageInList(*images);
1612 for (; (next=GetNextImageInList(curr)) != (Image *) NULL; curr=next)
1613 {
1614 if ( curr->columns != next->columns || curr->rows != next->rows
1615 || curr->page.x != next->page.x || curr->page.y != next->page.y )
1616 continue;
cristy8a9106f2011-07-05 14:39:26 +00001617 bounds=CompareImagesBounds(curr,next,CompareAnyLayer,exception);
cristy3ed852e2009-09-05 21:47:34 +00001618 if ( bounds.x < 0 ) {
1619 /*
1620 the two images are the same, merge time delays and delete one.
1621 */
cristybb503372010-05-27 20:51:26 +00001622 size_t time;
cristy3ed852e2009-09-05 21:47:34 +00001623 time = curr->delay*1000/curr->ticks_per_second;
1624 time += next->delay*1000/next->ticks_per_second;
1625 next->ticks_per_second = 100L;
1626 next->delay = time*curr->ticks_per_second/1000;
1627 next->iterations = curr->iterations;
1628 *images = curr;
1629 (void) DeleteImageFromList(images);
1630 }
1631 }
1632 *images = GetFirstImageInList(*images);
1633}
1634
1635/*
1636%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1637% %
1638% %
1639% %
1640% R e m o v e Z e r o D e l a y L a y e r s %
1641% %
1642% %
1643% %
1644%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1645%
1646% RemoveZeroDelayLayers() removes any image that as a zero delay time. Such
1647% images generally represent intermediate or partial updates in GIF
1648% animations used for file optimization. They are not ment to be displayed
1649% to users of the animation. Viewable images in an animation should have a
1650% time delay of 3 or more centi-seconds (hundredths of a second).
1651%
1652% However if all the frames have a zero time delay, then either the animation
1653% is as yet incomplete, or it is not a GIF animation. This a non-sensible
1654% situation, so no image will be removed and a 'Zero Time Animation' warning
1655% (exception) given.
1656%
1657% No warning will be given if no image was removed because all images had an
1658% appropriate non-zero time delay set.
1659%
1660% Due to the special requirements of GIF disposal handling, GIF animations
1661% should be coalesced first, before calling this function, though that is not
1662% a requirement.
1663%
1664% The format of the RemoveZeroDelayLayers method is:
1665%
1666% void RemoveZeroDelayLayers(Image **image, ExceptionInfo *exception)
1667%
1668% A description of each parameter follows:
1669%
1670% o images: the image list
1671%
1672% o exception: return any errors or warnings in this structure.
1673%
1674*/
1675MagickExport void RemoveZeroDelayLayers(Image **images,
1676 ExceptionInfo *exception)
1677{
1678 Image
1679 *i;
1680
1681 assert((*images) != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001682 assert((*images)->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001683 if ((*images)->debug != MagickFalse)
1684 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1685 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001686 assert(exception->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001687
1688 i=GetFirstImageInList(*images);
1689 for ( ; i != (Image *) NULL; i=GetNextImageInList(i))
1690 if ( i->delay != 0L ) break;
1691 if ( i == (Image *) NULL ) {
1692 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
cristyefe601c2013-01-05 17:51:12 +00001693 "ZeroTimeAnimation","`%s'",GetFirstImageInList(*images)->filename);
cristy3ed852e2009-09-05 21:47:34 +00001694 return;
1695 }
1696 i=GetFirstImageInList(*images);
1697 while ( i != (Image *) NULL )
1698 {
1699 if ( i->delay == 0L ) {
1700 (void) DeleteImageFromList(&i);
1701 *images=i;
1702 }
1703 else
1704 i=GetNextImageInList(i);
1705 }
1706 *images=GetFirstImageInList(*images);
1707}
1708
1709/*
1710%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1711% %
1712% %
1713% %
1714% C o m p o s i t e L a y e r s %
1715% %
1716% %
1717% %
1718%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1719%
anthonye5840b22012-03-17 12:22:34 +00001720% CompositeLayers() compose the source image sequence over the destination
1721% image sequence, starting with the current image in both lists.
cristy3ed852e2009-09-05 21:47:34 +00001722%
anthonye5840b22012-03-17 12:22:34 +00001723% Each layer from the two image lists are composted together until the end of
1724% one of the image lists is reached. The offset of each composition is also
1725% adjusted to match the virtual canvas offsets of each layer. As such the
1726% given offset is relative to the virtual canvas, and not the actual image.
cristy3ed852e2009-09-05 21:47:34 +00001727%
anthonye5840b22012-03-17 12:22:34 +00001728% Composition uses given x and y offsets, as the 'origin' location of the
1729% source images virtual canvas (not the real image) allowing you to compose a
1730% list of 'layer images' into the destiantioni images. This makes it well
1731% sutiable for directly composing 'Clears Frame Animations' or 'Coaleased
1732% Animations' onto a static or other 'Coaleased Animation' destination image
1733% list. GIF disposal handling is not looked at.
cristy3ed852e2009-09-05 21:47:34 +00001734%
anthonye5840b22012-03-17 12:22:34 +00001735% Special case:- If one of the image sequences is the last image (just a
1736% single image remaining), that image is repeatally composed with all the
1737% images in the other image list. Either the source or destination lists may
1738% be the single image, for this situation.
cristy3ed852e2009-09-05 21:47:34 +00001739%
anthonye5840b22012-03-17 12:22:34 +00001740% In the case of a single destination image (or last image given), that image
1741% will ve cloned to match the number of images remaining in the source image
1742% list.
1743%
1744% This is equivelent to the "-layer Composite" Shell API operator.
1745%
cristy3ed852e2009-09-05 21:47:34 +00001746%
1747% The format of the CompositeLayers method is:
1748%
anthonye5840b22012-03-17 12:22:34 +00001749% void CompositeLayers(Image *destination, const CompositeOperator
1750% compose, Image *source, const ssize_t x_offset, const ssize_t y_offset,
1751% ExceptionInfo *exception);
cristy3ed852e2009-09-05 21:47:34 +00001752%
1753% A description of each parameter follows:
1754%
1755% o destination: the destination images and results
1756%
1757% o source: source image(s) for the layer composition
1758%
1759% o compose, x_offset, y_offset: arguments passed on to CompositeImages()
1760%
1761% o exception: return any errors or warnings in this structure.
1762%
1763*/
cristye941a752011-10-15 01:52:48 +00001764
cristy3ed852e2009-09-05 21:47:34 +00001765static inline void CompositeCanvas(Image *destination,
cristye941a752011-10-15 01:52:48 +00001766 const CompositeOperator compose,Image *source,ssize_t x_offset,
1767 ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001768{
cristy9d314ff2011-03-09 01:30:28 +00001769 x_offset+=source->page.x-destination->page.x;
1770 y_offset+=source->page.y-destination->page.y;
cristy39172402012-03-30 13:04:39 +00001771 (void) CompositeImage(destination,source,compose,MagickTrue,x_offset,
cristyfeb3e962012-03-29 17:25:55 +00001772 y_offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00001773}
1774
1775MagickExport void CompositeLayers(Image *destination,
cristy9d314ff2011-03-09 01:30:28 +00001776 const CompositeOperator compose, Image *source,const ssize_t x_offset,
1777 const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001778{
1779 assert(destination != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001780 assert(destination->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001781 assert(source != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001782 assert(source->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001783 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001784 assert(exception->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001785 if (source->debug != MagickFalse || destination->debug != MagickFalse)
1786 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s - %s",
cristye941a752011-10-15 01:52:48 +00001787 source->filename, destination->filename);
cristy3ed852e2009-09-05 21:47:34 +00001788
1789 /*
1790 Overlay single source image over destation image/list
1791 */
anthonye5840b22012-03-17 12:22:34 +00001792 if ( source->next == (Image *) NULL )
cristy3ed852e2009-09-05 21:47:34 +00001793 while ( destination != (Image *) NULL )
1794 {
cristye941a752011-10-15 01:52:48 +00001795 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1796 exception);
cristy3ed852e2009-09-05 21:47:34 +00001797 destination=GetNextImageInList(destination);
1798 }
1799
1800 /*
anthonye5840b22012-03-17 12:22:34 +00001801 Overlay source image list over single destination.
1802 Multiple clones of destination image are created to match source list.
cristy3ed852e2009-09-05 21:47:34 +00001803 Original Destination image becomes first image of generated list.
1804 As such the image list pointer does not require any change in caller.
1805 Some animation attributes however also needs coping in this case.
1806 */
anthonye5840b22012-03-17 12:22:34 +00001807 else if ( destination->next == (Image *) NULL )
cristy3ed852e2009-09-05 21:47:34 +00001808 {
1809 Image *dest = CloneImage(destination,0,0,MagickTrue,exception);
1810
cristye941a752011-10-15 01:52:48 +00001811 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1812 exception);
cristy3ed852e2009-09-05 21:47:34 +00001813 /* copy source image attributes ? */
anthonyfe2045f2011-04-14 02:57:12 +00001814 if ( source->next != (Image *) NULL )
1815 {
1816 destination->delay = source->delay;
1817 destination->iterations = source->iterations;
1818 }
cristy3ed852e2009-09-05 21:47:34 +00001819 source=GetNextImageInList(source);
1820
1821 while ( source != (Image *) NULL )
1822 {
1823 AppendImageToList(&destination,
1824 CloneImage(dest,0,0,MagickTrue,exception));
1825 destination=GetLastImageInList(destination);
1826
cristye941a752011-10-15 01:52:48 +00001827 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1828 exception);
cristy3ed852e2009-09-05 21:47:34 +00001829 destination->delay = source->delay;
1830 destination->iterations = source->iterations;
1831 source=GetNextImageInList(source);
1832 }
1833 dest=DestroyImage(dest);
1834 }
1835
1836 /*
1837 Overlay a source image list over a destination image list
1838 until either list runs out of images. (Does not repeat)
1839 */
1840 else
1841 while ( source != (Image *) NULL && destination != (Image *) NULL )
1842 {
cristye941a752011-10-15 01:52:48 +00001843 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1844 exception);
cristy3ed852e2009-09-05 21:47:34 +00001845 source=GetNextImageInList(source);
1846 destination=GetNextImageInList(destination);
1847 }
1848}
1849
1850/*
1851%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1852% %
1853% %
1854% %
1855% M e r g e I m a g e L a y e r s %
1856% %
1857% %
1858% %
1859%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1860%
1861% MergeImageLayers() composes all the image layers from the current given
1862% image onward to produce a single image of the merged layers.
1863%
cristya0417062012-09-02 23:34:56 +00001864% The inital canvas's size depends on the given LayerMethod, and is
cristy3ed852e2009-09-05 21:47:34 +00001865% initialized using the first images background color. The images
1866% are then compositied onto that image in sequence using the given
1867% composition that has been assigned to each individual image.
1868%
1869% The format of the MergeImageLayers is:
1870%
1871% Image *MergeImageLayers(const Image *image,
cristya0417062012-09-02 23:34:56 +00001872% const LayerMethod method, ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001873%
1874% A description of each parameter follows:
1875%
1876% o image: the image list to be composited together
1877%
1878% o method: the method of selecting the size of the initial canvas.
1879%
1880% MergeLayer: Merge all layers onto a canvas just large enough
1881% to hold all the actual images. The virtual canvas of the
1882% first image is preserved but otherwise ignored.
1883%
1884% FlattenLayer: Use the virtual canvas size of first image.
1885% Images which fall outside this canvas is clipped.
1886% This can be used to 'fill out' a given virtual canvas.
1887%
1888% MosaicLayer: Start with the virtual canvas of the first image,
1889% enlarging left and right edges to contain all images.
1890% Images with negative offsets will be clipped.
1891%
1892% TrimBoundsLayer: Determine the overall bounds of all the image
1893% layers just as in "MergeLayer", then adjust the the canvas
1894% and offsets to be relative to those bounds, without overlaying
1895% the images.
1896%
1897% WARNING: a new image is not returned, the original image
1898% sequence page data is modified instead.
1899%
1900% o exception: return any errors or warnings in this structure.
1901%
1902*/
cristya0417062012-09-02 23:34:56 +00001903MagickExport Image *MergeImageLayers(Image *image,const LayerMethod method,
cristye941a752011-10-15 01:52:48 +00001904 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001905{
1906#define MergeLayersTag "Merge/Layers"
1907
1908 Image
1909 *canvas;
1910
1911 MagickBooleanType
1912 proceed;
1913
cristy3ed852e2009-09-05 21:47:34 +00001914 RectangleInfo
1915 page;
1916
cristy3ed852e2009-09-05 21:47:34 +00001917 register const Image
1918 *next;
1919
cristybb503372010-05-27 20:51:26 +00001920 size_t
cristycee97112010-05-28 00:44:52 +00001921 number_images,
1922 height,
1923 width;
1924
1925 ssize_t
1926 scene;
cristy3ed852e2009-09-05 21:47:34 +00001927
1928 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001929 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001930 if (image->debug != MagickFalse)
1931 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1932 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001933 assert(exception->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001934 /*
1935 Determine canvas image size, and its virtual canvas size and offset
1936 */
1937 page=image->page;
1938 width=image->columns;
1939 height=image->rows;
1940 switch (method)
1941 {
1942 case TrimBoundsLayer:
1943 case MergeLayer:
1944 default:
1945 {
cristy7fcdfd02011-11-20 03:36:37 +00001946 next=GetNextImageInList(image);
1947 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
1948 {
1949 if (page.x > next->page.x)
1950 {
1951 width+=page.x-next->page.x;
1952 page.x=next->page.x;
1953 }
1954 if (page.y > next->page.y)
1955 {
1956 height+=page.y-next->page.y;
1957 page.y=next->page.y;
1958 }
1959 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns-page.x))
cristy9a4ef392014-09-06 12:46:41 +00001960 width=(size_t) next->page.x+(ssize_t) next->columns-page.x;
cristy7fcdfd02011-11-20 03:36:37 +00001961 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows-page.y))
1962 height=(size_t) next->page.y+(ssize_t) next->rows-page.y;
cristy3ed852e2009-09-05 21:47:34 +00001963 }
1964 break;
1965 }
1966 case FlattenLayer:
1967 {
cristy7fcdfd02011-11-20 03:36:37 +00001968 if (page.width > 0)
cristy3ed852e2009-09-05 21:47:34 +00001969 width=page.width;
cristy7fcdfd02011-11-20 03:36:37 +00001970 if (page.height > 0)
cristy3ed852e2009-09-05 21:47:34 +00001971 height=page.height;
1972 page.x=0;
1973 page.y=0;
1974 break;
1975 }
1976 case MosaicLayer:
1977 {
cristy7fcdfd02011-11-20 03:36:37 +00001978 if (page.width > 0)
cristy3ed852e2009-09-05 21:47:34 +00001979 width=page.width;
cristy7fcdfd02011-11-20 03:36:37 +00001980 if (page.height > 0)
cristy3ed852e2009-09-05 21:47:34 +00001981 height=page.height;
cristy7fcdfd02011-11-20 03:36:37 +00001982 for (next=image; next != (Image *) NULL; next=GetNextImageInList(next))
1983 {
1984 if (method == MosaicLayer)
1985 {
1986 page.x=next->page.x;
1987 page.y=next->page.y;
1988 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns))
1989 width=(size_t) next->page.x+next->columns;
1990 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows))
1991 height=(size_t) next->page.y+next->rows;
1992 }
cristy3ed852e2009-09-05 21:47:34 +00001993 }
1994 page.width=width;
1995 page.height=height;
1996 page.x=0;
1997 page.y=0;
1998 }
1999 break;
2000 }
cristy3ed852e2009-09-05 21:47:34 +00002001 /*
cristy7fcdfd02011-11-20 03:36:37 +00002002 Set virtual canvas size if not defined.
cristy3ed852e2009-09-05 21:47:34 +00002003 */
cristy7fcdfd02011-11-20 03:36:37 +00002004 if (page.width == 0)
2005 page.width=page.x < 0 ? width : width+page.x;
2006 if (page.height == 0)
2007 page.height=page.y < 0 ? height : height+page.y;
2008 /*
2009 Handle "TrimBoundsLayer" method separately to normal 'layer merge'.
2010 */
2011 if (method == TrimBoundsLayer)
cristy3ed852e2009-09-05 21:47:34 +00002012 {
cristy7fcdfd02011-11-20 03:36:37 +00002013 number_images=GetImageListLength(image);
2014 for (scene=0; scene < (ssize_t) number_images; scene++)
2015 {
2016 image->page.x-=page.x;
2017 image->page.y-=page.y;
2018 image->page.width=width;
2019 image->page.height=height;
2020 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2021 number_images);
2022 if (proceed == MagickFalse)
2023 break;
2024 image=GetNextImageInList(image);
cristyee534e52011-11-20 03:41:17 +00002025 if (image == (Image *) NULL)
2026 break;
cristy7fcdfd02011-11-20 03:36:37 +00002027 }
2028 return((Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002029 }
cristy3ed852e2009-09-05 21:47:34 +00002030 /*
2031 Create canvas size of width and height, and background color.
2032 */
2033 canvas=CloneImage(image,width,height,MagickTrue,exception);
2034 if (canvas == (Image *) NULL)
2035 return((Image *) NULL);
cristyea1a8aa2011-10-20 13:24:06 +00002036 (void) SetImageBackgroundColor(canvas,exception);
cristy3ed852e2009-09-05 21:47:34 +00002037 canvas->page=page;
2038 canvas->dispose=UndefinedDispose;
cristy3ed852e2009-09-05 21:47:34 +00002039 /*
2040 Compose images onto canvas, with progress monitor
2041 */
2042 number_images=GetImageListLength(image);
cristybb503372010-05-27 20:51:26 +00002043 for (scene=0; scene < (ssize_t) number_images; scene++)
cristy3ed852e2009-09-05 21:47:34 +00002044 {
cristy40e1d032012-03-30 13:26:42 +00002045 (void) CompositeImage(canvas,image,image->compose,MagickTrue,image->page.x-
cristye941a752011-10-15 01:52:48 +00002046 canvas->page.x,image->page.y-canvas->page.y,exception);
cristycee97112010-05-28 00:44:52 +00002047 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2048 number_images);
cristy3ed852e2009-09-05 21:47:34 +00002049 if (proceed == MagickFalse)
2050 break;
2051 image=GetNextImageInList(image);
cristy7fcdfd02011-11-20 03:36:37 +00002052 if (image == (Image *) NULL)
2053 break;
cristy3ed852e2009-09-05 21:47:34 +00002054 }
2055 return(canvas);
2056}
2057