Browse Source

Add macro block analyse

pull/1552/head
Brian Popow 5 years ago
parent
commit
79115d183e
  1. 111
      src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs
  2. 558
      src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
  3. 80
      src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
  4. 18
      src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs
  5. 12
      src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs
  6. 4
      src/ImageSharp/Formats/WebP/WebPConstants.cs
  7. 14
      src/ImageSharp/Formats/WebP/WebPLookupTables.cs

111
src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
@ -10,6 +11,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
private const uint NonTrivialSym = 0xffffffff;
/// <summary>
/// Size of histogram used by CollectHistogram.
/// </summary>
private const int MaxCoeffThresh = 31;
private int maxValue;
private int lastNonZero;
/// <summary>
/// Initializes a new instance of the <see cref="Vp8LHistogram"/> class.
/// </summary>
@ -313,6 +323,101 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return true;
}
public void CollectHistogram(Span<byte> reference, Span<byte> pred, int startBlock, int endBlock)
{
int j;
var distribution = new int[MaxCoeffThresh + 1];
for (j = startBlock; j < endBlock; ++j)
{
var output = new short[16];
this.Vp8FTransform(reference.Slice(WebPLookupTables.Vp8DspScan[j]), pred.Slice(WebPLookupTables.Vp8DspScan[j]), output);
// Convert coefficients to bin.
for (int k = 0; k < 16; ++k)
{
int v = Math.Abs(output[k]) >> 3;
int clippedValue = ClipMax(v, MaxCoeffThresh);
++distribution[clippedValue];
}
}
this.SetHistogramData(distribution);
}
public int GetAlpha()
{
// 'alpha' will later be clipped to [0..MAX_ALPHA] range, clamping outer
// values which happen to be mostly noise. This leaves the maximum precision
// for handling the useful small values which contribute most.
int maxValue = this.maxValue;
int lastNonZero = this.lastNonZero;
int alpha = (maxValue > 1) ? WebPConstants.AlphaScale * lastNonZero / maxValue : 0;
return alpha;
}
private void SetHistogramData(int[] distribution)
{
int maxValue = 0;
int lastNonZero = 1;
for (int k = 0; k <= MaxCoeffThresh; ++k)
{
int value = distribution[k];
if (value > 0)
{
if (value > maxValue)
{
maxValue = value;
}
lastNonZero = k;
}
}
this.maxValue = maxValue;
this.lastNonZero = lastNonZero;
}
private void Vp8FTransform(Span<byte> src, Span<byte> reference, Span<short> output)
{
int i;
var tmp = new int[16];
for (i = 0; i < 4; ++i)
{
int d0 = src[0] - reference[0]; // 9bit dynamic range ([-255,255])
int d1 = src[1] - reference[1];
int d2 = src[2] - reference[2];
int d3 = src[3] - reference[3];
int a0 = d0 + d3; // 10b [-510,510]
int a1 = d1 + d2;
int a2 = d1 - d2;
int a3 = d0 - d3;
tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160]
tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542]
tmp[2 + (i * 4)] = (a0 - a1) * 8;
tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9;
// Do not change the span in the last iteration.
if (i < 3)
{
src = src.Slice(WebPConstants.Bps);
reference = reference.Slice(WebPConstants.Bps);
}
}
for (i = 0; i < 4; ++i)
{
int a0 = tmp[0 + i] + tmp[12 + i]; // 15b
int a1 = tmp[4 + i] + tmp[8 + i];
int a2 = tmp[4 + i] - tmp[8 + i];
int a3 = tmp[0 + i] - tmp[12 + i];
output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b
output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + ((a3 != 0) ? 1 : 0));
output[8 + i] = (short)((a0 - a1 + 7) >> 4);
output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16);
}
}
private void AddLiteral(Vp8LHistogram b, Vp8LHistogram output, int literalSize)
{
if (this.IsUsed[0])
@ -524,5 +629,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
output[i] = a[i] + b[i];
}
}
[MethodImpl(InliningOptions.ShortMethod)]
private static int ClipMax(int v, int max)
{
return (v > max) ? max : v;
}
}
}

