blob: d80c2ca1f9899d18e4b669f1eaaba017f9f50a12 [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 %
14% John Cristy %
15% Anthony Thyssen %
16% January 2006 %
17% %
18% %
cristy1454be72011-12-19 01:52:48 +000019% Copyright 1999-2012 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% %
25% http://www.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*/
cristy4c08aed2011-07-01 19:47:50 +000040#include "MagickCore/studio.h"
41#include "MagickCore/artifact.h"
42#include "MagickCore/cache.h"
43#include "MagickCore/color.h"
44#include "MagickCore/color-private.h"
45#include "MagickCore/composite.h"
46#include "MagickCore/effect.h"
47#include "MagickCore/exception.h"
48#include "MagickCore/exception-private.h"
49#include "MagickCore/geometry.h"
50#include "MagickCore/image.h"
51#include "MagickCore/layer.h"
52#include "MagickCore/list.h"
53#include "MagickCore/memory_.h"
54#include "MagickCore/monitor.h"
55#include "MagickCore/monitor-private.h"
56#include "MagickCore/option.h"
57#include "MagickCore/pixel-accessor.h"
58#include "MagickCore/property.h"
59#include "MagickCore/profile.h"
60#include "MagickCore/resource_.h"
61#include "MagickCore/resize.h"
62#include "MagickCore/statistic.h"
63#include "MagickCore/string_.h"
64#include "MagickCore/transform.h"
cristy3ed852e2009-09-05 21:47:34 +000065
66/*
67%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
68% %
69% %
70% %
71+ C l e a r B o u n d s %
72% %
73% %
74% %
75%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
76%
77% ClearBounds() Clear the area specified by the bounds in an image to
78% transparency. This typically used to handle Background Disposal
79% for the previous frame in an animation sequence.
80%
81% WARNING: no bounds checks are performed, except for the null or
82% missed image, for images that don't change. in all other cases
83% bound must fall within the image.
84%
85% The format is:
86%
cristy7c3af952011-10-20 16:04:16 +000087% void ClearBounds(Image *image,RectangleInfo *bounds
88% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +000089%
90% A description of each parameter follows:
91%
92% o image: the image to had the area cleared in
93%
94% o bounds: the area to be clear within the imag image
95%
cristy7c3af952011-10-20 16:04:16 +000096% o exception: return any errors or warnings in this structure.
97%
cristy3ed852e2009-09-05 21:47:34 +000098*/
cristy7c3af952011-10-20 16:04:16 +000099static void ClearBounds(Image *image,RectangleInfo *bounds,
100 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000101{
cristybb503372010-05-27 20:51:26 +0000102 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000103 y;
104
105 if (bounds->x < 0)
106 return;
cristy63240882011-08-05 19:05:27 +0000107 if (image->matte == MagickFalse)
108 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristybb503372010-05-27 20:51:26 +0000109 for (y=0; y < (ssize_t) bounds->height; y++)
cristy3ed852e2009-09-05 21:47:34 +0000110 {
cristybb503372010-05-27 20:51:26 +0000111 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000112 x;
113
cristy4c08aed2011-07-01 19:47:50 +0000114 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000115 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000116
117 q=GetAuthenticPixels(image,bounds->x,bounds->y+y,bounds->width,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000118 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000119 break;
cristybb503372010-05-27 20:51:26 +0000120 for (x=0; x < (ssize_t) bounds->width; x++)
cristy3ed852e2009-09-05 21:47:34 +0000121 {
cristy4c08aed2011-07-01 19:47:50 +0000122 SetPixelAlpha(image,TransparentAlpha,q);
cristyed231572011-07-14 02:18:59 +0000123 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000124 }
125 if (SyncAuthenticPixels(image,exception) == MagickFalse)
126 break;
127 }
128}
129
130/*
131%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
132% %
133% %
134% %
135+ I s B o u n d s C l e a r e d %
136% %
137% %
138% %
139%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
140%
141% IsBoundsCleared() tests whether any pixel in the bounds given, gets cleared
142% when going from the first image to the second image. This typically used
143% to check if a proposed disposal method will work successfully to generate
144% the second frame image from the first disposed form of the previous frame.
145%
146% The format is:
147%
148% MagickBooleanType IsBoundsCleared(const Image *image1,
149% const Image *image2,RectangleInfo bounds,ExceptionInfo *exception)
150%
151% A description of each parameter follows:
152%
153% o image1, image 2: the images to check for cleared pixels
154%
155% o bounds: the area to be clear within the imag image
156%
157% o exception: return any errors or warnings in this structure.
158%
159% WARNING: no bounds checks are performed, except for the null or
160% missed image, for images that don't change. in all other cases
161% bound must fall within the image.
162%
163*/
164static MagickBooleanType IsBoundsCleared(const Image *image1,
165 const Image *image2,RectangleInfo *bounds,ExceptionInfo *exception)
166{
cristybb503372010-05-27 20:51:26 +0000167 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000168 x;
169
cristy4c08aed2011-07-01 19:47:50 +0000170 register const Quantum
cristy3ed852e2009-09-05 21:47:34 +0000171 *p,
172 *q;
173
cristy9d314ff2011-03-09 01:30:28 +0000174 ssize_t
175 y;
176
cristye23ec9d2011-08-16 18:15:40 +0000177 if (bounds->x < 0)
178 return(MagickFalse);
cristybb503372010-05-27 20:51:26 +0000179 for (y=0; y < (ssize_t) bounds->height; y++)
cristy3ed852e2009-09-05 21:47:34 +0000180 {
181 p=GetVirtualPixels(image1,bounds->x,bounds->y+y,bounds->width,1,
182 exception);
183 q=GetVirtualPixels(image2,bounds->x,bounds->y+y,bounds->width,1,
184 exception);
cristy4c08aed2011-07-01 19:47:50 +0000185 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000186 break;
cristybb503372010-05-27 20:51:26 +0000187 for (x=0; x < (ssize_t) bounds->width; x++)
cristy3ed852e2009-09-05 21:47:34 +0000188 {
cristy4c08aed2011-07-01 19:47:50 +0000189 if ((GetPixelAlpha(image1,p) <= (Quantum) (QuantumRange/2)) &&
190 (GetPixelAlpha(image1,q) > (Quantum) (QuantumRange/2)))
cristy3ed852e2009-09-05 21:47:34 +0000191 break;
cristyed231572011-07-14 02:18:59 +0000192 p+=GetPixelChannels(image1);
cristy3ed852e2009-09-05 21:47:34 +0000193 q++;
194 }
cristybb503372010-05-27 20:51:26 +0000195 if (x < (ssize_t) bounds->width)
cristy3ed852e2009-09-05 21:47:34 +0000196 break;
197 }
cristybb503372010-05-27 20:51:26 +0000198 return(y < (ssize_t) bounds->height ? MagickTrue : MagickFalse);
cristy3ed852e2009-09-05 21:47:34 +0000199}
200
201/*
202%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
203% %
204% %
205% %
206% C o a l e s c e I m a g e s %
207% %
208% %
209% %
210%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
211%
212% CoalesceImages() composites a set of images while respecting any page
213% offsets and disposal methods. GIF, MIFF, and MNG animation sequences
214% typically start with an image background and each subsequent image
215% varies in size and offset. A new image sequence is returned with all
216% images the same size as the first images virtual canvas and composited
217% with the next image in the sequence.
218%
219% The format of the CoalesceImages method is:
220%
221% Image *CoalesceImages(Image *image,ExceptionInfo *exception)
222%
223% A description of each parameter follows:
224%
225% o image: the image sequence.
226%
227% o exception: return any errors or warnings in this structure.
228%
229*/
230MagickExport Image *CoalesceImages(const Image *image,ExceptionInfo *exception)
231{
232 Image
233 *coalesce_image,
234 *dispose_image,
235 *previous;
236
237 register Image
238 *next;
239
240 RectangleInfo
241 bounds;
242
243 /*
244 Coalesce the image sequence.
245 */
246 assert(image != (Image *) NULL);
247 assert(image->signature == MagickSignature);
248 if (image->debug != MagickFalse)
249 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
250 assert(exception != (ExceptionInfo *) NULL);
251 assert(exception->signature == MagickSignature);
cristy3ed852e2009-09-05 21:47:34 +0000252 next=GetFirstImageInList(image);
253 bounds=next->page;
254 if (bounds.width == 0)
255 {
256 bounds.width=next->columns;
257 if (bounds.x > 0)
258 bounds.width+=bounds.x;
259 }
260 if (bounds.height == 0)
261 {
262 bounds.height=next->rows;
263 if (bounds.y > 0)
264 bounds.height+=bounds.y;
265 }
266 bounds.x=0;
267 bounds.y=0;
268 coalesce_image=CloneImage(next,bounds.width,bounds.height,MagickTrue,
269 exception);
270 if (coalesce_image == (Image *) NULL)
271 return((Image *) NULL);
272 coalesce_image->page=bounds;
273 coalesce_image->dispose=NoneDispose;
cristyea1a8aa2011-10-20 13:24:06 +0000274 (void) SetImageBackgroundColor(coalesce_image,exception);
cristy3ed852e2009-09-05 21:47:34 +0000275 /*
276 Coalesce rest of the images.
277 */
278 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
cristy39172402012-03-30 13:04:39 +0000279 (void) CompositeImage(coalesce_image,next,CopyCompositeOp,MagickTrue,
cristyfeb3e962012-03-29 17:25:55 +0000280 next->page.x,next->page.y,exception);
cristy3ed852e2009-09-05 21:47:34 +0000281 next=GetNextImageInList(next);
282 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
283 {
284 /*
285 Determine the bounds that was overlaid in the previous image.
286 */
287 previous=GetPreviousImageInList(next);
288 bounds=previous->page;
289 bounds.width=previous->columns;
290 bounds.height=previous->rows;
291 if (bounds.x < 0)
292 {
293 bounds.width+=bounds.x;
294 bounds.x=0;
295 }
cristybb503372010-05-27 20:51:26 +0000296 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) coalesce_image->columns)
cristy3ed852e2009-09-05 21:47:34 +0000297 bounds.width=coalesce_image->columns-bounds.x;
298 if (bounds.y < 0)
299 {
300 bounds.height+=bounds.y;
301 bounds.y=0;
302 }
cristybb503372010-05-27 20:51:26 +0000303 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) coalesce_image->rows)
cristy3ed852e2009-09-05 21:47:34 +0000304 bounds.height=coalesce_image->rows-bounds.y;
305 /*
306 Replace the dispose image with the new coalesced image.
307 */
308 if (GetPreviousImageInList(next)->dispose != PreviousDispose)
309 {
310 dispose_image=DestroyImage(dispose_image);
311 dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
312 if (dispose_image == (Image *) NULL)
313 {
314 coalesce_image=DestroyImageList(coalesce_image);
315 return((Image *) NULL);
316 }
317 }
318 /*
319 Clear the overlaid area of the coalesced bounds for background disposal
320 */
321 if (next->previous->dispose == BackgroundDispose)
cristy7c3af952011-10-20 16:04:16 +0000322 ClearBounds(dispose_image,&bounds,exception);
cristy3ed852e2009-09-05 21:47:34 +0000323 /*
324 Next image is the dispose image, overlaid with next frame in sequence.
325 */
326 coalesce_image->next=CloneImage(dispose_image,0,0,MagickTrue,exception);
327 coalesce_image->next->previous=coalesce_image;
328 previous=coalesce_image;
329 coalesce_image=GetNextImageInList(coalesce_image);
cristyfeb3e962012-03-29 17:25:55 +0000330 (void) CompositeImage(coalesce_image,next,next->matte != MagickFalse ?
cristy39172402012-03-30 13:04:39 +0000331 OverCompositeOp : CopyCompositeOp,MagickTrue,next->page.x,next->page.y,
cristye941a752011-10-15 01:52:48 +0000332 exception);
cristy3ed852e2009-09-05 21:47:34 +0000333 (void) CloneImageProfiles(coalesce_image,next);
334 (void) CloneImageProperties(coalesce_image,next);
335 (void) CloneImageArtifacts(coalesce_image,next);
336 coalesce_image->page=previous->page;
337 /*
338 If a pixel goes opaque to transparent, use background dispose.
339 */
340 if (IsBoundsCleared(previous,coalesce_image,&bounds,exception))
341 coalesce_image->dispose=BackgroundDispose;
342 else
343 coalesce_image->dispose=NoneDispose;
344 previous->dispose=coalesce_image->dispose;
345 }
346 dispose_image=DestroyImage(dispose_image);
347 return(GetFirstImageInList(coalesce_image));
348}
349
350/*
351%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
352% %
353% %
354% %
355% D i s p o s e I m a g e s %
356% %
357% %
358% %
359%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
360%
361% DisposeImages() returns the coalesced frames of a GIF animation as it would
362% appear after the GIF dispose method of that frame has been applied. That
363% is it returned the appearance of each frame before the next is overlaid.
364%
365% The format of the DisposeImages method is:
366%
367% Image *DisposeImages(Image *image,ExceptionInfo *exception)
368%
369% A description of each parameter follows:
370%
cristyfeb3e962012-03-29 17:25:55 +0000371% o images: the image sequence.
cristy3ed852e2009-09-05 21:47:34 +0000372%
373% o exception: return any errors or warnings in this structure.
374%
375*/
cristyfeb3e962012-03-29 17:25:55 +0000376MagickExport Image *DisposeImages(const Image *images,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000377{
378 Image
379 *dispose_image,
380 *dispose_images;
381
382 register Image
cristyfeb3e962012-03-29 17:25:55 +0000383 *image,
384 *next;
anthonyb6d08c52010-09-13 01:17:04 +0000385
386 RectangleInfo
387 bounds;
cristy3ed852e2009-09-05 21:47:34 +0000388
389 /*
390 Run the image through the animation sequence
391 */
cristyfeb3e962012-03-29 17:25:55 +0000392 assert(images != (Image *) NULL);
393 assert(images->signature == MagickSignature);
394 if (images->debug != MagickFalse)
395 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
cristy3ed852e2009-09-05 21:47:34 +0000396 assert(exception != (ExceptionInfo *) NULL);
397 assert(exception->signature == MagickSignature);
cristyfeb3e962012-03-29 17:25:55 +0000398 image=GetFirstImageInList(images);
399 dispose_image=CloneImage(image,image->page.width,image->page.height,
400 MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000401 if (dispose_image == (Image *) NULL)
402 return((Image *) NULL);
cristyfeb3e962012-03-29 17:25:55 +0000403 dispose_image->page=image->page;
cristy3ed852e2009-09-05 21:47:34 +0000404 dispose_image->page.x=0;
405 dispose_image->page.y=0;
406 dispose_image->dispose=NoneDispose;
cristy4c08aed2011-07-01 19:47:50 +0000407 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
cristyea1a8aa2011-10-20 13:24:06 +0000408 (void) SetImageBackgroundColor(dispose_image,exception);
cristy3ed852e2009-09-05 21:47:34 +0000409 dispose_images=NewImageList();
cristyfeb3e962012-03-29 17:25:55 +0000410 for (next=image; image != (Image *) NULL; image=GetNextImageInList(image))
cristy3ed852e2009-09-05 21:47:34 +0000411 {
412 Image
413 *current_image;
414
415 /*
416 Overlay this frame's image over the previous disposal image.
417 */
418 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
419 if (current_image == (Image *) NULL)
420 {
421 dispose_images=DestroyImageList(dispose_images);
422 dispose_image=DestroyImage(dispose_image);
423 return((Image *) NULL);
424 }
cristyfeb3e962012-03-29 17:25:55 +0000425 (void) CompositeImage(current_image,next,next->matte != MagickFalse ?
cristy39172402012-03-30 13:04:39 +0000426 OverCompositeOp : CopyCompositeOp,MagickTrue,next->page.x,next->page.y,
cristye941a752011-10-15 01:52:48 +0000427 exception);
cristy3ed852e2009-09-05 21:47:34 +0000428 /*
429 Handle Background dispose: image is displayed for the delay period.
430 */
cristyfeb3e962012-03-29 17:25:55 +0000431 if (next->dispose == BackgroundDispose)
cristy3ed852e2009-09-05 21:47:34 +0000432 {
cristyfeb3e962012-03-29 17:25:55 +0000433 bounds=next->page;
434 bounds.width=next->columns;
435 bounds.height=next->rows;
cristy3ed852e2009-09-05 21:47:34 +0000436 if (bounds.x < 0)
437 {
438 bounds.width+=bounds.x;
439 bounds.x=0;
440 }
cristybb503372010-05-27 20:51:26 +0000441 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
cristy3ed852e2009-09-05 21:47:34 +0000442 bounds.width=current_image->columns-bounds.x;
443 if (bounds.y < 0)
444 {
445 bounds.height+=bounds.y;
446 bounds.y=0;
447 }
cristybb503372010-05-27 20:51:26 +0000448 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
cristy3ed852e2009-09-05 21:47:34 +0000449 bounds.height=current_image->rows-bounds.y;
cristy7c3af952011-10-20 16:04:16 +0000450 ClearBounds(current_image,&bounds,exception);
cristy3ed852e2009-09-05 21:47:34 +0000451 }
452 /*
453 Select the appropriate previous/disposed image.
454 */
cristyfeb3e962012-03-29 17:25:55 +0000455 if (next->dispose == PreviousDispose)
cristy3ed852e2009-09-05 21:47:34 +0000456 current_image=DestroyImage(current_image);
457 else
458 {
459 dispose_image=DestroyImage(dispose_image);
460 dispose_image=current_image;
anthonyb6d08c52010-09-13 01:17:04 +0000461 current_image=(Image *)NULL;
cristy3ed852e2009-09-05 21:47:34 +0000462 }
anthonyb6d08c52010-09-13 01:17:04 +0000463 /*
464 Save the dispose image just calculated for return.
465 */
cristy3ed852e2009-09-05 21:47:34 +0000466 {
467 Image
468 *dispose;
469
cristy3ed852e2009-09-05 21:47:34 +0000470 dispose=CloneImage(dispose_image,0,0,MagickTrue,exception);
471 if (dispose == (Image *) NULL)
472 {
473 dispose_images=DestroyImageList(dispose_images);
474 dispose_image=DestroyImage(dispose_image);
475 return((Image *) NULL);
476 }
cristyfeb3e962012-03-29 17:25:55 +0000477 (void) CloneImageProfiles(dispose,next);
478 (void) CloneImageProperties(dispose,next);
479 (void) CloneImageArtifacts(dispose,next);
cristy3ed852e2009-09-05 21:47:34 +0000480 dispose->page.x=0;
481 dispose->page.y=0;
cristyfeb3e962012-03-29 17:25:55 +0000482 dispose->dispose=next->dispose;
cristy3ed852e2009-09-05 21:47:34 +0000483 AppendImageToList(&dispose_images,dispose);
484 }
485 }
486 dispose_image=DestroyImage(dispose_image);
487 return(GetFirstImageInList(dispose_images));
488}
489
490/*
491%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
492% %
493% %
494% %
495+ C o m p a r e P i x e l s %
496% %
497% %
498% %
499%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
500%
501% ComparePixels() Compare the two pixels and return true if the pixels
502% differ according to the given LayerType comparision method.
503%
cristy8a9106f2011-07-05 14:39:26 +0000504% This currently only used internally by CompareImagesBounds(). It is
cristy3ed852e2009-09-05 21:47:34 +0000505% doubtful that this sub-routine will be useful outside this module.
506%
507% The format of the ComparePixels method is:
508%
509% MagickBooleanType *ComparePixels(const ImageLayerMethod method,
cristy4c08aed2011-07-01 19:47:50 +0000510% const PixelInfo *p,const PixelInfo *q)
cristy3ed852e2009-09-05 21:47:34 +0000511%
512% A description of each parameter follows:
513%
514% o method: What differences to look for. Must be one of
515% CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
516%
517% o p, q: the pixels to test for appropriate differences.
518%
519*/
520
521static MagickBooleanType ComparePixels(const ImageLayerMethod method,
cristy4c08aed2011-07-01 19:47:50 +0000522 const PixelInfo *p,const PixelInfo *q)
cristy3ed852e2009-09-05 21:47:34 +0000523{
524 MagickRealType
525 o1,
526 o2;
527
528 /*
529 Any change in pixel values
530 */
531 if (method == CompareAnyLayer)
cristy4c08aed2011-07-01 19:47:50 +0000532 return((MagickBooleanType)(IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
cristy3ed852e2009-09-05 21:47:34 +0000533
cristy4c08aed2011-07-01 19:47:50 +0000534 o1 = (p->matte != MagickFalse) ? p->alpha : OpaqueAlpha;
535 o2 = (q->matte != MagickFalse) ? q->alpha : OpaqueAlpha;
cristy3ed852e2009-09-05 21:47:34 +0000536
537 /*
538 Pixel goes from opaque to transprency
539 */
540 if (method == CompareClearLayer)
541 return((MagickBooleanType) ( (o1 <= ((MagickRealType) QuantumRange/2.0)) &&
542 (o2 > ((MagickRealType) QuantumRange/2.0)) ) );
543
544 /*
545 overlay would change first pixel by second
546 */
547 if (method == CompareOverlayLayer)
548 {
549 if (o2 > ((MagickRealType) QuantumRange/2.0))
550 return MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +0000551 return((MagickBooleanType) (IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
cristy3ed852e2009-09-05 21:47:34 +0000552 }
553 return(MagickFalse);
554}
555
556
557/*
558%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
559% %
560% %
561% %
562+ C o m p a r e I m a g e B o u n d s %
563% %
564% %
565% %
566%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
567%
cristy8a9106f2011-07-05 14:39:26 +0000568% CompareImagesBounds() Given two images return the smallest rectangular area
cristy3ed852e2009-09-05 21:47:34 +0000569% by which the two images differ, accourding to the given 'Compare...'
570% layer method.
571%
572% This currently only used internally in this module, but may eventually
573% be used by other modules.
574%
cristy8a9106f2011-07-05 14:39:26 +0000575% The format of the CompareImagesBounds method is:
cristy3ed852e2009-09-05 21:47:34 +0000576%
cristy8a9106f2011-07-05 14:39:26 +0000577% RectangleInfo *CompareImagesBounds(const ImageLayerMethod method,
cristy3ed852e2009-09-05 21:47:34 +0000578% const Image *image1, const Image *image2, ExceptionInfo *exception)
579%
580% A description of each parameter follows:
581%
cristy4c08aed2011-07-01 19:47:50 +0000582% o method: What differences to look for. Must be one of CompareAnyLayer,
583% CompareClearLayer, CompareOverlayLayer.
cristy3ed852e2009-09-05 21:47:34 +0000584%
585% o image1, image2: the two images to compare.
586%
587% o exception: return any errors or warnings in this structure.
588%
589*/
590
cristy8a9106f2011-07-05 14:39:26 +0000591static RectangleInfo CompareImagesBounds(const Image *image1,const Image *image2,
cristy3ed852e2009-09-05 21:47:34 +0000592 const ImageLayerMethod method,ExceptionInfo *exception)
593{
594 RectangleInfo
595 bounds;
596
cristy4c08aed2011-07-01 19:47:50 +0000597 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000598 pixel1,
599 pixel2;
600
cristy4c08aed2011-07-01 19:47:50 +0000601 register const Quantum
cristy3ed852e2009-09-05 21:47:34 +0000602 *p,
603 *q;
604
cristybb503372010-05-27 20:51:26 +0000605 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000606 x;
607
cristy9d314ff2011-03-09 01:30:28 +0000608 ssize_t
609 y;
610
cristy3ed852e2009-09-05 21:47:34 +0000611 /*
cristy4c08aed2011-07-01 19:47:50 +0000612 Set bounding box of the differences between images.
cristy3ed852e2009-09-05 21:47:34 +0000613 */
cristy4c08aed2011-07-01 19:47:50 +0000614 GetPixelInfo(image1,&pixel1);
615 GetPixelInfo(image2,&pixel2);
cristybb503372010-05-27 20:51:26 +0000616 for (x=0; x < (ssize_t) image1->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000617 {
618 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
619 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +0000620 if ((p == (const Quantum *) NULL) ||
cristyacd2ed22011-08-30 01:44:23 +0000621 (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000622 break;
cristybb503372010-05-27 20:51:26 +0000623 for (y=0; y < (ssize_t) image1->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000624 {
cristy803640d2011-11-17 02:11:32 +0000625 GetPixelInfoPixel(image1,p,&pixel1);
626 GetPixelInfoPixel(image2,q,&pixel2);
cristy3ed852e2009-09-05 21:47:34 +0000627 if (ComparePixels(method,&pixel1,&pixel2))
628 break;
cristyed231572011-07-14 02:18:59 +0000629 p+=GetPixelChannels(image1);
cristy3ed852e2009-09-05 21:47:34 +0000630 q++;
631 }
cristybb503372010-05-27 20:51:26 +0000632 if (y < (ssize_t) image1->rows)
cristy3ed852e2009-09-05 21:47:34 +0000633 break;
634 }
cristybb503372010-05-27 20:51:26 +0000635 if (x >= (ssize_t) image1->columns)
cristy3ed852e2009-09-05 21:47:34 +0000636 {
637 /*
638 Images are identical, return a null image.
639 */
640 bounds.x=-1;
641 bounds.y=-1;
642 bounds.width=1;
643 bounds.height=1;
644 return(bounds);
645 }
646 bounds.x=x;
cristybb503372010-05-27 20:51:26 +0000647 for (x=(ssize_t) image1->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +0000648 {
649 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
650 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +0000651 if ((p == (const Quantum *) NULL) ||
cristyacd2ed22011-08-30 01:44:23 +0000652 (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000653 break;
cristybb503372010-05-27 20:51:26 +0000654 for (y=0; y < (ssize_t) image1->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000655 {
cristy803640d2011-11-17 02:11:32 +0000656 GetPixelInfoPixel(image1,p,&pixel1);
657 GetPixelInfoPixel(image2,q,&pixel2);
cristy3ed852e2009-09-05 21:47:34 +0000658 if (ComparePixels(method,&pixel1,&pixel2))
659 break;
cristyed231572011-07-14 02:18:59 +0000660 p+=GetPixelChannels(image1);
cristy3ed852e2009-09-05 21:47:34 +0000661 q++;
662 }
cristybb503372010-05-27 20:51:26 +0000663 if (y < (ssize_t) image1->rows)
cristy3ed852e2009-09-05 21:47:34 +0000664 break;
665 }
cristybb503372010-05-27 20:51:26 +0000666 bounds.width=(size_t) (x-bounds.x+1);
667 for (y=0; y < (ssize_t) image1->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000668 {
669 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
670 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000671 if ((p == (const Quantum *) NULL) ||
cristyacd2ed22011-08-30 01:44:23 +0000672 (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000673 break;
cristybb503372010-05-27 20:51:26 +0000674 for (x=0; x < (ssize_t) image1->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000675 {
cristy803640d2011-11-17 02:11:32 +0000676 GetPixelInfoPixel(image1,p,&pixel1);
677 GetPixelInfoPixel(image2,q,&pixel2);
cristy3ed852e2009-09-05 21:47:34 +0000678 if (ComparePixels(method,&pixel1,&pixel2))
679 break;
cristyed231572011-07-14 02:18:59 +0000680 p+=GetPixelChannels(image1);
cristy3ed852e2009-09-05 21:47:34 +0000681 q++;
682 }
cristybb503372010-05-27 20:51:26 +0000683 if (x < (ssize_t) image1->columns)
cristy3ed852e2009-09-05 21:47:34 +0000684 break;
685 }
686 bounds.y=y;
cristybb503372010-05-27 20:51:26 +0000687 for (y=(ssize_t) image1->rows-1; y >= 0; y--)
cristy3ed852e2009-09-05 21:47:34 +0000688 {
689 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
690 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000691 if ((p == (const Quantum *) NULL) ||
cristyacd2ed22011-08-30 01:44:23 +0000692 (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000693 break;
cristybb503372010-05-27 20:51:26 +0000694 for (x=0; x < (ssize_t) image1->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000695 {
cristy803640d2011-11-17 02:11:32 +0000696 GetPixelInfoPixel(image1,p,&pixel1);
697 GetPixelInfoPixel(image2,q,&pixel2);
cristy3ed852e2009-09-05 21:47:34 +0000698 if (ComparePixels(method,&pixel1,&pixel2))
699 break;
cristyed231572011-07-14 02:18:59 +0000700 p+=GetPixelChannels(image1);
cristy3ed852e2009-09-05 21:47:34 +0000701 q++;
702 }
cristybb503372010-05-27 20:51:26 +0000703 if (x < (ssize_t) image1->columns)
cristy3ed852e2009-09-05 21:47:34 +0000704 break;
705 }
cristybb503372010-05-27 20:51:26 +0000706 bounds.height=(size_t) (y-bounds.y+1);
cristy3ed852e2009-09-05 21:47:34 +0000707 return(bounds);
708}
709
710/*
711%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
712% %
713% %
714% %
715% C o m p a r e I m a g e L a y e r s %
716% %
717% %
718% %
719%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
720%
cristy8a9106f2011-07-05 14:39:26 +0000721% CompareImagesLayers() compares each image with the next in a sequence and
cristy3ed852e2009-09-05 21:47:34 +0000722% returns the minimum bounding region of all the pixel differences (of the
723% ImageLayerMethod specified) it discovers.
724%
725% Images do NOT have to be the same size, though it is best that all the
726% images are 'coalesced' (images are all the same size, on a flattened
727% canvas, so as to represent exactly how an specific frame should look).
728%
729% No GIF dispose methods are applied, so GIF animations must be coalesced
730% before applying this image operator to find differences to them.
731%
cristy8a9106f2011-07-05 14:39:26 +0000732% The format of the CompareImagesLayers method is:
cristy3ed852e2009-09-05 21:47:34 +0000733%
cristy8a9106f2011-07-05 14:39:26 +0000734% Image *CompareImagesLayers(const Image *images,
cristy3ed852e2009-09-05 21:47:34 +0000735% const ImageLayerMethod method,ExceptionInfo *exception)
736%
737% A description of each parameter follows:
738%
739% o image: the image.
740%
741% o method: the layers type to compare images with. Must be one of...
742% CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
743%
744% o exception: return any errors or warnings in this structure.
745%
746*/
747
cristy8a9106f2011-07-05 14:39:26 +0000748MagickExport Image *CompareImagesLayers(const Image *image,
cristy3ed852e2009-09-05 21:47:34 +0000749 const ImageLayerMethod method, ExceptionInfo *exception)
750{
751 Image
752 *image_a,
753 *image_b,
754 *layers;
755
756 RectangleInfo
757 *bounds;
758
759 register const Image
760 *next;
761
cristybb503372010-05-27 20:51:26 +0000762 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000763 i;
764
765 assert(image != (const Image *) NULL);
766 assert(image->signature == MagickSignature);
767 if (image->debug != MagickFalse)
768 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
769 assert(exception != (ExceptionInfo *) NULL);
770 assert(exception->signature == MagickSignature);
771 assert((method == CompareAnyLayer) ||
772 (method == CompareClearLayer) ||
773 (method == CompareOverlayLayer));
774 /*
775 Allocate bounds memory.
776 */
777 next=GetFirstImageInList(image);
778 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
779 GetImageListLength(next),sizeof(*bounds));
780 if (bounds == (RectangleInfo *) NULL)
781 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
782 /*
783 Set up first comparision images.
784 */
785 image_a=CloneImage(next,next->page.width,next->page.height,
786 MagickTrue,exception);
787 if (image_a == (Image *) NULL)
788 {
789 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
790 return((Image *) NULL);
791 }
cristy4c08aed2011-07-01 19:47:50 +0000792 image_a->background_color.alpha=(Quantum) TransparentAlpha;
cristyea1a8aa2011-10-20 13:24:06 +0000793 (void) SetImageBackgroundColor(image_a,exception);
cristy3ed852e2009-09-05 21:47:34 +0000794 image_a->page=next->page;
795 image_a->page.x=0;
796 image_a->page.y=0;
cristy39172402012-03-30 13:04:39 +0000797 (void) CompositeImage(image_a,next,CopyCompositeOp,MagickTrue,next->page.x,
cristyfeb3e962012-03-29 17:25:55 +0000798 next->page.y,exception);
cristy3ed852e2009-09-05 21:47:34 +0000799 /*
800 Compute the bounding box of changes for the later images
801 */
802 i=0;
803 next=GetNextImageInList(next);
804 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
805 {
806 image_b=CloneImage(image_a,0,0,MagickTrue,exception);
807 if (image_b == (Image *) NULL)
808 {
809 image_a=DestroyImage(image_a);
810 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
811 return((Image *) NULL);
812 }
cristy39172402012-03-30 13:04:39 +0000813 (void) CompositeImage(image_a,next,CopyCompositeOp,MagickTrue,next->page.x,
cristye941a752011-10-15 01:52:48 +0000814 next->page.y,exception);
cristy8a9106f2011-07-05 14:39:26 +0000815 bounds[i]=CompareImagesBounds(image_b,image_a,method,exception);
cristy3ed852e2009-09-05 21:47:34 +0000816
817 image_b=DestroyImage(image_b);
818 i++;
819 }
820 image_a=DestroyImage(image_a);
821 /*
822 Clone first image in sequence.
823 */
824 next=GetFirstImageInList(image);
825 layers=CloneImage(next,0,0,MagickTrue,exception);
826 if (layers == (Image *) NULL)
827 {
828 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
829 return((Image *) NULL);
830 }
831 /*
832 Deconstruct the image sequence.
833 */
834 i=0;
835 next=GetNextImageInList(next);
836 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
837 {
838 image_a=CloneImage(next,0,0,MagickTrue,exception);
839 if (image_a == (Image *) NULL)
840 break;
841 image_b=CropImage(image_a,&bounds[i],exception);
842 image_a=DestroyImage(image_a);
843 if (image_b == (Image *) NULL)
844 break;
845 AppendImageToList(&layers,image_b);
846 i++;
847 }
848 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
849 if (next != (Image *) NULL)
850 {
851 layers=DestroyImageList(layers);
852 return((Image *) NULL);
853 }
854 return(GetFirstImageInList(layers));
855}
856
857/*
858%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
859% %
860% %
861% %
cristy3ed852e2009-09-05 21:47:34 +0000862+ O p t i m i z e L a y e r F r a m e s %
863% %
864% %
865% %
866%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
867%
anthony9d570022010-09-11 07:18:35 +0000868% OptimizeLayerFrames() takes a coalesced GIF animation, and compares each
869% frame against the three different 'disposal' forms of the previous frame.
870% From this it then attempts to select the smallest cropped image and
871% disposal method needed to reproduce the resulting image.
cristy3ed852e2009-09-05 21:47:34 +0000872%
glennrp2489f532011-06-25 03:02:43 +0000873% Note that this not easy, and may require the expansion of the bounds
anthony9d570022010-09-11 07:18:35 +0000874% of previous frame, simply clear pixels for the next animation frame to
875% transparency according to the selected dispose method.
cristy3ed852e2009-09-05 21:47:34 +0000876%
877% The format of the OptimizeLayerFrames method is:
878%
cristy4c08aed2011-07-01 19:47:50 +0000879% Image *OptimizeLayerFrames(const Image *image,
880% const ImageLayerMethod method, ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000881%
882% A description of each parameter follows:
883%
884% o image: the image.
885%
anthony9d570022010-09-11 07:18:35 +0000886% o method: the layers technique to optimize with. Must be one of...
cristy4c08aed2011-07-01 19:47:50 +0000887% OptimizeImageLayer, or OptimizePlusLayer. The Plus form allows
888% the addition of extra 'zero delay' frames to clear pixels from
889% the previous frame, and the removal of frames that done change,
890% merging the delay times together.
cristy3ed852e2009-09-05 21:47:34 +0000891%
892% o exception: return any errors or warnings in this structure.
893%
894*/
895/*
anthony9d570022010-09-11 07:18:35 +0000896 Define a 'fake' dispose method where the frame is duplicated, (for
897 OptimizePlusLayer) with a extra zero time delay frame which does a
898 BackgroundDisposal to clear the pixels that need to be cleared.
cristy3ed852e2009-09-05 21:47:34 +0000899*/
900#define DupDispose ((DisposeType)9)
901/*
902 Another 'fake' dispose method used to removed frames that don't change.
903*/
904#define DelDispose ((DisposeType)8)
905
anthony9d570022010-09-11 07:18:35 +0000906#define DEBUG_OPT_FRAME 0
907
cristy3ed852e2009-09-05 21:47:34 +0000908static Image *OptimizeLayerFrames(const Image *image,
909 const ImageLayerMethod method, ExceptionInfo *exception)
910{
911 ExceptionInfo
912 *sans_exception;
913
914 Image
915 *prev_image,
916 *dup_image,
917 *bgnd_image,
918 *optimized_image;
919
920 RectangleInfo
921 try_bounds,
922 bgnd_bounds,
923 dup_bounds,
924 *bounds;
925
926 MagickBooleanType
927 add_frames,
928 try_cleared,
929 cleared;
930
931 DisposeType
932 *disposals;
933
934 register const Image
anthonyb6d08c52010-09-13 01:17:04 +0000935 *curr;
cristy3ed852e2009-09-05 21:47:34 +0000936
cristybb503372010-05-27 20:51:26 +0000937 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000938 i;
939
940 assert(image != (const Image *) NULL);
941 assert(image->signature == MagickSignature);
942 if (image->debug != MagickFalse)
943 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
944 assert(exception != (ExceptionInfo *) NULL);
945 assert(exception->signature == MagickSignature);
946 assert(method == OptimizeLayer ||
947 method == OptimizeImageLayer ||
948 method == OptimizePlusLayer);
949
950 /*
951 Are we allowed to add/remove frames from animation
952 */
953 add_frames=method == OptimizePlusLayer ? MagickTrue : MagickFalse;
954 /*
955 Ensure all the images are the same size
956 */
anthonyb6d08c52010-09-13 01:17:04 +0000957 curr=GetFirstImageInList(image);
958 for (; curr != (Image *) NULL; curr=GetNextImageInList(curr))
cristy3ed852e2009-09-05 21:47:34 +0000959 {
anthonyb6d08c52010-09-13 01:17:04 +0000960 if ((curr->columns != image->columns) || (curr->rows != image->rows))
cristy3ed852e2009-09-05 21:47:34 +0000961 ThrowImageException(OptionError,"ImagesAreNotTheSameSize");
962 /*
anthony9d570022010-09-11 07:18:35 +0000963 FUTURE: also check that image is also fully coalesced (full page)
964 Though as long as they are the same size it should not matter.
cristy3ed852e2009-09-05 21:47:34 +0000965 */
966 }
967 /*
anthony9d570022010-09-11 07:18:35 +0000968 Allocate memory (times 2 if we allow the use of frame duplications)
cristy3ed852e2009-09-05 21:47:34 +0000969 */
anthonyb6d08c52010-09-13 01:17:04 +0000970 curr=GetFirstImageInList(image);
cristy3ed852e2009-09-05 21:47:34 +0000971 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
anthonyb6d08c52010-09-13 01:17:04 +0000972 GetImageListLength(curr),(add_frames != MagickFalse ? 2UL : 1UL)*
cristy3ed852e2009-09-05 21:47:34 +0000973 sizeof(*bounds));
974 if (bounds == (RectangleInfo *) NULL)
975 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
976 disposals=(DisposeType *) AcquireQuantumMemory((size_t)
977 GetImageListLength(image),(add_frames != MagickFalse ? 2UL : 1UL)*
978 sizeof(*disposals));
979 if (disposals == (DisposeType *) NULL)
980 {
981 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
982 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
983 }
984 /*
985 Initialise Previous Image as fully transparent
986 */
anthonyb6d08c52010-09-13 01:17:04 +0000987 prev_image=CloneImage(curr,curr->page.width,curr->page.height,
cristy3ed852e2009-09-05 21:47:34 +0000988 MagickTrue,exception);
989 if (prev_image == (Image *) NULL)
990 {
991 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
992 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
993 return((Image *) NULL);
994 }
anthonyb6d08c52010-09-13 01:17:04 +0000995 prev_image->page=curr->page; /* ERROR: <-- should not be need, but is! */
cristy3ed852e2009-09-05 21:47:34 +0000996 prev_image->page.x=0;
997 prev_image->page.y=0;
998 prev_image->dispose=NoneDispose;
999
cristy4c08aed2011-07-01 19:47:50 +00001000 prev_image->background_color.alpha=(Quantum) TransparentAlpha;
cristyea1a8aa2011-10-20 13:24:06 +00001001 (void) SetImageBackgroundColor(prev_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001002 /*
1003 Figure out the area of overlay of the first frame
1004 No pixel could be cleared as all pixels are already cleared.
1005 */
anthony9d570022010-09-11 07:18:35 +00001006#if DEBUG_OPT_FRAME
1007 i=0;
cristy5acdd942011-05-27 19:45:39 +00001008 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
anthony9d570022010-09-11 07:18:35 +00001009#endif
cristy3ed852e2009-09-05 21:47:34 +00001010 disposals[0]=NoneDispose;
cristy8a9106f2011-07-05 14:39:26 +00001011 bounds[0]=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
anthony9d570022010-09-11 07:18:35 +00001012#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001013 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g\n\n",
cristy1e604812011-05-19 18:07:50 +00001014 (double) bounds[i].width,(double) bounds[i].height,
1015 (double) bounds[i].x,(double) bounds[i].y );
anthony9d570022010-09-11 07:18:35 +00001016#endif
cristy3ed852e2009-09-05 21:47:34 +00001017 /*
1018 Compute the bounding box of changes for each pair of images.
1019 */
1020 i=1;
1021 bgnd_image=(Image *)NULL;
1022 dup_image=(Image *)NULL;
1023 dup_bounds.width=0;
1024 dup_bounds.height=0;
1025 dup_bounds.x=0;
1026 dup_bounds.y=0;
anthonyb6d08c52010-09-13 01:17:04 +00001027 curr=GetNextImageInList(curr);
1028 for ( ; curr != (const Image *) NULL; curr=GetNextImageInList(curr))
cristy3ed852e2009-09-05 21:47:34 +00001029 {
anthony9d570022010-09-11 07:18:35 +00001030#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001031 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
anthony9d570022010-09-11 07:18:35 +00001032#endif
cristy3ed852e2009-09-05 21:47:34 +00001033 /*
1034 Assume none disposal is the best
1035 */
cristy8a9106f2011-07-05 14:39:26 +00001036 bounds[i]=CompareImagesBounds(curr->previous,curr,CompareAnyLayer,exception);
anthonyb6d08c52010-09-13 01:17:04 +00001037 cleared=IsBoundsCleared(curr->previous,curr,&bounds[i],exception);
cristy3ed852e2009-09-05 21:47:34 +00001038 disposals[i-1]=NoneDispose;
anthony9d570022010-09-11 07:18:35 +00001039#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001040 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g%s%s\n",
anthony9d570022010-09-11 07:18:35 +00001041 (double) bounds[i].width,(double) bounds[i].height,
1042 (double) bounds[i].x,(double) bounds[i].y,
1043 bounds[i].x < 0?" (unchanged)":"",
1044 cleared?" (pixels cleared)":"");
1045#endif
cristy3ed852e2009-09-05 21:47:34 +00001046 if ( bounds[i].x < 0 ) {
1047 /*
1048 Image frame is exactly the same as the previous frame!
1049 If not adding frames leave it to be cropped down to a null image.
1050 Otherwise mark previous image for deleted, transfering its crop bounds
1051 to the current image.
1052 */
1053 if ( add_frames && i>=2 ) {
1054 disposals[i-1]=DelDispose;
1055 disposals[i]=NoneDispose;
1056 bounds[i]=bounds[i-1];
1057 i++;
1058 continue;
1059 }
1060 }
1061 else
1062 {
1063 /*
1064 Compare a none disposal against a previous disposal
1065 */
cristy8a9106f2011-07-05 14:39:26 +00001066 try_bounds=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
anthonyb6d08c52010-09-13 01:17:04 +00001067 try_cleared=IsBoundsCleared(prev_image,curr,&try_bounds,exception);
anthony9d570022010-09-11 07:18:35 +00001068#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001069 (void) FormatLocaleFile(stderr, "test_prev: %.20gx%.20g%+.20g%+.20g%s\n",
anthony9d570022010-09-11 07:18:35 +00001070 (double) try_bounds.width,(double) try_bounds.height,
1071 (double) try_bounds.x,(double) try_bounds.y,
1072 try_cleared?" (pixels were cleared)":"");
1073#endif
cristy3ed852e2009-09-05 21:47:34 +00001074 if ( (!try_cleared && cleared ) ||
1075 try_bounds.width * try_bounds.height
1076 < bounds[i].width * bounds[i].height )
1077 {
1078 cleared=try_cleared;
1079 bounds[i]=try_bounds;
1080 disposals[i-1]=PreviousDispose;
anthony9d570022010-09-11 07:18:35 +00001081#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001082 (void) FormatLocaleFile(stderr, "previous: accepted\n");
anthony9d570022010-09-11 07:18:35 +00001083 } else {
cristy5acdd942011-05-27 19:45:39 +00001084 (void) FormatLocaleFile(stderr, "previous: rejected\n");
anthony9d570022010-09-11 07:18:35 +00001085#endif
cristy3ed852e2009-09-05 21:47:34 +00001086 }
1087
1088 /*
1089 If we are allowed lets try a complex frame duplication.
1090 It is useless if the previous image already clears pixels correctly.
1091 This method will always clear all the pixels that need to be cleared.
1092 */
anthony9d570022010-09-11 07:18:35 +00001093 dup_bounds.width=dup_bounds.height=0; /* no dup, no pixel added */
cristy3ed852e2009-09-05 21:47:34 +00001094 if ( add_frames )
1095 {
anthonyb6d08c52010-09-13 01:17:04 +00001096 dup_image=CloneImage(curr->previous,curr->previous->page.width,
1097 curr->previous->page.height,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001098 if (dup_image == (Image *) NULL)
1099 {
1100 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1101 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1102 prev_image=DestroyImage(prev_image);
1103 return((Image *) NULL);
1104 }
cristy8a9106f2011-07-05 14:39:26 +00001105 dup_bounds=CompareImagesBounds(dup_image,curr,CompareClearLayer,exception);
cristy7c3af952011-10-20 16:04:16 +00001106 ClearBounds(dup_image,&dup_bounds,exception);
cristy8a9106f2011-07-05 14:39:26 +00001107 try_bounds=CompareImagesBounds(dup_image,curr,CompareAnyLayer,exception);
cristy3ed852e2009-09-05 21:47:34 +00001108 if ( cleared ||
1109 dup_bounds.width*dup_bounds.height
1110 +try_bounds.width*try_bounds.height
1111 < bounds[i].width * bounds[i].height )
1112 {
1113 cleared=MagickFalse;
1114 bounds[i]=try_bounds;
1115 disposals[i-1]=DupDispose;
1116 /* to be finalised later, if found to be optimial */
1117 }
1118 else
1119 dup_bounds.width=dup_bounds.height=0;
1120 }
cristy3ed852e2009-09-05 21:47:34 +00001121 /*
1122 Now compare against a simple background disposal
1123 */
anthonyb6d08c52010-09-13 01:17:04 +00001124 bgnd_image=CloneImage(curr->previous,curr->previous->page.width,
1125 curr->previous->page.height,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001126 if (bgnd_image == (Image *) NULL)
1127 {
1128 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1129 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1130 prev_image=DestroyImage(prev_image);
anthony9d570022010-09-11 07:18:35 +00001131 if ( dup_image != (Image *) NULL)
1132 dup_image=DestroyImage(dup_image);
cristy3ed852e2009-09-05 21:47:34 +00001133 return((Image *) NULL);
1134 }
anthony9d570022010-09-11 07:18:35 +00001135 bgnd_bounds=bounds[i-1]; /* interum bounds of the previous image */
cristy7c3af952011-10-20 16:04:16 +00001136 ClearBounds(bgnd_image,&bgnd_bounds,exception);
cristy8a9106f2011-07-05 14:39:26 +00001137 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
anthonyb6d08c52010-09-13 01:17:04 +00001138 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
anthony9d570022010-09-11 07:18:35 +00001139#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001140 (void) FormatLocaleFile(stderr, "background: %s\n",
anthony9d570022010-09-11 07:18:35 +00001141 try_cleared?"(pixels cleared)":"");
1142#endif
cristy3ed852e2009-09-05 21:47:34 +00001143 if ( try_cleared )
1144 {
1145 /*
1146 Straight background disposal failed to clear pixels needed!
1147 Lets try expanding the disposal area of the previous frame, to
1148 include the pixels that are cleared. This guaranteed
1149 to work, though may not be the most optimized solution.
1150 */
cristy8a9106f2011-07-05 14:39:26 +00001151 try_bounds=CompareImagesBounds(curr->previous,curr,CompareClearLayer,exception);
anthony9d570022010-09-11 07:18:35 +00001152#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001153 (void) FormatLocaleFile(stderr, "expand_clear: %.20gx%.20g%+.20g%+.20g%s\n",
anthony9d570022010-09-11 07:18:35 +00001154 (double) try_bounds.width,(double) try_bounds.height,
1155 (double) try_bounds.x,(double) try_bounds.y,
1156 try_bounds.x<0?" (no expand nessary)":"");
1157#endif
cristy3ed852e2009-09-05 21:47:34 +00001158 if ( bgnd_bounds.x < 0 )
1159 bgnd_bounds = try_bounds;
1160 else
1161 {
anthony9d570022010-09-11 07:18:35 +00001162#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001163 (void) FormatLocaleFile(stderr, "expand_bgnd: %.20gx%.20g%+.20g%+.20g\n",
anthony9d570022010-09-11 07:18:35 +00001164 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1165 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1166#endif
cristy3ed852e2009-09-05 21:47:34 +00001167 if ( try_bounds.x < bgnd_bounds.x )
1168 {
1169 bgnd_bounds.width+= bgnd_bounds.x-try_bounds.x;
1170 if ( bgnd_bounds.width < try_bounds.width )
1171 bgnd_bounds.width = try_bounds.width;
1172 bgnd_bounds.x = try_bounds.x;
1173 }
1174 else
1175 {
1176 try_bounds.width += try_bounds.x - bgnd_bounds.x;
1177 if ( bgnd_bounds.width < try_bounds.width )
1178 bgnd_bounds.width = try_bounds.width;
1179 }
1180 if ( try_bounds.y < bgnd_bounds.y )
1181 {
1182 bgnd_bounds.height += bgnd_bounds.y - try_bounds.y;
1183 if ( bgnd_bounds.height < try_bounds.height )
1184 bgnd_bounds.height = try_bounds.height;
1185 bgnd_bounds.y = try_bounds.y;
1186 }
1187 else
1188 {
1189 try_bounds.height += try_bounds.y - bgnd_bounds.y;
1190 if ( bgnd_bounds.height < try_bounds.height )
1191 bgnd_bounds.height = try_bounds.height;
1192 }
anthony9d570022010-09-11 07:18:35 +00001193#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001194 (void) FormatLocaleFile(stderr, " to : %.20gx%.20g%+.20g%+.20g\n",
anthony9d570022010-09-11 07:18:35 +00001195 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1196 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1197#endif
cristy3ed852e2009-09-05 21:47:34 +00001198 }
cristy7c3af952011-10-20 16:04:16 +00001199 ClearBounds(bgnd_image,&bgnd_bounds,exception);
anthony9d570022010-09-11 07:18:35 +00001200#if DEBUG_OPT_FRAME
1201/* Something strange is happening with a specific animation
1202 * CompareAnyLayers (normal method) and CompareClearLayers returns the whole
1203 * image, which is not posibly correct! As verified by previous tests.
1204 * Something changed beyond the bgnd_bounds clearing. But without being able
1205 * to see, or writet he image at this point it is hard to tell what is wrong!
1206 * Only CompareOverlay seemed to return something sensible.
1207 */
cristy8a9106f2011-07-05 14:39:26 +00001208 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareClearLayer,exception);
cristy5acdd942011-05-27 19:45:39 +00001209 (void) FormatLocaleFile(stderr, "expand_ctst: %.20gx%.20g%+.20g%+.20g\n",
anthony9d570022010-09-11 07:18:35 +00001210 (double) try_bounds.width,(double) try_bounds.height,
1211 (double) try_bounds.x,(double) try_bounds.y );
cristy8a9106f2011-07-05 14:39:26 +00001212 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
anthonyb6d08c52010-09-13 01:17:04 +00001213 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
cristy5acdd942011-05-27 19:45:39 +00001214 (void) FormatLocaleFile(stderr, "expand_any : %.20gx%.20g%+.20g%+.20g%s\n",
anthony9d570022010-09-11 07:18:35 +00001215 (double) try_bounds.width,(double) try_bounds.height,
1216 (double) try_bounds.x,(double) try_bounds.y,
1217 try_cleared?" (pixels cleared)":"");
1218#endif
cristy8a9106f2011-07-05 14:39:26 +00001219 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareOverlayLayer,exception);
anthony9d570022010-09-11 07:18:35 +00001220#if DEBUG_OPT_FRAME
anthonyb6d08c52010-09-13 01:17:04 +00001221 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
cristy5acdd942011-05-27 19:45:39 +00001222 (void) FormatLocaleFile(stderr, "expand_test: %.20gx%.20g%+.20g%+.20g%s\n",
anthony9d570022010-09-11 07:18:35 +00001223 (double) try_bounds.width,(double) try_bounds.height,
1224 (double) try_bounds.x,(double) try_bounds.y,
1225 try_cleared?" (pixels cleared)":"");
1226#endif
cristy3ed852e2009-09-05 21:47:34 +00001227 }
1228 /*
1229 Test if this background dispose is smaller than any of the
1230 other methods we tryed before this (including duplicated frame)
1231 */
1232 if ( cleared ||
1233 bgnd_bounds.width*bgnd_bounds.height
1234 +try_bounds.width*try_bounds.height
1235 < bounds[i-1].width*bounds[i-1].height
1236 +dup_bounds.width*dup_bounds.height
1237 +bounds[i].width*bounds[i].height )
1238 {
1239 cleared=MagickFalse;
1240 bounds[i-1]=bgnd_bounds;
1241 bounds[i]=try_bounds;
1242 if ( disposals[i-1] == DupDispose )
1243 dup_image=DestroyImage(dup_image);
1244 disposals[i-1]=BackgroundDispose;
anthony9d570022010-09-11 07:18:35 +00001245#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001246 (void) FormatLocaleFile(stderr, "expand_bgnd: accepted\n");
anthony9d570022010-09-11 07:18:35 +00001247 } else {
cristy5acdd942011-05-27 19:45:39 +00001248 (void) FormatLocaleFile(stderr, "expand_bgnd: reject\n");
anthony9d570022010-09-11 07:18:35 +00001249#endif
cristy3ed852e2009-09-05 21:47:34 +00001250 }
1251 }
1252 /*
1253 Finalise choice of dispose, set new prev_image,
1254 and junk any extra images as appropriate,
1255 */
1256 if ( disposals[i-1] == DupDispose )
1257 {
1258 if (bgnd_image != (Image *) NULL)
1259 bgnd_image=DestroyImage(bgnd_image);
1260 prev_image=DestroyImage(prev_image);
1261 prev_image=dup_image, dup_image=(Image *) NULL;
1262 bounds[i+1]=bounds[i];
1263 bounds[i]=dup_bounds;
1264 disposals[i-1]=DupDispose;
1265 disposals[i]=BackgroundDispose;
1266 i++;
1267 }
1268 else
1269 {
anthony9d570022010-09-11 07:18:35 +00001270 if ( dup_image != (Image *) NULL)
1271 dup_image=DestroyImage(dup_image);
cristy3ed852e2009-09-05 21:47:34 +00001272 if ( disposals[i-1] != PreviousDispose )
1273 prev_image=DestroyImage(prev_image);
1274 if ( disposals[i-1] == BackgroundDispose )
1275 prev_image=bgnd_image, bgnd_image=(Image *)NULL;
anthony9d570022010-09-11 07:18:35 +00001276 if (bgnd_image != (Image *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001277 bgnd_image=DestroyImage(bgnd_image);
cristy3ed852e2009-09-05 21:47:34 +00001278 if ( disposals[i-1] == NoneDispose )
1279 {
anthonyb6d08c52010-09-13 01:17:04 +00001280 prev_image=CloneImage(curr->previous,curr->previous->page.width,
1281 curr->previous->page.height,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001282 if (prev_image == (Image *) NULL)
1283 {
1284 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1285 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1286 return((Image *) NULL);
1287 }
1288 }
anthony9d570022010-09-11 07:18:35 +00001289
cristy3ed852e2009-09-05 21:47:34 +00001290 }
anthony9d570022010-09-11 07:18:35 +00001291 assert(prev_image != (Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001292 disposals[i]=disposals[i-1];
anthony9d570022010-09-11 07:18:35 +00001293#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001294 (void) FormatLocaleFile(stderr, "final %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
anthony9d570022010-09-11 07:18:35 +00001295 (double) i-1,
cristy042ee782011-04-22 18:48:30 +00001296 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i-1]),
anthony9d570022010-09-11 07:18:35 +00001297 (double) bounds[i-1].width, (double) bounds[i-1].height,
1298 (double) bounds[i-1].x, (double) bounds[i-1].y );
1299#endif
1300#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001301 (void) FormatLocaleFile(stderr, "interum %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
anthony9d570022010-09-11 07:18:35 +00001302 (double) i,
cristy042ee782011-04-22 18:48:30 +00001303 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i]),
anthony9d570022010-09-11 07:18:35 +00001304 (double) bounds[i].width, (double) bounds[i].height,
1305 (double) bounds[i].x, (double) bounds[i].y );
cristy5acdd942011-05-27 19:45:39 +00001306 (void) FormatLocaleFile(stderr, "\n");
anthony9d570022010-09-11 07:18:35 +00001307#endif
cristy3ed852e2009-09-05 21:47:34 +00001308 i++;
1309 }
1310 prev_image=DestroyImage(prev_image);
1311 /*
1312 Optimize all images in sequence.
1313 */
1314 sans_exception=AcquireExceptionInfo();
1315 i=0;
anthonyb6d08c52010-09-13 01:17:04 +00001316 curr=GetFirstImageInList(image);
cristy3ed852e2009-09-05 21:47:34 +00001317 optimized_image=NewImageList();
anthonyb6d08c52010-09-13 01:17:04 +00001318 while ( curr != (const Image *) NULL )
cristy3ed852e2009-09-05 21:47:34 +00001319 {
anthonyb6d08c52010-09-13 01:17:04 +00001320 prev_image=CloneImage(curr,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001321 if (prev_image == (Image *) NULL)
1322 break;
1323 if ( disposals[i] == DelDispose ) {
cristybb503372010-05-27 20:51:26 +00001324 size_t time = 0;
cristy3ed852e2009-09-05 21:47:34 +00001325 while ( disposals[i] == DelDispose ) {
anthonyb6d08c52010-09-13 01:17:04 +00001326 time += curr->delay*1000/curr->ticks_per_second;
1327 curr=GetNextImageInList(curr);
cristy3ed852e2009-09-05 21:47:34 +00001328 i++;
1329 }
anthonyb6d08c52010-09-13 01:17:04 +00001330 time += curr->delay*1000/curr->ticks_per_second;
cristy3ed852e2009-09-05 21:47:34 +00001331 prev_image->ticks_per_second = 100L;
1332 prev_image->delay = time*prev_image->ticks_per_second/1000;
1333 }
1334 bgnd_image=CropImage(prev_image,&bounds[i],sans_exception);
1335 prev_image=DestroyImage(prev_image);
1336 if (bgnd_image == (Image *) NULL)
1337 break;
1338 bgnd_image->dispose=disposals[i];
1339 if ( disposals[i] == DupDispose ) {
1340 bgnd_image->delay=0;
1341 bgnd_image->dispose=NoneDispose;
1342 }
1343 else
anthonyb6d08c52010-09-13 01:17:04 +00001344 curr=GetNextImageInList(curr);
cristy3ed852e2009-09-05 21:47:34 +00001345 AppendImageToList(&optimized_image,bgnd_image);
1346 i++;
1347 }
1348 sans_exception=DestroyExceptionInfo(sans_exception);
1349 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1350 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
anthonyb6d08c52010-09-13 01:17:04 +00001351 if (curr != (Image *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001352 {
1353 optimized_image=DestroyImageList(optimized_image);
1354 return((Image *) NULL);
1355 }
1356 return(GetFirstImageInList(optimized_image));
1357}
1358
1359/*
1360%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1361% %
1362% %
1363% %
1364% O p t i m i z e I m a g e L a y e r s %
1365% %
1366% %
1367% %
1368%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1369%
1370% OptimizeImageLayers() compares each image the GIF disposed forms of the
1371% previous image in the sequence. From this it attempts to select the
1372% smallest cropped image to replace each frame, while preserving the results
1373% of the GIF animation.
1374%
1375% The format of the OptimizeImageLayers method is:
1376%
1377% Image *OptimizeImageLayers(const Image *image,
1378% ExceptionInfo *exception)
1379%
1380% A description of each parameter follows:
1381%
1382% o image: the image.
1383%
1384% o exception: return any errors or warnings in this structure.
1385%
1386*/
1387MagickExport Image *OptimizeImageLayers(const Image *image,
1388 ExceptionInfo *exception)
1389{
1390 return(OptimizeLayerFrames(image,OptimizeImageLayer,exception));
1391}
1392
1393/*
1394%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1395% %
1396% %
1397% %
1398% O p t i m i z e P l u s I m a g e L a y e r s %
1399% %
1400% %
1401% %
1402%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1403%
1404% OptimizeImagePlusLayers() is exactly as OptimizeImageLayers(), but may
1405% also add or even remove extra frames in the animation, if it improves
1406% the total number of pixels in the resulting GIF animation.
1407%
1408% The format of the OptimizePlusImageLayers method is:
1409%
1410% Image *OptimizePlusImageLayers(const Image *image,
1411% ExceptionInfo *exception)
1412%
1413% A description of each parameter follows:
1414%
1415% o image: the image.
1416%
1417% o exception: return any errors or warnings in this structure.
1418%
1419*/
1420MagickExport Image *OptimizePlusImageLayers(const Image *image,
1421 ExceptionInfo *exception)
1422{
1423 return OptimizeLayerFrames(image, OptimizePlusLayer, exception);
1424}
1425
1426/*
1427%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1428% %
1429% %
1430% %
1431% 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 %
1432% %
1433% %
1434% %
1435%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1436%
1437% OptimizeImageTransparency() takes a frame optimized GIF animation, and
1438% compares the overlayed pixels against the disposal image resulting from all
1439% the previous frames in the animation. Any pixel that does not change the
1440% disposal image (and thus does not effect the outcome of an overlay) is made
1441% transparent.
1442%
1443% WARNING: This modifies the current images directly, rather than generate
1444% a new image sequence.
1445%
1446% The format of the OptimizeImageTransperency method is:
1447%
1448% void OptimizeImageTransperency(Image *image,ExceptionInfo *exception)
1449%
1450% A description of each parameter follows:
1451%
1452% o image: the image sequence
1453%
1454% o exception: return any errors or warnings in this structure.
1455%
1456*/
1457MagickExport void OptimizeImageTransparency(const Image *image,
1458 ExceptionInfo *exception)
1459{
1460 Image
1461 *dispose_image;
1462
1463 register Image
1464 *next;
1465
1466 /*
1467 Run the image through the animation sequence
1468 */
1469 assert(image != (Image *) NULL);
1470 assert(image->signature == MagickSignature);
1471 if (image->debug != MagickFalse)
1472 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1473 assert(exception != (ExceptionInfo *) NULL);
1474 assert(exception->signature == MagickSignature);
1475 next=GetFirstImageInList(image);
1476 dispose_image=CloneImage(next,next->page.width,next->page.height,
1477 MagickTrue,exception);
1478 if (dispose_image == (Image *) NULL)
1479 return;
1480 dispose_image->page=next->page;
1481 dispose_image->page.x=0;
1482 dispose_image->page.y=0;
1483 dispose_image->dispose=NoneDispose;
cristy4c08aed2011-07-01 19:47:50 +00001484 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
cristyea1a8aa2011-10-20 13:24:06 +00001485 (void) SetImageBackgroundColor(dispose_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001486
1487 while ( next != (Image *) NULL )
1488 {
1489 Image
1490 *current_image;
1491
1492 /*
1493 Overlay this frame's image over the previous disposal image
1494 */
1495 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
1496 if (current_image == (Image *) NULL)
1497 {
1498 dispose_image=DestroyImage(dispose_image);
1499 return;
1500 }
cristyfeb3e962012-03-29 17:25:55 +00001501 (void) CompositeImage(current_image,next,next->matte != MagickFalse ?
cristy39172402012-03-30 13:04:39 +00001502 OverCompositeOp : CopyCompositeOp,MagickTrue,next->page.x,next->page.y,
cristye941a752011-10-15 01:52:48 +00001503 exception);
cristy3ed852e2009-09-05 21:47:34 +00001504 /*
1505 At this point the image would be displayed, for the delay period
1506 **
1507 Work out the disposal of the previous image
1508 */
1509 if (next->dispose == BackgroundDispose)
1510 {
1511 RectangleInfo
1512 bounds=next->page;
1513
1514 bounds.width=next->columns;
1515 bounds.height=next->rows;
1516 if (bounds.x < 0)
1517 {
1518 bounds.width+=bounds.x;
1519 bounds.x=0;
1520 }
cristybb503372010-05-27 20:51:26 +00001521 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
cristy3ed852e2009-09-05 21:47:34 +00001522 bounds.width=current_image->columns-bounds.x;
1523 if (bounds.y < 0)
1524 {
1525 bounds.height+=bounds.y;
1526 bounds.y=0;
1527 }
cristybb503372010-05-27 20:51:26 +00001528 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
cristy3ed852e2009-09-05 21:47:34 +00001529 bounds.height=current_image->rows-bounds.y;
cristy7c3af952011-10-20 16:04:16 +00001530 ClearBounds(current_image, &bounds,exception);
cristy3ed852e2009-09-05 21:47:34 +00001531 }
1532 if (next->dispose != PreviousDispose)
1533 {
1534 dispose_image=DestroyImage(dispose_image);
1535 dispose_image=current_image;
1536 }
1537 else
1538 current_image=DestroyImage(current_image);
1539
1540 /*
1541 Optimize Transparency of the next frame (if present)
1542 */
1543 next=GetNextImageInList(next);
cristyfeb3e962012-03-29 17:25:55 +00001544 if (next != (Image *) NULL) {
1545 (void) CompositeImage(next,dispose_image,ChangeMaskCompositeOp,
cristy39172402012-03-30 13:04:39 +00001546 MagickTrue,-(next->page.x),-(next->page.y),exception);
cristy3ed852e2009-09-05 21:47:34 +00001547 }
1548 }
1549 dispose_image=DestroyImage(dispose_image);
1550 return;
1551}
1552
1553/*
1554%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1555% %
1556% %
1557% %
1558% R e m o v e D u p l i c a t e L a y e r s %
1559% %
1560% %
1561% %
1562%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1563%
1564% RemoveDuplicateLayers() removes any image that is exactly the same as the
1565% next image in the given image list. Image size and virtual canvas offset
1566% must also match, though not the virtual canvas size itself.
1567%
1568% No check is made with regards to image disposal setting, though it is the
1569% dispose setting of later image that is kept. Also any time delays are also
1570% added together. As such coalesced image animations should still produce the
1571% same result, though with duplicte frames merged into a single frame.
1572%
1573% The format of the RemoveDuplicateLayers method is:
1574%
1575% void RemoveDuplicateLayers(Image **image, ExceptionInfo *exception)
1576%
1577% A description of each parameter follows:
1578%
1579% o images: the image list
1580%
1581% o exception: return any errors or warnings in this structure.
1582%
1583*/
1584MagickExport void RemoveDuplicateLayers(Image **images,
1585 ExceptionInfo *exception)
1586{
1587 register Image
1588 *curr,
1589 *next;
1590
1591 RectangleInfo
1592 bounds;
1593
1594 assert((*images) != (const Image *) NULL);
1595 assert((*images)->signature == MagickSignature);
1596 if ((*images)->debug != MagickFalse)
1597 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1598 assert(exception != (ExceptionInfo *) NULL);
1599 assert(exception->signature == MagickSignature);
1600
1601 curr=GetFirstImageInList(*images);
1602 for (; (next=GetNextImageInList(curr)) != (Image *) NULL; curr=next)
1603 {
1604 if ( curr->columns != next->columns || curr->rows != next->rows
1605 || curr->page.x != next->page.x || curr->page.y != next->page.y )
1606 continue;
cristy8a9106f2011-07-05 14:39:26 +00001607 bounds=CompareImagesBounds(curr,next,CompareAnyLayer,exception);
cristy3ed852e2009-09-05 21:47:34 +00001608 if ( bounds.x < 0 ) {
1609 /*
1610 the two images are the same, merge time delays and delete one.
1611 */
cristybb503372010-05-27 20:51:26 +00001612 size_t time;
cristy3ed852e2009-09-05 21:47:34 +00001613 time = curr->delay*1000/curr->ticks_per_second;
1614 time += next->delay*1000/next->ticks_per_second;
1615 next->ticks_per_second = 100L;
1616 next->delay = time*curr->ticks_per_second/1000;
1617 next->iterations = curr->iterations;
1618 *images = curr;
1619 (void) DeleteImageFromList(images);
1620 }
1621 }
1622 *images = GetFirstImageInList(*images);
1623}
1624
1625/*
1626%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1627% %
1628% %
1629% %
1630% R e m o v e Z e r o D e l a y L a y e r s %
1631% %
1632% %
1633% %
1634%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1635%
1636% RemoveZeroDelayLayers() removes any image that as a zero delay time. Such
1637% images generally represent intermediate or partial updates in GIF
1638% animations used for file optimization. They are not ment to be displayed
1639% to users of the animation. Viewable images in an animation should have a
1640% time delay of 3 or more centi-seconds (hundredths of a second).
1641%
1642% However if all the frames have a zero time delay, then either the animation
1643% is as yet incomplete, or it is not a GIF animation. This a non-sensible
1644% situation, so no image will be removed and a 'Zero Time Animation' warning
1645% (exception) given.
1646%
1647% No warning will be given if no image was removed because all images had an
1648% appropriate non-zero time delay set.
1649%
1650% Due to the special requirements of GIF disposal handling, GIF animations
1651% should be coalesced first, before calling this function, though that is not
1652% a requirement.
1653%
1654% The format of the RemoveZeroDelayLayers method is:
1655%
1656% void RemoveZeroDelayLayers(Image **image, ExceptionInfo *exception)
1657%
1658% A description of each parameter follows:
1659%
1660% o images: the image list
1661%
1662% o exception: return any errors or warnings in this structure.
1663%
1664*/
1665MagickExport void RemoveZeroDelayLayers(Image **images,
1666 ExceptionInfo *exception)
1667{
1668 Image
1669 *i;
1670
1671 assert((*images) != (const Image *) NULL);
1672 assert((*images)->signature == MagickSignature);
1673 if ((*images)->debug != MagickFalse)
1674 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1675 assert(exception != (ExceptionInfo *) NULL);
1676 assert(exception->signature == MagickSignature);
1677
1678 i=GetFirstImageInList(*images);
1679 for ( ; i != (Image *) NULL; i=GetNextImageInList(i))
1680 if ( i->delay != 0L ) break;
1681 if ( i == (Image *) NULL ) {
1682 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
anthonye5b39652012-04-21 05:37:29 +00001683 "ZeroTimeAnimation","'%s'",GetFirstImageInList(*images)->filename);
cristy3ed852e2009-09-05 21:47:34 +00001684 return;
1685 }
1686 i=GetFirstImageInList(*images);
1687 while ( i != (Image *) NULL )
1688 {
1689 if ( i->delay == 0L ) {
1690 (void) DeleteImageFromList(&i);
1691 *images=i;
1692 }
1693 else
1694 i=GetNextImageInList(i);
1695 }
1696 *images=GetFirstImageInList(*images);
1697}
1698
1699/*
1700%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1701% %
1702% %
1703% %
1704% C o m p o s i t e L a y e r s %
1705% %
1706% %
1707% %
1708%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1709%
anthonye5840b22012-03-17 12:22:34 +00001710% CompositeLayers() compose the source image sequence over the destination
1711% image sequence, starting with the current image in both lists.
cristy3ed852e2009-09-05 21:47:34 +00001712%
anthonye5840b22012-03-17 12:22:34 +00001713% Each layer from the two image lists are composted together until the end of
1714% one of the image lists is reached. The offset of each composition is also
1715% adjusted to match the virtual canvas offsets of each layer. As such the
1716% given offset is relative to the virtual canvas, and not the actual image.
cristy3ed852e2009-09-05 21:47:34 +00001717%
anthonye5840b22012-03-17 12:22:34 +00001718% Composition uses given x and y offsets, as the 'origin' location of the
1719% source images virtual canvas (not the real image) allowing you to compose a
1720% list of 'layer images' into the destiantioni images. This makes it well
1721% sutiable for directly composing 'Clears Frame Animations' or 'Coaleased
1722% Animations' onto a static or other 'Coaleased Animation' destination image
1723% list. GIF disposal handling is not looked at.
cristy3ed852e2009-09-05 21:47:34 +00001724%
anthonye5840b22012-03-17 12:22:34 +00001725% Special case:- If one of the image sequences is the last image (just a
1726% single image remaining), that image is repeatally composed with all the
1727% images in the other image list. Either the source or destination lists may
1728% be the single image, for this situation.
cristy3ed852e2009-09-05 21:47:34 +00001729%
anthonye5840b22012-03-17 12:22:34 +00001730% In the case of a single destination image (or last image given), that image
1731% will ve cloned to match the number of images remaining in the source image
1732% list.
1733%
1734% This is equivelent to the "-layer Composite" Shell API operator.
1735%
cristy3ed852e2009-09-05 21:47:34 +00001736%
1737% The format of the CompositeLayers method is:
1738%
anthonye5840b22012-03-17 12:22:34 +00001739% void CompositeLayers(Image *destination, const CompositeOperator
1740% compose, Image *source, const ssize_t x_offset, const ssize_t y_offset,
1741% ExceptionInfo *exception);
cristy3ed852e2009-09-05 21:47:34 +00001742%
1743% A description of each parameter follows:
1744%
1745% o destination: the destination images and results
1746%
1747% o source: source image(s) for the layer composition
1748%
1749% o compose, x_offset, y_offset: arguments passed on to CompositeImages()
1750%
1751% o exception: return any errors or warnings in this structure.
1752%
1753*/
cristye941a752011-10-15 01:52:48 +00001754
cristy3ed852e2009-09-05 21:47:34 +00001755static inline void CompositeCanvas(Image *destination,
cristye941a752011-10-15 01:52:48 +00001756 const CompositeOperator compose,Image *source,ssize_t x_offset,
1757 ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001758{
cristy9d314ff2011-03-09 01:30:28 +00001759 x_offset+=source->page.x-destination->page.x;
1760 y_offset+=source->page.y-destination->page.y;
cristy39172402012-03-30 13:04:39 +00001761 (void) CompositeImage(destination,source,compose,MagickTrue,x_offset,
cristyfeb3e962012-03-29 17:25:55 +00001762 y_offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00001763}
1764
1765MagickExport void CompositeLayers(Image *destination,
cristy9d314ff2011-03-09 01:30:28 +00001766 const CompositeOperator compose, Image *source,const ssize_t x_offset,
1767 const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001768{
1769 assert(destination != (Image *) NULL);
1770 assert(destination->signature == MagickSignature);
1771 assert(source != (Image *) NULL);
1772 assert(source->signature == MagickSignature);
1773 assert(exception != (ExceptionInfo *) NULL);
1774 assert(exception->signature == MagickSignature);
1775 if (source->debug != MagickFalse || destination->debug != MagickFalse)
1776 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s - %s",
cristye941a752011-10-15 01:52:48 +00001777 source->filename, destination->filename);
cristy3ed852e2009-09-05 21:47:34 +00001778
1779 /*
1780 Overlay single source image over destation image/list
1781 */
anthonye5840b22012-03-17 12:22:34 +00001782 if ( source->next == (Image *) NULL )
cristy3ed852e2009-09-05 21:47:34 +00001783 while ( destination != (Image *) NULL )
1784 {
cristye941a752011-10-15 01:52:48 +00001785 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1786 exception);
cristy3ed852e2009-09-05 21:47:34 +00001787 destination=GetNextImageInList(destination);
1788 }
1789
1790 /*
anthonye5840b22012-03-17 12:22:34 +00001791 Overlay source image list over single destination.
1792 Multiple clones of destination image are created to match source list.
cristy3ed852e2009-09-05 21:47:34 +00001793 Original Destination image becomes first image of generated list.
1794 As such the image list pointer does not require any change in caller.
1795 Some animation attributes however also needs coping in this case.
1796 */
anthonye5840b22012-03-17 12:22:34 +00001797 else if ( destination->next == (Image *) NULL )
cristy3ed852e2009-09-05 21:47:34 +00001798 {
1799 Image *dest = CloneImage(destination,0,0,MagickTrue,exception);
1800
cristye941a752011-10-15 01:52:48 +00001801 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1802 exception);
cristy3ed852e2009-09-05 21:47:34 +00001803 /* copy source image attributes ? */
anthonyfe2045f2011-04-14 02:57:12 +00001804 if ( source->next != (Image *) NULL )
1805 {
1806 destination->delay = source->delay;
1807 destination->iterations = source->iterations;
1808 }
cristy3ed852e2009-09-05 21:47:34 +00001809 source=GetNextImageInList(source);
1810
1811 while ( source != (Image *) NULL )
1812 {
1813 AppendImageToList(&destination,
1814 CloneImage(dest,0,0,MagickTrue,exception));
1815 destination=GetLastImageInList(destination);
1816
cristye941a752011-10-15 01:52:48 +00001817 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1818 exception);
cristy3ed852e2009-09-05 21:47:34 +00001819 destination->delay = source->delay;
1820 destination->iterations = source->iterations;
1821 source=GetNextImageInList(source);
1822 }
1823 dest=DestroyImage(dest);
1824 }
1825
1826 /*
1827 Overlay a source image list over a destination image list
1828 until either list runs out of images. (Does not repeat)
1829 */
1830 else
1831 while ( source != (Image *) NULL && destination != (Image *) NULL )
1832 {
cristye941a752011-10-15 01:52:48 +00001833 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1834 exception);
cristy3ed852e2009-09-05 21:47:34 +00001835 source=GetNextImageInList(source);
1836 destination=GetNextImageInList(destination);
1837 }
1838}
1839
1840/*
1841%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1842% %
1843% %
1844% %
1845% M e r g e I m a g e L a y e r s %
1846% %
1847% %
1848% %
1849%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1850%
1851% MergeImageLayers() composes all the image layers from the current given
1852% image onward to produce a single image of the merged layers.
1853%
1854% The inital canvas's size depends on the given ImageLayerMethod, and is
1855% initialized using the first images background color. The images
1856% are then compositied onto that image in sequence using the given
1857% composition that has been assigned to each individual image.
1858%
1859% The format of the MergeImageLayers is:
1860%
1861% Image *MergeImageLayers(const Image *image,
1862% const ImageLayerMethod method, ExceptionInfo *exception)
1863%
1864% A description of each parameter follows:
1865%
1866% o image: the image list to be composited together
1867%
1868% o method: the method of selecting the size of the initial canvas.
1869%
1870% MergeLayer: Merge all layers onto a canvas just large enough
1871% to hold all the actual images. The virtual canvas of the
1872% first image is preserved but otherwise ignored.
1873%
1874% FlattenLayer: Use the virtual canvas size of first image.
1875% Images which fall outside this canvas is clipped.
1876% This can be used to 'fill out' a given virtual canvas.
1877%
1878% MosaicLayer: Start with the virtual canvas of the first image,
1879% enlarging left and right edges to contain all images.
1880% Images with negative offsets will be clipped.
1881%
1882% TrimBoundsLayer: Determine the overall bounds of all the image
1883% layers just as in "MergeLayer", then adjust the the canvas
1884% and offsets to be relative to those bounds, without overlaying
1885% the images.
1886%
1887% WARNING: a new image is not returned, the original image
1888% sequence page data is modified instead.
1889%
1890% o exception: return any errors or warnings in this structure.
1891%
1892*/
cristye941a752011-10-15 01:52:48 +00001893MagickExport Image *MergeImageLayers(Image *image,const ImageLayerMethod method,
1894 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001895{
1896#define MergeLayersTag "Merge/Layers"
1897
1898 Image
1899 *canvas;
1900
1901 MagickBooleanType
1902 proceed;
1903
cristy3ed852e2009-09-05 21:47:34 +00001904 RectangleInfo
1905 page;
1906
cristy3ed852e2009-09-05 21:47:34 +00001907 register const Image
1908 *next;
1909
cristybb503372010-05-27 20:51:26 +00001910 size_t
cristycee97112010-05-28 00:44:52 +00001911 number_images,
1912 height,
1913 width;
1914
1915 ssize_t
1916 scene;
cristy3ed852e2009-09-05 21:47:34 +00001917
1918 assert(image != (Image *) NULL);
1919 assert(image->signature == MagickSignature);
1920 if (image->debug != MagickFalse)
1921 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1922 assert(exception != (ExceptionInfo *) NULL);
1923 assert(exception->signature == MagickSignature);
1924 /*
1925 Determine canvas image size, and its virtual canvas size and offset
1926 */
1927 page=image->page;
1928 width=image->columns;
1929 height=image->rows;
1930 switch (method)
1931 {
1932 case TrimBoundsLayer:
1933 case MergeLayer:
1934 default:
1935 {
cristy7fcdfd02011-11-20 03:36:37 +00001936 next=GetNextImageInList(image);
1937 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
1938 {
1939 if (page.x > next->page.x)
1940 {
1941 width+=page.x-next->page.x;
1942 page.x=next->page.x;
1943 }
1944 if (page.y > next->page.y)
1945 {
1946 height+=page.y-next->page.y;
1947 page.y=next->page.y;
1948 }
1949 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns-page.x))
1950 width=(size_t) next->page.x+(ssize_t)next->columns-page.x;
1951 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows-page.y))
1952 height=(size_t) next->page.y+(ssize_t) next->rows-page.y;
cristy3ed852e2009-09-05 21:47:34 +00001953 }
1954 break;
1955 }
1956 case FlattenLayer:
1957 {
cristy7fcdfd02011-11-20 03:36:37 +00001958 if (page.width > 0)
cristy3ed852e2009-09-05 21:47:34 +00001959 width=page.width;
cristy7fcdfd02011-11-20 03:36:37 +00001960 if (page.height > 0)
cristy3ed852e2009-09-05 21:47:34 +00001961 height=page.height;
1962 page.x=0;
1963 page.y=0;
1964 break;
1965 }
1966 case MosaicLayer:
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;
cristy7fcdfd02011-11-20 03:36:37 +00001972 for (next=image; next != (Image *) NULL; next=GetNextImageInList(next))
1973 {
1974 if (method == MosaicLayer)
1975 {
1976 page.x=next->page.x;
1977 page.y=next->page.y;
1978 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns))
1979 width=(size_t) next->page.x+next->columns;
1980 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows))
1981 height=(size_t) next->page.y+next->rows;
1982 }
cristy3ed852e2009-09-05 21:47:34 +00001983 }
1984 page.width=width;
1985 page.height=height;
1986 page.x=0;
1987 page.y=0;
1988 }
1989 break;
1990 }
cristy3ed852e2009-09-05 21:47:34 +00001991 /*
cristy7fcdfd02011-11-20 03:36:37 +00001992 Set virtual canvas size if not defined.
cristy3ed852e2009-09-05 21:47:34 +00001993 */
cristy7fcdfd02011-11-20 03:36:37 +00001994 if (page.width == 0)
1995 page.width=page.x < 0 ? width : width+page.x;
1996 if (page.height == 0)
1997 page.height=page.y < 0 ? height : height+page.y;
1998 /*
1999 Handle "TrimBoundsLayer" method separately to normal 'layer merge'.
2000 */
2001 if (method == TrimBoundsLayer)
cristy3ed852e2009-09-05 21:47:34 +00002002 {
cristy7fcdfd02011-11-20 03:36:37 +00002003 number_images=GetImageListLength(image);
2004 for (scene=0; scene < (ssize_t) number_images; scene++)
2005 {
2006 image->page.x-=page.x;
2007 image->page.y-=page.y;
2008 image->page.width=width;
2009 image->page.height=height;
2010 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2011 number_images);
2012 if (proceed == MagickFalse)
2013 break;
2014 image=GetNextImageInList(image);
cristyee534e52011-11-20 03:41:17 +00002015 if (image == (Image *) NULL)
2016 break;
cristy7fcdfd02011-11-20 03:36:37 +00002017 }
2018 return((Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002019 }
cristy3ed852e2009-09-05 21:47:34 +00002020 /*
2021 Create canvas size of width and height, and background color.
2022 */
2023 canvas=CloneImage(image,width,height,MagickTrue,exception);
2024 if (canvas == (Image *) NULL)
2025 return((Image *) NULL);
cristyea1a8aa2011-10-20 13:24:06 +00002026 (void) SetImageBackgroundColor(canvas,exception);
cristy3ed852e2009-09-05 21:47:34 +00002027 canvas->page=page;
2028 canvas->dispose=UndefinedDispose;
cristy3ed852e2009-09-05 21:47:34 +00002029 /*
2030 Compose images onto canvas, with progress monitor
2031 */
2032 number_images=GetImageListLength(image);
cristybb503372010-05-27 20:51:26 +00002033 for (scene=0; scene < (ssize_t) number_images; scene++)
cristy3ed852e2009-09-05 21:47:34 +00002034 {
cristy40e1d032012-03-30 13:26:42 +00002035 (void) CompositeImage(canvas,image,image->compose,MagickTrue,image->page.x-
cristye941a752011-10-15 01:52:48 +00002036 canvas->page.x,image->page.y-canvas->page.y,exception);
cristycee97112010-05-28 00:44:52 +00002037 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2038 number_images);
cristy3ed852e2009-09-05 21:47:34 +00002039 if (proceed == MagickFalse)
2040 break;
2041 image=GetNextImageInList(image);
cristy7fcdfd02011-11-20 03:36:37 +00002042 if (image == (Image *) NULL)
2043 break;
cristy3ed852e2009-09-05 21:47:34 +00002044 }
2045 return(canvas);
2046}
2047