Browse Source

Refine intra16/intra4 sub-modes based on distortion (still WIP)

pull/1552/head
Brian Popow 6 years ago
parent
commit
65fb30adc9
  1. 36
      src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs
  2. 599
      src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
  3. 301
      src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
  4. 104
      src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs
  5. 9
      src/ImageSharp/Formats/WebP/WebPConstants.cs
  6. 116
      src/ImageSharp/Formats/WebP/WebPLookupTables.cs

36
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<byte> 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<byte> 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<byte> dst, int x, int y, byte v)
{
dst[x + (y * WebPConstants.Bps)] = v;
}
[MethodImpl(InliningOptions.ShortMethod)]
private static int Clamp255(int x)
{

599
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, ...)
/// </summary>
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
/// </summary>
public IMemoryOwner<byte> UvTop { get; set; }
/// <summary>
/// Gets or sets the intra mode predictors (4x4 blocks).
/// </summary>
public IMemoryOwner<byte> Preds { get; set; }
/// <summary>
/// Gets or sets the non-zero pattern.
/// </summary>
public IMemoryOwner<uint> Nz { get; set; }
/// <summary>
/// Gets or sets the intra mode predictors (4x4 blocks).
/// Gets 32+5 boundary samples needed by intra4x4.
/// </summary>
public IMemoryOwner<byte> Preds { get; set; }
public byte[] I4Boundary { get; }
/// <summary>
/// Gets or sets the index to the current top boundary sample.
/// </summary>
public int I4BoundaryIdx { get; set; }
/// <summary>
/// Gets or sets the current intra4x4 mode being tested.
/// </summary>
public int I4 { get; set; }
/// <summary>
/// Gets or sets the top-non-zero context.
/// </summary>
public int[] TopNz { get; set; }
public int[] TopNz { get; }
/// <summary>
/// Gets or sets the left-non-zero. leftNz[8] is independent.
/// </summary>
public int[] LeftNz { get; set; }
public int[] LeftNz { get; }
/// <summary>
/// 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<byte> 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<byte> y, Span<byte> u, Span<byte> 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;
}
/// <inheritdoc/>
public void Dispose()
{
}
/// <summary>
/// Go to next macroblock.
/// </summary>
@ -421,39 +524,141 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
return --this.CountDown > 0;
}
private void Mean16x4(Span<byte> input, Span<uint> dc)
public void SaveBoundary()
{
int x;
for (int k = 0; k < 4; ++k)
int x = this.X;
int y = this.Y;
Span<byte> ySrc = this.YuvOut.AsSpan(YOffEnc);
Span<byte> 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<byte> yuvOut)
{
Span<byte> blk = yuvOut.Slice(WebPLookupTables.Vp8Scan[this.I4]);
Span<byte> 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<byte> left = this.X != 0 ? this.YLeft.AsSpan() : null;
Span<byte> top = this.Y != 0 ? this.YTop.Slice(this.yTopIdx) : null;
this.EncPredLuma16(this.YuvP, left, top);
}
private void MakeChroma8Preds()
public void MakeChroma8Preds()
{
Span<byte> left = this.X != 0 ? this.UvLeft.AsSpan() : null;
Span<byte> 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<byte> input, Span<uint> 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<byte> dst, Span<byte> left, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> left, Span<byte> top, int size, int round, int shift)
{
int dc = 0;
@ -610,6 +831,272 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
}
private void Dc4(Span<byte> dst, Span<byte> 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<byte> dst, Span<byte> top)
{
Span<byte> clip = this.clip1.AsSpan(255 - top[-1]);
for (int y = 0; y < 4; ++y)
{
Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> 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<uint> nz = this.Nz.GetSpan();
uint tnz = nz[0];
uint lnz = nz[-1]; // TODO: -1?
Span<int> topNz = this.TopNz;
Span<int> 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));
}
/// <summary>
/// Set count down.
/// </summary>

301
src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs

@ -55,6 +55,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.UvTop = this.memoryAllocator.Allocate<byte>(mbw * 16 * 2);
this.Preds = this.memoryAllocator.Allocate<byte>(((4 * mbw) + 1) * ((4 * mbh) + 1));
this.Nz = this.memoryAllocator.Allocate<uint>(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
/// </summary>
private IMemoryOwner<uint> Nz { get; }
/// <summary>
/// Gets a rough limit for header bits per MB.
/// </summary>
private int MbHeaderLimit { get; }
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
this.ConvertRgbToYuv(image);
Span<byte> y = this.Y.GetSpan();
Span<byte> u = this.U.GetSpan();
Span<byte> 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<byte> y, Span<byte> u, Span<byte> 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<byte> src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc);
for (mode = 0; mode < numPredModes; ++mode)
{
Span<byte> 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<byte> 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<byte> 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<byte> 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<byte> src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc);
for (mode = 0; mode < numPredModes; ++mode)
{
Span<byte> 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<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -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<byte> a, Span<byte> b)
{
return this.GetSse(a, b, 16, 16);
}
private int Vp8Sse16X8(Span<byte> a, Span<byte> b)
{
return this.GetSse(a, b, 16, 8);
}
[MethodImpl(InliningOptions.ShortMethod)]
private int Vp8Sse4X4(Span<byte> a, Span<byte> b)
{
return this.GetSse(a, b, 4, 4);
}
[MethodImpl(InliningOptions.ShortMethod)]
private int GetSse(Span<byte> a, Span<byte> 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<byte> src)
{
uint v = src[0] * 0x01010101u;
Span<byte> 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;
}
}
}

104
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
{
/// <summary>
/// Class to accumulate score and info during RD-optimization and mode evaluation.
/// </summary>
internal class Vp8ModeScore
{
public const long MaxCost = 0x7fffffffffffffL;
/// <summary>
/// Initializes a new instance of the <see cref="Vp8ModeScore"/> class.
/// </summary>
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];
}
/// <summary>
/// Distortion.
/// </summary>
public long D { get; set; }
/// <summary>
/// Spectral distortion.
/// </summary>
public long SD { get; set; }
/// <summary>
/// Header bits.
/// </summary>
public long H { get; set; }
/// <summary>
/// Rate.
/// </summary>
public long R { get; set; }
/// <summary>
/// Score.
/// </summary>
public long Score { get; set; }
/// <summary>
/// Quantized levels for luma-DC.
/// </summary>
public short[] YDcLevels { get; }
/// <summary>
/// Quantized levels for luma-AC.
/// </summary>
public short[][] YAcLevels { get; }
/// <summary>
/// Quantized levels for chroma.
/// </summary>
public short[][] UvLevels { get; }
/// <summary>
/// Mode number for intra16 prediction.
/// </summary>
public int ModeI16 { get; set; }
/// <summary>
/// Mode numbers for intra4 predictions.
/// </summary>
public byte[] ModesI4 { get; }
/// <summary>
/// Mode number of chroma prediction.
/// </summary>
public int ModeUv { get; set; }
/// <summary>
/// Non-zero blocks.
/// </summary>
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;
}
}
}

9
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 };
/// <summary>
/// Distortion multiplier (equivalent of lambda).
/// </summary>
public const int RdDistoMult = 256;
/// <summary>
/// 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.

116
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][];
/// <summary>
/// Lookup table for small values of log2(int).
/// </summary>
@ -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 };
}
}
}

Loading…
Cancel
Save