From caed173b389e15647eca99bc29c6962c596abf93 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 21 Jun 2021 20:19:58 +0200 Subject: [PATCH] Add PickBestIntra4 --- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 49 +++--- .../Formats/WebP/Lossy/Vp8Encoder.cs | 154 ++++++++++++++++-- .../Formats/WebP/Lossy/Vp8ModeScore.cs | 20 +++ src/ImageSharp/Formats/WebP/WebpConstants.cs | 8 + 4 files changed, 186 insertions(+), 45 deletions(-) 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;