From 65fb30adc90b52c42e221985660dc306427fa2a1 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 26 Oct 2020 11:24:24 +0100 Subject: [PATCH] Refine intra16/intra4 sub-modes based on distortion (still WIP) --- .../Formats/WebP/Lossy/LossyUtils.cs | 36 +- .../Formats/WebP/Lossy/Vp8EncIterator.cs | 599 +++++++++++++++++- .../Formats/WebP/Lossy/Vp8Encoder.cs | 301 ++++++++- .../Formats/WebP/Lossy/Vp8ModeScore.cs | 104 +++ src/ImageSharp/Formats/WebP/WebPConstants.cs | 9 + .../Formats/WebP/WebPLookupTables.cs | 116 ++++ 6 files changed, 1101 insertions(+), 64 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index 5d8d8b545f..83e8e95e1a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -722,6 +722,24 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); } + [MethodImpl(InliningOptions.ShortMethod)] + public static byte Avg2(byte a, byte b) + { + return (byte)((a + b + 1) >> 1); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static byte Avg3(byte a, byte b, byte c) + { + return (byte)((a + (2 * b) + c + 2) >> 2); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void Dst(Span dst, int x, int y, byte v) + { + dst[x + (y * WebPConstants.Bps)] = v; + } + // Complex In-loop filtering (Paragraph 15.3) private static void FilterLoop24( Span p, @@ -949,24 +967,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - [MethodImpl(InliningOptions.ShortMethod)] - private static byte Avg2(byte a, byte b) - { - return (byte)((a + b + 1) >> 1); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static byte Avg3(byte a, byte b, byte c) - { - return (byte)((a + (2 * b) + c + 2) >> 2); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Dst(Span dst, int x, int y, byte v) - { - dst[x + (y * WebPConstants.Bps)] = v; - } - [MethodImpl(InliningOptions.ShortMethod)] private static int Clamp255(int x) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index a87caa34b0..f1160d8f12 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Buffers.Binary; using SixLabors.ImageSharp.Formats.WebP.Lossless; using SixLabors.ImageSharp.Memory; @@ -12,13 +13,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// Iterator structure to iterate through macroblocks, pointing to the /// right neighbouring data (samples, predictions, contexts, ...) /// - internal class Vp8EncIterator : IDisposable + internal class Vp8EncIterator { - private const int YOffEnc = 0; + public const int YOffEnc = 0; - private const int UOffEnc = 16; + public const int UOffEnc = 16; - private const int VOffEnc = 16 + 8; + public const int VOffEnc = 16 + 8; private const int MaxUvMode = 2; @@ -51,12 +52,43 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private const int C8HE8 = C8VE8 + (1 * 16); - private readonly int[] vp8I16ModeOffsets = { I16DC16, I16TM16, I16VE16, I16HE16 }; + public static readonly int[] Vp8I16ModeOffsets = { I16DC16, I16TM16, I16VE16, I16HE16 }; - private readonly int[] vp8UvModeOffsets = { C8DC8, C8TM8, C8VE8, C8HE8 }; + public static readonly int[] Vp8UvModeOffsets = { C8DC8, C8TM8, C8VE8, C8HE8 }; + + private const int I4DC4 = (3 * 16 * WebPConstants.Bps) + 0; + + private const int I4TM4 = I4DC4 + 4; + + private const int I4VE4 = I4DC4 + 8; + + private const int I4HE4 = I4DC4 + 12; + + private const int I4RD4 = I4DC4 + 16; + + private const int I4VR4 = I4RD4 + 20; + + private const int I4LD4 = I4RD4 + 24; + + private const int I4VL4 = I4RD4 + 28; + + private const int I4HD4 = (3 * 16 * WebPConstants.Bps) + (4 * WebPConstants.Bps); + + private const int I4HU4 = I4HD4 + 4; + + public static readonly int[] Vp8I4ModeOffsets = { I4DC4, I4TM4, I4VE4, I4HE4, I4RD4, I4VR4, I4LD4, I4VL4, I4HD4, I4HU4 }; private readonly byte[] clip1 = new byte[255 + 510 + 1]; // clips [-255,510] to [0,255] + // Array to record the position of the top sample to pass to the prediction functions. + private readonly byte[] vp8TopLeftI4 = + { + 17, 21, 25, 29, + 13, 17, 21, 25, + 9, 13, 17, 21, + 5, 9, 13, 17 + }; + private int currentMbIdx; private int nzIdx; @@ -90,6 +122,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.UvLeft = new byte[32]; this.TopNz = new int[9]; this.LeftNz = new int[9]; + this.I4Boundary = new byte[37]; // To match the C++ initial values of the reference implementation, initialize all with 204. byte defaultInitVal = 204; @@ -158,25 +191,40 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// public IMemoryOwner UvTop { get; set; } + /// + /// Gets or sets the intra mode predictors (4x4 blocks). + /// + public IMemoryOwner Preds { get; set; } + /// /// Gets or sets the non-zero pattern. /// public IMemoryOwner Nz { get; set; } /// - /// Gets or sets the intra mode predictors (4x4 blocks). + /// Gets 32+5 boundary samples needed by intra4x4. /// - public IMemoryOwner Preds { get; set; } + public byte[] I4Boundary { get; } + + /// + /// Gets or sets the index to the current top boundary sample. + /// + public int I4BoundaryIdx { get; set; } + + /// + /// Gets or sets the current intra4x4 mode being tested. + /// + public int I4 { get; set; } /// /// Gets or sets the top-non-zero context. /// - public int[] TopNz { get; set; } + public int[] TopNz { get; } /// /// Gets or sets the left-non-zero. leftNz[8] is independent. /// - public int[] LeftNz { get; set; } + public int[] LeftNz { get; } /// /// Gets or sets the number of mb still to be processed. @@ -193,6 +241,56 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private Vp8MacroBlockInfo[] Mb { get; } + public void Init() + { + this.Reset(); + } + + public void InitFilter() + { + // TODO: add support for autofilter + } + + public void StartI4() + { + int i; + this.I4 = 0; // first 4x4 sub-block. + this.I4BoundaryIdx = this.vp8TopLeftI4[0]; + + // Import the boundary samples. + for (i = 0; i < 17; ++i) + { + // left + this.I4Boundary[i] = this.YLeft[15 - i]; + } + + Span yTop = this.YTop.GetSpan(); + for (i = 0; i < 16; ++i) + { + // top + this.I4Boundary[17 + i] = yTop[i]; + } + + // top-right samples have a special case on the far right of the picture + if (this.X < this.mbw - 1) + { + for (i = 16; i < 16 + 4; ++i) + { + this.I4Boundary[17 + i] = yTop[i]; + } + } + else + { + // else, replicate the last valid pixel four times + for (i = 16; i < 16 + 4; ++i) + { + this.I4Boundary[17 + i] = this.I4Boundary[17 + 15]; + } + } + + NzToBytes(); // import the non-zero context. + } + // Import uncompressed samples from source. public void Import(Span y, Span u, Span v, int yStride, int uvStride, int width, int height) { @@ -300,7 +398,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (mode = 0; mode < maxMode; ++mode) { var histo = new Vp8LHistogram(); - histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(this.vp8I16ModeOffsets[mode]), 0, 16); + histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(Vp8I16ModeOffsets[mode]), 0, 16); int alpha = histo.GetAlpha(); if (alpha > bestAlpha) { @@ -325,7 +423,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (mode = 0; mode < maxMode; ++mode) { var histo = new Vp8LHistogram(); - histo.CollectHistogram(this.YuvIn.AsSpan(UOffEnc), this.YuvP.AsSpan(this.vp8UvModeOffsets[mode]), 16, 16 + 4 + 4); + histo.CollectHistogram(this.YuvIn.AsSpan(UOffEnc), this.YuvP.AsSpan(Vp8UvModeOffsets[mode]), 16, 16 + 4 + 4); int alpha = histo.GetAlpha(); if (alpha > bestAlpha) { @@ -356,7 +454,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I16X16; } - private void SetIntra4Mode(byte[] modes) + public void SetIntra4Mode(byte[] modes) { int modesIdx = 0; int predIdx = this.predIdx; @@ -370,7 +468,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I4X4; } - private void SetIntraUvMode(int mode) + public short[] GetCostModeI4(byte[] modes) + { + int predsWidth = this.predsWidth; + int x = this.I4 & 3; + int y = this.I4 >> 2; + int left = (int)((x == 0) ? this.Preds.GetSpan()[(y * predsWidth) - 1] : modes[this.I4 - 1]); + int top = (int)((y == 0) ? this.Preds.GetSpan()[-predsWidth + x] : modes[this.I4 - 4]); + return WebPLookupTables.Vp8FixedCostsI4[top, left]; + } + + public void SetIntraUvMode(int mode) { this.CurrentMacroBlockInfo.UvMode = mode; } @@ -394,11 +502,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return this.CountDown <= 0; } - /// - public void Dispose() - { - } - /// /// Go to next macroblock. /// @@ -421,39 +524,141 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return --this.CountDown > 0; } - private void Mean16x4(Span input, Span dc) + public void SaveBoundary() { - int x; - for (int k = 0; k < 4; ++k) + int x = this.X; + int y = this.Y; + Span ySrc = this.YuvOut.AsSpan(YOffEnc); + Span uvSrc = this.YuvOut.AsSpan(UOffEnc); + if (x < this.mbw - 1) { - uint avg = 0; - for (int y = 0; y < 4; ++y) + // left + for (int i = 0; i < 16; ++i) { - for (x = 0; x < 4; ++x) - { - avg += input[x + (y * WebPConstants.Bps)]; - } + this.YLeft[i + 1] = ySrc[15 + (i * WebPConstants.Bps)]; } - dc[k] = avg; - input = input.Slice(4); // go to next 4x4 block. + for (int i = 0; i < 8; ++i) + { + this.UvLeft[i + 1] = uvSrc[7 + (i * WebPConstants.Bps)]; + this.UvLeft[i + 16 + 1] = uvSrc[15 + (i * WebPConstants.Bps)]; + } + + // top-left (before 'top'!) + this.YLeft[0] = this.YTop.GetSpan()[15]; + this.UvLeft[0] = this.UvTop.GetSpan()[0 + 7]; + this.UvLeft[16] = this.UvTop.GetSpan()[8 + 7]; + } + + if (y < this.mbh - 1) + { + // top + ySrc.Slice(15 * WebPConstants.Bps, 16).CopyTo(this.YTop.GetSpan()); + uvSrc.Slice(7 * WebPConstants.Bps, 8 + 8).CopyTo(this.UvTop.GetSpan()); } } - private void MakeLuma16Preds() + public bool RotateI4(Span yuvOut) + { + Span blk = yuvOut.Slice(WebPLookupTables.Vp8Scan[this.I4]); + Span top = this.I4Boundary.AsSpan(this.I4BoundaryIdx); + int i; + + // Update the cache with 7 fresh samples. + for (i = 0; i <= 3; ++i) + { + top[-4 + i] = blk[i + (3 * WebPConstants.Bps)]; // Store future top samples. + } + + if ((this.I4 & 3) != 3) + { + // if not on the right sub-blocks #3, #7, #11, #15 + for (i = 0; i <= 2; ++i) + { + // store future left samples + top[i] = blk[3 + ((2 - i) * WebPConstants.Bps)]; + } + } + else + { + // else replicate top-right samples, as says the specs. + for (i = 0; i <= 3; ++i) + { + top[i] = top[i + 4]; + } + } + + // move pointers to next sub-block + ++this.I4; + if (this.I4 == 16) + { + // we're done + return false; + } + + this.I4BoundaryIdx = this.vp8TopLeftI4[this.I4]; + + return true; + } + + public void ResetAfterSkip() + { + if (this.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16) + { + // Reset all predictors. + this.Nz.GetSpan()[0] = 0; + this.LeftNz[8] = 0; + } + else + { + this.Nz.GetSpan()[0] &= 1 << 24; // Preserve the dc_nz bit. + } + } + + public void MakeLuma16Preds() { Span left = this.X != 0 ? this.YLeft.AsSpan() : null; Span top = this.Y != 0 ? this.YTop.Slice(this.yTopIdx) : null; this.EncPredLuma16(this.YuvP, left, top); } - private void MakeChroma8Preds() + public void MakeChroma8Preds() { Span left = this.X != 0 ? this.UvLeft.AsSpan() : null; Span top = this.Y != 0 ? this.UvTop.Slice(this.uvTopIdx) : null; this.EncPredChroma8(this.YuvP, left, top); } + public void MakeIntra4Preds() + { + this.EncPredLuma4(this.YuvP, this.I4Boundary.AsSpan(this.I4BoundaryIdx)); + } + + public void SwapOut() + { + byte[] tmp = this.YuvOut; + this.YuvOut = this.YuvOut2; + this.YuvOut2 = tmp; + } + + private void Mean16x4(Span input, Span dc) + { + for (int k = 0; k < 4; ++k) + { + uint avg = 0; + for (int y = 0; y < 4; ++y) + { + for (int x = 0; x < 4; ++x) + { + avg += input[x + (y * WebPConstants.Bps)]; + } + } + + dc[k] = avg; + input = input.Slice(4); // go to next 4x4 block. + } + } + // luma 16x16 prediction (paragraph 12.3). private void EncPredLuma16(Span dst, Span left, Span top) { @@ -490,6 +695,22 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.TrueMotion(dst.Slice(C8TM8), left, top, 8); } + // Left samples are top[-5 .. -2], top_left is top[-1], top are + // located at top[0..3], and top right is top[4..7] + private void EncPredLuma4(Span dst, Span top) + { + this.Dc4(dst.Slice(I4DC4), top); + this.Tm4(dst.Slice(I4TM4), top); + this.Ve4(dst.Slice(I4VE4), top); + this.He4(dst.Slice(I4HE4), top); + this.Rd4(dst.Slice(I4RD4), top); + this.Vr4(dst.Slice(I4VR4), top); + this.Ld4(dst.Slice(I4LD4), top); + this.Vl4(dst.Slice(I4VL4), top); + this.Hd4(dst.Slice(I4HD4), top); + this.Hu4(dst.Slice(I4HU4), top); + } + private void DcMode(Span dst, Span left, Span top, int size, int round, int shift) { int dc = 0; @@ -610,6 +831,272 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } + private void Dc4(Span dst, Span top) + { + uint dc = 4; + int i; + for (i = 0; i < 4; ++i) + { + dc += (uint)(top[i] + top[-5 + i]); + } + + this.Fill(dst, (int)(dc >> 3), 4); + } + + private void Tm4(Span dst, Span top) + { + Span clip = this.clip1.AsSpan(255 - top[-1]); + for (int y = 0; y < 4; ++y) + { + Span clipTable = clip.Slice(top[-2 - y]); + for (int x = 0; x < 4; ++x) + { + dst[x] = clipTable[top[x]]; + } + + dst = dst.Slice(WebPConstants.Bps); + } + } + + private void Ve4(Span dst, Span top) + { + // vertical + byte[] vals = + { + LossyUtils.Avg3(top[-1], top[0], top[1]), + LossyUtils.Avg3(top[ 0], top[1], top[2]), + LossyUtils.Avg3(top[ 1], top[2], top[3]), + LossyUtils.Avg3(top[ 2], top[3], top[4]) + }; + + for (int i = 0; i < 4; ++i) + { + vals.AsSpan().CopyTo(dst.Slice(i * WebPConstants.Bps)); + } + } + + private void He4(Span dst, Span top) + { + // horizontal + byte X = top[-1]; + byte I = top[-2]; + byte J = top[-3]; + byte K = top[-4]; + byte L = top[-5]; + + uint val = 0x01010101U * LossyUtils.Avg3(X, I, J); + BinaryPrimitives.WriteUInt32BigEndian(dst, val); + val = 0x01010101U * LossyUtils.Avg3(I, J, K); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebPConstants.Bps), val); + val = 0x01010101U * LossyUtils.Avg3(J, K, L); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebPConstants.Bps), val); + val = 0x01010101U * LossyUtils.Avg3(K, L, L); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebPConstants.Bps), val); + } + + private void Rd4(Span dst, Span top) + { + byte X = top[-1]; + byte I = top[-2]; + byte J = top[-3]; + byte K = top[-4]; + byte L = top[-5]; + byte A = top[0]; + byte B = top[1]; + byte C = top[2]; + byte D = top[3]; + + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(J, K, L)); + var ijk = LossyUtils.Avg3(I, J, K); + LossyUtils.Dst(dst, 0, 2, ijk); + LossyUtils.Dst(dst, 1, 3, ijk); + var xij = LossyUtils.Avg3(X, I, J); + LossyUtils.Dst(dst, 0, 1, xij); + LossyUtils.Dst(dst, 1, 2, xij); + LossyUtils.Dst(dst, 2, 3, xij); + var axi = LossyUtils.Avg3(A, X, I); + LossyUtils.Dst(dst, 0, 0, axi); + LossyUtils.Dst(dst, 1, 1, axi); + LossyUtils.Dst(dst, 2, 2, axi); + LossyUtils.Dst(dst, 3, 3, axi); + var bax = LossyUtils.Avg3(B, A, X); + LossyUtils.Dst(dst, 1, 0, bax); + LossyUtils.Dst(dst, 2, 1, bax); + LossyUtils.Dst(dst, 3, 2, bax); + var cba = LossyUtils.Avg3(C, B, A); + LossyUtils.Dst(dst, 2, 0, cba); + LossyUtils.Dst(dst, 3, 1, cba); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(D, C, B)); + } + + private void Vr4(Span dst, Span top) + { + byte X = top[-1]; + byte I = top[-2]; + byte J = top[-3]; + byte K = top[-4]; + byte A = top[0]; + byte B = top[1]; + byte C = top[2]; + byte D = top[3]; + + var xa = LossyUtils.Avg2(X, A); + LossyUtils.Dst(dst, 0, 0, xa); + LossyUtils.Dst(dst, 1, 2, xa); + var ab = LossyUtils.Avg2(A, B); + LossyUtils.Dst(dst, 1, 0, ab); + LossyUtils.Dst(dst, 2, 2, ab); + var bc = LossyUtils.Avg2(B, C); + LossyUtils.Dst(dst, 2, 0, bc); + LossyUtils.Dst(dst, 3, 2, bc); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg2(C, D)); + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(K, J, I)); + LossyUtils.Dst(dst, 0, 2, LossyUtils.Avg3(J, I, X)); + var ixa = LossyUtils.Avg3(I, X, A); + LossyUtils.Dst(dst, 0, 1, ixa); + LossyUtils.Dst(dst, 1, 3, ixa); + var xab = LossyUtils.Avg3(X, A, B); + LossyUtils.Dst(dst, 1, 1, xab); + LossyUtils.Dst(dst, 2, 3, xab); + var abc = LossyUtils.Avg3(A, B, C); + LossyUtils.Dst(dst, 2, 1, abc); + LossyUtils.Dst(dst, 3, 3, abc); + LossyUtils.Dst(dst, 3, 1, LossyUtils.Avg3(B, C, D)); + } + + private void Ld4(Span dst, Span top) + { + byte A = top[0]; + byte B = top[1]; + byte C = top[2]; + byte D = top[3]; + byte E = top[4]; + byte F = top[5]; + byte G = top[6]; + byte H = top[7]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg3(A, B, C)); + var bcd = LossyUtils.Avg3(B, C, D); + LossyUtils.Dst(dst, 1, 0, bcd); + LossyUtils.Dst(dst, 0, 1, bcd); + var cde = LossyUtils.Avg3(C, D, E); + LossyUtils.Dst(dst, 2, 0, cde); + LossyUtils.Dst(dst, 1, 1, cde); + LossyUtils.Dst(dst, 0, 2, cde); + var def = LossyUtils.Avg3(D, E, F); + LossyUtils.Dst(dst, 3, 0, def); + LossyUtils.Dst(dst, 2, 1, def); + LossyUtils.Dst(dst, 1, 2, def); + LossyUtils.Dst(dst, 0, 3, def); + var efg = LossyUtils.Avg3(E, F, G); + LossyUtils.Dst(dst, 3, 1, efg); + LossyUtils.Dst(dst, 2, 2, efg); + LossyUtils.Dst(dst, 1, 3, efg); + var fgh = LossyUtils.Avg3(F, G, H); + LossyUtils.Dst(dst, 3, 2, fgh); + LossyUtils.Dst(dst, 2, 3, fgh); + LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(G, H, H)); + } + + private void Vl4(Span dst, Span top) + { + byte A = top[0]; + byte B = top[1]; + byte C = top[2]; + byte D = top[3]; + byte E = top[4]; + byte F = top[5]; + byte G = top[6]; + byte H = top[7]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(A, B)); + var bc = LossyUtils.Avg2(B, C); + LossyUtils.Dst(dst, 1, 0, bc); + LossyUtils.Dst(dst, 0, 2, bc); + var cd = LossyUtils.Avg2(C, D); + LossyUtils.Dst(dst, 2, 0, cd); + LossyUtils.Dst(dst, 1, 2, cd); + var de = LossyUtils.Avg2(D, E); + LossyUtils.Dst(dst, 3, 0, de); + LossyUtils.Dst(dst, 2, 2, de); + LossyUtils.Dst(dst, 0, 1, LossyUtils.Avg3(A, B, C)); + var bcd = LossyUtils.Avg3(B,C,D); + LossyUtils.Dst(dst, 1, 1, bcd); + LossyUtils.Dst(dst, 0, 3, bcd); + var cde = LossyUtils.Avg3(C, D, E); + LossyUtils.Dst(dst, 2, 1, cde); + LossyUtils.Dst(dst, 1, 3, cde); + var def = LossyUtils.Avg3(D, E, F); + LossyUtils.Dst(dst, 3, 1, def); + LossyUtils.Dst(dst, 2, 3, def); + LossyUtils.Dst(dst, 3,2, LossyUtils.Avg3(E, F, G)); + LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(F, G, H)); + } + + private void Hd4(Span dst, Span top) + { + byte X = top[-1]; + byte I = top[-2]; + byte J = top[-3]; + byte K = top[-4]; + byte L = top[-5]; + byte A = top[0]; + byte B = top[1]; + byte C = top[2]; + + var ix = LossyUtils.Avg2(I, X); + LossyUtils.Dst(dst, 0, 0, ix); + LossyUtils.Dst(dst, 2, 1, ix); + var ji = LossyUtils.Avg2(J,I); + LossyUtils.Dst(dst, 0, 1, ji); + LossyUtils.Dst(dst, 2, 2, ji); + var kj = LossyUtils.Avg2(K, J); + LossyUtils.Dst(dst, 0, 2, kj); + LossyUtils.Dst(dst, 2, 3, kj); + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg2(L, K)); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(A, B, C)); + LossyUtils.Dst(dst, 2, 0, LossyUtils.Avg3(X, A, B)); + var ixa = LossyUtils.Avg3(I, X, A); + LossyUtils.Dst(dst, 1, 0, ixa); + LossyUtils.Dst(dst, 3, 1, ixa); + var jix = LossyUtils.Avg3(J, I, X); + LossyUtils.Dst(dst, 1, 1, jix); + LossyUtils.Dst(dst, 3, 2, jix); + var kji = LossyUtils.Avg3(K, J, I); + LossyUtils.Dst(dst, 1, 2, kji); + LossyUtils.Dst(dst, 3, 3, kji); + LossyUtils.Dst(dst, 1, 3, LossyUtils.Avg3(L, K, J)); + } + + private void Hu4(Span dst, Span top) + { + byte I = top[-2]; + byte J = top[-3]; + byte K = top[-4]; + byte L = top[-5]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(I, J)); + var jk = LossyUtils.Avg2(J, K); + LossyUtils.Dst(dst, 2, 0, jk); + LossyUtils.Dst(dst, 0, 1, jk); + var kl = LossyUtils.Avg2(K, L); + LossyUtils.Dst(dst, 2, 1, kl); + LossyUtils.Dst(dst, 0, 2, kl); + LossyUtils.Dst(dst, 1, 0, LossyUtils.Avg3(I, J, K)); + var jkl = LossyUtils.Avg3(J, K, L); + LossyUtils.Dst(dst, 3, 0, jkl); + LossyUtils.Dst(dst, 1, 1, jkl); + var kll = LossyUtils.Avg3(K, L, L); + LossyUtils.Dst(dst, 3, 1, kll); + LossyUtils.Dst(dst, 1, 2, kll); + LossyUtils.Dst(dst, 3, 2, L); + LossyUtils.Dst(dst, 2, 2, L); + LossyUtils.Dst(dst, 0, 3, L); + LossyUtils.Dst(dst, 1, 3, L); + LossyUtils.Dst(dst, 2, 3, L); + LossyUtils.Dst(dst, 3, 3, L); + } + private void Fill(Span dst, int value, int size) { for (int j = 0; j < size; ++j) @@ -710,6 +1197,54 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.Nz.GetSpan().Fill(0); } + private void NzToBytes() + { + Span nz = this.Nz.GetSpan(); + uint tnz = nz[0]; + uint lnz = nz[-1]; // TODO: -1? + Span topNz = this.TopNz; + Span leftNz = this.LeftNz; + + // Top-Y + topNz[0] = this.Bit(tnz, 12); + topNz[1] = this.Bit(tnz, 13); + topNz[2] = this.Bit(tnz, 14); + topNz[3] = this.Bit(tnz, 15); + + // Top-U + topNz[4] = this.Bit(tnz, 18); + topNz[5] = this.Bit(tnz, 19); + + // Top-V + topNz[6] = this.Bit(tnz, 22); + topNz[7] = this.Bit(tnz, 23); + + // DC + topNz[8] = this.Bit(tnz, 24); + + // left-Y + leftNz[0] = this.Bit(lnz, 3); + leftNz[1] = this.Bit(lnz, 7); + leftNz[2] = this.Bit(lnz, 11); + leftNz[3] = this.Bit(lnz, 15); + + // left-U + leftNz[4] = this.Bit(lnz, 17); + leftNz[5] = this.Bit(lnz, 19); + + // left-V + leftNz[6] = this.Bit(lnz, 21); + leftNz[7] = this.Bit(lnz, 23); + + // left-DC is special, iterated separately. + } + + // Convert packed context to byte array. + private int Bit(uint nz, int n) + { + return (int)(nz & (1 << n)); + } + /// /// Set count down. /// diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index a38b167c07..3374dff01c 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -55,6 +55,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.UvTop = this.memoryAllocator.Allocate(mbw * 16 * 2); this.Preds = this.memoryAllocator.Allocate(((4 * mbw) + 1) * ((4 * mbh) + 1)); this.Nz = this.memoryAllocator.Allocate(mbw + 1); + this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (mbw * mbh); // TODO: properly initialize the bitwriter this.bitWriter = new Vp8BitWriter(); @@ -91,17 +92,24 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private IMemoryOwner Nz { get; } + /// + /// Gets a rough limit for header bits per MB. + /// + private int MbHeaderLimit { get; } + public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { + int width = image.Width; + int height = image.Height; this.ConvertRgbToYuv(image); Span y = this.Y.GetSpan(); Span u = this.U.GetSpan(); Span v = this.V.GetSpan(); - int mbw = (image.Width + 15) >> 4; - int mbh = (image.Height + 15) >> 4; - int yStride = image.Width; + int mbw = (width + 15) >> 4; + int mbh = (height + 15) >> 4; + int yStride = width; int uvStride = (yStride + 1) >> 1; var mb = new Vp8MacroBlockInfo[mbw * mbh]; for (int i = 0; i < mb.Length; i++) @@ -113,21 +121,29 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy int method = 4; // TODO: hardcoded for now int quality = 100; // TODO: hardcoded for now var alphas = new int[WebPConstants.MaxAlpha + 1]; - int uvAlpha = 0; - int alpha = 0; - if (!it.IsDone()) + int alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, method, quality, alphas, out int uvAlpha); + + // Analysis is done, proceed to actual coding. + + // TODO: EncodeAlpha(); + it.Init(); + it.InitFilter(); + do { - do + var info = new Vp8ModeScore(); + it.Import(y, u, v, yStride, uvStride, width, height); + if (!this.Decimate(it, info, method)) { - it.Import(y, u, v, yStride, uvStride, image.Width, image.Height); - int bestAlpha = this.MbAnalyze(it, method, quality, alphas, out var bestUvAlpha); - - // Accumulate for later complexity analysis. - alpha += bestAlpha; - uvAlpha += bestUvAlpha; + this.CodeResiduals(it); } - while (it.Next()); + else + { + it.ResetAfterSkip(); + } + + it.SaveBoundary(); } + while (it.Next()); throw new NotImplementedException(); } @@ -143,6 +159,27 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.Preds.Dispose(); } + private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span y, Span u, Span v, int yStride, int uvStride, int method, int quality, int[] alphas, out int uvAlpha) + { + int alpha = 0; + uvAlpha = 0; + if (!it.IsDone()) + { + do + { + it.Import(y, u, v, yStride, uvStride, width, height); + int bestAlpha = this.MbAnalyze(it, method, quality, alphas, out var bestUvAlpha); + + // Accumulate for later complexity analysis. + alpha += bestAlpha; + uvAlpha += bestUvAlpha; + } + while (it.Next()); + } + + return alpha; + } + private int MbAnalyze(Vp8EncIterator it, int method, int quality, int[] alphas, out int bestUvAlpha) { it.SetIntra16Mode(0); // default: Intra16, DC_PRED @@ -170,6 +207,187 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return bestAlpha; // Mixed susceptibility (not just luma). } + private bool Decimate(Vp8EncIterator it, Vp8ModeScore rd, int method) + { + rd.InitScore(); + + it.MakeLuma16Preds(); + it.MakeChroma8Preds(); + + // TODO: add support for Rate-distortion optimization levels + // At this point we have heuristically decided intra16 / intra4. + // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). + // For method <= 1, we don't re-examine the decision but just go ahead with + // quantization/reconstruction. + this.RefineUsingDistortion(it, rd, method >= 2, method >= 1); + + bool isSkipped = rd.Nz == 0; + it.SetSkip(isSkipped); + + return isSkipped; + } + + // Refine intra16/intra4 sub-modes based on distortion only (not rate). + private void RefineUsingDistortion(Vp8EncIterator it, Vp8ModeScore rd, bool tryBothModes, bool refineUvMode) + { + long bestScore = Vp8ModeScore.MaxCost; + int nz = 0; + int mode; + bool isI16 = tryBothModes || (it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16); + + // TODO: VP8SegmentInfo* const dqm = &it->enc_->dqm_[it->mb_->segment_]; + + // Some empiric constants, of approximate order of magnitude. + int lambdaDi16 = 106; + int lambdaDi4 = 11; + int lambdaDuv = 120; + long scoreI4 = 676000; // TODO: hardcoded for now: long scoreI4 = dqm->i4_penalty_; + long i4BitSum = 0; + long bitLimit = tryBothModes + ? this.MbHeaderLimit + : Vp8ModeScore.MaxCost; // no early-out allowed. + int numPredModes = 4; + int numBModes = 10; + + if (isI16) + { + int bestMode = -1; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); + for (mode = 0; mode < numPredModes; ++mode) + { + Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I16ModeOffsets[mode]); + long score = (this.Vp8Sse16X16(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsI16[mode] * lambdaDi16); + + if (mode > 0 && WebPConstants.Vp8FixedCostsI16[mode] > bitLimit) + { + continue; + } + + if (score < bestScore) + { + bestMode = mode; + bestScore = score; + } + } + + if (it.X == 0 || it.Y == 0) + { + // Avoid starting a checkerboard resonance from the border. See bug #432 of libwebp. + if (this.IsFlatSource16(src)) + { + bestMode = (it.X == 0) ? 0 : 2; + tryBothModes = false; // Stick to i16. + } + } + + it.SetIntra16Mode(bestMode); + + // We'll reconstruct later, if i16 mode actually gets selected. + } + + // Next, evaluate Intra4. + if (tryBothModes || !isI16) + { + // We don't evaluate the rate here, but just account for it through a + // constant penalty (i4 mode usually needs more bits compared to i16). + isI16 = false; + it.StartI4(); + do + { + int bestI4Mode = -1; + long bestI4Score = Vp8ModeScore.MaxCost; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc + WebPLookupTables.Vp8Scan[it.I4]); + short[] modeCosts = it.GetCostModeI4(rd.ModesI4); + + it.MakeIntra4Preds(); + for (mode = 0; mode < numBModes; ++mode) + { + Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I4ModeOffsets[mode]); + long score = (this.Vp8Sse4X4(src, reference) * WebPConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); + if (score < bestI4Score) + { + bestI4Mode = mode; + bestI4Score = score; + } + } + + i4BitSum += modeCosts[bestI4Mode]; + rd.ModesI4[it.I4] = (byte)bestI4Mode; + scoreI4 += bestI4Score; + if (scoreI4 >= bestScore || i4BitSum > bitLimit) + { + // Intra4 won't be better than Intra16. Bail out and pick Intra16. + isI16 = true; + break; + } + else + { + // Reconstruct partial block inside yuv_out2 buffer + Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc + WebPLookupTables.Vp8Scan[it.I4]); + // TODO: nz |= ReconstructIntra4(it, rd.YAcLevels[it.I4], src, tmpDst, bestI4Mode) << it.I4; + } + } + while (it.RotateI4(it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc))); + } + + // Final reconstruction, depending on which mode is selected. + if (!isI16) + { + it.SetIntra4Mode(rd.ModesI4); + it.SwapOut(); + bestScore = scoreI4; + } + else + { + // TODO: nz = ReconstructIntra16(it, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), it.Preds.GetSpan()[0]); + } + + // ... and UV! + if (refineUvMode) + { + int bestMode = -1; + long bestUvScore = Vp8ModeScore.MaxCost; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); + for (mode = 0; mode < numPredModes; ++mode) + { + Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8UvModeOffsets[mode]); + long score = (this.Vp8Sse16X8(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsUv[mode] * lambdaDuv); + if (score < bestUvScore) + { + bestMode = mode; + bestUvScore = score; + } + } + + it.SetIntraUvMode(bestMode); + } + + // TODO: nz |= ReconstructUv(it, rd, it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc), it.CurrentMacroBlockInfo.UvMode); + + rd.Nz = (uint)nz; + rd.Score = bestScore; + } + + private void CodeResiduals(Vp8EncIterator it) + { + + } + + private void ReconstructIntra16() + { + + } + + private void ReconstructIntra4() + { + + } + + private void ReconstructUv() + { + + } + private void ConvertRgbToYuv(Image image) where TPixel : unmanaged, IPixel { @@ -469,5 +687,60 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { return (v < min) ? min : (v > max) ? max : v; } + + [MethodImpl(InliningOptions.ShortMethod)] + private int Vp8Sse16X16(Span a, Span b) + { + return this.GetSse(a, b, 16, 16); + } + + private int Vp8Sse16X8(Span a, Span b) + { + return this.GetSse(a, b, 16, 8); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int Vp8Sse4X4(Span a, Span b) + { + return this.GetSse(a, b, 4, 4); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int GetSse(Span a, Span b, int w, int h) + { + int count = 0; + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + int diff = a[x] - b[x]; + count += diff * diff; + } + + a = a.Slice(WebPConstants.Bps); + b = b.Slice(WebPConstants.Bps); + } + + return count; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private bool IsFlatSource16(Span src) + { + uint v = src[0] * 0x01010101u; + Span vSpan = BitConverter.GetBytes(v).AsSpan(); + for (int i = 0; i < 16; ++i) + { + if (src.Slice(0, 4).SequenceEqual(vSpan) || src.Slice(4, 4).SequenceEqual(vSpan) || + src.Slice(0, 8).SequenceEqual(vSpan) || src.Slice(12, 4).SequenceEqual(vSpan)) + { + return false; + } + + src = src.Slice(WebPConstants.Bps); + } + + return true; + } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs new file mode 100644 index 0000000000..3b793762a7 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + /// + /// Class to accumulate score and info during RD-optimization and mode evaluation. + /// + internal class Vp8ModeScore + { + public const long MaxCost = 0x7fffffffffffffL; + + /// + /// Initializes a new instance of the class. + /// + public Vp8ModeScore() + { + this.YDcLevels = new short[16]; + this.YAcLevels = new short[16][]; + for (int i = 0; i < 16; i++) + { + this.YAcLevels[i] = new short[16]; + } + + this.UvLevels = new short[4 + 4][]; + for (int i = 0; i < 8; i++) + { + this.UvLevels[i] = new short[16]; + } + + this.ModesI4 = new byte[16]; + } + + /// + /// Distortion. + /// + public long D { get; set; } + + /// + /// Spectral distortion. + /// + public long SD { get; set; } + + /// + /// Header bits. + /// + public long H { get; set; } + + /// + /// Rate. + /// + public long R { get; set; } + + /// + /// Score. + /// + public long Score { get; set; } + + /// + /// Quantized levels for luma-DC. + /// + public short[] YDcLevels { get; } + + /// + /// Quantized levels for luma-AC. + /// + public short[][] YAcLevels { get; } + + /// + /// Quantized levels for chroma. + /// + public short[][] UvLevels { get; } + + /// + /// Mode number for intra16 prediction. + /// + public int ModeI16 { get; set; } + + /// + /// Mode numbers for intra4 predictions. + /// + public byte[] ModesI4 { get; } + + /// + /// Mode number of chroma prediction. + /// + public int ModeUv { get; set; } + + /// + /// Non-zero blocks. + /// + public uint Nz { get; set; } + + public void InitScore() + { + this.D = 0; + this.SD = 0; + this.R = 0; + this.H = 0; + this.Nz = 0; + this.Score = MaxCost; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index cb751e2165..9a2b3ee8b6 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -209,6 +209,15 @@ namespace SixLabors.ImageSharp.Formats.WebP public const int AlphaScale = 2 * MaxAlpha; + public static readonly short[] Vp8FixedCostsUv = { 302, 984, 439, 642 }; + + public static readonly short[] Vp8FixedCostsI16 = { 663, 919, 872, 919 }; + + /// + /// Distortion multiplier (equivalent of lambda). + /// + public const int RdDistoMult = 256; + /// /// How many extra lines are needed on the MB boundary for caching, given a filtering level. /// Simple filter(1): up to 2 luma samples are read and 1 is written. diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index 1a38912eaf..9cfd9ec4cd 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -36,6 +36,17 @@ namespace SixLabors.ImageSharp.Formats.WebP 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps) // V }; + public static readonly short[] Vp8Scan = + { + // Luma + 0 + (0 * WebPConstants.Bps), 4 + (0 * WebPConstants.Bps), 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), + 0 + (4 * WebPConstants.Bps), 4 + (4 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps), + 0 + (8 * WebPConstants.Bps), 4 + (8 * WebPConstants.Bps), 8 + (8 * WebPConstants.Bps), 12 + (8 * WebPConstants.Bps), + 0 + (12 * WebPConstants.Bps), 4 + (12 * WebPConstants.Bps), 8 + (12 * WebPConstants.Bps), 12 + (12 * WebPConstants.Bps), + }; + + public static readonly short[,][] Vp8FixedCostsI4 = new short[10, 10][]; + /// /// Lookup table for small values of log2(int). /// @@ -960,6 +971,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } InitializeModesProbabilities(); + InitializeFixedCostsI4(); } private static void InitializeModesProbabilities() @@ -1066,5 +1078,109 @@ namespace SixLabors.ImageSharp.Formats.WebP ModesProba[9, 8] = new byte[] { 32, 41, 20, 117, 151, 142, 20, 21, 163 }; ModesProba[9, 9] = new byte[] { 112, 19, 12, 61, 195, 128, 48, 4, 24 }; } + + private static void InitializeFixedCostsI4() + { + Vp8FixedCostsI4[0, 0] = new short[] { 40, 1151, 1723, 1874, 2103, 2019, 1628, 1777, 2226, 2137 }; + Vp8FixedCostsI4[0, 1] = new short[] { 192, 469, 1296, 1308, 1849, 1794, 1781, 1703, 1713, 1522 }; + Vp8FixedCostsI4[0, 2] = new short[] { 142, 910, 762, 1684, 1849, 1576, 1460, 1305, 1801, 1657 }; + Vp8FixedCostsI4[0, 3] = new short[] { 559, 641, 1370, 421, 1182, 1569, 1612, 1725, 863, 1007 }; + Vp8FixedCostsI4[0, 4] = new short[] { 299, 1059, 1256, 1108, 636, 1068, 1581, 1883, 869, 1142 }; + Vp8FixedCostsI4[0, 5] = new short[] { 277, 1111, 707, 1362, 1089, 672, 1603, 1541, 1545, 1291 }; + Vp8FixedCostsI4[0, 6] = new short[] { 214, 781, 1609, 1303, 1632, 2229, 726, 1560, 1713, 918 }; + Vp8FixedCostsI4[0, 7] = new short[] { 152, 1037, 1046, 1759, 1983, 2174, 1358, 742, 1740, 1390 }; + Vp8FixedCostsI4[0, 8] = new short[] { 512, 1046, 1420, 753, 752, 1297, 1486, 1613, 460, 1207 }; + Vp8FixedCostsI4[0, 9] = new short[] { 424, 827, 1362, 719, 1462, 1202, 1199, 1476, 1199, 538 }; + Vp8FixedCostsI4[1, 0] = new short[] { 240, 402, 1134, 1491, 1659, 1505, 1517, 1555, 1979, 2099 }; + Vp8FixedCostsI4[1, 1] = new short[] { 467, 242, 960, 1232, 1714, 1620, 1834, 1570, 1676, 1391 }; + Vp8FixedCostsI4[1, 2] = new short[] { 500, 455, 463, 1507, 1699, 1282, 1564, 982, 2114, 2114 }; + Vp8FixedCostsI4[1, 3] = new short[] { 672, 643, 1372, 331, 1589, 1667, 1453, 1938, 996, 876 }; + Vp8FixedCostsI4[1, 4] = new short[] { 458, 783, 1037, 911, 738, 968, 1165, 1518, 859, 1033 }; + Vp8FixedCostsI4[1, 5] = new short[] { 504, 815, 504, 1139, 1219, 719, 1506, 1085, 1268, 1268 }; + Vp8FixedCostsI4[1, 6] = new short[] { 333, 630, 1445, 1239, 1883, 3672, 799, 1548, 1865, 598 }; + Vp8FixedCostsI4[1, 7] = new short[] { 399, 644, 746, 1342, 1856, 1350, 1493, 613, 1855, 1015 }; + Vp8FixedCostsI4[1, 8] = new short[] { 622, 749, 1205, 608, 1066, 1408, 1290, 1406, 546, 971 }; + Vp8FixedCostsI4[1, 9] = new short[] { 500, 753, 1041, 668, 1230, 1617, 1297, 1425, 1383, 523 }; + Vp8FixedCostsI4[2, 0] = new short[] { 394, 553, 523, 1502, 1536, 981, 1608, 1142, 1666, 2181 }; + Vp8FixedCostsI4[2, 1] = new short[] { 655, 430, 375, 1411, 1861, 1220, 1677, 1135, 1978, 1553 }; + Vp8FixedCostsI4[2, 2] = new short[] { 690, 640, 245, 1954, 2070, 1194, 1528, 982, 1972, 2232 }; + Vp8FixedCostsI4[2, 3] = new short[] { 559, 834, 741, 867, 1131, 980, 1225, 852, 1092, 784 }; + Vp8FixedCostsI4[2, 4] = new short[] { 690, 875, 516, 959, 673, 894, 1056, 1190, 1528, 1126 }; + Vp8FixedCostsI4[2, 5] = new short[] { 740, 951, 384, 1277, 1177, 492, 1579, 1155, 1846, 1513 }; + Vp8FixedCostsI4[2, 6] = new short[] { 323, 775, 1062, 1776, 3062, 1274, 813, 1188, 1372, 655 }; + Vp8FixedCostsI4[2, 7] = new short[] { 488, 971, 484, 1767, 1515, 1775, 1115, 503, 1539, 1461 }; + Vp8FixedCostsI4[2, 8] = new short[] { 740, 1006, 998, 709, 851, 1230, 1337, 788, 741, 721 }; + Vp8FixedCostsI4[2, 9] = new short[] { 522, 1073, 573, 1045, 1346, 887, 1046, 1146, 1203, 697 }; + Vp8FixedCostsI4[3, 0] = new short[] { 105, 864, 1442, 1009, 1934, 1840, 1519, 1920, 1673, 1579 }; + Vp8FixedCostsI4[3, 1] = new short[] { 534, 305, 1193, 683, 1388, 2164, 1802, 1894, 1264, 1170 }; + Vp8FixedCostsI4[3, 2] = new short[] { 305, 518, 877, 1108, 1426, 3215, 1425, 1064, 1320, 1242 }; + Vp8FixedCostsI4[3, 3] = new short[] { 683, 732, 1927, 257, 1493, 2048, 1858, 1552, 1055, 947 }; + Vp8FixedCostsI4[3, 4] = new short[] { 394, 814, 1024, 660, 959, 1556, 1282, 1289, 893, 1047 }; + Vp8FixedCostsI4[3, 5] = new short[] { 528, 615, 996, 940, 1201, 635, 1094, 2515, 803, 1358 }; + Vp8FixedCostsI4[3, 6] = new short[] { 347, 614, 1609, 1187, 3133, 1345, 1007, 1339, 1017, 667 }; + Vp8FixedCostsI4[3, 7] = new short[] { 218, 740, 878, 1605, 3650, 3650, 1345, 758, 1357, 1617 }; + Vp8FixedCostsI4[3, 8] = new short[] { 672, 750, 1541, 558, 1257, 1599, 1870, 2135, 402, 1087 }; + Vp8FixedCostsI4[3, 9] = new short[] { 592, 684, 1161, 430, 1092, 1497, 1475, 1489, 1095, 822 }; + Vp8FixedCostsI4[4, 0] = new short[] { 228, 1056, 1059, 1368, 752, 982, 1512, 1518, 987, 1782 }; + Vp8FixedCostsI4[4, 1] = new short[] { 494, 514, 818, 942, 965, 892, 1610, 1356, 1048, 1363 }; + Vp8FixedCostsI4[4, 2] = new short[] { 512, 648, 591, 1042, 761, 991, 1196, 1454, 1309, 1463 }; + Vp8FixedCostsI4[4, 3] = new short[] { 683, 749, 1043, 676, 841, 1396, 1133, 1138, 654, 939 }; + Vp8FixedCostsI4[4, 4] = new short[] { 622, 1101, 1126, 994, 361, 1077, 1203, 1318, 877, 1219 }; + Vp8FixedCostsI4[4, 5] = new short[] { 631, 1068, 857, 1650, 651, 477, 1650, 1419, 828, 1170 }; + Vp8FixedCostsI4[4, 6] = new short[] { 555, 727, 1068, 1335, 3127, 1339, 820, 1331, 1077, 429 }; + Vp8FixedCostsI4[4, 7] = new short[] { 504, 879, 624, 1398, 889, 889, 1392, 808, 891, 1406 }; + Vp8FixedCostsI4[4, 8] = new short[] { 683, 1602, 1289, 977, 578, 983, 1280, 1708, 406, 1122 }; + Vp8FixedCostsI4[4, 9] = new short[] { 399, 865, 1433, 1070, 1072, 764, 968, 1477, 1223, 678 }; + Vp8FixedCostsI4[5, 0] = new short[] { 333, 760, 935, 1638, 1010, 529, 1646, 1410, 1472, 2219 }; + Vp8FixedCostsI4[5, 1] = new short[] { 512, 494, 750, 1160, 1215, 610, 1870, 1868, 1628, 1169 }; + Vp8FixedCostsI4[5, 2] = new short[] { 572, 646, 492, 1934, 1208, 603, 1580, 1099, 1398, 1995 }; + Vp8FixedCostsI4[5, 3] = new short[] { 786, 789, 942, 581, 1018, 951, 1599, 1207, 731, 768 }; + Vp8FixedCostsI4[5, 4] = new short[] { 690, 1015, 672, 1078, 582, 504, 1693, 1438, 1108, 2897 }; + Vp8FixedCostsI4[5, 5] = new short[] { 768, 1267, 571, 2005, 1243, 244, 2881, 1380, 1786, 1453 }; + Vp8FixedCostsI4[5, 6] = new short[] { 452, 899, 1293, 903, 1311, 3100, 465, 1311, 1319, 813 }; + Vp8FixedCostsI4[5, 7] = new short[] { 394, 927, 942, 1103, 1358, 1104, 946, 593, 1363, 1109 }; + Vp8FixedCostsI4[5, 8] = new short[] { 559, 1005, 1007, 1016, 658, 1173, 1021, 1164, 623, 1028 }; + Vp8FixedCostsI4[5, 9] = new short[] { 564, 796, 632, 1005, 1014, 863, 2316, 1268, 938, 764 }; + Vp8FixedCostsI4[6, 0] = new short[] { 266, 606, 1098, 1228, 1497, 1243, 948, 1030, 1734, 1461 }; + Vp8FixedCostsI4[6, 1] = new short[] { 366, 585, 901, 1060, 1407, 1247, 876, 1134, 1620, 1054 }; + Vp8FixedCostsI4[6, 2] = new short[] { 452, 565, 542, 1729, 1479, 1479, 1016, 886, 2938, 1150 }; + Vp8FixedCostsI4[6, 3] = new short[] { 555, 1088, 1533, 950, 1354, 895, 834, 1019, 1021, 496 }; + Vp8FixedCostsI4[6, 4] = new short[] { 704, 815, 1193, 971, 973, 640, 1217, 2214, 832, 578 }; + Vp8FixedCostsI4[6, 5] = new short[] { 672, 1245, 579, 871, 875, 774, 872, 1273, 1027, 949 }; + Vp8FixedCostsI4[6, 6] = new short[] { 296, 1134, 2050, 1784, 1636, 3425, 442, 1550, 2076, 722 }; + Vp8FixedCostsI4[6, 7] = new short[] { 342, 982, 1259, 1846, 1848, 1848, 622, 568, 1847, 1052 }; + Vp8FixedCostsI4[6, 8] = new short[] { 555, 1064, 1304, 828, 746, 1343, 1075, 1329, 1078, 494 }; + Vp8FixedCostsI4[6, 9] = new short[] { 288, 1167, 1285, 1174, 1639, 1639, 833, 2254, 1304, 509 }; + Vp8FixedCostsI4[7, 0] = new short[] { 342, 719, 767, 1866, 1757, 1270, 1246, 550, 1746, 2151 }; + Vp8FixedCostsI4[7, 1] = new short[] { 483, 653, 694, 1509, 1459, 1410, 1218, 507, 1914, 1266 }; + Vp8FixedCostsI4[7, 2] = new short[] { 488, 757, 447, 2979, 1813, 1268, 1654, 539, 1849, 2109 }; + Vp8FixedCostsI4[7, 3] = new short[] { 522, 1097, 1085, 851, 1365, 1111, 851, 901, 961, 605 }; + Vp8FixedCostsI4[7, 4] = new short[] { 709, 716, 841, 728, 736, 945, 941, 862, 2845, 1057 }; + Vp8FixedCostsI4[7, 5] = new short[] { 512, 1323, 500, 1336, 1083, 681, 1342, 717, 1604, 1350 }; + Vp8FixedCostsI4[7, 6] = new short[] { 452, 1155, 1372, 1900, 1501, 3290, 311, 944, 1919, 922 }; + Vp8FixedCostsI4[7, 7] = new short[] { 403, 1520, 977, 2132, 1733, 3522, 1076, 276, 3335, 1547 }; + Vp8FixedCostsI4[7, 8] = new short[] { 559, 1374, 1101, 615, 673, 2462, 974, 795, 984, 984 }; + Vp8FixedCostsI4[7, 9] = new short[] { 547, 1122, 1062, 812, 1410, 951, 1140, 622, 1268, 651 }; + Vp8FixedCostsI4[8, 0] = new short[] { 165, 982, 1235, 938, 1334, 1366, 1659, 1578, 964, 1612 }; + Vp8FixedCostsI4[8, 1] = new short[] { 592, 422, 925, 847, 1139, 1112, 1387, 2036, 861, 1041 }; + Vp8FixedCostsI4[8, 2] = new short[] { 403, 837, 732, 770, 941, 1658, 1250, 809, 1407, 1407 }; + Vp8FixedCostsI4[8, 3] = new short[] { 896, 874, 1071, 381, 1568, 1722, 1437, 2192, 480, 1035 }; + Vp8FixedCostsI4[8, 4] = new short[] { 640, 1098, 1012, 1032, 684, 1382, 1581, 2106, 416, 865 }; + Vp8FixedCostsI4[8, 5] = new short[] { 559, 1005, 819, 914, 710, 770, 1418, 920, 838, 1435 }; + Vp8FixedCostsI4[8, 6] = new short[] { 415, 1258, 1245, 870, 1278, 3067, 770, 1021, 1287, 522 }; + Vp8FixedCostsI4[8, 7] = new short[] { 406, 990, 601, 1009, 1265, 1265, 1267, 759, 1017, 1277 }; + Vp8FixedCostsI4[8, 8] = new short[] { 968, 1182, 1329, 788, 1032, 1292, 1705, 1714, 203, 1403 }; + Vp8FixedCostsI4[8, 9] = new short[] { 732, 877, 1279, 471, 901, 1161, 1545, 1294, 755, 755 }; + Vp8FixedCostsI4[9, 0] = new short[] { 111, 931, 1378, 1185, 1933, 1648, 1148, 1714, 1873, 1307 }; + Vp8FixedCostsI4[9, 1] = new short[] { 406, 414, 1030, 1023, 1910, 1404, 1313, 1647, 1509, 793 }; + Vp8FixedCostsI4[9, 2] = new short[] { 342, 640, 575, 1088, 1241, 1349, 1161, 1350, 1756, 1502 }; + Vp8FixedCostsI4[9, 3] = new short[] { 559, 766, 1185, 357, 1682, 1428, 1329, 1897, 1219, 802 }; + Vp8FixedCostsI4[9, 4] = new short[] { 473, 909, 1164, 771, 719, 2508, 1427, 1432, 722, 782 }; + Vp8FixedCostsI4[9, 5] = new short[] { 342, 892, 785, 1145, 1150, 794, 1296, 1550, 973, 1057 }; + Vp8FixedCostsI4[9, 6] = new short[] { 208, 1036, 1326, 1343, 1606, 3395, 815, 1455, 1618, 712 }; + Vp8FixedCostsI4[9, 7] = new short[] { 228, 928, 890, 1046, 3499, 1711, 994, 829, 1720, 1318 }; + Vp8FixedCostsI4[9, 8] = new short[] { 768, 724, 1058, 636, 991, 1075, 1319, 1324, 616, 825 }; + Vp8FixedCostsI4[9, 9] = new short[] { 305, 1167, 1358, 899, 1587, 1587, 987, 1988, 1332, 501 }; + } } }