Browse Source

Reduced intermediate allocations: Webp

pull/2415/head
Günther Foidl 3 years ago
parent
commit
d9e8d79ddd
  1. 2
      src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs
  2. 22
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  3. 15
      src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
  4. 32
      src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs
  5. 26
      src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs
  6. 16
      src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs
  7. 57
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  8. 5
      src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
  9. 2
      src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs
  10. 8
      src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs
  11. 20
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  12. 25
      src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs
  13. 11
      src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs
  14. 27
      src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs
  15. 35
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  16. 26
      src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
  17. 104
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

2
src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs

@ -192,7 +192,7 @@ internal class Vp8LBitReader : BitReaderBase
[MethodImpl(InliningOptions.ShortMethod)]
private void ShiftBytes()
{
System.Span<byte> dataSpan = this.Data!.Memory.Span;
Span<byte> dataSpan = this.Data!.Memory.Span;
while (this.bitPos >= 8 && this.pos < this.len)
{
this.value >>= 8;

22
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
/// <summary>
/// A scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] scratchBuffer = new byte[4];
private ScratchBuffer scratchBuffer; // mutable struct, don't make readonly
/// <summary>
/// Initializes a new instance of the <see cref="BitWriterBase"/> 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<byte> buf = this.scratchBuffer.AsSpan(0, 4);
Span<byte> 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<byte> dataBytes, bool alphaDataIsCompressed)
{
uint size = (uint)dataBytes.Length + 1;
Span<byte> buf = this.scratchBuffer.AsSpan(0, 4);
Span<byte> 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<byte> buf = this.scratchBuffer.AsSpan(0, 4);
Span<byte> 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<byte> buf = this.scratchBuffer.AsSpan(0, 4);
Span<byte> 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<byte> Span => MemoryMarshal.CreateSpan(ref this.scratch[0], 4);
}
}

15
src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs

@ -14,11 +14,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter;
/// </summary>
internal class Vp8LBitWriter : BitWriterBase
{
/// <summary>
/// A scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] scratchBuffer = new byte[8];
/// <summary>
/// This is the minimum amount of size the memory buffer is guaranteed to grow when extra space is needed.
/// </summary>
@ -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<byte> 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<byte> 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;

32
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
{
/// <summary>
/// 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<Vp8LHistogram> imageHisto, Vp8LHistogram tmpHisto, ushort[] histogramSymbols)
public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, uint quality, int histoBits, int cacheBits, List<Vp8LHistogram> imageHisto, Vp8LHistogram tmpHisto, Span<ushort> 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<Vp8LHistogram> origHistograms, List<Vp8LHistogram> histograms, ushort[] histogramSymbols)
private static int HistogramCopyAndAnalyze(List<Vp8LHistogram> origHistograms, List<Vp8LHistogram> histograms, Span<ushort> 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<Vp8LHistogram> histograms,
ushort[] clusters,
Span<ushort> clusters,
ushort[] clusterMappings,
Vp8LHistogram curCombo,
ushort[] binMap,
int numBins,
double combineCostFactor)
{
var binInfo = new HistogramBinInfo[BinSize];
Span<HistogramBinInfo> 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.
/// </summary>
private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numClusters, ushort[] clusterMappingsTmp, ushort[] symbols)
private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numClusters, ushort[] clusterMappingsTmp, Span<ushort> 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<int> 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<int> src = mappings.AsSpan(mappingIndex + 1, numUsed - mappingIndex - 1);
Span<int> dst = mappings.AsSpan(mappingIndex);
int mappingIndex = mappings.IndexOf(bestIdx2);
Span<int> src = mappings.Slice(mappingIndex + 1, numUsed - mappingIndex - 1);
Span<int> 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<Vp8LHistogram> input, List<Vp8LHistogram> output, ushort[] symbols)
private static void HistogramRemap(List<Vp8LHistogram> input, List<Vp8LHistogram> output, Span<ushort> symbols)
{
int inSize = input.Count;
int outSize = output.Count;

26
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<HuffmanTree> huffTree, HuffmanTreeCode huffCode)
{
int numSymbols = huffCode.NumSymbols;
bufRle.AsSpan().Clear();
@ -159,7 +159,7 @@ internal static class HuffmanUtils
/// <param name="histogramSize">The size of the histogram.</param>
/// <param name="treeDepthLimit">The tree depth limit.</param>
/// <param name="bitDepths">How many bits are used for the symbol.</param>
public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths)
public static void GenerateOptimalTree(Span<HuffmanTree> tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths)
{
uint countMin;
int treeSizeOrig = 0;
@ -177,7 +177,7 @@ internal static class HuffmanUtils
return;
}
Span<HuffmanTree> treePool = tree.AsSpan(treeSizeOrig);
Span<HuffmanTree> 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<HuffmanTree> treeSlice = tree.AsSpan(0, treeSize);
Span<HuffmanTree> 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<int> 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<int> counts = stackalloc int[WebpConstants.MaxAllowedCodeLength + 1]; // number of codes of each length.
Span<int> 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<uint> nextCode = stackalloc uint[WebpConstants.MaxAllowedCodeLength + 1];
Span<int> 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.
/// </summary>
private static int NextTableBitSize(int[] count, int len, int rootBits)
private static int NextTableBitSize(ReadOnlySpan<int> count, int len, int rootBits)
{
int left = 1 << (len - rootBits);
while (len < WebpConstants.MaxAllowedCodeLength)

16
src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs

@ -57,11 +57,13 @@ internal static unsafe class PredictorEncoder
Span<short> 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<byte> maxDiffs = MemoryMarshal.Cast<uint, byte>(currentRow[(width + 1)..]);
float bestDiff = MaxDiffCost;
int bestMode = 0;
uint[] residuals = new uint[1 << WebpConstants.MaxTransformBits];
Span<uint> 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;
}

