@@ -89,6 +89,11 @@ internal sealed class GifDecoderCore : ImageDecoderCore
89
89
/// </summary>
90
90
private GifMetadata ? gifMetadata ;
91
91
92
+ /// <summary>
93
+ /// The background color index.
94
+ /// </summary>
95
+ private byte backgroundColorIndex ;
96
+
92
97
/// <summary>
93
98
/// Initializes a new instance of the <see cref="GifDecoderCore"/> class.
94
99
/// </summary>
@@ -108,6 +113,10 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
108
113
uint frameCount = 0 ;
109
114
Image < TPixel > ? image = null ;
110
115
ImageFrame < TPixel > ? previousFrame = null ;
116
+ GifDisposalMethod ? previousDisposalMethod = null ;
117
+ bool globalColorTableUsed = false ;
118
+ Color backgroundColor = Color . Transparent ;
119
+
111
120
try
112
121
{
113
122
this . ReadLogicalScreenDescriptorAndGlobalColorTable ( stream ) ;
@@ -123,7 +132,7 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
123
132
break ;
124
133
}
125
134
126
- this . ReadFrame ( stream , ref image , ref previousFrame ) ;
135
+ globalColorTableUsed |= this . ReadFrame ( stream , ref image , ref previousFrame , ref previousDisposalMethod , ref backgroundColor ) ;
127
136
128
137
// Reset per-frame state.
129
138
this . imageDescriptor = default ;
@@ -158,6 +167,13 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
158
167
break ;
159
168
}
160
169
}
170
+
171
+ // We cannot always trust the global GIF palette has actually been used.
172
+ // https://github.com/SixLabors/ImageSharp/issues/2866
173
+ if ( ! globalColorTableUsed )
174
+ {
175
+ this . gifMetadata . ColorTableMode = GifColorTableMode . Local ;
176
+ }
161
177
}
162
178
finally
163
179
{
@@ -417,7 +433,14 @@ private void ReadComments(BufferedReadStream stream)
417
433
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
418
434
/// <param name="image">The image to decode the information to.</param>
419
435
/// <param name="previousFrame">The previous frame.</param>
420
- private void ReadFrame < TPixel > ( BufferedReadStream stream , ref Image < TPixel > ? image , ref ImageFrame < TPixel > ? previousFrame )
436
+ /// <param name="previousDisposalMethod">The previous disposal method.</param>
437
+ /// <param name="backgroundColor">The background color.</param>
438
+ private bool ReadFrame < TPixel > (
439
+ BufferedReadStream stream ,
440
+ ref Image < TPixel > ? image ,
441
+ ref ImageFrame < TPixel > ? previousFrame ,
442
+ ref GifDisposalMethod ? previousDisposalMethod ,
443
+ ref Color backgroundColor )
421
444
where TPixel : unmanaged, IPixel < TPixel >
422
445
{
423
446
this . ReadImageDescriptor ( stream ) ;
@@ -444,10 +467,52 @@ private void ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
444
467
}
445
468
446
469
ReadOnlySpan < Rgb24 > colorTable = MemoryMarshal . Cast < byte , Rgb24 > ( rawColorTable ) ;
447
- this . ReadFrameColors ( stream , ref image , ref previousFrame , colorTable , this . imageDescriptor ) ;
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
+ }
448
511
449
512
// Skip any remaining blocks
450
513
SkipBlock ( stream ) ;
514
+
515
+ return ! hasLocalColorTable ;
451
516
}
452
517
453
518
/// <summary>
@@ -457,57 +522,74 @@ private void ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
457
522
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
458
523
/// <param name="image">The image to decode the information to.</param>
459
524
/// <param name="previousFrame">The previous frame.</param>
525
+ /// <param name="previousDisposalMethod">The previous disposal method.</param>
460
526
/// <param name="colorTable">The color table containing the available colors.</param>
461
527
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
528
+ /// <param name="backgroundPixel">The background color pixel.</param>
462
529
private void ReadFrameColors < TPixel > (
463
530
BufferedReadStream stream ,
464
531
ref Image < TPixel > ? image ,
465
532
ref ImageFrame < TPixel > ? previousFrame ,
533
+ ref GifDisposalMethod ? previousDisposalMethod ,
466
534
ReadOnlySpan < Rgb24 > colorTable ,
467
- in GifImageDescriptor descriptor )
535
+ in GifImageDescriptor descriptor ,
536
+ TPixel backgroundPixel )
468
537
where TPixel : unmanaged, IPixel < TPixel >
469
538
{
470
539
int imageWidth = this . logicalScreenDescriptor . Width ;
471
540
int imageHeight = this . logicalScreenDescriptor . Height ;
472
541
bool transFlag = this . graphicsControlExtension . TransparencyFlag ;
542
+ GifDisposalMethod disposalMethod = this . graphicsControlExtension . DisposalMethod ;
543
+ ImageFrame < TPixel > currentFrame ;
544
+ ImageFrame < TPixel > ? restoreFrame = null ;
473
545
474
- ImageFrame < TPixel > ? prevFrame = null ;
475
- ImageFrame < TPixel > ? currentFrame = null ;
476
- ImageFrame < TPixel > imageFrame ;
546
+ if ( previousFrame is null && previousDisposalMethod is null )
547
+ {
548
+ image = transFlag
549
+ ? new Image < TPixel > ( this . configuration , imageWidth , imageHeight , this . metadata )
550
+ : new Image < TPixel > ( this . configuration , imageWidth , imageHeight , backgroundPixel , this . metadata ) ;
477
551
478
- if ( previousFrame is null )
552
+ this . SetFrameMetadata ( image . Frames . RootFrame . Metadata ) ;
553
+ currentFrame = image . Frames . RootFrame ;
554
+ }
555
+ else
479
556
{
480
- if ( ! transFlag )
557
+ if ( previousFrame != null )
481
558
{
482
- image = new Image < TPixel > ( this . configuration , imageWidth , imageHeight , Color . Black . ToPixel < TPixel > ( ) , this . metadata ) ;
559
+ currentFrame = image ! . Frames . AddFrame ( previousFrame ) ;
483
560
}
484
561
else
485
562
{
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 ) ;
563
+ currentFrame = image ! . Frames . CreateFrame ( backgroundPixel ) ;
488
564
}
489
565
490
- this . SetFrameMetadata ( image . Frames . RootFrame . Metadata ) ;
566
+ this . SetFrameMetadata ( currentFrame . Metadata ) ;
491
567
492
- imageFrame = image . Frames . RootFrame ;
493
- }
494
- else
495
- {
496
568
if ( this . graphicsControlExtension . DisposalMethod == GifDisposalMethod . RestoreToPrevious )
497
569
{
498
- prevFrame = previousFrame ;
570
+ restoreFrame = previousFrame ;
499
571
}
500
572
501
- // We create a clone of the frame and add it.
502
- // We will overpaint the difference of pixels on the current frame to create a complete image.
503
- // This ensures that we have enough pixel data to process without distortion. #2450
504
- currentFrame = image ! . Frames . AddFrame ( previousFrame ) ;
573
+ if ( previousDisposalMethod == GifDisposalMethod . RestoreToBackground )
574
+ {
575
+ this . RestoreToBackground ( currentFrame , backgroundPixel , transFlag ) ;
576
+ }
577
+ }
505
578
506
- this . SetFrameMetadata ( currentFrame . Metadata ) ;
579
+ if ( this . graphicsControlExtension . DisposalMethod == GifDisposalMethod . RestoreToPrevious )
580
+ {
581
+ previousFrame = restoreFrame ;
582
+ }
583
+ else
584
+ {
585
+ previousFrame = currentFrame ;
586
+ }
507
587
508
- imageFrame = currentFrame ;
588
+ previousDisposalMethod = disposalMethod ;
509
589
510
- this . RestoreToBackground ( imageFrame ) ;
590
+ if ( disposalMethod == GifDisposalMethod . RestoreToBackground )
591
+ {
592
+ this . restoreArea = Rectangle . Intersect ( image . Bounds , new ( descriptor . Left , descriptor . Top , descriptor . Width , descriptor . Height ) ) ;
511
593
}
512
594
513
595
if ( colorTable . Length == 0 )
@@ -573,7 +655,7 @@ private void ReadFrameColors<TPixel>(
573
655
}
574
656
575
657
lzwDecoder . DecodePixelRow ( indicesRow ) ;
576
- ref TPixel rowRef = ref MemoryMarshal . GetReference ( imageFrame . PixelBuffer . DangerousGetRowSpan ( writeY ) ) ;
658
+ ref TPixel rowRef = ref MemoryMarshal . GetReference ( currentFrame . PixelBuffer . DangerousGetRowSpan ( writeY ) ) ;
577
659
578
660
if ( ! transFlag )
579
661
{
@@ -605,19 +687,6 @@ private void ReadFrameColors<TPixel>(
605
687
}
606
688
}
607
689
}
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
690
}
622
691
623
692
/// <summary>
@@ -638,6 +707,11 @@ private void ReadFrameMetadata(BufferedReadStream stream, List<ImageFrameMetadat
638
707
this . currentLocalColorTable ??= this . configuration . MemoryAllocator . Allocate < byte > ( 768 , AllocationOptions . Clean ) ;
639
708
stream . Read ( this . currentLocalColorTable . GetSpan ( ) [ ..length ] ) ;
640
709
}
710
+ else
711
+ {
712
+ this . currentLocalColorTable = null ;
713
+ this . currentLocalColorTableSize = 0 ;
714
+ }
641
715
642
716
// Skip the frame indices. Pixels length + mincode size.
643
717
// The gif format does not tell us the length of the compressed data beforehand.
@@ -662,7 +736,9 @@ private void ReadFrameMetadata(BufferedReadStream stream, List<ImageFrameMetadat
662
736
/// </summary>
663
737
/// <typeparam name="TPixel">The pixel format.</typeparam>
664
738
/// <param name="frame">The frame.</param>
665
- private void RestoreToBackground < TPixel > ( ImageFrame < TPixel > frame )
739
+ /// <param name="background">The background color.</param>
740
+ /// <param name="transparent">Whether the background is transparent.</param>
741
+ private void RestoreToBackground < TPixel > ( ImageFrame < TPixel > frame , TPixel background , bool transparent )
666
742
where TPixel : unmanaged, IPixel < TPixel >
667
743
{
668
744
if ( this . restoreArea is null )
@@ -672,7 +748,14 @@ private void RestoreToBackground<TPixel>(ImageFrame<TPixel> frame)
672
748
673
749
Rectangle interest = Rectangle . Intersect ( frame . Bounds ( ) , this . restoreArea . Value ) ;
674
750
Buffer2DRegion < TPixel > pixelRegion = frame . PixelBuffer . GetRegion ( interest ) ;
675
- pixelRegion . Clear ( ) ;
751
+ if ( transparent )
752
+ {
753
+ pixelRegion . Clear ( ) ;
754
+ }
755
+ else
756
+ {
757
+ pixelRegion . Fill ( background ) ;
758
+ }
676
759
677
760
this . restoreArea = null ;
678
761
}
@@ -787,7 +870,9 @@ private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream s
787
870
}
788
871
}
789
872
790
- this . gifMetadata . BackgroundColorIndex = this . logicalScreenDescriptor . BackgroundColorIndex ;
873
+ byte index = this . logicalScreenDescriptor . BackgroundColorIndex ;
874
+ this . backgroundColorIndex = index ;
875
+ this . gifMetadata . BackgroundColorIndex = index ;
791
876
}
792
877
793
878
private unsafe struct ScratchBuffer
0 commit comments