@@ -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,17 @@ 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
+ byte backgroundIndex ;
160
+ if ( this . backgroundColor . HasValue )
161
+ {
162
+ backgroundIndex = GetBackgroundIndex ( quantized , this . backgroundColor . Value ) ;
163
+ }
164
+ else
165
+ {
166
+ backgroundIndex = derivedTransparencyIndex >= 0
167
+ ? frameMetadata . TransparencyIndex
168
+ : gifMetadata . BackgroundColorIndex ;
169
+ }
147
170
148
171
// Get the number of bits.
149
172
int bitDepth = ColorNumerics . GetBitsNeededForColorDepth ( quantized . Palette . Length ) ;
@@ -161,15 +184,21 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
161
184
162
185
// Write application extensions.
163
186
XmpProfile ? xmpProfile = image . Metadata . XmpProfile ?? image . Frames . RootFrame . Metadata . XmpProfile ;
164
- this . WriteApplicationExtensions ( stream , image . Frames . Count , gifMetadata . RepeatCount , xmpProfile ) ;
187
+ this . WriteApplicationExtensions ( stream , image . Frames . Count , this . repeatCount ?? gifMetadata . RepeatCount , xmpProfile ) ;
165
188
}
166
189
167
190
this . EncodeFirstFrame ( stream , frameMetadata , quantized ) ;
168
191
169
192
// Capture the global palette for reuse on subsequent frames and cleanup the quantized frame.
170
193
TPixel [ ] globalPalette = image . Frames . Count == 1 ? [ ] : quantized . Palette . ToArray ( ) ;
171
194
172
- this . EncodeAdditionalFrames ( stream , image , globalPalette , derivedTransparencyIndex , frameMetadata . DisposalMode ) ;
195
+ this . EncodeAdditionalFrames (
196
+ stream ,
197
+ image ,
198
+ globalPalette ,
199
+ derivedTransparencyIndex ,
200
+ frameMetadata . DisposalMode ,
201
+ cancellationToken ) ;
173
202
174
203
stream . WriteByte ( GifConstants . EndIntroducer ) ;
175
204
@@ -194,7 +223,8 @@ private void EncodeAdditionalFrames<TPixel>(
194
223
Image < TPixel > image ,
195
224
ReadOnlyMemory < TPixel > globalPalette ,
196
225
int globalTransparencyIndex ,
197
- FrameDisposalMode previousDisposalMode )
226
+ FrameDisposalMode previousDisposalMode ,
227
+ CancellationToken cancellationToken )
198
228
where TPixel : unmanaged, IPixel < TPixel >
199
229
{
200
230
if ( image . Frames . Count == 1 )
@@ -213,6 +243,16 @@ private void EncodeAdditionalFrames<TPixel>(
213
243
214
244
for ( int i = 1 ; i < image . Frames . Count ; i ++ )
215
245
{
246
+ if ( cancellationToken . IsCancellationRequested )
247
+ {
248
+ if ( hasPaletteQuantizer )
249
+ {
250
+ paletteQuantizer . Dispose ( ) ;
251
+ }
252
+
253
+ return ;
254
+ }
255
+
216
256
// Gather the metadata for this frame.
217
257
ImageFrame < TPixel > currentFrame = image . Frames [ i ] ;
218
258
ImageFrame < TPixel > ? nextFrame = i < image . Frames . Count - 1 ? image . Frames [ i + 1 ] : null ;
@@ -291,6 +331,10 @@ private void EncodeAdditionalFrame<TPixel>(
291
331
292
332
ImageFrame < TPixel > ? previous = previousDisposalMode == FrameDisposalMode . RestoreToBackground ? null : previousFrame ;
293
333
334
+ Color background = metadata . DisposalMode == FrameDisposalMode . RestoreToBackground
335
+ ? this . backgroundColor ?? Color . Transparent
336
+ : Color . Transparent ;
337
+
294
338
// Deduplicate and quantize the frame capturing only required parts.
295
339
( bool difference , Rectangle bounds ) =
296
340
AnimationUtilities . DeDuplicatePixels (
@@ -299,7 +343,7 @@ private void EncodeAdditionalFrame<TPixel>(
299
343
currentFrame ,
300
344
nextFrame ,
301
345
encodingFrame ,
302
- Color . Transparent ,
346
+ background ,
303
347
true ) ;
304
348
305
349
using IndexedImageFrame < TPixel > quantized = this . QuantizeAdditionalFrameAndUpdateMetadata (
@@ -428,14 +472,12 @@ private IndexedImageFrame<TPixel> QuantizeAdditionalFrameAndUpdateMetadata<TPixe
428
472
private static byte ClampIndex ( int value ) => ( byte ) Numerics . Clamp ( value , byte . MinValue , byte . MaxValue ) ;
429
473
430
474
/// <summary>
431
- /// Returns the index of the most transparent color in the palette.
475
+ /// Returns the index of the transparent color in the palette.
432
476
/// </summary>
433
477
/// <param name="quantized">The current quantized frame.</param>
434
478
/// <param name="metadata">The current gif frame metadata.</param>
435
479
/// <typeparam name="TPixel">The pixel format.</typeparam>
436
- /// <returns>
437
- /// The <see cref="int"/>.
438
- /// </returns>
480
+ /// <returns>The <see cref="int"/>.</returns>
439
481
private static int GetTransparentIndex < TPixel > ( IndexedImageFrame < TPixel > ? quantized , GifFrameMetadata ? metadata )
440
482
where TPixel : unmanaged, IPixel < TPixel >
441
483
{
@@ -463,6 +505,36 @@ private static int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel>? quanti
463
505
return index ;
464
506
}
465
507
508
+ /// <summary>
509
+ /// Returns the index of the background color in the palette.
510
+ /// </summary>
511
+ /// <param name="quantized">The current quantized frame.</param>
512
+ /// <param name="background">The background color to match.</param>
513
+ /// <typeparam name="TPixel">The pixel format.</typeparam>
514
+ /// <returns>The <see cref="byte"/>.</returns>
515
+ private static byte GetBackgroundIndex < TPixel > ( IndexedImageFrame < TPixel > ? quantized , Color background )
516
+ where TPixel : unmanaged, IPixel < TPixel >
517
+ {
518
+ int index = - 1 ;
519
+ if ( quantized != null )
520
+ {
521
+ TPixel backgroundPixel = background . ToPixel < TPixel > ( ) ;
522
+ ReadOnlySpan < TPixel > palette = quantized . Palette . Span ;
523
+ for ( int i = 0 ; i < palette . Length ; i ++ )
524
+ {
525
+ if ( ! backgroundPixel . Equals ( palette [ i ] ) )
526
+ {
527
+ continue ;
528
+ }
529
+
530
+ index = i ;
531
+ break ;
532
+ }
533
+ }
534
+
535
+ return ( byte ) Numerics . Clamp ( index , 0 , 255 ) ;
536
+ }
537
+
466
538
/// <summary>
467
539
/// Writes the file header signature and version to the stream.
468
540
/// </summary>
0 commit comments