Browse Source

Merge remote-tracking branch 'upstream/main' into animated-webp-encoder

pull/2569/head
James Jackson-South 3 years ago
parent
commit
8e9473e24d
  1. 58
      src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs
  2. 16
      src/ImageSharp/Formats/Webp/Lossless/CostModel.cs
  3. 193
      src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs
  4. 18
      src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs
  5. 2
      src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs
  6. 6
      src/ImageSharp/Formats/Webp/Lossless/Vp8LBitEntropy.cs
  7. 120
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  8. 308
      src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs
  9. 110
      src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogramSet.cs
  10. 13
      tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs
  11. 29
      tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs
  12. 14
      tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs

58
src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs

@ -50,8 +50,10 @@ internal static class BackwardReferenceEncoder
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 @@ internal static class BackwardReferenceEncoder
}
// 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 @@ internal static class BackwardReferenceEncoder
{
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 @@ internal static class BackwardReferenceEncoder
/// The local color cache is also disabled for the lower (smaller then 25) quality.
/// </summary>
/// <returns>Best cache size.</returns>
private static int CalculateBestCacheSize(ReadOnlySpan<uint> bgra, uint quality, Vp8LBackwardRefs refs, int bestCacheBits)
private static int CalculateBestCacheSize(
MemoryAllocator memoryAllocator,
Span<ColorCache> colorCache,
ReadOnlySpan<uint> bgra,
uint quality,
Vp8LBackwardRefs refs,
int bestCacheBits)
{
int cacheBitsMax = quality <= 25 ? 0 : bestCacheBits;
if (cacheBitsMax == 0)
@ -134,11 +140,11 @@ internal static class BackwardReferenceEncoder
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 @@ internal static class BackwardReferenceEncoder
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 @@ internal static class BackwardReferenceEncoder
}
}
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 @@ internal static class BackwardReferenceEncoder
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 @@ internal static class BackwardReferenceEncoder
}
costModel.Build(xSize, cacheBits, refs);
using var costManager = new CostManager(memoryAllocator, distArrayBuffer, pixCount, costModel);
using CostManager costManager = new(memoryAllocator, distArrayBuffer, pixCount, costModel);
Span<float> costManagerCosts = costManager.Costs.GetSpan();
Span<ushort> distArray = distArrayBuffer.GetSpan();
@ -441,12 +447,12 @@ internal static class BackwardReferenceEncoder
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 @@ internal static class BackwardReferenceEncoder
bestLength = MaxLength;
break;
}
else
{
bestLength = currLength;
}
bestLength = currLength;
}
}
}