558
src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using SixLabors.ImageSharp.Formats.WebP.Lossless;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.WebP.Lossy
@ -19,24 +20,92 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
private const int VOffEnc = 16 + 8;
private const int MaxUvMode = 2;
private const int MaxIntra16Mode = 2;
private const int MaxIntra4Mode = 2;
private readonly int mbw;
private readonly int mbh;
public Vp8EncIterator(MemoryAllocator memoryAllocator, IMemoryOwner<byte> yTop, IMemoryOwner<byte> uvTop, int mbw, int mbh)
/// <summary>
/// Stride of the prediction plane(=4*mbw + 1).
/// </summary>
private readonly int predsWidth;
private const int I16DC16 = 0 * 16 * WebPConstants.Bps;
private const int I16TM16 = I16DC16 + 16;
private const int I16VE16 = 1 * 16 * WebPConstants.Bps;
private const int I16HE16 = I16VE16 + 16;
private const int C8DC8 = 2 * 16 * WebPConstants.Bps;
private const int C8TM8 = C8DC8 + (1 * 16);
private const int C8VE8 = (2 * 16 * WebPConstants.Bps) + (8 * WebPConstants.Bps);
private const int C8HE8 = C8VE8 + (1 * 16);
private readonly int[] vp8I16ModeOffsets = { I16DC16, I16TM16, I16VE16, I16HE16 };
private readonly int[] vp8UvModeOffsets = { C8DC8, C8TM8, C8VE8, C8HE8 };
private readonly byte[] clip1 = new byte[255 + 510 + 1]; // clips [-255,510] to [0,255]
private int currentMbIdx;
private int nzIdx;
private int predIdx;
private int yTopIdx;
private int uvTopIdx;
public Vp8EncIterator(IMemoryOwner<byte> yTop, IMemoryOwner<byte> uvTop, IMemoryOwner<byte> preds, IMemoryOwner<uint> nz, Vp8MacroBlockInfo[] mb, int mbw, int mbh)
{
this.mbw = mbw;
this.mbh = mbh;
this.Mb = mb;
this.currentMbIdx = 0;
this.nzIdx = 0;
this.predIdx = 0;
this.yTopIdx = 0;
this.uvTopIdx = 0;
this.YTop = yTop;
this.UvTop = uvTop;
this.YuvIn = memoryAllocator.Allocate<byte>(WebPConstants.Bps * 16);
this.YuvOut = memoryAllocator.Allocate<byte>(WebPConstants.Bps * 16);
this.YuvOut2 = memoryAllocator.Allocate<byte>(WebPConstants.Bps * 16);
this.YLeft = memoryAllocator.Allocate<byte>(WebPConstants.Bps + 1);
this.ULeft = memoryAllocator.Allocate<byte>(16);
this.VLeft = memoryAllocator.Allocate<byte>(16);
this.TopNz = memoryAllocator.Allocate<int>(9);
this.LeftNz = memoryAllocator.Allocate<int>(9);
this.Preds = preds;
this.Nz = nz;
this.predsWidth = (4 * mbw) + 1;
this.YuvIn = new byte[WebPConstants.Bps * 16];
this.YuvOut = new byte[WebPConstants.Bps * 16];
this.YuvOut2 = new byte[WebPConstants.Bps * 16];
this.YuvP = new byte[(32 * WebPConstants.Bps) + (16 * WebPConstants.Bps) + (8 * WebPConstants.Bps)]; // I16+Chroma+I4 preds
this.YLeft = new byte[32];
this.ULeft = new byte[32];
this.VLeft = new byte[32];
this.TopNz = new int[9];
this.LeftNz = new int[9];
// To match the C++ initial values, initialize all with 204.
byte defaultInitVal = 204;
this.YuvIn.AsSpan().Fill(defaultInitVal);
this.YuvOut.AsSpan().Fill(defaultInitVal);
this.YuvOut2.AsSpan().Fill(defaultInitVal);
this.YuvP.AsSpan().Fill(defaultInitVal);
this.YLeft.AsSpan().Fill(defaultInitVal);
this.ULeft.AsSpan().Fill(defaultInitVal);
this.VLeft.AsSpan().Fill(defaultInitVal);
for (int i = -255; i <= 255 + 255; ++i)
{
this.clip1[255 + i] = this.Clip8b(i);
}
this.Reset();
}
@ -54,29 +123,37 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
/// <summary>
/// Gets or sets the input samples.
/// </summary>
public IMemoryOwner<byte> YuvIn { get; set; }
public byte[] YuvIn { get; set; }
/// <summary>
/// Gets or sets the output samples.
/// </summary>
public IMemoryOwner<byte> YuvOut { get; set; }
public byte[] YuvOut { get; set; }
/// <summary>
/// Gets or sets the secondary buffer swapped with YuvOut.
/// </summary>
public byte[] YuvOut2 { get; set; }
public IMemoryOwner<byte> YuvOut2 { get; set; }
/// <summary>
/// Gets or sets the scratch buffer for prediction.
/// </summary>
public byte[] YuvP { get; set; }
/// <summary>
/// Gets or sets the left luma samples.
/// </summary>
public IMemoryOwner<byte> YLeft { get; set; }
public byte[] YLeft { get; set; }
/// <summary>
/// Gets or sets the left u samples.
/// </summary>
public IMemoryOwner<byte> ULeft { get; set; }
public byte[] ULeft { get; set; }
/// <summary>
/// Gets or sets the left v samples.
/// </summary>
public IMemoryOwner<byte> VLeft { get; set; }
public byte[] VLeft { get; set; }
/// <summary>
/// Gets or sets the top luma samples at position 'X'.
@ -93,21 +170,37 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
/// </summary>
public IMemoryOwner<uint> Nz { get; set; }
/// <summary>
/// Gets or sets the intra mode predictors (4x4 blocks).
/// </summary>
public IMemoryOwner<byte> Preds { get; set; }
/// <summary>
/// Gets or sets the top-non-zero context.
/// </summary>
public IMemoryOwner<int> TopNz { get; set; }
public int[] TopNz { get; set; }
/// <summary>
/// Gets or sets the left-non-zero. leftNz[8] is independent.
/// </summary>
public IMemoryOwner<int> LeftNz { get; set; }
public int[] LeftNz { get; set; }
/// <summary>
/// Gets or sets the number of mb still to be processed.
/// </summary>
public int CountDown { get; set; }
public Vp8MacroBlockInfo CurrentMacroBlockInfo
{
get
{
return this.Mb[this.currentMbIdx];
}
}
private Vp8MacroBlockInfo[] Mb { get; }
// 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)
{
int yStartIdx = ((this.Y * yStride) + this.X) * 16;
@ -120,9 +213,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
int uvw = (w + 1) >> 1;
int uvh = (h + 1) >> 1;
Span<byte> yuvIn = this.YuvIn.Slice(YOffEnc);
Span<byte> uIn = this.YuvIn.Slice(UOffEnc);
Span<byte> vIn = this.YuvIn.Slice(VOffEnc);
Span<byte> yuvIn = this.YuvIn.AsSpan(YOffEnc);
Span<byte> uIn = this.YuvIn.AsSpan(UOffEnc);
Span<byte> vIn = this.YuvIn.AsSpan(VOffEnc);
this.ImportBlock(ySrc, yStride, yuvIn, w, h, 16);
this.ImportBlock(uSrc, uvStride, uIn, uvw, uvh, 8);
this.ImportBlock(vSrc, uvStride, vIn, uvw, uvh, 8);
@ -134,9 +227,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
else
{
Span<byte> yLeft = this.YLeft.GetSpan();
Span<byte> uLeft = this.ULeft.GetSpan();
Span<byte> vLeft = this.VLeft.GetSpan();
Span<byte> yLeft = this.YLeft.AsSpan();
Span<byte> uLeft = this.ULeft.AsSpan();
Span<byte> vLeft = this.VLeft.AsSpan();
if (this.Y == 0)
{
yLeft[0] = 127;
@ -145,9 +238,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
else
{
yLeft[0] = ySrc[-1 - yStride];
uLeft[0] = uSrc[-1 - uvStride];
vLeft[0] = vSrc[-1 - uvStride];
yLeft[0] = y[yStartIdx - 1 - yStride];
uLeft[0] = u[uvStartIdx - 1 - uvStride];
vLeft[0] = v[uvStartIdx - 1 - uvStride];
}
this.ImportLine(y.Slice(yStartIdx - 1), yStride, yLeft.Slice(1), h, 16);
@ -155,19 +248,155 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.ImportLine(v.Slice(uvStartIdx - 1), uvStride, vLeft.Slice(1), uvh, 8);
}
Span<byte> yTop = this.YTop.Slice(this.yTopIdx);
if (this.Y == 0)
{
this.YTop.GetSpan().Fill(127);
yTop.Fill(127);
this.UvTop.GetSpan().Fill(127);
}
else
{
this.ImportLine(y.Slice(yStartIdx - yStride), 1, this.YTop.GetSpan(), w, 16);
this.ImportLine(y.Slice(yStartIdx - yStride), 1, yTop, w, 16);
this.ImportLine(u.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan(), uvw, 8);
this.ImportLine(v.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan().Slice(8), uvw, 8);
}
}
public int FastMbAnalyze(int quality)
{
// Empirical cut-off value, should be around 16 (~=block size). We use the
// [8-17] range and favor intra4 at high quality, intra16 for low quality.
int q = quality;
int kThreshold = 8 + ((17 - 8) * q / 100);
int k;
var dc = new uint[16];
uint m;
uint m2;
for (k = 0; k < 16; k += 4)
{
this.Mean16x4(this.YuvIn.AsSpan(YOffEnc + (k * WebPConstants.Bps)), dc.AsSpan(k));
}
for (m = 0, m2 = 0, k = 0; k < 16; ++k)
{
m += dc[k];
m2 += dc[k] * dc[k];
}
if (kThreshold * m2 < m * m)
{
this.SetIntra16Mode(0); // DC16
}
else
{
var modes = new byte[16]; // DC4
this.SetIntra4Mode(modes);
}
return 0;
}
public int MbAnalyzeBestIntra16Mode()
{
int maxMode = MaxIntra16Mode;
int mode;
int bestAlpha = -1;
int bestMode = 0;
this.MakeLuma16Preds();
for (mode = 0; mode < maxMode; ++mode)
{
var histo = new Vp8LHistogram();
histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(this.vp8I16ModeOffsets[mode]), 0, 16);
int alpha = histo.GetAlpha();
if (alpha > bestAlpha)
{
bestAlpha = alpha;
bestMode = mode;
}
}
this.SetIntra16Mode(bestMode);
return bestAlpha;
}
public int MbAnalyzeBestUvMode()
{
int bestAlpha = -1;
int smallestAlpha = 0;
int bestMode = 0;
int maxMode = MaxUvMode;
int mode;
this.MakeChroma8Preds();
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);
int alpha = histo.GetAlpha();
if (alpha > bestAlpha)
{
bestAlpha = alpha;
}
// The best prediction mode tends to be the one with the smallest alpha.
if (mode == 0 || alpha < smallestAlpha)
{
smallestAlpha = alpha;
bestMode = mode;
}
}
this.SetIntraUvMode(bestMode);
return bestAlpha;
}
public void SetIntra16Mode(int mode)
{
Span<byte> preds = this.Preds.Slice(this.predIdx);
for (int y = 0; y < 4; ++y)
{
preds.Slice(0, 4).Fill((byte)mode);
preds = preds.Slice(this.predsWidth);
}
this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I16X16;
}
private void SetIntra4Mode(byte[] modes)
{
int modesIdx = 0;
Span<byte> preds = this.Preds.Slice(this.predIdx);
for (int y = 4; y > 0; --y)
{
// TODO:
// memcpy(preds, modes, 4 * sizeof(*modes));
preds = preds.Slice(this.predsWidth);
modesIdx += 4;
}
this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I4X4;
}
private void SetIntraUvMode(int mode)
{
this.CurrentMacroBlockInfo.UvMode = mode;
}
public void SetSkip(bool skip)
{
this.CurrentMacroBlockInfo.Skip = skip;
}
public void SetSegment(int segment)
{
this.CurrentMacroBlockInfo.Segment = segment;
}
/// <summary>
/// Returns true if iteration is finished.
/// </summary>
/// <returns>True if iterator is finished.</returns>
public bool IsDone()
{
return this.CountDown <= 0;
@ -176,36 +405,226 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
/// <inheritdoc/>
public void Dispose()
{
this.YuvIn.Dispose();
this.YuvOut.Dispose();
this.YuvOut2.Dispose();
this.YLeft.Dispose();
this.ULeft.Dispose();
this.VLeft.Dispose();
this.Nz.Dispose();
this.LeftNz.Dispose();
this.TopNz.Dispose();
}
public bool Next(int mbw)
/// <summary>
/// Go to next macroblock.
/// </summary>
/// <returns>Returns false if not finished.</returns>
public bool Next()
{
if (++this.X == mbw)
if (++this.X == this.mbw)
{
this.SetRow(++this.Y);
}
else
{
// TODO:
/* it->preds_ += 4;
it->mb_ += 1;
it->nz_ += 1;
it->y_top_ += 16;
it->uv_top_ += 16;*/
this.currentMbIdx++;
this.nzIdx++;
this.predIdx += 4;
this.yTopIdx += 16;
this.uvTopIdx += 16;
}
return --this.CountDown > 0;
}
private void Mean16x4(Span<byte> input, Span<uint> dc)
{
int x;
for (int k = 0; k < 4; ++k)
{
uint avg = 0;
for (int y = 0; y < 4; ++y)
{
for (x = 0; x < 4; ++x)
{
avg += input[x + (y * WebPConstants.Bps)];
}
}
dc[k] = avg;
input = input.Slice(4); // go to next 4x4 block.
}
}
private 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()
{
Span<byte> left = this.X != 0 ? this.ULeft.AsSpan() : null;
Span<byte> top = this.Y != 0 ? this.UvTop.Slice(this.uvTopIdx) : null;
this.EncPredChroma8(this.YuvP, left, top);
}
// luma 16x16 prediction (paragraph 12.3)
private void EncPredLuma16(Span<byte> dst, Span<byte> left, Span<byte> top)
{
this.DcMode(dst.Slice(I16DC16), left, top, 16, 16, 5);
this.VerticalPred(dst.Slice(I16VE16), top, 16);
this.HorizontalPred(dst.Slice(I16HE16), left, 16);
this.TrueMotion(dst.Slice(I16TM16), left, top, 16);
}
// Chroma 8x8 prediction (paragraph 12.2)
private void EncPredChroma8(Span<byte> dst, Span<byte> left, Span<byte> top)
{
// U block
this.DcMode(dst.Slice(C8DC8), left, top, 8, 8, 4);
this.VerticalPred(dst.Slice(C8VE8), top, 8);
this.HorizontalPred(dst.Slice(C8HE8), left, 8);
this.TrueMotion(dst.Slice(C8TM8), left, top, 8);
// V block
dst = dst.Slice(8);
if (top != null)
{
top = top.Slice(8);
}
if (left != null)
{
left = left.Slice(16);
}
this.DcMode(dst.Slice(C8DC8), left, top, 8, 8, 4);
this.VerticalPred(dst.Slice(C8VE8), top, 8);
this.HorizontalPred(dst.Slice(C8HE8), left, 8);
this.TrueMotion(dst.Slice(C8TM8), left, top, 8);
}
private void DcMode(Span<byte> dst, Span<byte> left, Span<byte> top, int size, int round, int shift)
{
int dc = 0;
int j;
if (top != null)
{
for (j = 0; j < size; ++j)
{
dc += top[j];
}
if (left != null)
{
// top and left present.
left = left.Slice(1); // in the reference implementation, left starts at -1.
for (j = 0; j < size; ++j)
{
dc += left[j];
}
}
else
{
// top, but no left.
dc += dc;
}
dc = (dc + round) >> shift;
}
else if (left != null)
{
// left but no top.
for (j = 0; j < size; ++j)
{
dc += left[j];
}
dc += dc;
dc = (dc + round) >> shift;
}
else
{
// no top, no left, nothing.
dc = 0x80;
}
this.Fill(dst, dc, size);
}
private void VerticalPred(Span<byte> dst, Span<byte> top, int size)
{
if (top != null)
{
for (int j = 0; j < size; ++j)
{
top.Slice(0, size).CopyTo(dst.Slice(j * WebPConstants.Bps));
}
}
else
{
this.Fill(dst, 127, size);
}
}
private void HorizontalPred(Span<byte> dst, Span<byte> left, int size)
{
if (left != null)
{
left = left.Slice(1); // in the reference implementation, left starts at - 1.
for (int j = 0; j < size; ++j)
{
dst.Slice(j * WebPConstants.Bps, size).Fill(left[j]);
}
}
else
{
this.Fill(dst, 129, size);
}
}
private void TrueMotion(Span<byte> dst, Span<byte> left, Span<byte> top, int size)
{
if (left != null)
{
if (top != null)
{
Span<byte> clip = this.clip1.AsSpan(255 - left[0]); // left [0] instead of left[-1], original left starts at -1
for (int y = 0; y < size; ++y)
{
Span<byte> clipTable = clip.Slice(left[y + 1]); // left[y]
for (int x = 0; x < size; ++x)
{
dst[x] = clipTable[top[x]];
}
dst = dst.Slice(WebPConstants.Bps);
}
}
else
{
this.HorizontalPred(dst, left, size);
}
}
else
{
// true motion without left samples (hence: with default 129 value)
// is equivalent to VE prediction where you just copy the top samples.
// Note that if top samples are not available, the default value is
// then 129, and not 127 as in the VerticalPred case.
if (top != null)
{
this.VerticalPred(dst, top, size);
}
else
{
this.Fill(dst, 129, size);
}
}
}
private void Fill(Span<byte> dst, int value, int size)
{
for (int j = 0; j < size; ++j)
{
dst.Slice(j * WebPConstants.Bps, size).Fill((byte)value);
}
}
private void ImportBlock(Span<byte> src, int srcStride, Span<byte> dst, int w, int h, int size)
{
int dstIdx = 0;
@ -243,52 +662,77 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
}
/// <summary>
/// Restart a scan.
/// </summary>
private void Reset()
{
this.SetRow(0);
this.SetCountDown(this.mbw * this.mbh);
this.InitTop();
// TODO: memset(it->bit_count_, 0, sizeof(it->bit_count_));
}
/// <summary>
/// Reset iterator position to row 'y'.
/// </summary>
/// <param name="y">The y position.</param>
private void SetRow(int y)
{
this.X = 0;
this.Y = y;
this.currentMbIdx = y * this.mbw;
this.nzIdx = 0;
this.yTopIdx = 0;
this.uvTopIdx = 0;
this.InitLeft();
// TODO:
// it->preds_ = enc->preds_ + y * 4 * enc->preds_w_;
// it->nz_ = enc->nz_;
// it->mb_ = enc->mb_info_ + y * enc->mb_w_;
// it->y_top_ = enc->y_top_;
// it->uv_top_ = enc->uv_top_;
}
private void InitLeft()
{
Span<byte> yLeft = this.YLeft.GetSpan();
Span<byte> uLeft = this.ULeft.GetSpan();
Span<byte> vLeft = this.VLeft.GetSpan();
Span<byte> yLeft = this.YLeft.AsSpan();
Span<byte> uLeft = this.ULeft.AsSpan();
Span<byte> vLeft = this.VLeft.AsSpan();
byte val = (byte)((this.Y > 0) ? 129 : 127);
yLeft[0] = val;
uLeft[0] = val;
vLeft[0] = val;
this.YLeft.Slice(1).Fill(129);
this.ULeft.Slice(1).Fill(129);
this.VLeft.Slice(1).Fill(129);
this.LeftNz.GetSpan()[8] = 0;
uLeft[16] = val;
vLeft[16] = val;
yLeft.Slice(1, 16).Fill(129);
uLeft.Slice(1, 8).Fill(129);
vLeft.Slice(1, 8).Fill(129);
uLeft.Slice(1 + 16, 8).Fill(129);
vLeft.Slice(1 + 16, 8).Fill(129);
this.LeftNz[8] = 0;
}
private void InitTop()
{
int topSize = this.mbw * 16;
this.YTop.Slice(0, topSize).Fill(127);
// TODO: memset(enc->nz_, 0, enc->mb_w_ * sizeof(*enc->nz_));
this.Nz.GetSpan().Fill(0);
}
/// <summary>
/// Set count down.
/// </summary>
/// <param name="countDown">Number of iterations to go.</param>
private void SetCountDown(int countDown)
{
this.CountDown = countDown;
}
private byte Clip8b(int v)
{
return ((v & ~0xff) == 0) ? (byte)v : (v < 0) ? (byte)0 : (byte)255;
}
}
}

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

