Skip to content
Merged
10 changes: 7 additions & 3 deletions src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public static Vp8LBackwardRefs GetBackwardReferences(
double bitCostBest = -1;
int cacheBitsInitial = cacheBits;
Vp8LHashChain hashChainBox = null;
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
for (int lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1)
{
int cacheBitsTmp = cacheBitsInitial;
Expand Down Expand Up @@ -81,7 +83,7 @@ public static Vp8LBackwardRefs GetBackwardReferences(

// Keep the best backward references.
var histo = new Vp8LHistogram(worst, cacheBitsTmp);
double bitCost = histo.EstimateBits();
double bitCost = histo.EstimateBits(stats, bitsEntropy);

if (lz77TypeBest == 0 || bitCost < bitCostBest)
{
Expand All @@ -100,7 +102,7 @@ public static Vp8LBackwardRefs GetBackwardReferences(
Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox;
BackwardReferencesTraceBackwards(width, height, bgra, cacheBits, hashChainTmp, best, worst);
var histo = new Vp8LHistogram(worst, cacheBits);
double bitCostTrace = histo.EstimateBits();
double bitCostTrace = histo.EstimateBits(stats, bitsEntropy);
if (bitCostTrace < bitCostBest)
{
best = worst;
Expand Down Expand Up @@ -214,9 +216,11 @@ private static int CalculateBestCacheSize(ReadOnlySpan<uint> bgra, int quality,
}
}

var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
for (int i = 0; i <= cacheBitsMax; i++)
{
double entropy = histos[i].EstimateBits();
double entropy = histos[i].EstimateBits(stats, bitsEntropy);
if (i == 0 || entropy < entropyMin)
{
entropyMin = entropy;
Expand Down
41 changes: 29 additions & 12 deletions src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,12 @@ private static void HistogramAnalyzeEntropyBin(List<Vp8LHistogram> histograms, u

private static int HistogramCopyAndAnalyze(List<Vp8LHistogram> origHistograms, List<Vp8LHistogram> histograms, ushort[] histogramSymbols)
{
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
for (int clusterId = 0, i = 0; i < origHistograms.Count; i++)
{
Vp8LHistogram origHistogram = origHistograms[i];
origHistogram.UpdateHistogramCost();
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])
Expand All @@ -175,7 +177,14 @@ private static int HistogramCopyAndAnalyze(List<Vp8LHistogram> origHistograms, L
return numUsed;
}

private static void HistogramCombineEntropyBin(List<Vp8LHistogram> histograms, ushort[] clusters, ushort[] clusterMappings, Vp8LHistogram curCombo, ushort[] binMap, int numBins, double combineCostFactor)
private static void HistogramCombineEntropyBin(
List<Vp8LHistogram> histograms,
ushort[] clusters,
ushort[] clusterMappings,
Vp8LHistogram curCombo,
ushort[] binMap,
int numBins,
double combineCostFactor)
{
var binInfo = new HistogramBinInfo[BinSize];
for (int idx = 0; idx < numBins; idx++)
Expand All @@ -191,6 +200,8 @@ private static void HistogramCombineEntropyBin(List<Vp8LHistogram> histograms, u
}

var indicesToRemove = new List<int>();
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
for (int idx = 0; idx < histograms.Count; idx++)
{
if (histograms[idx] == null)
Expand All @@ -209,7 +220,7 @@ private static void HistogramCombineEntropyBin(List<Vp8LHistogram> histograms, u
// Try to merge #idx into #first (both share the same binId)
double bitCost = histograms[idx].BitCost;
double bitCostThresh = -bitCost * combineCostFactor;
double currCostDiff = histograms[first].AddEval(histograms[idx], bitCostThresh, curCombo);
double currCostDiff = histograms[first].AddEval(histograms[idx], stats, bitsEntropy, bitCostThresh, curCombo);

if (currCostDiff < bitCostThresh)
{
Expand Down Expand Up @@ -308,6 +319,8 @@ private static bool HistogramCombineStochastic(List<Vp8LHistogram> histograms, i
int numUsed = histograms.Count(h => h != null);
int outerIters = numUsed;
int numTriesNoSuccess = outerIters / 2;
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();

if (numUsed < minClusterSize)
{
Expand Down Expand Up @@ -354,7 +367,7 @@ private static bool HistogramCombineStochastic(List<Vp8LHistogram> histograms, i
idx2 = mappings[idx2];

// Calculate cost reduction on combination.
double currCost = HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, idx2, bestCost);
double currCost = HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, idx2, bestCost, stats, bitsEntropy);

// Found a better pair?
if (currCost < 0)
Expand Down Expand Up @@ -428,7 +441,7 @@ private static bool HistogramCombineStochastic(List<Vp8LHistogram> histograms, i
if (doEval)
{
// Re-evaluate the cost of an updated pair.
HistoListUpdatePair(histograms[p.Idx1], histograms[p.Idx2], 0.0d, p);
HistoListUpdatePair(histograms[p.Idx1], histograms[p.Idx2], stats, bitsEntropy, 0.0d, p);
if (p.CostDiff >= 0.0d)
{
histoPriorityList[j] = histoPriorityList[histoPriorityList.Count - 1];
Expand Down Expand Up @@ -456,6 +469,8 @@ private static void HistogramCombineGreedy(List<Vp8LHistogram> histograms)
// Priority list of histogram pairs.
var histoPriorityList = new List<HistogramPair>();
int maxSize = histoSize * histoSize;
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();

for (int i = 0; i < histoSize; i++)
{
Expand All @@ -471,7 +486,7 @@ private static void HistogramCombineGreedy(List<Vp8LHistogram> histograms)
continue;
}

HistoPriorityListPush(histoPriorityList, maxSize, histograms, i, j, 0.0d);
HistoPriorityListPush(histoPriorityList, maxSize, histograms, i, j, 0.0d, stats, bitsEntropy);
}
}

Expand Down Expand Up @@ -510,7 +525,7 @@ private static void HistogramCombineGreedy(List<Vp8LHistogram> histograms)
continue;
}

HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, i, 0.0d);
HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, i, 0.0d, stats, bitsEntropy);
}
}
}
Expand All @@ -519,6 +534,8 @@ private static void HistogramRemap(List<Vp8LHistogram> input, List<Vp8LHistogram
{
int inSize = input.Count;
int outSize = output.Count;
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
if (outSize > 1)
{
for (int i = 0; i < inSize; i++)
Expand All @@ -534,7 +551,7 @@ private static void HistogramRemap(List<Vp8LHistogram> input, List<Vp8LHistogram
double bestBits = double.MaxValue;
for (int k = 0; k < outSize; k++)
{
double curBits = output[k].AddThresh(input[i], bestBits);
double curBits = output[k].AddThresh(input[i], stats, bitsEntropy, bestBits);
if (k == 0 || curBits < bestBits)
{
bestBits = curBits;
Expand Down Expand Up @@ -577,7 +594,7 @@ private static void HistogramRemap(List<Vp8LHistogram> input, List<Vp8LHistogram
/// 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)
private static double HistoPriorityListPush(List<HistogramPair> histoList, int maxSize, List<Vp8LHistogram> histograms, int idx1, int idx2, double threshold, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy)
{
var pair = new HistogramPair();

Expand All @@ -598,7 +615,7 @@ private static double HistoPriorityListPush(List<HistogramPair> histoList, int m
Vp8LHistogram h1 = histograms[idx1];
Vp8LHistogram h2 = histograms[idx2];

HistoListUpdatePair(h1, h2, threshold, pair);
HistoListUpdatePair(h1, h2, stats, bitsEntropy, threshold, pair);

// Do not even consider the pair if it does not improve the entropy.
if (pair.CostDiff >= threshold)
Expand All @@ -616,11 +633,11 @@ private static double HistoPriorityListPush(List<HistogramPair> histoList, int m
/// <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.
/// </summary>
private static void HistoListUpdatePair(Vp8LHistogram h1, Vp8LHistogram h2, 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;
h1.GetCombinedHistogramEntropy(h2, sumCost + threshold, costInitial: pair.CostCombo, out double cost);
h1.GetCombinedHistogramEntropy(h2, stats, bitsEntropy, sumCost + threshold, costInitial: pair.CostCombo, out double cost);
pair.CostCombo = cost;
pair.CostDiff = pair.CostCombo - sumCost;
}
Expand Down
9 changes: 4 additions & 5 deletions src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,13 @@ public static int Compare(HuffmanTree t1, HuffmanTree t2)
{
return -1;
}
else if (t1.TotalCount < t2.TotalCount)

if (t1.TotalCount < t2.TotalCount)
{
return 1;
}
else
{
return t1.Value < t2.Value ? -1 : 1;
}

return t1.Value < t2.Value ? -1 : 1;
}

public IDeepCloneable DeepClone() => new HuffmanTree(this);
Expand Down
5 changes: 5 additions & 0 deletions src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,14 @@ public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int
}

// Build the Huffman tree.
#if NET5_0_OR_GREATER
Span<HuffmanTree> treeSlice = tree.AsSpan().Slice(0, treeSize);
treeSlice.Sort(HuffmanTree.Compare);
#else
HuffmanTree[] treeCopy = tree.AsSpan().Slice(0, treeSize).ToArray();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unfortunate that .NET Core 3.1 doesn't give us sort here. We could copy ArraySortHelper or even use something like SortUtility from Drawing but I don't know if it's worth the effort. @antonfirsov what do you think?

Array.Sort(treeCopy, HuffmanTree.Compare);
treeCopy.AsSpan().CopyTo(tree);
#endif

if (treeSize > 1)
{
Expand Down
Loading