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