Skip to content

Commit

Permalink
Merge pull request #2546 from SixLabors/js/webp-allocations
Browse files Browse the repository at this point in the history
WebP - Reduce the allocations in lossless encoding
  • Loading branch information
JimBobSquarePants authored Nov 6, 2023
2 parents 0888e54 + c51f5ae commit 7e1ee9e
Show file tree
Hide file tree
Showing 12 changed files with 571 additions and 316 deletions.
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 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;
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 OwnedVp8LHistogram histo = OwnedVp8LHistogram.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 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);
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

0 comments on commit 7e1ee9e

Please sign in to comment.