57
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

@ -23,7 +23,7 @@ internal class Vp8LEncoder : IDisposable
/// <summary>
/// Scratch buffer to reduce allocations.
/// </summary>
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<uint> 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<ushort> histogramSymbols = histogramImageXySize <= 64 ? stackalloc ushort[histogramImageXySize] : new ushort[histogramImageXySize];
Span<HuffmanTree> 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
/// </summary>
private void EncodePalette(bool lowEffort)
{
Span<uint> tmpPalette = new uint[WebpConstants.MaxPaletteSize];
Span<uint> tmpPalette = stackalloc uint[WebpConstants.MaxPaletteSize];
int paletteSize = this.PaletteSize;
Span<uint> 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<HuffmanTree> 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<HuffmanTree> huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode)
{
int count = 0;
Span<int> symbols = this.scratch.AsSpan(0, 2);
Span<int> 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<HuffmanTree> 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<ushort> 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<double> entropyComp = stackalloc double[(int)HistoIx.HistoTotal];
Span<double> 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<HuffmanTree> 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();
}
/// <summary>
/// Scratch buffer to reduce allocations.
/// </summary>
private unsafe struct ScratchBuffer
{
private fixed int scratch[256];
public Span<int> Span => MemoryMarshal.CreateSpan(ref this.scratch[0], 256);
}
}

5
src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs

