Browse Source

Fix some VP8 encoding mistakes

pull/1552/head
Brian Popow 6 years ago
parent
commit
98fb8af07d
  1. 81
      src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
  2. 65
      src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
  3. 48
      src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs

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

@ -99,21 +99,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
private int uvTopIdx; private int uvTopIdx;
public Vp8EncIterator(IMemoryOwner<byte> yTop, IMemoryOwner<byte> uvTop, IMemoryOwner<byte> preds, IMemoryOwner<uint> nz, Vp8MacroBlockInfo[] mb, int mbw, int mbh) public Vp8EncIterator(byte[] yTop, byte[] uvTop, uint[] nz, Vp8MacroBlockInfo[] mb, byte[] preds, int mbw, int mbh)
{ {
this.mbw = mbw; this.mbw = mbw;
this.mbh = mbh; this.mbh = mbh;
this.Mb = mb; this.Mb = mb;
this.currentMbIdx = 0; this.currentMbIdx = 0;
this.nzIdx = 0; this.nzIdx = 1;
this.predIdx = 0;
this.yTopIdx = 0; this.yTopIdx = 0;
this.uvTopIdx = 0; this.uvTopIdx = 0;
this.YTop = yTop; this.YTop = yTop;
this.UvTop = uvTop; this.UvTop = uvTop;
this.Preds = preds;
this.Nz = nz; this.Nz = nz;
this.Preds = preds;
this.predsWidth = (4 * mbw) + 1; this.predsWidth = (4 * mbw) + 1;
this.predIdx = this.predsWidth;
this.YuvIn = new byte[WebPConstants.Bps * 16]; this.YuvIn = new byte[WebPConstants.Bps * 16];
this.YuvOut = new byte[WebPConstants.Bps * 16]; this.YuvOut = new byte[WebPConstants.Bps * 16];
this.YuvOut2 = new byte[WebPConstants.Bps * 16]; this.YuvOut2 = new byte[WebPConstants.Bps * 16];
@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.I4Boundary = new byte[37]; this.I4Boundary = new byte[37];
this.BitCount = new long[4, 3]; this.BitCount = new long[4, 3];
// To match the C++ initial values of the reference implementation, initialize all with 204. // To match the C initial values of the reference implementation, initialize all with 204.
byte defaultInitVal = 204; byte defaultInitVal = 204;
this.YuvIn.AsSpan().Fill(defaultInitVal); this.YuvIn.AsSpan().Fill(defaultInitVal);
this.YuvOut.AsSpan().Fill(defaultInitVal); this.YuvOut.AsSpan().Fill(defaultInitVal);
@ -133,7 +133,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.YuvP.AsSpan().Fill(defaultInitVal); this.YuvP.AsSpan().Fill(defaultInitVal);
this.YLeft.AsSpan().Fill(defaultInitVal); this.YLeft.AsSpan().Fill(defaultInitVal);
this.UvLeft.AsSpan().Fill(defaultInitVal); this.UvLeft.AsSpan().Fill(defaultInitVal);
this.Preds.GetSpan().Fill(defaultInitVal);
for (int i = -255; i <= 255 + 255; ++i) for (int i = -255; i <= 255 + 255; ++i)
{ {
@ -186,17 +185,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
/// <summary> /// <summary>
/// Gets the top luma samples at position 'X'. /// Gets the top luma samples at position 'X'.
/// </summary> /// </summary>
public IMemoryOwner<byte> YTop { get; } public byte[] YTop { get; }
/// <summary> /// <summary>
/// Gets the top u/v samples at position 'X', packed as 16 bytes. /// Gets the top u/v samples at position 'X', packed as 16 bytes.
/// </summary> /// </summary>
public IMemoryOwner<byte> UvTop { get; } public byte[] UvTop { get; }
/// <summary> /// <summary>
/// Gets the intra mode predictors (4x4 blocks). /// Gets the intra mode predictors (4x4 blocks).
/// </summary> /// </summary>
public IMemoryOwner<byte> Preds { get; } public byte[] Preds { get; }
/// <summary> /// <summary>
/// Gets the current start index of the intra mode predictors. /// Gets the current start index of the intra mode predictors.
@ -212,7 +211,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
/// <summary> /// <summary>
/// Gets the non-zero pattern. /// Gets the non-zero pattern.
/// </summary> /// </summary>
public IMemoryOwner<uint> Nz { get; } public uint[] Nz { get; }
/// <summary> /// <summary>
/// Gets 32+5 boundary samples needed by intra4x4. /// Gets 32+5 boundary samples needed by intra4x4.
@ -292,7 +291,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.I4Boundary[i] = this.YLeft[15 - i + 1]; this.I4Boundary[i] = this.YLeft[15 - i + 1];
} }
Span<byte> yTop = this.YTop.GetSpan(); Span<byte> yTop = this.YTop.AsSpan(this.yTopIdx);
for (i = 0; i < 16; ++i) for (i = 0; i < 16; ++i)
{ {
// top // top
@ -320,7 +319,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
} }
// Import uncompressed samples from source. // 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) public void Import(Span<byte> y, Span<byte> u, Span<byte> v, int yStride, int uvStride, int width, int height, bool importBoundarySamples)
{ {
int yStartIdx = ((this.Y * yStride) + this.X) * 16; int yStartIdx = ((this.Y * yStride) + this.X) * 16;
int uvStartIdx = ((this.Y * uvStride) + this.X) * 8; int uvStartIdx = ((this.Y * uvStride) + this.X) * 8;
@ -339,6 +338,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.ImportBlock(uSrc, uvStride, uIn, uvw, uvh, 8); this.ImportBlock(uSrc, uvStride, uIn, uvw, uvh, 8);
this.ImportBlock(vSrc, uvStride, vIn, uvw, uvh, 8); this.ImportBlock(vSrc, uvStride, vIn, uvw, uvh, 8);
if (!importBoundarySamples)
{
return;
}
// Import source (uncompressed) samples into boundary. // Import source (uncompressed) samples into boundary.
if (this.X == 0) if (this.X == 0)
{ {
@ -367,17 +371,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.ImportLine(v.Slice(uvStartIdx - 1), uvStride, vLeft.Slice(1), uvh, 8); this.ImportLine(v.Slice(uvStartIdx - 1), uvStride, vLeft.Slice(1), uvh, 8);
} }
Span<byte> yTop = this.YTop.Slice(this.yTopIdx, 16); Span<byte> yTop = this.YTop.AsSpan(this.yTopIdx, 16);
if (this.Y == 0) if (this.Y == 0)
{ {
yTop.Fill(127); yTop.Fill(127);
this.UvTop.Slice(this.uvTopIdx, 16).Fill(127); this.UvTop.AsSpan(this.uvTopIdx, 16).Fill(127);
} }
else else
{ {
this.ImportLine(y.Slice(yStartIdx - yStride), 1, yTop, w, 16); this.ImportLine(y.Slice(yStartIdx - yStride), 1, yTop, w, 16);
this.ImportLine(u.Slice(uvStartIdx - uvStride), 1, this.UvTop.Slice(this.uvTopIdx, 8), uvw, 8); this.ImportLine(u.Slice(uvStartIdx - uvStride), 1, this.UvTop.AsSpan(this.uvTopIdx, 8), uvw, 8);
this.ImportLine(v.Slice(uvStartIdx - uvStride), 1, this.UvTop.Slice(this.uvTopIdx + 8, 8), uvw, 8); this.ImportLine(v.Slice(uvStartIdx - uvStride), 1, this.UvTop.AsSpan(this.uvTopIdx + 8, 8), uvw, 8);
} }
} }
@ -472,7 +476,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
public void SetIntra16Mode(int mode) public void SetIntra16Mode(int mode)
{ {
Span<byte> preds = this.Preds.Slice(this.predIdx); Span<byte> preds = this.Preds.AsSpan(this.predIdx);
for (int y = 0; y < 4; ++y) for (int y = 0; y < 4; ++y)
{ {
preds.Slice(0, 4).Fill((byte)mode); preds.Slice(0, 4).Fill((byte)mode);
@ -488,7 +492,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
int predIdx = this.predIdx; int predIdx = this.predIdx;
for (int y = 4; y > 0; --y) for (int y = 4; y > 0; --y)
{ {
modes.AsSpan(modesIdx, 4).CopyTo(this.Preds.Slice(predIdx)); modes.AsSpan(modesIdx, 4).CopyTo(this.Preds.AsSpan(predIdx));
predIdx += this.predsWidth; predIdx += this.predsWidth;
modesIdx += 4; modesIdx += 4;
} }
@ -502,8 +506,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
int predIdx = this.predIdx; int predIdx = this.predIdx;
int x = this.I4 & 3; int x = this.I4 & 3;
int y = this.I4 >> 2; int y = this.I4 >> 2;
int left = (x == 0) ? this.Preds.Slice(predIdx + (y * predsWidth) - 1)[0] : modes[this.I4 - 1]; int left = (x == 0) ? this.Preds[predIdx + (y * predsWidth) - 1] : modes[this.I4 - 1];
int top = (y == 0) ? this.Preds.Slice(predIdx - predsWidth + x)[0] : modes[this.I4 - 4]; int top = (y == 0) ? this.Preds[predIdx - predsWidth + x] : modes[this.I4 - 4];
return WebPLookupTables.Vp8FixedCostsI4[top, left]; return WebPLookupTables.Vp8FixedCostsI4[top, left];
} }
@ -574,16 +578,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
} }
// top-left (before 'top'!) // top-left (before 'top'!)
this.YLeft[0] = this.YTop.GetSpan()[15]; this.YLeft[0] = this.YTop[this.yTopIdx + 15];
this.UvLeft[0] = this.UvTop.GetSpan()[0 + 7]; this.UvLeft[0] = this.UvTop[this.uvTopIdx + 0 + 7];
this.UvLeft[16] = this.UvTop.GetSpan()[8 + 7]; this.UvLeft[16] = this.UvTop[this.uvTopIdx + 8 + 7];
} }
if (y < this.mbh - 1) if (y < this.mbh - 1)
{ {
// top // top
ySrc.Slice(15 * WebPConstants.Bps, 16).CopyTo(this.YTop.GetSpan()); ySrc.Slice(15 * WebPConstants.Bps, 16).CopyTo(this.YTop.AsSpan(this.yTopIdx));
uvSrc.Slice(7 * WebPConstants.Bps, 8 + 8).CopyTo(this.UvTop.GetSpan()); uvSrc.Slice(7 * WebPConstants.Bps, 8 + 8).CopyTo(this.UvTop.AsSpan(this.uvTopIdx));
} }
} }
@ -636,26 +640,26 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
if (this.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16) if (this.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16)
{ {
// Reset all predictors. // Reset all predictors.
this.Nz.GetSpan()[0] = 0; this.Nz[this.nzIdx] = 0;
this.LeftNz[8] = 0; this.LeftNz[8] = 0;
} }
else else
{ {
this.Nz.GetSpan()[0] &= 1 << 24; // Preserve the dc_nz bit. this.Nz[this.nzIdx] &= 1 << 24; // Preserve the dc_nz bit.
} }
} }
public void MakeLuma16Preds() public void MakeLuma16Preds()
{ {
Span<byte> left = this.X != 0 ? this.YLeft.AsSpan() : null; Span<byte> left = this.X != 0 ? this.YLeft.AsSpan() : null;
Span<byte> top = this.Y != 0 ? this.YTop.Slice(this.yTopIdx) : null; Span<byte> top = this.Y != 0 ? this.YTop.AsSpan(this.yTopIdx) : null;
this.EncPredLuma16(this.YuvP, left, top); this.EncPredLuma16(this.YuvP, left, top);
} }
public void MakeChroma8Preds() public void MakeChroma8Preds()
{ {
Span<byte> left = this.X != 0 ? this.UvLeft.AsSpan() : null; Span<byte> left = this.X != 0 ? this.UvLeft.AsSpan() : null;
Span<byte> top = this.Y != 0 ? this.UvTop.Slice(this.uvTopIdx) : null; Span<byte> top = this.Y != 0 ? this.UvTop.AsSpan(this.uvTopIdx) : null;
this.EncPredChroma8(this.YuvP, left, top); this.EncPredChroma8(this.YuvP, left, top);
} }
@ -673,10 +677,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
public void NzToBytes() public void NzToBytes()
{ {
Span<uint> nz = this.Nz.GetSpan(); Span<uint> nz = this.Nz.AsSpan();
uint lnz = 0; // TODO: -1? uint lnz = nz[this.nzIdx - 1];
uint tnz = nz[0]; uint tnz = nz[this.nzIdx];
Span<int> topNz = this.TopNz; Span<int> topNz = this.TopNz;
Span<int> leftNz = this.LeftNz; Span<int> leftNz = this.LeftNz;
@ -731,6 +735,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
nz |= (uint)((leftNz[0] << 3) | (leftNz[1] << 7)); nz |= (uint)((leftNz[0] << 3) | (leftNz[1] << 7));
nz |= (uint)(leftNz[2] << 11); nz |= (uint)(leftNz[2] << 11);
nz |= (uint)((leftNz[4] << 17) | (leftNz[6] << 21)); nz |= (uint)((leftNz[4] << 17) | (leftNz[6] << 21));
this.Nz[this.nzIdx] = nz;
} }
private void Mean16x4(Span<byte> input, Span<uint> dc) private void Mean16x4(Span<byte> input, Span<uint> dc)
@ -1257,7 +1263,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.X = 0; this.X = 0;
this.Y = y; this.Y = y;
this.currentMbIdx = y * this.mbw; this.currentMbIdx = y * this.mbw;
this.nzIdx = 0; this.nzIdx = 1; // note: in reference source nz starts at -1.
this.yTopIdx = 0; this.yTopIdx = 0;
this.uvTopIdx = 0; this.uvTopIdx = 0;
this.predIdx = this.predsWidth + (y * 4 * this.predsWidth); this.predIdx = this.predsWidth + (y * 4 * this.predsWidth);
@ -1285,15 +1291,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
private void InitTop() private void InitTop()
{ {
int topSize = this.mbw * 16; int topSize = this.mbw * 16;
this.YTop.Slice(0, topSize).Fill(127); this.YTop.AsSpan(0, topSize).Fill(127);
this.UvTop.GetSpan().Fill(127); this.UvTop.AsSpan().Fill(127);
this.Nz.GetSpan().Fill(0); this.Nz.AsSpan().Fill(0);
} }
// Convert packed context to byte array.
private int Bit(uint nz, int n) private int Bit(uint nz, int n)
{ {
return (int)(nz & (1 << n)); return (nz & (1 << n)) != 0 ? 1 : 0;
} }
/// <summary> /// <summary>

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

