Skip to content

Commit a29b5fc

Browse files
Trim buffer to minimum region.
1 parent 4f6a53c commit a29b5fc

File tree

2 files changed

+58
-22
lines changed

2 files changed

+58
-22
lines changed

src/ImageSharp/Formats/Gif/GifEncoderCore.cs

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -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);

src/ImageSharp/Formats/Gif/LzwEncoder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ public LzwEncoder(MemoryAllocator memoryAllocator, int colorDepth)
186186
/// </summary>
187187
/// <param name="indexedPixels">The 2D buffer of indexed pixels.</param>
188188
/// <param name="stream">The stream to write to.</param>
189-
public void Encode(Buffer2D<byte> indexedPixels, Stream stream)
189+
public void Encode(Buffer2DRegion<byte> indexedPixels, Stream stream)
190190
{
191191
// Write "initial code size" byte
192192
stream.WriteByte((byte)this.initialCodeSize);
@@ -249,7 +249,7 @@ private void ClearBlock(Stream stream)
249249
/// <param name="indexedPixels">The 2D buffer of indexed pixels.</param>
250250
/// <param name="initialBits">The initial bits.</param>
251251
/// <param name="stream">The stream to write to.</param>
252-
private void Compress(Buffer2D<byte> indexedPixels, int initialBits, Stream stream)
252+
private void Compress(Buffer2DRegion<byte> indexedPixels, int initialBits, Stream stream)
253253
{
254254
// Set up the globals: globalInitialBits - initial number of bits
255255
this.globalInitialBits = initialBits;

0 commit comments

Comments
 (0)