@@ -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,12 @@ 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+ if ( ! TryGetBackgroundIndex ( quantized , this . backgroundColor , out byte backgroundIndex ) )
160+ {
161+ backgroundIndex = derivedTransparencyIndex >= 0
162+ ? frameMetadata . TransparencyIndex
163+ : gifMetadata . BackgroundColorIndex ;
164+ }
147165
148166 // Get the number of bits.
149167 int bitDepth = ColorNumerics . GetBitsNeededForColorDepth ( quantized . Palette . Length ) ;
@@ -161,15 +179,21 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
161179
162180 // Write application extensions.
163181 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 ) ;
165183 }
166184
167185 this . EncodeFirstFrame ( stream , frameMetadata , quantized ) ;
168186
169187 // Capture the global palette for reuse on subsequent frames and cleanup the quantized frame.
170188 TPixel [ ] globalPalette = image . Frames . Count == 1 ? [ ] : quantized . Palette . ToArray ( ) ;
171189
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 ) ;
173197
174198 stream . WriteByte ( GifConstants . EndIntroducer ) ;
175199
@@ -194,7 +218,8 @@ private void EncodeAdditionalFrames<TPixel>(
194218 Image < TPixel > image ,
195219 ReadOnlyMemory < TPixel > globalPalette ,
196220 int globalTransparencyIndex ,
197- FrameDisposalMode previousDisposalMode )
221+ FrameDisposalMode previousDisposalMode ,
222+ CancellationToken cancellationToken )
198223 where TPixel : unmanaged, IPixel < TPixel >
199224 {
200225 if ( image . Frames . Count == 1 )
@@ -213,6 +238,16 @@ private void EncodeAdditionalFrames<TPixel>(
213238
214239 for ( int i = 1 ; i < image . Frames . Count ; i ++ )
215240 {
241+ if ( cancellationToken . IsCancellationRequested )
242+ {
243+ if ( hasPaletteQuantizer )
244+ {
245+ paletteQuantizer . Dispose ( ) ;
246+ }
247+
248+ return ;
249+ }
250+
216251 // Gather the metadata for this frame.
217252 ImageFrame < TPixel > currentFrame = image . Frames [ i ] ;
218253 ImageFrame < TPixel > ? nextFrame = i < image . Frames . Count - 1 ? image . Frames [ i + 1 ] : null ;
@@ -291,6 +326,10 @@ private void EncodeAdditionalFrame<TPixel>(
291326
292327 ImageFrame < TPixel > ? previous = previousDisposalMode == FrameDisposalMode . RestoreToBackground ? null : previousFrame ;
293328
329+ Color background = metadata . DisposalMode == FrameDisposalMode . RestoreToBackground
330+ ? this . backgroundColor ?? Color . Transparent
331+ : Color . Transparent ;
332+
294333 // Deduplicate and quantize the frame capturing only required parts.
295334 ( bool difference , Rectangle bounds ) =
296335 AnimationUtilities . DeDuplicatePixels (
@@ -299,7 +338,7 @@ private void EncodeAdditionalFrame<TPixel>(
299338 currentFrame ,
300339 nextFrame ,
301340 encodingFrame ,
302- Color . Transparent ,
341+ background ,
303342 true ) ;
304343
305344 using IndexedImageFrame < TPixel > quantized = this . QuantizeAdditionalFrameAndUpdateMetadata (
@@ -428,14 +467,12 @@ private IndexedImageFrame<TPixel> QuantizeAdditionalFrameAndUpdateMetadata<TPixe
428467 private static byte ClampIndex ( int value ) => ( byte ) Numerics . Clamp ( value , byte . MinValue , byte . MaxValue ) ;
429468
430469 /// <summary>
431- /// Returns the index of the most transparent color in the palette.
470+ /// Returns the index of the transparent color in the palette.
432471 /// </summary>
433472 /// <param name="quantized">The current quantized frame.</param>
434473 /// <param name="metadata">The current gif frame metadata.</param>
435474 /// <typeparam name="TPixel">The pixel format.</typeparam>
436- /// <returns>
437- /// The <see cref="int"/>.
438- /// </returns>
475+ /// <returns>The <see cref="int"/>.</returns>
439476 private static int GetTransparentIndex < TPixel > ( IndexedImageFrame < TPixel > ? quantized , GifFrameMetadata ? metadata )
440477 where TPixel : unmanaged, IPixel < TPixel >
441478 {
@@ -463,6 +500,47 @@ private static int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel>? quanti
463500 return index ;
464501 }
465502
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+
466544 /// <summary>
467545 /// Writes the file header signature and version to the stream.
468546 /// </summary>
0 commit comments