16
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;
/// <summary>
/// Initializes a new instance of the <see cref="CostModel"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="literalArraySize">The literal array size.</param>
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 @@ internal class CostModel
public void Build(int xSize, int cacheBits, Vp8LBackwardRefs backwardRefs)
{
var histogram = new Vp8LHistogram(cacheBits);
using System.Collections.Generic.List<PixOrCopy>.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 @@ internal class CostModel
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<uint> populationCounts, double[] output)
{
uint sum = 0;
int nonzeros = 0;

193
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<Vp8LHistogram> imageHisto, Vp8LHistogram tmpHisto, Span<ushort> histogramSymbols)
public static void GetHistoImageSymbols(
MemoryAllocator memoryAllocator,
int xSize,
int ySize,
Vp8LBackwardRefs refs,
uint quality,
int histoBits,
int cacheBits,
Vp8LHistogramSet imageHisto,
Vp8LHistogram tmpHisto,
Span<ushort> 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<Vp8LHistogram>(imageHistoRawSize);
for (int i = 0; i < imageHistoRawSize; i++)
{
origHisto.Add(new Vp8LHistogram(cacheBits));
}
const int entropyCombineNumBins = BinSize;
using IMemoryOwner<ushort> tmp = memoryAllocator.Allocate<ushort>(imageHistoRawSize * 2, AllocationOptions.Clean);
Span<ushort> mapTmp = tmp.Slice(0, imageHistoRawSize);
Span<ushort> 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 @@ internal static class HistogramEncoder
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 @@ internal static class HistogramEncoder
HistogramRemap(origHisto, imageHisto, histogramSymbols);
}
private static void RemoveEmptyHistograms(List<Vp8LHistogram> 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);
}
/// <summary>
/// Construct the histograms from the backward references.
/// </summary>
private static void HistogramBuild(int xSize, int histoBits, Vp8LBackwardRefs backwardRefs, List<Vp8LHistogram> 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 @@ internal static class HistogramEncoder
/// Partition histograms to different entropy bins for three dominant (literal,
/// red and blue) symbol costs and compute the histogram aggregate bitCost.
/// </summary>
private static void HistogramAnalyzeEntropyBin(List<Vp8LHistogram> histograms, ushort[] binMap)
private static void HistogramAnalyzeEntropyBin(Vp8LHistogramSet histograms, Span<ushort> 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 @@ internal static class HistogramEncoder
}
}
private static int HistogramCopyAndAnalyze(List<Vp8LHistogram> origHistograms, List<Vp8LHistogram> histograms, Span<ushort> histogramSymbols)
private static int HistogramCopyAndAnalyze(
Vp8LHistogramSet origHistograms,
Vp8LHistogramSet histograms,
Span<ushort> 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 @@ internal static class HistogramEncoder
}
else
{
histograms[i] = (Vp8LHistogram)origHistogram.DeepClone();
origHistogram.CopyTo(histograms[i]);
histogramSymbols[i] = (ushort)clusterId++;
}
}
@ -184,11 +196,11 @@ internal static class HistogramEncoder
}
private static void HistogramCombineEntropyBin(
List<Vp8LHistogram> histograms,
Vp8LHistogramSet histograms,
Span<ushort> clusters,
ushort[] clusterMappings,
Span<ushort> clusterMappings,
Vp8LHistogram curCombo,
ushort[] binMap,
ReadOnlySpan<ushort> binMap,
int numBins,
double combineCostFactor)
{
@ -205,9 +217,9 @@ internal static class HistogramEncoder
clusterMappings[idx] = (ushort)idx;
}
var indicesToRemove = new List<int>();
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
List<int> indicesToRemove = new();
Vp8LStreaks stats = new();
Vp8LBitEntropy bitsEntropy = new();
for (int idx = 0; idx < histograms.Count; idx++)
{
if (histograms[idx] == null)
@ -236,13 +248,11 @@ internal static class HistogramEncoder
// 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 @@ internal static class HistogramEncoder
}
}
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 @@ internal static class HistogramEncoder
/// Given a Histogram set, the mapping of clusters 'clusterMapping' and the
/// current assignment of the cells in 'symbols', merge the clusters and assign the smallest possible clusters values.
/// </summary>
private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numClusters, ushort[] clusterMappingsTmp, Span<ushort> symbols)
private static void OptimizeHistogramSymbols(Span<ushort> clusterMappings, int numClusters, Span<ushort> clusterMappingsTmp, Span<ushort> symbols)
{
bool doContinue = true;
@ -293,7 +303,7 @@ internal static class HistogramEncoder
// 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 @@ internal static class HistogramEncoder
/// Perform histogram aggregation using a stochastic approach.
/// </summary>
/// <returns>true if a greedy approach needs to be performed afterwards, false otherwise.</returns>
private static bool HistogramCombineStochastic(List<Vp8LHistogram> 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 @@ internal static class HistogramEncoder
// 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<HistogramPair>();
int maxSize = 9;
List<HistogramPair> histoPriorityList = new();
const int maxSize = 9;
// Fill the initial mapping.
Span<int> 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 @@ internal static class HistogramEncoder
int mappingIndex = mappings.IndexOf(bestIdx2);
Span<int> src = mappings.Slice(mappingIndex + 1, numUsed - mappingIndex - 1);
Span<int> dst = mappings.Slice(mappingIndex);
Span<int> 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 @@ internal static class HistogramEncoder
// 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 @@ internal static class HistogramEncoder
// 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 @@ internal static class HistogramEncoder
triesWithNoSuccess = 0;
}
bool doGreedy = numUsed <= minClusterSize;
return doGreedy;
return numUsed <= minClusterSize;
}
private static void HistogramCombineGreedy(List<Vp8LHistogram> histograms)
private static void HistogramCombineGreedy(Vp8LHistogramSet histograms)
{
int histoSize = histograms.Count(h => h != null);
// Priority list of histogram pairs.
var histoPriorityList = new List<HistogramPair>();
List<HistogramPair> 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 @@ internal static class HistogramEncoder
// 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 @@ internal static class HistogramEncoder
}
}
private static void HistogramRemap(List<Vp8LHistogram> input, List<Vp8LHistogram> output, Span<ushort> symbols)
private static void HistogramRemap(
Vp8LHistogramSet input,
Vp8LHistogramSet output,
Span<ushort> 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 @@ internal static class HistogramEncoder
}
// Recompute each output.
int paletteCodeBits = output.First().PaletteCodeBits;
output.Clear();
int paletteCodeBits = output[0].PaletteCodeBits;
for (int i = 0; i < outSize; i++)
{
output.Add(new Vp8LHistogram(paletteCodeBits));
output[i].Clear();
output[i].PaletteCodeBits = paletteCodeBits;
}
for (int i = 0; i < inSize; i++)
@ -600,20 +610,26 @@ internal static class HistogramEncoder
/// Create a pair from indices "idx1" and "idx2" provided its cost is inferior to "threshold", a negative entropy.
/// </summary>
/// <returns>The cost of the pair, or 0 if it superior to threshold.</returns>
private static double HistoPriorityListPush(List<HistogramPair> histoList, int maxSize, List<Vp8LHistogram> histograms, int idx1, int idx2, double threshold, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy)
private static double HistoPriorityListPush(
List<HistogramPair> 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 @@ internal static class HistogramEncoder
}
/// <summary>
/// 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.
/// </summary>
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;

