@@ -89,6 +89,9 @@ internal sealed class GifDecoderCore : ImageDecoderCore
89
89
/// </summary>
90
90
private GifMetadata ? gifMetadata ;
91
91
92
+ // The background color used to fill the frame.
93
+ private Color backgroundColor ;
94
+
92
95
/// <summary>
93
96
/// Initializes a new instance of the <see cref="GifDecoderCore"/> class.
94
97
/// </summary>
@@ -108,9 +111,12 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
108
111
uint frameCount = 0 ;
109
112
Image < TPixel > ? image = null ;
110
113
ImageFrame < TPixel > ? previousFrame = null ;
114
+ GifDisposalMethod ? previousDisposalMethod = null ;
115
+
111
116
try
112
117
{
113
118
this . ReadLogicalScreenDescriptorAndGlobalColorTable ( stream ) ;
119
+ TPixel backgroundPixel = this . backgroundColor . ToPixel < TPixel > ( ) ;
114
120
115
121
// Loop though the respective gif parts and read the data.
116
122
int nextFlag = stream . ReadByte ( ) ;
@@ -123,7 +129,7 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
123
129
break ;
124
130
}
125
131
126
- this . ReadFrame ( stream , ref image , ref previousFrame ) ;
132
+ this . ReadFrame ( stream , ref image , ref previousFrame , ref previousDisposalMethod , backgroundPixel ) ;
127
133
128
134
// Reset per-frame state.
129
135
this . imageDescriptor = default ;
@@ -417,7 +423,14 @@ private void ReadComments(BufferedReadStream stream)
417
423
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
418
424
/// <param name="image">The image to decode the information to.</param>
419
425
/// <param name="previousFrame">The previous frame.</param>
420
- private void ReadFrame < TPixel > ( BufferedReadStream stream , ref Image < TPixel > ? image , ref ImageFrame < TPixel > ? previousFrame )
426
+ /// <param name="previousDisposalMethod">The previous disposal method.</param>
427
+ /// <param name="backgroundPixel">The background color pixel.</param>
428
+ private void ReadFrame < TPixel > (
429
+ BufferedReadStream stream ,
430
+ ref Image < TPixel > ? image ,
431
+ ref ImageFrame < TPixel > ? previousFrame ,
432
+ ref GifDisposalMethod ? previousDisposalMethod ,
433
+ TPixel backgroundPixel )
421
434
where TPixel : unmanaged, IPixel < TPixel >
422
435
{
423
436
this . ReadImageDescriptor ( stream ) ;
@@ -444,7 +457,7 @@ private void ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
444
457
}
445
458
446
459
ReadOnlySpan < Rgb24 > colorTable = MemoryMarshal . Cast < byte , Rgb24 > ( rawColorTable ) ;
447
- this . ReadFrameColors ( stream , ref image , ref previousFrame , colorTable , this . imageDescriptor ) ;
460
+ this . ReadFrameColors ( stream , ref image , ref previousFrame , ref previousDisposalMethod , colorTable , this . imageDescriptor , backgroundPixel ) ;
448
461
449
462
// Skip any remaining blocks
450
463
SkipBlock ( stream ) ;
@@ -457,57 +470,58 @@ private void ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
457
470
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
458
471
/// <param name="image">The image to decode the information to.</param>
459
472
/// <param name="previousFrame">The previous frame.</param>
473
+ /// <param name="previousDisposalMethod">The previous disposal method.</param>
460
474
/// <param name="colorTable">The color table containing the available colors.</param>
461
475
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
476
+ /// <param name="backgroundPixel">The background color pixel.</param>
462
477
private void ReadFrameColors < TPixel > (
463
478
BufferedReadStream stream ,
464
479
ref Image < TPixel > ? image ,
465
480
ref ImageFrame < TPixel > ? previousFrame ,
481
+ ref GifDisposalMethod ? previousDisposalMethod ,
466
482
ReadOnlySpan < Rgb24 > colorTable ,
467
- in GifImageDescriptor descriptor )
483
+ in GifImageDescriptor descriptor ,
484
+ TPixel backgroundPixel )
468
485
where TPixel : unmanaged, IPixel < TPixel >
469
486
{
470
487
int imageWidth = this . logicalScreenDescriptor . Width ;
471
488
int imageHeight = this . logicalScreenDescriptor . Height ;
472
489
bool transFlag = this . graphicsControlExtension . TransparencyFlag ;
473
490
474
- ImageFrame < TPixel > ? prevFrame = null ;
475
- ImageFrame < TPixel > ? currentFrame = null ;
476
- ImageFrame < TPixel > imageFrame ;
491
+ GifDisposalMethod disposalMethod = this . graphicsControlExtension . DisposalMethod ;
477
492
493
+ ImageFrame < TPixel > currentFrame ;
478
494
if ( previousFrame is null )
479
495
{
480
- if ( ! transFlag )
481
- {
482
- image = new Image < TPixel > ( this . configuration , imageWidth , imageHeight , Color . Black . ToPixel < TPixel > ( ) , this . metadata ) ;
483
- }
484
- else
485
- {
486
- // This initializes the image to become fully transparent because the alpha channel is zero.
487
- image = new Image < TPixel > ( this . configuration , imageWidth , imageHeight , this . metadata ) ;
488
- }
496
+ image = transFlag
497
+ ? new Image < TPixel > ( this . configuration , imageWidth , imageHeight , this . metadata )
498
+ : new Image < TPixel > ( this . configuration , imageWidth , imageHeight , backgroundPixel , this . metadata ) ;
489
499
490
500
this . SetFrameMetadata ( image . Frames . RootFrame . Metadata ) ;
491
-
492
- imageFrame = image . Frames . RootFrame ;
501
+ currentFrame = image . Frames . RootFrame ;
493
502
}
494
503
else
495
504
{
496
- if ( this . graphicsControlExtension . DisposalMethod == GifDisposalMethod . RestoreToPrevious )
497
- {
498
- prevFrame = previousFrame ;
499
- }
500
-
501
505
// We create a clone of the frame and add it.
502
506
// We will overpaint the difference of pixels on the current frame to create a complete image.
503
507
// This ensures that we have enough pixel data to process without distortion. #2450
504
508
currentFrame = image ! . Frames . AddFrame ( previousFrame ) ;
505
509
506
510
this . SetFrameMetadata ( currentFrame . Metadata ) ;
507
511
508
- imageFrame = currentFrame ;
512
+ if ( previousDisposalMethod == GifDisposalMethod . RestoreToBackground )
513
+ {
514
+ this . RestoreToBackground ( currentFrame , backgroundPixel , transFlag ) ;
515
+ }
516
+ }
517
+
518
+ Rectangle interest = Rectangle . Intersect ( image . Bounds , new ( descriptor . Left , descriptor . Top , descriptor . Width , descriptor . Height ) ) ;
519
+ previousFrame = currentFrame ;
520
+ previousDisposalMethod = disposalMethod ;
509
521
510
- this . RestoreToBackground ( imageFrame ) ;
522
+ if ( disposalMethod == GifDisposalMethod . RestoreToBackground )
523
+ {
524
+ this . restoreArea = interest ;
511
525
}
512
526
513
527
if ( colorTable . Length == 0 )
@@ -573,7 +587,7 @@ private void ReadFrameColors<TPixel>(
573
587
}
574
588
575
589
lzwDecoder . DecodePixelRow ( indicesRow ) ;
576
- ref TPixel rowRef = ref MemoryMarshal . GetReference ( imageFrame . PixelBuffer . DangerousGetRowSpan ( writeY ) ) ;
590
+ ref TPixel rowRef = ref MemoryMarshal . GetReference ( currentFrame . PixelBuffer . DangerousGetRowSpan ( writeY ) ) ;
577
591
578
592
if ( ! transFlag )
579
593
{
@@ -605,19 +619,6 @@ private void ReadFrameColors<TPixel>(
605
619
}
606
620
}
607
621
}
608
-
609
- if ( prevFrame != null )
610
- {
611
- previousFrame = prevFrame ;
612
- return ;
613
- }
614
-
615
- previousFrame = currentFrame ?? image . Frames . RootFrame ;
616
-
617
- if ( this . graphicsControlExtension . DisposalMethod == GifDisposalMethod . RestoreToBackground )
618
- {
619
- this . restoreArea = new Rectangle ( descriptor . Left , descriptor . Top , descriptor . Width , descriptor . Height ) ;
620
- }
621
622
}
622
623
623
624
/// <summary>
@@ -638,6 +639,11 @@ private void ReadFrameMetadata(BufferedReadStream stream, List<ImageFrameMetadat
638
639
this . currentLocalColorTable ??= this . configuration . MemoryAllocator . Allocate < byte > ( 768 , AllocationOptions . Clean ) ;
639
640
stream . Read ( this . currentLocalColorTable . GetSpan ( ) [ ..length ] ) ;
640
641
}
642
+ else
643
+ {
644
+ this . currentLocalColorTable = null ;
645
+ this . currentLocalColorTableSize = 0 ;
646
+ }
641
647
642
648
// Skip the frame indices. Pixels length + mincode size.
643
649
// The gif format does not tell us the length of the compressed data beforehand.
@@ -662,7 +668,9 @@ private void ReadFrameMetadata(BufferedReadStream stream, List<ImageFrameMetadat
662
668
/// </summary>
663
669
/// <typeparam name="TPixel">The pixel format.</typeparam>
664
670
/// <param name="frame">The frame.</param>
665
- private void RestoreToBackground < TPixel > ( ImageFrame < TPixel > frame )
671
+ /// <param name="background">The background color.</param>
672
+ /// <param name="transparent">Whether the background is transparent.</param>
673
+ private void RestoreToBackground < TPixel > ( ImageFrame < TPixel > frame , TPixel background , bool transparent )
666
674
where TPixel : unmanaged, IPixel < TPixel >
667
675
{
668
676
if ( this . restoreArea is null )
@@ -672,7 +680,14 @@ private void RestoreToBackground<TPixel>(ImageFrame<TPixel> frame)
672
680
673
681
Rectangle interest = Rectangle . Intersect ( frame . Bounds ( ) , this . restoreArea . Value ) ;
674
682
Buffer2DRegion < TPixel > pixelRegion = frame . PixelBuffer . GetRegion ( interest ) ;
675
- pixelRegion . Clear ( ) ;
683
+ if ( transparent )
684
+ {
685
+ pixelRegion . Clear ( ) ;
686
+ }
687
+ else
688
+ {
689
+ pixelRegion . Fill ( background ) ;
690
+ }
676
691
677
692
this . restoreArea = null ;
678
693
}
@@ -787,7 +802,18 @@ private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream s
787
802
}
788
803
}
789
804
790
- this . gifMetadata . BackgroundColorIndex = this . logicalScreenDescriptor . BackgroundColorIndex ;
805
+ // If the global color table is present, we can set the background color
806
+ // otherwise we default to transparent to match browser behavior.
807
+ ReadOnlyMemory < Color > ? table = this . gifMetadata . GlobalColorTable ;
808
+ byte index = this . logicalScreenDescriptor . BackgroundColorIndex ;
809
+ if ( table is not null && index < table . Value . Length )
810
+ {
811
+ this . backgroundColor = table . Value . Span [ index ] ;
812
+ }
813
+ else
814
+ {
815
+ this . backgroundColor = Color . Transparent ;
816
+ }
791
817
}
792
818
793
819
private unsafe struct ScratchBuffer
0 commit comments