@@ -291,20 +291,20 @@ private void EncodeFrame<TPixel>(
291291
292292 this . WriteGraphicalControlExtension ( metadata , transparencyIndex , stream ) ;
293293
294- // TODO: Consider an optimization that trims down the buffer to the minimum size required.
295- // We would use a process similar to entropy crop where we trim the buffer from the edges
296- // until we hit a non-transparent pixel.
297- this . WriteImageDescriptor ( frame , useLocal , stream ) ;
294+ // Assign the correct buffer to compress.
295+ // If we are using a local palette or it's the first run then we want to use the quantized frame.
296+ Buffer2D < byte > buffer = useLocal || frameIndex == 0 ? ( ( IPixelSource ) quantized ) . PixelBuffer : indices ;
297+
298+ // Trim down the buffer to the minimum size required.
299+ Buffer2DRegion < byte > region = TrimTransparentPixels ( buffer , transparencyIndex ) ;
300+ this . WriteImageDescriptor ( region . Rectangle , useLocal , stream ) ;
298301
299302 if ( useLocal )
300303 {
301304 this . WriteColorTable ( quantized , stream ) ;
302305 }
303306
304- // Assign the correct buffer to compress.
305- // If we are using a local palette or it's the first run then we want to use the quantized frame.
306- Buffer2D < byte > buffer = useLocal || frameIndex == 0 ? ( ( IPixelSource ) quantized ) . PixelBuffer : indices ;
307- this . WriteImageData ( buffer , stream ) ;
307+ this . WriteImageData ( region , stream ) ;
308308
309309 // Swap the buffers.
310310 ( quantized , previousQuantized ) = ( previousQuantized , quantized ) ;
@@ -386,6 +386,44 @@ private static void DeDuplicatePixels<TPixel>(
386386 }
387387 }
388388
389+ private static Buffer2DRegion < byte > TrimTransparentPixels ( Buffer2D < byte > buffer , int transparencyIndex )
390+ {
391+ if ( transparencyIndex < 0 )
392+ {
393+ return buffer . GetRegion ( ) ;
394+ }
395+
396+ byte trimmableIndex = unchecked ( ( byte ) transparencyIndex ) ;
397+
398+ int top = int . MaxValue ;
399+ int bottom = int . MinValue ;
400+ int left = int . MaxValue ;
401+ int right = int . MinValue ;
402+
403+ for ( int y = 0 ; y < buffer . Height ; y ++ )
404+ {
405+ Span < byte > rowSpan = buffer . DangerousGetRowSpan ( y ) ;
406+ for ( int x = 0 ; x < rowSpan . Length ; x ++ )
407+ {
408+ if ( rowSpan [ x ] != trimmableIndex )
409+ {
410+ top = Math . Min ( top , y ) ;
411+ bottom = Math . Max ( bottom , y ) ;
412+ left = Math . Min ( left , x ) ;
413+ right = Math . Max ( right , x ) ;
414+ }
415+ }
416+ }
417+
418+ if ( top == int . MaxValue || bottom == int . MinValue )
419+ {
420+ // No valid rectangle found
421+ return buffer . GetRegion ( ) ;
422+ }
423+
424+ return buffer . GetRegion ( Rectangle . FromLTRB ( left , top , right , bottom ) ) ;
425+ }
426+
389427 /// <summary>
390428 /// Returns the index of the most transparent color in the palette.
391429 /// </summary>
@@ -619,7 +657,7 @@ private void WriteExtension<TGifExtension>(TGifExtension extension, Stream strea
619657 }
620658
621659 IMemoryOwner < byte > ? owner = null ;
622- Span < byte > extensionBuffer = stackalloc byte [ 0 ] ; // workaround compiler limitation
660+ Span < byte > extensionBuffer = stackalloc byte [ 0 ] ; // workaround compiler limitation
623661 if ( extensionSize > 128 )
624662 {
625663 owner = this . memoryAllocator . Allocate < byte > ( extensionSize + 3 ) ;
@@ -642,14 +680,12 @@ private void WriteExtension<TGifExtension>(TGifExtension extension, Stream strea
642680 }
643681
644682 /// <summary>
645- /// Writes the image descriptor to the stream.
683+ /// Writes the image frame descriptor to the stream.
646684 /// </summary>
647- /// <typeparam name="TPixel">The pixel format.</typeparam>
648- /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to be encoded.</param>
685+ /// <param name="rectangle">The frame location and size.</param>
649686 /// <param name="hasColorTable">Whether to use the global color table.</param>
650687 /// <param name="stream">The stream to write to.</param>
651- private void WriteImageDescriptor < TPixel > ( ImageFrame < TPixel > image , bool hasColorTable , Stream stream )
652- where TPixel : unmanaged, IPixel < TPixel >
688+ private void WriteImageDescriptor ( Rectangle rectangle , bool hasColorTable , Stream stream )
653689 {
654690 byte packedValue = GifImageDescriptor . GetPackedValue (
655691 localColorTableFlag : hasColorTable ,
@@ -658,10 +694,10 @@ private void WriteImageDescriptor<TPixel>(ImageFrame<TPixel> image, bool hasColo
658694 localColorTableSize : this . bitDepth - 1 ) ;
659695
660696 GifImageDescriptor descriptor = new (
661- left : 0 ,
662- top : 0 ,
663- width : ( ushort ) image . Width ,
664- height : ( ushort ) image . Height ,
697+ left : ( ushort ) rectangle . X ,
698+ top : ( ushort ) rectangle . Y ,
699+ width : ( ushort ) rectangle . Width ,
700+ height : ( ushort ) rectangle . Height ,
665701 packed : packedValue ) ;
666702
667703 Span < byte > buffer = stackalloc byte [ 20 ] ;
@@ -697,9 +733,9 @@ private void WriteColorTable<TPixel>(IndexedImageFrame<TPixel> image, Stream str
697733 /// <summary>
698734 /// Writes the image pixel data to the stream.
699735 /// </summary>
700- /// <param name="indices">The <see cref="Buffer2D {Byte}"/> containing indexed pixels.</param>
736+ /// <param name="indices">The <see cref="Buffer2DRegion {Byte}"/> containing indexed pixels.</param>
701737 /// <param name="stream">The stream to write to.</param>
702- private void WriteImageData ( Buffer2D < byte > indices , Stream stream )
738+ private void WriteImageData ( Buffer2DRegion < byte > indices , Stream stream )
703739 {
704740 using LzwEncoder encoder = new ( this . memoryAllocator , ( byte ) this . bitDepth ) ;
705741 encoder . Encode ( indices , stream ) ;
0 commit comments