From d9e8d79dddbd4619ab34757cd8ff035f58c1647a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Sat, 25 Mar 2023 22:35:41 +0100 Subject: [PATCH] Reduced intermediate allocations: Webp --- .../Formats/Webp/BitReader/Vp8LBitReader.cs | 2 +- .../Formats/Webp/BitWriter/BitWriterBase.cs | 22 ++-- .../Formats/Webp/BitWriter/Vp8LBitWriter.cs | 15 +-- .../Formats/Webp/Lossless/HistogramEncoder.cs | 32 ++++-- .../Formats/Webp/Lossless/HuffmanUtils.cs | 26 ++--- .../Formats/Webp/Lossless/PredictorEncoder.cs | 16 +-- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 57 ++++------ .../Webp/Lossless/WebpLosslessDecoder.cs | 5 +- src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs | 2 +- .../Formats/Webp/Lossy/Vp8EncIterator.cs | 8 +- .../Formats/Webp/Lossy/Vp8Encoder.cs | 20 ++-- .../Formats/Webp/Lossy/Vp8Histogram.cs | 25 ++--- .../Formats/Webp/Lossy/Vp8Residual.cs | 11 +- .../Formats/Webp/Lossy/WebpLossyDecoder.cs | 27 ++--- .../Formats/Webp/WebpAnimationDecoder.cs | 35 +++--- .../Formats/Webp/WebpChunkParsingUtils.cs | 26 +++-- .../Formats/Webp/WebpDecoderCore.cs | 104 ++++++++++-------- 17 files changed, 212 insertions(+), 221 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs b/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs index 8da717545..659576cf1 100644 --- a/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs @@ -192,7 +192,7 @@ internal class Vp8LBitReader : BitReaderBase [MethodImpl(InliningOptions.ShortMethod)] private void ShiftBytes() { - System.Span dataSpan = this.Data!.Memory.Span; + Span dataSpan = this.Data!.Memory.Span; while (this.bitPos >= 8 && this.pos < this.len) { this.value >>= 8; diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs index 02b1d0ab6..8baf2cc15 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers.Binary; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; @@ -23,7 +24,7 @@ internal abstract class BitWriterBase /// /// A scratch buffer to reduce allocations. /// - private readonly byte[] scratchBuffer = new byte[4]; + private ScratchBuffer scratchBuffer; // mutable struct, don't make readonly /// /// Initializes a new instance of the class. @@ -90,8 +91,8 @@ internal abstract class BitWriterBase protected void WriteRiffHeader(Stream stream, uint riffSize) { stream.Write(WebpConstants.RiffFourCc); - BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, riffSize); - stream.Write(this.scratchBuffer.AsSpan(0, 4)); + BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer.Span, riffSize); + stream.Write(this.scratchBuffer.Span.Slice(0, 4)); stream.Write(WebpConstants.WebpHeader); } @@ -128,7 +129,7 @@ internal abstract class BitWriterBase DebugGuard.NotNull(metadataBytes, nameof(metadataBytes)); uint size = (uint)metadataBytes.Length; - Span buf = this.scratchBuffer.AsSpan(0, 4); + Span buf = this.scratchBuffer.Span.Slice(0, 4); BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)chunkType); stream.Write(buf); BinaryPrimitives.WriteUInt32LittleEndian(buf, size); @@ -151,7 +152,7 @@ internal abstract class BitWriterBase protected void WriteAlphaChunk(Stream stream, Span dataBytes, bool alphaDataIsCompressed) { uint size = (uint)dataBytes.Length + 1; - Span buf = this.scratchBuffer.AsSpan(0, 4); + Span buf = this.scratchBuffer.Span.Slice(0, 4); BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Alpha); stream.Write(buf); BinaryPrimitives.WriteUInt32LittleEndian(buf, size); @@ -182,7 +183,7 @@ internal abstract class BitWriterBase { uint size = (uint)iccProfileBytes.Length; - Span buf = this.scratchBuffer.AsSpan(0, 4); + Span buf = this.scratchBuffer.Span.Slice(0, 4); BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Iccp); stream.Write(buf); BinaryPrimitives.WriteUInt32LittleEndian(buf, size); @@ -245,7 +246,7 @@ internal abstract class BitWriterBase flags |= 32; } - Span buf = this.scratchBuffer.AsSpan(0, 4); + Span buf = this.scratchBuffer.Span.Slice(0, 4); stream.Write(WebpConstants.Vp8XMagicBytes); BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize); stream.Write(buf); @@ -256,4 +257,11 @@ internal abstract class BitWriterBase BinaryPrimitives.WriteUInt32LittleEndian(buf, height - 1); stream.Write(buf[..3]); } + + private unsafe struct ScratchBuffer + { + private fixed byte scratch[4]; + + public Span Span => MemoryMarshal.CreateSpan(ref this.scratch[0], 4); + } } diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs index 22bc195d6..9dc791239 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs @@ -14,11 +14,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter; /// internal class Vp8LBitWriter : BitWriterBase { - /// - /// A scratch buffer to reduce allocations. - /// - private readonly byte[] scratchBuffer = new byte[8]; - /// /// This is the minimum amount of size the memory buffer is guaranteed to grow when extra space is needed. /// @@ -194,8 +189,9 @@ internal class Vp8LBitWriter : BitWriterBase stream.Write(WebpConstants.Vp8LMagicBytes); // Write Vp8 Header. - BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, size); - stream.Write(this.scratchBuffer.AsSpan(0, 4)); + Span scratchBuffer = stackalloc byte[8]; + BinaryPrimitives.WriteUInt32LittleEndian(scratchBuffer, size); + stream.Write(scratchBuffer.Slice(0, 4)); stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte); // Write the encoded bytes of the image to the stream. @@ -228,8 +224,9 @@ internal class Vp8LBitWriter : BitWriterBase this.BitWriterResize(extraSize); } - BinaryPrimitives.WriteUInt64LittleEndian(this.scratchBuffer, this.bits); - this.scratchBuffer.AsSpan(0, 4).CopyTo(this.Buffer.AsSpan(this.cur)); + Span scratchBuffer = stackalloc byte[8]; + BinaryPrimitives.WriteUInt64LittleEndian(scratchBuffer, this.bits); + scratchBuffer.Slice(0, 4).CopyTo(this.Buffer.AsSpan(this.cur)); this.cur += WriterBytes; this.bits >>= WriterBits; diff --git a/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs index 5ac330151..dd59ed209 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs @@ -6,7 +6,7 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.Webp.Lossless; -internal class HistogramEncoder +internal static class HistogramEncoder { /// /// Number of partitions for the three dominant (literal, red and blue) symbol costs. @@ -27,7 +27,7 @@ internal class HistogramEncoder private const ushort InvalidHistogramSymbol = ushort.MaxValue; - public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, uint quality, int histoBits, int cacheBits, List imageHisto, Vp8LHistogram tmpHisto, ushort[] histogramSymbols) + public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, uint quality, int histoBits, int cacheBits, List imageHisto, Vp8LHistogram tmpHisto, Span histogramSymbols) { int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(xSize, histoBits) : 1; int histoYSize = histoBits > 0 ? LosslessUtils.SubSampleSize(ySize, histoBits) : 1; @@ -148,7 +148,7 @@ internal class HistogramEncoder } } - private static int HistogramCopyAndAnalyze(List origHistograms, List histograms, ushort[] histogramSymbols) + private static int HistogramCopyAndAnalyze(List origHistograms, List histograms, Span histogramSymbols) { var stats = new Vp8LStreaks(); var bitsEntropy = new Vp8LBitEntropy(); @@ -171,20 +171,28 @@ internal class HistogramEncoder } } - int numUsed = histogramSymbols.Count(h => h != InvalidHistogramSymbol); + int numUsed = 0; + foreach (ushort h in histogramSymbols) + { + if (h != InvalidHistogramSymbol) + { + numUsed++; + } + } + return numUsed; } private static void HistogramCombineEntropyBin( List histograms, - ushort[] clusters, + Span clusters, ushort[] clusterMappings, Vp8LHistogram curCombo, ushort[] binMap, int numBins, double combineCostFactor) { - var binInfo = new HistogramBinInfo[BinSize]; + Span binInfo = stackalloc HistogramBinInfo[BinSize]; for (int idx = 0; idx < numBins; idx++) { binInfo[idx].First = -1; @@ -258,7 +266,7 @@ internal class HistogramEncoder /// Given a Histogram set, the mapping of clusters 'clusterMapping' and the /// current assignment of the cells in 'symbols', merge the clusters and assign the smallest possible clusters values. /// - private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numClusters, ushort[] clusterMappingsTmp, ushort[] symbols) + private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numClusters, ushort[] clusterMappingsTmp, Span symbols) { bool doContinue = true; @@ -331,7 +339,7 @@ internal class HistogramEncoder int maxSize = 9; // Fill the initial mapping. - int[] mappings = new int[histograms.Count]; + Span mappings = histograms.Count <= 64 ? stackalloc int[histograms.Count] : new int[histograms.Count]; for (int j = 0, iter = 0; iter < histograms.Count; iter++) { if (histograms[iter] == null) @@ -388,9 +396,9 @@ internal class HistogramEncoder int bestIdx1 = histoPriorityList[0].Idx1; int bestIdx2 = histoPriorityList[0].Idx2; - int mappingIndex = Array.IndexOf(mappings, bestIdx2); - Span src = mappings.AsSpan(mappingIndex + 1, numUsed - mappingIndex - 1); - Span dst = mappings.AsSpan(mappingIndex); + int mappingIndex = mappings.IndexOf(bestIdx2); + Span src = mappings.Slice(mappingIndex + 1, numUsed - mappingIndex - 1); + Span dst = mappings.Slice(mappingIndex); src.CopyTo(dst); // Merge the histograms and remove bestIdx2 from the list. @@ -528,7 +536,7 @@ internal class HistogramEncoder } } - private static void HistogramRemap(List input, List output, ushort[] symbols) + private static void HistogramRemap(List input, List output, Span symbols) { int inSize = input.Count; int outSize = output.Count; diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs index 18104331c..39ad967e3 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs @@ -25,7 +25,7 @@ internal static class HuffmanUtils 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf }; - public static void CreateHuffmanTree(uint[] histogram, int treeDepthLimit, bool[] bufRle, HuffmanTree[] huffTree, HuffmanTreeCode huffCode) + public static void CreateHuffmanTree(uint[] histogram, int treeDepthLimit, bool[] bufRle, Span huffTree, HuffmanTreeCode huffCode) { int numSymbols = huffCode.NumSymbols; bufRle.AsSpan().Clear(); @@ -159,7 +159,7 @@ internal static class HuffmanUtils /// The size of the histogram. /// The tree depth limit. /// How many bits are used for the symbol. - public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths) + public static void GenerateOptimalTree(Span tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths) { uint countMin; int treeSizeOrig = 0; @@ -177,7 +177,7 @@ internal static class HuffmanUtils return; } - Span treePool = tree.AsSpan(treeSizeOrig); + Span treePool = tree.Slice(treeSizeOrig); // For block sizes with less than 64k symbols we never need to do a // second iteration of this loop. @@ -202,14 +202,8 @@ internal static class HuffmanUtils } // Build the Huffman tree. -#if NET5_0_OR_GREATER - Span treeSlice = tree.AsSpan(0, treeSize); + Span treeSlice = tree.Slice(0, treeSize); treeSlice.Sort(HuffmanTree.Compare); -#else - HuffmanTree[] treeCopy = tree.AsSpan(0, treeSize).ToArray(); - Array.Sort(treeCopy, HuffmanTree.Compare); - treeCopy.AsSpan().CopyTo(tree); -#endif if (treeSize > 1) { @@ -312,12 +306,12 @@ internal static class HuffmanUtils DebugGuard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize)); // sorted[codeLengthsSize] is a pre-allocated array for sorting symbols by code length. - int[] sorted = new int[codeLengthsSize]; + Span sorted = codeLengthsSize <= 64 ? stackalloc int[codeLengthsSize] : new int[codeLengthsSize]; int totalSize = 1 << rootBits; // total size root table + 2nd level table. int len; // current code length. int symbol; // symbol index in original or sorted table. - int[] counts = new int[WebpConstants.MaxAllowedCodeLength + 1]; // number of codes of each length. - int[] offsets = new int[WebpConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length. + Span counts = stackalloc int[WebpConstants.MaxAllowedCodeLength + 1]; // number of codes of each length. + Span offsets = stackalloc int[WebpConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length. // Build histogram of code lengths. for (symbol = 0; symbol < codeLengthsSize; ++symbol) @@ -544,8 +538,8 @@ internal static class HuffmanUtils private static void ConvertBitDepthsToSymbols(HuffmanTreeCode tree) { // 0 bit-depth means that the symbol does not exist. - uint[] nextCode = new uint[WebpConstants.MaxAllowedCodeLength + 1]; - int[] depthCount = new int[WebpConstants.MaxAllowedCodeLength + 1]; + Span nextCode = stackalloc uint[WebpConstants.MaxAllowedCodeLength + 1]; + Span depthCount = stackalloc int[WebpConstants.MaxAllowedCodeLength + 1]; int len = tree.NumSymbols; for (int i = 0; i < len; i++) @@ -603,7 +597,7 @@ internal static class HuffmanUtils /// Returns the table width of the next 2nd level table. count is the histogram of bit lengths for the remaining symbols, /// len is the code length of the next processed symbol. /// - private static int NextTableBitSize(int[] count, int len, int rootBits) + private static int NextTableBitSize(ReadOnlySpan count, int len, int rootBits) { int left = 1 << (len - rootBits); while (len < WebpConstants.MaxAllowedCodeLength) diff --git a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs index 689c63f5b..2170eb198 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs @@ -57,11 +57,13 @@ internal static unsafe class PredictorEncoder Span scratch = stackalloc short[8]; // TODO: Can we optimize this? - int[][] histo = new int[4][]; - for (int i = 0; i < 4; i++) + int[][] histo = { - histo[i] = new int[256]; - } + new int[256], + new int[256], + new int[256], + new int[256] + }; if (lowEffort) { @@ -233,7 +235,7 @@ internal static unsafe class PredictorEncoder Span maxDiffs = MemoryMarshal.Cast(currentRow[(width + 1)..]); float bestDiff = MaxDiffCost; int bestMode = 0; - uint[] residuals = new uint[1 << WebpConstants.MaxTransformBits]; + Span residuals = stackalloc uint[1 << WebpConstants.MaxTransformBits]; // 256 bytes for (int i = 0; i < 4; i++) { histoArgb[i].AsSpan().Clear(); @@ -299,9 +301,7 @@ internal static unsafe class PredictorEncoder if (curDiff < bestDiff) { - int[][] tmp = histoArgb; - histoArgb = bestHisto; - bestHisto = tmp; + (bestHisto, histoArgb) = (histoArgb, bestHisto); bestDiff = curDiff; bestMode = mode; } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index d678da602..e3c2797bf 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -23,7 +23,7 @@ internal class Vp8LEncoder : IDisposable /// /// Scratch buffer to reduce allocations. /// - private readonly int[] scratch = new int[256]; + private ScratchBuffer scratch; // mutable struct, don't make readonly private readonly int[][] histoArgb = { new int[256], new int[256], new int[256], new int[256] }; @@ -549,12 +549,8 @@ internal class Vp8LEncoder : IDisposable // bgra data with transformations applied. Span bgra = this.EncodedData.GetSpan(); int histogramImageXySize = LosslessUtils.SubSampleSize(width, this.HistoBits) * LosslessUtils.SubSampleSize(height, this.HistoBits); - ushort[] histogramSymbols = new ushort[histogramImageXySize]; - HuffmanTree[] huffTree = new HuffmanTree[3 * WebpConstants.CodeLengthCodes]; - for (int i = 0; i < huffTree.Length; i++) - { - huffTree[i] = default; - } + Span histogramSymbols = histogramImageXySize <= 64 ? stackalloc ushort[histogramImageXySize] : new ushort[histogramImageXySize]; + Span huffTree = stackalloc HuffmanTree[3 * WebpConstants.CodeLengthCodes]; if (useCache) { @@ -607,10 +603,6 @@ internal class Vp8LEncoder : IDisposable int histogramImageSize = histogramImage.Count; int bitArraySize = 5 * histogramImageSize; HuffmanTreeCode[] huffmanCodes = new HuffmanTreeCode[bitArraySize]; - for (int i = 0; i < huffmanCodes.Length; i++) - { - huffmanCodes[i] = default; - } GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes); @@ -702,7 +694,7 @@ internal class Vp8LEncoder : IDisposable /// private void EncodePalette(bool lowEffort) { - Span tmpPalette = new uint[WebpConstants.MaxPaletteSize]; + Span tmpPalette = stackalloc uint[WebpConstants.MaxPaletteSize]; int paletteSize = this.PaletteSize; Span palette = this.Palette.Memory.Span; this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); @@ -763,7 +755,7 @@ internal class Vp8LEncoder : IDisposable int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits); int transformHeight = LosslessUtils.SubSampleSize(height, colorTransformBits); - PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, this.quality, this.EncodedData.GetSpan(), this.TransformData.GetSpan(), this.scratch); + PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, this.quality, this.EncodedData.GetSpan(), this.TransformData.GetSpan(), this.scratch.Span); this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2); @@ -778,16 +770,7 @@ internal class Vp8LEncoder : IDisposable ushort[] histogramSymbols = new ushort[1]; // Only one tree, one symbol. HuffmanTreeCode[] huffmanCodes = new HuffmanTreeCode[5]; - for (int i = 0; i < huffmanCodes.Length; i++) - { - huffmanCodes[i] = default; - } - - HuffmanTree[] huffTree = new HuffmanTree[3UL * WebpConstants.CodeLengthCodes]; - for (int i = 0; i < huffTree.Length; i++) - { - huffTree[i] = default; - } + Span huffTree = stackalloc HuffmanTree[3 * WebpConstants.CodeLengthCodes]; // Calculate backward references from the image pixels. hashChain.Fill(bgra, quality, width, height, lowEffort); @@ -847,10 +830,10 @@ internal class Vp8LEncoder : IDisposable this.StoreImageToBitMask(width, 0, refs, histogramSymbols, huffmanCodes); } - private void StoreHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode) + private void StoreHuffmanCode(Span huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode) { int count = 0; - Span symbols = this.scratch.AsSpan(0, 2); + Span symbols = this.scratch.Span.Slice(0, 2); symbols.Clear(); const int maxBits = 8; const int maxSymbol = 1 << maxBits; @@ -901,7 +884,7 @@ internal class Vp8LEncoder : IDisposable } } - private void StoreFullHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree) + private void StoreFullHuffmanCode(Span huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree) { int i; byte[] codeLengthBitDepth = new byte[WebpConstants.CodeLengthCodes]; @@ -1013,7 +996,7 @@ internal class Vp8LEncoder : IDisposable } } - private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs backwardRefs, ushort[] histogramSymbols, HuffmanTreeCode[] huffmanCodes) + private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs backwardRefs, Span histogramSymbols, HuffmanTreeCode[] huffmanCodes) { int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(width, histoBits) : 1; int tileMask = histoBits == 0 ? 0 : -(1 << histoBits); @@ -1143,8 +1126,8 @@ internal class Vp8LEncoder : IDisposable prevRow = currentRow; } - double[] entropyComp = new double[(int)HistoIx.HistoTotal]; - double[] entropy = new double[(int)EntropyIx.NumEntropyIx]; + Span entropyComp = stackalloc double[(int)HistoIx.HistoTotal]; + Span entropy = stackalloc double[(int)EntropyIx.NumEntropyIx]; int lastModeToAnalyze = usePalette ? (int)EntropyIx.Palette : (int)EntropyIx.SpatialSubGreen; // Let's add one zero to the predicted histograms. The zeros are removed @@ -1647,11 +1630,7 @@ internal class Vp8LEncoder : IDisposable // Create Huffman trees. bool[] bufRle = new bool[maxNumSymbols]; - HuffmanTree[] huffTree = new HuffmanTree[3 * maxNumSymbols]; - for (int i = 0; i < huffTree.Length; i++) - { - huffTree[i] = default; - } + Span huffTree = stackalloc HuffmanTree[3 * maxNumSymbols]; for (int i = 0; i < histogramImage.Count; i++) { @@ -1849,4 +1828,14 @@ internal class Vp8LEncoder : IDisposable this.TransformData.Dispose(); this.HashChain.Dispose(); } + + /// + /// Scratch buffer to reduce allocations. + /// + private unsafe struct ScratchBuffer + { + private fixed int scratch[256]; + + public Span Span => MemoryMarshal.CreateSpan(ref this.scratch[0], 256); + } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs index 84ddd4b78..19ea42419 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs @@ -498,10 +498,7 @@ internal sealed class WebpLosslessDecoder private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, Span table) { bool simpleCode = this.bitReader.ReadBit(); - for (int i = 0; i < alphabetSize; i++) - { - codeLengths[i] = 0; - } + codeLengths.AsSpan(0, alphabetSize).Clear(); if (simpleCode) { diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index 76de2e8f4..e9eb1110b 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs @@ -121,7 +121,7 @@ internal static unsafe class QuantEnc var rdi4 = new Vp8ModeScore(); var rdTmp = new Vp8ModeScore(); var res = new Vp8Residual(); - Span tmpLevels = new short[16]; + Span tmpLevels = stackalloc short[16]; do { const int numBlocks = 1; diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs index b33ef57a6..7211f9376 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs @@ -374,7 +374,7 @@ internal class Vp8EncIterator } else { - byte[] modes = new byte[16]; // DC4 + Span modes = stackalloc byte[16]; // DC4 this.SetIntra4Mode(modes); } @@ -407,7 +407,7 @@ internal class Vp8EncIterator public int MbAnalyzeBestIntra4Mode(int bestAlpha) { - byte[] modes = new byte[16]; + Span modes = stackalloc byte[16]; const int maxMode = MaxIntra4Mode; Vp8Histogram totalHisto = new(); int curHisto = 0; @@ -494,13 +494,13 @@ internal class Vp8EncIterator this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I16X16; } - public void SetIntra4Mode(byte[] modes) + public void SetIntra4Mode(ReadOnlySpan modes) { int modesIdx = 0; int predIdx = this.PredIdx; for (int y = 4; y > 0; y--) { - modes.AsSpan(modesIdx, 4).CopyTo(this.Preds.AsSpan(predIdx)); + modes.Slice(modesIdx, 4).CopyTo(this.Preds.AsSpan(predIdx)); predIdx += this.predsWidth; modesIdx += 4; } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs index eefc4cd0a..f17d965e8 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs @@ -329,7 +329,7 @@ internal class Vp8Encoder : IDisposable int uvStride = (yStride + 1) >> 1; Vp8EncIterator it = new(this.YTop, this.UvTop, this.Nz, this.MbInfo, this.Preds, this.TopDerr, this.Mbw, this.Mbh); - int[] alphas = new int[WebpConstants.MaxAlpha + 1]; + Span alphas = stackalloc int[WebpConstants.MaxAlpha + 1]; this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); int totalMb = this.Mbw * this.Mbw; this.alpha /= totalMb; @@ -625,15 +625,15 @@ internal class Vp8Encoder : IDisposable } // Simplified k-Means, to assign Nb segments based on alpha-histogram. - private void AssignSegments(int[] alphas) + private void AssignSegments(ReadOnlySpan alphas) { int nb = this.SegmentHeader.NumSegments < NumMbSegments ? this.SegmentHeader.NumSegments : NumMbSegments; - int[] centers = new int[NumMbSegments]; + Span centers = stackalloc int[NumMbSegments]; int weightedAverage = 0; - int[] map = new int[WebpConstants.MaxAlpha + 1]; + Span map = stackalloc int[WebpConstants.MaxAlpha + 1]; int n, k; - int[] accum = new int[NumMbSegments]; - int[] distAccum = new int[NumMbSegments]; + Span accum = stackalloc int[NumMbSegments]; + Span distAccum = stackalloc int[NumMbSegments]; // Bracket the input. for (n = 0; n <= WebpConstants.MaxAlpha && alphas[n] == 0; ++n) @@ -719,7 +719,7 @@ internal class Vp8Encoder : IDisposable this.SetSegmentAlphas(centers, weightedAverage); } - private void SetSegmentAlphas(int[] centers, int mid) + private void SetSegmentAlphas(ReadOnlySpan centers, int mid) { int nb = this.SegmentHeader.NumSegments; Vp8SegmentInfo[] dqm = this.SegmentInfos; @@ -840,7 +840,7 @@ internal class Vp8Encoder : IDisposable private void SetSegmentProbas() { - int[] p = new int[NumMbSegments]; + Span p = stackalloc int[NumMbSegments]; int n; for (n = 0; n < this.Mbw * this.Mbh; ++n) @@ -931,7 +931,7 @@ internal class Vp8Encoder : IDisposable } } - private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span y, Span u, Span v, int yStride, int uvStride, int[] alphas, out int uvAlpha) + private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span y, Span u, Span v, int yStride, int uvStride, Span alphas, out int uvAlpha) { int alpha = 0; uvAlpha = 0; @@ -952,7 +952,7 @@ internal class Vp8Encoder : IDisposable return alpha; } - private int MbAnalyze(Vp8EncIterator it, int[] alphas, out int bestUvAlpha) + private int MbAnalyze(Vp8EncIterator it, Span alphas, out int bestUvAlpha) { it.SetIntra16Mode(0); // default: Intra16, DC_PRED it.SetSkip(false); // not skipped. diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs index 4036fb284..2ace43d2d 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs @@ -10,12 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy; internal sealed class Vp8Histogram { - private readonly int[] scratch = new int[16]; - - private readonly short[] output = new short[16]; - - private readonly int[] distribution = new int[MaxCoeffThresh + 1]; - /// /// Size of histogram used by CollectHistogram. /// @@ -47,17 +41,20 @@ internal sealed class Vp8Histogram public void CollectHistogram(Span reference, Span pred, int startBlock, int endBlock) { + Span scratch = stackalloc int[16]; + Span output = stackalloc short[16]; + Span distribution = stackalloc int[MaxCoeffThresh + 1]; + int j; - this.distribution.AsSpan().Clear(); for (j = startBlock; j < endBlock; j++) { - Vp8Encoding.FTransform(reference[WebpLookupTables.Vp8DspScan[j]..], pred[WebpLookupTables.Vp8DspScan[j]..], this.output, this.scratch); + Vp8Encoding.FTransform(reference[WebpLookupTables.Vp8DspScan[j]..], pred[WebpLookupTables.Vp8DspScan[j]..], output, scratch); // Convert coefficients to bin. if (Avx2.IsSupported) { // Load. - ref short outputRef = ref MemoryMarshal.GetReference(this.output); + ref short outputRef = ref MemoryMarshal.GetReference(output); Vector256 out0 = Unsafe.As>(ref outputRef); // v = abs(out) >> 3 @@ -73,21 +70,21 @@ internal sealed class Vp8Histogram // Convert coefficients to bin. for (int k = 0; k < 16; ++k) { - ++this.distribution[this.output[k]]; + ++distribution[output[k]]; } } else { for (int k = 0; k < 16; ++k) { - int v = Math.Abs(this.output[k]) >> 3; + int v = Math.Abs(output[k]) >> 3; int clippedValue = ClipMax(v, MaxCoeffThresh); - ++this.distribution[clippedValue]; + ++distribution[clippedValue]; } } } - this.SetHistogramData(this.distribution); + this.SetHistogramData(distribution); } public void Merge(Vp8Histogram other) @@ -103,7 +100,7 @@ internal sealed class Vp8Histogram } } - private void SetHistogramData(int[] distribution) + private void SetHistogramData(ReadOnlySpan distribution) { int maxValue = 0; int lastNonZero = 1; diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs index 7384379da..177041506 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs @@ -15,10 +15,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy; /// internal class Vp8Residual { - private readonly byte[] scratch = new byte[32]; - - private readonly ushort[] scratchUShort = new ushort[16]; - public int First { get; set; } public int Last { get; set; } @@ -162,9 +158,10 @@ internal class Vp8Residual if (Avx2.IsSupported) { - Span ctxs = this.scratch.AsSpan(0, 16); - Span levels = this.scratch.AsSpan(16, 16); - Span absLevels = this.scratchUShort.AsSpan(); + Span scratch = stackalloc byte[32]; + Span ctxs = scratch.Slice(0, 16); + Span levels = scratch.Slice(16); + Span absLevels = stackalloc ushort[16]; // Precompute clamped levels and contexts, packed to 8b. ref short outputRef = ref MemoryMarshal.GetReference(this.Coeffs); diff --git a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs index 96ed8903a..cb13825bc 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs @@ -34,16 +34,6 @@ internal sealed class WebpLossyDecoder /// private readonly Configuration configuration; - /// - /// Scratch buffer to reduce allocations. - /// - private readonly int[] scratch = new int[16]; - - /// - /// Another scratch buffer to reduce allocations. - /// - private readonly byte[] scratchBytes = new byte[4]; - /// /// Initializes a new instance of the class. /// @@ -409,6 +399,9 @@ internal sealed class WebpLossyDecoder topYuv.V.CopyTo(yuv[(vOff - WebpConstants.Bps)..]); } + Span scratch = stackalloc int[16]; + Span scratchBytes = stackalloc byte[4]; + // Predict and add residuals. if (block.IsI4x4) { @@ -448,7 +441,7 @@ internal sealed class WebpLossyDecoder LossyUtils.TM4(dst, yuv, offset); break; case 2: - LossyUtils.VE4(dst, yuv, offset, this.scratchBytes); + LossyUtils.VE4(dst, yuv, offset, scratchBytes); break; case 3: LossyUtils.HE4(dst, yuv, offset); @@ -473,7 +466,7 @@ internal sealed class WebpLossyDecoder break; } - DoTransform(bits, coeffs.AsSpan(n * 16), dst, this.scratch); + DoTransform(bits, coeffs.AsSpan(n * 16), dst, scratch); } } else @@ -508,7 +501,7 @@ internal sealed class WebpLossyDecoder { for (int n = 0; n < 16; ++n, bits <<= 2) { - DoTransform(bits, coeffs.AsSpan(n * 16), yDst[WebpConstants.Scan[n]..], this.scratch); + DoTransform(bits, coeffs.AsSpan(n * 16), yDst[WebpConstants.Scan[n]..], scratch); } } } @@ -547,8 +540,8 @@ internal sealed class WebpLossyDecoder break; } - DoUVTransform(bitsUv, coeffs.AsSpan(16 * 16), uDst, this.scratch); - DoUVTransform(bitsUv >> 8, coeffs.AsSpan(20 * 16), vDst, this.scratch); + DoUVTransform(bitsUv, coeffs.AsSpan(16 * 16), uDst, scratch); + DoUVTransform(bitsUv >> 8, coeffs.AsSpan(20 * 16), vDst, scratch); // Stash away top samples for next block. if (mby < dec.MbHeight - 1) @@ -875,14 +868,14 @@ internal sealed class WebpLossyDecoder else { // Parse DC - short[] dc = new short[16]; + Span dc = stackalloc short[16]; int ctx = (int)(mb.NoneZeroDcCoeffs + leftMb.NoneZeroDcCoeffs); int nz = GetCoeffs(br, bands[1], ctx, q.Y2Mat, 0, dc); mb.NoneZeroDcCoeffs = leftMb.NoneZeroDcCoeffs = (uint)(nz > 0 ? 1 : 0); if (nz > 1) { // More than just the DC -> perform the full transform. - LossyUtils.TransformWht(dc, dst, this.scratch); + LossyUtils.TransformWht(dc, dst, stackalloc int[16]); } else { diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index abaa85ef1..21337ce6c 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -17,11 +17,6 @@ namespace SixLabors.ImageSharp.Formats.Webp; /// internal class WebpAnimationDecoder : IDisposable { - /// - /// Reusable buffer. - /// - private readonly byte[] buffer = new byte[4]; - /// /// Used for allocating memory during the decoding operations. /// @@ -89,11 +84,12 @@ internal class WebpAnimationDecoder : IDisposable this.webpMetadata = this.metadata.GetWebpMetadata(); this.webpMetadata.AnimationLoopCount = features.AnimationLoopCount; + Span buffer = stackalloc byte[4]; uint frameCount = 0; int remainingBytes = (int)completeDataSize; while (remainingBytes > 0) { - WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); + WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); remainingBytes -= 4; switch (chunkType) { @@ -103,7 +99,7 @@ internal class WebpAnimationDecoder : IDisposable break; case WebpChunkType.Xmp: case WebpChunkType.Exif: - WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, false, this.buffer); + WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, false, buffer); break; default: WebpThrowHelper.ThrowImageFormatException("Read unexpected webp chunk data"); @@ -134,15 +130,16 @@ internal class WebpAnimationDecoder : IDisposable { AnimationFrameData frameData = this.ReadFrameHeader(stream); long streamStartPosition = stream.Position; + Span buffer = stackalloc byte[4]; - WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); + WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); bool hasAlpha = false; byte alphaChunkHeader = 0; if (chunkType is WebpChunkType.Alpha) { alphaChunkHeader = this.ReadAlphaData(stream); hasAlpha = true; - chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); + chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); } WebpImageInfo? webpInfo = null; @@ -150,12 +147,12 @@ internal class WebpAnimationDecoder : IDisposable switch (chunkType) { case WebpChunkType.Vp8: - webpInfo = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features); + webpInfo = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features); features.Alpha = hasAlpha; features.AlphaChunkHeader = alphaChunkHeader; break; case WebpChunkType.Vp8L: - webpInfo = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features); + webpInfo = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features); break; default: WebpThrowHelper.ThrowImageFormatException("Read unexpected chunk type, should be VP8 or VP8L"); @@ -226,7 +223,7 @@ internal class WebpAnimationDecoder : IDisposable { this.alphaData?.Dispose(); - uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer); + uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, stackalloc byte[4]); int alphaDataSize = (int)(alphaChunkSize - 1); this.alphaData = this.memoryAllocator.Allocate(alphaDataSize); @@ -353,24 +350,26 @@ internal class WebpAnimationDecoder : IDisposable /// Animation frame data. private AnimationFrameData ReadFrameHeader(BufferedReadStream stream) { + Span buffer = stackalloc byte[4]; + AnimationFrameData data = new() { - DataSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer), + DataSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer), // 3 bytes for the X coordinate of the upper left corner of the frame. - X = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer), + X = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer), // 3 bytes for the Y coordinate of the upper left corner of the frame. - Y = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer), + Y = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer), // Frame width Minus One. - Width = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer) + 1, + Width = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer) + 1, // Frame height Minus One. - Height = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer) + 1, + Height = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer) + 1, // Frame duration. - Duration = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer) + Duration = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer) }; byte flags = (byte)stream.ReadByte(); diff --git a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs index e4acdf311..a7ae474e4 100644 --- a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs +++ b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs @@ -18,7 +18,7 @@ internal static class WebpChunkParsingUtils /// Reads the header of a lossy webp image. /// /// Information about this webp image. - public static WebpImageInfo ReadVp8Header(MemoryAllocator memoryAllocator, BufferedReadStream stream, byte[] buffer, WebpFeatures features) + public static WebpImageInfo ReadVp8Header(MemoryAllocator memoryAllocator, BufferedReadStream stream, Span buffer, WebpFeatures features) { // VP8 data size (not including this 4 bytes). int bytesRead = stream.Read(buffer, 0, 4); @@ -77,7 +77,7 @@ internal static class WebpChunkParsingUtils WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 magic bytes"); } - if (!buffer.AsSpan(0, 3).SequenceEqual(WebpConstants.Vp8HeaderMagicBytes)) + if (!buffer.Slice(0, 3).SequenceEqual(WebpConstants.Vp8HeaderMagicBytes)) { WebpThrowHelper.ThrowImageFormatException("VP8 magic bytes not found"); } @@ -91,7 +91,7 @@ internal static class WebpChunkParsingUtils uint tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer); uint width = tmp & 0x3fff; sbyte xScale = (sbyte)(tmp >> 6); - tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(2)); + tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(2)); uint height = tmp & 0x3fff; sbyte yScale = (sbyte)(tmp >> 6); remaining -= 7; @@ -140,7 +140,7 @@ internal static class WebpChunkParsingUtils /// Reads the header of a lossless webp image. /// /// Information about this image. - public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, BufferedReadStream stream, byte[] buffer, WebpFeatures features) + public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, BufferedReadStream stream, Span buffer, WebpFeatures features) { // VP8 data size. uint imageDataSize = ReadChunkSize(stream, buffer); @@ -195,7 +195,7 @@ internal static class WebpChunkParsingUtils /// After the image header, image data will follow. After that optional image metadata chunks (EXIF and XMP) can follow. /// /// Information about this webp image. - public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, byte[] buffer, WebpFeatures features) + public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, Span buffer, WebpFeatures features) { uint fileSize = ReadChunkSize(stream, buffer); @@ -253,7 +253,7 @@ internal static class WebpChunkParsingUtils /// The stream to read from. /// The buffer to store the read data into. /// A unsigned 24 bit integer. - public static uint ReadUnsignedInt24Bit(BufferedReadStream stream, byte[] buffer) + public static uint ReadUnsignedInt24Bit(BufferedReadStream stream, Span buffer) { if (stream.Read(buffer, 0, 3) == 3) { @@ -271,9 +271,11 @@ internal static class WebpChunkParsingUtils /// The stream to read the data from. /// Buffer to store the data read from the stream. /// The chunk size in bytes. - public static uint ReadChunkSize(BufferedReadStream stream, byte[] buffer) + public static uint ReadChunkSize(BufferedReadStream stream, Span buffer) { - if (stream.Read(buffer, 0, 4) == 4) + DebugGuard.IsTrue(buffer.Length == 4, "buffer has wrong length"); + + if (stream.Read(buffer) == 4) { uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer); return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1; @@ -290,9 +292,11 @@ internal static class WebpChunkParsingUtils /// /// Thrown if the input stream is not valid. /// - public static WebpChunkType ReadChunkType(BufferedReadStream stream, byte[] buffer) + public static WebpChunkType ReadChunkType(BufferedReadStream stream, Span buffer) { - if (stream.Read(buffer, 0, 4) == 4) + DebugGuard.IsTrue(buffer.Length == 4, "buffer has wrong length"); + + if (stream.Read(buffer) == 4) { var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer); return chunkType; @@ -306,7 +310,7 @@ internal static class WebpChunkParsingUtils /// If there are more such chunks, readers MAY ignore all except the first one. /// Also, a file may possibly contain both 'EXIF' and 'XMP ' chunks. /// - public static void ParseOptionalChunks(BufferedReadStream stream, WebpChunkType chunkType, ImageMetadata metadata, bool ignoreMetaData, byte[] buffer) + public static void ParseOptionalChunks(BufferedReadStream stream, WebpChunkType chunkType, ImageMetadata metadata, bool ignoreMetaData, Span buffer) { long streamLength = stream.Length; while (stream.Position < streamLength) diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index c158df44d..0d19dda02 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -18,11 +18,6 @@ namespace SixLabors.ImageSharp.Formats.Webp; /// internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable { - /// - /// Reusable buffer. - /// - private readonly byte[] buffer = new byte[4]; - /// /// General configuration options. /// @@ -80,8 +75,9 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable try { ImageMetadata metadata = new(); + Span buffer = stackalloc byte[4]; - uint fileSize = this.ReadImageHeader(stream); + uint fileSize = ReadImageHeader(stream, buffer); using (this.webImageInfo = this.ReadVp8Info(stream, metadata)) { @@ -112,7 +108,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable // There can be optional chunks after the image data, like EXIF and XMP. if (this.webImageInfo.Features != null) { - this.ParseOptionalChunks(stream, metadata, this.webImageInfo.Features); + this.ParseOptionalChunks(stream, metadata, this.webImageInfo.Features, buffer); } return image; @@ -128,7 +124,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable /// public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { - this.ReadImageHeader(stream); + ReadImageHeader(stream, stackalloc byte[4]); ImageMetadata metadata = new(); using (this.webImageInfo = this.ReadVp8Info(stream, metadata, true)) @@ -144,8 +140,9 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable /// Reads and skips over the image header. /// /// The stream to decode from. + /// Temporary buffer. /// The file size in bytes. - private uint ReadImageHeader(BufferedReadStream stream) + private static uint ReadImageHeader(BufferedReadStream stream, Span buffer) { // Skip FourCC header, we already know its a RIFF file at this point. stream.Skip(4); @@ -153,7 +150,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable // Read file size. // The size of the file in bytes starting at offset 8. // The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC. - uint fileSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer); + uint fileSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer); // Skip 'WEBP' from the header. stream.Skip(4); @@ -172,35 +169,36 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable { WebpMetadata webpMetadata = metadata.GetFormatMetadata(WebpFormat.Instance); - WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); + Span buffer = stackalloc byte[4]; + WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); WebpFeatures features = new(); switch (chunkType) { case WebpChunkType.Vp8: webpMetadata.FileFormat = WebpFileFormatType.Lossy; - return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features); + return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features); case WebpChunkType.Vp8L: webpMetadata.FileFormat = WebpFileFormatType.Lossless; - return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features); + return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features); case WebpChunkType.Vp8X: - WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(stream, this.buffer, features); + WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(stream, buffer, features); while (stream.Position < stream.Length) { - chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); + chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); if (chunkType == WebpChunkType.Vp8) { webpMetadata.FileFormat = WebpFileFormatType.Lossy; - webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features); + webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features); } else if (chunkType == WebpChunkType.Vp8L) { webpMetadata.FileFormat = WebpFileFormatType.Lossless; - webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features); + webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features); } else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType)) { - bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, metadata, chunkType, features, ignoreAlpha); + bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, metadata, chunkType, features, ignoreAlpha, buffer); if (isAnimationChunk) { return webpInfos; @@ -209,7 +207,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable else { // Ignore unknown chunks. - uint chunkSize = this.ReadChunkSize(stream); + uint chunkSize = ReadChunkSize(stream, buffer); stream.Skip((int)chunkSize); } } @@ -230,34 +228,36 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable /// The chunk type. /// The webp image features. /// For identify, the alpha data should not be read. + /// Temporary buffer. /// true, if its a alpha chunk. private bool ParseOptionalExtendedChunks( BufferedReadStream stream, ImageMetadata metadata, WebpChunkType chunkType, WebpFeatures features, - bool ignoreAlpha) + bool ignoreAlpha, + Span buffer) { switch (chunkType) { case WebpChunkType.Iccp: - this.ReadIccProfile(stream, metadata); + this.ReadIccProfile(stream, metadata, buffer); break; case WebpChunkType.Exif: - this.ReadExifProfile(stream, metadata); + this.ReadExifProfile(stream, metadata, buffer); break; case WebpChunkType.Xmp: - this.ReadXmpProfile(stream, metadata); + this.ReadXmpProfile(stream, metadata, buffer); break; case WebpChunkType.AnimationParameter: - this.ReadAnimationParameters(stream, features); + ReadAnimationParameters(stream, features, buffer); return true; case WebpChunkType.Alpha: - this.ReadAlphaData(stream, features, ignoreAlpha); + this.ReadAlphaData(stream, features, ignoreAlpha, buffer); break; default: WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); @@ -273,7 +273,8 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable /// The stream to decode from. /// The image metadata. /// The webp features. - private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features) + /// Temporary buffer. + private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features, Span buffer) { if (this.skipMetadata || (!features.ExifProfile && !features.XmpMetaData)) { @@ -284,19 +285,19 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable while (stream.Position < streamLength) { // Read chunk header. - WebpChunkType chunkType = this.ReadChunkType(stream); + WebpChunkType chunkType = ReadChunkType(stream, buffer); if (chunkType == WebpChunkType.Exif && metadata.ExifProfile == null) { - this.ReadExifProfile(stream, metadata); + this.ReadExifProfile(stream, metadata, buffer); } else if (chunkType == WebpChunkType.Xmp && metadata.XmpProfile == null) { - this.ReadXmpProfile(stream, metadata); + this.ReadXmpProfile(stream, metadata, buffer); } else { // Skip duplicate XMP or EXIF chunk. - uint chunkLength = this.ReadChunkSize(stream); + uint chunkLength = ReadChunkSize(stream, buffer); stream.Skip((int)chunkLength); } } @@ -307,9 +308,10 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable /// /// The stream to decode from. /// The image metadata. - private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata) + /// Temporary buffer. + private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata, Span buffer) { - uint exifChunkSize = this.ReadChunkSize(stream); + uint exifChunkSize = ReadChunkSize(stream, buffer); if (this.skipMetadata) { stream.Skip((int)exifChunkSize); @@ -333,9 +335,10 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable /// /// The stream to decode from. /// The image metadata. - private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata) + /// Temporary buffer. + private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata, Span buffer) { - uint xmpChunkSize = this.ReadChunkSize(stream); + uint xmpChunkSize = ReadChunkSize(stream, buffer); if (this.skipMetadata) { stream.Skip((int)xmpChunkSize); @@ -359,9 +362,10 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable /// /// The stream to decode from. /// The image metadata. - private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata) + /// Temporary buffer. + private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata, Span buffer) { - uint iccpChunkSize = this.ReadChunkSize(stream); + uint iccpChunkSize = ReadChunkSize(stream, buffer); if (this.skipMetadata) { stream.Skip((int)iccpChunkSize); @@ -388,22 +392,23 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable /// /// The stream to decode from. /// The webp features. - private void ReadAnimationParameters(BufferedReadStream stream, WebpFeatures features) + /// Temporary buffer. + private static void ReadAnimationParameters(BufferedReadStream stream, WebpFeatures features, Span buffer) { features.Animation = true; - uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer); + uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer); byte blue = (byte)stream.ReadByte(); byte green = (byte)stream.ReadByte(); byte red = (byte)stream.ReadByte(); byte alpha = (byte)stream.ReadByte(); features.AnimationBackgroundColor = new Color(new Rgba32(red, green, blue, alpha)); - int bytesRead = stream.Read(this.buffer, 0, 2); + int bytesRead = stream.Read(buffer, 0, 2); if (bytesRead != 2) { WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the animation loop count"); } - features.AnimationLoopCount = BinaryPrimitives.ReadUInt16LittleEndian(this.buffer); + features.AnimationLoopCount = BinaryPrimitives.ReadUInt16LittleEndian(buffer); } /// @@ -412,9 +417,10 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable /// The stream to decode from. /// The features. /// if set to true, skips the chunk data. - private void ReadAlphaData(BufferedReadStream stream, WebpFeatures features, bool ignoreAlpha) + /// Temporary buffer. + private void ReadAlphaData(BufferedReadStream stream, WebpFeatures features, bool ignoreAlpha, Span buffer) { - uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer); + uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer); if (ignoreAlpha) { stream.Skip((int)alphaChunkSize); @@ -436,14 +442,15 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable /// Identifies the chunk type from the chunk. /// /// The stream to decode from. + /// Temporary buffer. /// /// Thrown if the input stream is not valid. /// - private WebpChunkType ReadChunkType(BufferedReadStream stream) + private static WebpChunkType ReadChunkType(BufferedReadStream stream, Span buffer) { - if (stream.Read(this.buffer, 0, 4) == 4) + if (stream.Read(buffer, 0, 4) == 4) { - return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); + return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer); } throw new ImageFormatException("Invalid Webp data."); @@ -454,13 +461,14 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable /// so the chunk size will be increased by 1 in those cases. /// /// The stream to decode from. + /// Temporary buffer. /// The chunk size in bytes. /// Invalid data. - private uint ReadChunkSize(BufferedReadStream stream) + private static uint ReadChunkSize(BufferedReadStream stream, Span buffer) { - if (stream.Read(this.buffer, 0, 4) == 4) + if (stream.Read(buffer, 0, 4) == 4) { - uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer); return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1; }