diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 3ecc363fa4..d1d29dca6b 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -28,11 +28,6 @@ namespace SixLabors.ImageSharp.Formats.Png;
///
internal sealed class PngDecoderCore : IImageDecoderInternals
{
- ///
- /// Reusable buffer.
- ///
- private readonly byte[] buffer = new byte[4];
-
///
/// The general decoder options.
///
@@ -154,9 +149,11 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.currentStream = stream;
this.currentStream.Skip(8);
Image image = null;
+ Span 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 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 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
///
/// Reads a chunk from the stream.
///
+ /// Temporary buffer.
/// The image format chunk.
///
/// The .
///
- private bool TryReadChunk(out PngChunk chunk)
+ private bool TryReadChunk(Span 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.
///
/// The .
- private void ValidateChunk(in PngChunk chunk)
+ /// Temporary buffer.
+ private void ValidateChunk(in PngChunk chunk, Span buffer)
{
- uint inputCrc = this.ReadChunkCrc();
+ uint inputCrc = this.ReadChunkCrc(buffer);
if (chunk.IsCritical)
{
@@ -1513,13 +1517,14 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
///
/// Reads the cycle redundancy chunk from the data.
///
+ /// Temporary buffer.
[MethodImpl(InliningOptions.ShortMethod)]
- private uint ReadChunkCrc()
+ private uint ReadChunkCrc(Span 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
///
/// Identifies the chunk type from the chunk.
///
+ /// Temporary buffer.
///
/// Thrown if the input stream is not valid.
///
[MethodImpl(InliningOptions.ShortMethod)]
- private PngChunkType ReadChunkType()
+ private PngChunkType ReadChunkType(Span 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
///
/// Attempts to read the length of the next chunk.
///
+ /// Temporary buffer.
/// The result length. If the return type is this parameter is passed uninitialized.
///
/// Whether the length was read.
///
[MethodImpl(InliningOptions.ShortMethod)]
- private bool TryReadChunkLength(out int result)
+ private bool TryReadChunkLength(Span 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;
}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 5794da3d56..a2edbc4c3f 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -38,15 +38,10 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
///
private readonly Configuration configuration;
- ///
- /// Reusable buffer for writing general data.
- ///
- private readonly byte[] buffer = new byte[8];
-
///
/// Reusable buffer for writing chunk data.
///
- private readonly byte[] chunkDataBuffer = new byte[16];
+ private ScratchBuffer chunkDataBuffer; // mutable struct, don't make readonly
///
/// 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);
}
///
@@ -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);
}
///
@@ -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 alpha = this.chunkDataBuffer.AsSpan();
+ Span 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
/// The of the data to write.
private void WriteChunk(Stream stream, PngChunkType type, Span data, int offset, int length)
{
- BinaryPrimitives.WriteInt32BigEndian(this.buffer, length);
- BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type);
+ Span 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
}
///
@@ -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 Span => MemoryMarshal.CreateSpan(ref this.scratch[0], 16);
+ }
}