Skip to content

Commit 05c3f2b

Browse files
authored
Merge pull request #1846 from SixLabors/bp/webpimprovements
Webp improvements
2 parents 2c5c9f1 + 1450849 commit 05c3f2b

19 files changed

+175
-129
lines changed

src/ImageSharp/Formats/Webp/EntropyIx.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
66
/// <summary>
77
/// These five modes are evaluated and their respective entropy is computed.
88
/// </summary>
9-
internal enum EntropyIx
9+
internal enum EntropyIx : byte
1010
{
1111
Direct = 0,
1212

src/ImageSharp/Formats/Webp/HistoIx.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace SixLabors.ImageSharp.Formats.Webp
55
{
6-
internal enum HistoIx
6+
internal enum HistoIx : byte
77
{
88
HistoAlpha = 0,
99

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

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5+
using System.Buffers;
56
using System.Collections.Generic;
7+
using SixLabors.ImageSharp.Memory;
68

79
namespace SixLabors.ImageSharp.Formats.Webp.Lossless
810
{
9-
internal class BackwardReferenceEncoder
11+
internal static class BackwardReferenceEncoder
1012
{
1113
/// <summary>
1214
/// Maximum bit length.
@@ -41,6 +43,7 @@ public static Vp8LBackwardRefs GetBackwardReferences(
4143
int quality,
4244
int lz77TypesToTry,
4345
ref int cacheBits,
46+
MemoryAllocator memoryAllocator,
4447
Vp8LHashChain hashChain,
4548
Vp8LBackwardRefs best,
4649
Vp8LBackwardRefs worst)
@@ -69,7 +72,7 @@ public static Vp8LBackwardRefs GetBackwardReferences(
6972
BackwardReferencesLz77(width, height, bgra, 0, hashChain, worst);
7073
break;
7174
case Vp8LLz77Type.Lz77Box:
72-
hashChainBox = new Vp8LHashChain(width * height);
75+
hashChainBox = new Vp8LHashChain(memoryAllocator, width * height);
7376
BackwardReferencesLz77Box(width, height, bgra, 0, hashChain, hashChainBox, worst);
7477
break;
7578
}
@@ -100,7 +103,7 @@ public static Vp8LBackwardRefs GetBackwardReferences(
100103
if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25)
101104
{
102105
Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox;
103-
BackwardReferencesTraceBackwards(width, height, bgra, cacheBits, hashChainTmp, best, worst);
106+
BackwardReferencesTraceBackwards(width, height, memoryAllocator, bgra, cacheBits, hashChainTmp, best, worst);
104107
var histo = new Vp8LHistogram(worst, cacheBits);
105108
double bitCostTrace = histo.EstimateBits(stats, bitsEntropy);
106109
if (bitCostTrace < bitCostBest)
@@ -111,6 +114,8 @@ public static Vp8LBackwardRefs GetBackwardReferences(
111114

112115
BackwardReferences2DLocality(width, best);
113116

117+
hashChainBox?.Dispose();
118+
114119
return best;
115120
}
116121

@@ -234,29 +239,32 @@ private static int CalculateBestCacheSize(ReadOnlySpan<uint> bgra, int quality,
234239
private static void BackwardReferencesTraceBackwards(
235240
int xSize,
236241
int ySize,
242+
MemoryAllocator memoryAllocator,
237243
ReadOnlySpan<uint> bgra,
238244
int cacheBits,
239245
Vp8LHashChain hashChain,
240246
Vp8LBackwardRefs refsSrc,
241247
Vp8LBackwardRefs refsDst)
242248
{
243249
int distArraySize = xSize * ySize;
244-
ushort[] distArray = new ushort[distArraySize];
250+
using IMemoryOwner<ushort> distArrayBuffer = memoryAllocator.Allocate<ushort>(distArraySize);
251+
Span<ushort> distArray = distArrayBuffer.GetSpan();
245252

246-
BackwardReferencesHashChainDistanceOnly(xSize, ySize, bgra, cacheBits, hashChain, refsSrc, distArray);
253+
BackwardReferencesHashChainDistanceOnly(xSize, ySize, memoryAllocator, bgra, cacheBits, hashChain, refsSrc, distArrayBuffer);
247254
int chosenPathSize = TraceBackwards(distArray, distArraySize);
248-
Span<ushort> chosenPath = distArray.AsSpan(distArraySize - chosenPathSize);
255+
Span<ushort> chosenPath = distArray.Slice(distArraySize - chosenPathSize);
249256
BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst);
250257
}
251258

252259
private static void BackwardReferencesHashChainDistanceOnly(
253260
int xSize,
254261
int ySize,
262+
MemoryAllocator memoryAllocator,
255263
ReadOnlySpan<uint> bgra,
256264
int cacheBits,
257265
Vp8LHashChain hashChain,
258266
Vp8LBackwardRefs refs,
259-
ushort[] distArray)
267+
IMemoryOwner<ushort> distArrayBuffer)
260268
{
261269
int pixCount = xSize * ySize;
262270
bool useColorCache = cacheBits > 0;
@@ -275,22 +283,24 @@ private static void BackwardReferencesHashChainDistanceOnly(
275283
}
276284

277285
costModel.Build(xSize, cacheBits, refs);
278-
var costManager = new CostManager(distArray, pixCount, costModel);
286+
using var costManager = new CostManager(memoryAllocator, distArrayBuffer, pixCount, costModel);
287+
Span<float> costManagerCosts = costManager.Costs.GetSpan();
288+
Span<ushort> distArray = distArrayBuffer.GetSpan();
279289

280290
// We loop one pixel at a time, but store all currently best points to non-processed locations from this point.
281291
distArray[0] = 0;
282292

283293
// Add first pixel as literal.
284-
AddSingleLiteralWithCostModel(bgra, colorCache, costModel, 0, useColorCache, 0.0f, costManager.Costs, distArray);
294+
AddSingleLiteralWithCostModel(bgra, colorCache, costModel, 0, useColorCache, 0.0f, costManagerCosts, distArray);
285295

286296
for (int i = 1; i < pixCount; i++)
287297
{
288-
float prevCost = costManager.Costs[i - 1];
298+
float prevCost = costManagerCosts[i - 1];
289299
int offset = hashChain.FindOffset(i);
290300
int len = hashChain.FindLength(i);
291301

292302
// Try adding the pixel as a literal.
293-
AddSingleLiteralWithCostModel(bgra, colorCache, costModel, i, useColorCache, prevCost, costManager.Costs, distArray);
303+
AddSingleLiteralWithCostModel(bgra, colorCache, costModel, i, useColorCache, prevCost, costManagerCosts, distArray);
294304

295305
// If we are dealing with a non-literal.
296306
if (len >= 2)
@@ -334,7 +344,7 @@ private static void BackwardReferencesHashChainDistanceOnly(
334344
costManager.UpdateCostAtIndex(j - 1, false);
335345
costManager.UpdateCostAtIndex(j, false);
336346

337-
costManager.PushInterval(costManager.Costs[j - 1] + offsetCost, j, lenJ);
347+
costManager.PushInterval(costManagerCosts[j - 1] + offsetCost, j, lenJ);
338348
reach = j + lenJ - 1;
339349
}
340350
}
@@ -346,7 +356,7 @@ private static void BackwardReferencesHashChainDistanceOnly(
346356
}
347357
}
348358

349-
private static int TraceBackwards(ushort[] distArray, int distArraySize)
359+
private static int TraceBackwards(Span<ushort> distArray, int distArraySize)
350360
{
351361
int chosenPathSize = 0;
352362
int pathPos = distArraySize;
@@ -426,8 +436,8 @@ private static void AddSingleLiteralWithCostModel(
426436
int idx,
427437
bool useColorCache,
428438
float prevCost,
429-
float[] cost,
430-
ushort[] distArray)
439+
Span<float> cost,
440+
Span<ushort> distArray)
431441
{
432442
double costVal = prevCost;
433443
uint color = bgra[idx];
@@ -617,7 +627,8 @@ private static void BackwardReferencesLz77Box(int xSize, int ySize, ReadOnlySpan
617627
}
618628
}
619629

620-
hashChain.OffsetLength[0] = 0;
630+
Span<uint> hashChainOffsetLength = hashChain.OffsetLength.GetSpan();
631+
hashChainOffsetLength[0] = 0;
621632
for (i = 1; i < pixelCount; i++)
622633
{
623634
int ind;
@@ -695,19 +706,19 @@ private static void BackwardReferencesLz77Box(int xSize, int ySize, ReadOnlySpan
695706

696707
if (bestLength <= MinLength)
697708
{
698-
hashChain.OffsetLength[i] = 0;
709+
hashChainOffsetLength[i] = 0;
699710
bestOffsetPrev = 0;
700711
bestLengthPrev = 0;
701712
}
702713
else
703714
{
704-
hashChain.OffsetLength[i] = (uint)((bestOffset << MaxLengthBits) | bestLength);
715+
hashChainOffsetLength[i] = (uint)((bestOffset << MaxLengthBits) | bestLength);
705716
bestOffsetPrev = bestOffset;
706717
bestLengthPrev = bestLength;
707718
}
708719
}
709720

710-
hashChain.OffsetLength[0] = 0;
721+
hashChainOffsetLength[0] = 0;
711722
BackwardReferencesLz77(xSize, ySize, bgra, cacheBits, hashChain, refs);
712723
}
713724

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

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

4+
using System;
5+
using System.Buffers;
46
using System.Collections.Generic;
7+
using SixLabors.ImageSharp.Memory;
58

69
namespace SixLabors.ImageSharp.Formats.Webp.Lossless
710
{
@@ -10,20 +13,29 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
1013
/// It caches the different CostCacheInterval, caches the different
1114
/// GetLengthCost(costModel, k) in costCache and the CostInterval's.
1215
/// </summary>
13-
internal class CostManager
16+
internal sealed class CostManager : IDisposable
1417
{
1518
private CostInterval head;
1619

17-
public CostManager(ushort[] distArray, int pixCount, CostModel costModel)
20+
private const int FreeIntervalsStartCount = 25;
21+
22+
private readonly Stack<CostInterval> freeIntervals = new(FreeIntervalsStartCount);
23+
24+
public CostManager(MemoryAllocator memoryAllocator, IMemoryOwner<ushort> distArray, int pixCount, CostModel costModel)
1825
{
1926
int costCacheSize = pixCount > BackwardReferenceEncoder.MaxLength ? BackwardReferenceEncoder.MaxLength : pixCount;
2027

2128
this.CacheIntervals = new List<CostCacheInterval>();
2229
this.CostCache = new List<double>();
23-
this.Costs = new float[pixCount];
30+
this.Costs = memoryAllocator.Allocate<float>(pixCount);
2431
this.DistArray = distArray;
2532
this.Count = 0;
2633

34+
for (int i = 0; i < FreeIntervalsStartCount; i++)
35+
{
36+
this.freeIntervals.Push(new CostInterval());
37+
}
38+
2739
// Fill in the cost cache.
2840
this.CacheIntervalsSize++;
2941
this.CostCache.Add(costModel.GetLengthCost(0));
@@ -64,10 +76,7 @@ public CostManager(ushort[] distArray, int pixCount, CostModel costModel)
6476
}
6577

6678
// Set the initial costs high for every pixel as we will keep the minimum.
67-
for (int i = 0; i < pixCount; i++)
68-
{
69-
this.Costs[i] = 1e38f;
70-
}
79+
this.Costs.GetSpan().Fill(1e38f);
7180
}
7281

7382
/// <summary>
@@ -82,9 +91,9 @@ public CostManager(ushort[] distArray, int pixCount, CostModel costModel)
8291

8392
public int CacheIntervalsSize { get; }
8493

85-
public float[] Costs { get; }
94+
public IMemoryOwner<float> Costs { get; }
8695

87-
public ushort[] DistArray { get; }
96+
public IMemoryOwner<ushort> DistArray { get; }
8897

8998
public List<CostCacheInterval> CacheIntervals { get; }
9099

@@ -128,17 +137,19 @@ public void PushInterval(double distanceCost, int position, int len)
128137
// interval logic, just serialize it right away. This constant is empirical.
129138
int skipDistance = 10;
130139

140+
Span<float> costs = this.Costs.GetSpan();
141+
Span<ushort> distArray = this.DistArray.GetSpan();
131142
if (len < skipDistance)
132143
{
133144
for (int j = position; j < position + len; j++)
134145
{
135146
int k = j - position;
136147
float costTmp = (float)(distanceCost + this.CostCache[k]);
137148

138-
if (this.Costs[j] > costTmp)
149+
if (costs[j] > costTmp)
139150
{
140-
this.Costs[j] = costTmp;
141-
this.DistArray[j] = (ushort)(k + 1);
151+
costs[j] = costTmp;
152+
distArray[j] = (ushort)(k + 1);
142153
}
143154
}
144155

@@ -201,10 +212,8 @@ public void PushInterval(double distanceCost, int position, int len)
201212
this.InsertInterval(interval, interval.Cost, interval.Index, end, endOriginal);
202213
break;
203214
}
204-
else
205-
{
206-
interval.End = start;
207-
}
215+
216+
interval.End = start;
208217
}
209218
}
210219

@@ -226,6 +235,10 @@ private void PopInterval(CostInterval interval)
226235

227236
this.ConnectIntervals(interval.Previous, interval.Next);
228237
this.Count--;
238+
239+
interval.Next = null;
240+
interval.Previous = null;
241+
this.freeIntervals.Push(interval);
229242
}
230243

231244
private void InsertInterval(CostInterval intervalIn, float cost, int position, int start, int end)
@@ -236,13 +249,19 @@ private void InsertInterval(CostInterval intervalIn, float cost, int position, i
236249
}
237250

238251
// TODO: should we use COST_CACHE_INTERVAL_SIZE_MAX?
239-
var intervalNew = new CostInterval()
252+
CostInterval intervalNew;
253+
if (this.freeIntervals.Count > 0)
240254
{
241-
Cost = cost,
242-
Start = start,
243-
End = end,
244-
Index = position
245-
};
255+
intervalNew = this.freeIntervals.Pop();
256+
intervalNew.Cost = cost;
257+
intervalNew.Start = start;
258+
intervalNew.End = end;
259+
intervalNew.Index = position;
260+
}
261+
else
262+
{
263+
intervalNew = new CostInterval() { Cost = cost, Start = start, End = end, Index = position };
264+
}
246265

247266
this.PositionOrphanInterval(intervalNew, intervalIn);
248267
this.Count++;
@@ -297,12 +316,17 @@ private void ConnectIntervals(CostInterval prev, CostInterval next)
297316
/// </summary>
298317
private void UpdateCost(int i, int position, float cost)
299318
{
319+
Span<float> costs = this.Costs.GetSpan();
320+
Span<ushort> distArray = this.DistArray.GetSpan();
300321
int k = i - position;
301-
if (this.Costs[i] > cost)
322+
if (costs[i] > cost)
302323
{
303-
this.Costs[i] = cost;
304-
this.DistArray[i] = (ushort)(k + 1);
324+
costs[i] = cost;
325+
distArray[i] = (ushort)(k + 1);
305326
}
306327
}
328+
329+
/// <inheritdoc />
330+
public void Dispose() => this.Costs.Dispose();
307331
}
308332
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
1313
/// - UsePackedTable: few enough literal symbols, so all the bit codes can fit into a small look-up table PackedTable[]
1414
/// The common literal base, if applicable, is stored in 'LiteralArb'.
1515
/// </summary>
16-
internal class HTreeGroup
16+
internal struct HTreeGroup
1717
{
1818
public HTreeGroup(uint packedTableSize)
1919
{
2020
this.HTrees = new List<HuffmanCode[]>(WebpConstants.HuffmanCodesPerMetaCode);
2121
this.PackedTable = new HuffmanCode[packedTableSize];
22-
for (int i = 0; i < packedTableSize; i++)
23-
{
24-
this.PackedTable[i] = new HuffmanCode();
25-
}
22+
this.IsTrivialCode = false;
23+
this.IsTrivialLiteral = false;
24+
this.LiteralArb = 0;
25+
this.UsePackedTable = false;
2626
}
2727

2828
/// <summary>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
99
/// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes.
1010
/// </summary>
1111
[DebuggerDisplay("BitsUsed: {BitsUsed}, Value: {Value}")]
12-
internal class HuffmanCode
12+
internal struct HuffmanCode
1313
{
1414
/// <summary>
1515
/// Gets or sets the number of bits used for this symbol.

0 commit comments

Comments
 (0)