@@ -291,20 +291,20 @@ private void EncodeFrame<TPixel>(
291
291
292
292
this . WriteGraphicalControlExtension ( metadata , transparencyIndex , stream ) ;
293
293
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 ) ;
298
301
299
302
if ( useLocal )
300
303
{
301
304
this . WriteColorTable ( quantized , stream ) ;
302
305
}
303
306
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 ) ;
308
308
309
309
// Swap the buffers.
310
310
( quantized , previousQuantized ) = ( previousQuantized , quantized ) ;
@@ -386,6 +386,44 @@ private static void DeDuplicatePixels<TPixel>(
386
386
}
387
387
}
388
388
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
+
389
427
/// <summary>
390
428
/// Returns the index of the most transparent color in the palette.
391
429
/// </summary>
@@ -619,7 +657,7 @@ private void WriteExtension<TGifExtension>(TGifExtension extension, Stream strea
619
657
}
620
658
621
659
IMemoryOwner < byte > ? owner = null ;
622
- Span < byte > extensionBuffer = stackalloc byte [ 0 ] ; // workaround compiler limitation
660
+ Span < byte > extensionBuffer = stackalloc byte [ 0 ] ; // workaround compiler limitation
623
661
if ( extensionSize > 128 )
624
662
{
625
663
owner = this . memoryAllocator . Allocate < byte > ( extensionSize + 3 ) ;
@@ -642,14 +680,12 @@ private void WriteExtension<TGifExtension>(TGifExtension extension, Stream strea
642
680
}
643
681
644
682
/// <summary>
645
- /// Writes the image descriptor to the stream.
683
+ /// Writes the image frame descriptor to the stream.
646
684
/// </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>
649
686
/// <param name="hasColorTable">Whether to use the global color table.</param>
650
687
/// <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 )
653
689
{
654
690
byte packedValue = GifImageDescriptor . GetPackedValue (
655
691
localColorTableFlag : hasColorTable ,
@@ -658,10 +694,10 @@ private void WriteImageDescriptor<TPixel>(ImageFrame<TPixel> image, bool hasColo
658
694
localColorTableSize : this . bitDepth - 1 ) ;
659
695
660
696
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 ,
665
701
packed : packedValue ) ;
666
702
667
703
Span < byte > buffer = stackalloc byte [ 20 ] ;
@@ -697,9 +733,9 @@ private void WriteColorTable<TPixel>(IndexedImageFrame<TPixel> image, Stream str
697
733
/// <summary>
698
734
/// Writes the image pixel data to the stream.
699
735
/// </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>
701
737
/// <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 )
703
739
{
704
740
using LzwEncoder encoder = new ( this . memoryAllocator , ( byte ) this . bitDepth ) ;
705
741
encoder . Encode ( indices , stream ) ;
0 commit comments