Browse Source

Reduce allocations

pull/1799/head
Brian Popow 5 years ago
parent
commit
70c99d3d02
  1. 10
      src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs
  2. 41
      src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs
  3. 9
      src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs
  4. 2
      src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs
  5. 6
      src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs
  6. 123
      src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs
  7. 29
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  8. 57
      src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs
  9. 9
      src/ImageSharp/Formats/Webp/Lossless/Vp8LStreaks.cs
  10. 3
      src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
  11. 51
      src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs
  12. 86
      src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs
  13. 27
      src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs
  14. 18
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  15. 54
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs
  16. 23
      src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs
  17. 18
      src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs
  18. 5
      src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs
  19. 30
      src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs
  20. 6
      tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs

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

@ -49,6 +49,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
double bitCostBest = -1; double bitCostBest = -1;
int cacheBitsInitial = cacheBits; int cacheBitsInitial = cacheBits;
Vp8LHashChain hashChainBox = null; Vp8LHashChain hashChainBox = null;
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
for (int lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1) for (int lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1)
{ {
int cacheBitsTmp = cacheBitsInitial; int cacheBitsTmp = cacheBitsInitial;
@ -81,7 +83,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
// Keep the best backward references. // Keep the best backward references.
var histo = new Vp8LHistogram(worst, cacheBitsTmp); var histo = new Vp8LHistogram(worst, cacheBitsTmp);
double bitCost = histo.EstimateBits(); double bitCost = histo.EstimateBits(stats, bitsEntropy);
if (lz77TypeBest == 0 || bitCost < bitCostBest) if (lz77TypeBest == 0 || bitCost < bitCostBest)
{ {
@ -100,7 +102,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox; Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox;
BackwardReferencesTraceBackwards(width, height, bgra, cacheBits, hashChainTmp, best, worst); BackwardReferencesTraceBackwards(width, height, bgra, cacheBits, hashChainTmp, best, worst);
var histo = new Vp8LHistogram(worst, cacheBits); var histo = new Vp8LHistogram(worst, cacheBits);
double bitCostTrace = histo.EstimateBits(); double bitCostTrace = histo.EstimateBits(stats, bitsEntropy);
if (bitCostTrace < bitCostBest) if (bitCostTrace < bitCostBest)
{ {
best = worst; best = worst;
@ -214,9 +216,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
} }
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
for (int i = 0; i <= cacheBitsMax; i++) for (int i = 0; i <= cacheBitsMax; i++)
{ {
double entropy = histos[i].EstimateBits(); double entropy = histos[i].EstimateBits(stats, bitsEntropy);
if (i == 0 || entropy < entropyMin) if (i == 0 || entropy < entropyMin)
{ {
entropyMin = entropy; entropyMin = entropy;

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

@ -152,10 +152,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
private static int HistogramCopyAndAnalyze(List<Vp8LHistogram> origHistograms, List<Vp8LHistogram> histograms, ushort[] histogramSymbols) 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++) for (int clusterId = 0, i = 0; i < origHistograms.Count; i++)
{ {
Vp8LHistogram origHistogram = origHistograms[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). // 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]) if (!origHistogram.IsUsed[0] && !origHistogram.IsUsed[1] && !origHistogram.IsUsed[2] && !origHistogram.IsUsed[3] && !origHistogram.IsUsed[4])
@ -175,7 +177,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
return numUsed; 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]; var binInfo = new HistogramBinInfo[BinSize];
for (int idx = 0; idx < numBins; idx++) for (int idx = 0; idx < numBins; idx++)
@ -191,6 +200,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
var indicesToRemove = new List<int>(); var indicesToRemove = new List<int>();
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
for (int idx = 0; idx < histograms.Count; idx++) for (int idx = 0; idx < histograms.Count; idx++)
{ {
if (histograms[idx] == null) if (histograms[idx] == null)
@ -209,7 +220,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
// Try to merge #idx into #first (both share the same binId) // Try to merge #idx into #first (both share the same binId)
double bitCost = histograms[idx].BitCost; double bitCost = histograms[idx].BitCost;
double bitCostThresh = -bitCost * combineCostFactor; 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) if (currCostDiff < bitCostThresh)
{ {
@ -308,6 +319,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int numUsed = histograms.Count(h => h != null); int numUsed = histograms.Count(h => h != null);
int outerIters = numUsed; int outerIters = numUsed;
int numTriesNoSuccess = outerIters / 2; int numTriesNoSuccess = outerIters / 2;
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
if (numUsed < minClusterSize) if (numUsed < minClusterSize)
{ {
@ -354,7 +367,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
idx2 = mappings[idx2]; idx2 = mappings[idx2];
// Calculate cost reduction on combination. // 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? // Found a better pair?
if (currCost < 0) if (currCost < 0)
@ -428,7 +441,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
if (doEval) if (doEval)
{ {
// Re-evaluate the cost of an updated pair. // 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) if (p.CostDiff >= 0.0d)
{ {
histoPriorityList[j] = histoPriorityList[histoPriorityList.Count - 1]; histoPriorityList[j] = histoPriorityList[histoPriorityList.Count - 1];
@ -456,6 +469,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
// Priority list of histogram pairs. // Priority list of histogram pairs.
var histoPriorityList = new List<HistogramPair>(); var histoPriorityList = new List<HistogramPair>();
int maxSize = histoSize * histoSize; int maxSize = histoSize * histoSize;
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
for (int i = 0; i < histoSize; i++) for (int i = 0; i < histoSize; i++)
{ {
@ -471,7 +486,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
continue; continue;
} }
HistoPriorityListPush(histoPriorityList, maxSize, histograms, i, j, 0.0d); HistoPriorityListPush(histoPriorityList, maxSize, histograms, i, j, 0.0d, stats, bitsEntropy);
} }
} }
@ -510,7 +525,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
continue; continue;
} }
HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, i, 0.0d); HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, i, 0.0d, stats, bitsEntropy);
} }
} }
} }
@ -519,6 +534,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
int inSize = input.Count; int inSize = input.Count;
int outSize = output.Count; int outSize = output.Count;
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
if (outSize > 1) if (outSize > 1)
{ {
for (int i = 0; i < inSize; i++) for (int i = 0; i < inSize; i++)
@ -534,7 +551,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
double bestBits = double.MaxValue; double bestBits = double.MaxValue;
for (int k = 0; k < outSize; k++) 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) if (k == 0 || curBits < bestBits)
{ {
bestBits = curBits; bestBits = curBits;
@ -577,7 +594,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// Create a pair from indices "idx1" and "idx2" provided its cost is inferior to "threshold", a negative entropy. /// Create a pair from indices "idx1" and "idx2" provided its cost is inferior to "threshold", a negative entropy.
/// </summary> /// </summary>
/// <returns>The cost of the pair, or 0 if it superior to threshold.</returns> /// <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(); var pair = new HistogramPair();
@ -598,7 +615,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
Vp8LHistogram h1 = histograms[idx1]; Vp8LHistogram h1 = histograms[idx1];
Vp8LHistogram h2 = histograms[idx2]; 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. // Do not even consider the pair if it does not improve the entropy.
if (pair.CostDiff >= threshold) if (pair.CostDiff >= threshold)
@ -616,11 +633,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// <summary> /// <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. /// 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> /// </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; double sumCost = h1.BitCost + h2.BitCost;
pair.CostCombo = 0.0d; 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.CostCombo = cost;
pair.CostDiff = pair.CostCombo - sumCost; pair.CostDiff = pair.CostCombo - sumCost;
} }

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

@ -49,14 +49,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
return -1; return -1;
} }
else if (t1.TotalCount < t2.TotalCount)
if (t1.TotalCount < t2.TotalCount)
{ {
return 1; return 1;
} }
else
{ return t1.Value < t2.Value ? -1 : 1;
return t1.Value < t2.Value ? -1 : 1;
}
} }
public IDeepCloneable DeepClone() => new HuffmanTree(this); public IDeepCloneable DeepClone() => new HuffmanTree(this);

2
src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs

