@@ -54,6 +54,19 @@ internal sealed class GifEncoderCore
54
54
/// </summary>
55
55
private readonly IPixelSamplingStrategy pixelSamplingStrategy ;
56
56
57
+ /// <summary>
58
+ /// The default background color of the canvas when animating.
59
+ /// This color may be used to fill the unused space on the canvas around the frames,
60
+ /// as well as the transparent pixels of the first frame.
61
+ /// The background color is also used when a frame disposal mode is <see cref="FrameDisposalMode.RestoreToBackground"/>.
62
+ /// </summary>
63
+ private readonly Color ? backgroundColor ;
64
+
65
+ /// <summary>
66
+ /// The number of times any animation is repeated.
67
+ /// </summary>
68
+ private readonly ushort ? repeatCount ;
69
+
57
70
/// <summary>
58
71
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
59
72
/// </summary>
@@ -68,6 +81,8 @@ public GifEncoderCore(Configuration configuration, GifEncoder encoder)
68
81
this . hasQuantizer = encoder . Quantizer is not null ;
69
82
this . colorTableMode = encoder . ColorTableMode ;
70
83
this . pixelSamplingStrategy = encoder . PixelSamplingStrategy ;
84
+ this . backgroundColor = encoder . BackgroundColor ;
85
+ this . repeatCount = encoder . RepeatCount ;
71
86
}
72
87
73
88
/// <summary>
@@ -141,9 +156,12 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
141
156
frameMetadata . TransparencyIndex = ClampIndex ( derivedTransparencyIndex ) ;
142
157
}
143
158
144
- byte backgroundIndex = derivedTransparencyIndex >= 0
145
- ? frameMetadata . TransparencyIndex
146
- : gifMetadata . BackgroundColorIndex ;
159
+ if ( ! TryGetBackgroundIndex ( quantized , this . backgroundColor , out byte backgroundIndex ) )
160
+ {
161
+ backgroundIndex = derivedTransparencyIndex >= 0
162
+ ? frameMetadata . TransparencyIndex
163
+ : gifMetadata . BackgroundColorIndex ;
164
+ }
147
165
148
166
// Get the number of bits.
149
167
int bitDepth = ColorNumerics . GetBitsNeededForColorDepth ( quantized . Palette . Length ) ;
@@ -161,15 +179,21 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
161
179
162
180
// Write application extensions.
163
181
XmpProfile ? xmpProfile = image . Metadata . XmpProfile ?? image . Frames . RootFrame . Metadata . XmpProfile ;
164
- this . WriteApplicationExtensions ( stream , image . Frames . Count , gifMetadata . RepeatCount , xmpProfile ) ;
182
+ this . WriteApplicationExtensions ( stream , image . Frames . Count , this . repeatCount ?? gifMetadata . RepeatCount , xmpProfile ) ;
165
183
}
166
184
167
185
this . EncodeFirstFrame ( stream , frameMetadata , quantized ) ;
168
186
169
187
// Capture the global palette for reuse on subsequent frames and cleanup the quantized frame.
170
188
TPixel [ ] globalPalette = image . Frames . Count == 1 ? [ ] : quantized . Palette . ToArray ( ) ;
171
189
172
- this . EncodeAdditionalFrames ( stream , image , globalPalette , derivedTransparencyIndex , frameMetadata . DisposalMode ) ;
190
+ this . EncodeAdditionalFrames (
191
+ stream ,
192
+ image ,
193
+ globalPalette ,
194
+ derivedTransparencyIndex ,
195
+ frameMetadata . DisposalMode ,
196
+ cancellationToken ) ;
173
197
174
198
stream . WriteByte ( GifConstants . EndIntroducer ) ;
175
199
@@ -194,7 +218,8 @@ private void EncodeAdditionalFrames<TPixel>(
194
218
Image < TPixel > image ,
195
219
ReadOnlyMemory < TPixel > globalPalette ,
196
220
int globalTransparencyIndex ,
197
- FrameDisposalMode previousDisposalMode )
221
+ FrameDisposalMode previousDisposalMode ,
222
+ CancellationToken cancellationToken )
198
223
where TPixel : unmanaged, IPixel < TPixel >
199
224
{
200
225
if ( image . Frames . Count == 1 )
@@ -213,6 +238,16 @@ private void EncodeAdditionalFrames<TPixel>(
213
238
214
239
for ( int i = 1 ; i < image . Frames . Count ; i ++ )
215
240
{
241
+ if ( cancellationToken . IsCancellationRequested )
242
+ {
243
+ if ( hasPaletteQuantizer )
244
+ {
245
+ paletteQuantizer . Dispose ( ) ;
246
+ }
247
+
248
+ return ;
249
+ }
250
+
216
251
// Gather the metadata for this frame.
217
252
ImageFrame < TPixel > currentFrame = image . Frames [ i ] ;
218
253
ImageFrame < TPixel > ? nextFrame = i < image . Frames . Count - 1 ? image . Frames [ i + 1 ] : null ;
@@ -291,6 +326,10 @@ private void EncodeAdditionalFrame<TPixel>(
291
326
292
327
ImageFrame < TPixel > ? previous = previousDisposalMode == FrameDisposalMode . RestoreToBackground ? null : previousFrame ;
293
328
329
+ Color background = metadata . DisposalMode == FrameDisposalMode . RestoreToBackground
330
+ ? this . backgroundColor ?? Color . Transparent
331
+ : Color . Transparent ;
332
+
294
333
// Deduplicate and quantize the frame capturing only required parts.
295
334
( bool difference , Rectangle bounds ) =
296
335
AnimationUtilities . DeDuplicatePixels (
@@ -299,7 +338,7 @@ private void EncodeAdditionalFrame<TPixel>(
299
338
currentFrame ,
300
339
nextFrame ,
301
340
encodingFrame ,
302
- Color . Transparent ,
341
+ background ,
303
342
true ) ;
304
343
305
344
using IndexedImageFrame < TPixel > quantized = this . QuantizeAdditionalFrameAndUpdateMetadata (
@@ -428,14 +467,12 @@ private IndexedImageFrame<TPixel> QuantizeAdditionalFrameAndUpdateMetadata<TPixe
428
467
private static byte ClampIndex ( int value ) => ( byte ) Numerics . Clamp ( value , byte . MinValue , byte . MaxValue ) ;
429
468
430
469
/// <summary>
431
- /// Returns the index of the most transparent color in the palette.
470
+ /// Returns the index of the transparent color in the palette.
432
471
/// </summary>
433
472
/// <param name="quantized">The current quantized frame.</param>
434
473
/// <param name="metadata">The current gif frame metadata.</param>
435
474
/// <typeparam name="TPixel">The pixel format.</typeparam>
436
- /// <returns>
437
- /// The <see cref="int"/>.
438
- /// </returns>
475
+ /// <returns>The <see cref="int"/>.</returns>
439
476
private static int GetTransparentIndex < TPixel > ( IndexedImageFrame < TPixel > ? quantized , GifFrameMetadata ? metadata )
440
477
where TPixel : unmanaged, IPixel < TPixel >
441
478
{
@@ -463,6 +500,47 @@ private static int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel>? quanti
463
500
return index ;
464
501
}
465
502
503
+ /// <summary>
504
+ /// Returns the index of the background color in the palette.
505
+ /// </summary>
506
+ /// <param name="quantized">The current quantized frame.</param>
507
+ /// <param name="background">The background color to match.</param>
508
+ /// <param name="index">The index in the palette of the background color.</param>
509
+ /// <typeparam name="TPixel">The pixel format.</typeparam>
510
+ /// <returns>The <see cref="bool"/>.</returns>
511
+ private static bool TryGetBackgroundIndex < TPixel > (
512
+ IndexedImageFrame < TPixel > ? quantized ,
513
+ Color ? background ,
514
+ out byte index )
515
+ where TPixel : unmanaged, IPixel < TPixel >
516
+ {
517
+ int match = - 1 ;
518
+ if ( quantized != null && background . HasValue )
519
+ {
520
+ TPixel backgroundPixel = background . Value . ToPixel < TPixel > ( ) ;
521
+ ReadOnlySpan < TPixel > palette = quantized . Palette . Span ;
522
+ for ( int i = 0 ; i < palette . Length ; i ++ )
523
+ {
524
+ if ( ! backgroundPixel . Equals ( palette [ i ] ) )
525
+ {
526
+ continue ;
527
+ }
528
+
529
+ match = i ;
530
+ break ;
531
+ }
532
+ }
533
+
534
+ if ( match >= 0 )
535
+ {
536
+ index = ( byte ) Numerics . Clamp ( match , 0 , 255 ) ;
537
+ return true ;
538
+ }
539
+
540
+ index = 0 ;
541
+ return false ;
542
+ }
543
+
466
544
/// <summary>
467
545
/// Writes the file header signature and version to the stream.
468
546
/// </summary>
0 commit comments