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); + } }