@@ -89,8 +89,10 @@ 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 ;
92
+ /// <summary>
93
+ /// The background color index.
94
+ /// </summary>
95
+ private byte backgroundColorIndex ;
94
96
95
97
/// <summary>
96
98
/// Initializes a new instance of the <see cref="GifDecoderCore"/> class.
@@ -113,11 +115,11 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
113
115
ImageFrame < TPixel > ? previousFrame = null ;
114
116
GifDisposalMethod ? previousDisposalMethod = null ;
115
117
bool globalColorTableUsed = false ;
118
+ Color backgroundColor = Color . Transparent ;
116
119
117
120
try
118
121
{
119
122
this . ReadLogicalScreenDescriptorAndGlobalColorTable ( stream ) ;
120
- TPixel backgroundPixel = this . backgroundColor . ToPixel < TPixel > ( ) ;
121
123
122
124
// Loop though the respective gif parts and read the data.
123
125
int nextFlag = stream . ReadByte ( ) ;
@@ -130,7 +132,7 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
130
132
break ;
131
133
}
132
134
133
- globalColorTableUsed |= this . ReadFrame ( stream , ref image , ref previousFrame , ref previousDisposalMethod , backgroundPixel ) ;
135
+ globalColorTableUsed |= this . ReadFrame ( stream , ref image , ref previousFrame , ref previousDisposalMethod , ref backgroundColor ) ;
134
136
135
137
// Reset per-frame state.
136
138
this . imageDescriptor = default ;
@@ -432,13 +434,13 @@ private void ReadComments(BufferedReadStream stream)
432
434
/// <param name="image">The image to decode the information to.</param>
433
435
/// <param name="previousFrame">The previous frame.</param>
434
436
/// <param name="previousDisposalMethod">The previous disposal method.</param>
435
- /// <param name="backgroundPixel ">The background color pixel .</param>
437
+ /// <param name="backgroundColor ">The background color.</param>
436
438
private bool ReadFrame < TPixel > (
437
439
BufferedReadStream stream ,
438
440
ref Image < TPixel > ? image ,
439
441
ref ImageFrame < TPixel > ? previousFrame ,
440
442
ref GifDisposalMethod ? previousDisposalMethod ,
441
- TPixel backgroundPixel )
443
+ ref Color backgroundColor )
442
444
where TPixel : unmanaged, IPixel < TPixel >
443
445
{
444
446
this . ReadImageDescriptor ( stream ) ;
@@ -465,7 +467,47 @@ private bool ReadFrame<TPixel>(
465
467
}
466
468
467
469
ReadOnlySpan < Rgb24 > colorTable = MemoryMarshal . Cast < byte , Rgb24 > ( rawColorTable ) ;
468
- this . ReadFrameColors ( stream , ref image , ref previousFrame , ref previousDisposalMethod , colorTable , this . imageDescriptor , backgroundPixel ) ;
470
+
471
+ // First frame
472
+ if ( image is null )
473
+ {
474
+ if ( this . backgroundColorIndex < colorTable . Length )
475
+ {
476
+ backgroundColor = colorTable [ this . backgroundColorIndex ] ;
477
+ }
478
+ else
479
+ {
480
+ backgroundColor = Color . Transparent ;
481
+ }
482
+
483
+ if ( this . graphicsControlExtension . TransparencyFlag )
484
+ {
485
+ backgroundColor = backgroundColor . WithAlpha ( 0 ) ;
486
+ }
487
+ }
488
+
489
+ this . ReadFrameColors ( stream , ref image , ref previousFrame , ref previousDisposalMethod , colorTable , this . imageDescriptor , backgroundColor . ToPixel < TPixel > ( ) ) ;
490
+
491
+ // Update from newly decoded frame.
492
+ if ( this . graphicsControlExtension . DisposalMethod != GifDisposalMethod . RestoreToPrevious )
493
+ {
494
+ if ( this . backgroundColorIndex < colorTable . Length )
495
+ {
496
+ backgroundColor = colorTable [ this . backgroundColorIndex ] ;
497
+ }
498
+ else
499
+ {
500
+ backgroundColor = Color . Transparent ;
501
+ }
502
+
503
+ // TODO: I don't understand why this is always set to alpha of zero.
504
+ // This should be dependent on the transparency flag of the graphics
505
+ // control extension. ImageMagick does the same.
506
+ // if (this.graphicsControlExtension.TransparencyFlag)
507
+ {
508
+ backgroundColor = backgroundColor . WithAlpha ( 0 ) ;
509
+ }
510
+ }
469
511
470
512
// Skip any remaining blocks
471
513
SkipBlock ( stream ) ;
@@ -497,11 +539,11 @@ private void ReadFrameColors<TPixel>(
497
539
int imageWidth = this . logicalScreenDescriptor . Width ;
498
540
int imageHeight = this . logicalScreenDescriptor . Height ;
499
541
bool transFlag = this . graphicsControlExtension . TransparencyFlag ;
500
-
501
542
GifDisposalMethod disposalMethod = this . graphicsControlExtension . DisposalMethod ;
502
-
503
543
ImageFrame < TPixel > currentFrame ;
504
- if ( previousFrame is null )
544
+ ImageFrame < TPixel > ? restoreFrame = null ;
545
+
546
+ if ( previousFrame is null && previousDisposalMethod is null )
505
547
{
506
548
image = transFlag
507
549
? new Image < TPixel > ( this . configuration , imageWidth , imageHeight , this . metadata )
@@ -512,26 +554,42 @@ private void ReadFrameColors<TPixel>(
512
554
}
513
555
else
514
556
{
515
- // We create a clone of the frame and add it.
516
- // We will overpaint the difference of pixels on the current frame to create a complete image.
517
- // This ensures that we have enough pixel data to process without distortion. #2450
518
- currentFrame = image ! . Frames . AddFrame ( previousFrame ) ;
557
+ if ( previousFrame != null )
558
+ {
559
+ currentFrame = image ! . Frames . AddFrame ( previousFrame ) ;
560
+ }
561
+ else
562
+ {
563
+ currentFrame = image ! . Frames . CreateFrame ( backgroundPixel ) ;
564
+ }
519
565
520
566
this . SetFrameMetadata ( currentFrame . Metadata ) ;
521
567
568
+ if ( this . graphicsControlExtension . DisposalMethod == GifDisposalMethod . RestoreToPrevious )
569
+ {
570
+ restoreFrame = previousFrame ;
571
+ }
572
+
522
573
if ( previousDisposalMethod == GifDisposalMethod . RestoreToBackground )
523
574
{
524
575
this . RestoreToBackground ( currentFrame , backgroundPixel , transFlag ) ;
525
576
}
526
577
}
527
578
528
- Rectangle interest = Rectangle . Intersect ( image . Bounds , new ( descriptor . Left , descriptor . Top , descriptor . Width , descriptor . Height ) ) ;
529
- previousFrame = currentFrame ;
579
+ if ( this . graphicsControlExtension . DisposalMethod == GifDisposalMethod . RestoreToPrevious )
580
+ {
581
+ previousFrame = restoreFrame ;
582
+ }
583
+ else
584
+ {
585
+ previousFrame = currentFrame ;
586
+ }
587
+
530
588
previousDisposalMethod = disposalMethod ;
531
589
532
590
if ( disposalMethod == GifDisposalMethod . RestoreToBackground )
533
591
{
534
- this . restoreArea = interest ;
592
+ this . restoreArea = Rectangle . Intersect ( image . Bounds , new ( descriptor . Left , descriptor . Top , descriptor . Width , descriptor . Height ) ) ;
535
593
}
536
594
537
595
if ( colorTable . Length == 0 )
@@ -812,19 +870,9 @@ private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream s
812
870
}
813
871
}
814
872
815
- // If the global color table is present, we can set the background color
816
- // otherwise we default to transparent to match browser behavior.
817
- ReadOnlyMemory < Color > ? table = this . gifMetadata . GlobalColorTable ;
818
873
byte index = this . logicalScreenDescriptor . BackgroundColorIndex ;
819
- if ( table is not null && index < table . Value . Length )
820
- {
821
- this . backgroundColor = table . Value . Span [ index ] ;
822
- this . gifMetadata . BackgroundColorIndex = index ;
823
- }
824
- else
825
- {
826
- this . backgroundColor = Color . Transparent ;
827
- }
874
+ this . backgroundColorIndex = index ;
875
+ this . gifMetadata . BackgroundColorIndex = index ;
828
876
}
829
877
830
878
private unsafe struct ScratchBuffer
0 commit comments