18
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<HuffmanTree> huffTree, HuffmanTreeCode huffCode)
public static void CreateHuffmanTree(Span<uint> histogram, int treeDepthLimit, bool[] bufRle, Span<HuffmanTree> huffTree, HuffmanTreeCode huffCode)
{
int numSymbols = huffCode.NumSymbols;
bufRle.AsSpan().Clear();
@ -40,7 +40,7 @@ internal static class HuffmanUtils
/// Change the population counts in a way that the consequent
/// Huffman tree compression, especially its RLE-part, give smaller output.
/// </summary>
public static void OptimizeHuffmanForRle(int length, bool[] goodForRle, uint[] counts)
public static void OptimizeHuffmanForRle(int length, bool[] goodForRle, Span<uint> counts)
{
// 1) Let's make the Huffman code more compatible with rle encoding.
for (; length >= 0; --length)
@ -116,7 +116,7 @@ internal static class HuffmanUtils
{
// 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 @@ internal static class HuffmanUtils
/// <param name="histogramSize">The size of the histogram.</param>
/// <param name="treeDepthLimit">The tree depth limit.</param>
/// <param name="bitDepths">How many bits are used for the symbol.</param>
public static void GenerateOptimalTree(Span<HuffmanTree> tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths)
public static void GenerateOptimalTree(Span<HuffmanTree> tree, Span<uint> histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths)
{
uint countMin;
int treeSizeOrig = 0;
@ -177,7 +177,7 @@ internal static class HuffmanUtils
return;
}
Span<HuffmanTree> treePool = tree.Slice(treeSizeOrig);
Span<HuffmanTree> 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 @@ internal static class HuffmanUtils
}
// Build the Huffman tree.
Span<HuffmanTree> treeSlice = tree.Slice(0, treeSize);
Span<HuffmanTree> treeSlice = tree[..treeSize];
treeSlice.Sort(HuffmanTree.Compare);
if (treeSize > 1)
@ -357,7 +357,7 @@ internal static class HuffmanUtils
// 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 @@ internal static class HuffmanUtils
for (; countsLen > 0; countsLen--)
{
var huffmanCode = new HuffmanCode()
HuffmanCode huffmanCode = new()
{
BitsUsed = len,
Value = (uint)sorted[symbol++]
@ -432,7 +432,7 @@ internal static class HuffmanUtils
};
}
var huffmanCode = new HuffmanCode
HuffmanCode huffmanCode = new()
{
BitsUsed = len - rootBits,
Value = (uint)sorted[symbol++]

2
src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs

@ -38,7 +38,7 @@ internal sealed class PixOrCopy
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;

6
src/ImageSharp/Formats/Webp/Lossless/Vp8LBitEntropy.cs

@ -125,7 +125,7 @@ internal class Vp8LBitEntropy
/// <summary>
/// Get the entropy for the distribution 'X'.
/// </summary>
public void BitsEntropyUnrefined(uint[] x, int length, Vp8LStreaks stats)
public void BitsEntropyUnrefined(Span<uint> x, int length, Vp8LStreaks stats)
{
int i;
int iPrev = 0;
@ -147,7 +147,7 @@ internal class Vp8LBitEntropy
this.Entropy += LosslessUtils.FastSLog2(this.Sum);
}
public void GetCombinedEntropyUnrefined(uint[] x, uint[] y, int length, Vp8LStreaks stats)
public void GetCombinedEntropyUnrefined(Span<uint> x, Span<uint> y, int length, Vp8LStreaks stats)
{
int i;
int iPrev = 0;
@ -169,7 +169,7 @@ internal class Vp8LBitEntropy
this.Entropy += LosslessUtils.FastSLog2(this.Sum);
}
public void GetEntropyUnrefined(uint[] x, int length, Vp8LStreaks stats)
public void GetEntropyUnrefined(Span<uint> x, int length, Vp8LStreaks stats)
{
int i;
int iPrev = 0;

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

@ -650,15 +650,21 @@ internal class Vp8LEncoder : IDisposable
Vp8LBackwardRefs refsTmp = this.Refs[refsBest.Equals(this.Refs[0]) ? 1 : 0];
this.bitWriter.Reset(bwInit);
Vp8LHistogram tmpHisto = new(cacheBits);
List<Vp8LHistogram> 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;
@ -739,9 +745,7 @@ internal class Vp8LEncoder : IDisposable
// 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;
@ -848,13 +852,8 @@ internal class Vp8LEncoder : IDisposable
refsTmp1,
refsTmp2);
List<Vp8LHistogram> histogramImage = new()
{
new Vp8LHistogram(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);
@ -894,7 +893,7 @@ internal class Vp8LEncoder : IDisposable
private void StoreHuffmanCode(Span<HuffmanTree> huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode)
{
int count = 0;
Span<int> symbols = this.scratch.Span.Slice(0, 2);
Span<int> symbols = this.scratch.Span[..2];
symbols.Clear();
const int maxBits = 8;
const int maxSymbol = 1 << maxBits;
@ -947,6 +946,7 @@ internal class Vp8LEncoder : IDisposable
private void StoreFullHuffmanCode(Span<HuffmanTree> 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];
@ -1057,7 +1057,12 @@ internal class Vp8LEncoder : IDisposable
}
}
private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs backwardRefs, Span<ushort> histogramSymbols, HuffmanTreeCode[] huffmanCodes)
private void StoreImageToBitMask(
int width,
int histoBits,
Vp8LBackwardRefs backwardRefs,
Span<ushort> histogramSymbols,
HuffmanTreeCode[] huffmanCodes)
{
int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(width, histoBits) : 1;
int tileMask = histoBits == 0 ? 0 : -(1 << histoBits);
@ -1069,10 +1074,10 @@ internal class Vp8LEncoder : IDisposable
int tileY = y & tileMask;
int histogramIx = histogramSymbols[0];
Span<HuffmanTreeCode> codes = huffmanCodes.AsSpan(5 * histogramIx);
using List<PixOrCopy>.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;
@ -1085,7 +1090,7 @@ internal class Vp8LEncoder : IDisposable
{
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);
}
}
@ -1446,10 +1451,8 @@ internal class Vp8LEncoder : IDisposable
useLut = false;
break;
}
else
{
buffer[ind] = (uint)j;
}
buffer[ind] = (uint)j;
}
if (useLut)
@ -1658,14 +1661,12 @@ internal class Vp8LEncoder : IDisposable
}
// 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<Vp8LHistogram> histogramImage, HuffmanTreeCode[] huffmanCodes)
private static void GetHuffBitLengthsAndCodes(Vp8LHistogramSet histogramImage, HuffmanTreeCode[] huffmanCodes)
{
int maxNumSymbols = 0;
@ -1676,13 +1677,25 @@ internal class Vp8LEncoder : IDisposable
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++)
{
@ -1696,8 +1709,9 @@ internal class Vp8LEncoder : IDisposable
}
// Create Huffman trees.
// TODO: Allocations.
bool[] bufRle = new bool[maxNumSymbols];
Span<HuffmanTree> huffTree = stackalloc HuffmanTree[3 * maxNumSymbols];
HuffmanTree[] huffTree = new HuffmanTree[3 * maxNumSymbols];
for (int i = 0; i < histogramImage.Count; i++)
{
@ -1749,8 +1763,18 @@ internal class Vp8LEncoder : IDisposable
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;
}
}
/// <summary>
@ -1787,11 +1811,7 @@ internal class Vp8LEncoder : IDisposable
[MethodImpl(InliningOptions.ShortMethod)]
private static void BitWriterSwap(ref Vp8LBitWriter src, ref Vp8LBitWriter dst)
{
Vp8LBitWriter tmp = src;
src = dst;
dst = tmp;
}
=> (dst, src) = (src, dst);
/// <summary>
/// Calculates the bits used for the transformation.
@ -1799,9 +1819,21 @@ internal class Vp8LEncoder : IDisposable
[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)]

