diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs
index c28ae6cfa8..350f6d00dc 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs
@@ -480,18 +480,22 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
int[] tmp = new int[16];
// horizontal pass.
+ int inputOffset = 0;
for (int i = 0; i < 4; ++i)
{
- int a0 = input[0] + input[2];
- int a1 = input[1] + input[3];
- int a2 = input[1] - input[3];
- int a3 = input[0] - input[2];
+ int inputOffsetPlusOne = inputOffset + 1;
+ int inputOffsetPlusTwo = inputOffset + 2;
+ int inputOffsetPlusThree = inputOffset + 3;
+ int a0 = input[inputOffset] + input[inputOffsetPlusTwo];
+ int a1 = input[inputOffsetPlusOne] + input[inputOffsetPlusThree];
+ int a2 = input[inputOffsetPlusOne] - input[inputOffsetPlusThree];
+ int a3 = input[inputOffset] - input[inputOffsetPlusTwo];
tmp[0 + (i * 4)] = a0 + a1;
tmp[1 + (i * 4)] = a3 + a2;
tmp[2 + (i * 4)] = a3 - a2;
tmp[3 + (i * 4)] = a0 - a1;
- input = input.Slice(WebpConstants.Bps);
+ inputOffset += WebpConstants.Bps;
}
// vertical pass
@@ -549,6 +553,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// an input in [-2048, 2047] interval. We then need to add a dst value in the [0, 255] range.
// In the worst case scenario, the input to clip_8b() can be as large as [-60713, 60968].
tmpOffset = 0;
+ int dstOffset = 0;
for (int i = 0; i < 4; ++i)
{
// horizontal pass
@@ -560,12 +565,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
int b = dc - tmp[tmpOffsetPlus8];
int c = Mul2(tmp[tmpOffsetPlus4]) - Mul1(tmp[tmpOffsetPlus12]);
int d = Mul1(tmp[tmpOffsetPlus4]) + Mul2(tmp[tmpOffsetPlus12]);
- Store(dst, 0, 0, a + d);
- Store(dst, 1, 0, b + c);
- Store(dst, 2, 0, b - c);
- Store(dst, 3, 0, a - d);
+ Store(dst.Slice(dstOffset), 0, 0, a + d);
+ Store(dst.Slice(dstOffset), 1, 0, b + c);
+ Store(dst.Slice(dstOffset), 2, 0, b - c);
+ Store(dst.Slice(dstOffset), 3, 0, a - d);
tmpOffset++;
- dst = dst.Slice(WebpConstants.Bps);
+
+ dstOffset += WebpConstants.Bps;
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs
index 3d3a522ba2..4015a18a98 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs
@@ -8,10 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
///
/// Initializes a new instance of the class.
///
- public Vp8CostArray()
- {
- this.Costs = new ushort[WebpConstants.NumCtx * (67 + 1)];
- }
+ public Vp8CostArray() => this.Costs = new ushort[WebpConstants.NumCtx * (67 + 1)];
public ushort[] Costs { get; }
}
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs
new file mode 100644
index 0000000000..763c89c570
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.Formats.Webp;
+using SixLabors.ImageSharp.Formats.Webp.Lossy;
+
+namespace SixLabors.ImageSharp.Formats.WebP.Lossy
+{
+ internal class Vp8Costs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Vp8Costs()
+ {
+ this.Costs = new Vp8CostArray[WebpConstants.NumCtx];
+ for (int i = 0; i < WebpConstants.NumCtx; i++)
+ {
+ this.Costs[i] = new Vp8CostArray();
+ }
+ }
+
+ ///
+ /// Gets the Costs.
+ ///
+ public Vp8CostArray[] Costs { get; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
index d4ef150f61..ff8f192e10 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
@@ -77,6 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.LeftNz = new int[9];
this.I4Boundary = new byte[37];
this.BitCount = new long[4, 3];
+ this.Scratch = new byte[WebpConstants.Bps * 16];
// To match the C initial values of the reference implementation, initialize all with 204.
byte defaultInitVal = 204;
@@ -86,6 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.YuvP.AsSpan().Fill(defaultInitVal);
this.YLeft.AsSpan().Fill(defaultInitVal);
this.UvLeft.AsSpan().Fill(defaultInitVal);
+ this.Scratch.AsSpan().Fill(defaultInitVal);
this.Reset();
}
@@ -210,6 +212,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
///
public int CountDown { get; set; }
+ ///
+ /// Gets the scratch buffer.
+ ///
+ public byte[] Scratch { get; }
+
public Vp8MacroBlockInfo CurrentMacroBlockInfo => this.Mb[this.currentMbIdx];
private Vp8MacroBlockInfo[] Mb { get; }
@@ -459,14 +466,39 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
int x = this.I4 & 3;
int y = this.I4 >> 2;
var res = new Vp8Residual();
- int R = 0;
- int ctx;
+ int r = 0;
res.Init(0, 3, proba);
- ctx = this.TopNz[x] + this.LeftNz[y];
+ int ctx = this.TopNz[x] + this.LeftNz[y];
res.SetCoeffs(levels);
- R += res.GetResidualCost(ctx);
- return R;
+ r += res.GetResidualCost(ctx);
+ return r;
+ }
+
+ public int GetCostUv(Vp8ModeScore rd, Vp8EncProba proba)
+ {
+ var res = new Vp8Residual();
+ int r = 0;
+
+ // re-import the non-zero context.
+ this.NzToBytes();
+
+ res.Init(0, 2, proba);
+ for (int ch = 0; ch <= 2; ch += 2)
+ {
+ for (int y = 0; y < 2; ++y)
+ {
+ for (int x = 0; x < 2; ++x)
+ {
+ int ctx = this.TopNz[4 + ch + x] + this.LeftNz[4 + ch + y];
+ res.SetCoeffs(rd.UvLevels.AsSpan((ch * 2) + x + (y * 2)));
+ r += res.GetResidualCost(ctx);
+ this.TopNz[4 + ch + x] = this.LeftNz[4 + ch + y] = (res.Last >= 0) ? 1 : 0;
+ }
+ }
+ }
+
+ return r;
}
public void SetIntraUvMode(int mode) => this.CurrentMacroBlockInfo.UvMode = mode;
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs
index a2c3a001ed..3481b26c17 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using SixLabors.ImageSharp.Formats.WebP.Lossy;
namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
@@ -45,23 +46,23 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
}
- this.LevelCost = new Vp8CostArray[WebpConstants.NumTypes][];
+ this.LevelCost = new Vp8Costs[WebpConstants.NumTypes][];
for (int i = 0; i < this.LevelCost.Length; i++)
{
- this.LevelCost[i] = new Vp8CostArray[WebpConstants.NumBands];
+ this.LevelCost[i] = new Vp8Costs[WebpConstants.NumBands];
for (int j = 0; j < this.LevelCost[i].Length; j++)
{
- this.LevelCost[i][j] = new Vp8CostArray();
+ this.LevelCost[i][j] = new Vp8Costs();
}
}
- this.RemappedCosts = new Vp8CostArray[WebpConstants.NumTypes][];
+ this.RemappedCosts = new Vp8Costs[WebpConstants.NumTypes][];
for (int i = 0; i < this.RemappedCosts.Length; i++)
{
- this.RemappedCosts[i] = new Vp8CostArray[16];
+ this.RemappedCosts[i] = new Vp8Costs[16];
for (int j = 0; j < this.RemappedCosts[i].Length; j++)
{
- this.RemappedCosts[i][j] = new Vp8CostArray();
+ this.RemappedCosts[i][j] = new Vp8Costs();
}
}
@@ -102,9 +103,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public Vp8Stats[][] Stats { get; }
- public Vp8CostArray[][] LevelCost { get; }
+ public Vp8Costs[][] LevelCost { get; }
- public Vp8CostArray[][] RemappedCosts { get; }
+ public Vp8Costs[][] RemappedCosts { get; }
///
/// Gets or sets the number of skipped blocks.
@@ -120,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
if (!this.Dirty)
{
- return; // nothing to do.
+ return; // Nothing to do.
}
for (int ctype = 0; ctype < WebpConstants.NumTypes; ++ctype)
@@ -130,17 +131,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
for (int ctx = 0; ctx < WebpConstants.NumCtx; ++ctx)
{
Vp8ProbaArray p = this.Coeffs[ctype][band].Probabilities[ctx];
- Span table = this.LevelCost[ctype][band].Costs.AsSpan(ctx * MaxVariableLevel);
+ Vp8CostArray table = this.LevelCost[ctype][band].Costs[ctx];
int cost0 = (ctx > 0) ? LossyUtils.Vp8BitCost(1, p.Probabilities[0]) : 0;
int costBase = LossyUtils.Vp8BitCost(1, p.Probabilities[1]) + cost0;
int v;
- table[0] = (ushort)(LossyUtils.Vp8BitCost(0, p.Probabilities[1]) + cost0);
+ table.Costs[0] = (ushort)(LossyUtils.Vp8BitCost(0, p.Probabilities[1]) + cost0);
for (v = 1; v <= MaxVariableLevel; ++v)
{
- table[v] = (ushort)(costBase + VariableLevelCost(v, p.Probabilities));
+ table.Costs[v] = (ushort)(costBase + VariableLevelCost(v, p.Probabilities));
}
- // Starting at level 67 and up, the variable part of the cost is actually constant.
+ // Starting at level 67 and up, the variable part of the cost is actually constant
}
}
@@ -148,9 +149,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
for (int ctx = 0; ctx < WebpConstants.NumCtx; ++ctx)
{
- Span dst = this.RemappedCosts[ctype][n].Costs.AsSpan(ctx * MaxVariableLevel, MaxVariableLevel);
- Span src = this.LevelCost[ctype][WebpConstants.Vp8EncBands[n]].Costs.AsSpan(ctx * MaxVariableLevel, MaxVariableLevel);
- src.CopyTo(dst);
+ Vp8CostArray dst = this.RemappedCosts[ctype][n].Costs[ctx];
+ Vp8CostArray src = this.LevelCost[ctype][WebpConstants.Vp8EncBands[n]].Costs[ctx];
+ src.Costs.CopyTo(dst.Costs.AsSpan());
}
}
}
@@ -170,7 +171,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
for (int p = 0; p < WebpConstants.NumProbas; ++p)
{
- var stats = this.Stats[t][b].Stats[c].Stats[p];
+ uint stats = this.Stats[t][b].Stats[c].Stats[p];
int nb = (int)((stats >> 0) & 0xffff);
int total = (int)((stats >> 16) & 0xffff);
int updateProba = WebpLookupTables.CoeffsUpdateProba[t, b, c, p];
@@ -234,10 +235,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
}
- private static int CalcSkipProba(long nb, long total)
- {
- return (int)(total != 0 ? (total - nb) * 255 / total : 255);
- }
+ private static int CalcSkipProba(long nb, long total) => (int)(total != 0 ? (total - nb) * 255 / total : 255);
private static int VariableLevelCost(int level, Span probas)
{
@@ -260,15 +258,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Collect statistics and deduce probabilities for next coding pass.
// Return the total bit-cost for coding the probability updates.
- private static int CalcTokenProba(int nb, int total)
- {
- return nb != 0 ? (255 - (nb * 255 / total)) : 255;
- }
+ private static int CalcTokenProba(int nb, int total) => nb != 0 ? (255 - (nb * 255 / total)) : 255;
// Cost of coding 'nb' 1's and 'total-nb' 0's using 'proba' probability.
- private static int BranchCost(int nb, int total, int proba)
- {
- return (nb * LossyUtils.Vp8BitCost(1, (byte)proba)) + ((total - nb) * LossyUtils.Vp8BitCost(0, (byte)proba));
- }
+ private static int BranchCost(int nb, int total, int proba) => (nb * LossyUtils.Vp8BitCost(1, (byte)proba)) + ((total - nb) * LossyUtils.Vp8BitCost(0, (byte)proba));
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
index 68cf2773ba..757babdc0a 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
@@ -63,6 +63,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private readonly byte[] averageBytesPerMb = { 50, 24, 16, 9, 7, 5, 3, 2 };
+ private readonly ushort[] weightY = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 };
+
private const int NumMbSegments = 4;
private const int NumBModes = 10;
@@ -268,8 +270,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
///
private int MbHeaderLimit { get; }
- private readonly ushort[] weightY = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 };
-
///
/// Encodes the image to the specified stream from the .
///
@@ -452,7 +452,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
it.SaveBoundary();
}
- while (it.Next());
+ while (it.Next() && --nbMbs > 0);
sizeP0 += this.SegmentHeader.Size;
if (stats.DoSizeSearch)
@@ -706,13 +706,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Initialize segments' filtering
this.SetupFilterStrength();
- this.SetupMatrices(dqm);
+ this.SetupMatrices(dqm, snsStrength);
}
private void SetupFilterStrength()
{
int filterSharpness = 0; // TODO: filterSharpness is hardcoded
- var filterType = 1; // TODO: filterType is hardcoded
+ int filterType = 1; // TODO: filterType is hardcoded
// level0 is in [0..500]. Using '-f 50' as filter_strength is mid-filtering.
int level0 = 5 * FilterStrength;
@@ -787,8 +787,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
proba.NbSkip = 0;
}
- private void SetupMatrices(Vp8SegmentInfo[] dqm)
+ private void SetupMatrices(Vp8SegmentInfo[] dqm, int snsStrength)
{
+ int tlambdaScale = (this.method >= 4) ? snsStrength : 0;
for (int i = 0; i < dqm.Length; ++i)
{
Vp8SegmentInfo m = dqm[i];
@@ -808,8 +809,26 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
m.Uv.Q[1] = WebpLookupTables.AcTable[Clip(q + this.DqUvAc, 0, 127)];
int qi4 = m.Y1.Expand(0);
- m.Y2.Expand(1); // qi16
- m.Uv.Expand(2); // quv
+ int qi16 = m.Y2.Expand(1);
+ int quv = m.Uv.Expand(2);
+
+ m.I4Penalty = 1000 * qi4 * qi4;
+
+ m.LambdaI16 = 3 * qi16 * qi16;
+ m.LambdaI4 = (3 * qi4 * qi4) >> 7;
+ m.LambdaUv = (3 * quv * quv) >> 6;
+ m.LambdaMode = (1 * qi4 * qi4) >> 7;
+ m.TLambda = (tlambdaScale * qi4) >> 5;
+
+ // none of these constants should be < 1.
+ m.LambdaI16 = m.LambdaI16 < 1 ? 1 : m.LambdaI16;
+ m.LambdaI4 = m.LambdaI4 < 1 ? 1 : m.LambdaI4;
+ m.LambdaUv = m.LambdaUv < 1 ? 1 : m.LambdaUv;
+ m.LambdaMode = m.LambdaMode < 1 ? 1 : m.LambdaMode;
+ m.TLambda = m.TLambda < 1 ? 1 : m.TLambda;
+
+ m.MinDisto = 20 * m.Y1.Q[0];
+ m.MaxEdge = 0;
m.I4Penalty = 1000 * qi4 * qi4;
}
@@ -864,15 +883,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
it.MakeLuma16Preds();
it.MakeChroma8Preds();
- if (rdOpt > Vp8RdLevel.RdOptNone)
+ // TODO: disabled picking best mode because its still bugged.
+ // if (rdOpt > Vp8RdLevel.RdOptNone)
+ if (false)
{
- this.PickBestIntra16(it, rd);
+ this.PickBestIntra16(it, ref rd);
if (this.method >= 2)
{
- this.PickBestIntra4(it, rd);
+ this.PickBestIntra4(it, ref rd);
}
- this.PickBestUv(it, rd);
+ this.PickBestUv(it, ref rd);
}
else
{
@@ -889,7 +910,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return isSkipped;
}
- private void PickBestIntra16(Vp8EncIterator it, Vp8ModeScore rd)
+ private void PickBestIntra16(Vp8EncIterator it, ref Vp8ModeScore rd)
{
const int numBlocks = 16;
Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment];
@@ -913,7 +934,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Measure RD-score.
rdCur.D = Vp8Sse16X16(src, tmpDst);
- rdCur.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto16x16(src, tmpDst, this.weightY)) : 0;
+ rdCur.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto16X16(src, tmpDst, this.weightY)) : 0;
rdCur.H = WebpConstants.Vp8FixedCostsI16[mode];
rdCur.R = this.GetCostLuma16(it, rdCur);
@@ -959,16 +980,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
}
- private bool PickBestIntra4(Vp8EncIterator it, Vp8ModeScore rd)
+ private bool PickBestIntra4(Vp8EncIterator it, ref Vp8ModeScore rd)
{
Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment];
- int lambda = dqm.LambdaI16;
+ int lambda = dqm.LambdaI4;
int tlambda = dqm.TLambda;
Span src0 = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc);
Span bestBlocks = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc);
int totalHeaderBits = 0;
var rdBest = new Vp8ModeScore();
- IMemoryOwner scratchBuffer = this.memoryAllocator.Allocate(512);
if (this.maxI4HeaderBits == 0)
{
@@ -988,7 +1008,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Span src = src0.Slice(WebpLookupTables.Vp8Scan[it.I4]);
short[] modeCosts = it.GetCostModeI4(rd.ModesI4);
Span bestBlock = bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4]);
- Span tmpDst = scratchBuffer.GetSpan();
+ Span tmpDst = it.Scratch.AsSpan();
+ tmpDst.Fill(0);
rdi4.InitScore();
it.MakeIntra4Preds();
@@ -1002,7 +1023,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Compute RD-score.
rdTmp.D = Vp8Sse4X4(src, tmpDst);
- rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto4x4(src, tmpDst, this.weightY)) : 0;
+ rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto4X4(src, tmpDst, this.weightY)) : 0;
rdTmp.H = modeCosts[mode];
// Add flatness penalty, to avoid flat area to be mispredicted by a complex mode.
@@ -1033,11 +1054,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Span tmp = tmpDst;
tmpDst = bestBlock;
bestBlock = tmp;
- tmpLevels.AsSpan(0, rdBest.YAcLevels[it.I4]).CopyTo(rdBest.YAcLevels);
+ tmpLevels.AsSpan().CopyTo(rdBest.YAcLevels);
}
}
- rdi4.SetRdScore(lambda);
+ rdi4.SetRdScore(dqm.LambdaMode);
rdBest.AddScore(rdi4);
if (rdBest.Score >= rd.Score)
{
@@ -1050,11 +1071,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return false;
}
- // Copy selected samples if not in the right place already.
- if (bestBlock != bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4]))
- {
- Vp8Copy4x4(bestBlock, bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4]));
- }
+ // Copy selected samples to the right place.
+ Vp8Copy4X4(bestBlock, bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4]));
rd.ModesI4[it.I4] = (byte)bestMode;
it.TopNz[it.I4 & 3] = it.LeftNz[it.I4 >> 2] = rdi4.Nz != 0 ? 1 : 0;
@@ -1071,9 +1089,56 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return true;
}
- private void PickBestUv(Vp8EncIterator it, Vp8ModeScore rd)
+ private void PickBestUv(Vp8EncIterator it, ref Vp8ModeScore rd)
{
+ const int numBlocks = 8;
+ Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment];
+ int lambda = dqm.LambdaUv;
+ Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc);
+ Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.UOffEnc);
+ Span dst0 = it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc);
+ Span dst = dst0;
+ var rdBest = new Vp8ModeScore();
+ int mode;
+ rd.ModeUv = -1;
+ rdBest.InitScore();
+ for (mode = 0; mode < NumPredModes; ++mode)
+ {
+ var rdUv = new Vp8ModeScore();
+
+ // Reconstruct
+ rdUv.Nz = (uint)this.ReconstructUv(it, dqm, rdUv, tmpDst, mode);
+
+ // Compute RD-score
+ rdUv.D = Vp8Sse16X8(src, tmpDst);
+ rdUv.SD = 0; // not calling TDisto here: it tends to flatten areas.
+ rdUv.H = WebpConstants.Vp8FixedCostsUv[mode];
+ rdUv.R = it.GetCostUv(rdUv, this.Proba);
+ if (mode > 0 && IsFlat(rdUv.UvLevels, numBlocks, WebpConstants.FlatnessLimitIUv))
+ {
+ rdUv.R += WebpConstants.FlatnessPenality * numBlocks;
+ }
+
+ rdUv.SetRdScore(lambda);
+ if (mode == 0 || rdUv.Score < rdBest.Score)
+ {
+ rdBest.CopyScore(rdUv);
+ rd.ModeUv = mode;
+ rdUv.UvLevels.CopyTo(rd.UvLevels.AsSpan());
+ Span tmp = dst;
+ dst = tmpDst;
+ tmpDst = tmp;
+ }
+ }
+
+ it.SetIntraUvMode(rd.ModeUv);
+ rd.AddScore(rdBest);
+ if (dst != dst0)
+ {
+ // copy 16x8 block if needed.
+ Vp8Copy16X8(dst, dst0);
+ }
}
// TODO: move to Vp8EncIterator
@@ -1358,7 +1423,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
int ctx = it.TopNz[4 + ch + x] + it.LeftNz[4 + ch + y];
residual.SetCoeffs(rd.UvLevels.AsSpan(16 * ((ch * 2) + x + (y * 2)), 16));
- var res = residual.RecordCoeffs(ctx);
+ int res = residual.RecordCoeffs(ctx);
it.TopNz[4 + ch + x] = res;
it.LeftNz[4 + ch + y] = res;
}
@@ -1552,7 +1617,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
[MethodImpl(InliningOptions.ShortMethod)]
- private static void Vp8Copy4x4(Span src, Span dst) => Copy(src, dst, 4, 4);
+ private static void Vp8Copy4X4(Span src, Span dst) => Copy(src, dst, 4, 4);
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static void Vp8Copy16X8(Span src, Span dst) => Copy(src, dst, 16, 8);
[MethodImpl(InliningOptions.ShortMethod)]
private static void Copy(Span src, Span dst, int w, int h)
@@ -1566,23 +1634,22 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
[MethodImpl(InliningOptions.ShortMethod)]
- private static int Vp8Disto16x16(Span a, Span b, Span w)
+ private static int Vp8Disto16X16(Span a, Span b, Span w)
{
- int D = 0;
- int x, y;
- for (y = 0; y < 16 * WebpConstants.Bps; y += 4 * WebpConstants.Bps)
+ int d = 0;
+ for (int y = 0; y < 16 * WebpConstants.Bps; y += 4 * WebpConstants.Bps)
{
- for (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);
}
}
- return D;
+ return d;
}
[MethodImpl(InliningOptions.ShortMethod)]
- private static int Vp8Disto4x4(Span a, Span b, Span w)
+ private static int Vp8Disto4X4(Span a, Span b, Span w)
{
int sum1 = LossyUtils.TTransform(a, w);
int sum2 = LossyUtils.TTransform(b, w);
@@ -1605,7 +1672,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
}
- levels = levels.Slice(16, 16);
+ levels = levels.Slice(16);
}
return true;
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs
index fce1570447..7bb917a6d7 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ProbaArray.cs
@@ -11,10 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
///
/// Initializes a new instance of the class.
///
- public Vp8ProbaArray()
- {
- this.Probabilities = new byte[WebpConstants.NumProbas];
- }
+ public Vp8ProbaArray() => this.Probabilities = new byte[WebpConstants.NumProbas];
///
/// Gets the probabilities.
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs
index 76cea1a034..4a48a94cac 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using SixLabors.ImageSharp.Formats.WebP.Lossy;
namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
@@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public Vp8Stats[] Stats { get; set; }
- public ushort[] Costs { get; set; }
+ public Vp8Costs[] Costs { get; set; }
public void Init(int first, int coeffType, Vp8EncProba prob)
{
@@ -30,10 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.CoeffType = coeffType;
this.Prob = prob.Coeffs[this.CoeffType];
this.Stats = prob.Stats[this.CoeffType];
- this.Costs = new ushort[WebpConstants.NumCtx * (WebpConstants.MaxVariableLevel + 1)];
-
- // TODO:
- // res->costs = enc->proba_.remapped_costs_[coeff_type];
+ this.Costs = prob.RemappedCosts[this.CoeffType];
}
public void SetCoeffs(Span coeffs)
@@ -117,8 +115,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
int n = this.First;
int p0 = this.Prob[n].Probabilities[ctx0].Probabilities[0];
- ushort[] costs = this.Costs;
- Span t = costs.AsSpan(n * ctx0);
+ Vp8Costs[] costs = this.Costs;
+ Vp8CostArray t = costs[n].Costs[ctx0];
// bitCost(1, p0) is already incorporated in t[] tables, but only if ctx != 0
// (as required by the syntax). For ctx0 == 0, we need to add it here or it'll
@@ -135,19 +133,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
v = Math.Abs(this.Coeffs[n]);
int ctx = (v >= 2) ? 2 : v;
- cost += LevelCost(t, v);
- t[0] = costs[(n + 1) * ctx];
+ cost += LevelCost(t.Costs, v);
+ t = costs[n + 1].Costs[ctx];
}
// Last coefficient is always non-zero
v = Math.Abs(this.Coeffs[n]);
- cost += LevelCost(t, v);
+ cost += LevelCost(t.Costs, v);
if (n < 15)
{
int b = WebpConstants.Vp8EncBands[n + 1];
int ctx = (v == 1) ? 1 : 2;
- int last_p0 = this.Prob[b].Probabilities[ctx].Probabilities[0];
- cost += LossyUtils.Vp8BitCost(0, (byte)last_p0);
+ int lastP0 = this.Prob[b].Probabilities[ctx].Probabilities[0];
+ cost += LossyUtils.Vp8BitCost(0, (byte)lastP0);
}
return cost;
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs
index bb04eaa115..a9d2464ae3 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs
@@ -59,8 +59,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public int LambdaI16 { get; set; }
+ public int LambdaI4 { get; set; }
+
public int TLambda { get; set; }
+ public int LambdaUv { get; set; }
+
public int LambdaMode { get; set; }
public void StoreMaxDelta(Span dcs)