From 96bb6665229cfdc4946b1d96958a9e8052f805c0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 22 Jun 2021 12:03:26 +0200 Subject: [PATCH] Add PickBestUv --- .../Formats/WebP/Lossy/LossyUtils.cs | 26 ++-- .../Formats/WebP/Lossy/Vp8CostArray.cs | 5 +- src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs | 28 ++++ .../Formats/WebP/Lossy/Vp8EncIterator.cs | 42 ++++- .../Formats/WebP/Lossy/Vp8EncProba.cs | 50 +++--- .../Formats/WebP/Lossy/Vp8Encoder.cs | 143 +++++++++++++----- .../Formats/WebP/Lossy/Vp8ProbaArray.cs | 5 +- .../Formats/WebP/Lossy/Vp8Residual.cs | 22 ++- .../Formats/WebP/Lossy/Vp8SegmentInfo.cs | 4 + 9 files changed, 223 insertions(+), 102 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8Costs.cs 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)