308
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;
/// <summary>
/// Initializes a new instance of the <see cref="Vp8LHistogram"/> class.
/// </summary>
/// <param name="other">The histogram to create an instance from.</param>
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;
}
/// <summary>
/// Initializes a new instance of the <see cref="Vp8LHistogram"/> class.
/// </summary>
/// <param name="basePointer">The base pointer to the backing memory.</param>
/// <param name="refs">The backward references to initialize the histogram with.</param>
/// <param name="paletteCodeBits">The palette code bits.</param>
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);
/// <summary>
/// Initializes a new instance of the <see cref="Vp8LHistogram"/> class.
/// </summary>
/// <param name="basePointer">The base pointer to the backing memory.</param>
/// <param name="paletteCodeBits">The palette code bits.</param>
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;
}
/// <summary>
@ -85,22 +78,59 @@ internal sealed class Vp8LHistogram : IDeepCloneable
/// </summary>
public double BlueCost { get; set; }
public uint[] Red { get; }
public Span<uint> Red => new(this.red, RedSize);
public uint[] Blue { get; }
public Span<uint> Blue => new(this.blue, BlueSize);
public uint[] Alpha { get; }
public Span<uint> Alpha => new(this.alpha, AlphaSize);
public uint[] Literal { get; }
public Span<uint> Distance => new(this.distance, DistanceSize);
public uint[] Distance { get; }
public Span<uint> Literal => new(this.literal, LiteralSize);
public uint TrivialSymbol { get; set; }
public bool[] IsUsed { get; }
private Span<uint> IsUsedSpan => new(this.isUsed, UsedSize);
private Span<uint> 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;
/// <summary>
/// Creates a copy of the given <see cref="Vp8LHistogram"/> class.
/// </summary>
/// <param name="other">The histogram to copy to.</param>
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;
}
/// <inheritdoc/>
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;
}
/// <summary>
/// Collect all the references into a histogram (without reset).
@ -108,10 +138,9 @@ internal sealed class Vp8LHistogram : IDeepCloneable
/// <param name="refs">The backward references.</param>
public void StoreRefs(Vp8LBackwardRefs refs)
{
using List<PixOrCopy>.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 @@ internal sealed class Vp8LHistogram : IDeepCloneable
{
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 @@ internal sealed class Vp8LHistogram : IDeepCloneable
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 @@ internal sealed class Vp8LHistogram : IDeepCloneable
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 @@ internal sealed class Vp8LHistogram : IDeepCloneable
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 @@ internal sealed class Vp8LHistogram : IDeepCloneable
}
}
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<uint> x,
Span<uint> y,
int length,
bool isXUsed,
bool isYUsed,
bool trivialAtEnd,
Vp8LStreaks stats,
Vp8LBitEntropy bitEntropy)
{
stats.Clear();
bitEntropy.Init();
@ -450,18 +482,15 @@ internal sealed class Vp8LHistogram : IDeepCloneable
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 @@ internal sealed class Vp8LHistogram : IDeepCloneable
/// <summary>
/// Get the symbol entropy for the distribution 'population'.
/// </summary>
private static double PopulationCost(uint[] population, int length, ref uint trivialSym, ref bool isUsed, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy)
private double PopulationCost(Span<uint> population, int length, ref uint trivialSym, int isUsedIndex, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy)
{
bitEntropy.Init();
stats.Clear();
@ -491,7 +520,7 @@ internal sealed class Vp8LHistogram : IDeepCloneable
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 @@ internal sealed class Vp8LHistogram : IDeepCloneable
}
}
}
internal sealed unsafe class OwnedVp8LHistogram : Vp8LHistogram, IDisposable
{
private readonly IMemoryOwner<uint> bufferOwner;
private MemoryHandle bufferHandle;
private bool isDisposed;
private OwnedVp8LHistogram(
IMemoryOwner<uint> bufferOwner,
ref MemoryHandle bufferHandle,
uint* basePointer,
int paletteCodeBits)
: base(basePointer, paletteCodeBits)
{
this.bufferOwner = bufferOwner;
this.bufferHandle = bufferHandle;
}
/// <summary>
/// Creates an <see cref="OwnedVp8LHistogram"/> that is not a member of a <see cref="Vp8LHistogramSet"/>.
/// </summary>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="paletteCodeBits">The palette code bits.</param>
public static OwnedVp8LHistogram Create(MemoryAllocator memoryAllocator, int paletteCodeBits)
{
IMemoryOwner<uint> bufferOwner = memoryAllocator.Allocate<uint>(BufferSize, AllocationOptions.Clean);
MemoryHandle bufferHandle = bufferOwner.Memory.Pin();
return new OwnedVp8LHistogram(bufferOwner, ref bufferHandle, (uint*)bufferHandle.Pointer, paletteCodeBits);
}
/// <summary>
/// Creates an <see cref="OwnedVp8LHistogram"/> that is not a member of a <see cref="Vp8LHistogramSet"/>.
/// </summary>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="refs">The backward references to initialize the histogram with.</param>
/// <param name="paletteCodeBits">The palette code bits.</param>
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;
}
}
}

