Coverage Report

Created: 2025-10-12 07:48

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