@ -704,7 +704,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// Compute the combined Shanon's entropy for distribution {X} and {X+Y}. /// Compute the combined Shanon's entropy for distribution {X} and {X+Y}.
/// </summary> /// </summary>
/// <returns>Shanon entropy.</returns> /// <returns>Shanon entropy.</returns>
public static float CombinedShannonEntropy(int[] x, int[] y) public static float CombinedShannonEntropy(Span<int> x, Span<int> y)
{ {
double retVal = 0.0d; double retVal = 0.0d;
uint sumX = 0, sumXY = 0; uint sumX = 0, sumXY = 0;

6
src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
public uint BgraOrDistance { get; set; } public uint BgraOrDistance { get; set; }
public static PixOrCopy CreateCacheIdx(int idx) => public static PixOrCopy CreateCacheIdx(int idx) =>
new PixOrCopy() new()
{ {
Mode = PixOrCopyMode.CacheIdx, Mode = PixOrCopyMode.CacheIdx,
BgraOrDistance = (uint)idx, BgraOrDistance = (uint)idx,
@ -23,14 +23,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
}; };
public static PixOrCopy CreateLiteral(uint bgra) => public static PixOrCopy CreateLiteral(uint bgra) =>
new PixOrCopy() new()
{ {
Mode = PixOrCopyMode.Literal, Mode = PixOrCopyMode.Literal,
BgraOrDistance = bgra, BgraOrDistance = bgra,
Len = 1 Len = 1
}; };
public static PixOrCopy CreateCopy(uint distance, ushort len) => new PixOrCopy() public static PixOrCopy CreateCopy(uint distance, ushort len) => new()
{ {
Mode = PixOrCopyMode.Copy, Mode = PixOrCopyMode.Copy,
BgraOrDistance = distance, BgraOrDistance = distance,

123
src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs

@ -17,6 +17,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// </summary> /// </summary>
internal static unsafe class PredictorEncoder internal static unsafe class PredictorEncoder
{ {
private static readonly sbyte[] DeltaLut = { 16, 16, 8, 4, 2, 2, 2 };
private static readonly sbyte[][] Offset =
{
new sbyte[] { 0, -1 }, new sbyte[] { 0, 1 }, new sbyte[] { -1, 0 }, new sbyte[] { 1, 0 }, new sbyte[] { -1, -1 }, new sbyte[] { -1, 1 }, new sbyte[] { 1, -1 }, new sbyte[] { 1, 1 }
};
private const int GreenRedToBlueNumAxis = 8; private const int GreenRedToBlueNumAxis = 8;
private const int GreenRedToBlueMaxIters = 7; private const int GreenRedToBlueMaxIters = 7;
@ -41,6 +48,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
Span<uint> bgra, Span<uint> bgra,
Span<uint> bgraScratch, Span<uint> bgraScratch,
Span<uint> image, Span<uint> image,
int[][] histoArgb,
int[][] bestHisto,
bool nearLossless, bool nearLossless,
int nearLosslessQuality, int nearLosslessQuality,
WebpTransparentColorMode transparentColorMode, WebpTransparentColorMode transparentColorMode,
@ -80,6 +89,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
histo, histo,
bgraScratch, bgraScratch,
bgra, bgra,
histoArgb,
bestHisto,
maxQuantization, maxQuantization,
transparentColorMode, transparentColorMode,
usedSubtractGreen, usedSubtractGreen,
@ -105,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
lowEffort); lowEffort);
} }
public static void ColorSpaceTransform(int width, int height, int bits, int quality, Span<uint> bgra, Span<uint> image) public static void ColorSpaceTransform(int width, int height, int bits, int quality, Span<uint> bgra, Span<uint> image, Span<int> scratch)
{ {
int maxTileSize = 1 << bits; int maxTileSize = 1 << bits;
int tileXSize = LosslessUtils.SubSampleSize(width, bits); int tileXSize = LosslessUtils.SubSampleSize(width, bits);
@ -139,7 +150,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
height, height,
accumulatedRedHisto, accumulatedRedHisto,
accumulatedBlueHisto, accumulatedBlueHisto,
bgra); bgra,
scratch);
image[offset] = MultipliersToColorCode(prevX); image[offset] = MultipliersToColorCode(prevX);
CopyTileWithColorTransform(width, height, tileXOffset, tileYOffset, maxTileSize, prevX, bgra); CopyTileWithColorTransform(width, height, tileXOffset, tileYOffset, maxTileSize, prevX, bgra);
@ -188,6 +200,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int[][] accumulated, int[][] accumulated,
Span<uint> argbScratch, Span<uint> argbScratch,
Span<uint> argb, Span<uint> argb,
int[][] histoArgb,
int[][] bestHisto,
int maxQuantization, int maxQuantization,
WebpTransparentColorMode transparentColorMode, WebpTransparentColorMode transparentColorMode,
bool usedSubtractGreen, bool usedSubtractGreen,
@ -222,21 +236,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
float bestDiff = MaxDiffCost; float bestDiff = MaxDiffCost;
int bestMode = 0; int bestMode = 0;
uint[] residuals = new uint[1 << WebpConstants.MaxTransformBits]; uint[] residuals = new uint[1 << WebpConstants.MaxTransformBits];
int[][] histoArgb = new int[4][];
int[][] bestHisto = new int[4][];
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
histoArgb[i] = new int[256]; histoArgb[i].AsSpan().Clear();
bestHisto[i] = new int[256]; bestHisto[i].AsSpan().Clear();
} }
for (int mode = 0; mode < numPredModes; mode++) for (int mode = 0; mode < numPredModes; mode++)
{ {
for (int i = 0; i < 4; i++)
{
histoArgb[i].AsSpan().Fill(0);
}
if (startY > 0) if (startY > 0)
{ {
// Read the row above the tile which will become the first upper_row. // Read the row above the tile which will become the first upper_row.
@ -300,6 +307,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
bestDiff = curDiff; bestDiff = curDiff;
bestMode = mode; bestMode = mode;
} }
for (int i = 0; i < 4; i++)
{
histoArgb[i].AsSpan().Clear();
}
} }
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
@ -819,7 +831,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
} }
private static Vp8LMultipliers GetBestColorTransformForTile(int tileX, int tileY, int bits, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int quality, int xSize, int ySize, int[] accumulatedRedHisto, int[] accumulatedBlueHisto, Span<uint> argb) private static Vp8LMultipliers GetBestColorTransformForTile(
int tileX,
int tileY,
int bits,
Vp8LMultipliers prevX,
Vp8LMultipliers prevY,
int quality,
int xSize,
int ySize,
int[] accumulatedRedHisto,
int[] accumulatedBlueHisto,
Span<uint> argb,
Span<int> scratch)
{ {
int maxTileSize = 1 << bits; int maxTileSize = 1 << bits;
int tileYOffset = tileY * maxTileSize; int tileYOffset = tileY * maxTileSize;
@ -832,18 +856,28 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
var bestTx = default(Vp8LMultipliers); var bestTx = default(Vp8LMultipliers);
GetBestGreenToRed(tileArgb, xSize, tileWidth, tileHeight, prevX, prevY, quality, accumulatedRedHisto, ref bestTx); GetBestGreenToRed(tileArgb, xSize, scratch, tileWidth, tileHeight, prevX, prevY, quality, accumulatedRedHisto, ref bestTx);
GetBestGreenRedToBlue(tileArgb, xSize, tileWidth, tileHeight, prevX, prevY, quality, accumulatedBlueHisto, ref bestTx); GetBestGreenRedToBlue(tileArgb, xSize, scratch, tileWidth, tileHeight, prevX, prevY, quality, accumulatedBlueHisto, ref bestTx);
return bestTx; return bestTx;
} }
private static void GetBestGreenToRed(Span<uint> argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int quality, int[] accumulatedRedHisto, ref Vp8LMultipliers bestTx) private static void GetBestGreenToRed(
Span<uint> argb,
int stride,
Span<int> scratch,
int tileWidth,
int tileHeight,
Vp8LMultipliers prevX,
Vp8LMultipliers prevY,
int quality,
int[] accumulatedRedHisto,
ref Vp8LMultipliers bestTx)
{ {
int maxIters = 4 + ((7 * quality) >> 8); // in range [4..6] int maxIters = 4 + ((7 * quality) >> 8); // in range [4..6]
int greenToRedBest = 0; int greenToRedBest = 0;
double bestDiff = GetPredictionCostCrossColorRed(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToRedBest, accumulatedRedHisto); double bestDiff = GetPredictionCostCrossColorRed(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToRedBest, accumulatedRedHisto);
for (int iter = 0; iter < maxIters; iter++) for (int iter = 0; iter < maxIters; iter++)
{ {
// ColorTransformDelta is a 3.5 bit fixed point, so 32 is equal to // ColorTransformDelta is a 3.5 bit fixed point, so 32 is equal to
@ -855,7 +889,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
for (int offset = -delta; offset <= delta; offset += 2 * delta) for (int offset = -delta; offset <= delta; offset += 2 * delta)
{ {
int greenToRedCur = offset + greenToRedBest; int greenToRedCur = offset + greenToRedBest;
double curDiff = GetPredictionCostCrossColorRed(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToRedCur, accumulatedRedHisto); double curDiff = GetPredictionCostCrossColorRed(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToRedCur, accumulatedRedHisto);
if (curDiff < bestDiff) if (curDiff < bestDiff)
{ {
bestDiff = curDiff; bestDiff = curDiff;
@ -867,24 +901,22 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
bestTx.GreenToRed = (byte)(greenToRedBest & 0xff); bestTx.GreenToRed = (byte)(greenToRedBest & 0xff);
} }
private static void GetBestGreenRedToBlue(Span<uint> argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int quality, int[] accumulatedBlueHisto, ref Vp8LMultipliers bestTx) private static void GetBestGreenRedToBlue(Span<uint> argb, int stride, Span<int> scratch, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int quality, int[] accumulatedBlueHisto, ref Vp8LMultipliers bestTx)
{ {
int iters = (quality < 25) ? 1 : (quality > 50) ? GreenRedToBlueMaxIters : 4; int iters = (quality < 25) ? 1 : (quality > 50) ? GreenRedToBlueMaxIters : 4;
int greenToBlueBest = 0; int greenToBlueBest = 0;
int redToBlueBest = 0; int redToBlueBest = 0;
sbyte[][] offset = { new sbyte[] { 0, -1 }, new sbyte[] { 0, 1 }, new sbyte[] { -1, 0 }, new sbyte[] { 1, 0 }, new sbyte[] { -1, -1 }, new sbyte[] { -1, 1 }, new sbyte[] { 1, -1 }, new sbyte[] { 1, 1 } };
sbyte[] deltaLut = { 16, 16, 8, 4, 2, 2, 2 };
// Initial value at origin: // Initial value at origin:
double bestDiff = GetPredictionCostCrossColorBlue(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToBlueBest, redToBlueBest, accumulatedBlueHisto); double bestDiff = GetPredictionCostCrossColorBlue(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToBlueBest, redToBlueBest, accumulatedBlueHisto);
for (int iter = 0; iter < iters; iter++) for (int iter = 0; iter < iters; iter++)
{ {
int delta = deltaLut[iter]; int delta = DeltaLut[iter];
for (int axis = 0; axis < GreenRedToBlueNumAxis; axis++) for (int axis = 0; axis < GreenRedToBlueNumAxis; axis++)
{ {
int greenToBlueCur = (offset[axis][0] * delta) + greenToBlueBest; int greenToBlueCur = (Offset[axis][0] * delta) + greenToBlueBest;
int redToBlueCur = (offset[axis][1] * delta) + redToBlueBest; int redToBlueCur = (Offset[axis][1] * delta) + redToBlueBest;
double curDiff = GetPredictionCostCrossColorBlue(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToBlueCur, redToBlueCur, accumulatedBlueHisto); double curDiff = GetPredictionCostCrossColorBlue(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToBlueCur, redToBlueCur, accumulatedBlueHisto);
if (curDiff < bestDiff) if (curDiff < bestDiff)
{ {
bestDiff = curDiff; bestDiff = curDiff;
@ -910,9 +942,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
bestTx.RedToBlue = (byte)(redToBlueBest & 0xff); bestTx.RedToBlue = (byte)(redToBlueBest & 0xff);
} }
private static double GetPredictionCostCrossColorRed(Span<uint> argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int greenToRed, int[] accumulatedRedHisto) private static double GetPredictionCostCrossColorRed(
Span<uint> argb,
int stride,
Span<int> scratch,
int tileWidth,
int tileHeight,
Vp8LMultipliers prevX,
Vp8LMultipliers prevY,
int greenToRed,
int[] accumulatedRedHisto)
{ {
int[] histo = new int[256]; Span<int> histo = scratch.Slice(0, 256);
histo.Clear();
CollectColorRedTransforms(argb, stride, tileWidth, tileHeight, greenToRed, histo); CollectColorRedTransforms(argb, stride, tileWidth, tileHeight, greenToRed, histo);
double curDiff = PredictionCostCrossColor(accumulatedRedHisto, histo); double curDiff = PredictionCostCrossColor(accumulatedRedHisto, histo);
@ -937,9 +979,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
return curDiff; return curDiff;
} }
private static double GetPredictionCostCrossColorBlue(Span<uint> argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int greenToBlue, int redToBlue, int[] accumulatedBlueHisto) private static double GetPredictionCostCrossColorBlue(
Span<uint> argb,
int stride,
Span<int> scratch,
int tileWidth,
int tileHeight,
Vp8LMultipliers prevX,
Vp8LMultipliers prevY,
int greenToBlue,
int redToBlue,
int[] accumulatedBlueHisto)
{ {
int[] histo = new int[256]; Span<int> histo = scratch.Slice(0, 256);
histo.Clear();
CollectColorBlueTransforms(argb, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo); CollectColorBlueTransforms(argb, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo);
double curDiff = PredictionCostCrossColor(accumulatedBlueHisto, histo); double curDiff = PredictionCostCrossColor(accumulatedBlueHisto, histo);
@ -980,7 +1033,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
return curDiff; return curDiff;
} }
private static void CollectColorRedTransforms(Span<uint> bgra, int stride, int tileWidth, int tileHeight, int greenToRed, int[] histo) private static void CollectColorRedTransforms(Span<uint> bgra, int stride, int tileWidth, int tileHeight, int greenToRed, Span<int> histo)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
if (Sse41.IsSupported) if (Sse41.IsSupported)
@ -1036,7 +1089,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
} }
private static void CollectColorRedTransformsNoneVectorized(Span<uint> bgra, int stride, int tileWidth, int tileHeight, int greenToRed, int[] histo) private static void CollectColorRedTransformsNoneVectorized(Span<uint> bgra, int stride, int tileWidth, int tileHeight, int greenToRed, Span<int> histo)
{ {
int pos = 0; int pos = 0;
while (tileHeight-- > 0) while (tileHeight-- > 0)
@ -1051,7 +1104,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
} }
private static void CollectColorBlueTransforms(Span<uint> bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, int[] histo) private static void CollectColorBlueTransforms(Span<uint> bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span<int> histo)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
if (Sse41.IsSupported) if (Sse41.IsSupported)
@ -1114,7 +1167,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
} }
private static void CollectColorBlueTransformsNoneVectorized(Span<uint> bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, int[] histo) private static void CollectColorBlueTransformsNoneVectorized(Span<uint> bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span<int> histo)
{ {
int pos = 0; int pos = 0;
while (tileHeight-- > 0) while (tileHeight-- > 0)
@ -1143,7 +1196,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static double PredictionCostCrossColor(int[] accumulated, int[] counts) private static double PredictionCostCrossColor(int[] accumulated, Span<int> counts)
{ {
// Favor low entropy, locally and globally. // Favor low entropy, locally and globally.
// Favor small absolute values for PredictionCostSpatial. // Favor small absolute values for PredictionCostSpatial.
@ -1152,7 +1205,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static float PredictionCostSpatial(int[] counts, int weight0, double expVal) private static float PredictionCostSpatial(Span<int> counts, int weight0, double expVal)
{ {
int significantSymbols = 256 >> 4; int significantSymbols = 256 >> 4;
double expDecayFactor = 0.6; double expDecayFactor = 0.6;

29
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

@ -19,6 +19,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// </summary> /// </summary>
internal class Vp8LEncoder : IDisposable internal class Vp8LEncoder : IDisposable
{ {
/// <summary>
/// Scratch buffer to reduce allocations.
/// </summary>
private readonly int[] scratch = new int[256];
private int[][] histoArgb = { new int[256], new int[256], new int[256], new int[256] };
private int[][] bestHisto = { new int[256], new int[256], new int[256], new int[256] };
/// <summary> /// <summary>
/// The <see cref="MemoryAllocator"/> to use for buffer allocations. /// The <see cref="MemoryAllocator"/> to use for buffer allocations.
/// </summary> /// </summary>
@ -76,6 +85,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
private const int PaletteInvSize = 1 << PaletteInvSizeBits; private const int PaletteInvSize = 1 << PaletteInvSizeBits;
private static readonly byte[] Order = { 1, 2, 0, 3 };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Vp8LEncoder"/> class. /// Initializes a new instance of the <see cref="Vp8LEncoder"/> class.
/// </summary> /// </summary>
@ -675,6 +686,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.EncodedData.GetSpan(), this.EncodedData.GetSpan(),
this.BgraScratch.GetSpan(), this.BgraScratch.GetSpan(),
this.TransformData.GetSpan(), this.TransformData.GetSpan(),
this.histoArgb,
this.bestHisto,
this.nearLossless, this.nearLossless,
nearLosslessStrength, nearLosslessStrength,
this.transparentColorMode, this.transparentColorMode,
@ -694,7 +707,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits); int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits);
int transformHeight = LosslessUtils.SubSampleSize(height, colorTransformBits); int transformHeight = LosslessUtils.SubSampleSize(height, colorTransformBits);
PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, this.quality, this.EncodedData.GetSpan(), this.TransformData.GetSpan()); PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, this.quality, this.EncodedData.GetSpan(), this.TransformData.GetSpan(), this.scratch);
this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits(WebpConstants.TransformPresent, 1);
this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2); this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2);
@ -736,7 +749,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
var histogramImage = new List<Vp8LHistogram>() var histogramImage = new List<Vp8LHistogram>()
{ {
new Vp8LHistogram(cacheBits) new(cacheBits)
}; };
// Build histogram image and symbols from backward references. // Build histogram image and symbols from backward references.
@ -780,7 +793,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
private void StoreHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode) private void StoreHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode)
{ {
int count = 0; int count = 0;
int[] symbols = { 0, 0 }; Span<int> symbols = this.scratch.AsSpan(0, 2);
symbols.Clear();
int maxBits = 8; int maxBits = 8;
int maxSymbol = 1 << maxBits; int maxSymbol = 1 << maxBits;
@ -973,10 +987,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
if (v.IsLiteral()) if (v.IsLiteral())
{ {
byte[] order = { 1, 2, 0, 3 };
for (int k = 0; k < 4; k++) for (int k = 0; k < 4; k++)
{ {
int code = (int)v.Literal(order[k]); int code = (int)v.Literal(Order[k]);
this.bitWriter.WriteHuffmanCode(codes[k], code); this.bitWriter.WriteHuffmanCode(codes[k], code);
} }
} }
@ -1092,9 +1105,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
histo[(int)HistoIx.HistoBluePred * 256]++; histo[(int)HistoIx.HistoBluePred * 256]++;
histo[(int)HistoIx.HistoAlphaPred * 256]++; histo[(int)HistoIx.HistoAlphaPred * 256]++;
var bitEntropy = new Vp8LBitEntropy();
for (int j = 0; j < (int)HistoIx.HistoTotal; j++) for (int j = 0; j < (int)HistoIx.HistoTotal; j++)
{ {
var bitEntropy = new Vp8LBitEntropy(); bitEntropy.Init();
Span<uint> curHisto = histo.Slice(j * 256, 256); Span<uint> curHisto = histo.Slice(j * 256, 256);
bitEntropy.BitsEntropyUnrefined(curHisto, 256); bitEntropy.BitsEntropyUnrefined(curHisto, 256);
entropyComp[j] = bitEntropy.BitsEntropyRefine(); entropyComp[j] = bitEntropy.BitsEntropyRefine();
@ -1447,7 +1461,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
return mid; return mid;
} }
else if (sorted[mid] < color)
if (sorted[mid] < color)
{ {
low = mid; low = mid;
} }

57
src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs

@ -157,29 +157,30 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// Estimate how many bits the combined entropy of literals and distance approximately maps to. /// Estimate how many bits the combined entropy of literals and distance approximately maps to.
/// </summary> /// </summary>
/// <returns>Estimated bits.</returns> /// <returns>Estimated bits.</returns>
public double EstimateBits() public double EstimateBits(Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy)
{ {
uint notUsed = 0; uint notUsed = 0;
return return
PopulationCost(this.Literal, this.NumCodes(), ref notUsed, ref this.IsUsed[0]) PopulationCost(this.Literal, this.NumCodes(), ref notUsed, ref this.IsUsed[0], stats, bitsEntropy)
+ PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[1]) + PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[1], stats, bitsEntropy)
+ PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[2]) + PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[2], stats, bitsEntropy)
+ PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[3]) + PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[3], stats, bitsEntropy)
+ PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4]) + PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4], stats, bitsEntropy)
+ ExtraCost(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes) + ExtraCost(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes)
+ ExtraCost(this.Distance, WebpConstants.NumDistanceCodes); + ExtraCost(this.Distance, WebpConstants.NumDistanceCodes);
} }
public void UpdateHistogramCost() public void UpdateHistogramCost(Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy)
{ {
uint alphaSym = 0, redSym = 0, blueSym = 0; uint alphaSym = 0, redSym = 0, blueSym = 0;
uint notUsed = 0; uint notUsed = 0;
double alphaCost = PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref alphaSym, ref this.IsUsed[3]);
double distanceCost = PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4]) + ExtraCost(this.Distance, WebpConstants.NumDistanceCodes); double alphaCost = PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref alphaSym, ref this.IsUsed[3], stats, bitsEntropy);
double distanceCost = PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4], stats, bitsEntropy) + ExtraCost(this.Distance, WebpConstants.NumDistanceCodes);
int numCodes = this.NumCodes(); int numCodes = this.NumCodes();
this.LiteralCost = PopulationCost(this.Literal, numCodes, ref notUsed, ref this.IsUsed[0]) + ExtraCost(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes); this.LiteralCost = PopulationCost(this.Literal, numCodes, ref notUsed, ref this.IsUsed[0], stats, bitsEntropy) + ExtraCost(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes);
this.RedCost = PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref redSym, ref this.IsUsed[1]); this.RedCost = PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref redSym, ref this.IsUsed[1], stats, bitsEntropy);
this.BlueCost = PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref blueSym, ref this.IsUsed[2]); this.BlueCost = PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref blueSym, ref this.IsUsed[2], stats, bitsEntropy);
this.BitCost = this.LiteralCost + this.RedCost + this.BlueCost + alphaCost + distanceCost; this.BitCost = this.LiteralCost + this.RedCost + this.BlueCost + alphaCost + distanceCost;
if ((alphaSym | redSym | blueSym) == NonTrivialSym) if ((alphaSym | redSym | blueSym) == NonTrivialSym)
{ {
@ -198,11 +199,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// Since the previous score passed is 'costThreshold', we only need to compare /// Since the previous score passed is 'costThreshold', we only need to compare
/// the partial cost against 'costThreshold + C(a) + C(b)' to possibly bail-out early. /// the partial cost against 'costThreshold + C(a) + C(b)' to possibly bail-out early.
/// </summary> /// </summary>
public double AddEval(Vp8LHistogram b, double costThreshold, Vp8LHistogram output) public double AddEval(Vp8LHistogram b, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy, double costThreshold, Vp8LHistogram output)
{ {
double sumCost = this.BitCost + b.BitCost; double sumCost = this.BitCost + b.BitCost;
costThreshold += sumCost; costThreshold += sumCost;
if (this.GetCombinedHistogramEntropy(b, costThreshold, costInitial: 0, out double cost)) if (this.GetCombinedHistogramEntropy(b, stats, bitsEntropy, costThreshold, costInitial: 0, out double cost))
{ {
this.Add(b, output); this.Add(b, output);
output.BitCost = cost; output.BitCost = cost;
@ -212,10 +213,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
return cost - sumCost; return cost - sumCost;
} }
public double AddThresh(Vp8LHistogram b, double costThreshold) public double AddThresh(Vp8LHistogram b, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy, double costThreshold)
{ {
double costInitial = -this.BitCost; double costInitial = -this.BitCost;
this.GetCombinedHistogramEntropy(b, costThreshold, costInitial, out double cost); this.GetCombinedHistogramEntropy(b, stats, bitsEntropy, costThreshold, costInitial, out double cost);
return cost; return cost;
} }
@ -239,12 +240,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
: NonTrivialSym; : NonTrivialSym;
} }
public bool GetCombinedHistogramEntropy(Vp8LHistogram b, double costThreshold, double costInitial, out double cost) public bool GetCombinedHistogramEntropy(Vp8LHistogram b, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy, double costThreshold, double costInitial, out double cost)
{ {
bool trivialAtEnd = false; bool trivialAtEnd = false;
cost = costInitial; cost = costInitial;
cost += GetCombinedEntropy(this.Literal, b.Literal, this.NumCodes(), this.IsUsed[0], b.IsUsed[0], false); cost += GetCombinedEntropy(this.Literal, b.Literal, this.NumCodes(), this.IsUsed[0], b.IsUsed[0], false, stats, bitEntropy);
cost += ExtraCostCombined(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), b.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes); cost += ExtraCostCombined(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), b.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes);
@ -267,25 +268,25 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
} }
cost += GetCombinedEntropy(this.Red, b.Red, WebpConstants.NumLiteralCodes, this.IsUsed[1], b.IsUsed[1], trivialAtEnd); cost += GetCombinedEntropy(this.Red, b.Red, WebpConstants.NumLiteralCodes, this.IsUsed[1], b.IsUsed[1], trivialAtEnd, stats, bitEntropy);
if (cost > costThreshold) if (cost > costThreshold)
{ {
return false; return false;
} }
cost += GetCombinedEntropy(this.Blue, b.Blue, WebpConstants.NumLiteralCodes, this.IsUsed[2], b.IsUsed[2], trivialAtEnd); cost += GetCombinedEntropy(this.Blue, b.Blue, WebpConstants.NumLiteralCodes, this.IsUsed[2], b.IsUsed[2], trivialAtEnd, stats, bitEntropy);
if (cost > costThreshold) if (cost > costThreshold)
{ {
return false; return false;
} }
cost += GetCombinedEntropy(this.Alpha, b.Alpha, WebpConstants.NumLiteralCodes, this.IsUsed[3], b.IsUsed[3], trivialAtEnd); cost += GetCombinedEntropy(this.Alpha, b.Alpha, WebpConstants.NumLiteralCodes, this.IsUsed[3], b.IsUsed[3], trivialAtEnd, stats, bitEntropy);
if (cost > costThreshold) if (cost > costThreshold)
{ {
return false; return false;
} }
cost += GetCombinedEntropy(this.Distance, b.Distance, WebpConstants.NumDistanceCodes, this.IsUsed[4], b.IsUsed[4], false); cost += GetCombinedEntropy(this.Distance, b.Distance, WebpConstants.NumDistanceCodes, this.IsUsed[4], b.IsUsed[4], false, stats, bitEntropy);
if (cost > costThreshold) if (cost > costThreshold)
{ {
return false; return false;
@ -415,9 +416,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
} }
private static double GetCombinedEntropy(uint[] x, uint[] y, int length, bool isXUsed, bool isYUsed, bool trivialAtEnd) private static double GetCombinedEntropy(uint[] x, uint[] y, int length, bool isXUsed, bool isYUsed, bool trivialAtEnd, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy)
{ {
var stats = new Vp8LStreaks(); stats.Clear();
bitEntropy.Init();
if (trivialAtEnd) if (trivialAtEnd)
{ {
// This configuration is due to palettization that transforms an indexed // This configuration is due to palettization that transforms an indexed
@ -435,7 +437,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
return stats.FinalHuffmanCost(); return stats.FinalHuffmanCost();
} }
var bitEntropy = new Vp8LBitEntropy();
if (isXUsed) if (isXUsed)
{ {
if (isYUsed) if (isYUsed)
@ -479,10 +480,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// <summary> /// <summary>
/// Get the symbol entropy for the distribution 'population'. /// Get the symbol entropy for the distribution 'population'.
/// </summary> /// </summary>
private static double PopulationCost(uint[] population, int length, ref uint trivialSym, ref bool isUsed) private static double PopulationCost(uint[] population, int length, ref uint trivialSym, ref bool isUsed, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy)
{ {
var bitEntropy = new Vp8LBitEntropy(); bitEntropy.Init();
var stats = new Vp8LStreaks(); stats.Clear();
bitEntropy.BitsEntropyUnrefined(population, length, stats); bitEntropy.BitsEntropyUnrefined(population, length, stats);
trivialSym = (bitEntropy.NoneZeros == 1) ? bitEntropy.NoneZeroCode : NonTrivialSym; trivialSym = (bitEntropy.NoneZeros == 1) ? bitEntropy.NoneZeroCode : NonTrivialSym;

9
src/ImageSharp/Formats/Webp/Lossless/Vp8LStreaks.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Webp.Lossless namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
internal class Vp8LStreaks internal class Vp8LStreaks
@ -28,6 +30,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// </summary> /// </summary>
public int[][] Streaks { get; } public int[][] Streaks { get; }
public void Clear()
{
this.Counts.AsSpan().Clear();
this.Streaks[0].AsSpan().Clear();
this.Streaks[1].AsSpan().Clear();
}
public double FinalHuffmanCost() public double FinalHuffmanCost()
{ {
// The constants in this function are experimental and got rounded from // The constants in this function are experimental and got rounded from

3
src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs

@ -418,6 +418,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
var huffmanTables = new HuffmanCode[numHTreeGroups * tableSize]; var huffmanTables = new HuffmanCode[numHTreeGroups * tableSize];
var hTreeGroups = new HTreeGroup[numHTreeGroups]; var hTreeGroups = new HTreeGroup[numHTreeGroups];
Span<HuffmanCode> huffmanTable = huffmanTables.AsSpan(); Span<HuffmanCode> huffmanTable = huffmanTables.AsSpan();
int[] codeLengths = new int[maxAlphabetSize];
for (int i = 0; i < numHTreeGroupsMax; i++) for (int i = 0; i < numHTreeGroupsMax; i++)
{ {
hTreeGroups[i] = new HTreeGroup(HuffmanUtils.HuffmanPackedTableSize); hTreeGroups[i] = new HTreeGroup(HuffmanUtils.HuffmanPackedTableSize);
@ -425,7 +426,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int totalSize = 0; int totalSize = 0;
bool isTrivialLiteral = true; bool isTrivialLiteral = true;
int maxBits = 0; int maxBits = 0;
int[] codeLengths = new int[maxAlphabetSize]; codeLengths.AsSpan().Clear();
for (int j = 0; j < WebpConstants.HuffmanCodesPerMetaCode; j++) for (int j = 0; j < WebpConstants.HuffmanCodesPerMetaCode; j++)
{ {
int alphabetSize = WebpConstants.AlphabetSize[j]; int alphabetSize = WebpConstants.AlphabetSize[j];

51
src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs

@ -58,14 +58,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static int Vp8Disto16X16(Span<byte> a, Span<byte> b, Span<ushort> w) public static int Vp8Disto16X16(Span<byte> a, Span<byte> b, Span<ushort> w, Span<int> scratch)
{ {
int d = 0; int d = 0;
for (int y = 0; y < 16 * WebpConstants.Bps; y += 4 * WebpConstants.Bps) for (int y = 0; y < 16 * WebpConstants.Bps; y += 4 * WebpConstants.Bps)
{ {
for (int x = 0; x < 16; x += 4) for (int x = 0; x < 16; x += 4)
{ {
d += Vp8Disto4X4(a.Slice(x + y), b.Slice(x + y), w); d += Vp8Disto4X4(a.Slice(x + y), b.Slice(x + y), w, scratch);
} }
} }
@ -73,10 +73,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static int Vp8Disto4X4(Span<byte> a, Span<byte> b, Span<ushort> w) public static int Vp8Disto4X4(Span<byte> a, Span<byte> b, Span<ushort> w, Span<int> scratch)
{ {
int sum1 = TTransform(a, w); int sum1 = TTransform(a, w, scratch);
int sum2 = TTransform(b, w); int sum2 = TTransform(b, w, scratch);
return Math.Abs(sum2 - sum1) >> 5; return Math.Abs(sum2 - sum1) >> 5;
} }
@ -252,18 +252,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static void TM4(Span<byte> dst, Span<byte> yuv, int offset) => TrueMotion(dst, yuv, offset, 4); public static void TM4(Span<byte> dst, Span<byte> yuv, int offset) => TrueMotion(dst, yuv, offset, 4);
public static void VE4(Span<byte> dst, Span<byte> yuv, int offset) public static void VE4(Span<byte> dst, Span<byte> yuv, int offset, Span<byte> vals)
{ {
// vertical // vertical
int topOffset = offset - WebpConstants.Bps; int topOffset = offset - WebpConstants.Bps;
byte[] vals = vals[0] = Avg3(yuv[topOffset - 1], yuv[topOffset], yuv[topOffset + 1]);
{ vals[1] = Avg3(yuv[topOffset], yuv[topOffset + 1], yuv[topOffset + 2]);
Avg3(yuv[topOffset - 1], yuv[topOffset], yuv[topOffset + 1]), vals[2] = Avg3(yuv[topOffset + 1], yuv[topOffset + 2], yuv[topOffset + 3]);
Avg3(yuv[topOffset], yuv[topOffset + 1], yuv[topOffset + 2]), vals[3] = Avg3(yuv[topOffset + 2], yuv[topOffset + 3], yuv[topOffset + 4]);
Avg3(yuv[topOffset + 1], yuv[topOffset + 2], yuv[topOffset + 3]),
Avg3(yuv[topOffset + 2], yuv[topOffset + 3], yuv[topOffset + 4])
};
int endIdx = 4 * WebpConstants.Bps; int endIdx = 4 * WebpConstants.Bps;
for (int i = 0; i < endIdx; i += WebpConstants.Bps) for (int i = 0; i < endIdx; i += WebpConstants.Bps)
{ {
@ -504,9 +500,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// <summary> /// <summary>
/// Paragraph 14.3: Implementation of the Walsh-Hadamard transform inversion. /// Paragraph 14.3: Implementation of the Walsh-Hadamard transform inversion.
/// </summary> /// </summary>
public static void TransformWht(Span<short> input, Span<short> output) public static void TransformWht(Span<short> input, Span<short> output, Span<int> scratch)
{ {
int[] tmp = new int[16]; Span<int> tmp = scratch.Slice(0, 16);
tmp.Clear();
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
int iPlus4 = 4 + i; int iPlus4 = 4 + i;
@ -544,10 +541,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// Returns the weighted sum of the absolute value of transformed coefficients. /// Returns the weighted sum of the absolute value of transformed coefficients.
/// w[] contains a row-major 4 by 4 symmetric matrix. /// w[] contains a row-major 4 by 4 symmetric matrix.
/// </summary> /// </summary>
public static int TTransform(Span<byte> input, Span<ushort> w) public static int TTransform(Span<byte> input, Span<ushort> w, Span<int> scratch)
{ {
int sum = 0; int sum = 0;
int[] tmp = new int[16]; Span<int> tmp = scratch.Slice(0, 16);
tmp.Clear();
// horizontal pass. // horizontal pass.
int inputOffset = 0; int inputOffset = 0;
@ -591,15 +589,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return sum; return sum;
} }
public static void TransformTwo(Span<short> src, Span<byte> dst) public static void TransformTwo(Span<short> src, Span<byte> dst, Span<int> scratch)
{ {
TransformOne(src, dst); TransformOne(src, dst, scratch);
TransformOne(src.Slice(16), dst.Slice(4)); TransformOne(src.Slice(16), dst.Slice(4), scratch);
} }
public static void TransformOne(Span<short> src, Span<byte> dst) public static void TransformOne(Span<short> src, Span<byte> dst, Span<int> scratch)
{ {
Span<int> tmp = stackalloc int[4 * 4]; Span<int> tmp = scratch.Slice(0, 16);
tmp.Clear();
int tmpOffset = 0; int tmpOffset = 0;
for (int srcOffset = 0; srcOffset < 4; srcOffset++) for (int srcOffset = 0; srcOffset < 4; srcOffset++)
{ {
@ -671,10 +670,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Store2(dst, 3, a - d4, d1, c1); Store2(dst, 3, a - d4, d1, c1);
} }
public static void TransformUv(Span<short> src, Span<byte> dst) public static void TransformUv(Span<short> src, Span<byte> dst, Span<int> scratch)
{ {
TransformTwo(src.Slice(0 * 16), dst); TransformTwo(src.Slice(0 * 16), dst, scratch);
TransformTwo(src.Slice(2 * 16), dst.Slice(4 * WebpConstants.Bps)); TransformTwo(src.Slice(2 * 16), dst.Slice(4 * WebpConstants.Bps), scratch);
} }
public static void TransformDcuv(Span<short> src, Span<byte> dst) public static void TransformDcuv(Span<short> src, Span<byte> dst)

86
src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs

@ -31,7 +31,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
int lambda = dqm.LambdaI16; int lambda = dqm.LambdaI16;
int tlambda = dqm.TLambda; int tlambda = dqm.TLambda;
Span<byte> src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); Span<byte> src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc);
Span<int> scratch = it.Scratch3;
var rdTmp = new Vp8ModeScore(); var rdTmp = new Vp8ModeScore();
var res = new Vp8Residual();
Vp8ModeScore rdCur = rdTmp; Vp8ModeScore rdCur = rdTmp;
Vp8ModeScore rdBest = rd; Vp8ModeScore rdBest = rd;
int mode; int mode;
@ -39,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
rd.ModeI16 = -1; rd.ModeI16 = -1;
for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) for (mode = 0; mode < WebpConstants.NumPredModes; ++mode)
{ {
// scratch buffer. // Scratch buffer.
Span<byte> tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc); Span<byte> tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc);
rdCur.ModeI16 = mode; rdCur.ModeI16 = mode;
@ -48,9 +50,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Measure RD-score. // Measure RD-score.
rdCur.D = LossyUtils.Vp8Sse16X16(src, tmpDst); rdCur.D = LossyUtils.Vp8Sse16X16(src, tmpDst);
rdCur.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto16X16(src, tmpDst, WeightY)) : 0; rdCur.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto16X16(src, tmpDst, WeightY, scratch)) : 0;
rdCur.H = WebpConstants.Vp8FixedCostsI16[mode]; rdCur.H = WebpConstants.Vp8FixedCostsI16[mode];
rdCur.R = it.GetCostLuma16(rdCur, proba); rdCur.R = it.GetCostLuma16(rdCur, proba, res);
if (isFlat) if (isFlat)
{ {
@ -101,6 +103,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
int tlambda = dqm.TLambda; int tlambda = dqm.TLambda;
Span<byte> src0 = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); Span<byte> src0 = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc);
Span<byte> bestBlocks = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc); Span<byte> bestBlocks = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc);
Span<int> scratch = it.Scratch3;
int totalHeaderBits = 0; int totalHeaderBits = 0;
var rdBest = new Vp8ModeScore(); var rdBest = new Vp8ModeScore();
@ -113,31 +116,35 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
rdBest.H = 211; // '211' is the value of VP8BitCost(0, 145) rdBest.H = 211; // '211' is the value of VP8BitCost(0, 145)
rdBest.SetRdScore(dqm.LambdaMode); rdBest.SetRdScore(dqm.LambdaMode);
it.StartI4(); it.StartI4();
var rdi4 = new Vp8ModeScore();
var rdTmp = new Vp8ModeScore();
var res = new Vp8Residual();
Span<short> tmpLevels = new short[16];
do do
{ {
int numBlocks = 1; int numBlocks = 1;
var rdi4 = new Vp8ModeScore(); rdi4.Clear();
int mode; int mode;
int bestMode = -1; int bestMode = -1;
Span<byte> src = src0.Slice(WebpLookupTables.Vp8Scan[it.I4]); Span<byte> src = src0.Slice(WebpLookupTables.Vp8Scan[it.I4]);
short[] modeCosts = it.GetCostModeI4(rd.ModesI4); short[] modeCosts = it.GetCostModeI4(rd.ModesI4);
Span<byte> bestBlock = bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4]); Span<byte> bestBlock = bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4]);
Span<byte> tmpDst = it.Scratch.AsSpan(); Span<byte> tmpDst = it.Scratch.AsSpan();
tmpDst.Fill(0); tmpDst.Clear();
rdi4.InitScore(); rdi4.InitScore();
it.MakeIntra4Preds(); it.MakeIntra4Preds();
for (mode = 0; mode < WebpConstants.NumBModes; ++mode) for (mode = 0; mode < WebpConstants.NumBModes; ++mode)
{ {
var rdTmp = new Vp8ModeScore(); rdTmp.Clear();
short[] tmpLevels = new short[16]; tmpLevels.Clear();
// Reconstruct. // Reconstruct.
rdTmp.Nz = (uint)ReconstructIntra4(it, dqm, tmpLevels, src, tmpDst, mode); rdTmp.Nz = (uint)ReconstructIntra4(it, dqm, tmpLevels, src, tmpDst, mode);
// Compute RD-score. // Compute RD-score.
rdTmp.D = LossyUtils.Vp8Sse4X4(src, tmpDst); rdTmp.D = LossyUtils.Vp8Sse4X4(src, tmpDst);
rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto4X4(src, tmpDst, WeightY)) : 0; rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto4X4(src, tmpDst, WeightY, scratch)) : 0;
rdTmp.H = modeCosts[mode]; rdTmp.H = modeCosts[mode];
// Add flatness penalty, to avoid flat area to be mispredicted by a complex mode. // Add flatness penalty, to avoid flat area to be mispredicted by a complex mode.
@ -150,15 +157,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
rdTmp.R = 0; rdTmp.R = 0;
} }
// early-out check. // Early-out check.
rdTmp.SetRdScore(lambda); rdTmp.SetRdScore(lambda);
if (bestMode >= 0 && rdTmp.Score >= rdi4.Score) if (bestMode >= 0 && rdTmp.Score >= rdi4.Score)
{ {
continue; continue;
} }
// finish computing score. // Finish computing score.
rdTmp.R += it.GetCostLuma4(tmpLevels, proba); rdTmp.R += it.GetCostLuma4(tmpLevels, proba, res);
rdTmp.SetRdScore(lambda); rdTmp.SetRdScore(lambda);
if (bestMode < 0 || rdTmp.Score < rdi4.Score) if (bestMode < 0 || rdTmp.Score < rdi4.Score)
@ -213,13 +220,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Span<byte> dst0 = it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc); Span<byte> dst0 = it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc);
Span<byte> dst = dst0; Span<byte> dst = dst0;
var rdBest = new Vp8ModeScore(); var rdBest = new Vp8ModeScore();
var rdUv = new Vp8ModeScore();
var res = new Vp8Residual();
int mode; int mode;
rd.ModeUv = -1; rd.ModeUv = -1;
rdBest.InitScore(); rdBest.InitScore();
for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) for (mode = 0; mode < WebpConstants.NumPredModes; ++mode)
{ {
var rdUv = new Vp8ModeScore(); rdUv.Clear();
// Reconstruct // Reconstruct
rdUv.Nz = (uint)ReconstructUv(it, dqm, rdUv, tmpDst, mode); rdUv.Nz = (uint)ReconstructUv(it, dqm, rdUv, tmpDst, mode);
@ -228,7 +237,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
rdUv.D = LossyUtils.Vp8Sse16X8(src, tmpDst); rdUv.D = LossyUtils.Vp8Sse16X8(src, tmpDst);
rdUv.SD = 0; // not calling TDisto here: it tends to flatten areas. rdUv.SD = 0; // not calling TDisto here: it tends to flatten areas.
rdUv.H = WebpConstants.Vp8FixedCostsUv[mode]; rdUv.H = WebpConstants.Vp8FixedCostsUv[mode];
rdUv.R = it.GetCostUv(rdUv, proba); rdUv.R = it.GetCostUv(rdUv, proba, res);
if (mode > 0 && IsFlat(rdUv.UvLevels, numBlocks, WebpConstants.FlatnessLimitIUv)) if (mode > 0 && IsFlat(rdUv.UvLevels, numBlocks, WebpConstants.FlatnessLimitIUv))
{ {
rdUv.R += WebpConstants.FlatnessPenality * numBlocks; rdUv.R += WebpConstants.FlatnessPenality * numBlocks;
@ -271,16 +280,24 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Span<byte> src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); Span<byte> src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc);
int nz = 0; int nz = 0;
int n; int n;
short[] dcTmp = new short[16]; Span<short> shortScratchSpan = it.Scratch2.AsSpan();
short[] tmp = new short[16 * 16]; Span<int> scratch = it.Scratch3.AsSpan(0, 16);
Span<short> tmpSpan = tmp.AsSpan(); shortScratchSpan.Clear();
scratch.Clear();
Span<short> dcTmp = shortScratchSpan.Slice(0, 16);
Span<short> tmp = shortScratchSpan.Slice(16, 16 * 16);
for (n = 0; n < 16; n += 2) for (n = 0; n < 16; n += 2)
{ {
Vp8Encoding.FTransform2(src.Slice(WebpLookupTables.Vp8Scan[n]), reference.Slice(WebpLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 16), tmpSpan.Slice((n + 1) * 16, 16)); Vp8Encoding.FTransform2(
src.Slice(WebpLookupTables.Vp8Scan[n]),
reference.Slice(WebpLookupTables.Vp8Scan[n]),
tmp.Slice(n * 16, 16),
tmp.Slice((n + 1) * 16, 16),
scratch);
} }
Vp8Encoding.FTransformWht(tmp, dcTmp); Vp8Encoding.FTransformWht(tmp, dcTmp, scratch);
nz |= QuantizeBlock(dcTmp, rd.YDcLevels, dqm.Y2) << 24; nz |= QuantizeBlock(dcTmp, rd.YDcLevels, dqm.Y2) << 24;
for (n = 0; n < 16; n += 2) for (n = 0; n < 16; n += 2)
@ -288,14 +305,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Zero-out the first coeff, so that: a) nz is correct below, and // Zero-out the first coeff, so that: a) nz is correct below, and
// b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified. // b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified.
tmp[n * 16] = tmp[(n + 1) * 16] = 0; tmp[n * 16] = tmp[(n + 1) * 16] = 0;
nz |= Quantize2Blocks(tmpSpan.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), dqm.Y1) << n; nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), dqm.Y1) << n;
} }
// Transform back. // Transform back.
LossyUtils.TransformWht(dcTmp, tmpSpan); LossyUtils.TransformWht(dcTmp, tmp, scratch);
for (n = 0; n < 16; n += 2) for (n = 0; n < 16; n += 2)
{ {
Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8Scan[n]), true); Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8Scan[n]), tmp.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8Scan[n]), true, scratch);
} }
return nz; return nz;
@ -304,10 +321,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public static int ReconstructIntra4(Vp8EncIterator it, Vp8SegmentInfo dqm, Span<short> levels, Span<byte> src, Span<byte> yuvOut, int mode) public static int ReconstructIntra4(Vp8EncIterator it, Vp8SegmentInfo dqm, Span<short> levels, Span<byte> src, Span<byte> yuvOut, int mode)
{ {
Span<byte> reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); Span<byte> reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]);
short[] tmp = new short[16]; Span<short> tmp = it.Scratch2.AsSpan(0, 16);
Vp8Encoding.FTransform(src, reference, tmp); Span<int> scratch = it.Scratch3.AsSpan(0, 16);
tmp.Clear();
scratch.Clear();
Vp8Encoding.FTransform(src, reference, tmp, scratch);
int nz = QuantizeBlock(tmp, levels, dqm.Y1); int nz = QuantizeBlock(tmp, levels, dqm.Y1);
Vp8Encoding.ITransform(reference, tmp, yuvOut, false); Vp8Encoding.ITransform(reference, tmp, yuvOut, false, scratch);
return nz; return nz;
} }
@ -318,27 +338,31 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Span<byte> src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); Span<byte> src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc);
int nz = 0; int nz = 0;
int n; int n;
short[] tmp = new short[8 * 16]; Span<short> tmp = it.Scratch2.AsSpan(0, 8 * 16);
Span<int> scratch = it.Scratch3.AsSpan(0, 16);
tmp.Clear();
scratch.Clear();
for (n = 0; n < 8; n += 2) for (n = 0; n < 8; n += 2)
{ {
Vp8Encoding.FTransform2( Vp8Encoding.FTransform2(
src.Slice(WebpLookupTables.Vp8ScanUv[n]), src.Slice(WebpLookupTables.Vp8ScanUv[n]),
reference.Slice(WebpLookupTables.Vp8ScanUv[n]), reference.Slice(WebpLookupTables.Vp8ScanUv[n]),
tmp.AsSpan(n * 16, 16), tmp.Slice(n * 16, 16),
tmp.AsSpan((n + 1) * 16, 16)); tmp.Slice((n + 1) * 16, 16),
scratch);
} }
CorrectDcValues(it, dqm.Uv, tmp, rd); CorrectDcValues(it, dqm.Uv, tmp, rd);
for (n = 0; n < 8; n += 2) for (n = 0; n < 8; n += 2)
{ {
nz |= Quantize2Blocks(tmp.AsSpan(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), dqm.Uv) << n; nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), dqm.Uv) << n;
} }
for (n = 0; n < 8; n += 2) for (n = 0; n < 8; n += 2)
{ {
Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8ScanUv[n]), tmp.AsSpan(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8ScanUv[n]), true); Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8ScanUv[n]), tmp.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8ScanUv[n]), true, scratch);
} }
return nz << 16; return nz << 16;
@ -556,7 +580,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return (sign ? -v0 : v0) >> DSCALE; return (sign ? -v0 : v0) >> DSCALE;
} }
public static void CorrectDcValues(Vp8EncIterator it, Vp8Matrix mtx, short[] tmp, Vp8ModeScore rd) public static void CorrectDcValues(Vp8EncIterator it, Vp8Matrix mtx, Span<short> tmp, Vp8ModeScore rd)
{ {
#pragma warning disable SA1005 // Single line comments should begin with single space #pragma warning disable SA1005 // Single line comments should begin with single space
// | top[0] | top[1] // | top[0] | top[1]
@ -571,7 +595,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{ {
Span<sbyte> top = it.TopDerr.AsSpan((it.X * 4) + ch, 2); Span<sbyte> top = it.TopDerr.AsSpan((it.X * 4) + ch, 2);
Span<sbyte> left = it.LeftDerr.AsSpan(ch, 2); Span<sbyte> left = it.LeftDerr.AsSpan(ch, 2);
Span<short> c = tmp.AsSpan(ch * 4 * 16, 4 * 16); Span<short> c = tmp.Slice(ch * 4 * 16, 4 * 16);
c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE)); c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE));
int err0 = QuantizeSingle(c, mtx); int err0 = QuantizeSingle(c, mtx);
c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE)); c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE));