@ -46,17 +46,25 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
var pixelCount = width * height;
int mbw = (width + 15) >> 4;
int mbh = (height + 15) >> 4;
var uvSize = ((width + 1) >> 1) * ((height + 1) >> 1);
this.Y = this.memoryAllocator.Allocate<byte>(pixelCount);
this.U = this.memoryAllocator.Allocate<byte>(uvSize);
this.V = this.memoryAllocator.Allocate<byte>(uvSize);
this.YTop = this.memoryAllocator.Allocate<byte>(mbw * 16);
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);
// TODO: properly initialize the bitwriter
this.bitWriter = new Vp8BitWriter();
}
/// <summary>
/// Gets or sets the global susceptibility.
/// </summary>
public int Alpha { get; set; }
private IMemoryOwner<byte> Y { get; }
private IMemoryOwner<byte> U { get; }
@ -73,6 +81,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
/// </summary>
private IMemoryOwner<byte> UvTop { get; }
/// <summary>
/// Gets the prediction modes: (4*mbw+1) * (4*mbh+1).
/// </summary>
private IMemoryOwner<byte> Preds { get; }
/// <summary>
/// Gets the non-zero pattern.
/// </summary>
private IMemoryOwner<uint> Nz { get; }
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -85,15 +103,30 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
int mbh = (image.Height + 15) >> 4;
int yStride = image.Width;
int uvStride = (yStride + 1) >> 1;
var it = new Vp8EncIterator(this.memoryAllocator, this.YTop, this.UvTop, mbw, mbh);
var mb = new Vp8MacroBlockInfo[mbw * mbh];
for (int i = 0; i < mb.Length; i++)
{
mb[i] = new Vp8MacroBlockInfo();
}
var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Preds, this.Nz, mb, mbw, mbh);
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())
{
do
{
it.Import(y, u, v, yStride, uvStride, image.Width, image.Height);
// TODO: MBAnalyze
int bestAlpha = this.MbAnalyze(it, method, quality, alphas, out var bestUvAlpha);
// Accumulate for later complexity analysis.
alpha += bestAlpha;
uvAlpha += bestUvAlpha;
}
while (it.Next(mbw));
while (it.Next());
}
throw new NotImplementedException();
@ -107,6 +140,34 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.V.Dispose();
this.YTop.Dispose();
this.UvTop.Dispose();
this.Preds.Dispose();
}
private int MbAnalyze(Vp8EncIterator it, int method, int quality, int[] alphas, out int bestUvAlpha)
{
it.SetIntra16Mode(0); // default: Intra16, DC_PRED
it.SetSkip(false); // not skipped.
it.SetSegment(0); // default segment, spec-wise.
int bestAlpha;
if (method <= 1)
{
bestAlpha = it.FastMbAnalyze(quality);
}
else
{
bestAlpha = it.MbAnalyzeBestIntra16Mode();
}
bestUvAlpha = it.MbAnalyzeBestUvMode();
// Final susceptibility mix.
bestAlpha = ((3 * bestAlpha) + bestUvAlpha + 2) >> 2;
bestAlpha = this.FinalAlphaValue(bestAlpha);
alphas[bestAlpha]++;
it.CurrentMacroBlockInfo.Alpha = bestAlpha; // For later remapping.
return bestAlpha; // Mixed susceptibility (not just luma)
}
private void ConvertRgbToYuv<TPixel>(Image<TPixel> image)
@ -395,5 +456,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2);
return ((uv & ~0xff) == 0) ? uv : (uv < 0) ? 0 : 255;
}
[MethodImpl(InliningOptions.ShortMethod)]
private int FinalAlphaValue(int alpha)
{
alpha = WebPConstants.MaxAlpha - alpha;
return this.Clip(alpha, 0, WebPConstants.MaxAlpha);
}
[MethodImpl(InliningOptions.ShortMethod)]
private int Clip(int v, int min, int max)
{
return (v < min) ? min : (v > max) ? max : v;
}
}
}