@ -149,9 +149,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.Y = this.memoryAllocator.Allocate<byte>(pixelCount); this.Y = this.memoryAllocator.Allocate<byte>(pixelCount);
this.U = this.memoryAllocator.Allocate<byte>(uvSize); this.U = this.memoryAllocator.Allocate<byte>(uvSize);
this.V = this.memoryAllocator.Allocate<byte>(uvSize); this.V = this.memoryAllocator.Allocate<byte>(uvSize);
this.YTop = this.memoryAllocator.Allocate<byte>(this.mbw * 16); this.YTop = new byte[this.mbw * 16];
this.UvTop = this.memoryAllocator.Allocate<byte>(this.mbw * 16 * 2); this.UvTop = new byte[this.mbw * 16 * 2];
this.Nz = this.memoryAllocator.Allocate<uint>(this.mbw + 1); this.Nz = new uint[this.mbw + 1];
this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (this.mbw * this.mbh); this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (this.mbw * this.mbh);
int predSize = (((4 * this.mbw) + 1) * ((4 * this.mbh) + 1)) + this.predsWidth + 1; int predSize = (((4 * this.mbw) + 1) * ((4 * this.mbh) + 1)) + this.predsWidth + 1;
@ -169,10 +169,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.proba = new Vp8EncProba(); this.proba = new Vp8EncProba();
// this.Preds = this.memoryAllocator.Allocate<byte>(predSize); this.Preds = new byte[predSize * 2]; // TODO: figure out how much mem we need here. This is too much.
this.Preds = this.memoryAllocator.Allocate<byte>(predSize * 2); // TODO: figure out how much mem we need here. This is too much.
this.predsWidth = (4 * this.mbw) + 1; this.predsWidth = (4 * this.mbw) + 1;
// Initialize with default values, which the reference c implementation uses,
// to be able to compare to the original and spot differences.
this.Preds.AsSpan().Fill(205);
this.Nz.AsSpan().Fill(3452816845);
this.ResetBoundaryPredictions(); this.ResetBoundaryPredictions();
// Initialize the bitwriter. // Initialize the bitwriter.
@ -205,22 +209,22 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
/// <summary> /// <summary>
/// Gets the top luma samples. /// Gets the top luma samples.
/// </summary> /// </summary>
private IMemoryOwner<byte> YTop { get; } private byte[] YTop { get; }
/// <summary> /// <summary>
/// Gets the top u/v samples. U and V are packed into 16 bytes (8 U + 8 V). /// Gets the top u/v samples. U and V are packed into 16 bytes (8 U + 8 V).
/// </summary> /// </summary>
private IMemoryOwner<byte> UvTop { get; } private byte[] UvTop { get; }
/// <summary> /// <summary>
/// Gets the prediction modes: (4*mbw+1) * (4*mbh+1). /// Gets the non-zero pattern.
/// </summary> /// </summary>
private IMemoryOwner<byte> Preds { get; } private uint[] Nz { get; }
/// <summary> /// <summary>
/// Gets the non-zero pattern. /// Gets the prediction modes: (4*mbw+1) * (4*mbh+1).
/// </summary> /// </summary>
private IMemoryOwner<uint> Nz { get; } private byte[] Preds { get; }
/// <summary> /// <summary>
/// Gets a rough limit for header bits per MB. /// Gets a rough limit for header bits per MB.
@ -251,7 +255,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
segmentInfos[i] = new Vp8SegmentInfo(); segmentInfos[i] = new Vp8SegmentInfo();
} }
var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Preds, this.Nz, this.mbInfo, this.mbw, this.mbh); var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.mbw, this.mbh);
var alphas = new int[WebPConstants.MaxAlpha + 1]; var alphas = new int[WebPConstants.MaxAlpha + 1];
this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha);
@ -268,7 +272,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
do do
{ {
var info = new Vp8ModeScore(); var info = new Vp8ModeScore();
it.Import(y, u, v, yStride, uvStride, width, height); it.Import(y, u, v, yStride, uvStride, width, height, false);
if (!this.Decimate(it, segmentInfos, info, this.rdOptLevel)) if (!this.Decimate(it, segmentInfos, info, this.rdOptLevel))
{ {
this.CodeResiduals(it, info); this.CodeResiduals(it, info);
@ -292,9 +296,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.Y.Dispose(); this.Y.Dispose();
this.U.Dispose(); this.U.Dispose();
this.V.Dispose(); this.V.Dispose();
this.YTop.Dispose();
this.UvTop.Dispose();
this.Preds.Dispose();
} }
/// <summary> /// <summary>
@ -305,7 +306,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
private void StatLoop(int width, int height, int yStride, int uvStride, Vp8SegmentInfo[] segmentInfos) private void StatLoop(int width, int height, int yStride, int uvStride, Vp8SegmentInfo[] segmentInfos)
{ {
int targetSize = 0; // TODO: target size is hardcoded. int targetSize = 0; // TODO: target size is hardcoded.
float targetPsnr = 0.0f; // TDOO: targetPsnr is hardcoded. float targetPsnr = 0.0f; // TODO: targetPsnr is hardcoded.
int method = this.method; int method = this.method;
bool doSearch = false; // TODO: doSearch hardcoded for now. bool doSearch = false; // TODO: doSearch hardcoded for now.
bool fastProbe = (method == 0 || method == 3) && !doSearch; bool fastProbe = (method == 0 || method == 3) && !doSearch;
@ -377,7 +378,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
Span<byte> y = this.Y.GetSpan(); Span<byte> y = this.Y.GetSpan();
Span<byte> u = this.U.GetSpan(); Span<byte> u = this.U.GetSpan();
Span<byte> v = this.V.GetSpan(); Span<byte> v = this.V.GetSpan();
var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Preds, this.Nz, this.mbInfo, this.mbw, this.mbh); var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.mbw, this.mbh);
long size = 0; long size = 0;
long sizeP0 = 0; long sizeP0 = 0;
long distortion = 0; long distortion = 0;
@ -387,7 +388,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
do do
{ {
var info = new Vp8ModeScore(); var info = new Vp8ModeScore();
it.Import(y, u, v, yStride, uvStride, width, height); it.Import(y, u, v, yStride, uvStride, width, height, false);
if (this.Decimate(it, segmentInfos, info, rdOpt)) if (this.Decimate(it, segmentInfos, info, rdOpt))
{ {
// Just record the number of skips and act like skipProba is not used. // Just record the number of skips and act like skipProba is not used.
@ -432,8 +433,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
private void ResetBoundaryPredictions() private void ResetBoundaryPredictions()
{ {
Span<byte> top = this.Preds.GetSpan(); Span<byte> top = this.Preds.AsSpan(); // original source top starts at: enc->preds_ - enc->preds_w_
Span<byte> left = this.Preds.Slice(this.predsWidth - 1); Span<byte> left = this.Preds.AsSpan(this.predsWidth - 1);
for (int i = 0; i < 4 * this.mbw; ++i) for (int i = 0; i < 4 * this.mbw; ++i)
{ {
top[i] = (int)IntraPredictionMode.DcPrediction; top[i] = (int)IntraPredictionMode.DcPrediction;
@ -444,7 +445,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
left[i * this.predsWidth] = (int)IntraPredictionMode.DcPrediction; left[i * this.predsWidth] = (int)IntraPredictionMode.DcPrediction;
} }
// TODO: enc->nz_[-1] = 0; // constant this.Nz[0] = 0; // constant
} }
// Simplified k-Means, to assign Nb segments based on alpha-histogram. // Simplified k-Means, to assign Nb segments based on alpha-histogram.
@ -704,7 +705,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
{ {
do do
{ {
it.Import(y, u, v, yStride, uvStride, width, height); it.Import(y, u, v, yStride, uvStride, width, height, true);
int bestAlpha = this.MbAnalyze(it, alphas, out var bestUvAlpha); int bestAlpha = this.MbAnalyze(it, alphas, out var bestUvAlpha);
// Accumulate for later complexity analysis. // Accumulate for later complexity analysis.
@ -875,7 +876,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
} }
else else
{ {
nz = this.ReconstructIntra16(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), it.Preds.Slice(it.PredIdx)[0]); int intra16Mode = it.Preds[it.PredIdx];
nz = this.ReconstructIntra16(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), intra16Mode);
} }
// ... and UV! // ... and UV!
@ -974,7 +976,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
int x, y, ch; int x, y, ch;
var residual = new Vp8Residual(); var residual = new Vp8Residual();
bool i16 = it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; bool i16 = it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16;
int segment = it.CurrentMacroBlockInfo.Segment;
it.NzToBytes(); it.NzToBytes();
@ -1041,7 +1042,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.FTransform2(src.Slice(WebPLookupTables.Vp8Scan[n]), reference.Slice(WebPLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 16), tmpSpan.Slice((n + 1) * 16, 16)); this.FTransform2(src.Slice(WebPLookupTables.Vp8Scan[n]), reference.Slice(WebPLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 16), tmpSpan.Slice((n + 1) * 16, 16));
} }
this.FTransformWht(tmp.AsSpan(0), dcTmp); this.FTransformWht(tmp, dcTmp);
nz |= this.QuantizeBlock(dcTmp, rd.YDcLevels, dqm.Y2) << 24; nz |= this.QuantizeBlock(dcTmp, rd.YDcLevels, dqm.Y2) << 24;
for (n = 0; n < 16; n += 2) for (n = 0; n < 16; n += 2)
@ -1049,7 +1050,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
// Zero-out the first coeff, so that: a) nz is correct below, and // Zero-out the first coeff, so that: a) nz is correct below, and
// b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified. // b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified.
tmp[n * 16] = tmp[(n + 1) * 16] = 0; tmp[n * 16] = tmp[(n + 1) * 16] = 0;
nz |= this.Quantize2Blocks(tmpSpan.Slice(n * 16), rd.YAcLevels.AsSpan(n * 16, 32), dqm.Y1) << n; nz |= this.Quantize2Blocks(tmpSpan.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), dqm.Y1) << n;
} }
// Transform back. // Transform back.
@ -1279,10 +1280,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
int b = dc - tmp[8]; int b = dc - tmp[8];
int c = Mul(tmp[4], KC2) - Mul(tmp[12], KC1); int c = Mul(tmp[4], KC2) - Mul(tmp[12], KC1);
int d = Mul(tmp[4], KC1) + Mul(tmp[12], KC2); int d = Mul(tmp[4], KC1) + Mul(tmp[12], KC2);
Store(dst, reference, 0, i, (byte)(a + d)); Store(dst, reference, 0, i, (a + d));
Store(dst, reference, 1, i, (byte)(b + c)); Store(dst, reference, 1, i, (b + c));
Store(dst, reference, 2, i, (byte)(b - c)); Store(dst, reference, 2, i, (b - c));
Store(dst, reference, 3, i, (byte)(a - d)); Store(dst, reference, 3, i, (a - d));
tmp = tmp.Slice(1); tmp = tmp.Slice(1);
} }
} }
@ -1675,7 +1676,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static void Store(Span<byte> dst, Span<byte> reference, int x, int y, byte v) private static void Store(Span<byte> dst, Span<byte> reference, int x, int y, int v)
{ {
dst[x + (y * WebPConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebPConstants.Bps)] + (v >> 3)); dst[x + (y * WebPConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebPConstants.Bps)] + (v >> 3));
} }