27
src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs

@ -81,6 +81,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.I4Boundary = new byte[37]; this.I4Boundary = new byte[37];
this.BitCount = new long[4, 3]; this.BitCount = new long[4, 3];
this.Scratch = new byte[WebpConstants.Bps * 16]; this.Scratch = new byte[WebpConstants.Bps * 16];
this.Scratch2 = new short[17 * 16];
this.Scratch3 = new int[16];
// To match the C initial values of the reference implementation, initialize all with 204. // To match the C initial values of the reference implementation, initialize all with 204.
byte defaultInitVal = 204; byte defaultInitVal = 204;
@ -216,10 +218,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public int CountDown { get; set; } public int CountDown { get; set; }
/// <summary> /// <summary>
/// Gets the scratch buffer. /// Gets the byte scratch buffer.
/// </summary> /// </summary>
public byte[] Scratch { get; } public byte[] Scratch { get; }
/// <summary>
/// Gets the short scratch buffer.
/// </summary>
public short[] Scratch2 { get; }
/// <summary>
/// Gets the int scratch buffer.
/// </summary>
public int[] Scratch3 { get; }
public Vp8MacroBlockInfo CurrentMacroBlockInfo => this.Mb[this.currentMbIdx]; public Vp8MacroBlockInfo CurrentMacroBlockInfo => this.Mb[this.currentMbIdx];
private Vp8MacroBlockInfo[] Mb { get; } private Vp8MacroBlockInfo[] Mb { get; }
@ -380,7 +392,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
int bestMode = 0; int bestMode = 0;
this.MakeLuma16Preds(); this.MakeLuma16Preds();
for (mode = 0; mode < maxMode; ++mode) for (mode = 0; mode < maxMode; mode++)
{ {
var histo = new Vp8Histogram(); var histo = new Vp8Histogram();
histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]), 0, 16); histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]), 0, 16);
@ -499,9 +511,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I4X4; this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I4X4;
} }
public int GetCostLuma16(Vp8ModeScore rd, Vp8EncProba proba) public int GetCostLuma16(Vp8ModeScore rd, Vp8EncProba proba, Vp8Residual res)
{ {
var res = new Vp8Residual();
int r = 0; int r = 0;
// re-import the non-zero context. // re-import the non-zero context.
@ -539,11 +550,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return WebpLookupTables.Vp8FixedCostsI4[top, left]; return WebpLookupTables.Vp8FixedCostsI4[top, left];
} }
public int GetCostLuma4(short[] levels, Vp8EncProba proba) public int GetCostLuma4(Span<short> levels, Vp8EncProba proba, Vp8Residual res)
{ {
int x = this.I4 & 3; int x = this.I4 & 3;
int y = this.I4 >> 2; int y = this.I4 >> 2;
var res = new Vp8Residual();
int r = 0; int r = 0;
res.Init(0, 3, proba); res.Init(0, 3, proba);
@ -553,9 +563,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return r; return r;
} }
public int GetCostUv(Vp8ModeScore rd, Vp8EncProba proba) public int GetCostUv(Vp8ModeScore rd, Vp8EncProba proba, Vp8Residual res)
{ {
var res = new Vp8Residual();
int r = 0; int r = 0;
// re-import the non-zero context. // re-import the non-zero context.
@ -741,7 +750,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vp8Encoding.EncPredChroma8(this.YuvP, left, top); Vp8Encoding.EncPredChroma8(this.YuvP, left, top);
} }
public void MakeIntra4Preds() => Vp8Encoding.EncPredLuma4(this.YuvP, this.I4Boundary, this.I4BoundaryIdx); public void MakeIntra4Preds() => Vp8Encoding.EncPredLuma4(this.YuvP, this.I4Boundary, this.I4BoundaryIdx, this.Scratch.AsSpan(0, 4));
public void SwapOut() public void SwapOut()
{ {

18
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

@ -70,6 +70,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// </summary> /// </summary>
private int uvAlpha; private int uvAlpha;
/// <summary>
/// Scratch buffer to reduce allocations.
/// </summary>
private readonly int[] scratch = new int[16];
private readonly byte[] averageBytesPerMb = { 50, 24, 16, 9, 7, 5, 3, 2 }; private readonly byte[] averageBytesPerMb = { 50, 24, 16, 9, 7, 5, 3, 2 };
private const int NumMbSegments = 4; private const int NumMbSegments = 4;
@ -321,18 +326,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.StatLoop(width, height, yStride, uvStride); this.StatLoop(width, height, yStride, uvStride);
it.Init(); it.Init();
it.InitFilter(); it.InitFilter();
var info = new Vp8ModeScore();
var residual = new Vp8Residual();
do do
{ {
bool dontUseSkip = !this.Proba.UseSkipProba; bool dontUseSkip = !this.Proba.UseSkipProba;
info.Clear();
var info = new Vp8ModeScore();
it.Import(y, u, v, yStride, uvStride, width, height, false); it.Import(y, u, v, yStride, uvStride, width, height, false);
// Warning! order is important: first call VP8Decimate() and // Warning! order is important: first call VP8Decimate() and
// *then* decide how to code the skip decision if there's one. // *then* decide how to code the skip decision if there's one.
if (!this.Decimate(it, ref info, this.rdOptLevel) || dontUseSkip) if (!this.Decimate(it, ref info, this.rdOptLevel) || dontUseSkip)
{ {
this.CodeResiduals(it, info); this.CodeResiduals(it, info, residual);
} }
else else
{ {
@ -447,9 +453,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
it.Init(); it.Init();
this.SetLoopParams(stats.Q); this.SetLoopParams(stats.Q);
var info = new Vp8ModeScore();
do do
{ {
var info = new Vp8ModeScore(); info.Clear();
it.Import(y, u, v, yStride, uvStride, width, height, false); it.Import(y, u, v, yStride, uvStride, width, height, false);
if (this.Decimate(it, ref info, rdOpt)) if (this.Decimate(it, ref info, rdOpt))
{ {
@ -930,10 +937,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return isSkipped; return isSkipped;
} }
private void CodeResiduals(Vp8EncIterator it, Vp8ModeScore rd) private void CodeResiduals(Vp8EncIterator it, Vp8ModeScore rd, Vp8Residual residual)
{ {
int x, y, ch; int x, y, ch;
var residual = new Vp8Residual();
bool i16 = it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; bool i16 = it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16;
int segment = it.CurrentMacroBlockInfo.Segment; int segment = it.CurrentMacroBlockInfo.Segment;

54
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs

@ -68,22 +68,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
} }
public static void ITransform(Span<byte> reference, Span<short> input, Span<byte> dst, bool doTwo) public static void ITransform(Span<byte> reference, Span<short> input, Span<byte> dst, bool doTwo, Span<int> scratch)
{ {
ITransformOne(reference, input, dst); ITransformOne(reference, input, dst, scratch);
if (doTwo) if (doTwo)
{ {
ITransformOne(reference.Slice(4), input.Slice(16), dst.Slice(4)); ITransformOne(reference.Slice(4), input.Slice(16), dst.Slice(4), scratch);
} }
} }
public static void ITransformOne(Span<byte> reference, Span<short> input, Span<byte> dst) public static void ITransformOne(Span<byte> reference, Span<short> input, Span<byte> dst, Span<int> scratch)
{ {
int i; int i;
#pragma warning disable SA1312 // Variable names should begin with lower-case letter Span<int> tmp = scratch.Slice(0, 16);
int[] C = new int[4 * 4]; tmp.Clear();
#pragma warning restore SA1312 // Variable names should begin with lower-case letter
Span<int> tmp = C.AsSpan();
for (i = 0; i < 4; i++) for (i = 0; i < 4; i++)
{ {
// vertical pass. // vertical pass.
@ -99,7 +97,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
input = input.Slice(1); input = input.Slice(1);
} }
tmp = C.AsSpan(); tmp = scratch;
for (i = 0; i < 4; i++) for (i = 0; i < 4; i++)
{ {
// horizontal pass. // horizontal pass.
@ -116,16 +114,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
} }
public static void FTransform2(Span<byte> src, Span<byte> reference, Span<short> output, Span<short> output2) public static void FTransform2(Span<byte> src, Span<byte> reference, Span<short> output, Span<short> output2, Span<int> scratch)
{ {
FTransform(src, reference, output); FTransform(src, reference, output, scratch);
FTransform(src.Slice(4), reference.Slice(4), output2); FTransform(src.Slice(4), reference.Slice(4), output2, scratch);
} }
public static void FTransform(Span<byte> src, Span<byte> reference, Span<short> output) public static void FTransform(Span<byte> src, Span<byte> reference, Span<short> output, Span<int> scratch)
{ {
int i; int i;
int[] tmp = new int[16]; Span<int> tmp = scratch.Slice(0, 16);
tmp.Clear();
int srcIdx = 0; int srcIdx = 0;
int refIdx = 0; int refIdx = 0;
for (i = 0; i < 4; i++) for (i = 0; i < 4; i++)
@ -160,9 +160,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
} }
public static void FTransformWht(Span<short> input, Span<short> output) public static void FTransformWht(Span<short> input, Span<short> output, Span<int> scratch)
{ {
int[] tmp = new int[16]; Span<int> tmp = scratch.Slice(0, 16);
tmp.Clear();
int i; int i;
int inputIdx = 0; int inputIdx = 0;
for (i = 0; i < 4; i++) for (i = 0; i < 4; i++)
@ -234,11 +236,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Left samples are top[-5 .. -2], top_left is top[-1], top are // Left samples are top[-5 .. -2], top_left is top[-1], top are
// located at top[0..3], and top right is top[4..7] // located at top[0..3], and top right is top[4..7]
public static void EncPredLuma4(Span<byte> dst, Span<byte> top, int topOffset) public static void EncPredLuma4(Span<byte> dst, Span<byte> top, int topOffset, Span<byte> vals)
{ {
Dc4(dst.Slice(I4DC4), top, topOffset); Dc4(dst.Slice(I4DC4), top, topOffset);
Tm4(dst.Slice(I4TM4), top, topOffset); Tm4(dst.Slice(I4TM4), top, topOffset);
Ve4(dst.Slice(I4VE4), top, topOffset); Ve4(dst.Slice(I4VE4), top, topOffset, vals);
He4(dst.Slice(I4HE4), top, topOffset); He4(dst.Slice(I4HE4), top, topOffset);
Rd4(dst.Slice(I4RD4), top, topOffset); Rd4(dst.Slice(I4RD4), top, topOffset);
Vr4(dst.Slice(I4VR4), top, topOffset); Vr4(dst.Slice(I4VR4), top, topOffset);
@ -395,20 +397,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
} }
private static void Ve4(Span<byte> dst, Span<byte> top, int topOffset) private static void Ve4(Span<byte> dst, Span<byte> top, int topOffset, Span<byte> vals)
{ {
// vertical // vertical
byte[] vals = vals[0] = LossyUtils.Avg3(top[topOffset - 1], top[topOffset], top[topOffset + 1]);
{ vals[1] = LossyUtils.Avg3(top[topOffset], top[topOffset + 1], top[topOffset + 2]);
LossyUtils.Avg3(top[topOffset - 1], top[topOffset], top[topOffset + 1]), vals[2] = LossyUtils.Avg3(top[topOffset + 1], top[topOffset + 2], top[topOffset + 3]);
LossyUtils.Avg3(top[topOffset], top[topOffset + 1], top[topOffset + 2]), vals[3] = LossyUtils.Avg3(top[topOffset + 2], top[topOffset + 3], top[topOffset + 4]);
LossyUtils.Avg3(top[topOffset + 1], top[topOffset + 2], top[topOffset + 3]),
LossyUtils.Avg3(top[topOffset + 2], top[topOffset + 3], top[topOffset + 4])
};
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
vals.AsSpan().CopyTo(dst.Slice(i * WebpConstants.Bps)); vals.CopyTo(dst.Slice(i * WebpConstants.Bps));
} }
} }

23
src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs

@ -8,6 +8,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{ {
internal class Vp8Histogram internal class Vp8Histogram
{ {
private readonly int[] scratch = new int[16];
private readonly short[] output = new short[16];
private readonly int[] distribution = new int[MaxCoeffThresh + 1];
/// <summary> /// <summary>
/// Size of histogram used by CollectHistogram. /// Size of histogram used by CollectHistogram.
/// </summary> /// </summary>
@ -40,23 +46,22 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public void CollectHistogram(Span<byte> reference, Span<byte> pred, int startBlock, int endBlock) public void CollectHistogram(Span<byte> reference, Span<byte> pred, int startBlock, int endBlock)
{ {
int j; int j;
int[] distribution = new int[MaxCoeffThresh + 1]; this.distribution.AsSpan().Clear();
for (j = startBlock; j < endBlock; j++) for (j = startBlock; j < endBlock; j++)
{ {
short[] output = new short[16]; this.output.AsSpan().Clear();
this.Vp8FTransform(reference.Slice(WebpLookupTables.Vp8DspScan[j]), pred.Slice(WebpLookupTables.Vp8DspScan[j]), this.output);
this.Vp8FTransform(reference.Slice(WebpLookupTables.Vp8DspScan[j]), pred.Slice(WebpLookupTables.Vp8DspScan[j]), output);
// Convert coefficients to bin. // Convert coefficients to bin.
for (int k = 0; k < 16; ++k) for (int k = 0; k < 16; ++k)
{ {
int v = Math.Abs(output[k]) >> 3; int v = Math.Abs(this.output[k]) >> 3;
int clippedValue = ClipMax(v, MaxCoeffThresh); int clippedValue = ClipMax(v, MaxCoeffThresh);
++distribution[clippedValue]; ++this.distribution[clippedValue];
} }
} }
this.SetHistogramData(distribution); this.SetHistogramData(this.distribution);
} }
public void Merge(Vp8Histogram other) public void Merge(Vp8Histogram other)
@ -97,7 +102,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private void Vp8FTransform(Span<byte> src, Span<byte> reference, Span<short> output) private void Vp8FTransform(Span<byte> src, Span<byte> reference, Span<short> output)
{ {
int i; int i;
int[] tmp = new int[16]; Span<int> tmp = this.scratch;
tmp.Clear();
for (i = 0; i < 4; i++) for (i = 0; i < 4; i++)
{ {
int d0 = src[0] - reference[0]; // 9bit dynamic range ([-255,255]) int d0 = src[0] - reference[0]; // 9bit dynamic range ([-255,255])

18
src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Webp.Lossy namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{ {
/// <summary> /// <summary>
@ -93,6 +95,22 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// </summary> /// </summary>
public int[,] Derr { get; } public int[,] Derr { get; }
public void Clear()
{
this.YDcLevels.AsSpan().Clear();
this.YAcLevels.AsSpan().Clear();
this.UvLevels.AsSpan().Clear();
this.ModesI4.AsSpan().Clear();
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
{
this.Derr[i, j] = 0;
}
}
}
public void InitScore() public void InitScore()
{ {
this.D = 0; this.D = 0;

5
src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public int CoeffType { get; set; } public int CoeffType { get; set; }
public short[] Coeffs { get; set; } public short[] Coeffs { get; } = new short[16];
public Vp8BandProbas[] Prob { get; set; } public Vp8BandProbas[] Prob { get; set; }
@ -31,6 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.Prob = prob.Coeffs[this.CoeffType]; this.Prob = prob.Coeffs[this.CoeffType];
this.Stats = prob.Stats[this.CoeffType]; this.Stats = prob.Stats[this.CoeffType];
this.Costs = prob.RemappedCosts[this.CoeffType]; this.Costs = prob.RemappedCosts[this.CoeffType];
this.Coeffs.AsSpan().Clear();
} }
public void SetCoeffs(Span<short> coeffs) public void SetCoeffs(Span<short> coeffs)
@ -46,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
} }
this.Coeffs = coeffs.Slice(0, 16).ToArray(); coeffs.Slice(0, 16).CopyTo(this.Coeffs);
} }
// Simulate block coding, but only record statistics. // Simulate block coding, but only record statistics.

30
src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs

@ -34,6 +34,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// </summary> /// </summary>
private readonly Configuration configuration; private readonly Configuration configuration;
/// <summary>
/// Scratch buffer to reduce allocations.
/// </summary>
private readonly int[] scratch = new int[16];
/// <summary>
/// Another scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] scratchBytes = new byte[4];
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebpLossyDecoder"/> class. /// Initializes a new instance of the <see cref="WebpLossyDecoder"/> class.
/// </summary> /// </summary>
@ -395,7 +405,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
LossyUtils.TM4(dst, yuv, offset); LossyUtils.TM4(dst, yuv, offset);
break; break;
case 2: case 2:
LossyUtils.VE4(dst, yuv, offset); LossyUtils.VE4(dst, yuv, offset, this.scratchBytes);
break; break;
case 3: case 3:
LossyUtils.HE4(dst, yuv, offset); LossyUtils.HE4(dst, yuv, offset);
@ -420,7 +430,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
break; break;
} }
this.DoTransform(bits, coeffs.AsSpan(n * 16), dst); this.DoTransform(bits, coeffs.AsSpan(n * 16), dst, this.scratch);
} }
} }
else else
@ -456,7 +466,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{ {
for (int n = 0; n < 16; ++n, bits <<= 2) for (int n = 0; n < 16; ++n, bits <<= 2)
{ {
this.DoTransform(bits, coeffs.AsSpan(n * 16), yDst.Slice(WebpConstants.Scan[n])); this.DoTransform(bits, coeffs.AsSpan(n * 16), yDst.Slice(WebpConstants.Scan[n]), this.scratch);
} }
} }
} }
@ -496,8 +506,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
break; break;
} }
this.DoUVTransform(bitsUv, coeffs.AsSpan(16 * 16), uDst); this.DoUVTransform(bitsUv, coeffs.AsSpan(16 * 16), uDst, this.scratch);
this.DoUVTransform(bitsUv >> 8, coeffs.AsSpan(20 * 16), vDst); this.DoUVTransform(bitsUv >> 8, coeffs.AsSpan(20 * 16), vDst, this.scratch);
// Stash away top samples for next block. // Stash away top samples for next block.
if (mby < dec.MbHeight - 1) if (mby < dec.MbHeight - 1)
@ -787,12 +797,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
} }
private void DoTransform(uint bits, Span<short> src, Span<byte> dst) private void DoTransform(uint bits, Span<short> src, Span<byte> dst, Span<int> scratch)
{ {
switch (bits >> 30) switch (bits >> 30)
{ {
case 3: case 3:
LossyUtils.TransformOne(src, dst); LossyUtils.TransformOne(src, dst, scratch);
break; break;
case 2: case 2:
LossyUtils.TransformAc3(src, dst); LossyUtils.TransformAc3(src, dst);
@ -803,7 +813,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
} }
private void DoUVTransform(uint bits, Span<short> src, Span<byte> dst) private void DoUVTransform(uint bits, Span<short> src, Span<byte> dst, Span<int> scratch)
{ {
// any non-zero coeff at all? // any non-zero coeff at all?
if ((bits & 0xff) > 0) if ((bits & 0xff) > 0)
@ -811,7 +821,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// any non-zero AC coefficient? // any non-zero AC coefficient?
if ((bits & 0xaa) > 0) if ((bits & 0xaa) > 0)
{ {
LossyUtils.TransformUv(src, dst); // note we don't use the AC3 variant for U/V. LossyUtils.TransformUv(src, dst, scratch); // note we don't use the AC3 variant for U/V.
} }
else else
{ {
@ -884,7 +894,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
if (nz > 1) if (nz > 1)
{ {
// More than just the DC -> perform the full transform. // More than just the DC -> perform the full transform.
LossyUtils.TransformWht(dc, dst); LossyUtils.TransformWht(dc, dst, this.scratch);
} }
else else
{ {

6
tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs

@ -90,9 +90,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits); int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits);
int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits); int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits);
uint[] transformData = new uint[transformWidth * transformHeight]; uint[] transformData = new uint[transformWidth * transformHeight];
int[] scratch = new int[256];
// act // act
PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData); PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData, scratch);
// assert // assert
Assert.Equal(expectedData, transformData); Assert.Equal(expectedData, transformData);
@ -119,9 +120,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits); int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits);
int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits); int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits);
uint[] transformData = new uint[transformWidth * transformHeight]; uint[] transformData = new uint[transformWidth * transformHeight];
int[] scratch = new int[256];
// act // act
PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData); PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData, scratch);
// assert // assert
Assert.Equal(expectedData, transformData); Assert.Equal(expectedData, transformData);

Loading…
Cancel
Save