@@ -54,6 +54,19 @@ internal sealed class GifEncoderCore
5454 /// </summary>
5555 private readonly IPixelSamplingStrategy pixelSamplingStrategy ;
5656
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+
5770 /// <summary>
5871 /// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
5972 /// </summary>
@@ -68,6 +81,8 @@ public GifEncoderCore(Configuration configuration, GifEncoder encoder)
6881 this . hasQuantizer = encoder . Quantizer is not null ;
6982 this . colorTableMode = encoder . ColorTableMode ;
7083 this . pixelSamplingStrategy = encoder . PixelSamplingStrategy ;
84+ this . backgroundColor = encoder . BackgroundColor ;
85+ this . repeatCount = encoder . RepeatCount ;
7186 }
7287
7388 /// <summary>
@@ -141,9 +156,17 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
141156 frameMetadata . TransparencyIndex = ClampIndex ( derivedTransparencyIndex ) ;
142157 }
143158
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+ }
147170
148171 // Get the number of bits.
149172 int bitDepth = ColorNumerics . GetBitsNeededForColorDepth ( quantized . Palette . Length ) ;
@@ -161,15 +184,21 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
161184
162185 // Write application extensions.
163186 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 ) ;
165188 }
166189
167190 this . EncodeFirstFrame ( stream , frameMetadata , quantized ) ;
168191
169192 // Capture the global palette for reuse on subsequent frames and cleanup the quantized frame.
170193 TPixel [ ] globalPalette = image . Frames . Count == 1 ? [ ] : quantized . Palette . ToArray ( ) ;
171194
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 ) ;
173202
174203 stream . WriteByte ( GifConstants . EndIntroducer ) ;
175204
@@ -194,7 +223,8 @@ private void EncodeAdditionalFrames<TPixel>(
194223 Image < TPixel > image ,
195224 ReadOnlyMemory < TPixel > globalPalette ,
196225 int globalTransparencyIndex ,
197- FrameDisposalMode previousDisposalMode )
226+ FrameDisposalMode previousDisposalMode ,
227+ CancellationToken cancellationToken )
198228 where TPixel : unmanaged, IPixel < TPixel >
199229 {
200230 if ( image . Frames . Count == 1 )
@@ -213,6 +243,16 @@ private void EncodeAdditionalFrames<TPixel>(
213243
214244 for ( int i = 1 ; i < image . Frames . Count ; i ++ )
215245 {
246+ if ( cancellationToken . IsCancellationRequested )
247+ {
248+ if ( hasPaletteQuantizer )
249+ {
250+ paletteQuantizer . Dispose ( ) ;
251+ }
252+
253+ return ;
254+ }
255+
216256 // Gather the metadata for this frame.
217257 ImageFrame < TPixel > currentFrame = image . Frames [ i ] ;
218258 ImageFrame < TPixel > ? nextFrame = i < image . Frames . Count - 1 ? image . Frames [ i + 1 ] : null ;
@@ -291,6 +331,10 @@ private void EncodeAdditionalFrame<TPixel>(
291331
292332 ImageFrame < TPixel > ? previous = previousDisposalMode == FrameDisposalMode . RestoreToBackground ? null : previousFrame ;
293333
334+ Color background = metadata . DisposalMode == FrameDisposalMode . RestoreToBackground
335+ ? this . backgroundColor ?? Color . Transparent
336+ : Color . Transparent ;
337+
294338 // Deduplicate and quantize the frame capturing only required parts.
295339 ( bool difference , Rectangle bounds ) =
296340 AnimationUtilities . DeDuplicatePixels (
@@ -299,7 +343,7 @@ private void EncodeAdditionalFrame<TPixel>(
299343 currentFrame ,
300344 nextFrame ,
301345 encodingFrame ,
302- Color . Transparent ,
346+ background ,
303347 true ) ;
304348
305349 using IndexedImageFrame < TPixel > quantized = this . QuantizeAdditionalFrameAndUpdateMetadata (
@@ -428,14 +472,12 @@ private IndexedImageFrame<TPixel> QuantizeAdditionalFrameAndUpdateMetadata<TPixe
428472 private static byte ClampIndex ( int value ) => ( byte ) Numerics . Clamp ( value , byte . MinValue , byte . MaxValue ) ;
429473
430474 /// <summary>
431- /// Returns the index of the most transparent color in the palette.
475+ /// Returns the index of the transparent color in the palette.
432476 /// </summary>
433477 /// <param name="quantized">The current quantized frame.</param>
434478 /// <param name="metadata">The current gif frame metadata.</param>
435479 /// <typeparam name="TPixel">The pixel format.</typeparam>
436- /// <returns>
437- /// The <see cref="int"/>.
438- /// </returns>
480+ /// <returns>The <see cref="int"/>.</returns>
439481 private static int GetTransparentIndex < TPixel > ( IndexedImageFrame < TPixel > ? quantized , GifFrameMetadata ? metadata )
440482 where TPixel : unmanaged, IPixel < TPixel >
441483 {
@@ -463,6 +505,36 @@ private static int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel>? quanti
463505 return index ;
464506 }
465507
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+
466538 /// <summary>
467539 /// Writes the file header signature and version to the stream.
468540 /// </summary>
0 commit comments