48
src/ImageSharp/Formats/WebP/Lossy/Vp8Residual.cs

@ -71,34 +71,36 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
{ {
this.RecordStats(0, s, 1); this.RecordStats(0, s, 1);
s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[0]; s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[0];
this.RecordStats(1, s, 1); }
if (this.RecordStats((v + 1) > 2u ? 1 : 0, s, 2) == 0)
this.RecordStats(1, s, 1);
var bit = 2u < (uint)(v + 1);
if (this.RecordStats(bit ? 1 : 0, s, 2) == 0)
{
// v = -1 or 1
s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[1];
}
else
{
v = Math.Abs(v);
if (v > MaxVariableLevel)
{ {
// v = -1 or 1 v = MaxVariableLevel;
s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[1];
} }
else
{
v = Math.Abs(v);
if (v > MaxVariableLevel)
{
v = MaxVariableLevel;
}
int bits = WebPLookupTables.Vp8LevelCodes[v - 1][1]; int bits = WebPLookupTables.Vp8LevelCodes[v - 1][1];
int pattern = WebPLookupTables.Vp8LevelCodes[v - 1][0]; int pattern = WebPLookupTables.Vp8LevelCodes[v - 1][0];
int i; int i;
for (i = 0; (pattern >>= 1) != 0; ++i) for (i = 0; (pattern >>= 1) != 0; ++i)
{
int mask = 2 << i;
if ((pattern & 1) != 0)
{ {
int mask = 2 << i; this.RecordStats((bits & mask) != 0 ? 1 : 0, s, 3 + i);
if ((pattern & 1) != 0)
{
this.RecordStats(bits & mask, s, 3 + i);
}
} }
s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[2];
} }
s = this.Stats[WebPConstants.Vp8EncBands[n]].Stats[2];
} }
} }
@ -119,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
statsArr.Stats[idx] = ((statsArr.Stats[idx] + 1u) >> 1) & 0x7fff7fffu; // -> divide the stats by 2. statsArr.Stats[idx] = ((statsArr.Stats[idx] + 1u) >> 1) & 0x7fff7fffu; // -> divide the stats by 2.
} }
// record bit count (lower 16 bits) and increment total count (upper 16 bits). // Record bit count (lower 16 bits) and increment total count (upper 16 bits).
statsArr.Stats[idx] += 0x00010000u + (uint)bit; statsArr.Stats[idx] += 0x00010000u + (uint)bit;
return bit; return bit;

Loading…
Cancel
Save