diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs
index dd5c9ebda..a97dfc3de 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs
+++ b/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;
+ ///
+ /// Size of histogram used by CollectHistogram.
+ ///
+ private const int MaxCoeffThresh = 31;
+
+ private int maxValue;
+
+ private int lastNonZero;
+
///
/// Initializes a new instance of the class.
///
@@ -313,6 +323,101 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return true;
}
+ public void CollectHistogram(Span reference, Span 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 src, Span reference, Span 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;
+ }
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
index da026b199..fe7d4e095 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 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 yTop, IMemoryOwner uvTop, int mbw, int mbh)
+ ///
+ /// Stride of the prediction plane(=4*mbw + 1).
+ ///
+ 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 yTop, IMemoryOwner uvTop, IMemoryOwner preds, IMemoryOwner 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(WebPConstants.Bps * 16);
- this.YuvOut = memoryAllocator.Allocate(WebPConstants.Bps * 16);
- this.YuvOut2 = memoryAllocator.Allocate(WebPConstants.Bps * 16);
- this.YLeft = memoryAllocator.Allocate(WebPConstants.Bps + 1);
- this.ULeft = memoryAllocator.Allocate(16);
- this.VLeft = memoryAllocator.Allocate(16);
- this.TopNz = memoryAllocator.Allocate(9);
- this.LeftNz = memoryAllocator.Allocate(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
///
/// Gets or sets the input samples.
///
- public IMemoryOwner YuvIn { get; set; }
+ public byte[] YuvIn { get; set; }
///
/// Gets or sets the output samples.
///
- public IMemoryOwner YuvOut { get; set; }
+ public byte[] YuvOut { get; set; }
+
+ ///
+ /// Gets or sets the secondary buffer swapped with YuvOut.
+ ///
+ public byte[] YuvOut2 { get; set; }
- public IMemoryOwner YuvOut2 { get; set; }
+ ///
+ /// Gets or sets the scratch buffer for prediction.
+ ///
+ public byte[] YuvP { get; set; }
///
/// Gets or sets the left luma samples.
///
- public IMemoryOwner YLeft { get; set; }
+ public byte[] YLeft { get; set; }
///
/// Gets or sets the left u samples.
///
- public IMemoryOwner ULeft { get; set; }
+ public byte[] ULeft { get; set; }
///
/// Gets or sets the left v samples.
///
- public IMemoryOwner VLeft { get; set; }
+ public byte[] VLeft { get; set; }
///
/// Gets or sets the top luma samples at position 'X'.
@@ -93,21 +170,37 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
///
public IMemoryOwner Nz { get; set; }
+ ///
+ /// Gets or sets the intra mode predictors (4x4 blocks).
+ ///
+ public IMemoryOwner Preds { get; set; }
+
///
/// Gets or sets the top-non-zero context.
///
- public IMemoryOwner TopNz { get; set; }
+ public int[] TopNz { get; set; }
///
/// Gets or sets the left-non-zero. leftNz[8] is independent.
///
- public IMemoryOwner LeftNz { get; set; }
+ public int[] LeftNz { get; set; }
///
/// Gets or sets the number of mb still to be processed.
///
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 y, Span u, Span 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 yuvIn = this.YuvIn.Slice(YOffEnc);
- Span uIn = this.YuvIn.Slice(UOffEnc);
- Span vIn = this.YuvIn.Slice(VOffEnc);
+ Span yuvIn = this.YuvIn.AsSpan(YOffEnc);
+ Span uIn = this.YuvIn.AsSpan(UOffEnc);
+ Span 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 yLeft = this.YLeft.GetSpan();
- Span uLeft = this.ULeft.GetSpan();
- Span vLeft = this.VLeft.GetSpan();
+ Span yLeft = this.YLeft.AsSpan();
+ Span uLeft = this.ULeft.AsSpan();
+ Span 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 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 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 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;
+ }
+
+ ///
+ /// Returns true if iteration is finished.
+ ///
+ /// True if iterator is finished.
public bool IsDone()
{
return this.CountDown <= 0;
@@ -176,36 +405,226 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
///
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)
+ ///
+ /// Go to next macroblock.
+ ///
+ /// Returns false if not finished.
+ 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 input, Span 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 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()
+ {
+ Span left = this.X != 0 ? this.ULeft.AsSpan() : null;
+ Span 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 dst, Span left, Span 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 dst, Span left, Span 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 dst, Span left, Span 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 dst, Span 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 dst, Span 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 dst, Span left, Span top, int size)
+ {
+ if (left != null)
+ {
+ if (top != null)
+ {
+ Span 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 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 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 src, int srcStride, Span dst, int w, int h, int size)
{
int dstIdx = 0;
@@ -243,52 +662,77 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
}
+ ///
+ /// Restart a scan.
+ ///
private void Reset()
{
this.SetRow(0);
this.SetCountDown(this.mbw * this.mbh);
this.InitTop();
+
// TODO: memset(it->bit_count_, 0, sizeof(it->bit_count_));
}
+ ///
+ /// Reset iterator position to row 'y'.
+ ///
+ /// The y position.
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 yLeft = this.YLeft.GetSpan();
- Span uLeft = this.ULeft.GetSpan();
- Span vLeft = this.VLeft.GetSpan();
+ Span yLeft = this.YLeft.AsSpan();
+ Span uLeft = this.ULeft.AsSpan();
+ Span 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);
}
+ ///
+ /// Set count down.
+ ///
+ /// Number of iterations to go.
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;
+ }
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
index 1fddabaa0..7aec1870d 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
+++ b/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(pixelCount);
this.U = this.memoryAllocator.Allocate(uvSize);
this.V = this.memoryAllocator.Allocate(uvSize);
this.YTop = this.memoryAllocator.Allocate(mbw * 16);
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);
// TODO: properly initialize the bitwriter
this.bitWriter = new Vp8BitWriter();
}
+ ///
+ /// Gets or sets the global susceptibility.
+ ///
+ public int Alpha { get; set; }
+
private IMemoryOwner Y { get; }
private IMemoryOwner U { get; }
@@ -73,6 +81,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
///
private IMemoryOwner UvTop { get; }
+ ///
+ /// Gets the prediction modes: (4*mbw+1) * (4*mbh+1).
+ ///
+ private IMemoryOwner Preds { get; }
+
+ ///
+ /// Gets the non-zero pattern.
+ ///
+ private IMemoryOwner Nz { get; }
+
public void Encode(Image image, Stream stream)
where TPixel : unmanaged, IPixel
{
@@ -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(Image 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;
+ }
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockInfo.cs
new file mode 100644
index 000000000..294e548e8
--- /dev/null
+++ b/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; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8MacroBlockType.cs
new file mode 100644
index 000000000..1178851ca
--- /dev/null
+++ b/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
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs
index 4293018ef..cb751e216 100644
--- a/src/ImageSharp/Formats/WebP/WebPConstants.cs
+++ b/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;
+
///
/// 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 448a4a3c2..1a38912ea 100644
--- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs
+++ b/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
+ };
+
///
/// Lookup table for small values of log2(int).
///