Skip to content

Commit a06011a

Browse files
authored
Merge pull request #1799 from SixLabors/bp/reduceallocations
Webp encoding: Reduce allocations.
2 parents 5a7c1f4 + e97c364 commit a06011a

22 files changed

+498
-289
lines changed

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ public static Vp8LBackwardRefs GetBackwardReferences(
4949
double bitCostBest = -1;
5050
int cacheBitsInitial = cacheBits;
5151
Vp8LHashChain hashChainBox = null;
52+
var stats = new Vp8LStreaks();
53+
var bitsEntropy = new Vp8LBitEntropy();
5254
for (int lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1)
5355
{
5456
int cacheBitsTmp = cacheBitsInitial;
@@ -81,7 +83,7 @@ public static Vp8LBackwardRefs GetBackwardReferences(
8183

8284
// Keep the best backward references.
8385
var histo = new Vp8LHistogram(worst, cacheBitsTmp);
84-
double bitCost = histo.EstimateBits();
86+
double bitCost = histo.EstimateBits(stats, bitsEntropy);
8587

8688
if (lz77TypeBest == 0 || bitCost < bitCostBest)
8789
{
@@ -100,7 +102,7 @@ public static Vp8LBackwardRefs GetBackwardReferences(
100102
Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox;
101103
BackwardReferencesTraceBackwards(width, height, bgra, cacheBits, hashChainTmp, best, worst);
102104
var histo = new Vp8LHistogram(worst, cacheBits);
103-
double bitCostTrace = histo.EstimateBits();
105+
double bitCostTrace = histo.EstimateBits(stats, bitsEntropy);
104106
if (bitCostTrace < bitCostBest)
105107
{
106108
best = worst;
@@ -214,9 +216,11 @@ private static int CalculateBestCacheSize(ReadOnlySpan<uint> bgra, int quality,
214216
}
215217
}
216218

219+
var stats = new Vp8LStreaks();
220+
var bitsEntropy = new Vp8LBitEntropy();
217221
for (int i = 0; i <= cacheBitsMax; i++)
218222
{
219-
double entropy = histos[i].EstimateBits();
223+
double entropy = histos[i].EstimateBits(stats, bitsEntropy);
220224
if (i == 0 || entropy < entropyMin)
221225
{
222226
entropyMin = entropy;

src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,12 @@ private static void HistogramAnalyzeEntropyBin(List<Vp8LHistogram> histograms, u
152152

153153
private static int HistogramCopyAndAnalyze(List<Vp8LHistogram> origHistograms, List<Vp8LHistogram> histograms, ushort[] histogramSymbols)
154154
{
155+
var stats = new Vp8LStreaks();
156+
var bitsEntropy = new Vp8LBitEntropy();
155157
for (int clusterId = 0, i = 0; i < origHistograms.Count; i++)
156158
{
157159
Vp8LHistogram origHistogram = origHistograms[i];
158-
origHistogram.UpdateHistogramCost();
160+
origHistogram.UpdateHistogramCost(stats, bitsEntropy);
159161

160162
// Skip the histogram if it is completely empty, which can happen for tiles with no information (when they are skipped because of LZ77).
161163
if (!origHistogram.IsUsed[0] && !origHistogram.IsUsed[1] && !origHistogram.IsUsed[2] && !origHistogram.IsUsed[3] && !origHistogram.IsUsed[4])
@@ -175,7 +177,14 @@ private static int HistogramCopyAndAnalyze(List<Vp8LHistogram> origHistograms, L
175177
return numUsed;
176178
}
177179

178-
private static void HistogramCombineEntropyBin(List<Vp8LHistogram> histograms, ushort[] clusters, ushort[] clusterMappings, Vp8LHistogram curCombo, ushort[] binMap, int numBins, double combineCostFactor)
180+
private static void HistogramCombineEntropyBin(
181+
List<Vp8LHistogram> histograms,
182+
ushort[] clusters,
183+
ushort[] clusterMappings,
184+
Vp8LHistogram curCombo,
185+
ushort[] binMap,
186+
int numBins,
187+
double combineCostFactor)
179188
{
180189
var binInfo = new HistogramBinInfo[BinSize];
181190
for (int idx = 0; idx < numBins; idx++)
@@ -191,6 +200,8 @@ private static void HistogramCombineEntropyBin(List<Vp8LHistogram> histograms, u
191200
}
192201

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

214225
if (currCostDiff < bitCostThresh)
215226
{
@@ -308,6 +319,8 @@ private static bool HistogramCombineStochastic(List<Vp8LHistogram> histograms, i
308319
int numUsed = histograms.Count(h => h != null);
309320
int outerIters = numUsed;
310321
int numTriesNoSuccess = outerIters / 2;
322+
var stats = new Vp8LStreaks();
323+
var bitsEntropy = new Vp8LBitEntropy();
311324

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

356369
// Calculate cost reduction on combination.
357-
double currCost = HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, idx2, bestCost);
370+
double currCost = HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, idx2, bestCost, stats, bitsEntropy);
358371

359372
// Found a better pair?
360373
if (currCost < 0)
@@ -428,7 +441,7 @@ private static bool HistogramCombineStochastic(List<Vp8LHistogram> histograms, i
428441
if (doEval)
429442
{
430443
// Re-evaluate the cost of an updated pair.
431-
HistoListUpdatePair(histograms[p.Idx1], histograms[p.Idx2], 0.0d, p);
444+
HistoListUpdatePair(histograms[p.Idx1], histograms[p.Idx2], stats, bitsEntropy, 0.0d, p);
432445
if (p.CostDiff >= 0.0d)
433446
{
434447
histoPriorityList[j] = histoPriorityList[histoPriorityList.Count - 1];
@@ -456,6 +469,8 @@ private static void HistogramCombineGreedy(List<Vp8LHistogram> histograms)
456469
// Priority list of histogram pairs.
457470
var histoPriorityList = new List<HistogramPair>();
458471
int maxSize = histoSize * histoSize;
472+
var stats = new Vp8LStreaks();
473+
var bitsEntropy = new Vp8LBitEntropy();
459474

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

474-
HistoPriorityListPush(histoPriorityList, maxSize, histograms, i, j, 0.0d);
489+
HistoPriorityListPush(histoPriorityList, maxSize, histograms, i, j, 0.0d, stats, bitsEntropy);
475490
}
476491
}
477492

@@ -510,7 +525,7 @@ private static void HistogramCombineGreedy(List<Vp8LHistogram> histograms)
510525
continue;
511526
}
512527

513-
HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, i, 0.0d);
528+
HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, i, 0.0d, stats, bitsEntropy);
514529
}
515530
}
516531
}
@@ -519,6 +534,8 @@ private static void HistogramRemap(List<Vp8LHistogram> input, List<Vp8LHistogram
519534
{
520535
int inSize = input.Count;
521536
int outSize = output.Count;
537+
var stats = new Vp8LStreaks();
538+
var bitsEntropy = new Vp8LBitEntropy();
522539
if (outSize > 1)
523540
{
524541
for (int i = 0; i < inSize; i++)
@@ -534,7 +551,7 @@ private static void HistogramRemap(List<Vp8LHistogram> input, List<Vp8LHistogram
534551
double bestBits = double.MaxValue;
535552
for (int k = 0; k < outSize; k++)
536553
{
537-
double curBits = output[k].AddThresh(input[i], bestBits);
554+
double curBits = output[k].AddThresh(input[i], stats, bitsEntropy, bestBits);
538555
if (k == 0 || curBits < bestBits)
539556
{
540557
bestBits = curBits;
@@ -577,7 +594,7 @@ private static void HistogramRemap(List<Vp8LHistogram> input, List<Vp8LHistogram
577594
/// Create a pair from indices "idx1" and "idx2" provided its cost is inferior to "threshold", a negative entropy.
578595
/// </summary>
579596
/// <returns>The cost of the pair, or 0 if it superior to threshold.</returns>
580-
private static double HistoPriorityListPush(List<HistogramPair> histoList, int maxSize, List<Vp8LHistogram> histograms, int idx1, int idx2, double threshold)
597+
private static double HistoPriorityListPush(List<HistogramPair> histoList, int maxSize, List<Vp8LHistogram> histograms, int idx1, int idx2, double threshold, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy)
581598
{
582599
var pair = new HistogramPair();
583600

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

601-
HistoListUpdatePair(h1, h2, threshold, pair);
618+
HistoListUpdatePair(h1, h2, stats, bitsEntropy, threshold, pair);
602619

603620
// Do not even consider the pair if it does not improve the entropy.
604621
if (pair.CostDiff >= threshold)
@@ -616,11 +633,11 @@ private static double HistoPriorityListPush(List<HistogramPair> histoList, int m
616633
/// <summary>
617634
/// 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.
618635
/// </summary>
619-
private static void HistoListUpdatePair(Vp8LHistogram h1, Vp8LHistogram h2, double threshold, HistogramPair pair)
636+
private static void HistoListUpdatePair(Vp8LHistogram h1, Vp8LHistogram h2, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy, double threshold, HistogramPair pair)
620637
{
621638
double sumCost = h1.BitCost + h2.BitCost;
622639
pair.CostCombo = 0.0d;
623-
h1.GetCombinedHistogramEntropy(h2, sumCost + threshold, costInitial: pair.CostCombo, out double cost);
640+
h1.GetCombinedHistogramEntropy(h2, stats, bitsEntropy, sumCost + threshold, costInitial: pair.CostCombo, out double cost);
624641
pair.CostCombo = cost;
625642
pair.CostDiff = pair.CostCombo - sumCost;
626643
}

src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,13 @@ public static int Compare(HuffmanTree t1, HuffmanTree t2)
4949
{
5050
return -1;
5151
}
52-
else if (t1.TotalCount < t2.TotalCount)
52+
53+
if (t1.TotalCount < t2.TotalCount)
5354
{
5455
return 1;
5556
}
56-
else
57-
{
58-
return t1.Value < t2.Value ? -1 : 1;
59-
}
57+
58+
return t1.Value < t2.Value ? -1 : 1;
6059
}
6160

6261
public IDeepCloneable DeepClone() => new HuffmanTree(this);

src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,14 @@ public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int
202202
}
203203

204204
// Build the Huffman tree.
205-
HuffmanTree[] treeCopy = tree.AsSpan().Slice(0, treeSize).ToArray();
205+
#if NET5_0_OR_GREATER
206+
Span<HuffmanTree> treeSlice = tree.AsSpan(0, treeSize);
207+
treeSlice.Sort(HuffmanTree.Compare);
208+
#else
209+
HuffmanTree[] treeCopy = tree.AsSpan(0, treeSize).ToArray();
206210
Array.Sort(treeCopy, HuffmanTree.Compare);
207211
treeCopy.AsSpan().CopyTo(tree);
212+
#endif
208213

209214
if (treeSize > 1)
210215
{

0 commit comments

Comments
 (0)