diff --git a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs index 61133142bf..211185dbba 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs @@ -50,8 +50,10 @@ public static Vp8LBackwardRefs GetBackwardReferences( double bitCostBest = -1; int cacheBitsInitial = cacheBits; Vp8LHashChain? hashChainBox = null; - var stats = new Vp8LStreaks(); - var bitsEntropy = new Vp8LBitEntropy(); + Vp8LStreaks stats = new(); + Vp8LBitEntropy bitsEntropy = new(); + + ColorCache[] colorCache = new ColorCache[WebpConstants.MaxColorCacheBits + 1]; for (int lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1) { int cacheBitsTmp = cacheBitsInitial; @@ -76,21 +78,19 @@ public static Vp8LBackwardRefs GetBackwardReferences( } // Next, try with a color cache and update the references. - cacheBitsTmp = CalculateBestCacheSize(bgra, quality, worst, cacheBitsTmp); + cacheBitsTmp = CalculateBestCacheSize(memoryAllocator, colorCache, bgra, quality, worst, cacheBitsTmp); if (cacheBitsTmp > 0) { BackwardRefsWithLocalCache(bgra, cacheBitsTmp, worst); } // Keep the best backward references. - var histo = new Vp8LHistogram(worst, cacheBitsTmp); + using OwnedVp8LHistogram histo = OwnedVp8LHistogram.Create(memoryAllocator, worst, cacheBitsTmp); double bitCost = histo.EstimateBits(stats, bitsEntropy); if (lz77TypeBest == 0 || bitCost < bitCostBest) { - Vp8LBackwardRefs tmp = worst; - worst = best; - best = tmp; + (best, worst) = (worst, best); bitCostBest = bitCost; cacheBits = cacheBitsTmp; lz77TypeBest = lz77Type; @@ -102,7 +102,7 @@ public static Vp8LBackwardRefs GetBackwardReferences( { Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox!; BackwardReferencesTraceBackwards(width, height, memoryAllocator, bgra, cacheBits, hashChainTmp, best, worst); - var histo = new Vp8LHistogram(worst, cacheBits); + using OwnedVp8LHistogram histo = OwnedVp8LHistogram.Create(memoryAllocator, worst, cacheBits); double bitCostTrace = histo.EstimateBits(stats, bitsEntropy); if (bitCostTrace < bitCostBest) { @@ -123,7 +123,13 @@ public static Vp8LBackwardRefs GetBackwardReferences( /// The local color cache is also disabled for the lower (smaller then 25) quality. /// /// Best cache size. - private static int CalculateBestCacheSize(ReadOnlySpan bgra, uint quality, Vp8LBackwardRefs refs, int bestCacheBits) + private static int CalculateBestCacheSize( + MemoryAllocator memoryAllocator, + Span colorCache, + ReadOnlySpan bgra, + uint quality, + Vp8LBackwardRefs refs, + int bestCacheBits) { int cacheBitsMax = quality <= 25 ? 0 : bestCacheBits; if (cacheBitsMax == 0) @@ -134,11 +140,11 @@ private static int CalculateBestCacheSize(ReadOnlySpan bgra, uint quality, double entropyMin = MaxEntropy; int pos = 0; - var colorCache = new ColorCache[WebpConstants.MaxColorCacheBits + 1]; - var histos = new Vp8LHistogram[WebpConstants.MaxColorCacheBits + 1]; - for (int i = 0; i <= WebpConstants.MaxColorCacheBits; i++) + + using Vp8LHistogramSet histos = new(memoryAllocator, colorCache.Length, 0); + for (int i = 0; i < colorCache.Length; i++) { - histos[i] = new Vp8LHistogram(paletteCodeBits: i); + histos[i].PaletteCodeBits = i; colorCache[i] = new ColorCache(i); } @@ -149,10 +155,10 @@ private static int CalculateBestCacheSize(ReadOnlySpan bgra, uint quality, if (v.IsLiteral()) { uint pix = bgra[pos++]; - uint a = (pix >> 24) & 0xff; - uint r = (pix >> 16) & 0xff; - uint g = (pix >> 8) & 0xff; - uint b = (pix >> 0) & 0xff; + int a = (int)(pix >> 24) & 0xff; + int r = (int)(pix >> 16) & 0xff; + int g = (int)(pix >> 8) & 0xff; + int b = (int)(pix >> 0) & 0xff; // The keys of the caches can be derived from the longest one. int key = ColorCache.HashPix(pix, 32 - cacheBitsMax); @@ -218,8 +224,8 @@ private static int CalculateBestCacheSize(ReadOnlySpan bgra, uint quality, } } - var stats = new Vp8LStreaks(); - var bitsEntropy = new Vp8LBitEntropy(); + Vp8LStreaks stats = new(); + Vp8LBitEntropy bitsEntropy = new(); for (int i = 0; i <= cacheBitsMax; i++) { double entropy = histos[i].EstimateBits(stats, bitsEntropy); @@ -266,7 +272,7 @@ private static void BackwardReferencesHashChainDistanceOnly( int pixCount = xSize * ySize; bool useColorCache = cacheBits > 0; int literalArraySize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (cacheBits > 0 ? 1 << cacheBits : 0); - var costModel = new CostModel(literalArraySize); + CostModel costModel = new(memoryAllocator, literalArraySize); int offsetPrev = -1; int lenPrev = -1; double offsetCost = -1; @@ -280,7 +286,7 @@ private static void BackwardReferencesHashChainDistanceOnly( } costModel.Build(xSize, cacheBits, refs); - using var costManager = new CostManager(memoryAllocator, distArrayBuffer, pixCount, costModel); + using CostManager costManager = new(memoryAllocator, distArrayBuffer, pixCount, costModel); Span costManagerCosts = costManager.Costs.GetSpan(); Span distArray = distArrayBuffer.GetSpan(); @@ -441,12 +447,12 @@ private static void AddSingleLiteralWithCostModel( int ix = useColorCache ? colorCache!.Contains(color) : -1; if (ix >= 0) { - double mul0 = 0.68; + const double mul0 = 0.68; costVal += costModel.GetCacheCost((uint)ix) * mul0; } else { - double mul1 = 0.82; + const double mul1 = 0.82; if (useColorCache) { colorCache!.Insert(color); @@ -693,10 +699,8 @@ private static void BackwardReferencesLz77Box(int xSize, int ySize, ReadOnlySpan bestLength = MaxLength; break; } - else - { - bestLength = currLength; - } + + bestLength = currLength; } } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs b/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs index c99e8fe6e2..beebc48abc 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs @@ -1,18 +1,23 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Formats.Webp.Lossless; internal class CostModel { + private readonly MemoryAllocator memoryAllocator; private const int ValuesInBytes = 256; /// /// Initializes a new instance of the class. /// + /// The memory allocator. /// The literal array size. - public CostModel(int literalArraySize) + public CostModel(MemoryAllocator memoryAllocator, int literalArraySize) { + this.memoryAllocator = memoryAllocator; this.Alpha = new double[ValuesInBytes]; this.Red = new double[ValuesInBytes]; this.Blue = new double[ValuesInBytes]; @@ -32,13 +37,12 @@ public CostModel(int literalArraySize) public void Build(int xSize, int cacheBits, Vp8LBackwardRefs backwardRefs) { - var histogram = new Vp8LHistogram(cacheBits); - using System.Collections.Generic.List.Enumerator refsEnumerator = backwardRefs.Refs.GetEnumerator(); + using OwnedVp8LHistogram histogram = OwnedVp8LHistogram.Create(this.memoryAllocator, cacheBits); // The following code is similar to HistogramCreate but converts the distance to plane code. - while (refsEnumerator.MoveNext()) + for (int i = 0; i < backwardRefs.Refs.Count; i++) { - histogram.AddSinglePixOrCopy(refsEnumerator.Current, true, xSize); + histogram.AddSinglePixOrCopy(backwardRefs.Refs[i], true, xSize); } ConvertPopulationCountTableToBitEstimates(histogram.NumCodes(), histogram.Literal, this.Literal); @@ -70,7 +74,7 @@ public double GetCacheCost(uint idx) public double GetLiteralCost(uint v) => this.Alpha[v >> 24] + this.Red[(v >> 16) & 0xff] + this.Literal[(v >> 8) & 0xff] + this.Blue[v & 0xff]; - private static void ConvertPopulationCountTableToBitEstimates(int numSymbols, uint[] populationCounts, double[] output) + private static void ConvertPopulationCountTableToBitEstimates(int numSymbols, Span populationCounts, double[] output) { uint sum = 0; int nonzeros = 0; diff --git a/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs index dd59ed2097..3a96362cfd 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs @@ -2,7 +2,9 @@ // Licensed under the Six Labors Split License. #nullable disable +using System.Buffers; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Webp.Lossless; @@ -27,19 +29,28 @@ internal static 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, Span histogramSymbols) + public static void GetHistoImageSymbols( + MemoryAllocator memoryAllocator, + int xSize, + int ySize, + Vp8LBackwardRefs refs, + uint quality, + int histoBits, + int cacheBits, + Vp8LHistogramSet imageHisto, + Vp8LHistogram tmpHisto, + Span histogramSymbols) { int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(xSize, histoBits) : 1; int histoYSize = histoBits > 0 ? LosslessUtils.SubSampleSize(ySize, histoBits) : 1; int imageHistoRawSize = histoXSize * histoYSize; - int entropyCombineNumBins = BinSize; - ushort[] mapTmp = new ushort[imageHistoRawSize]; - ushort[] clusterMappings = new ushort[imageHistoRawSize]; - var origHisto = new List(imageHistoRawSize); - for (int i = 0; i < imageHistoRawSize; i++) - { - origHisto.Add(new Vp8LHistogram(cacheBits)); - } + const int entropyCombineNumBins = BinSize; + + using IMemoryOwner tmp = memoryAllocator.Allocate(imageHistoRawSize * 2, AllocationOptions.Clean); + Span mapTmp = tmp.Slice(0, imageHistoRawSize); + Span clusterMappings = tmp.Slice(imageHistoRawSize, imageHistoRawSize); + + using Vp8LHistogramSet origHisto = new(memoryAllocator, imageHistoRawSize, cacheBits); // Construct the histograms from the backward references. HistogramBuild(xSize, histoBits, refs, origHisto); @@ -50,18 +61,17 @@ public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs r bool entropyCombine = numUsed > entropyCombineNumBins * 2 && quality < 100; if (entropyCombine) { - ushort[] binMap = mapTmp; int numClusters = numUsed; double combineCostFactor = GetCombineCostFactor(imageHistoRawSize, quality); - HistogramAnalyzeEntropyBin(imageHisto, binMap); + HistogramAnalyzeEntropyBin(imageHisto, mapTmp); // Collapse histograms with similar entropy. - HistogramCombineEntropyBin(imageHisto, histogramSymbols, clusterMappings, tmpHisto, binMap, entropyCombineNumBins, combineCostFactor); + HistogramCombineEntropyBin(imageHisto, histogramSymbols, clusterMappings, tmpHisto, mapTmp, entropyCombineNumBins, combineCostFactor); OptimizeHistogramSymbols(clusterMappings, numClusters, mapTmp, histogramSymbols); } - float x = quality / 100.0f; + float x = quality / 100F; // Cubic ramp between 1 and MaxHistoGreedy: int thresholdSize = (int)(1 + (x * x * x * (MaxHistoGreedy - 1))); @@ -77,26 +87,25 @@ public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs r HistogramRemap(origHisto, imageHisto, histogramSymbols); } - private static void RemoveEmptyHistograms(List histograms) + private static void RemoveEmptyHistograms(Vp8LHistogramSet histograms) { - int size = 0; - for (int i = 0; i < histograms.Count; i++) + for (int i = histograms.Count - 1; i >= 0; i--) { if (histograms[i] == null) { - continue; + histograms.RemoveAt(i); } - - histograms[size++] = histograms[i]; } - - histograms.RemoveRange(size, histograms.Count - size); } /// /// Construct the histograms from the backward references. /// - private static void HistogramBuild(int xSize, int histoBits, Vp8LBackwardRefs backwardRefs, List histograms) + private static void HistogramBuild( + int xSize, + int histoBits, + Vp8LBackwardRefs backwardRefs, + Vp8LHistogramSet histograms) { int x = 0, y = 0; int histoXSize = LosslessUtils.SubSampleSize(xSize, histoBits); @@ -119,10 +128,10 @@ private static void HistogramBuild(int xSize, int histoBits, Vp8LBackwardRefs ba /// Partition histograms to different entropy bins for three dominant (literal, /// red and blue) symbol costs and compute the histogram aggregate bitCost. /// - private static void HistogramAnalyzeEntropyBin(List histograms, ushort[] binMap) + private static void HistogramAnalyzeEntropyBin(Vp8LHistogramSet histograms, Span binMap) { int histoSize = histograms.Count; - var costRange = new DominantCostRange(); + DominantCostRange costRange = new(); // Analyze the dominant (literal, red and blue) entropy costs. for (int i = 0; i < histoSize; i++) @@ -148,17 +157,20 @@ private static void HistogramAnalyzeEntropyBin(List histograms, u } } - private static int HistogramCopyAndAnalyze(List origHistograms, List histograms, Span histogramSymbols) + private static int HistogramCopyAndAnalyze( + Vp8LHistogramSet origHistograms, + Vp8LHistogramSet histograms, + Span histogramSymbols) { - var stats = new Vp8LStreaks(); - var bitsEntropy = new Vp8LBitEntropy(); + Vp8LStreaks stats = new(); + Vp8LBitEntropy bitsEntropy = new(); for (int clusterId = 0, i = 0; i < origHistograms.Count; i++) { Vp8LHistogram origHistogram = origHistograms[i]; origHistogram.UpdateHistogramCost(stats, bitsEntropy); // Skip the histogram if it is completely empty, which can happen for tiles with no information (when they are skipped because of LZ77). - if (!origHistogram.IsUsed[0] && !origHistogram.IsUsed[1] && !origHistogram.IsUsed[2] && !origHistogram.IsUsed[3] && !origHistogram.IsUsed[4]) + if (!origHistogram.IsUsed(0) && !origHistogram.IsUsed(1) && !origHistogram.IsUsed(2) && !origHistogram.IsUsed(3) && !origHistogram.IsUsed(4)) { origHistograms[i] = null; histograms[i] = null; @@ -166,7 +178,7 @@ private static int HistogramCopyAndAnalyze(List origHistograms, L } else { - histograms[i] = (Vp8LHistogram)origHistogram.DeepClone(); + origHistogram.CopyTo(histograms[i]); histogramSymbols[i] = (ushort)clusterId++; } } @@ -184,11 +196,11 @@ private static int HistogramCopyAndAnalyze(List origHistograms, L } private static void HistogramCombineEntropyBin( - List histograms, + Vp8LHistogramSet histograms, Span clusters, - ushort[] clusterMappings, + Span clusterMappings, Vp8LHistogram curCombo, - ushort[] binMap, + ReadOnlySpan binMap, int numBins, double combineCostFactor) { @@ -205,9 +217,9 @@ private static void HistogramCombineEntropyBin( clusterMappings[idx] = (ushort)idx; } - var indicesToRemove = new List(); - var stats = new Vp8LStreaks(); - var bitsEntropy = new Vp8LBitEntropy(); + List indicesToRemove = new(); + Vp8LStreaks stats = new(); + Vp8LBitEntropy bitsEntropy = new(); for (int idx = 0; idx < histograms.Count; idx++) { if (histograms[idx] == null) @@ -236,13 +248,11 @@ private static void HistogramCombineEntropyBin( // histogram pairs. In that case, we fallback to combining // histograms as usual to avoid increasing the header size. bool tryCombine = curCombo.TrivialSymbol != NonTrivialSym || (histograms[idx].TrivialSymbol == NonTrivialSym && histograms[first].TrivialSymbol == NonTrivialSym); - int maxCombineFailures = 32; + const int maxCombineFailures = 32; if (tryCombine || binInfo[binId].NumCombineFailures >= maxCombineFailures) { // Move the (better) merged histogram to its final slot. - Vp8LHistogram tmp = curCombo; - curCombo = histograms[first]; - histograms[first] = tmp; + (histograms[first], curCombo) = (curCombo, histograms[first]); histograms[idx] = null; indicesToRemove.Add(idx); @@ -256,9 +266,9 @@ private static void HistogramCombineEntropyBin( } } - foreach (int index in indicesToRemove.OrderByDescending(i => i)) + for (int i = indicesToRemove.Count - 1; i >= 0; i--) { - histograms.RemoveAt(index); + histograms.RemoveAt(indicesToRemove[i]); } } @@ -266,7 +276,7 @@ private static void HistogramCombineEntropyBin( /// 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, Span symbols) + private static void OptimizeHistogramSymbols(Span clusterMappings, int numClusters, Span clusterMappingsTmp, Span symbols) { bool doContinue = true; @@ -293,7 +303,7 @@ private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numCl // Create a mapping from a cluster id to its minimal version. int clusterMax = 0; - clusterMappingsTmp.AsSpan().Clear(); + clusterMappingsTmp.Clear(); // Re-map the ids. for (int i = 0; i < symbols.Length; i++) @@ -318,15 +328,15 @@ private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numCl /// Perform histogram aggregation using a stochastic approach. /// /// true if a greedy approach needs to be performed afterwards, false otherwise. - private static bool HistogramCombineStochastic(List histograms, int minClusterSize) + private static bool HistogramCombineStochastic(Vp8LHistogramSet histograms, int minClusterSize) { uint seed = 1; int triesWithNoSuccess = 0; int numUsed = histograms.Count(h => h != null); int outerIters = numUsed; int numTriesNoSuccess = (int)((uint)outerIters / 2); - var stats = new Vp8LStreaks(); - var bitsEntropy = new Vp8LBitEntropy(); + Vp8LStreaks stats = new(); + Vp8LBitEntropy bitsEntropy = new(); if (numUsed < minClusterSize) { @@ -335,25 +345,25 @@ private static bool HistogramCombineStochastic(List histograms, i // Priority list of histogram pairs. Its size impacts the quality of the compression and the speed: // the smaller the faster but the worse for the compression. - var histoPriorityList = new List(); - int maxSize = 9; + List histoPriorityList = new(); + const int maxSize = 9; // Fill the initial mapping. Span mappings = histograms.Count <= 64 ? stackalloc int[histograms.Count] : new int[histograms.Count]; - for (int j = 0, iter = 0; iter < histograms.Count; iter++) + for (int j = 0, i = 0; i < histograms.Count; i++) { - if (histograms[iter] == null) + if (histograms[i] == null) { continue; } - mappings[j++] = iter; + mappings[j++] = i; } // Collapse similar histograms. - for (int iter = 0; iter < outerIters && numUsed >= minClusterSize && ++triesWithNoSuccess < numTriesNoSuccess; iter++) + for (int i = 0; i < outerIters && numUsed >= minClusterSize && ++triesWithNoSuccess < numTriesNoSuccess; i++) { - double bestCost = histoPriorityList.Count == 0 ? 0.0d : histoPriorityList[0].CostDiff; + double bestCost = histoPriorityList.Count == 0 ? 0D : histoPriorityList[0].CostDiff; int numTries = (int)((uint)numUsed / 2); uint randRange = (uint)((numUsed - 1) * numUsed); @@ -398,12 +408,12 @@ private static bool HistogramCombineStochastic(List histograms, i int mappingIndex = mappings.IndexOf(bestIdx2); Span src = mappings.Slice(mappingIndex + 1, numUsed - mappingIndex - 1); - Span dst = mappings.Slice(mappingIndex); + Span dst = mappings[mappingIndex..]; src.CopyTo(dst); // Merge the histograms and remove bestIdx2 from the list. HistogramAdd(histograms[bestIdx2], histograms[bestIdx1], histograms[bestIdx1]); - histograms.ElementAt(bestIdx1).BitCost = histoPriorityList[0].CostCombo; + histograms[bestIdx1].BitCost = histoPriorityList[0].CostCombo; histograms[bestIdx2] = null; numUsed--; @@ -418,7 +428,7 @@ private static bool HistogramCombineStochastic(List histograms, i // check for it all the time nevertheless. if (isIdx1Best && isIdx2Best) { - histoPriorityList[j] = histoPriorityList[histoPriorityList.Count - 1]; + histoPriorityList[j] = histoPriorityList[^1]; histoPriorityList.RemoveAt(histoPriorityList.Count - 1); continue; } @@ -439,18 +449,17 @@ private static bool HistogramCombineStochastic(List histograms, i // Make sure the index order is respected. if (p.Idx1 > p.Idx2) { - int tmp = p.Idx2; - p.Idx2 = p.Idx1; - p.Idx1 = tmp; + (p.Idx1, p.Idx2) = (p.Idx2, p.Idx1); } if (doEval) { // Re-evaluate the cost of an updated pair. - HistoListUpdatePair(histograms[p.Idx1], histograms[p.Idx2], stats, bitsEntropy, 0.0d, p); - if (p.CostDiff >= 0.0d) + HistoListUpdatePair(histograms[p.Idx1], histograms[p.Idx2], stats, bitsEntropy, 0D, p); + + if (p.CostDiff >= 0D) { - histoPriorityList[j] = histoPriorityList[histoPriorityList.Count - 1]; + histoPriorityList[j] = histoPriorityList[^1]; histoPriorityList.RemoveAt(histoPriorityList.Count - 1); continue; } @@ -463,20 +472,18 @@ private static bool HistogramCombineStochastic(List histograms, i triesWithNoSuccess = 0; } - bool doGreedy = numUsed <= minClusterSize; - - return doGreedy; + return numUsed <= minClusterSize; } - private static void HistogramCombineGreedy(List histograms) + private static void HistogramCombineGreedy(Vp8LHistogramSet histograms) { int histoSize = histograms.Count(h => h != null); // Priority list of histogram pairs. - var histoPriorityList = new List(); + List histoPriorityList = new(); int maxSize = histoSize * histoSize; - var stats = new Vp8LStreaks(); - var bitsEntropy = new Vp8LBitEntropy(); + Vp8LStreaks stats = new(); + Vp8LBitEntropy bitsEntropy = new(); for (int i = 0; i < histoSize; i++) { @@ -509,11 +516,11 @@ private static void HistogramCombineGreedy(List histograms) // Remove pairs intersecting the just combined best pair. for (int i = 0; i < histoPriorityList.Count;) { - HistogramPair p = histoPriorityList.ElementAt(i); + HistogramPair p = histoPriorityList[i]; if (p.Idx1 == idx1 || p.Idx2 == idx1 || p.Idx1 == idx2 || p.Idx2 == idx2) { // Replace item at pos i with the last one and shrinking the list. - histoPriorityList[i] = histoPriorityList[histoPriorityList.Count - 1]; + histoPriorityList[i] = histoPriorityList[^1]; histoPriorityList.RemoveAt(histoPriorityList.Count - 1); } else @@ -536,12 +543,15 @@ private static void HistogramCombineGreedy(List histograms) } } - private static void HistogramRemap(List input, List output, Span symbols) + private static void HistogramRemap( + Vp8LHistogramSet input, + Vp8LHistogramSet output, + Span symbols) { int inSize = input.Count; int outSize = output.Count; - var stats = new Vp8LStreaks(); - var bitsEntropy = new Vp8LBitEntropy(); + Vp8LStreaks stats = new(); + Vp8LBitEntropy bitsEntropy = new(); if (outSize > 1) { for (int i = 0; i < inSize; i++) @@ -577,11 +587,11 @@ private static void HistogramRemap(List input, List input, List /// The cost of the pair, or 0 if it superior to threshold. - private static double HistoPriorityListPush(List histoList, int maxSize, List histograms, int idx1, int idx2, double threshold, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy) + private static double HistoPriorityListPush( + List histoList, + int maxSize, + Vp8LHistogramSet histograms, + int idx1, + int idx2, + double threshold, + Vp8LStreaks stats, + Vp8LBitEntropy bitsEntropy) { - var pair = new HistogramPair(); + HistogramPair pair = new(); if (histoList.Count == maxSize) { - return 0.0d; + return 0D; } if (idx1 > idx2) { - int tmp = idx2; - idx2 = idx1; - idx1 = tmp; + (idx1, idx2) = (idx2, idx1); } pair.Idx1 = idx1; @@ -637,9 +653,16 @@ private static double HistoPriorityListPush(List histoList, int m } /// - /// Update the cost diff and combo of a pair of histograms. This needs to be called when the the histograms have been merged with a third one. + /// Update the cost diff and combo of a pair of histograms. This needs to be called when the histograms have been + /// merged with a third one. /// - private static void HistoListUpdatePair(Vp8LHistogram h1, Vp8LHistogram h2, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy, double threshold, HistogramPair pair) + private static void HistoListUpdatePair( + Vp8LHistogram h1, + Vp8LHistogram h2, + Vp8LStreaks stats, + Vp8LBitEntropy bitsEntropy, + double threshold, + HistogramPair pair) { double sumCost = h1.BitCost + h2.BitCost; pair.CostCombo = 0.0d; diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs index 39ad967e38..027d4f7ee9 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, Span huffTree, HuffmanTreeCode huffCode) + public static void CreateHuffmanTree(Span histogram, int treeDepthLimit, bool[] bufRle, Span huffTree, HuffmanTreeCode huffCode) { int numSymbols = huffCode.NumSymbols; bufRle.AsSpan().Clear(); @@ -40,7 +40,7 @@ public static void CreateHuffmanTree(uint[] histogram, int treeDepthLimit, bool[ /// Change the population counts in a way that the consequent /// Huffman tree compression, especially its RLE-part, give smaller output. /// - public static void OptimizeHuffmanForRle(int length, bool[] goodForRle, uint[] counts) + public static void OptimizeHuffmanForRle(int length, bool[] goodForRle, Span counts) { // 1) Let's make the Huffman code more compatible with rle encoding. for (; length >= 0; --length) @@ -116,7 +116,7 @@ public static void OptimizeHuffmanForRle(int length, bool[] goodForRle, uint[] c { // We don't want to change value at counts[i], // that is already belonging to the next stride. Thus - 1. - counts[i - k - 1] = count; + counts[(int)(i - k - 1)] = count; } } @@ -159,7 +159,7 @@ public static void OptimizeHuffmanForRle(int length, bool[] goodForRle, uint[] c /// The size of the histogram. /// The tree depth limit. /// How many bits are used for the symbol. - public static void GenerateOptimalTree(Span tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths) + public static void GenerateOptimalTree(Span tree, Span histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths) { uint countMin; int treeSizeOrig = 0; @@ -177,7 +177,7 @@ public static void GenerateOptimalTree(Span tree, uint[] histogram, return; } - Span treePool = tree.Slice(treeSizeOrig); + Span treePool = tree[treeSizeOrig..]; // For block sizes with less than 64k symbols we never need to do a // second iteration of this loop. @@ -202,7 +202,7 @@ public static void GenerateOptimalTree(Span tree, uint[] histogram, } // Build the Huffman tree. - Span treeSlice = tree.Slice(0, treeSize); + Span treeSlice = tree[..treeSize]; treeSlice.Sort(HuffmanTree.Compare); if (treeSize > 1) @@ -357,7 +357,7 @@ public static int BuildHuffmanTable(Span table, int rootBits, int[] // Special case code with only one value. if (offsets[WebpConstants.MaxAllowedCodeLength] == 1) { - var huffmanCode = new HuffmanCode() + HuffmanCode huffmanCode = new() { BitsUsed = 0, Value = (uint)sorted[0] @@ -390,7 +390,7 @@ public static int BuildHuffmanTable(Span table, int rootBits, int[] for (; countsLen > 0; countsLen--) { - var huffmanCode = new HuffmanCode() + HuffmanCode huffmanCode = new() { BitsUsed = len, Value = (uint)sorted[symbol++] @@ -432,7 +432,7 @@ public static int BuildHuffmanTable(Span table, int rootBits, int[] }; } - var huffmanCode = new HuffmanCode + HuffmanCode huffmanCode = new() { BitsUsed = len - rootBits, Value = (uint)sorted[symbol++] diff --git a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs index 6a28e5b3fb..cedc809382 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs @@ -37,7 +37,7 @@ public static PixOrCopy CreateLiteral(uint bgra) => Len = len }; - public uint Literal(int component) => (this.BgraOrDistance >> (component * 8)) & 0xff; + public int Literal(int component) => (int)(this.BgraOrDistance >> (component * 8)) & 0xFF; public uint CacheIdx() => this.BgraOrDistance; diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LBitEntropy.cs index 649845b025..330d1c555e 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LBitEntropy.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LBitEntropy.cs @@ -125,7 +125,7 @@ public void BitsEntropyUnrefined(Span array, int n) /// /// Get the entropy for the distribution 'X'. /// - public void BitsEntropyUnrefined(uint[] x, int length, Vp8LStreaks stats) + public void BitsEntropyUnrefined(Span x, int length, Vp8LStreaks stats) { int i; int iPrev = 0; @@ -147,7 +147,7 @@ public void BitsEntropyUnrefined(uint[] x, int length, Vp8LStreaks stats) this.Entropy += LosslessUtils.FastSLog2(this.Sum); } - public void GetCombinedEntropyUnrefined(uint[] x, uint[] y, int length, Vp8LStreaks stats) + public void GetCombinedEntropyUnrefined(Span x, Span y, int length, Vp8LStreaks stats) { int i; int iPrev = 0; @@ -169,7 +169,7 @@ public void GetCombinedEntropyUnrefined(uint[] x, uint[] y, int length, Vp8LStre this.Entropy += LosslessUtils.FastSLog2(this.Sum); } - public void GetEntropyUnrefined(uint[] x, int length, Vp8LStreaks stats) + public void GetEntropyUnrefined(Span x, int length, Vp8LStreaks stats) { int i; int iPrev = 0; diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 469e4c9ab0..878d487a86 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -589,15 +589,21 @@ private void EncodeImage(int width, int height, bool useCache, CrunchConfig conf Vp8LBackwardRefs refsTmp = this.Refs[refsBest.Equals(this.Refs[0]) ? 1 : 0]; this.bitWriter.Reset(bwInit); - Vp8LHistogram tmpHisto = new(cacheBits); - List histogramImage = new(histogramImageXySize); - for (int i = 0; i < histogramImageXySize; i++) - { - histogramImage.Add(new Vp8LHistogram(cacheBits)); - } + using OwnedVp8LHistogram tmpHisto = OwnedVp8LHistogram.Create(this.memoryAllocator, cacheBits); + using Vp8LHistogramSet histogramImage = new(this.memoryAllocator, histogramImageXySize, cacheBits); // Build histogram image and symbols from backward references. - HistogramEncoder.GetHistoImageSymbols(width, height, refsBest, this.quality, this.HistoBits, cacheBits, histogramImage, tmpHisto, histogramSymbols); + HistogramEncoder.GetHistoImageSymbols( + this.memoryAllocator, + width, + height, + refsBest, + this.quality, + this.HistoBits, + cacheBits, + histogramImage, + tmpHisto, + histogramSymbols); // Create Huffman bit lengths and codes for each histogram image. int histogramImageSize = histogramImage.Count; @@ -678,9 +684,7 @@ private void EncodeImage(int width, int height, bool useCache, CrunchConfig conf // Keep track of the smallest image so far. if (isFirstIteration || (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes())) { - Vp8LBitWriter tmp = this.bitWriter; - this.bitWriter = bitWriterBest; - bitWriterBest = tmp; + (bitWriterBest, this.bitWriter) = (this.bitWriter, bitWriterBest); } isFirstIteration = false; @@ -787,13 +791,8 @@ private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8L refsTmp1, refsTmp2); - List histogramImage = new() - { - new(cacheBits) - }; - // Build histogram image and symbols from backward references. - histogramImage[0].StoreRefs(refs); + using Vp8LHistogramSet histogramImage = new(this.memoryAllocator, refs, 1, cacheBits); // Create Huffman bit lengths and codes for each histogram image. GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes); @@ -833,7 +832,7 @@ private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8L private void StoreHuffmanCode(Span huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode) { int count = 0; - Span symbols = this.scratch.Span.Slice(0, 2); + Span symbols = this.scratch.Span[..2]; symbols.Clear(); const int maxBits = 8; const int maxSymbol = 1 << maxBits; @@ -886,6 +885,7 @@ private void StoreHuffmanCode(Span huffTree, HuffmanTreeToken[] tok private void StoreFullHuffmanCode(Span huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree) { + // TODO: Allocations. This method is called in a loop. int i; byte[] codeLengthBitDepth = new byte[WebpConstants.CodeLengthCodes]; short[] codeLengthBitDepthSymbols = new short[WebpConstants.CodeLengthCodes]; @@ -996,7 +996,12 @@ private void StoreHuffmanTreeOfHuffmanTreeToBitMask(byte[] codeLengthBitDepth) } } - private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs backwardRefs, Span 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); @@ -1008,10 +1013,10 @@ private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs back int tileY = y & tileMask; int histogramIx = histogramSymbols[0]; Span codes = huffmanCodes.AsSpan(5 * histogramIx); - using List.Enumerator c = backwardRefs.Refs.GetEnumerator(); - while (c.MoveNext()) + + for (int i = 0; i < backwardRefs.Refs.Count; i++) { - PixOrCopy v = c.Current; + PixOrCopy v = backwardRefs.Refs[i]; if (tileX != (x & tileMask) || tileY != (y & tileMask)) { tileX = x & tileMask; @@ -1024,7 +1029,7 @@ private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs back { for (int k = 0; k < 4; k++) { - int code = (int)v.Literal(Order[k]); + int code = v.Literal(Order[k]); this.bitWriter.WriteHuffmanCode(codes[k], code); } } @@ -1379,10 +1384,8 @@ private void ApplyPalette(Span src, int srcStride, Span dst, int dst useLut = false; break; } - else - { - buffer[ind] = (uint)j; - } + + buffer[ind] = (uint)j; } if (useLut) @@ -1591,14 +1594,12 @@ private static void GreedyMinimizeDeltas(Span palette, int numColors) } // Swap color(palette[bestIdx], palette[i]); - uint best = palette[bestIdx]; - palette[bestIdx] = palette[i]; - palette[i] = best; + (palette[i], palette[bestIdx]) = (palette[bestIdx], palette[i]); predict = palette[i]; } } - private static void GetHuffBitLengthsAndCodes(List histogramImage, HuffmanTreeCode[] huffmanCodes) + private static void GetHuffBitLengthsAndCodes(Vp8LHistogramSet histogramImage, HuffmanTreeCode[] huffmanCodes) { int maxNumSymbols = 0; @@ -1609,13 +1610,25 @@ private static void GetHuffBitLengthsAndCodes(List histogramImage int startIdx = 5 * i; for (int k = 0; k < 5; k++) { - int numSymbols = - k == 0 ? histo.NumCodes() : - k == 4 ? WebpConstants.NumDistanceCodes : 256; + int numSymbols; + if (k == 0) + { + numSymbols = histo.NumCodes(); + } + else if (k == 4) + { + numSymbols = WebpConstants.NumDistanceCodes; + } + else + { + numSymbols = 256; + } + huffmanCodes[startIdx + k].NumSymbols = numSymbols; } } + // TODO: Allocations. int end = 5 * histogramImage.Count; for (int i = 0; i < end; i++) { @@ -1629,8 +1642,9 @@ private static void GetHuffBitLengthsAndCodes(List histogramImage } // Create Huffman trees. + // TODO: Allocations. bool[] bufRle = new bool[maxNumSymbols]; - Span huffTree = stackalloc HuffmanTree[3 * maxNumSymbols]; + HuffmanTree[] huffTree = new HuffmanTree[3 * maxNumSymbols]; for (int i = 0; i < histogramImage.Count; i++) { @@ -1682,8 +1696,18 @@ private static int GetHistoBits(WebpEncodingMethod method, bool usePalette, int histoBits++; } - return histoBits < WebpConstants.MinHuffmanBits ? WebpConstants.MinHuffmanBits : - histoBits > WebpConstants.MaxHuffmanBits ? WebpConstants.MaxHuffmanBits : histoBits; + if (histoBits < WebpConstants.MinHuffmanBits) + { + return WebpConstants.MinHuffmanBits; + } + else if (histoBits > WebpConstants.MaxHuffmanBits) + { + return WebpConstants.MaxHuffmanBits; + } + else + { + return histoBits; + } } /// @@ -1720,11 +1744,7 @@ private static void BundleColorMap(Span row, int width, int xBits, Span (dst, src) = (src, dst); /// /// Calculates the bits used for the transformation. @@ -1732,9 +1752,21 @@ private static void BitWriterSwap(ref Vp8LBitWriter src, ref Vp8LBitWriter dst) [MethodImpl(InliningOptions.ShortMethod)] private static int GetTransformBits(WebpEncodingMethod method, int histoBits) { - int maxTransformBits = (int)method < 4 ? 6 : method > WebpEncodingMethod.Level4 ? 4 : 5; - int res = histoBits > maxTransformBits ? maxTransformBits : histoBits; - return res; + int maxTransformBits; + if ((int)method < 4) + { + maxTransformBits = 6; + } + else if (method > WebpEncodingMethod.Level4) + { + maxTransformBits = 4; + } + else + { + maxTransformBits = 5; + } + + return histoBits > maxTransformBits ? maxTransformBits : histoBits; } [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs index 5ec3f0d53d..f473977908 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs @@ -1,63 +1,56 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Webp.Lossless; -internal sealed class Vp8LHistogram : IDeepCloneable +internal abstract unsafe class Vp8LHistogram { private const uint NonTrivialSym = 0xffffffff; + private readonly uint* red; + private readonly uint* blue; + private readonly uint* alpha; + private readonly uint* distance; + private readonly uint* literal; + private readonly uint* isUsed; + + private const int RedSize = WebpConstants.NumLiteralCodes; + private const int BlueSize = WebpConstants.NumLiteralCodes; + private const int AlphaSize = WebpConstants.NumLiteralCodes; + private const int DistanceSize = WebpConstants.NumDistanceCodes; + public const int LiteralSize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (1 << WebpConstants.MaxColorCacheBits) + 1; + private const int UsedSize = 5; // 5 for literal, red, blue, alpha, distance + public const int BufferSize = RedSize + BlueSize + AlphaSize + DistanceSize + LiteralSize + UsedSize; /// /// Initializes a new instance of the class. /// - /// The histogram to create an instance from. - private Vp8LHistogram(Vp8LHistogram other) - : this(other.PaletteCodeBits) - { - other.Red.AsSpan().CopyTo(this.Red); - other.Blue.AsSpan().CopyTo(this.Blue); - other.Alpha.AsSpan().CopyTo(this.Alpha); - other.Literal.AsSpan().CopyTo(this.Literal); - other.Distance.AsSpan().CopyTo(this.Distance); - other.IsUsed.AsSpan().CopyTo(this.IsUsed); - this.LiteralCost = other.LiteralCost; - this.RedCost = other.RedCost; - this.BlueCost = other.BlueCost; - this.BitCost = other.BitCost; - this.TrivialSymbol = other.TrivialSymbol; - this.PaletteCodeBits = other.PaletteCodeBits; - } - - /// - /// Initializes a new instance of the class. - /// + /// The base pointer to the backing memory. /// The backward references to initialize the histogram with. /// The palette code bits. - public Vp8LHistogram(Vp8LBackwardRefs refs, int paletteCodeBits) - : this(paletteCodeBits) => this.StoreRefs(refs); + protected Vp8LHistogram(uint* basePointer, Vp8LBackwardRefs refs, int paletteCodeBits) + : this(basePointer, paletteCodeBits) => this.StoreRefs(refs); /// /// Initializes a new instance of the class. /// + /// The base pointer to the backing memory. /// The palette code bits. - public Vp8LHistogram(int paletteCodeBits) + protected Vp8LHistogram(uint* basePointer, int paletteCodeBits) { this.PaletteCodeBits = paletteCodeBits; - this.Red = new uint[WebpConstants.NumLiteralCodes + 1]; - this.Blue = new uint[WebpConstants.NumLiteralCodes + 1]; - this.Alpha = new uint[WebpConstants.NumLiteralCodes + 1]; - this.Distance = new uint[WebpConstants.NumDistanceCodes]; - - int literalSize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (1 << WebpConstants.MaxColorCacheBits); - this.Literal = new uint[literalSize + 1]; - - // 5 for literal, red, blue, alpha, distance. - this.IsUsed = new bool[5]; + this.red = basePointer; + this.blue = this.red + RedSize; + this.alpha = this.blue + BlueSize; + this.distance = this.alpha + AlphaSize; + this.literal = this.distance + DistanceSize; + this.isUsed = this.literal + LiteralSize; } /// @@ -85,22 +78,59 @@ public Vp8LHistogram(int paletteCodeBits) /// public double BlueCost { get; set; } - public uint[] Red { get; } + public Span Red => new(this.red, RedSize); - public uint[] Blue { get; } + public Span Blue => new(this.blue, BlueSize); - public uint[] Alpha { get; } + public Span Alpha => new(this.alpha, AlphaSize); - public uint[] Literal { get; } + public Span Distance => new(this.distance, DistanceSize); - public uint[] Distance { get; } + public Span Literal => new(this.literal, LiteralSize); public uint TrivialSymbol { get; set; } - public bool[] IsUsed { get; } + private Span IsUsedSpan => new(this.isUsed, UsedSize); + + private Span TotalSpan => new(this.red, BufferSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsUsed(int index) => this.IsUsedSpan[index] == 1u; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void IsUsed(int index, bool value) => this.IsUsedSpan[index] = value ? 1u : 0; + + /// + /// Creates a copy of the given class. + /// + /// The histogram to copy to. + public void CopyTo(Vp8LHistogram other) + { + this.Red.CopyTo(other.Red); + this.Blue.CopyTo(other.Blue); + this.Alpha.CopyTo(other.Alpha); + this.Literal.CopyTo(other.Literal); + this.Distance.CopyTo(other.Distance); + this.IsUsedSpan.CopyTo(other.IsUsedSpan); + + other.LiteralCost = this.LiteralCost; + other.RedCost = this.RedCost; + other.BlueCost = this.BlueCost; + other.BitCost = this.BitCost; + other.TrivialSymbol = this.TrivialSymbol; + other.PaletteCodeBits = this.PaletteCodeBits; + } - /// - public IDeepCloneable DeepClone() => new Vp8LHistogram(this); + public void Clear() + { + this.TotalSpan.Clear(); + this.PaletteCodeBits = 0; + this.BitCost = 0; + this.LiteralCost = 0; + this.RedCost = 0; + this.BlueCost = 0; + this.TrivialSymbol = 0; + } /// /// Collect all the references into a histogram (without reset). @@ -108,10 +138,9 @@ public Vp8LHistogram(int paletteCodeBits) /// The backward references. public void StoreRefs(Vp8LBackwardRefs refs) { - using List.Enumerator c = refs.Refs.GetEnumerator(); - while (c.MoveNext()) + for (int i = 0; i < refs.Refs.Count; i++) { - this.AddSinglePixOrCopy(c.Current, false); + this.AddSinglePixOrCopy(refs.Refs[i], false); } } @@ -163,12 +192,12 @@ public double EstimateBits(Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy) { uint notUsed = 0; return - PopulationCost(this.Literal, this.NumCodes(), ref notUsed, ref this.IsUsed[0], stats, bitsEntropy) - + PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[1], stats, bitsEntropy) - + PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[2], stats, bitsEntropy) - + PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[3], stats, bitsEntropy) - + PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4], stats, bitsEntropy) - + ExtraCost(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes) + this.PopulationCost(this.Literal, this.NumCodes(), ref notUsed, 0, stats, bitsEntropy) + + this.PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref notUsed, 1, stats, bitsEntropy) + + this.PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref notUsed, 2, stats, bitsEntropy) + + this.PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref notUsed, 3, stats, bitsEntropy) + + this.PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, 4, stats, bitsEntropy) + + ExtraCost(this.Literal[WebpConstants.NumLiteralCodes..], WebpConstants.NumLengthCodes) + ExtraCost(this.Distance, WebpConstants.NumDistanceCodes); } @@ -177,12 +206,12 @@ public void UpdateHistogramCost(Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy) uint alphaSym = 0, redSym = 0, blueSym = 0; uint notUsed = 0; - double alphaCost = PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref alphaSym, ref this.IsUsed[3], stats, bitsEntropy); - double distanceCost = PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4], stats, bitsEntropy) + ExtraCost(this.Distance, WebpConstants.NumDistanceCodes); + double alphaCost = this.PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref alphaSym, 3, stats, bitsEntropy); + double distanceCost = this.PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, 4, stats, bitsEntropy) + ExtraCost(this.Distance, WebpConstants.NumDistanceCodes); int numCodes = this.NumCodes(); - this.LiteralCost = PopulationCost(this.Literal, numCodes, ref notUsed, ref this.IsUsed[0], stats, bitsEntropy) + ExtraCost(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes); - this.RedCost = PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref redSym, ref this.IsUsed[1], stats, bitsEntropy); - this.BlueCost = PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref blueSym, ref this.IsUsed[2], stats, bitsEntropy); + this.LiteralCost = this.PopulationCost(this.Literal, numCodes, ref notUsed, 0, stats, bitsEntropy) + ExtraCost(this.Literal[WebpConstants.NumLiteralCodes..], WebpConstants.NumLengthCodes); + this.RedCost = this.PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref redSym, 1, stats, bitsEntropy); + this.BlueCost = this.PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref blueSym, 2, stats, bitsEntropy); this.BitCost = this.LiteralCost + this.RedCost + this.BlueCost + alphaCost + distanceCost; if ((alphaSym | redSym | blueSym) == NonTrivialSym) { @@ -234,7 +263,7 @@ public void Add(Vp8LHistogram b, Vp8LHistogram output) for (int i = 0; i < 5; i++) { - output.IsUsed[i] = this.IsUsed[i] | b.IsUsed[i]; + output.IsUsed(i, this.IsUsed(i) | b.IsUsed(i)); } output.TrivialSymbol = this.TrivialSymbol == b.TrivialSymbol @@ -247,9 +276,9 @@ public bool GetCombinedHistogramEntropy(Vp8LHistogram b, Vp8LStreaks stats, Vp8L bool trivialAtEnd = false; cost = costInitial; - cost += GetCombinedEntropy(this.Literal, b.Literal, this.NumCodes(), this.IsUsed[0], b.IsUsed[0], false, stats, bitEntropy); + cost += GetCombinedEntropy(this.Literal, b.Literal, this.NumCodes(), this.IsUsed(0), b.IsUsed(0), false, stats, bitEntropy); - cost += ExtraCostCombined(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), b.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes); + cost += ExtraCostCombined(this.Literal[WebpConstants.NumLiteralCodes..], b.Literal[WebpConstants.NumLiteralCodes..], WebpConstants.NumLengthCodes); if (cost > costThreshold) { @@ -270,155 +299,158 @@ public bool GetCombinedHistogramEntropy(Vp8LHistogram b, Vp8LStreaks stats, Vp8L } } - cost += GetCombinedEntropy(this.Red, b.Red, WebpConstants.NumLiteralCodes, this.IsUsed[1], b.IsUsed[1], trivialAtEnd, stats, bitEntropy); + cost += GetCombinedEntropy(this.Red, b.Red, WebpConstants.NumLiteralCodes, this.IsUsed(1), b.IsUsed(1), trivialAtEnd, stats, bitEntropy); if (cost > costThreshold) { return false; } - cost += GetCombinedEntropy(this.Blue, b.Blue, WebpConstants.NumLiteralCodes, this.IsUsed[2], b.IsUsed[2], trivialAtEnd, stats, bitEntropy); + cost += GetCombinedEntropy(this.Blue, b.Blue, WebpConstants.NumLiteralCodes, this.IsUsed(2), b.IsUsed(2), trivialAtEnd, stats, bitEntropy); if (cost > costThreshold) { return false; } - cost += GetCombinedEntropy(this.Alpha, b.Alpha, WebpConstants.NumLiteralCodes, this.IsUsed[3], b.IsUsed[3], trivialAtEnd, stats, bitEntropy); + cost += GetCombinedEntropy(this.Alpha, b.Alpha, WebpConstants.NumLiteralCodes, this.IsUsed(3), b.IsUsed(3), trivialAtEnd, stats, bitEntropy); if (cost > costThreshold) { return false; } - cost += GetCombinedEntropy(this.Distance, b.Distance, WebpConstants.NumDistanceCodes, this.IsUsed[4], b.IsUsed[4], false, stats, bitEntropy); + cost += GetCombinedEntropy(this.Distance, b.Distance, WebpConstants.NumDistanceCodes, this.IsUsed(4), b.IsUsed(4), false, stats, bitEntropy); if (cost > costThreshold) { return false; } cost += ExtraCostCombined(this.Distance, b.Distance, WebpConstants.NumDistanceCodes); - if (cost > costThreshold) - { - return false; - } - - return true; + return cost <= costThreshold; } private void AddLiteral(Vp8LHistogram b, Vp8LHistogram output, int literalSize) { - if (this.IsUsed[0]) + if (this.IsUsed(0)) { - if (b.IsUsed[0]) + if (b.IsUsed(0)) { AddVector(this.Literal, b.Literal, output.Literal, literalSize); } else { - this.Literal.AsSpan(0, literalSize).CopyTo(output.Literal); + this.Literal[..literalSize].CopyTo(output.Literal); } } - else if (b.IsUsed[0]) + else if (b.IsUsed(0)) { - b.Literal.AsSpan(0, literalSize).CopyTo(output.Literal); + b.Literal[..literalSize].CopyTo(output.Literal); } else { - output.Literal.AsSpan(0, literalSize).Clear(); + output.Literal[..literalSize].Clear(); } } private void AddRed(Vp8LHistogram b, Vp8LHistogram output, int size) { - if (this.IsUsed[1]) + if (this.IsUsed(1)) { - if (b.IsUsed[1]) + if (b.IsUsed(1)) { AddVector(this.Red, b.Red, output.Red, size); } else { - this.Red.AsSpan(0, size).CopyTo(output.Red); + this.Red[..size].CopyTo(output.Red); } } - else if (b.IsUsed[1]) + else if (b.IsUsed(1)) { - b.Red.AsSpan(0, size).CopyTo(output.Red); + b.Red[..size].CopyTo(output.Red); } else { - output.Red.AsSpan(0, size).Clear(); + output.Red[..size].Clear(); } } private void AddBlue(Vp8LHistogram b, Vp8LHistogram output, int size) { - if (this.IsUsed[2]) + if (this.IsUsed(2)) { - if (b.IsUsed[2]) + if (b.IsUsed(2)) { AddVector(this.Blue, b.Blue, output.Blue, size); } else { - this.Blue.AsSpan(0, size).CopyTo(output.Blue); + this.Blue[..size].CopyTo(output.Blue); } } - else if (b.IsUsed[2]) + else if (b.IsUsed(2)) { - b.Blue.AsSpan(0, size).CopyTo(output.Blue); + b.Blue[..size].CopyTo(output.Blue); } else { - output.Blue.AsSpan(0, size).Clear(); + output.Blue[..size].Clear(); } } private void AddAlpha(Vp8LHistogram b, Vp8LHistogram output, int size) { - if (this.IsUsed[3]) + if (this.IsUsed(3)) { - if (b.IsUsed[3]) + if (b.IsUsed(3)) { AddVector(this.Alpha, b.Alpha, output.Alpha, size); } else { - this.Alpha.AsSpan(0, size).CopyTo(output.Alpha); + this.Alpha[..size].CopyTo(output.Alpha); } } - else if (b.IsUsed[3]) + else if (b.IsUsed(3)) { - b.Alpha.AsSpan(0, size).CopyTo(output.Alpha); + b.Alpha[..size].CopyTo(output.Alpha); } else { - output.Alpha.AsSpan(0, size).Clear(); + output.Alpha[..size].Clear(); } } private void AddDistance(Vp8LHistogram b, Vp8LHistogram output, int size) { - if (this.IsUsed[4]) + if (this.IsUsed(4)) { - if (b.IsUsed[4]) + if (b.IsUsed(4)) { AddVector(this.Distance, b.Distance, output.Distance, size); } else { - this.Distance.AsSpan(0, size).CopyTo(output.Distance); + this.Distance[..size].CopyTo(output.Distance); } } - else if (b.IsUsed[4]) + else if (b.IsUsed(4)) { - b.Distance.AsSpan(0, size).CopyTo(output.Distance); + b.Distance[..size].CopyTo(output.Distance); } else { - output.Distance.AsSpan(0, size).Clear(); + output.Distance[..size].Clear(); } } - private static double GetCombinedEntropy(uint[] x, uint[] y, int length, bool isXUsed, bool isYUsed, bool trivialAtEnd, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy) + private static double GetCombinedEntropy( + Span x, + Span y, + int length, + bool isXUsed, + bool isYUsed, + bool trivialAtEnd, + Vp8LStreaks stats, + Vp8LBitEntropy bitEntropy) { stats.Clear(); bitEntropy.Init(); @@ -450,18 +482,15 @@ private static double GetCombinedEntropy(uint[] x, uint[] y, int length, bool is bitEntropy.GetEntropyUnrefined(x, length, stats); } } + else if (isYUsed) + { + bitEntropy.GetEntropyUnrefined(y, length, stats); + } else { - if (isYUsed) - { - bitEntropy.GetEntropyUnrefined(y, length, stats); - } - else - { - stats.Counts[0] = 1; - stats.Streaks[0][length > 3 ? 1 : 0] = length; - bitEntropy.Init(); - } + stats.Counts[0] = 1; + stats.Streaks[0][length > 3 ? 1 : 0] = length; + bitEntropy.Init(); } return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost(); @@ -482,7 +511,7 @@ private static double ExtraCostCombined(Span x, Span y, int length) /// /// Get the symbol entropy for the distribution 'population'. /// - private static double PopulationCost(uint[] population, int length, ref uint trivialSym, ref bool isUsed, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy) + private double PopulationCost(Span population, int length, ref uint trivialSym, int isUsedIndex, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy) { bitEntropy.Init(); stats.Clear(); @@ -491,7 +520,7 @@ private static double PopulationCost(uint[] population, int length, ref uint tri trivialSym = (bitEntropy.NoneZeros == 1) ? bitEntropy.NoneZeroCode : NonTrivialSym; // The histogram is used if there is at least one non-zero streak. - isUsed = stats.Streaks[1][0] != 0 || stats.Streaks[1][1] != 0; + this.IsUsed(isUsedIndex, stats.Streaks[1][0] != 0 || stats.Streaks[1][1] != 0); return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost(); } @@ -557,3 +586,56 @@ private static void AddVector(Span a, Span b, Span output, int } } } + +internal sealed unsafe class OwnedVp8LHistogram : Vp8LHistogram, IDisposable +{ + private readonly IMemoryOwner bufferOwner; + private MemoryHandle bufferHandle; + private bool isDisposed; + + private OwnedVp8LHistogram( + IMemoryOwner bufferOwner, + ref MemoryHandle bufferHandle, + uint* basePointer, + int paletteCodeBits) + : base(basePointer, paletteCodeBits) + { + this.bufferOwner = bufferOwner; + this.bufferHandle = bufferHandle; + } + + /// + /// Creates an that is not a member of a . + /// + /// The memory allocator. + /// The palette code bits. + public static OwnedVp8LHistogram Create(MemoryAllocator memoryAllocator, int paletteCodeBits) + { + IMemoryOwner bufferOwner = memoryAllocator.Allocate(BufferSize, AllocationOptions.Clean); + MemoryHandle bufferHandle = bufferOwner.Memory.Pin(); + return new OwnedVp8LHistogram(bufferOwner, ref bufferHandle, (uint*)bufferHandle.Pointer, paletteCodeBits); + } + + /// + /// Creates an that is not a member of a . + /// + /// The memory allocator. + /// The backward references to initialize the histogram with. + /// The palette code bits. + public static OwnedVp8LHistogram Create(MemoryAllocator memoryAllocator, Vp8LBackwardRefs refs, int paletteCodeBits) + { + OwnedVp8LHistogram histogram = Create(memoryAllocator, paletteCodeBits); + histogram.StoreRefs(refs); + return histogram; + } + + public void Dispose() + { + if (!this.isDisposed) + { + this.bufferHandle.Dispose(); + this.bufferOwner.Dispose(); + this.isDisposed = true; + } + } +} diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogramSet.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogramSet.cs new file mode 100644 index 0000000000..a46838ee67 --- /dev/null +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogramSet.cs @@ -0,0 +1,110 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +#nullable disable + +using System.Buffers; +using System.Collections; +using System.Diagnostics; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +internal sealed class Vp8LHistogramSet : IEnumerable, IDisposable +{ + private readonly IMemoryOwner buffer; + private MemoryHandle bufferHandle; + private readonly List items; + private bool isDisposed; + + public Vp8LHistogramSet(MemoryAllocator memoryAllocator, int capacity, int cacheBits) + { + this.buffer = memoryAllocator.Allocate(Vp8LHistogram.BufferSize * capacity, AllocationOptions.Clean); + this.bufferHandle = this.buffer.Memory.Pin(); + + unsafe + { + uint* basePointer = (uint*)this.bufferHandle.Pointer; + this.items = new List(capacity); + for (int i = 0; i < capacity; i++) + { + this.items.Add(new MemberVp8LHistogram(basePointer + (Vp8LHistogram.BufferSize * i), cacheBits)); + } + } + } + + public Vp8LHistogramSet(MemoryAllocator memoryAllocator, Vp8LBackwardRefs refs, int capacity, int cacheBits) + { + this.buffer = memoryAllocator.Allocate(Vp8LHistogram.BufferSize * capacity, AllocationOptions.Clean); + this.bufferHandle = this.buffer.Memory.Pin(); + + unsafe + { + uint* basePointer = (uint*)this.bufferHandle.Pointer; + this.items = new List(capacity); + for (int i = 0; i < capacity; i++) + { + this.items.Add(new MemberVp8LHistogram(basePointer + (Vp8LHistogram.BufferSize * i), refs, cacheBits)); + } + } + } + + public Vp8LHistogramSet(int capacity) => this.items = new(capacity); + + public Vp8LHistogramSet() => this.items = new(); + + public int Count => this.items.Count; + + public Vp8LHistogram this[int index] + { + get => this.items[index]; + set => this.items[index] = value; + } + + public void RemoveAt(int index) + { + this.CheckDisposed(); + this.items.RemoveAt(index); + } + + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + this.buffer.Dispose(); + this.bufferHandle.Dispose(); + this.items.Clear(); + this.isDisposed = true; + } + + public IEnumerator GetEnumerator() => ((IEnumerable)this.items).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this.items).GetEnumerator(); + + [Conditional("DEBUG")] + private void CheckDisposed() + { + if (this.isDisposed) + { + ThrowDisposed(); + } + } + + private static void ThrowDisposed() => throw new ObjectDisposedException(nameof(Vp8LHistogramSet)); + + private sealed unsafe class MemberVp8LHistogram : Vp8LHistogram + { + public MemberVp8LHistogram(uint* basePointer, int paletteCodeBits) + : base(basePointer, paletteCodeBits) + { + } + + public MemberVp8LHistogram(uint* basePointer, Vp8LBackwardRefs refs, int paletteCodeBits) + : base(basePointer, refs, paletteCodeBits) + { + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs index 65b4ae2c31..c5fa8d03da 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs @@ -43,9 +43,9 @@ public void Cleanup() [Benchmark(Description = "Magick Webp Lossy")] public void MagickWebpLossy() { - using var memoryStream = new MemoryStream(); + using MemoryStream memoryStream = new(); - var defines = new WebPWriteDefines + WebPWriteDefines defines = new() { Lossless = false, Method = 4, @@ -65,7 +65,7 @@ public void MagickWebpLossy() [Benchmark(Description = "ImageSharp Webp Lossy")] public void ImageSharpWebpLossy() { - using var memoryStream = new MemoryStream(); + using MemoryStream memoryStream = new(); this.webp.Save(memoryStream, new WebpEncoder() { FileFormat = WebpFileFormatType.Lossy, @@ -80,8 +80,8 @@ public void ImageSharpWebpLossy() [Benchmark(Baseline = true, Description = "Magick Webp Lossless")] public void MagickWebpLossless() { - using var memoryStream = new MemoryStream(); - var defines = new WebPWriteDefines + using MemoryStream memoryStream = new(); + WebPWriteDefines defines = new() { Lossless = true, Method = 4, @@ -97,12 +97,13 @@ public void MagickWebpLossless() [Benchmark(Description = "ImageSharp Webp Lossless")] public void ImageSharpWebpLossless() { - using var memoryStream = new MemoryStream(); + using MemoryStream memoryStream = new(); this.webp.Save(memoryStream, new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless, Method = WebpEncodingMethod.Level4, NearLossless = false, + Quality = 75, // This is equal to exact = false in libwebp, which is the default. TransparentColorMode = WebpTransparentColorMode.Clear diff --git a/tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs b/tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs index 11c4bb62e7..9c48e61823 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs @@ -11,7 +11,7 @@ public class DominantCostRangeTests [Fact] public void DominantCost_Constructor() { - var dominantCostRange = new DominantCostRange(); + DominantCostRange dominantCostRange = new(); Assert.Equal(0, dominantCostRange.LiteralMax); Assert.Equal(double.MaxValue, dominantCostRange.LiteralMin); Assert.Equal(0, dominantCostRange.RedMax); @@ -24,13 +24,11 @@ public void DominantCost_Constructor() public void UpdateDominantCostRange_Works() { // arrange - var dominantCostRange = new DominantCostRange(); - var histogram = new Vp8LHistogram(10) - { - LiteralCost = 1.0d, - RedCost = 2.0d, - BlueCost = 3.0d - }; + DominantCostRange dominantCostRange = new(); + using OwnedVp8LHistogram histogram = OwnedVp8LHistogram.Create(Configuration.Default.MemoryAllocator, 10); + histogram.LiteralCost = 1.0d; + histogram.RedCost = 2.0d; + histogram.BlueCost = 3.0d; // act dominantCostRange.UpdateDominantCostRange(histogram); @@ -50,7 +48,7 @@ public void UpdateDominantCostRange_Works() public void GetHistoBinIndex_Works(int partitions, int expectedIndex) { // arrange - var dominantCostRange = new DominantCostRange() + DominantCostRange dominantCostRange = new() { BlueMax = 253.4625, BlueMin = 109.0, @@ -59,13 +57,12 @@ public void GetHistoBinIndex_Works(int partitions, int expectedIndex) RedMax = 191.0, RedMin = 109.0 }; - var histogram = new Vp8LHistogram(6) - { - LiteralCost = 247.0d, - RedCost = 112.0d, - BlueCost = 202.0d, - BitCost = 733.0d - }; + using OwnedVp8LHistogram histogram = OwnedVp8LHistogram.Create(Configuration.Default.MemoryAllocator, 6); + histogram.LiteralCost = 247.0d; + histogram.RedCost = 112.0d; + histogram.BlueCost = 202.0d; + histogram.BitCost = 733.0d; + dominantCostRange.UpdateDominantCostRange(histogram); // act diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs index 39c3c89550..cfe79e49e6 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Webp.Lossless; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.Formats.Webp; @@ -65,7 +66,7 @@ private static void RunAddVectorTest() // All remaining values are expected to be zero. literals.AsSpan().CopyTo(expectedLiterals); - var backwardRefs = new Vp8LBackwardRefs(pixelData.Length); + Vp8LBackwardRefs backwardRefs = new(pixelData.Length); for (int i = 0; i < pixelData.Length; i++) { backwardRefs.Add(new PixOrCopy() @@ -76,15 +77,16 @@ private static void RunAddVectorTest() }); } - var histogram0 = new Vp8LHistogram(backwardRefs, 3); - var histogram1 = new Vp8LHistogram(backwardRefs, 3); + MemoryAllocator memoryAllocator = Configuration.Default.MemoryAllocator; + using OwnedVp8LHistogram histogram0 = OwnedVp8LHistogram.Create(memoryAllocator, backwardRefs, 3); + using OwnedVp8LHistogram histogram1 = OwnedVp8LHistogram.Create(memoryAllocator, backwardRefs, 3); for (int i = 0; i < 5; i++) { - histogram0.IsUsed[i] = true; - histogram1.IsUsed[i] = true; + histogram0.IsUsed(i, true); + histogram1.IsUsed(i, true); } - var output = new Vp8LHistogram(3); + using OwnedVp8LHistogram output = OwnedVp8LHistogram.Create(memoryAllocator, 3); // act histogram0.Add(histogram1, output);