@@ -422,68 +422,49 @@ private void ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
422422 {
423423 this . ReadImageDescriptor ( stream ) ;
424424
425- Buffer2D < byte > ? indices = null ;
426- try
427- {
428- // Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
429- bool hasLocalColorTable = this . imageDescriptor . LocalColorTableFlag ;
430-
431- if ( hasLocalColorTable )
432- {
433- // Read and store the local color table. We allocate the maximum possible size and slice to match.
434- int length = this . currentLocalColorTableSize = this . imageDescriptor . LocalColorTableSize * 3 ;
435- this . currentLocalColorTable ??= this . configuration . MemoryAllocator . Allocate < byte > ( 768 , AllocationOptions . Clean ) ;
436- stream . Read ( this . currentLocalColorTable . GetSpan ( ) [ ..length ] ) ;
437- }
438-
439- indices = this . configuration . MemoryAllocator . Allocate2D < byte > ( this . imageDescriptor . Width , this . imageDescriptor . Height , AllocationOptions . Clean ) ;
440- this . ReadFrameIndices ( stream , indices ) ;
425+ // Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
426+ bool hasLocalColorTable = this . imageDescriptor . LocalColorTableFlag ;
441427
442- Span < byte > rawColorTable = default ;
443- if ( hasLocalColorTable )
444- {
445- rawColorTable = this . currentLocalColorTable ! . GetSpan ( ) [ ..this . currentLocalColorTableSize ] ;
446- }
447- else if ( this . globalColorTable != null )
448- {
449- rawColorTable = this . globalColorTable . GetSpan ( ) ;
450- }
451-
452- ReadOnlySpan < Rgb24 > colorTable = MemoryMarshal . Cast < byte , Rgb24 > ( rawColorTable ) ;
453- this . ReadFrameColors ( ref image , ref previousFrame , indices , colorTable , this . imageDescriptor ) ;
428+ if ( hasLocalColorTable )
429+ {
430+ // Read and store the local color table. We allocate the maximum possible size and slice to match.
431+ int length = this . currentLocalColorTableSize = this . imageDescriptor . LocalColorTableSize * 3 ;
432+ this . currentLocalColorTable ??= this . configuration . MemoryAllocator . Allocate < byte > ( 768 , AllocationOptions . Clean ) ;
433+ stream . Read ( this . currentLocalColorTable . GetSpan ( ) [ ..length ] ) ;
434+ }
454435
455- // Skip any remaining blocks
456- SkipBlock ( stream ) ;
436+ Span < byte > rawColorTable = default ;
437+ if ( hasLocalColorTable )
438+ {
439+ rawColorTable = this . currentLocalColorTable ! . GetSpan ( ) [ ..this . currentLocalColorTableSize ] ;
457440 }
458- finally
441+ else if ( this . globalColorTable != null )
459442 {
460- indices ? . Dispose ( ) ;
443+ rawColorTable = this . globalColorTable . GetSpan ( ) ;
461444 }
462- }
463445
464- /// <summary>
465- /// Reads the frame indices marking the color to use for each pixel.
466- /// </summary>
467- /// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
468- /// <param name="indices">The 2D pixel buffer to write to.</param>
469- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
470- private void ReadFrameIndices ( BufferedReadStream stream , Buffer2D < byte > indices )
471- {
472- int minCodeSize = stream . ReadByte ( ) ;
473- using LzwDecoder lzwDecoder = new ( this . configuration . MemoryAllocator , stream ) ;
474- lzwDecoder . DecodePixels ( minCodeSize , indices ) ;
446+ ReadOnlySpan < Rgb24 > colorTable = MemoryMarshal . Cast < byte , Rgb24 > ( rawColorTable ) ;
447+ this . ReadFrameColors ( stream , ref image , ref previousFrame , colorTable , this . imageDescriptor ) ;
448+
449+ // Skip any remaining blocks
450+ SkipBlock ( stream ) ;
475451 }
476452
477453 /// <summary>
478454 /// Reads the frames colors, mapping indices to colors.
479455 /// </summary>
480456 /// <typeparam name="TPixel">The pixel format.</typeparam>
457+ /// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
481458 /// <param name="image">The image to decode the information to.</param>
482459 /// <param name="previousFrame">The previous frame.</param>
483- /// <param name="indices">The indexed pixels.</param>
484460 /// <param name="colorTable">The color table containing the available colors.</param>
485461 /// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
486- private void ReadFrameColors < TPixel > ( ref Image < TPixel > ? image , ref ImageFrame < TPixel > ? previousFrame , Buffer2D < byte > indices , ReadOnlySpan < Rgb24 > colorTable , in GifImageDescriptor descriptor )
462+ private void ReadFrameColors < TPixel > (
463+ BufferedReadStream stream ,
464+ ref Image < TPixel > ? image ,
465+ ref ImageFrame < TPixel > ? previousFrame ,
466+ ReadOnlySpan < Rgb24 > colorTable ,
467+ in GifImageDescriptor descriptor )
487468 where TPixel : unmanaged, IPixel < TPixel >
488469 {
489470 int imageWidth = this . logicalScreenDescriptor . Width ;
@@ -544,73 +525,83 @@ private void ReadFrameColors<TPixel>(ref Image<TPixel>? image, ref ImageFrame<TP
544525 byte transIndex = this . graphicsControlExtension . TransparencyIndex ;
545526 int colorTableMaxIdx = colorTable . Length - 1 ;
546527
547- for ( int y = descriptorTop ; y < descriptorBottom && y < imageHeight ; y ++ )
528+ // For a properly encoded gif the descriptor dimensions will never exceed the logical screen dimensions.
529+ // However we have images that exceed this that can be decoded by other libraries. #1530
530+ using IMemoryOwner < byte > indicesRowOwner = this . memoryAllocator . Allocate < byte > ( descriptor . Width ) ;
531+ Span < byte > indicesRow = indicesRowOwner . Memory . Span ;
532+ ref byte indicesRowRef = ref MemoryMarshal . GetReference ( indicesRow ) ;
533+
534+ int minCodeSize = stream . ReadByte ( ) ;
535+ if ( LzwDecoder . IsValidMinCodeSize ( minCodeSize ) )
548536 {
549- ref byte indicesRowRef = ref MemoryMarshal . GetReference ( indices . DangerousGetRowSpan ( y - descriptorTop ) ) ;
537+ using LzwDecoder lzwDecoder = new ( this . configuration . MemoryAllocator , stream , minCodeSize ) ;
550538
551- // Check if this image is interlaced.
552- int writeY ; // the target y offset to write to
553- if ( descriptor . InterlaceFlag )
539+ for ( int y = descriptorTop ; y < descriptorBottom && y < imageHeight ; y ++ )
554540 {
555- // If so then we read lines at predetermined offsets.
556- // When an entire image height worth of offset lines has been read we consider this a pass.
557- // With each pass the number of offset lines changes and the starting line changes.
558- if ( interlaceY >= descriptor . Height )
541+ // Check if this image is interlaced.
542+ int writeY ; // the target y offset to write to
543+ if ( descriptor . InterlaceFlag )
559544 {
560- interlacePass ++ ;
561- switch ( interlacePass )
545+ // If so then we read lines at predetermined offsets.
546+ // When an entire image height worth of offset lines has been read we consider this a pass.
547+ // With each pass the number of offset lines changes and the starting line changes.
548+ if ( interlaceY >= descriptor . Height )
562549 {
563- case 1 :
564- interlaceY = 4 ;
565- break ;
566- case 2 :
567- interlaceY = 2 ;
568- interlaceIncrement = 4 ;
569- break ;
570- case 3 :
571- interlaceY = 1 ;
572- interlaceIncrement = 2 ;
573- break ;
550+ interlacePass ++ ;
551+ switch ( interlacePass )
552+ {
553+ case 1 :
554+ interlaceY = 4 ;
555+ break ;
556+ case 2 :
557+ interlaceY = 2 ;
558+ interlaceIncrement = 4 ;
559+ break ;
560+ case 3 :
561+ interlaceY = 1 ;
562+ interlaceIncrement = 2 ;
563+ break ;
564+ }
574565 }
575- }
576566
577- writeY = interlaceY + descriptor . Top ;
578- interlaceY += interlaceIncrement ;
579- }
580- else
581- {
582- writeY = y ;
583- }
567+ writeY = Math . Min ( interlaceY + descriptor . Top , image . Height ) ;
568+ interlaceY += interlaceIncrement ;
569+ }
570+ else
571+ {
572+ writeY = y ;
573+ }
584574
585- ref TPixel rowRef = ref MemoryMarshal . GetReference ( imageFrame . PixelBuffer . DangerousGetRowSpan ( writeY ) ) ;
575+ lzwDecoder . DecodePixelRow ( indicesRow ) ;
576+ ref TPixel rowRef = ref MemoryMarshal . GetReference ( imageFrame . PixelBuffer . DangerousGetRowSpan ( writeY ) ) ;
586577
587- if ( ! transFlag )
588- {
589- // #403 The left + width value can be larger than the image width
590- for ( int x = descriptorLeft ; x < descriptorRight && x < imageWidth ; x ++ )
578+ if ( ! transFlag )
591579 {
592- int index = Numerics . Clamp ( Unsafe . Add ( ref indicesRowRef , ( uint ) ( x - descriptorLeft ) ) , 0 , colorTableMaxIdx ) ;
593- ref TPixel pixel = ref Unsafe . Add ( ref rowRef , ( uint ) x ) ;
594- Rgb24 rgb = colorTable [ index ] ;
595- pixel . FromRgb24 ( rgb ) ;
580+ // #403 The left + width value can be larger than the image width
581+ for ( int x = descriptorLeft ; x < descriptorRight && x < imageWidth ; x ++ )
582+ {
583+ int index = Numerics . Clamp ( Unsafe . Add ( ref indicesRowRef , ( uint ) ( x - descriptorLeft ) ) , 0 , colorTableMaxIdx ) ;
584+ ref TPixel pixel = ref Unsafe . Add ( ref rowRef , ( uint ) x ) ;
585+ Rgb24 rgb = colorTable [ index ] ;
586+ pixel . FromRgb24 ( rgb ) ;
587+ }
596588 }
597- }
598- else
599- {
600- for ( int x = descriptorLeft ; x < descriptorRight && x < imageWidth ; x ++ )
589+ else
601590 {
602- int rawIndex = Unsafe . Add ( ref indicesRowRef , ( uint ) ( x - descriptorLeft ) ) ;
603-
604- // Treat any out of bounds values as transparent.
605- if ( rawIndex > colorTableMaxIdx || rawIndex == transIndex )
591+ for ( int x = descriptorLeft ; x < descriptorRight && x < imageWidth ; x ++ )
606592 {
607- continue ;
608- }
593+ int index = Unsafe . Add ( ref indicesRowRef , ( uint ) ( x - descriptorLeft ) ) ;
594+
595+ // Treat any out of bounds values as transparent.
596+ if ( index > colorTableMaxIdx || index == transIndex )
597+ {
598+ continue ;
599+ }
609600
610- int index = Numerics . Clamp ( rawIndex , 0 , colorTableMaxIdx ) ;
611- ref TPixel pixel = ref Unsafe . Add ( ref rowRef , ( uint ) x ) ;
612- Rgb24 rgb = colorTable [ index ] ;
613- pixel . FromRgb24 ( rgb ) ;
601+ ref TPixel pixel = ref Unsafe . Add ( ref rowRef , ( uint ) x ) ;
602+ Rgb24 rgb = colorTable [ index ] ;
603+ pixel . FromRgb24 ( rgb ) ;
604+ }
614605 }
615606 }
616607 }
@@ -651,8 +642,11 @@ private void ReadFrameMetadata(BufferedReadStream stream, List<ImageFrameMetadat
651642 // Skip the frame indices. Pixels length + mincode size.
652643 // The gif format does not tell us the length of the compressed data beforehand.
653644 int minCodeSize = stream . ReadByte ( ) ;
654- using LzwDecoder lzwDecoder = new ( this . configuration . MemoryAllocator , stream ) ;
655- lzwDecoder . SkipIndices ( minCodeSize , this . imageDescriptor . Width * this . imageDescriptor . Height ) ;
645+ if ( LzwDecoder . IsValidMinCodeSize ( minCodeSize ) )
646+ {
647+ using LzwDecoder lzwDecoder = new ( this . configuration . MemoryAllocator , stream , minCodeSize ) ;
648+ lzwDecoder . SkipIndices ( this . imageDescriptor . Width * this . imageDescriptor . Height ) ;
649+ }
656650
657651 ImageFrameMetadata currentFrame = new ( ) ;
658652 frameMetadata . Add ( currentFrame ) ;
0 commit comments