@@ -89,8 +89,10 @@ internal sealed class GifDecoderCore : ImageDecoderCore
8989 /// </summary>
9090 private GifMetadata ? gifMetadata ;
9191
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 ;
9496
9597 /// <summary>
9698 /// Initializes a new instance of the <see cref="GifDecoderCore"/> class.
@@ -113,11 +115,11 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
113115 ImageFrame < TPixel > ? previousFrame = null ;
114116 GifDisposalMethod ? previousDisposalMethod = null ;
115117 bool globalColorTableUsed = false ;
118+ Color backgroundColor = Color . Transparent ;
116119
117120 try
118121 {
119122 this . ReadLogicalScreenDescriptorAndGlobalColorTable ( stream ) ;
120- TPixel backgroundPixel = this . backgroundColor . ToPixel < TPixel > ( ) ;
121123
122124 // Loop though the respective gif parts and read the data.
123125 int nextFlag = stream . ReadByte ( ) ;
@@ -130,7 +132,7 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
130132 break ;
131133 }
132134
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 ) ;
134136
135137 // Reset per-frame state.
136138 this . imageDescriptor = default ;
@@ -432,13 +434,13 @@ private void ReadComments(BufferedReadStream stream)
432434 /// <param name="image">The image to decode the information to.</param>
433435 /// <param name="previousFrame">The previous frame.</param>
434436 /// <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>
436438 private bool ReadFrame < TPixel > (
437439 BufferedReadStream stream ,
438440 ref Image < TPixel > ? image ,
439441 ref ImageFrame < TPixel > ? previousFrame ,
440442 ref GifDisposalMethod ? previousDisposalMethod ,
441- TPixel backgroundPixel )
443+ ref Color backgroundColor )
442444 where TPixel : unmanaged, IPixel < TPixel >
443445 {
444446 this . ReadImageDescriptor ( stream ) ;
@@ -465,7 +467,47 @@ private bool ReadFrame<TPixel>(
465467 }
466468
467469 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+ }
469511
470512 // Skip any remaining blocks
471513 SkipBlock ( stream ) ;
@@ -497,11 +539,11 @@ private void ReadFrameColors<TPixel>(
497539 int imageWidth = this . logicalScreenDescriptor . Width ;
498540 int imageHeight = this . logicalScreenDescriptor . Height ;
499541 bool transFlag = this . graphicsControlExtension . TransparencyFlag ;
500-
501542 GifDisposalMethod disposalMethod = this . graphicsControlExtension . DisposalMethod ;
502-
503543 ImageFrame < TPixel > currentFrame ;
504- if ( previousFrame is null )
544+ ImageFrame < TPixel > ? restoreFrame = null ;
545+
546+ if ( previousFrame is null && previousDisposalMethod is null )
505547 {
506548 image = transFlag
507549 ? new Image < TPixel > ( this . configuration , imageWidth , imageHeight , this . metadata )
@@ -512,26 +554,42 @@ private void ReadFrameColors<TPixel>(
512554 }
513555 else
514556 {
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+ }
519565
520566 this . SetFrameMetadata ( currentFrame . Metadata ) ;
521567
568+ if ( this . graphicsControlExtension . DisposalMethod == GifDisposalMethod . RestoreToPrevious )
569+ {
570+ restoreFrame = previousFrame ;
571+ }
572+
522573 if ( previousDisposalMethod == GifDisposalMethod . RestoreToBackground )
523574 {
524575 this . RestoreToBackground ( currentFrame , backgroundPixel , transFlag ) ;
525576 }
526577 }
527578
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+
530588 previousDisposalMethod = disposalMethod ;
531589
532590 if ( disposalMethod == GifDisposalMethod . RestoreToBackground )
533591 {
534- this . restoreArea = interest ;
592+ this . restoreArea = Rectangle . Intersect ( image . Bounds , new ( descriptor . Left , descriptor . Top , descriptor . Width , descriptor . Height ) ) ;
535593 }
536594
537595 if ( colorTable . Length == 0 )
@@ -812,19 +870,9 @@ private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream s
812870 }
813871 }
814872
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 ;
818873 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 ;
828876 }
829877
830878 private unsafe struct ScratchBuffer
0 commit comments