18
src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.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 Vp8MacroBlockInfo
{
public Vp8MacroBlockType MacroBlockType { get; set; }
public int UvMode { get; set; }
public bool Skip { get; set; }
public int Segment { get; set; }
public int Alpha { get; set; }
}
}

12
src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs

@ -0,0 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.WebP.Lossy
{
internal enum Vp8MacroBlockType
{
I4X4 = 0,
I16X16 = 1
}
}

4
src/ImageSharp/Formats/WebP/WebPConstants.cs

@ -205,6 +205,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
public const int AlphaFix = 19;
public const int MaxAlpha = 255;
public const int AlphaScale = 2 * MaxAlpha;
/// <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.

14
src/ImageSharp/Formats/WebP/WebPLookupTables.cs

@ -22,6 +22,20 @@ namespace SixLabors.ImageSharp.Formats.WebP
public static readonly int[] LinearToGammaTab = new int[WebPConstants.GammaTabSize + 1];
// Compute susceptibility based on DCT-coeff histograms:
// the higher, the "easier" the macroblock is to compress.
public static readonly int[] Vp8DspScan =
{
// 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),
0 + (0 * WebPConstants.Bps), 4 + (0 * WebPConstants.Bps), 0 + (4 * WebPConstants.Bps), 4 + (4 * WebPConstants.Bps), // U
8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps) // V
};
/// <summary>
/// Lookup table for small values of log2(int).
/// </summary>

Loading…
Cancel
Save