diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
index 2484ba8779..d4ef150f61 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
@@ -454,29 +454,32 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return WebpLookupTables.Vp8FixedCostsI4[top, left];
}
- public void SetIntraUvMode(int mode)
+ public int GetCostLuma4(short[] levels, Vp8EncProba proba)
{
- this.CurrentMacroBlockInfo.UvMode = mode;
- }
+ int x = this.I4 & 3;
+ int y = this.I4 >> 2;
+ var res = new Vp8Residual();
+ int R = 0;
+ int ctx;
- public void SetSkip(bool skip)
- {
- this.CurrentMacroBlockInfo.Skip = skip;
+ res.Init(0, 3, proba);
+ ctx = this.TopNz[x] + this.LeftNz[y];
+ res.SetCoeffs(levels);
+ R += res.GetResidualCost(ctx);
+ return R;
}
- public void SetSegment(int segment)
- {
- this.CurrentMacroBlockInfo.Segment = segment;
- }
+ public void SetIntraUvMode(int mode) => this.CurrentMacroBlockInfo.UvMode = mode;
+
+ public void SetSkip(bool skip) => this.CurrentMacroBlockInfo.Skip = skip;
+
+ public void SetSegment(int segment) => this.CurrentMacroBlockInfo.Segment = segment;
///
/// Returns true if iteration is finished.
///
/// True if iterator is finished.
- public bool IsDone()
- {
- return this.CountDown <= 0;
- }
+ public bool IsDone() => this.CountDown <= 0;
///
/// Go to next macroblock.
@@ -588,7 +591,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
else
{
- this.Nz[this.nzIdx] &= 1 << 24; // Preserve the dc_nz bit.
+ // Preserve the dc_nz bit.
+ this.Nz[this.nzIdx] &= 1 << 24;
}
}
@@ -606,10 +610,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
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);
public void SwapOut()
{
@@ -802,18 +803,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.TopDerr.AsSpan().Fill(0);
}
- private int Bit(uint nz, int n)
- {
- return (nz & (1 << n)) != 0 ? 1 : 0;
- }
+ private int Bit(uint nz, int n) => (nz & (1 << n)) != 0 ? 1 : 0;
///
/// Set count down.
///
/// Number of iterations to go.
- private void SetCountDown(int countDown)
- {
- this.CountDown = countDown;
- }
+ private void SetCountDown(int countDown) => this.CountDown = countDown;
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
index 4b8c476f3e..68cf2773ba 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
@@ -65,6 +65,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private const int NumMbSegments = 4;
+ private const int NumBModes = 10;
+
+ ///
+ /// The number of prediction modes.
+ ///
+ private const int NumPredModes = 4;
+
private const int MaxItersKMeans = 6;
// Convergence is considered reached if dq < DqLimit
@@ -81,11 +88,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// TODO: filterStrength is hardcoded, should be configurable.
private const int FilterStrength = 60;
- ///
- /// I16 mode (special case).
- ///
- private const int FlatenessLimitI16 = 0;
-
///
/// Initializes a new instance of the class.
///
@@ -266,12 +268,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
///
private int MbHeaderLimit { get; }
- ///
- /// The number of prediction modes.
- ///
- private const int NumPredModes = 4;
-
- private readonly ushort[] WeightY = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 };
+ 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 .
@@ -916,14 +913,14 @@ 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);
if (isFlat)
{
// Refine the first impression (which was in pixel space).
- isFlat = IsFlat(rdCur.YAcLevels, numBlocks, FlatenessLimitI16);
+ isFlat = IsFlat(rdCur.YAcLevels, numBlocks, WebpConstants.FlatnessLimitI16);
if (isFlat)
{
// Block is very flat. We put emphasis on the distortion being very low!
@@ -962,9 +959,116 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
}
- private void PickBestIntra4(Vp8EncIterator it, Vp8ModeScore rd)
+ private bool PickBestIntra4(Vp8EncIterator it, Vp8ModeScore rd)
{
+ Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment];
+ int lambda = dqm.LambdaI16;
+ 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)
+ {
+ return false;
+ }
+
+ rdBest.InitScore();
+ rdBest.H = 211; // '211' is the value of VP8BitCost(0, 145)
+ rdBest.SetRdScore(dqm.LambdaMode);
+ it.StartI4();
+ do
+ {
+ int numBlocks = 1;
+ var rdi4 = new Vp8ModeScore();
+ int mode;
+ int bestMode = -1;
+ 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();
+
+ rdi4.InitScore();
+ it.MakeIntra4Preds();
+ for (mode = 0; mode < NumBModes; ++mode)
+ {
+ var rdTmp = new Vp8ModeScore();
+ short[] tmpLevels = new short[16];
+
+ // Reconstruct.
+ rdTmp.Nz = (uint)this.ReconstructIntra4(it, dqm, tmpLevels, src, tmpDst, mode);
+
+ // Compute RD-score.
+ rdTmp.D = Vp8Sse4X4(src, tmpDst);
+ 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.
+ if (mode > 0 && IsFlat(tmpLevels, numBlocks, WebpConstants.FlatnessLimitI4))
+ {
+ rdTmp.R = WebpConstants.FlatnessPenality * numBlocks;
+ }
+ else
+ {
+ rdTmp.R = 0;
+ }
+
+ // early-out check.
+ rdTmp.SetRdScore(lambda);
+ if (bestMode >= 0 && rdTmp.Score >= rdi4.Score)
+ {
+ continue;
+ }
+
+ // finish computing score.
+ rdTmp.R += it.GetCostLuma4(tmpLevels, this.Proba);
+ rdTmp.SetRdScore(lambda);
+
+ if (bestMode < 0 || rdTmp.Score < rdi4.Score)
+ {
+ rdi4.CopyScore(rdTmp);
+ bestMode = mode;
+ Span tmp = tmpDst;
+ tmpDst = bestBlock;
+ bestBlock = tmp;
+ tmpLevels.AsSpan(0, rdBest.YAcLevels[it.I4]).CopyTo(rdBest.YAcLevels);
+ }
+ }
+
+ rdi4.SetRdScore(lambda);
+ rdBest.AddScore(rdi4);
+ if (rdBest.Score >= rd.Score)
+ {
+ return false;
+ }
+
+ totalHeaderBits += (int)rdi4.H; // <- equal to modeCosts[bestMode];
+ if (totalHeaderBits > this.maxI4HeaderBits)
+ {
+ 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]));
+ }
+
+ rd.ModesI4[it.I4] = (byte)bestMode;
+ it.TopNz[it.I4 & 3] = it.LeftNz[it.I4 >> 2] = rdi4.Nz != 0 ? 1 : 0;
+ }
+ while (it.RotateI4(bestBlocks));
+
+ // Finalize state.
+ rd.CopyScore(rdBest);
+ it.SetIntra4Mode(rd.ModesI4);
+ it.SwapOut();
+ rdBest.YAcLevels.AsSpan().CopyTo(rd.YAcLevels);
+
+ // Select intra4x4 over intra16x16.
+ return true;
}
private void PickBestUv(Vp8EncIterator it, Vp8ModeScore rd)
@@ -972,6 +1076,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
+ // TODO: move to Vp8EncIterator
private int GetCostLuma16(Vp8EncIterator it, Vp8ModeScore rd)
{
var res = new Vp8Residual();
@@ -1019,7 +1124,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
long bitLimit = tryBothModes
? this.MbHeaderLimit
: Vp8ModeScore.MaxCost; // no early-out allowed.
- const int numBModes = 10;
if (isI16)
{
@@ -1072,7 +1176,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
short[] modeCosts = it.GetCostModeI4(rd.ModesI4);
it.MakeIntra4Preds();
- for (mode = 0; mode < numBModes; ++mode)
+ for (mode = 0; mode < NumBModes; ++mode)
{
Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]);
long score = (Vp8Sse4X4(src, reference) * WebpConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4);
@@ -1447,6 +1551,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return count;
}
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static void Vp8Copy4x4(Span src, Span dst) => Copy(src, dst, 4, 4);
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static void Copy(Span src, Span dst, int w, int h)
+ {
+ for (int y = 0; y < h; ++y)
+ {
+ src.Slice(0, w).CopyTo(dst);
+ src = src.Slice(WebpConstants.Bps);
+ dst = dst.Slice(WebpConstants.Bps);
+ }
+ }
+
[MethodImpl(InliningOptions.ShortMethod)]
private static int Vp8Disto16x16(Span a, Span b, Span w)
{
@@ -1456,7 +1574,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
for (x = 0; x < 16; x += 4)
{
- D += Disto4x4(a.Slice(x + y), b.Slice(x + y), w);
+ D += Vp8Disto4x4(a.Slice(x + y), b.Slice(x + y), w);
}
}
@@ -1464,7 +1582,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
[MethodImpl(InliningOptions.ShortMethod)]
- private static int Disto4x4(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);
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs
index 90a8997cb6..e47fa71609 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs
@@ -97,6 +97,26 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.Score = MaxCost;
}
+ public void CopyScore(Vp8ModeScore other)
+ {
+ this.D = other.D;
+ this.SD = other.SD;
+ this.R = other.R;
+ this.H = other.H;
+ this.Nz = other.Nz; // note that nz is not accumulated, but just copied.
+ this.Score = other.Score;
+ }
+
+ public void AddScore(Vp8ModeScore other)
+ {
+ this.D += other.D;
+ this.SD += other.SD;
+ this.R += other.R;
+ this.H += other.H;
+ this.Nz |= other.Nz; // here, new nz bits are accumulated.
+ this.Score += other.Score;
+ }
+
public void SetRdScore(int lambda) => this.Score = ((this.R + this.H) * lambda) + (RdDistoMult * (this.D + this.SD));
}
}
diff --git a/src/ImageSharp/Formats/WebP/WebpConstants.cs b/src/ImageSharp/Formats/WebP/WebpConstants.cs
index 81434d8752..88aa7728af 100644
--- a/src/ImageSharp/Formats/WebP/WebpConstants.cs
+++ b/src/ImageSharp/Formats/WebP/WebpConstants.cs
@@ -224,6 +224,14 @@ namespace SixLabors.ImageSharp.Formats.Webp
public const int MaxVariableLevel = 67;
+ public const int FlatnessLimitI16 = 0;
+
+ public const int FlatnessLimitIUv = 2;
+
+ public const int FlatnessLimitI4 = 3;
+
+ public const int FlatnessPenality = 140;
+
// This is the common stride for enc/dec.
public const int Bps = 32;