Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebP - Reduce the allocations in lossless encoding #2546

Merged
merged 10 commits into from
Nov 6, 2023
58 changes: 31 additions & 27 deletions src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ public static Vp8LBackwardRefs GetBackwardReferences(
double bitCostBest = -1;
int cacheBitsInitial = cacheBits;
Vp8LHashChain? hashChainBox = null;
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
Vp8LStreaks stats = new();
Vp8LBitEntropy bitsEntropy = new();

ColorCache[] colorCache = new ColorCache[WebpConstants.MaxColorCacheBits + 1];
for (int lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1)
{
int cacheBitsTmp = cacheBitsInitial;
Expand All @@ -76,21 +78,19 @@ public static Vp8LBackwardRefs GetBackwardReferences(
}

// Next, try with a color cache and update the references.
cacheBitsTmp = CalculateBestCacheSize(bgra, quality, worst, cacheBitsTmp);
cacheBitsTmp = CalculateBestCacheSize(memoryAllocator, colorCache, bgra, quality, worst, cacheBitsTmp);
if (cacheBitsTmp > 0)
{
BackwardRefsWithLocalCache(bgra, cacheBitsTmp, worst);
}

// Keep the best backward references.
var histo = new Vp8LHistogram(worst, cacheBitsTmp);
using Vp8LHistogram histo = Vp8LHistogram.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;
Expand All @@ -102,7 +102,7 @@ public static Vp8LBackwardRefs GetBackwardReferences(
{
Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox!;
BackwardReferencesTraceBackwards(width, height, memoryAllocator, bgra, cacheBits, hashChainTmp, best, worst);
var histo = new Vp8LHistogram(worst, cacheBits);
using Vp8LHistogram histo = Vp8LHistogram.Create(memoryAllocator, worst, cacheBits);
double bitCostTrace = histo.EstimateBits(stats, bitsEntropy);
if (bitCostTrace < bitCostBest)
{
Expand All @@ -123,7 +123,13 @@ public static Vp8LBackwardRefs GetBackwardReferences(
/// 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)
Expand All @@ -134,11 +140,11 @@ private static int CalculateBestCacheSize(ReadOnlySpan<uint> bgra, uint quality,

double entropyMin = MaxEntropy;
int pos = 0;
var colorCache = new ColorCache[WebpConstants.MaxColorCacheBits + 1];
var histos = new Vp8LHistogram[WebpConstants.MaxColorCacheBits + 1];
for (int i = 0; i <= WebpConstants.MaxColorCacheBits; i++)

using Vp8LHistogramSet histos = new(memoryAllocator, colorCache.Length, 0);
for (int i = 0; i < colorCache.Length; i++)
{
histos[i] = new Vp8LHistogram(paletteCodeBits: i);
histos[i].PaletteCodeBits = i;
colorCache[i] = new ColorCache(i);
}

Expand All @@ -149,10 +155,10 @@ private static int CalculateBestCacheSize(ReadOnlySpan<uint> bgra, uint quality,
if (v.IsLiteral())
{
uint pix = bgra[pos++];
uint a = (pix >> 24) & 0xff;
uint r = (pix >> 16) & 0xff;
uint g = (pix >> 8) & 0xff;
uint b = (pix >> 0) & 0xff;
int a = (int)(pix >> 24) & 0xff;
int r = (int)(pix >> 16) & 0xff;
int g = (int)(pix >> 8) & 0xff;
int b = (int)(pix >> 0) & 0xff;

// The keys of the caches can be derived from the longest one.
int key = ColorCache.HashPix(pix, 32 - cacheBitsMax);
Expand Down Expand Up @@ -218,8 +224,8 @@ private static int CalculateBestCacheSize(ReadOnlySpan<uint> bgra, uint quality,
}
}

var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
Vp8LStreaks stats = new();
Vp8LBitEntropy bitsEntropy = new();
for (int i = 0; i <= cacheBitsMax; i++)
{
double entropy = histos[i].EstimateBits(stats, bitsEntropy);
Expand Down Expand Up @@ -266,7 +272,7 @@ private static void BackwardReferencesHashChainDistanceOnly(
int pixCount = xSize * ySize;
bool useColorCache = cacheBits > 0;
int literalArraySize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (cacheBits > 0 ? 1 << cacheBits : 0);
var costModel = new CostModel(literalArraySize);
CostModel costModel = new(memoryAllocator, literalArraySize);
int offsetPrev = -1;
int lenPrev = -1;
double offsetCost = -1;
Expand All @@ -280,7 +286,7 @@ private static void BackwardReferencesHashChainDistanceOnly(
}

costModel.Build(xSize, cacheBits, refs);
using var costManager = new CostManager(memoryAllocator, distArrayBuffer, pixCount, costModel);
using CostManager costManager = new(memoryAllocator, distArrayBuffer, pixCount, costModel);
Span<float> costManagerCosts = costManager.Costs.GetSpan();
Span<ushort> distArray = distArrayBuffer.GetSpan();

Expand Down Expand Up @@ -441,12 +447,12 @@ private static void AddSingleLiteralWithCostModel(
int ix = useColorCache ? colorCache!.Contains(color) : -1;
if (ix >= 0)
{
double mul0 = 0.68;
const double mul0 = 0.68;
costVal += costModel.GetCacheCost((uint)ix) * mul0;
}
else
{
double mul1 = 0.82;
const double mul1 = 0.82;
if (useColorCache)
{
colorCache!.Insert(color);
Expand Down Expand Up @@ -693,10 +699,8 @@ private static void BackwardReferencesLz77Box(int xSize, int ySize, ReadOnlySpan
bestLength = MaxLength;
break;
}
else
{
bestLength = currLength;
}

bestLength = currLength;
}
}
}
Expand Down
16 changes: 10 additions & 6 deletions src/ImageSharp/Formats/Webp/Lossless/CostModel.cs
Original file line number Diff line number Diff line change
@@ -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];
Expand All @@ -32,13 +37,12 @@ public CostModel(int literalArraySize)

public void Build(int xSize, int cacheBits, Vp8LBackwardRefs backwardRefs)
{
var histogram = new Vp8LHistogram(cacheBits);
using System.Collections.Generic.List<PixOrCopy>.Enumerator refsEnumerator = backwardRefs.Refs.GetEnumerator();
using Vp8LHistogram histogram = Vp8LHistogram.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);
Expand Down Expand Up @@ -70,7 +74,7 @@ public double GetCacheCost(uint idx)

public double GetLiteralCost(uint v) => this.Alpha[v >> 24] + this.Red[(v >> 16) & 0xff] + this.Literal[(v >> 8) & 0xff] + this.Blue[v & 0xff];

private static void ConvertPopulationCountTableToBitEstimates(int numSymbols, uint[] populationCounts, double[] output)
private static void ConvertPopulationCountTableToBitEstimates(int numSymbols, Span<uint> populationCounts, double[] output)
{
uint sum = 0;
int nonzeros = 0;
Expand Down
Loading
Loading