Browse Source

Reduced intermediate allocations: Png

pull/2415/head
Günther Foidl 3 years ago
parent
commit
31424c0b64
  1. 57
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  2. 50
      src/ImageSharp/Formats/Png/PngEncoderCore.cs

57
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -28,11 +28,6 @@ namespace SixLabors.ImageSharp.Formats.Png;
/// </summary>
internal sealed class PngDecoderCore : IImageDecoderInternals
{
/// <summary>
/// Reusable buffer.
/// </summary>
private readonly byte[] buffer = new byte[4];
/// <summary>
/// The general decoder options.
/// </summary>
@ -154,9 +149,11 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.currentStream = stream;
this.currentStream.Skip(8);
Image<TPixel> image = null;
Span<byte> buffer = stackalloc byte[20];
try
{
while (this.TryReadChunk(out PngChunk chunk))
while (this.TryReadChunk(buffer, out PngChunk chunk))
{
try
{
@ -252,10 +249,13 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
ImageMetadata metadata = new();
PngMetadata pngMetadata = metadata.GetPngMetadata();
this.currentStream = stream;
Span<byte> buffer = stackalloc byte[20];
this.currentStream.Skip(8);
try
{
while (this.TryReadChunk(out PngChunk chunk))
while (this.TryReadChunk(buffer, out PngChunk chunk))
{
try
{
@ -1401,9 +1401,11 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
return 0;
}
this.currentStream.Read(this.buffer, 0, 4);
Span<byte> buffer = stackalloc byte[20];
this.currentStream.Read(buffer, 0, 4);
if (this.TryReadChunk(out PngChunk chunk))
if (this.TryReadChunk(buffer, out PngChunk chunk))
{
if (chunk.Type == PngChunkType.Data)
{
@ -1420,11 +1422,12 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// <summary>
/// Reads a chunk from the stream.
/// </summary>
/// <param name="buffer">Temporary buffer.</param>
/// <param name="chunk">The image format chunk.</param>
/// <returns>
/// The <see cref="PngChunk"/>.
/// </returns>
private bool TryReadChunk(out PngChunk chunk)
private bool TryReadChunk(Span<byte> buffer, out PngChunk chunk)
{
if (this.nextChunk != null)
{
@ -1435,7 +1438,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
return true;
}
if (!this.TryReadChunkLength(out int length))
if (!this.TryReadChunkLength(buffer, out int length))
{
chunk = default;
@ -1446,7 +1449,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
while (length < 0 || length > (this.currentStream.Length - this.currentStream.Position))
{
// Not a valid chunk so try again until we reach a known chunk.
if (!this.TryReadChunkLength(out length))
if (!this.TryReadChunkLength(buffer, out length))
{
chunk = default;
@ -1454,7 +1457,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
}
}
PngChunkType type = this.ReadChunkType();
PngChunkType type = this.ReadChunkType(buffer);
// If we're reading color metadata only we're only interested in the IHDR and tRNS chunks.
// We can skip all other chunk data in the stream for better performance.
@ -1471,7 +1474,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
type: type,
data: this.ReadChunkData(length));
this.ValidateChunk(chunk);
this.ValidateChunk(chunk, buffer);
// Restore the stream position for IDAT chunks, because it will be decoded later and
// was only read to verifying the CRC is correct.
@ -1487,9 +1490,10 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// Validates the png chunk.
/// </summary>
/// <param name="chunk">The <see cref="PngChunk"/>.</param>
private void ValidateChunk(in PngChunk chunk)
/// <param name="buffer">Temporary buffer.</param>
private void ValidateChunk(in PngChunk chunk, Span<byte> buffer)
{
uint inputCrc = this.ReadChunkCrc();
uint inputCrc = this.ReadChunkCrc(buffer);
if (chunk.IsCritical)
{
@ -1513,13 +1517,14 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// <summary>
/// Reads the cycle redundancy chunk from the data.
/// </summary>
/// <param name="buffer">Temporary buffer.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private uint ReadChunkCrc()
private uint ReadChunkCrc(Span<byte> buffer)
{
uint crc = 0;
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
if (this.currentStream.Read(buffer, 0, 4) == 4)
{
crc = BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
crc = BinaryPrimitives.ReadUInt32BigEndian(buffer);
}
return crc;
@ -1554,15 +1559,16 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// <summary>
/// Identifies the chunk type from the chunk.
/// </summary>
/// <param name="buffer">Temporary buffer.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
private PngChunkType ReadChunkType()
private PngChunkType ReadChunkType(Span<byte> buffer)
{
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
if (this.currentStream.Read(buffer, 0, 4) == 4)
{
return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
}
PngThrowHelper.ThrowInvalidChunkType();
@ -1574,16 +1580,17 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// <summary>
/// Attempts to read the length of the next chunk.
/// </summary>
/// <param name="buffer">Temporary buffer.</param>
/// <param name="result">The result length. If the return type is <see langword="false"/> this parameter is passed uninitialized.</param>
/// <returns>
/// Whether the length was read.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
private bool TryReadChunkLength(out int result)
private bool TryReadChunkLength(Span<byte> buffer, out int result)
{
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
if (this.currentStream.Read(buffer, 0, 4) == 4)
{
result = BinaryPrimitives.ReadInt32BigEndian(this.buffer);
result = BinaryPrimitives.ReadInt32BigEndian(buffer);
return true;
}

50
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -38,15 +38,10 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// Reusable buffer for writing general data.
/// </summary>
private readonly byte[] buffer = new byte[8];
/// <summary>
/// Reusable buffer for writing chunk data.
/// </summary>
private readonly byte[] chunkDataBuffer = new byte[16];
private ScratchBuffer chunkDataBuffer; // mutable struct, don't make readonly
/// <summary>
/// The encoder with options
@ -576,9 +571,9 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
filterMethod: 0,
interlaceMethod: this.interlaceMode);
header.WriteTo(this.chunkDataBuffer);
header.WriteTo(this.chunkDataBuffer.Span);
this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer, 0, PngHeader.Size);
this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer.Span, 0, PngHeader.Size);
}
/// <summary>
@ -652,9 +647,9 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
return;
}
PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer);
PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer.Span);
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, PhysicalChunkData.Size);
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer.Span, 0, PhysicalChunkData.Size);
}
/// <summary>
@ -880,9 +875,9 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
// 4-byte unsigned integer of gamma * 100,000.
uint gammaValue = (uint)(this.gamma * 100_000F);
BinaryPrimitives.WriteUInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), gammaValue);
BinaryPrimitives.WriteUInt32BigEndian(this.chunkDataBuffer.Span.Slice(0, 4), gammaValue);
this.WriteChunk(stream, PngChunkType.Gamma, this.chunkDataBuffer, 0, 4);
this.WriteChunk(stream, PngChunkType.Gamma, this.chunkDataBuffer.Span, 0, 4);
}
}
@ -899,7 +894,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
return;
}
Span<byte> alpha = this.chunkDataBuffer.AsSpan();
Span<byte> alpha = this.chunkDataBuffer.Span;
if (pngMetadata.ColorType == PngColorType.Rgb)
{
if (pngMetadata.TransparentRgb48.HasValue && this.use16Bit)
@ -909,7 +904,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(2, 2), rgb.G);
BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(4, 2), rgb.B);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 6);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 6);
}
else if (pngMetadata.TransparentRgb24.HasValue)
{
@ -918,7 +913,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
alpha[1] = rgb.R;
alpha[3] = rgb.G;
alpha[5] = rgb.B;
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 6);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 6);
}
}
else if (pngMetadata.ColorType == PngColorType.Grayscale)
@ -926,13 +921,13 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
if (pngMetadata.TransparentL16.HasValue && this.use16Bit)
{
BinaryPrimitives.WriteUInt16LittleEndian(alpha, pngMetadata.TransparentL16.Value.PackedValue);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 2);
}
else if (pngMetadata.TransparentL8.HasValue)
{
alpha.Clear();
alpha[1] = pngMetadata.TransparentL8.Value.PackedValue;
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 2);
}
}
}
@ -1173,12 +1168,14 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// <param name="length">The of the data to write.</param>
private void WriteChunk(Stream stream, PngChunkType type, Span<byte> data, int offset, int length)
{
BinaryPrimitives.WriteInt32BigEndian(this.buffer, length);
BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type);
Span<byte> buffer = stackalloc byte[8];
BinaryPrimitives.WriteInt32BigEndian(buffer, length);
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4, 4), (uint)type);
stream.Write(this.buffer, 0, 8);
stream.Write(buffer);
uint crc = Crc32.Calculate(this.buffer.AsSpan(4, 4)); // Write the type buffer
uint crc = Crc32.Calculate(buffer.Slice(4)); // Write the type buffer
if (data.Length > 0 && length > 0)
{
@ -1187,9 +1184,9 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
crc = Crc32.Calculate(crc, data.Slice(offset, length));
}
BinaryPrimitives.WriteUInt32BigEndian(this.buffer, crc);
BinaryPrimitives.WriteUInt32BigEndian(buffer, crc);
stream.Write(this.buffer, 0, 4); // write the crc
stream.Write(buffer, 0, 4); // write the crc
}
/// <summary>
@ -1412,4 +1409,11 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
Type t when t == typeof(RgbaVector) => PngBitDepth.Bit16,
_ => PngBitDepth.Bit8
};
private unsafe struct ScratchBuffer
{
private fixed byte scratch[16];
public Span<byte> Span => MemoryMarshal.CreateSpan(ref this.scratch[0], 16);
}
}

Loading…
Cancel
Save