blob: e9969742b555536e4ed4b3656d556f82dc30d573 [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% %
cristyfe676ee2013-11-18 13:03:38 +000019% Copyright 1999-2014 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"
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;
cristy8a46d822012-08-28 23:32:39 +0000108 if (image->alpha_trait != BlendPixelTrait)
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
cristyc47d1f82009-11-26 01:44:43 +0000116 *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 {
cristy4c08aed2011-07-01 19:47:50 +0000188 if ((GetPixelAlpha(image1,p) <= (Quantum) (QuantumRange/2)) &&
189 (GetPixelAlpha(image1,q) > (Quantum) (QuantumRange/2)))
cristy3ed852e2009-09-05 21:47:34 +0000190 break;
cristyed231572011-07-14 02:18:59 +0000191 p+=GetPixelChannels(image1);
cristy3ed852e2009-09-05 21:47:34 +0000192 q++;
193 }
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);
246 assert(image->signature == MagickSignature);
247 if (image->debug != MagickFalse)
248 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
249 assert(exception != (ExceptionInfo *) NULL);
250 assert(exception->signature == MagickSignature);
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);
cristy3d1c2de2012-06-07 00:50:22 +0000271 (void) SetImageBackgroundColor(coalesce_image,exception);
cristy8a46d822012-08-28 23:32:39 +0000272 coalesce_image->alpha_trait=next->alpha_trait;
cristy3ed852e2009-09-05 21:47:34 +0000273 coalesce_image->page=bounds;
274 coalesce_image->dispose=NoneDispose;
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);
cristyed6987e2013-02-19 14:10:05 +0000330 (void) CompositeImage(coalesce_image,next,
331 next->alpha_trait == BlendPixelTrait ? OverCompositeOp : CopyCompositeOp,
332 MagickTrue,next->page.x,next->page.y,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 */
cristyf9aa1732013-02-27 19:55:06 +0000340 if (IsBoundsCleared(previous,coalesce_image,&bounds,exception) != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000341 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
cristyf9aa1732013-02-27 19:55:06 +0000362% appear after the GIF dispose method of that frame has been applied. That is
363% it returned the appearance of each frame before the next is overlaid.
cristy3ed852e2009-09-05 21:47:34 +0000364%
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
cristyf9aa1732013-02-27 19:55:06 +0000382 RectangleInfo
383 bounds;
384
cristy3ed852e2009-09-05 21:47:34 +0000385 register Image
cristyfeb3e962012-03-29 17:25:55 +0000386 *image,
387 *next;
anthonyb6d08c52010-09-13 01:17:04 +0000388
cristy3ed852e2009-09-05 21:47:34 +0000389 /*
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 }
cristyed6987e2013-02-19 14:10:05 +0000425 (void) CompositeImage(current_image,next,
426 next->alpha_trait == BlendPixelTrait ? OverCompositeOp : CopyCompositeOp,
427 MagickTrue,next->page.x,next->page.y,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;
cristyf9aa1732013-02-27 19:55:06 +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%
cristya0417062012-09-02 23:34:56 +0000509% MagickBooleanType *ComparePixels(const LayerMethod 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
cristya0417062012-09-02 23:34:56 +0000521static MagickBooleanType ComparePixels(const LayerMethod method,
cristy4c08aed2011-07-01 19:47:50 +0000522 const PixelInfo *p,const PixelInfo *q)
cristy3ed852e2009-09-05 21:47:34 +0000523{
cristya19f1d72012-08-07 18:24:38 +0000524 double
cristy3ed852e2009-09-05 21:47:34 +0000525 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
cristy8a46d822012-08-28 23:32:39 +0000534 o1 = (p->alpha_trait == BlendPixelTrait) ? p->alpha : OpaqueAlpha;
535 o2 = (q->alpha_trait == BlendPixelTrait) ? q->alpha : OpaqueAlpha;
cristy3ed852e2009-09-05 21:47:34 +0000536 /*
cristyed6987e2013-02-19 14:10:05 +0000537 Pixel goes from opaque to transprency.
cristy3ed852e2009-09-05 21:47:34 +0000538 */
539 if (method == CompareClearLayer)
cristya19f1d72012-08-07 18:24:38 +0000540 return((MagickBooleanType) ( (o1 <= ((double) QuantumRange/2.0)) &&
541 (o2 > ((double) QuantumRange/2.0)) ) );
cristy3ed852e2009-09-05 21:47:34 +0000542 /*
cristyed6987e2013-02-19 14:10:05 +0000543 Overlay would change first pixel by second.
cristy3ed852e2009-09-05 21:47:34 +0000544 */
545 if (method == CompareOverlayLayer)
546 {
cristya19f1d72012-08-07 18:24:38 +0000547 if (o2 > ((double) QuantumRange/2.0))
cristy3ed852e2009-09-05 21:47:34 +0000548 return MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +0000549 return((MagickBooleanType) (IsFuzzyEquivalencePixelInfo(p,q) == MagickFalse));
cristy3ed852e2009-09-05 21:47:34 +0000550 }
551 return(MagickFalse);
552}
553
554
555/*
556%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
557% %
558% %
559% %
560+ C o m p a r e I m a g e B o u n d s %
561% %
562% %
563% %
564%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
565%
cristy8a9106f2011-07-05 14:39:26 +0000566% CompareImagesBounds() Given two images return the smallest rectangular area
cristy3ed852e2009-09-05 21:47:34 +0000567% by which the two images differ, accourding to the given 'Compare...'
568% layer method.
569%
570% This currently only used internally in this module, but may eventually
571% be used by other modules.
572%
cristy8a9106f2011-07-05 14:39:26 +0000573% The format of the CompareImagesBounds method is:
cristy3ed852e2009-09-05 21:47:34 +0000574%
cristya0417062012-09-02 23:34:56 +0000575% RectangleInfo *CompareImagesBounds(const LayerMethod method,
cristy3ed852e2009-09-05 21:47:34 +0000576% const Image *image1, const Image *image2, ExceptionInfo *exception)
577%
578% A description of each parameter follows:
579%
cristy4c08aed2011-07-01 19:47:50 +0000580% o method: What differences to look for. Must be one of CompareAnyLayer,
581% CompareClearLayer, CompareOverlayLayer.
cristy3ed852e2009-09-05 21:47:34 +0000582%
583% o image1, image2: the two images to compare.
584%
585% o exception: return any errors or warnings in this structure.
586%
587*/
588
cristyed6987e2013-02-19 14:10:05 +0000589static RectangleInfo CompareImagesBounds(const Image *image1,
590 const Image *image2,const LayerMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000591{
592 RectangleInfo
593 bounds;
594
cristy4c08aed2011-07-01 19:47:50 +0000595 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000596 pixel1,
597 pixel2;
598
cristy4c08aed2011-07-01 19:47:50 +0000599 register const Quantum
cristy3ed852e2009-09-05 21:47:34 +0000600 *p,
601 *q;
602
cristybb503372010-05-27 20:51:26 +0000603 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000604 x;
605
cristy9d314ff2011-03-09 01:30:28 +0000606 ssize_t
607 y;
608
cristy3ed852e2009-09-05 21:47:34 +0000609 /*
cristy4c08aed2011-07-01 19:47:50 +0000610 Set bounding box of the differences between images.
cristy3ed852e2009-09-05 21:47:34 +0000611 */
cristy4c08aed2011-07-01 19:47:50 +0000612 GetPixelInfo(image1,&pixel1);
613 GetPixelInfo(image2,&pixel2);
cristybb503372010-05-27 20:51:26 +0000614 for (x=0; x < (ssize_t) image1->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000615 {
616 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
617 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +0000618 if ((p == (const Quantum *) NULL) ||
cristyacd2ed22011-08-30 01:44:23 +0000619 (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000620 break;
cristybb503372010-05-27 20:51:26 +0000621 for (y=0; y < (ssize_t) image1->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000622 {
cristy803640d2011-11-17 02:11:32 +0000623 GetPixelInfoPixel(image1,p,&pixel1);
624 GetPixelInfoPixel(image2,q,&pixel2);
cristy3ed852e2009-09-05 21:47:34 +0000625 if (ComparePixels(method,&pixel1,&pixel2))
626 break;
cristyed231572011-07-14 02:18:59 +0000627 p+=GetPixelChannels(image1);
cristy3ed852e2009-09-05 21:47:34 +0000628 q++;
629 }
cristybb503372010-05-27 20:51:26 +0000630 if (y < (ssize_t) image1->rows)
cristy3ed852e2009-09-05 21:47:34 +0000631 break;
632 }
cristybb503372010-05-27 20:51:26 +0000633 if (x >= (ssize_t) image1->columns)
cristy3ed852e2009-09-05 21:47:34 +0000634 {
635 /*
636 Images are identical, return a null image.
637 */
638 bounds.x=-1;
639 bounds.y=-1;
640 bounds.width=1;
641 bounds.height=1;
642 return(bounds);
643 }
644 bounds.x=x;
cristybb503372010-05-27 20:51:26 +0000645 for (x=(ssize_t) image1->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +0000646 {
647 p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
648 q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +0000649 if ((p == (const Quantum *) NULL) ||
cristyacd2ed22011-08-30 01:44:23 +0000650 (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000651 break;
cristybb503372010-05-27 20:51:26 +0000652 for (y=0; y < (ssize_t) image1->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000653 {
cristy803640d2011-11-17 02:11:32 +0000654 GetPixelInfoPixel(image1,p,&pixel1);
655 GetPixelInfoPixel(image2,q,&pixel2);
cristy3ed852e2009-09-05 21:47:34 +0000656 if (ComparePixels(method,&pixel1,&pixel2))
657 break;
cristyed231572011-07-14 02:18:59 +0000658 p+=GetPixelChannels(image1);
cristy3ed852e2009-09-05 21:47:34 +0000659 q++;
660 }
cristybb503372010-05-27 20:51:26 +0000661 if (y < (ssize_t) image1->rows)
cristy3ed852e2009-09-05 21:47:34 +0000662 break;
663 }
cristybb503372010-05-27 20:51:26 +0000664 bounds.width=(size_t) (x-bounds.x+1);
665 for (y=0; y < (ssize_t) image1->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000666 {
667 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
668 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000669 if ((p == (const Quantum *) NULL) ||
cristyacd2ed22011-08-30 01:44:23 +0000670 (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000671 break;
cristybb503372010-05-27 20:51:26 +0000672 for (x=0; x < (ssize_t) image1->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000673 {
cristy803640d2011-11-17 02:11:32 +0000674 GetPixelInfoPixel(image1,p,&pixel1);
675 GetPixelInfoPixel(image2,q,&pixel2);
cristy3ed852e2009-09-05 21:47:34 +0000676 if (ComparePixels(method,&pixel1,&pixel2))
677 break;
cristyed231572011-07-14 02:18:59 +0000678 p+=GetPixelChannels(image1);
cristy3ed852e2009-09-05 21:47:34 +0000679 q++;
680 }
cristybb503372010-05-27 20:51:26 +0000681 if (x < (ssize_t) image1->columns)
cristy3ed852e2009-09-05 21:47:34 +0000682 break;
683 }
684 bounds.y=y;
cristybb503372010-05-27 20:51:26 +0000685 for (y=(ssize_t) image1->rows-1; y >= 0; y--)
cristy3ed852e2009-09-05 21:47:34 +0000686 {
687 p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
688 q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000689 if ((p == (const Quantum *) NULL) ||
cristyacd2ed22011-08-30 01:44:23 +0000690 (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000691 break;
cristybb503372010-05-27 20:51:26 +0000692 for (x=0; x < (ssize_t) image1->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000693 {
cristy803640d2011-11-17 02:11:32 +0000694 GetPixelInfoPixel(image1,p,&pixel1);
695 GetPixelInfoPixel(image2,q,&pixel2);
cristy3ed852e2009-09-05 21:47:34 +0000696 if (ComparePixels(method,&pixel1,&pixel2))
697 break;
cristyed231572011-07-14 02:18:59 +0000698 p+=GetPixelChannels(image1);
cristy3ed852e2009-09-05 21:47:34 +0000699 q++;
700 }
cristybb503372010-05-27 20:51:26 +0000701 if (x < (ssize_t) image1->columns)
cristy3ed852e2009-09-05 21:47:34 +0000702 break;
703 }
cristybb503372010-05-27 20:51:26 +0000704 bounds.height=(size_t) (y-bounds.y+1);
cristy3ed852e2009-09-05 21:47:34 +0000705 return(bounds);
706}
707
708/*
709%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
710% %
711% %
712% %
713% C o m p a r e I m a g e L a y e r s %
714% %
715% %
716% %
717%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
718%
cristy8a9106f2011-07-05 14:39:26 +0000719% CompareImagesLayers() compares each image with the next in a sequence and
cristy3ed852e2009-09-05 21:47:34 +0000720% returns the minimum bounding region of all the pixel differences (of the
cristya0417062012-09-02 23:34:56 +0000721% LayerMethod specified) it discovers.
cristy3ed852e2009-09-05 21:47:34 +0000722%
723% Images do NOT have to be the same size, though it is best that all the
724% images are 'coalesced' (images are all the same size, on a flattened
725% canvas, so as to represent exactly how an specific frame should look).
726%
727% No GIF dispose methods are applied, so GIF animations must be coalesced
728% before applying this image operator to find differences to them.
729%
cristy8a9106f2011-07-05 14:39:26 +0000730% The format of the CompareImagesLayers method is:
cristy3ed852e2009-09-05 21:47:34 +0000731%
cristy8a9106f2011-07-05 14:39:26 +0000732% Image *CompareImagesLayers(const Image *images,
cristya0417062012-09-02 23:34:56 +0000733% const LayerMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000734%
735% A description of each parameter follows:
736%
737% o image: the image.
738%
739% o method: the layers type to compare images with. Must be one of...
740% CompareAnyLayer, CompareClearLayer, CompareOverlayLayer.
741%
742% o exception: return any errors or warnings in this structure.
743%
744*/
745
cristy8a9106f2011-07-05 14:39:26 +0000746MagickExport Image *CompareImagesLayers(const Image *image,
cristya0417062012-09-02 23:34:56 +0000747 const LayerMethod method, ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000748{
749 Image
750 *image_a,
751 *image_b,
752 *layers;
753
754 RectangleInfo
755 *bounds;
756
757 register const Image
758 *next;
759
cristybb503372010-05-27 20:51:26 +0000760 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000761 i;
762
763 assert(image != (const Image *) NULL);
764 assert(image->signature == MagickSignature);
765 if (image->debug != MagickFalse)
766 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
767 assert(exception != (ExceptionInfo *) NULL);
768 assert(exception->signature == MagickSignature);
769 assert((method == CompareAnyLayer) ||
770 (method == CompareClearLayer) ||
771 (method == CompareOverlayLayer));
772 /*
773 Allocate bounds memory.
774 */
775 next=GetFirstImageInList(image);
776 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
777 GetImageListLength(next),sizeof(*bounds));
778 if (bounds == (RectangleInfo *) NULL)
779 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
780 /*
781 Set up first comparision images.
782 */
783 image_a=CloneImage(next,next->page.width,next->page.height,
784 MagickTrue,exception);
785 if (image_a == (Image *) NULL)
786 {
787 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
788 return((Image *) NULL);
789 }
cristy4c08aed2011-07-01 19:47:50 +0000790 image_a->background_color.alpha=(Quantum) TransparentAlpha;
cristyea1a8aa2011-10-20 13:24:06 +0000791 (void) SetImageBackgroundColor(image_a,exception);
cristy3ed852e2009-09-05 21:47:34 +0000792 image_a->page=next->page;
793 image_a->page.x=0;
794 image_a->page.y=0;
cristy39172402012-03-30 13:04:39 +0000795 (void) CompositeImage(image_a,next,CopyCompositeOp,MagickTrue,next->page.x,
cristyfeb3e962012-03-29 17:25:55 +0000796 next->page.y,exception);
cristy3ed852e2009-09-05 21:47:34 +0000797 /*
798 Compute the bounding box of changes for the later images
799 */
800 i=0;
801 next=GetNextImageInList(next);
802 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
803 {
804 image_b=CloneImage(image_a,0,0,MagickTrue,exception);
805 if (image_b == (Image *) NULL)
806 {
807 image_a=DestroyImage(image_a);
808 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
809 return((Image *) NULL);
810 }
cristy39172402012-03-30 13:04:39 +0000811 (void) CompositeImage(image_a,next,CopyCompositeOp,MagickTrue,next->page.x,
cristye941a752011-10-15 01:52:48 +0000812 next->page.y,exception);
cristy8a9106f2011-07-05 14:39:26 +0000813 bounds[i]=CompareImagesBounds(image_b,image_a,method,exception);
cristy3ed852e2009-09-05 21:47:34 +0000814 image_b=DestroyImage(image_b);
815 i++;
816 }
817 image_a=DestroyImage(image_a);
818 /*
819 Clone first image in sequence.
820 */
821 next=GetFirstImageInList(image);
822 layers=CloneImage(next,0,0,MagickTrue,exception);
823 if (layers == (Image *) NULL)
824 {
825 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
826 return((Image *) NULL);
827 }
828 /*
829 Deconstruct the image sequence.
830 */
831 i=0;
832 next=GetNextImageInList(next);
833 for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
834 {
835 image_a=CloneImage(next,0,0,MagickTrue,exception);
836 if (image_a == (Image *) NULL)
837 break;
838 image_b=CropImage(image_a,&bounds[i],exception);
839 image_a=DestroyImage(image_a);
840 if (image_b == (Image *) NULL)
841 break;
842 AppendImageToList(&layers,image_b);
843 i++;
844 }
845 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
846 if (next != (Image *) NULL)
847 {
848 layers=DestroyImageList(layers);
849 return((Image *) NULL);
850 }
851 return(GetFirstImageInList(layers));
852}
853
854/*
855%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
856% %
857% %
858% %
cristy3ed852e2009-09-05 21:47:34 +0000859+ O p t i m i z e L a y e r F r a m e s %
860% %
861% %
862% %
863%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
864%
anthony9d570022010-09-11 07:18:35 +0000865% OptimizeLayerFrames() takes a coalesced GIF animation, and compares each
866% frame against the three different 'disposal' forms of the previous frame.
867% From this it then attempts to select the smallest cropped image and
868% disposal method needed to reproduce the resulting image.
cristy3ed852e2009-09-05 21:47:34 +0000869%
glennrp2489f532011-06-25 03:02:43 +0000870% Note that this not easy, and may require the expansion of the bounds
anthony9d570022010-09-11 07:18:35 +0000871% of previous frame, simply clear pixels for the next animation frame to
872% transparency according to the selected dispose method.
cristy3ed852e2009-09-05 21:47:34 +0000873%
874% The format of the OptimizeLayerFrames method is:
875%
cristy4c08aed2011-07-01 19:47:50 +0000876% Image *OptimizeLayerFrames(const Image *image,
cristya0417062012-09-02 23:34:56 +0000877% const LayerMethod method, ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000878%
879% A description of each parameter follows:
880%
881% o image: the image.
882%
anthony9d570022010-09-11 07:18:35 +0000883% o method: the layers technique to optimize with. Must be one of...
cristy4c08aed2011-07-01 19:47:50 +0000884% OptimizeImageLayer, or OptimizePlusLayer. The Plus form allows
885% the addition of extra 'zero delay' frames to clear pixels from
886% the previous frame, and the removal of frames that done change,
887% merging the delay times together.
cristy3ed852e2009-09-05 21:47:34 +0000888%
889% o exception: return any errors or warnings in this structure.
890%
891*/
892/*
anthony9d570022010-09-11 07:18:35 +0000893 Define a 'fake' dispose method where the frame is duplicated, (for
894 OptimizePlusLayer) with a extra zero time delay frame which does a
895 BackgroundDisposal to clear the pixels that need to be cleared.
cristy3ed852e2009-09-05 21:47:34 +0000896*/
897#define DupDispose ((DisposeType)9)
898/*
899 Another 'fake' dispose method used to removed frames that don't change.
900*/
901#define DelDispose ((DisposeType)8)
902
anthony9d570022010-09-11 07:18:35 +0000903#define DEBUG_OPT_FRAME 0
904
cristy3ed852e2009-09-05 21:47:34 +0000905static Image *OptimizeLayerFrames(const Image *image,
cristya0417062012-09-02 23:34:56 +0000906 const LayerMethod method, ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000907{
908 ExceptionInfo
909 *sans_exception;
910
911 Image
912 *prev_image,
913 *dup_image,
914 *bgnd_image,
915 *optimized_image;
916
917 RectangleInfo
918 try_bounds,
919 bgnd_bounds,
920 dup_bounds,
921 *bounds;
922
923 MagickBooleanType
924 add_frames,
925 try_cleared,
926 cleared;
927
928 DisposeType
929 *disposals;
930
931 register const Image
anthonyb6d08c52010-09-13 01:17:04 +0000932 *curr;
cristy3ed852e2009-09-05 21:47:34 +0000933
cristybb503372010-05-27 20:51:26 +0000934 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000935 i;
936
937 assert(image != (const Image *) NULL);
938 assert(image->signature == MagickSignature);
939 if (image->debug != MagickFalse)
940 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
941 assert(exception != (ExceptionInfo *) NULL);
942 assert(exception->signature == MagickSignature);
943 assert(method == OptimizeLayer ||
944 method == OptimizeImageLayer ||
945 method == OptimizePlusLayer);
cristy3ed852e2009-09-05 21:47:34 +0000946 /*
cristyed6987e2013-02-19 14:10:05 +0000947 Are we allowed to add/remove frames from animation?
cristy3ed852e2009-09-05 21:47:34 +0000948 */
949 add_frames=method == OptimizePlusLayer ? MagickTrue : MagickFalse;
950 /*
cristyed6987e2013-02-19 14:10:05 +0000951 Ensure all the images are the same size.
cristy3ed852e2009-09-05 21:47:34 +0000952 */
anthonyb6d08c52010-09-13 01:17:04 +0000953 curr=GetFirstImageInList(image);
954 for (; curr != (Image *) NULL; curr=GetNextImageInList(curr))
cristy3ed852e2009-09-05 21:47:34 +0000955 {
anthonyb6d08c52010-09-13 01:17:04 +0000956 if ((curr->columns != image->columns) || (curr->rows != image->rows))
cristy3ed852e2009-09-05 21:47:34 +0000957 ThrowImageException(OptionError,"ImagesAreNotTheSameSize");
958 /*
anthony9d570022010-09-11 07:18:35 +0000959 FUTURE: also check that image is also fully coalesced (full page)
960 Though as long as they are the same size it should not matter.
cristy3ed852e2009-09-05 21:47:34 +0000961 */
962 }
963 /*
anthony9d570022010-09-11 07:18:35 +0000964 Allocate memory (times 2 if we allow the use of frame duplications)
cristy3ed852e2009-09-05 21:47:34 +0000965 */
anthonyb6d08c52010-09-13 01:17:04 +0000966 curr=GetFirstImageInList(image);
cristy3ed852e2009-09-05 21:47:34 +0000967 bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
anthonyb6d08c52010-09-13 01:17:04 +0000968 GetImageListLength(curr),(add_frames != MagickFalse ? 2UL : 1UL)*
cristy3ed852e2009-09-05 21:47:34 +0000969 sizeof(*bounds));
970 if (bounds == (RectangleInfo *) NULL)
971 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
972 disposals=(DisposeType *) AcquireQuantumMemory((size_t)
973 GetImageListLength(image),(add_frames != MagickFalse ? 2UL : 1UL)*
974 sizeof(*disposals));
975 if (disposals == (DisposeType *) NULL)
976 {
977 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
978 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
979 }
980 /*
981 Initialise Previous Image as fully transparent
982 */
anthonyb6d08c52010-09-13 01:17:04 +0000983 prev_image=CloneImage(curr,curr->page.width,curr->page.height,
cristy3ed852e2009-09-05 21:47:34 +0000984 MagickTrue,exception);
985 if (prev_image == (Image *) NULL)
986 {
987 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
988 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
989 return((Image *) NULL);
990 }
anthonyb6d08c52010-09-13 01:17:04 +0000991 prev_image->page=curr->page; /* ERROR: <-- should not be need, but is! */
cristy3ed852e2009-09-05 21:47:34 +0000992 prev_image->page.x=0;
993 prev_image->page.y=0;
994 prev_image->dispose=NoneDispose;
995
cristy4c08aed2011-07-01 19:47:50 +0000996 prev_image->background_color.alpha=(Quantum) TransparentAlpha;
cristyea1a8aa2011-10-20 13:24:06 +0000997 (void) SetImageBackgroundColor(prev_image,exception);
cristy3ed852e2009-09-05 21:47:34 +0000998 /*
999 Figure out the area of overlay of the first frame
1000 No pixel could be cleared as all pixels are already cleared.
1001 */
anthony9d570022010-09-11 07:18:35 +00001002#if DEBUG_OPT_FRAME
1003 i=0;
cristy5acdd942011-05-27 19:45:39 +00001004 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
anthony9d570022010-09-11 07:18:35 +00001005#endif
cristy3ed852e2009-09-05 21:47:34 +00001006 disposals[0]=NoneDispose;
cristy8a9106f2011-07-05 14:39:26 +00001007 bounds[0]=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
anthony9d570022010-09-11 07:18:35 +00001008#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001009 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g\n\n",
cristy1e604812011-05-19 18:07:50 +00001010 (double) bounds[i].width,(double) bounds[i].height,
1011 (double) bounds[i].x,(double) bounds[i].y );
anthony9d570022010-09-11 07:18:35 +00001012#endif
cristy3ed852e2009-09-05 21:47:34 +00001013 /*
1014 Compute the bounding box of changes for each pair of images.
1015 */
1016 i=1;
1017 bgnd_image=(Image *)NULL;
1018 dup_image=(Image *)NULL;
1019 dup_bounds.width=0;
1020 dup_bounds.height=0;
1021 dup_bounds.x=0;
1022 dup_bounds.y=0;
anthonyb6d08c52010-09-13 01:17:04 +00001023 curr=GetNextImageInList(curr);
1024 for ( ; curr != (const Image *) NULL; curr=GetNextImageInList(curr))
cristy3ed852e2009-09-05 21:47:34 +00001025 {
anthony9d570022010-09-11 07:18:35 +00001026#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001027 (void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
anthony9d570022010-09-11 07:18:35 +00001028#endif
cristy3ed852e2009-09-05 21:47:34 +00001029 /*
1030 Assume none disposal is the best
1031 */
cristy8a9106f2011-07-05 14:39:26 +00001032 bounds[i]=CompareImagesBounds(curr->previous,curr,CompareAnyLayer,exception);
anthonyb6d08c52010-09-13 01:17:04 +00001033 cleared=IsBoundsCleared(curr->previous,curr,&bounds[i],exception);
cristy3ed852e2009-09-05 21:47:34 +00001034 disposals[i-1]=NoneDispose;
anthony9d570022010-09-11 07:18:35 +00001035#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001036 (void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g%s%s\n",
anthony9d570022010-09-11 07:18:35 +00001037 (double) bounds[i].width,(double) bounds[i].height,
1038 (double) bounds[i].x,(double) bounds[i].y,
1039 bounds[i].x < 0?" (unchanged)":"",
1040 cleared?" (pixels cleared)":"");
1041#endif
cristy3ed852e2009-09-05 21:47:34 +00001042 if ( bounds[i].x < 0 ) {
1043 /*
1044 Image frame is exactly the same as the previous frame!
1045 If not adding frames leave it to be cropped down to a null image.
1046 Otherwise mark previous image for deleted, transfering its crop bounds
1047 to the current image.
1048 */
1049 if ( add_frames && i>=2 ) {
1050 disposals[i-1]=DelDispose;
1051 disposals[i]=NoneDispose;
1052 bounds[i]=bounds[i-1];
1053 i++;
1054 continue;
1055 }
1056 }
1057 else
1058 {
1059 /*
1060 Compare a none disposal against a previous disposal
1061 */
cristy8a9106f2011-07-05 14:39:26 +00001062 try_bounds=CompareImagesBounds(prev_image,curr,CompareAnyLayer,exception);
anthonyb6d08c52010-09-13 01:17:04 +00001063 try_cleared=IsBoundsCleared(prev_image,curr,&try_bounds,exception);
anthony9d570022010-09-11 07:18:35 +00001064#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001065 (void) FormatLocaleFile(stderr, "test_prev: %.20gx%.20g%+.20g%+.20g%s\n",
anthony9d570022010-09-11 07:18:35 +00001066 (double) try_bounds.width,(double) try_bounds.height,
1067 (double) try_bounds.x,(double) try_bounds.y,
1068 try_cleared?" (pixels were cleared)":"");
1069#endif
cristy3ed852e2009-09-05 21:47:34 +00001070 if ( (!try_cleared && cleared ) ||
1071 try_bounds.width * try_bounds.height
1072 < bounds[i].width * bounds[i].height )
1073 {
1074 cleared=try_cleared;
1075 bounds[i]=try_bounds;
1076 disposals[i-1]=PreviousDispose;
anthony9d570022010-09-11 07:18:35 +00001077#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001078 (void) FormatLocaleFile(stderr, "previous: accepted\n");
anthony9d570022010-09-11 07:18:35 +00001079 } else {
cristy5acdd942011-05-27 19:45:39 +00001080 (void) FormatLocaleFile(stderr, "previous: rejected\n");
anthony9d570022010-09-11 07:18:35 +00001081#endif
cristy3ed852e2009-09-05 21:47:34 +00001082 }
1083
1084 /*
1085 If we are allowed lets try a complex frame duplication.
1086 It is useless if the previous image already clears pixels correctly.
1087 This method will always clear all the pixels that need to be cleared.
1088 */
anthony9d570022010-09-11 07:18:35 +00001089 dup_bounds.width=dup_bounds.height=0; /* no dup, no pixel added */
cristy3ed852e2009-09-05 21:47:34 +00001090 if ( add_frames )
1091 {
anthonyb6d08c52010-09-13 01:17:04 +00001092 dup_image=CloneImage(curr->previous,curr->previous->page.width,
1093 curr->previous->page.height,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001094 if (dup_image == (Image *) NULL)
1095 {
1096 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1097 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1098 prev_image=DestroyImage(prev_image);
1099 return((Image *) NULL);
1100 }
cristy8a9106f2011-07-05 14:39:26 +00001101 dup_bounds=CompareImagesBounds(dup_image,curr,CompareClearLayer,exception);
cristy7c3af952011-10-20 16:04:16 +00001102 ClearBounds(dup_image,&dup_bounds,exception);
cristy8a9106f2011-07-05 14:39:26 +00001103 try_bounds=CompareImagesBounds(dup_image,curr,CompareAnyLayer,exception);
cristy3ed852e2009-09-05 21:47:34 +00001104 if ( cleared ||
1105 dup_bounds.width*dup_bounds.height
1106 +try_bounds.width*try_bounds.height
1107 < bounds[i].width * bounds[i].height )
1108 {
1109 cleared=MagickFalse;
1110 bounds[i]=try_bounds;
1111 disposals[i-1]=DupDispose;
1112 /* to be finalised later, if found to be optimial */
1113 }
1114 else
1115 dup_bounds.width=dup_bounds.height=0;
1116 }
cristy3ed852e2009-09-05 21:47:34 +00001117 /*
1118 Now compare against a simple background disposal
1119 */
anthonyb6d08c52010-09-13 01:17:04 +00001120 bgnd_image=CloneImage(curr->previous,curr->previous->page.width,
1121 curr->previous->page.height,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001122 if (bgnd_image == (Image *) NULL)
1123 {
1124 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1125 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1126 prev_image=DestroyImage(prev_image);
anthony9d570022010-09-11 07:18:35 +00001127 if ( dup_image != (Image *) NULL)
1128 dup_image=DestroyImage(dup_image);
cristy3ed852e2009-09-05 21:47:34 +00001129 return((Image *) NULL);
1130 }
anthony9d570022010-09-11 07:18:35 +00001131 bgnd_bounds=bounds[i-1]; /* interum bounds of the previous image */
cristy7c3af952011-10-20 16:04:16 +00001132 ClearBounds(bgnd_image,&bgnd_bounds,exception);
cristy8a9106f2011-07-05 14:39:26 +00001133 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
anthonyb6d08c52010-09-13 01:17:04 +00001134 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
anthony9d570022010-09-11 07:18:35 +00001135#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001136 (void) FormatLocaleFile(stderr, "background: %s\n",
anthony9d570022010-09-11 07:18:35 +00001137 try_cleared?"(pixels cleared)":"");
1138#endif
cristy3ed852e2009-09-05 21:47:34 +00001139 if ( try_cleared )
1140 {
1141 /*
1142 Straight background disposal failed to clear pixels needed!
1143 Lets try expanding the disposal area of the previous frame, to
1144 include the pixels that are cleared. This guaranteed
1145 to work, though may not be the most optimized solution.
1146 */
cristy8a9106f2011-07-05 14:39:26 +00001147 try_bounds=CompareImagesBounds(curr->previous,curr,CompareClearLayer,exception);
anthony9d570022010-09-11 07:18:35 +00001148#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001149 (void) FormatLocaleFile(stderr, "expand_clear: %.20gx%.20g%+.20g%+.20g%s\n",
anthony9d570022010-09-11 07:18:35 +00001150 (double) try_bounds.width,(double) try_bounds.height,
1151 (double) try_bounds.x,(double) try_bounds.y,
1152 try_bounds.x<0?" (no expand nessary)":"");
1153#endif
cristy3ed852e2009-09-05 21:47:34 +00001154 if ( bgnd_bounds.x < 0 )
1155 bgnd_bounds = try_bounds;
1156 else
1157 {
anthony9d570022010-09-11 07:18:35 +00001158#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001159 (void) FormatLocaleFile(stderr, "expand_bgnd: %.20gx%.20g%+.20g%+.20g\n",
anthony9d570022010-09-11 07:18:35 +00001160 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1161 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1162#endif
cristy3ed852e2009-09-05 21:47:34 +00001163 if ( try_bounds.x < bgnd_bounds.x )
1164 {
1165 bgnd_bounds.width+= bgnd_bounds.x-try_bounds.x;
1166 if ( bgnd_bounds.width < try_bounds.width )
1167 bgnd_bounds.width = try_bounds.width;
1168 bgnd_bounds.x = try_bounds.x;
1169 }
1170 else
1171 {
1172 try_bounds.width += try_bounds.x - bgnd_bounds.x;
1173 if ( bgnd_bounds.width < try_bounds.width )
1174 bgnd_bounds.width = try_bounds.width;
1175 }
1176 if ( try_bounds.y < bgnd_bounds.y )
1177 {
1178 bgnd_bounds.height += bgnd_bounds.y - try_bounds.y;
1179 if ( bgnd_bounds.height < try_bounds.height )
1180 bgnd_bounds.height = try_bounds.height;
1181 bgnd_bounds.y = try_bounds.y;
1182 }
1183 else
1184 {
1185 try_bounds.height += try_bounds.y - bgnd_bounds.y;
1186 if ( bgnd_bounds.height < try_bounds.height )
1187 bgnd_bounds.height = try_bounds.height;
1188 }
anthony9d570022010-09-11 07:18:35 +00001189#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001190 (void) FormatLocaleFile(stderr, " to : %.20gx%.20g%+.20g%+.20g\n",
anthony9d570022010-09-11 07:18:35 +00001191 (double) bgnd_bounds.width,(double) bgnd_bounds.height,
1192 (double) bgnd_bounds.x,(double) bgnd_bounds.y );
1193#endif
cristy3ed852e2009-09-05 21:47:34 +00001194 }
cristy7c3af952011-10-20 16:04:16 +00001195 ClearBounds(bgnd_image,&bgnd_bounds,exception);
anthony9d570022010-09-11 07:18:35 +00001196#if DEBUG_OPT_FRAME
1197/* Something strange is happening with a specific animation
1198 * CompareAnyLayers (normal method) and CompareClearLayers returns the whole
1199 * image, which is not posibly correct! As verified by previous tests.
1200 * Something changed beyond the bgnd_bounds clearing. But without being able
1201 * to see, or writet he image at this point it is hard to tell what is wrong!
1202 * Only CompareOverlay seemed to return something sensible.
1203 */
cristy8a9106f2011-07-05 14:39:26 +00001204 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareClearLayer,exception);
cristy5acdd942011-05-27 19:45:39 +00001205 (void) FormatLocaleFile(stderr, "expand_ctst: %.20gx%.20g%+.20g%+.20g\n",
anthony9d570022010-09-11 07:18:35 +00001206 (double) try_bounds.width,(double) try_bounds.height,
1207 (double) try_bounds.x,(double) try_bounds.y );
cristy8a9106f2011-07-05 14:39:26 +00001208 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareAnyLayer,exception);
anthonyb6d08c52010-09-13 01:17:04 +00001209 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
cristy5acdd942011-05-27 19:45:39 +00001210 (void) FormatLocaleFile(stderr, "expand_any : %.20gx%.20g%+.20g%+.20g%s\n",
anthony9d570022010-09-11 07:18:35 +00001211 (double) try_bounds.width,(double) try_bounds.height,
1212 (double) try_bounds.x,(double) try_bounds.y,
1213 try_cleared?" (pixels cleared)":"");
1214#endif
cristy8a9106f2011-07-05 14:39:26 +00001215 try_bounds=CompareImagesBounds(bgnd_image,curr,CompareOverlayLayer,exception);
anthony9d570022010-09-11 07:18:35 +00001216#if DEBUG_OPT_FRAME
anthonyb6d08c52010-09-13 01:17:04 +00001217 try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
cristy5acdd942011-05-27 19:45:39 +00001218 (void) FormatLocaleFile(stderr, "expand_test: %.20gx%.20g%+.20g%+.20g%s\n",
anthony9d570022010-09-11 07:18:35 +00001219 (double) try_bounds.width,(double) try_bounds.height,
1220 (double) try_bounds.x,(double) try_bounds.y,
1221 try_cleared?" (pixels cleared)":"");
1222#endif
cristy3ed852e2009-09-05 21:47:34 +00001223 }
1224 /*
1225 Test if this background dispose is smaller than any of the
1226 other methods we tryed before this (including duplicated frame)
1227 */
1228 if ( cleared ||
1229 bgnd_bounds.width*bgnd_bounds.height
1230 +try_bounds.width*try_bounds.height
1231 < bounds[i-1].width*bounds[i-1].height
1232 +dup_bounds.width*dup_bounds.height
1233 +bounds[i].width*bounds[i].height )
1234 {
1235 cleared=MagickFalse;
1236 bounds[i-1]=bgnd_bounds;
1237 bounds[i]=try_bounds;
1238 if ( disposals[i-1] == DupDispose )
1239 dup_image=DestroyImage(dup_image);
1240 disposals[i-1]=BackgroundDispose;
anthony9d570022010-09-11 07:18:35 +00001241#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001242 (void) FormatLocaleFile(stderr, "expand_bgnd: accepted\n");
anthony9d570022010-09-11 07:18:35 +00001243 } else {
cristy5acdd942011-05-27 19:45:39 +00001244 (void) FormatLocaleFile(stderr, "expand_bgnd: reject\n");
anthony9d570022010-09-11 07:18:35 +00001245#endif
cristy3ed852e2009-09-05 21:47:34 +00001246 }
1247 }
1248 /*
1249 Finalise choice of dispose, set new prev_image,
1250 and junk any extra images as appropriate,
1251 */
1252 if ( disposals[i-1] == DupDispose )
1253 {
1254 if (bgnd_image != (Image *) NULL)
1255 bgnd_image=DestroyImage(bgnd_image);
1256 prev_image=DestroyImage(prev_image);
1257 prev_image=dup_image, dup_image=(Image *) NULL;
1258 bounds[i+1]=bounds[i];
1259 bounds[i]=dup_bounds;
1260 disposals[i-1]=DupDispose;
1261 disposals[i]=BackgroundDispose;
1262 i++;
1263 }
1264 else
1265 {
anthony9d570022010-09-11 07:18:35 +00001266 if ( dup_image != (Image *) NULL)
1267 dup_image=DestroyImage(dup_image);
cristy3ed852e2009-09-05 21:47:34 +00001268 if ( disposals[i-1] != PreviousDispose )
1269 prev_image=DestroyImage(prev_image);
1270 if ( disposals[i-1] == BackgroundDispose )
1271 prev_image=bgnd_image, bgnd_image=(Image *)NULL;
anthony9d570022010-09-11 07:18:35 +00001272 if (bgnd_image != (Image *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001273 bgnd_image=DestroyImage(bgnd_image);
cristy3ed852e2009-09-05 21:47:34 +00001274 if ( disposals[i-1] == NoneDispose )
1275 {
anthonyb6d08c52010-09-13 01:17:04 +00001276 prev_image=CloneImage(curr->previous,curr->previous->page.width,
1277 curr->previous->page.height,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001278 if (prev_image == (Image *) NULL)
1279 {
1280 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1281 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
1282 return((Image *) NULL);
1283 }
1284 }
anthony9d570022010-09-11 07:18:35 +00001285
cristy3ed852e2009-09-05 21:47:34 +00001286 }
anthony9d570022010-09-11 07:18:35 +00001287 assert(prev_image != (Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001288 disposals[i]=disposals[i-1];
anthony9d570022010-09-11 07:18:35 +00001289#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001290 (void) FormatLocaleFile(stderr, "final %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
anthony9d570022010-09-11 07:18:35 +00001291 (double) i-1,
cristy042ee782011-04-22 18:48:30 +00001292 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i-1]),
anthony9d570022010-09-11 07:18:35 +00001293 (double) bounds[i-1].width, (double) bounds[i-1].height,
1294 (double) bounds[i-1].x, (double) bounds[i-1].y );
1295#endif
1296#if DEBUG_OPT_FRAME
cristy5acdd942011-05-27 19:45:39 +00001297 (void) FormatLocaleFile(stderr, "interum %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
anthony9d570022010-09-11 07:18:35 +00001298 (double) i,
cristy042ee782011-04-22 18:48:30 +00001299 CommandOptionToMnemonic(MagickDisposeOptions, disposals[i]),
anthony9d570022010-09-11 07:18:35 +00001300 (double) bounds[i].width, (double) bounds[i].height,
1301 (double) bounds[i].x, (double) bounds[i].y );
cristy5acdd942011-05-27 19:45:39 +00001302 (void) FormatLocaleFile(stderr, "\n");
anthony9d570022010-09-11 07:18:35 +00001303#endif
cristy3ed852e2009-09-05 21:47:34 +00001304 i++;
1305 }
1306 prev_image=DestroyImage(prev_image);
1307 /*
1308 Optimize all images in sequence.
1309 */
1310 sans_exception=AcquireExceptionInfo();
1311 i=0;
anthonyb6d08c52010-09-13 01:17:04 +00001312 curr=GetFirstImageInList(image);
cristy3ed852e2009-09-05 21:47:34 +00001313 optimized_image=NewImageList();
anthonyb6d08c52010-09-13 01:17:04 +00001314 while ( curr != (const Image *) NULL )
cristy3ed852e2009-09-05 21:47:34 +00001315 {
anthonyb6d08c52010-09-13 01:17:04 +00001316 prev_image=CloneImage(curr,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001317 if (prev_image == (Image *) NULL)
1318 break;
1319 if ( disposals[i] == DelDispose ) {
cristybb503372010-05-27 20:51:26 +00001320 size_t time = 0;
cristy3ed852e2009-09-05 21:47:34 +00001321 while ( disposals[i] == DelDispose ) {
anthonyb6d08c52010-09-13 01:17:04 +00001322 time += curr->delay*1000/curr->ticks_per_second;
1323 curr=GetNextImageInList(curr);
cristy3ed852e2009-09-05 21:47:34 +00001324 i++;
1325 }
anthonyb6d08c52010-09-13 01:17:04 +00001326 time += curr->delay*1000/curr->ticks_per_second;
cristy3ed852e2009-09-05 21:47:34 +00001327 prev_image->ticks_per_second = 100L;
1328 prev_image->delay = time*prev_image->ticks_per_second/1000;
1329 }
1330 bgnd_image=CropImage(prev_image,&bounds[i],sans_exception);
1331 prev_image=DestroyImage(prev_image);
1332 if (bgnd_image == (Image *) NULL)
1333 break;
1334 bgnd_image->dispose=disposals[i];
1335 if ( disposals[i] == DupDispose ) {
1336 bgnd_image->delay=0;
1337 bgnd_image->dispose=NoneDispose;
1338 }
1339 else
anthonyb6d08c52010-09-13 01:17:04 +00001340 curr=GetNextImageInList(curr);
cristy3ed852e2009-09-05 21:47:34 +00001341 AppendImageToList(&optimized_image,bgnd_image);
1342 i++;
1343 }
1344 sans_exception=DestroyExceptionInfo(sans_exception);
1345 bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
1346 disposals=(DisposeType *) RelinquishMagickMemory(disposals);
anthonyb6d08c52010-09-13 01:17:04 +00001347 if (curr != (Image *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001348 {
1349 optimized_image=DestroyImageList(optimized_image);
1350 return((Image *) NULL);
1351 }
1352 return(GetFirstImageInList(optimized_image));
1353}
1354
1355/*
1356%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1357% %
1358% %
1359% %
1360% O p t i m i z e I m a g e L a y e r s %
1361% %
1362% %
1363% %
1364%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1365%
1366% OptimizeImageLayers() compares each image the GIF disposed forms of the
1367% previous image in the sequence. From this it attempts to select the
1368% smallest cropped image to replace each frame, while preserving the results
1369% of the GIF animation.
1370%
1371% The format of the OptimizeImageLayers method is:
1372%
1373% Image *OptimizeImageLayers(const Image *image,
1374% ExceptionInfo *exception)
1375%
1376% A description of each parameter follows:
1377%
1378% o image: the image.
1379%
1380% o exception: return any errors or warnings in this structure.
1381%
1382*/
1383MagickExport Image *OptimizeImageLayers(const Image *image,
1384 ExceptionInfo *exception)
1385{
1386 return(OptimizeLayerFrames(image,OptimizeImageLayer,exception));
1387}
1388
1389/*
1390%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1391% %
1392% %
1393% %
1394% O p t i m i z e P l u s I m a g e L a y e r s %
1395% %
1396% %
1397% %
1398%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1399%
1400% OptimizeImagePlusLayers() is exactly as OptimizeImageLayers(), but may
1401% also add or even remove extra frames in the animation, if it improves
1402% the total number of pixels in the resulting GIF animation.
1403%
1404% The format of the OptimizePlusImageLayers method is:
1405%
1406% Image *OptimizePlusImageLayers(const Image *image,
1407% ExceptionInfo *exception)
1408%
1409% A description of each parameter follows:
1410%
1411% o image: the image.
1412%
1413% o exception: return any errors or warnings in this structure.
1414%
1415*/
1416MagickExport Image *OptimizePlusImageLayers(const Image *image,
1417 ExceptionInfo *exception)
1418{
1419 return OptimizeLayerFrames(image, OptimizePlusLayer, exception);
1420}
1421
1422/*
1423%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1424% %
1425% %
1426% %
1427% 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 %
1428% %
1429% %
1430% %
1431%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1432%
1433% OptimizeImageTransparency() takes a frame optimized GIF animation, and
1434% compares the overlayed pixels against the disposal image resulting from all
1435% the previous frames in the animation. Any pixel that does not change the
1436% disposal image (and thus does not effect the outcome of an overlay) is made
1437% transparent.
1438%
1439% WARNING: This modifies the current images directly, rather than generate
1440% a new image sequence.
1441%
1442% The format of the OptimizeImageTransperency method is:
1443%
1444% void OptimizeImageTransperency(Image *image,ExceptionInfo *exception)
1445%
1446% A description of each parameter follows:
1447%
1448% o image: the image sequence
1449%
1450% o exception: return any errors or warnings in this structure.
1451%
1452*/
1453MagickExport void OptimizeImageTransparency(const Image *image,
1454 ExceptionInfo *exception)
1455{
1456 Image
1457 *dispose_image;
1458
1459 register Image
1460 *next;
1461
1462 /*
1463 Run the image through the animation sequence
1464 */
1465 assert(image != (Image *) NULL);
1466 assert(image->signature == MagickSignature);
1467 if (image->debug != MagickFalse)
1468 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1469 assert(exception != (ExceptionInfo *) NULL);
1470 assert(exception->signature == MagickSignature);
1471 next=GetFirstImageInList(image);
1472 dispose_image=CloneImage(next,next->page.width,next->page.height,
1473 MagickTrue,exception);
1474 if (dispose_image == (Image *) NULL)
1475 return;
1476 dispose_image->page=next->page;
1477 dispose_image->page.x=0;
1478 dispose_image->page.y=0;
1479 dispose_image->dispose=NoneDispose;
cristy4c08aed2011-07-01 19:47:50 +00001480 dispose_image->background_color.alpha=(Quantum) TransparentAlpha;
cristyea1a8aa2011-10-20 13:24:06 +00001481 (void) SetImageBackgroundColor(dispose_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001482
1483 while ( next != (Image *) NULL )
1484 {
1485 Image
1486 *current_image;
1487
1488 /*
1489 Overlay this frame's image over the previous disposal image
1490 */
1491 current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
1492 if (current_image == (Image *) NULL)
1493 {
1494 dispose_image=DestroyImage(dispose_image);
1495 return;
1496 }
cristy8a46d822012-08-28 23:32:39 +00001497 (void) CompositeImage(current_image,next,next->alpha_trait == BlendPixelTrait ?
cristy39172402012-03-30 13:04:39 +00001498 OverCompositeOp : CopyCompositeOp,MagickTrue,next->page.x,next->page.y,
cristye941a752011-10-15 01:52:48 +00001499 exception);
cristy3ed852e2009-09-05 21:47:34 +00001500 /*
1501 At this point the image would be displayed, for the delay period
1502 **
1503 Work out the disposal of the previous image
1504 */
1505 if (next->dispose == BackgroundDispose)
1506 {
1507 RectangleInfo
1508 bounds=next->page;
1509
1510 bounds.width=next->columns;
1511 bounds.height=next->rows;
1512 if (bounds.x < 0)
1513 {
1514 bounds.width+=bounds.x;
1515 bounds.x=0;
1516 }
cristybb503372010-05-27 20:51:26 +00001517 if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
cristy3ed852e2009-09-05 21:47:34 +00001518 bounds.width=current_image->columns-bounds.x;
1519 if (bounds.y < 0)
1520 {
1521 bounds.height+=bounds.y;
1522 bounds.y=0;
1523 }
cristybb503372010-05-27 20:51:26 +00001524 if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
cristy3ed852e2009-09-05 21:47:34 +00001525 bounds.height=current_image->rows-bounds.y;
cristy7c3af952011-10-20 16:04:16 +00001526 ClearBounds(current_image, &bounds,exception);
cristy3ed852e2009-09-05 21:47:34 +00001527 }
1528 if (next->dispose != PreviousDispose)
1529 {
1530 dispose_image=DestroyImage(dispose_image);
1531 dispose_image=current_image;
1532 }
1533 else
1534 current_image=DestroyImage(current_image);
1535
1536 /*
1537 Optimize Transparency of the next frame (if present)
1538 */
1539 next=GetNextImageInList(next);
cristyfeb3e962012-03-29 17:25:55 +00001540 if (next != (Image *) NULL) {
1541 (void) CompositeImage(next,dispose_image,ChangeMaskCompositeOp,
cristy39172402012-03-30 13:04:39 +00001542 MagickTrue,-(next->page.x),-(next->page.y),exception);
cristy3ed852e2009-09-05 21:47:34 +00001543 }
1544 }
1545 dispose_image=DestroyImage(dispose_image);
1546 return;
1547}
1548
1549/*
1550%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1551% %
1552% %
1553% %
1554% R e m o v e D u p l i c a t e L a y e r s %
1555% %
1556% %
1557% %
1558%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1559%
1560% RemoveDuplicateLayers() removes any image that is exactly the same as the
1561% next image in the given image list. Image size and virtual canvas offset
1562% must also match, though not the virtual canvas size itself.
1563%
1564% No check is made with regards to image disposal setting, though it is the
1565% dispose setting of later image that is kept. Also any time delays are also
1566% added together. As such coalesced image animations should still produce the
1567% same result, though with duplicte frames merged into a single frame.
1568%
1569% The format of the RemoveDuplicateLayers method is:
1570%
1571% void RemoveDuplicateLayers(Image **image, ExceptionInfo *exception)
1572%
1573% A description of each parameter follows:
1574%
1575% o images: the image list
1576%
1577% o exception: return any errors or warnings in this structure.
1578%
1579*/
1580MagickExport void RemoveDuplicateLayers(Image **images,
1581 ExceptionInfo *exception)
1582{
1583 register Image
1584 *curr,
1585 *next;
1586
1587 RectangleInfo
1588 bounds;
1589
1590 assert((*images) != (const Image *) NULL);
1591 assert((*images)->signature == MagickSignature);
1592 if ((*images)->debug != MagickFalse)
1593 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1594 assert(exception != (ExceptionInfo *) NULL);
1595 assert(exception->signature == MagickSignature);
1596
1597 curr=GetFirstImageInList(*images);
1598 for (; (next=GetNextImageInList(curr)) != (Image *) NULL; curr=next)
1599 {
1600 if ( curr->columns != next->columns || curr->rows != next->rows
1601 || curr->page.x != next->page.x || curr->page.y != next->page.y )
1602 continue;
cristy8a9106f2011-07-05 14:39:26 +00001603 bounds=CompareImagesBounds(curr,next,CompareAnyLayer,exception);
cristy3ed852e2009-09-05 21:47:34 +00001604 if ( bounds.x < 0 ) {
1605 /*
1606 the two images are the same, merge time delays and delete one.
1607 */
cristybb503372010-05-27 20:51:26 +00001608 size_t time;
cristy3ed852e2009-09-05 21:47:34 +00001609 time = curr->delay*1000/curr->ticks_per_second;
1610 time += next->delay*1000/next->ticks_per_second;
1611 next->ticks_per_second = 100L;
1612 next->delay = time*curr->ticks_per_second/1000;
1613 next->iterations = curr->iterations;
1614 *images = curr;
1615 (void) DeleteImageFromList(images);
1616 }
1617 }
1618 *images = GetFirstImageInList(*images);
1619}
1620
1621/*
1622%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1623% %
1624% %
1625% %
1626% R e m o v e Z e r o D e l a y L a y e r s %
1627% %
1628% %
1629% %
1630%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1631%
1632% RemoveZeroDelayLayers() removes any image that as a zero delay time. Such
1633% images generally represent intermediate or partial updates in GIF
1634% animations used for file optimization. They are not ment to be displayed
1635% to users of the animation. Viewable images in an animation should have a
1636% time delay of 3 or more centi-seconds (hundredths of a second).
1637%
1638% However if all the frames have a zero time delay, then either the animation
1639% is as yet incomplete, or it is not a GIF animation. This a non-sensible
1640% situation, so no image will be removed and a 'Zero Time Animation' warning
1641% (exception) given.
1642%
1643% No warning will be given if no image was removed because all images had an
1644% appropriate non-zero time delay set.
1645%
1646% Due to the special requirements of GIF disposal handling, GIF animations
1647% should be coalesced first, before calling this function, though that is not
1648% a requirement.
1649%
1650% The format of the RemoveZeroDelayLayers method is:
1651%
1652% void RemoveZeroDelayLayers(Image **image, ExceptionInfo *exception)
1653%
1654% A description of each parameter follows:
1655%
1656% o images: the image list
1657%
1658% o exception: return any errors or warnings in this structure.
1659%
1660*/
1661MagickExport void RemoveZeroDelayLayers(Image **images,
1662 ExceptionInfo *exception)
1663{
1664 Image
1665 *i;
1666
1667 assert((*images) != (const Image *) NULL);
1668 assert((*images)->signature == MagickSignature);
1669 if ((*images)->debug != MagickFalse)
1670 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
1671 assert(exception != (ExceptionInfo *) NULL);
1672 assert(exception->signature == MagickSignature);
1673
1674 i=GetFirstImageInList(*images);
1675 for ( ; i != (Image *) NULL; i=GetNextImageInList(i))
1676 if ( i->delay != 0L ) break;
1677 if ( i == (Image *) NULL ) {
1678 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
cristyefe601c2013-01-05 17:51:12 +00001679 "ZeroTimeAnimation","`%s'",GetFirstImageInList(*images)->filename);
cristy3ed852e2009-09-05 21:47:34 +00001680 return;
1681 }
1682 i=GetFirstImageInList(*images);
1683 while ( i != (Image *) NULL )
1684 {
1685 if ( i->delay == 0L ) {
1686 (void) DeleteImageFromList(&i);
1687 *images=i;
1688 }
1689 else
1690 i=GetNextImageInList(i);
1691 }
1692 *images=GetFirstImageInList(*images);
1693}
1694
1695/*
1696%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1697% %
1698% %
1699% %
1700% C o m p o s i t e L a y e r s %
1701% %
1702% %
1703% %
1704%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1705%
anthonye5840b22012-03-17 12:22:34 +00001706% CompositeLayers() compose the source image sequence over the destination
1707% image sequence, starting with the current image in both lists.
cristy3ed852e2009-09-05 21:47:34 +00001708%
anthonye5840b22012-03-17 12:22:34 +00001709% Each layer from the two image lists are composted together until the end of
1710% one of the image lists is reached. The offset of each composition is also
1711% adjusted to match the virtual canvas offsets of each layer. As such the
1712% given offset is relative to the virtual canvas, and not the actual image.
cristy3ed852e2009-09-05 21:47:34 +00001713%
anthonye5840b22012-03-17 12:22:34 +00001714% Composition uses given x and y offsets, as the 'origin' location of the
1715% source images virtual canvas (not the real image) allowing you to compose a
1716% list of 'layer images' into the destiantioni images. This makes it well
1717% sutiable for directly composing 'Clears Frame Animations' or 'Coaleased
1718% Animations' onto a static or other 'Coaleased Animation' destination image
1719% list. GIF disposal handling is not looked at.
cristy3ed852e2009-09-05 21:47:34 +00001720%
anthonye5840b22012-03-17 12:22:34 +00001721% Special case:- If one of the image sequences is the last image (just a
1722% single image remaining), that image is repeatally composed with all the
1723% images in the other image list. Either the source or destination lists may
1724% be the single image, for this situation.
cristy3ed852e2009-09-05 21:47:34 +00001725%
anthonye5840b22012-03-17 12:22:34 +00001726% In the case of a single destination image (or last image given), that image
1727% will ve cloned to match the number of images remaining in the source image
1728% list.
1729%
1730% This is equivelent to the "-layer Composite" Shell API operator.
1731%
cristy3ed852e2009-09-05 21:47:34 +00001732%
1733% The format of the CompositeLayers method is:
1734%
anthonye5840b22012-03-17 12:22:34 +00001735% void CompositeLayers(Image *destination, const CompositeOperator
1736% compose, Image *source, const ssize_t x_offset, const ssize_t y_offset,
1737% ExceptionInfo *exception);
cristy3ed852e2009-09-05 21:47:34 +00001738%
1739% A description of each parameter follows:
1740%
1741% o destination: the destination images and results
1742%
1743% o source: source image(s) for the layer composition
1744%
1745% o compose, x_offset, y_offset: arguments passed on to CompositeImages()
1746%
1747% o exception: return any errors or warnings in this structure.
1748%
1749*/
cristye941a752011-10-15 01:52:48 +00001750
cristy3ed852e2009-09-05 21:47:34 +00001751static inline void CompositeCanvas(Image *destination,
cristye941a752011-10-15 01:52:48 +00001752 const CompositeOperator compose,Image *source,ssize_t x_offset,
1753 ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001754{
cristy9d314ff2011-03-09 01:30:28 +00001755 x_offset+=source->page.x-destination->page.x;
1756 y_offset+=source->page.y-destination->page.y;
cristy39172402012-03-30 13:04:39 +00001757 (void) CompositeImage(destination,source,compose,MagickTrue,x_offset,
cristyfeb3e962012-03-29 17:25:55 +00001758 y_offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00001759}
1760
1761MagickExport void CompositeLayers(Image *destination,
cristy9d314ff2011-03-09 01:30:28 +00001762 const CompositeOperator compose, Image *source,const ssize_t x_offset,
1763 const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001764{
1765 assert(destination != (Image *) NULL);
1766 assert(destination->signature == MagickSignature);
1767 assert(source != (Image *) NULL);
1768 assert(source->signature == MagickSignature);
1769 assert(exception != (ExceptionInfo *) NULL);
1770 assert(exception->signature == MagickSignature);
1771 if (source->debug != MagickFalse || destination->debug != MagickFalse)
1772 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s - %s",
cristye941a752011-10-15 01:52:48 +00001773 source->filename, destination->filename);
cristy3ed852e2009-09-05 21:47:34 +00001774
1775 /*
1776 Overlay single source image over destation image/list
1777 */
anthonye5840b22012-03-17 12:22:34 +00001778 if ( source->next == (Image *) NULL )
cristy3ed852e2009-09-05 21:47:34 +00001779 while ( destination != (Image *) NULL )
1780 {
cristye941a752011-10-15 01:52:48 +00001781 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1782 exception);
cristy3ed852e2009-09-05 21:47:34 +00001783 destination=GetNextImageInList(destination);
1784 }
1785
1786 /*
anthonye5840b22012-03-17 12:22:34 +00001787 Overlay source image list over single destination.
1788 Multiple clones of destination image are created to match source list.
cristy3ed852e2009-09-05 21:47:34 +00001789 Original Destination image becomes first image of generated list.
1790 As such the image list pointer does not require any change in caller.
1791 Some animation attributes however also needs coping in this case.
1792 */
anthonye5840b22012-03-17 12:22:34 +00001793 else if ( destination->next == (Image *) NULL )
cristy3ed852e2009-09-05 21:47:34 +00001794 {
1795 Image *dest = CloneImage(destination,0,0,MagickTrue,exception);
1796
cristye941a752011-10-15 01:52:48 +00001797 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1798 exception);
cristy3ed852e2009-09-05 21:47:34 +00001799 /* copy source image attributes ? */
anthonyfe2045f2011-04-14 02:57:12 +00001800 if ( source->next != (Image *) NULL )
1801 {
1802 destination->delay = source->delay;
1803 destination->iterations = source->iterations;
1804 }
cristy3ed852e2009-09-05 21:47:34 +00001805 source=GetNextImageInList(source);
1806
1807 while ( source != (Image *) NULL )
1808 {
1809 AppendImageToList(&destination,
1810 CloneImage(dest,0,0,MagickTrue,exception));
1811 destination=GetLastImageInList(destination);
1812
cristye941a752011-10-15 01:52:48 +00001813 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1814 exception);
cristy3ed852e2009-09-05 21:47:34 +00001815 destination->delay = source->delay;
1816 destination->iterations = source->iterations;
1817 source=GetNextImageInList(source);
1818 }
1819 dest=DestroyImage(dest);
1820 }
1821
1822 /*
1823 Overlay a source image list over a destination image list
1824 until either list runs out of images. (Does not repeat)
1825 */
1826 else
1827 while ( source != (Image *) NULL && destination != (Image *) NULL )
1828 {
cristye941a752011-10-15 01:52:48 +00001829 CompositeCanvas(destination, compose, source, x_offset, y_offset,
1830 exception);
cristy3ed852e2009-09-05 21:47:34 +00001831 source=GetNextImageInList(source);
1832 destination=GetNextImageInList(destination);
1833 }
1834}
1835
1836/*
1837%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1838% %
1839% %
1840% %
1841% M e r g e I m a g e L a y e r s %
1842% %
1843% %
1844% %
1845%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1846%
1847% MergeImageLayers() composes all the image layers from the current given
1848% image onward to produce a single image of the merged layers.
1849%
cristya0417062012-09-02 23:34:56 +00001850% The inital canvas's size depends on the given LayerMethod, and is
cristy3ed852e2009-09-05 21:47:34 +00001851% initialized using the first images background color. The images
1852% are then compositied onto that image in sequence using the given
1853% composition that has been assigned to each individual image.
1854%
1855% The format of the MergeImageLayers is:
1856%
1857% Image *MergeImageLayers(const Image *image,
cristya0417062012-09-02 23:34:56 +00001858% const LayerMethod method, ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001859%
1860% A description of each parameter follows:
1861%
1862% o image: the image list to be composited together
1863%
1864% o method: the method of selecting the size of the initial canvas.
1865%
1866% MergeLayer: Merge all layers onto a canvas just large enough
1867% to hold all the actual images. The virtual canvas of the
1868% first image is preserved but otherwise ignored.
1869%
1870% FlattenLayer: Use the virtual canvas size of first image.
1871% Images which fall outside this canvas is clipped.
1872% This can be used to 'fill out' a given virtual canvas.
1873%
1874% MosaicLayer: Start with the virtual canvas of the first image,
1875% enlarging left and right edges to contain all images.
1876% Images with negative offsets will be clipped.
1877%
1878% TrimBoundsLayer: Determine the overall bounds of all the image
1879% layers just as in "MergeLayer", then adjust the the canvas
1880% and offsets to be relative to those bounds, without overlaying
1881% the images.
1882%
1883% WARNING: a new image is not returned, the original image
1884% sequence page data is modified instead.
1885%
1886% o exception: return any errors or warnings in this structure.
1887%
1888*/
cristya0417062012-09-02 23:34:56 +00001889MagickExport Image *MergeImageLayers(Image *image,const LayerMethod method,
cristye941a752011-10-15 01:52:48 +00001890 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001891{
1892#define MergeLayersTag "Merge/Layers"
1893
1894 Image
1895 *canvas;
1896
1897 MagickBooleanType
1898 proceed;
1899
cristy3ed852e2009-09-05 21:47:34 +00001900 RectangleInfo
1901 page;
1902
cristy3ed852e2009-09-05 21:47:34 +00001903 register const Image
1904 *next;
1905
cristybb503372010-05-27 20:51:26 +00001906 size_t
cristycee97112010-05-28 00:44:52 +00001907 number_images,
1908 height,
1909 width;
1910
1911 ssize_t
1912 scene;
cristy3ed852e2009-09-05 21:47:34 +00001913
1914 assert(image != (Image *) NULL);
1915 assert(image->signature == MagickSignature);
1916 if (image->debug != MagickFalse)
1917 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1918 assert(exception != (ExceptionInfo *) NULL);
1919 assert(exception->signature == MagickSignature);
1920 /*
1921 Determine canvas image size, and its virtual canvas size and offset
1922 */
1923 page=image->page;
1924 width=image->columns;
1925 height=image->rows;
1926 switch (method)
1927 {
1928 case TrimBoundsLayer:
1929 case MergeLayer:
1930 default:
1931 {
cristy7fcdfd02011-11-20 03:36:37 +00001932 next=GetNextImageInList(image);
1933 for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
1934 {
1935 if (page.x > next->page.x)
1936 {
1937 width+=page.x-next->page.x;
1938 page.x=next->page.x;
1939 }
1940 if (page.y > next->page.y)
1941 {
1942 height+=page.y-next->page.y;
1943 page.y=next->page.y;
1944 }
1945 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns-page.x))
cristy9a4ef392014-09-06 12:46:41 +00001946 width=(size_t) next->page.x+(ssize_t) next->columns-page.x;
cristy7fcdfd02011-11-20 03:36:37 +00001947 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows-page.y))
1948 height=(size_t) next->page.y+(ssize_t) next->rows-page.y;
cristy3ed852e2009-09-05 21:47:34 +00001949 }
1950 break;
1951 }
1952 case FlattenLayer:
1953 {
cristy7fcdfd02011-11-20 03:36:37 +00001954 if (page.width > 0)
cristy3ed852e2009-09-05 21:47:34 +00001955 width=page.width;
cristy7fcdfd02011-11-20 03:36:37 +00001956 if (page.height > 0)
cristy3ed852e2009-09-05 21:47:34 +00001957 height=page.height;
1958 page.x=0;
1959 page.y=0;
1960 break;
1961 }
1962 case MosaicLayer:
1963 {
cristy7fcdfd02011-11-20 03:36:37 +00001964 if (page.width > 0)
cristy3ed852e2009-09-05 21:47:34 +00001965 width=page.width;
cristy7fcdfd02011-11-20 03:36:37 +00001966 if (page.height > 0)
cristy3ed852e2009-09-05 21:47:34 +00001967 height=page.height;
cristy7fcdfd02011-11-20 03:36:37 +00001968 for (next=image; next != (Image *) NULL; next=GetNextImageInList(next))
1969 {
1970 if (method == MosaicLayer)
1971 {
1972 page.x=next->page.x;
1973 page.y=next->page.y;
1974 if ((ssize_t) width < (next->page.x+(ssize_t) next->columns))
1975 width=(size_t) next->page.x+next->columns;
1976 if ((ssize_t) height < (next->page.y+(ssize_t) next->rows))
1977 height=(size_t) next->page.y+next->rows;
1978 }
cristy3ed852e2009-09-05 21:47:34 +00001979 }
1980 page.width=width;
1981 page.height=height;
1982 page.x=0;
1983 page.y=0;
1984 }
1985 break;
1986 }
cristy3ed852e2009-09-05 21:47:34 +00001987 /*
cristy7fcdfd02011-11-20 03:36:37 +00001988 Set virtual canvas size if not defined.
cristy3ed852e2009-09-05 21:47:34 +00001989 */
cristy7fcdfd02011-11-20 03:36:37 +00001990 if (page.width == 0)
1991 page.width=page.x < 0 ? width : width+page.x;
1992 if (page.height == 0)
1993 page.height=page.y < 0 ? height : height+page.y;
1994 /*
1995 Handle "TrimBoundsLayer" method separately to normal 'layer merge'.
1996 */
1997 if (method == TrimBoundsLayer)
cristy3ed852e2009-09-05 21:47:34 +00001998 {
cristy7fcdfd02011-11-20 03:36:37 +00001999 number_images=GetImageListLength(image);
2000 for (scene=0; scene < (ssize_t) number_images; scene++)
2001 {
2002 image->page.x-=page.x;
2003 image->page.y-=page.y;
2004 image->page.width=width;
2005 image->page.height=height;
2006 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2007 number_images);
2008 if (proceed == MagickFalse)
2009 break;
2010 image=GetNextImageInList(image);
cristyee534e52011-11-20 03:41:17 +00002011 if (image == (Image *) NULL)
2012 break;
cristy7fcdfd02011-11-20 03:36:37 +00002013 }
2014 return((Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002015 }
cristy3ed852e2009-09-05 21:47:34 +00002016 /*
2017 Create canvas size of width and height, and background color.
2018 */
2019 canvas=CloneImage(image,width,height,MagickTrue,exception);
2020 if (canvas == (Image *) NULL)
2021 return((Image *) NULL);
cristyea1a8aa2011-10-20 13:24:06 +00002022 (void) SetImageBackgroundColor(canvas,exception);
cristy3ed852e2009-09-05 21:47:34 +00002023 canvas->page=page;
2024 canvas->dispose=UndefinedDispose;
cristy3ed852e2009-09-05 21:47:34 +00002025 /*
2026 Compose images onto canvas, with progress monitor
2027 */
2028 number_images=GetImageListLength(image);
cristybb503372010-05-27 20:51:26 +00002029 for (scene=0; scene < (ssize_t) number_images; scene++)
cristy3ed852e2009-09-05 21:47:34 +00002030 {
cristy40e1d032012-03-30 13:26:42 +00002031 (void) CompositeImage(canvas,image,image->compose,MagickTrue,image->page.x-
cristye941a752011-10-15 01:52:48 +00002032 canvas->page.x,image->page.y-canvas->page.y,exception);
cristycee97112010-05-28 00:44:52 +00002033 proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
2034 number_images);
cristy3ed852e2009-09-05 21:47:34 +00002035 if (proceed == MagickFalse)
2036 break;
2037 image=GetNextImageInList(image);
cristy7fcdfd02011-11-20 03:36:37 +00002038 if (image == (Image *) NULL)
2039 break;
cristy3ed852e2009-09-05 21:47:34 +00002040 }
2041 return(canvas);
2042}
2043