@ -498,10 +498,7 @@ internal sealed class WebpLosslessDecoder
private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, Span<HuffmanCode> table)
{
bool simpleCode = this.bitReader.ReadBit();
for (int i = 0; i < alphabetSize; i++)
{
codeLengths[i] = 0;
}
codeLengths.AsSpan(0, alphabetSize).Clear();
if (simpleCode)
{

2
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<short> tmpLevels = new short[16];
Span<short> tmpLevels = stackalloc short[16];
do
{
const int numBlocks = 1;

8
src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs

@ -374,7 +374,7 @@ internal class Vp8EncIterator
}
else
{
byte[] modes = new byte[16]; // DC4
Span<byte> 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<byte> 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<byte> 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;
}

20
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<int> 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<int> alphas)
{
int nb = this.SegmentHeader.NumSegments < NumMbSegments ? this.SegmentHeader.NumSegments : NumMbSegments;
int[] centers = new int[NumMbSegments];
Span<int> centers = stackalloc int[NumMbSegments];
int weightedAverage = 0;
int[] map = new int[WebpConstants.MaxAlpha + 1];
Span<int> map = stackalloc int[WebpConstants.MaxAlpha + 1];
int n, k;
int[] accum = new int[NumMbSegments];
int[] distAccum = new int[NumMbSegments];
Span<int> accum = stackalloc int[NumMbSegments];
Span<int> 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<int> 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<int> 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<byte> y, Span<byte> u, Span<byte> v, int yStride, int uvStride, int[] alphas, out int uvAlpha)
private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span<byte> y, Span<byte> u, Span<byte> v, int yStride, int uvStride, Span<int> 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<int> alphas, out int bestUvAlpha)
{
it.SetIntra16Mode(0); // default: Intra16, DC_PRED
it.SetSkip(false); // not skipped.

25
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];
/// <summary>
/// Size of histogram used by CollectHistogram.
/// </summary>
@ -47,17 +41,20 @@ internal sealed class Vp8Histogram
public void CollectHistogram(Span<byte> reference, Span<byte> pred, int startBlock, int endBlock)
{
Span<int> scratch = stackalloc int[16];
Span<short> output = stackalloc short[16];
Span<int> 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<short>(this.output);
ref short outputRef = ref MemoryMarshal.GetReference(output);
Vector256<byte> out0 = Unsafe.As<short, Vector256<byte>>(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<int> distribution)
{
int maxValue = 0;
int lastNonZero = 1;

11
src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs

@ -15,10 +15,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy;
/// </summary>
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<byte> ctxs = this.scratch.AsSpan(0, 16);
Span<byte> levels = this.scratch.AsSpan(16, 16);
Span<ushort> absLevels = this.scratchUShort.AsSpan();
Span<byte> scratch = stackalloc byte[32];
Span<byte> ctxs = scratch.Slice(0, 16);
Span<byte> levels = scratch.Slice(16);
Span<ushort> absLevels = stackalloc ushort[16];
// Precompute clamped levels and contexts, packed to 8b.
ref short outputRef = ref MemoryMarshal.GetReference<short>(this.Coeffs);

27
src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs

@ -34,16 +34,6 @@ internal sealed class WebpLossyDecoder
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// Scratch buffer to reduce allocations.
/// </summary>
private readonly int[] scratch = new int[16];
/// <summary>
/// Another scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] scratchBytes = new byte[4];
/// <summary>
/// Initializes a new instance of the <see cref="WebpLossyDecoder"/> class.
/// </summary>
@ -409,6 +399,9 @@ internal sealed class WebpLossyDecoder
topYuv.V.CopyTo(yuv[(vOff - WebpConstants.Bps)..]);
}
Span<int> scratch = stackalloc int[16];
Span<byte> 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<short> 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
{

35
src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

@ -17,11 +17,6 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// </summary>
internal class WebpAnimationDecoder : IDisposable
{
/// <summary>
/// Reusable buffer.
/// </summary>
private readonly byte[] buffer = new byte[4];
/// <summary>
/// Used for allocating memory during the decoding operations.
/// </summary>
@ -89,11 +84,12 @@ internal class WebpAnimationDecoder : IDisposable
this.webpMetadata = this.metadata.GetWebpMetadata();
this.webpMetadata.AnimationLoopCount = features.AnimationLoopCount;
Span<byte> 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<byte> 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<byte>(alphaDataSize);
@ -353,24 +350,26 @@ internal class WebpAnimationDecoder : IDisposable
/// <returns>Animation frame data.</returns>
private AnimationFrameData ReadFrameHeader(BufferedReadStream stream)
{
Span<byte> 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();

26
src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs

@ -18,7 +18,7 @@ internal static class WebpChunkParsingUtils
/// Reads the header of a lossy webp image.
/// </summary>
/// <returns>Information about this webp image.</returns>
public static WebpImageInfo ReadVp8Header(MemoryAllocator memoryAllocator, BufferedReadStream stream, byte[] buffer, WebpFeatures features)
public static WebpImageInfo ReadVp8Header(MemoryAllocator memoryAllocator, BufferedReadStream stream, Span<byte> 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.
/// </summary>
/// <returns>Information about this image.</returns>
public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, BufferedReadStream stream, byte[] buffer, WebpFeatures features)
public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, BufferedReadStream stream, Span<byte> 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.
/// </summary>
/// <returns>Information about this webp image.</returns>
public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, byte[] buffer, WebpFeatures features)
public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, Span<byte> buffer, WebpFeatures features)
{
uint fileSize = ReadChunkSize(stream, buffer);
@ -253,7 +253,7 @@ internal static class WebpChunkParsingUtils
/// <param name="stream">The stream to read from.</param>
/// <param name="buffer">The buffer to store the read data into.</param>
/// <returns>A unsigned 24 bit integer.</returns>
public static uint ReadUnsignedInt24Bit(BufferedReadStream stream, byte[] buffer)
public static uint ReadUnsignedInt24Bit(BufferedReadStream stream, Span<byte> buffer)
{
if (stream.Read(buffer, 0, 3) == 3)
{
@ -271,9 +271,11 @@ internal static class WebpChunkParsingUtils
/// <param name="stream">The stream to read the data from.</param>
/// <param name="buffer">Buffer to store the data read from the stream.</param>
/// <returns>The chunk size in bytes.</returns>
public static uint ReadChunkSize(BufferedReadStream stream, byte[] buffer)
public static uint ReadChunkSize(BufferedReadStream stream, Span<byte> 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
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
public static WebpChunkType ReadChunkType(BufferedReadStream stream, byte[] buffer)
public static WebpChunkType ReadChunkType(BufferedReadStream stream, Span<byte> 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.
/// </summary>
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<byte> buffer)
{
long streamLength = stream.Length;
while (stream.Position < streamLength)

104
src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

@ -18,11 +18,6 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// </summary>
internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
{
/// <summary>
/// Reusable buffer.
/// </summary>
private readonly byte[] buffer = new byte[4];
/// <summary>
/// General configuration options.
/// </summary>
@ -80,8 +75,9 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
try
{
ImageMetadata metadata = new();
Span<byte> 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
/// <inheritdoc />
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.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="buffer">Temporary buffer.</param>
/// <returns>The file size in bytes.</returns>
private uint ReadImageHeader(BufferedReadStream stream)
private static uint ReadImageHeader(BufferedReadStream stream, Span<byte> 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<byte> 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
/// <param name="chunkType">The chunk type.</param>
/// <param name="features">The webp image features.</param>
/// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param>
/// <param name="buffer">Temporary buffer.</param></param>
/// <returns>true, if its a alpha chunk.</returns>
private bool ParseOptionalExtendedChunks(
BufferedReadStream stream,
ImageMetadata metadata,
WebpChunkType chunkType,
WebpFeatures features,
bool ignoreAlpha)
bool ignoreAlpha,
Span<byte> 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
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="features">The webp features.</param>
private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features)
/// <param name="buffer">Temporary buffer.</param>
private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features, Span<byte> 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
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata)
/// <param name="buffer">Temporary buffer.</param>
private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata, Span<byte> 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
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata)
/// <param name="buffer">Temporary buffer.</param>
private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata, Span<byte> 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
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata)
/// <param name="buffer">Temporary buffer.</param>
private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata, Span<byte> 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
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="features">The webp features.</param>
private void ReadAnimationParameters(BufferedReadStream stream, WebpFeatures features)
/// <param name="buffer">Temporary buffer.</param>
private static void ReadAnimationParameters(BufferedReadStream stream, WebpFeatures features, Span<byte> 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);
}
/// <summary>
@ -412,9 +417,10 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <param name="stream">The stream to decode from.</param>
/// <param name="features">The features.</param>
/// <param name="ignoreAlpha">if set to true, skips the chunk data.</param>
private void ReadAlphaData(BufferedReadStream stream, WebpFeatures features, bool ignoreAlpha)
/// <param name="buffer">Temporary buffer.</param>
private void ReadAlphaData(BufferedReadStream stream, WebpFeatures features, bool ignoreAlpha, Span<byte> 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.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="buffer">Temporary buffer.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
private WebpChunkType ReadChunkType(BufferedReadStream stream)
private static WebpChunkType ReadChunkType(BufferedReadStream stream, Span<byte> 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.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="buffer">Temporary buffer.</param>
/// <returns>The chunk size in bytes.</returns>
/// <exception cref="ImageFormatException">Invalid data.</exception>
private uint ReadChunkSize(BufferedReadStream stream)
private static uint ReadChunkSize(BufferedReadStream stream, Span<byte> 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;
}

Loading…
Cancel
Save