@@ -89,6 +89,11 @@ internal sealed class GifDecoderCore : ImageDecoderCore
8989 /// </summary>
9090 private GifMetadata ? gifMetadata ;
9191
92+ /// <summary>
93+ /// The background color index.
94+ /// </summary>
95+ private byte backgroundColorIndex ;
96+
9297 /// <summary>
9398 /// Initializes a new instance of the <see cref="GifDecoderCore"/> class.
9499 /// </summary>
@@ -108,6 +113,10 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
108113 uint frameCount = 0 ;
109114 Image < TPixel > ? image = null ;
110115 ImageFrame < TPixel > ? previousFrame = null ;
116+ GifDisposalMethod ? previousDisposalMethod = null ;
117+ bool globalColorTableUsed = false ;
118+ Color backgroundColor = Color . Transparent ;
119+
111120 try
112121 {
113122 this . ReadLogicalScreenDescriptorAndGlobalColorTable ( stream ) ;
@@ -123,7 +132,7 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
123132 break ;
124133 }
125134
126- this . ReadFrame ( stream , ref image , ref previousFrame ) ;
135+ globalColorTableUsed |= this . ReadFrame ( stream , ref image , ref previousFrame , ref previousDisposalMethod , ref backgroundColor ) ;
127136
128137 // Reset per-frame state.
129138 this . imageDescriptor = default ;
@@ -158,6 +167,13 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
158167 break ;
159168 }
160169 }
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+ }
161177 }
162178 finally
163179 {
@@ -417,7 +433,14 @@ private void ReadComments(BufferedReadStream stream)
417433 /// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
418434 /// <param name="image">The image to decode the information to.</param>
419435 /// <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 )
421444 where TPixel : unmanaged, IPixel < TPixel >
422445 {
423446 this . ReadImageDescriptor ( stream ) ;
@@ -444,10 +467,52 @@ private void ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
444467 }
445468
446469 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+ }
448511
449512 // Skip any remaining blocks
450513 SkipBlock ( stream ) ;
514+
515+ return ! hasLocalColorTable ;
451516 }
452517
453518 /// <summary>
@@ -457,57 +522,74 @@ private void ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
457522 /// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
458523 /// <param name="image">The image to decode the information to.</param>
459524 /// <param name="previousFrame">The previous frame.</param>
525+ /// <param name="previousDisposalMethod">The previous disposal method.</param>
460526 /// <param name="colorTable">The color table containing the available colors.</param>
461527 /// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
528+ /// <param name="backgroundPixel">The background color pixel.</param>
462529 private void ReadFrameColors < TPixel > (
463530 BufferedReadStream stream ,
464531 ref Image < TPixel > ? image ,
465532 ref ImageFrame < TPixel > ? previousFrame ,
533+ ref GifDisposalMethod ? previousDisposalMethod ,
466534 ReadOnlySpan < Rgb24 > colorTable ,
467- in GifImageDescriptor descriptor )
535+ in GifImageDescriptor descriptor ,
536+ TPixel backgroundPixel )
468537 where TPixel : unmanaged, IPixel < TPixel >
469538 {
470539 int imageWidth = this . logicalScreenDescriptor . Width ;
471540 int imageHeight = this . logicalScreenDescriptor . Height ;
472541 bool transFlag = this . graphicsControlExtension . TransparencyFlag ;
542+ GifDisposalMethod disposalMethod = this . graphicsControlExtension . DisposalMethod ;
543+ ImageFrame < TPixel > currentFrame ;
544+ ImageFrame < TPixel > ? restoreFrame = null ;
473545
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 ) ;
477551
478- if ( previousFrame is null )
552+ this . SetFrameMetadata ( image . Frames . RootFrame . Metadata ) ;
553+ currentFrame = image . Frames . RootFrame ;
554+ }
555+ else
479556 {
480- if ( ! transFlag )
557+ if ( previousFrame != null )
481558 {
482- image = new Image < TPixel > ( this . configuration , imageWidth , imageHeight , Color . Black . ToPixel < TPixel > ( ) , this . metadata ) ;
559+ currentFrame = image ! . Frames . AddFrame ( previousFrame ) ;
483560 }
484561 else
485562 {
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 ) ;
488564 }
489565
490- this . SetFrameMetadata ( image . Frames . RootFrame . Metadata ) ;
566+ this . SetFrameMetadata ( currentFrame . Metadata ) ;
491567
492- imageFrame = image . Frames . RootFrame ;
493- }
494- else
495- {
496568 if ( this . graphicsControlExtension . DisposalMethod == GifDisposalMethod . RestoreToPrevious )
497569 {
498- prevFrame = previousFrame ;
570+ restoreFrame = previousFrame ;
499571 }
500572
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+ }
505578
506- this . SetFrameMetadata ( currentFrame . Metadata ) ;
579+ if ( this . graphicsControlExtension . DisposalMethod == GifDisposalMethod . RestoreToPrevious )
580+ {
581+ previousFrame = restoreFrame ;
582+ }
583+ else
584+ {
585+ previousFrame = currentFrame ;
586+ }
507587
508- imageFrame = currentFrame ;
588+ previousDisposalMethod = disposalMethod ;
509589
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 ) ) ;
511593 }
512594
513595 if ( colorTable . Length == 0 )
@@ -573,7 +655,7 @@ private void ReadFrameColors<TPixel>(
573655 }
574656
575657 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 ) ) ;
577659
578660 if ( ! transFlag )
579661 {
@@ -605,19 +687,6 @@ private void ReadFrameColors<TPixel>(
605687 }
606688 }
607689 }
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- }
621690 }
622691
623692 /// <summary>
@@ -638,6 +707,11 @@ private void ReadFrameMetadata(BufferedReadStream stream, List<ImageFrameMetadat
638707 this . currentLocalColorTable ??= this . configuration . MemoryAllocator . Allocate < byte > ( 768 , AllocationOptions . Clean ) ;
639708 stream . Read ( this . currentLocalColorTable . GetSpan ( ) [ ..length ] ) ;
640709 }
710+ else
711+ {
712+ this . currentLocalColorTable = null ;
713+ this . currentLocalColorTableSize = 0 ;
714+ }
641715
642716 // Skip the frame indices. Pixels length + mincode size.
643717 // 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
662736 /// </summary>
663737 /// <typeparam name="TPixel">The pixel format.</typeparam>
664738 /// <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 )
666742 where TPixel : unmanaged, IPixel < TPixel >
667743 {
668744 if ( this . restoreArea is null )
@@ -672,7 +748,14 @@ private void RestoreToBackground<TPixel>(ImageFrame<TPixel> frame)
672748
673749 Rectangle interest = Rectangle . Intersect ( frame . Bounds ( ) , this . restoreArea . Value ) ;
674750 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+ }
676759
677760 this . restoreArea = null ;
678761 }
@@ -787,7 +870,9 @@ private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream s
787870 }
788871 }
789872
790- this . gifMetadata . BackgroundColorIndex = this . logicalScreenDescriptor . BackgroundColorIndex ;
873+ byte index = this . logicalScreenDescriptor . BackgroundColorIndex ;
874+ this . backgroundColorIndex = index ;
875+ this . gifMetadata . BackgroundColorIndex = index ;
791876 }
792877
793878 private unsafe struct ScratchBuffer
0 commit comments