110
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<Vp8LHistogram>, IDisposable
{
private readonly IMemoryOwner<uint> buffer;
private MemoryHandle bufferHandle;
private readonly List<Vp8LHistogram> items;
private bool isDisposed;
public Vp8LHistogramSet(MemoryAllocator memoryAllocator, int capacity, int cacheBits)
{
this.buffer = memoryAllocator.Allocate<uint>(Vp8LHistogram.BufferSize * capacity, AllocationOptions.Clean);
this.bufferHandle = this.buffer.Memory.Pin();
unsafe
{
uint* basePointer = (uint*)this.bufferHandle.Pointer;
this.items = new List<Vp8LHistogram>(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<uint>(Vp8LHistogram.BufferSize * capacity, AllocationOptions.Clean);
this.bufferHandle = this.buffer.Memory.Pin();
unsafe
{
uint* basePointer = (uint*)this.bufferHandle.Pointer;
this.items = new List<Vp8LHistogram>(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<Vp8LHistogram> GetEnumerator() => ((IEnumerable<Vp8LHistogram>)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)
{
}
}
}

13
tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs

@ -43,9 +43,9 @@ public class EncodeWebp
[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 class EncodeWebp
[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 class EncodeWebp
[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 class EncodeWebp
[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

29
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 class DominantCostRangeTests
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 class DominantCostRangeTests
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 class DominantCostRangeTests
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

14
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 @@ public class Vp8LHistogramTests
// 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 @@ public class Vp8LHistogramTests
});
}
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);

Loading…
Cancel
Save