Browse Source

Reduced intermediate allocations: Gif

pull/2415/head
Günther Foidl 3 years ago
parent
commit
1d3ae0ed9d
  1. 29
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  2. 32
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs

29
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -22,7 +22,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// <summary> /// <summary>
/// The temp buffer used to reduce allocations. /// The temp buffer used to reduce allocations.
/// </summary> /// </summary>
private readonly byte[] buffer = new byte[16]; private ScratchBuffer buffer; // mutable struct, don't make readonly
/// <summary> /// <summary>
/// The global color table. /// The global color table.
@ -249,13 +249,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param> /// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
private void ReadGraphicalControlExtension(BufferedReadStream stream) private void ReadGraphicalControlExtension(BufferedReadStream stream)
{ {
int bytesRead = stream.Read(this.buffer, 0, 6); int bytesRead = stream.Read(this.buffer.Span, 0, 6);
if (bytesRead != 6) if (bytesRead != 6)
{ {
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension"); GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension");
} }
this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer); this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer.Span);
} }
/// <summary> /// <summary>
@ -264,13 +264,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param> /// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
private void ReadImageDescriptor(BufferedReadStream stream) private void ReadImageDescriptor(BufferedReadStream stream)
{ {
int bytesRead = stream.Read(this.buffer, 0, 9); int bytesRead = stream.Read(this.buffer.Span, 0, 9);
if (bytesRead != 9) if (bytesRead != 9)
{ {
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor"); GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor");
} }
this.imageDescriptor = GifImageDescriptor.Parse(this.buffer); this.imageDescriptor = GifImageDescriptor.Parse(this.buffer.Span);
if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0) if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0)
{ {
GifThrowHelper.ThrowInvalidImageContentException("Width or height should not be 0"); GifThrowHelper.ThrowInvalidImageContentException("Width or height should not be 0");
@ -283,13 +283,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param> /// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
private void ReadLogicalScreenDescriptor(BufferedReadStream stream) private void ReadLogicalScreenDescriptor(BufferedReadStream stream)
{ {
int bytesRead = stream.Read(this.buffer, 0, 7); int bytesRead = stream.Read(this.buffer.Span, 0, 7);
if (bytesRead != 7) if (bytesRead != 7)
{ {
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor"); GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor");
} }
this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer); this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer.Span);
} }
/// <summary> /// <summary>
@ -306,8 +306,8 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
long position = stream.Position; long position = stream.Position;
if (appLength == GifConstants.ApplicationBlockSize) if (appLength == GifConstants.ApplicationBlockSize)
{ {
stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize); stream.Read(this.buffer.Span, 0, GifConstants.ApplicationBlockSize);
bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes); bool isXmp = this.buffer.Span.StartsWith(GifConstants.XmpApplicationIdentificationBytes);
if (isXmp && !this.skipMetadata) if (isXmp && !this.skipMetadata)
{ {
GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(stream, this.memoryAllocator); GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(stream, this.memoryAllocator);
@ -331,8 +331,8 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
// http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension // http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension
if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize) if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize)
{ {
stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize); stream.Read(this.buffer.Span, 0, GifConstants.NetscapeLoopingSubBlockSize);
this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount; this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.Span.Slice(1)).RepeatCount;
stream.Skip(1); // Skip the terminator. stream.Skip(1); // Skip the terminator.
return; return;
} }
@ -762,4 +762,11 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
} }
} }
} }
private unsafe struct ScratchBuffer
{
private fixed byte scratch[16];
public Span<byte> Span => MemoryMarshal.CreateSpan(ref this.scratch[0], 16);
}
} }

32
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -28,11 +28,6 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
/// </summary> /// </summary>
private readonly Configuration configuration; private readonly Configuration configuration;
/// <summary>
/// A reusable buffer used to reduce allocations.
/// </summary>
private readonly byte[] buffer = new byte[20];
/// <summary> /// <summary>
/// Whether to skip metadata during encode. /// Whether to skip metadata during encode.
/// </summary> /// </summary>
@ -324,9 +319,10 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
backgroundColorIndex: unchecked((byte)transparencyIndex), backgroundColorIndex: unchecked((byte)transparencyIndex),
ratio); ratio);
descriptor.WriteTo(this.buffer); Span<byte> buffer = stackalloc byte[20];
descriptor.WriteTo(buffer);
stream.Write(this.buffer, 0, GifLogicalScreenDescriptor.Size); stream.Write(buffer, 0, GifLogicalScreenDescriptor.Size);
} }
/// <summary> /// <summary>
@ -365,12 +361,14 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
return; return;
} }
Span<byte> buffer = stackalloc byte[2];
for (int i = 0; i < metadata.Comments.Count; i++) for (int i = 0; i < metadata.Comments.Count; i++)
{ {
string comment = metadata.Comments[i]; string comment = metadata.Comments[i];
this.buffer[0] = GifConstants.ExtensionIntroducer; buffer[1] = GifConstants.CommentLabel;
this.buffer[1] = GifConstants.CommentLabel; buffer[0] = GifConstants.ExtensionIntroducer;
stream.Write(this.buffer, 0, 2); stream.Write(buffer);
// Comment will be stored in chunks of 255 bytes, if it exceeds this size. // Comment will be stored in chunks of 255 bytes, if it exceeds this size.
ReadOnlySpan<char> commentSpan = comment.AsSpan(); ReadOnlySpan<char> commentSpan = comment.AsSpan();
@ -437,22 +435,23 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
private void WriteExtension<TGifExtension>(TGifExtension extension, Stream stream) private void WriteExtension<TGifExtension>(TGifExtension extension, Stream stream)
where TGifExtension : struct, IGifExtension where TGifExtension : struct, IGifExtension
{ {
IMemoryOwner<byte>? owner = null;
Span<byte> extensionBuffer;
int extensionSize = extension.ContentLength; int extensionSize = extension.ContentLength;
if (extensionSize == 0) if (extensionSize == 0)
{ {
return; return;
} }
else if (extensionSize > this.buffer.Length - 3)
IMemoryOwner<byte>? owner = null;
Span<byte> extensionBuffer = stackalloc byte[0]; // workaround compiler limitation
if (extensionSize > 128)
{ {
owner = this.memoryAllocator.Allocate<byte>(extensionSize + 3); owner = this.memoryAllocator.Allocate<byte>(extensionSize + 3);
extensionBuffer = owner.GetSpan(); extensionBuffer = owner.GetSpan();
} }
else else
{ {
extensionBuffer = this.buffer; extensionBuffer = stackalloc byte[extensionSize + 3];
} }
extensionBuffer[0] = GifConstants.ExtensionIntroducer; extensionBuffer[0] = GifConstants.ExtensionIntroducer;
@ -489,9 +488,10 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
height: (ushort)image.Height, height: (ushort)image.Height,
packed: packedValue); packed: packedValue);
descriptor.WriteTo(this.buffer); Span<byte> buffer = stackalloc byte[20];
descriptor.WriteTo(buffer);
stream.Write(this.buffer, 0, GifImageDescriptor.Size); stream.Write(buffer, 0, GifImageDescriptor.Size);
} }
/// <summary> /// <summary>

Loading…
Cancel
Save