From 6eee467c623a1788e71f9dc1114ac8d7bffd67a4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 30 Oct 2020 19:25:21 +0100 Subject: [PATCH] CalculateLevelCosts --- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 8 +- .../Formats/WebP/Lossy/Vp8CostArray.cs | 18 ++ .../Formats/WebP/Lossy/Vp8EncProba.cs | 169 ++++++++++++++++++ .../Formats/WebP/Lossy/Vp8Encoder.cs | 45 +++-- .../Formats/WebP/Lossy/Vp8Residual.cs | 18 +- .../Formats/WebP/WebPLookupTables.cs | 51 ++++++ 6 files changed, 280 insertions(+), 29 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index 4e6260aa23..1150168eb7 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter public int PutCoeffs(int ctx, Vp8Residual residual) { int n = residual.First; - Vp8ProbaArray p = residual.Prob[n][ctx]; + Vp8ProbaArray p = residual.Prob[n].Probabilities[ctx]; if (!this.PutBit(residual.Last >= 0, p.Probabilities[0])) { return 0; @@ -68,13 +68,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter int v = sign ? -c : c; if (!this.PutBit(v != 0, p.Probabilities[1])) { - p = residual.Prob[WebPConstants.Bands[n]][0]; + p = residual.Prob[WebPConstants.Bands[n]].Probabilities[0]; continue; } if (!this.PutBit(v > 1, p.Probabilities[2])) { - p = residual.Prob[WebPConstants.Bands[n]][1]; + p = residual.Prob[WebPConstants.Bands[n]].Probabilities[1]; } else { @@ -150,7 +150,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter } } - p = residual.Prob[WebPConstants.Bands[n]][2]; + p = residual.Prob[WebPConstants.Bands[n]].Probabilities[2]; } this.PutBitUniform(sign ? 1 : 0); diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs new file mode 100644 index 0000000000..f0ff859898 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8CostArray.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal class Vp8CostArray + { + /// + /// Initializes a new instance of the class. + /// + public Vp8CostArray() + { + this.Costs = new ushort[WebPConstants.NumCtx * (67 + 1)]; + } + + public ushort[] Costs { get; } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs new file mode 100644 index 0000000000..8ae019ef6c --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs @@ -0,0 +1,169 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal class Vp8EncProba + { + /// + /// Last (inclusive) level with variable cost. + /// + private const int MaxVariableLevel = 67; + + /// + /// Initializes a new instance of the class. + /// + public Vp8EncProba() + { + this.Dirty = true; + this.UseSkipProba = false; + this.Segments = new byte[3]; + this.Coeffs = new Vp8BandProbas[WebPConstants.NumTypes][]; + for (int i = 0; i < this.Coeffs.Length; i++) + { + this.Coeffs[i] = new Vp8BandProbas[WebPConstants.NumBands]; + for (int j = 0; j < this.Coeffs[i].Length; j++) + { + this.Coeffs[i][j] = new Vp8BandProbas(); + } + } + + this.LevelCost = new Vp8CostArray[WebPConstants.NumTypes][]; + for (int i = 0; i < this.LevelCost.Length; i++) + { + this.LevelCost[i] = new Vp8CostArray[WebPConstants.NumBands]; + for (int j = 0; j < this.LevelCost[i].Length; j++) + { + this.LevelCost[i][j] = new Vp8CostArray(); + } + } + + this.RemappedCosts = new Vp8CostArray[WebPConstants.NumTypes][]; + for (int i = 0; i < this.RemappedCosts.Length; i++) + { + this.RemappedCosts[i] = new Vp8CostArray[16]; + for (int j = 0; j < this.RemappedCosts[i].Length; j++) + { + this.RemappedCosts[i][j] = new Vp8CostArray(); + } + } + + // Initialize with default probabilities. + this.Segments.AsSpan().Fill(255); + for (int t = 0; t < WebPConstants.NumTypes; ++t) + { + for (int b = 0; b < WebPConstants.NumBands; ++b) + { + for (int c = 0; c < WebPConstants.NumCtx; ++c) + { + Vp8ProbaArray dst = this.Coeffs[t][b].Probabilities[c]; + for (int p = 0; p < WebPConstants.NumProbas; ++p) + { + dst.Probabilities[p] = WebPLookupTables.DefaultCoeffsProba[t, b, c, p]; + } + } + } + } + } + + /// + /// Gets the probabilities for segment tree. + /// + public byte[] Segments { get; } + + /// + /// Gets the final probability of being skipped. + /// + public byte SkipProba { get; } + + /// + /// Gets a value indicating whether to use the skip probability. Note: we always use SkipProba for now. + /// + public bool UseSkipProba { get; } + + public Vp8BandProbas[][] Coeffs { get; } + + public Vp8CostArray[][] LevelCost { get; } + + public Vp8CostArray[][] RemappedCosts { get; } + + /// + /// Gets or sets the number of skipped blocks. + /// + public int NbSkip { get; set; } + + /// + /// Gets or sets a value indicating whether CalculateLevelCosts() needs to be called. + /// + public bool Dirty { get; set; } + + public void CalculateLevelCosts() + { + if (!this.Dirty) + { + return; // nothing to do. + } + + for (int ctype = 0; ctype < WebPConstants.NumTypes; ++ctype) + { + for (int band = 0; band < WebPConstants.NumBands; ++band) + { + 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); + int cost0 = (ctx > 0) ? this.BitCost(1, p.Probabilities[0]) : 0; + int costBase = this.BitCost(1, p.Probabilities[1]) + cost0; + int v; + table[0] = (ushort)(this.BitCost(0, p.Probabilities[1]) + cost0); + for (v = 1; v <= MaxVariableLevel; ++v) + { + table[v] = (ushort)(costBase + this.VariableLevelCost(v, p.Probabilities)); + } + + // Starting at level 67 and up, the variable part of the cost is actually constant. + } + } + + for (int n = 0; n < 16; ++n) + { + 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.Bands[n]].Costs.AsSpan(ctx * MaxVariableLevel, MaxVariableLevel); + src.CopyTo(dst); + } + } + } + + this.Dirty = false; + } + + private int VariableLevelCost(int level, Span probas) + { + int pattern = WebPLookupTables.Vp8LevelCodes[level - 1][0]; + int bits = WebPLookupTables.Vp8LevelCodes[level - 1][1]; + int cost = 0; + for (int i = 2; pattern != 0; ++i) + { + if ((pattern & 1) != 0) + { + cost += this.BitCost(bits & 1, probas[i]); + } + + bits >>= 1; + pattern >>= 1; + } + + return cost; + } + + // Cost of coding one event with probability 'proba'. + private int BitCost(int bit, byte proba) + { + return bit == 0 ? WebPLookupTables.Vp8EntropyCost[proba] : WebPLookupTables.Vp8EntropyCost[255 - proba]; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index fd3f541c70..c9426af16b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -62,6 +62,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private Vp8MacroBlockInfo[] mbInfo; + /// + /// Probabilities. + /// + private Vp8EncProba proba; + private int dqUvDc; private int dqUvAc; @@ -130,6 +135,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.mbInfo[i] = new Vp8MacroBlockInfo(); } + this.proba = new Vp8EncProba(); + // this.Preds = this.memoryAllocator.Allocate(predSize); this.Preds = this.memoryAllocator.Allocate(predSize * 2); // TODO: figure out how much mem we need here. This is too much. this.predsWidth = (4 * this.mbw) + 1; @@ -223,6 +230,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.AssignSegments(segmentInfos, alphas); this.SetSegmentParams(segmentInfos); this.SetSegmentProbas(segmentInfos); + this.ResetStats(); it.Init(); it.InitFilter(); do @@ -288,10 +296,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy var distAccum = new int[NumMbSegments]; // Bracket the input. - for (n = 0; n <= WebPConstants.MaxAlpha && alphas[n] == 0; ++n) { } + for (n = 0; n <= WebPConstants.MaxAlpha && alphas[n] == 0; ++n) + { + } minA = n; - for (n = WebPConstants.MaxAlpha; n > minA && alphas[n] == 0; --n) { } + for (n = WebPConstants.MaxAlpha; n > minA && alphas[n] == 0; --n) + { + } maxA = n; rangeA = maxA - minA; @@ -407,8 +419,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int nb = this.segmentHeader.NumSegments; int snsStrength = 50; // TODO: Spatial Noise Shaping, hardcoded for now. double amp = WebPConstants.SnsToDq * snsStrength / 100.0d / 128.0d; - double Q = this.quality / 100.0d; - double cBase = this.QualityToCompression(Q); + double cBase = this.QualityToCompression(this.quality / 100.0d); for (int i = 0; i < nb; ++i) { // We modulate the base coefficient to accommodate for the quantization @@ -430,6 +441,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // and make it safe. this.dqUvAc = this.Clip(this.dqUvAc, WebPConstants.QuantEncMinDqUv, WebPConstants.QuantEncMaxDqUv); + // We also boost the dc-uv-quant a little, based on sns-strength, since + // U/V channels are quite more reactive to high quants (flat DC-blocks + // tend to appear, and are unpleasant). + this.dqUvDc = -4 * snsStrength / 100; + this.dqUvDc = this.Clip(this.dqUvDc, -15, 15); // 4bit-signed max allowed + this.SetupMatrices(dqm); } @@ -441,6 +458,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // TODO: SetSegmentProbas } + private void ResetStats() + { + Vp8EncProba proba = this.proba; + proba.CalculateLevelCosts(); + proba.NbSkip = 0; + } + private void SetupMatrices(Vp8SegmentInfo[] dqm) { for (int i = 0; i < dqm.Length; ++i) @@ -687,15 +711,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy var pos1 = this.bitWriter.Pos; if (i16) { - residual.Init(0, 1); + residual.Init(0, 1, this.proba); residual.SetCoeffs(rd.YDcLevels); int res = this.bitWriter.PutCoeffs(it.TopNz[8] + it.LeftNz[8], residual); it.TopNz[8] = it.LeftNz[8] = res; - residual.Init(1, 0); + residual.Init(1, 0, this.proba); } else { - residual.Init(0, 3); + residual.Init(0, 3, this.proba); } // luma-AC @@ -713,7 +737,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy var pos2 = this.bitWriter.Pos; // U/V - residual.Init(0, 2); + residual.Init(0, 2, this.proba); for (ch = 0; ch <= 2; ch += 2) { for (y = 0; y < 2; ++y) @@ -789,11 +813,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); int nz = 0; int n; - var tmp = new short[8* 16]; + var tmp = new short[8 * 16]; for (n = 0; n < 8; n += 2) { - this.FTransform2(src.Slice(WebPLookupTables.Vp8ScanUv[n]), + this.FTransform2( + src.Slice(WebPLookupTables.Vp8ScanUv[n]), reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp.AsSpan(n * 16, 16), tmp.AsSpan((n + 1) * 16, 16)); diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs index ccc4471eee..96efe7f4f2 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs @@ -10,18 +10,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// internal class Vp8Residual { - /// - /// Initializes a new instance of the class. - /// - public Vp8Residual() - { - this.Prob = new Vp8ProbaArray[3][]; - for (int i = 0; i < 3; i++) - { - this.Prob[i] = new Vp8ProbaArray[11]; - } - } - public int First { get; set; } public int Last { get; set; } @@ -30,15 +18,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy public short[] Coeffs { get; set; } - public Vp8ProbaArray[][] Prob { get; } + public Vp8BandProbas[] Prob { get; set; } - public void Init(int first, int coeffType) + public void Init(int first, int coeffType, Vp8EncProba prob) { this.First = first; this.CoeffType = coeffType; + this.Prob = prob.Coeffs[this.CoeffType]; // TODO: - // res->prob = enc->proba_.coeffs_[coeff_type]; // res->stats = enc->proba_.stats_[coeff_type]; // res->costs = enc->proba_.remapped_costs_[coeff_type]; } diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index 6474d812d5..a550903e09 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -81,6 +81,57 @@ namespace SixLabors.ImageSharp.Formats.WebP 241, 243, 245, 247, 249, 251, 253, 127 }; + public static readonly ushort[] Vp8EntropyCost = + { + 1792, 1792, 1792, 1536, 1536, 1408, 1366, 1280, 1280, 1216, + 1178, 1152, 1110, 1076, 1061, 1024, 1024, 992, 968, 951, + 939, 911, 896, 878, 871, 854, 838, 820, 811, 794, + 786, 768, 768, 752, 740, 732, 720, 709, 704, 690, + 683, 672, 666, 655, 647, 640, 631, 622, 615, 607, + 598, 592, 586, 576, 572, 564, 559, 555, 547, 541, + 534, 528, 522, 512, 512, 504, 500, 494, 488, 483, + 477, 473, 467, 461, 458, 452, 448, 443, 438, 434, + 427, 424, 419, 415, 410, 406, 403, 399, 394, 390, + 384, 384, 377, 374, 370, 366, 362, 359, 355, 351, + 347, 342, 342, 336, 333, 330, 326, 323, 320, 316, + 312, 308, 305, 302, 299, 296, 293, 288, 287, 283, + 280, 277, 274, 272, 268, 266, 262, 256, 256, 256, + 251, 248, 245, 242, 240, 237, 234, 232, 228, 226, + 223, 221, 218, 216, 214, 211, 208, 205, 203, 201, + 198, 196, 192, 191, 188, 187, 183, 181, 179, 176, + 175, 171, 171, 168, 165, 163, 160, 159, 156, 154, + 152, 150, 148, 146, 144, 142, 139, 138, 135, 133, + 131, 128, 128, 125, 123, 121, 119, 117, 115, 113, + 111, 110, 107, 105, 103, 102, 100, 98, 96, 94, + 92, 91, 89, 86, 86, 83, 82, 80, 77, 76, + 74, 73, 71, 69, 67, 66, 64, 63, 61, 59, + 57, 55, 54, 52, 51, 49, 47, 46, 44, 43, + 41, 40, 38, 36, 35, 33, 32, 30, 29, 27, + 25, 24, 22, 21, 19, 18, 16, 15, 13, 12, + 10, 9, 7, 6, 4, 3 + }; + + public static readonly ushort[][] Vp8LevelCodes = + { + new ushort[] { 0x001, 0x000 }, new ushort[] { 0x007, 0x001 }, new ushort[] { 0x00f, 0x005 }, + new ushort[] { 0x00f, 0x00d }, new ushort[] { 0x033, 0x003 }, new ushort[] { 0x033, 0x003 }, new ushort[] { 0x033, 0x023 }, + new ushort[] { 0x033, 0x023 }, new ushort[] { 0x033, 0x023 }, new ushort[] { 0x033, 0x023 }, new ushort[] { 0x0d3, 0x013 }, + new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, + new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x093 }, + new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, + new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, + new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, + new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x153 }, + }; + /// /// Lookup table for small values of log2(int). ///