Browse Source

CorrectDCValues

pull/1552/head
Brian Popow 6 years ago
parent
commit
dd7032c693
  1. 2
      src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs
  2. 28
      src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
  3. 84
      src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
  4. 3
      tests/ImageSharp.Tests/TestImages.cs
  5. 3
      tests/Images/Input/WebP/peak.png

2
src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs

@ -564,7 +564,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter
// Writes the partition #0 modes (that is: all intra modes) // Writes the partition #0 modes (that is: all intra modes)
private void CodeIntraModes(Vp8BitWriter bitWriter) private void CodeIntraModes(Vp8BitWriter bitWriter)
{ {
var it = new Vp8EncIterator(this.enc.YTop, this.enc.UvTop, this.enc.Nz, this.enc.MbInfo, this.enc.Preds, this.enc.Mbw, this.enc.Mbh); var it = new Vp8EncIterator(this.enc.YTop, this.enc.UvTop, this.enc.Nz, this.enc.MbInfo, this.enc.Preds, this.enc.TopDerr, this.enc.Mbw, this.enc.Mbh);
int predsWidth = this.enc.PredsWidth; int predsWidth = this.enc.PredsWidth;
do do

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

@ -97,19 +97,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
private int uvTopIdx; private int uvTopIdx;
public Vp8EncIterator(byte[] yTop, byte[] uvTop, uint[] nz, Vp8MacroBlockInfo[] mb, byte[] preds, int mbw, int mbh) public Vp8EncIterator(byte[] yTop, byte[] uvTop, uint[] nz, Vp8MacroBlockInfo[] mb, byte[] preds, sbyte[] topDerr, int mbw, int mbh)
{ {
this.YTop = yTop;
this.UvTop = uvTop;
this.Nz = nz;
this.Mb = mb;
this.Preds = preds;
this.TopDerr = topDerr;
this.LeftDerr = new sbyte[2 * 2];
this.mbw = mbw; this.mbw = mbw;
this.mbh = mbh; this.mbh = mbh;
this.Mb = mb;
this.currentMbIdx = 0; this.currentMbIdx = 0;
this.nzIdx = 1; this.nzIdx = 1;
this.yTopIdx = 0; this.yTopIdx = 0;
this.uvTopIdx = 0; this.uvTopIdx = 0;
this.YTop = yTop;
this.UvTop = uvTop;
this.Nz = nz;
this.Preds = preds;
this.predsWidth = (4 * mbw) + 1; this.predsWidth = (4 * mbw) + 1;
this.predIdx = this.predsWidth; this.predIdx = this.predsWidth;
this.YuvIn = new byte[WebPConstants.Bps * 16]; this.YuvIn = new byte[WebPConstants.Bps * 16];
@ -180,6 +182,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
/// </summary> /// </summary>
public byte[] UvLeft { get; } public byte[] UvLeft { get; }
/// <summary>
/// Gets the left error diffusion (u/v).
/// </summary>
public sbyte[] LeftDerr { get; }
/// <summary> /// <summary>
/// Gets the top luma samples at position 'X'. /// Gets the top luma samples at position 'X'.
/// </summary> /// </summary>
@ -211,6 +218,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
/// </summary> /// </summary>
public uint[] Nz { get; } public uint[] Nz { get; }
/// <summary>
/// Gets the diffusion error.
/// </summary>
public sbyte[] TopDerr { get; }
/// <summary> /// <summary>
/// Gets 32+5 boundary samples needed by intra4x4. /// Gets 32+5 boundary samples needed by intra4x4.
/// </summary> /// </summary>
@ -1284,6 +1296,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
vLeft.Slice(1, 8).Fill(129); vLeft.Slice(1, 8).Fill(129);
this.LeftNz[8] = 0; this.LeftNz[8] = 0;
this.LeftDerr.AsSpan().Fill(0);
} }
private void InitTop() private void InitTop()
@ -1297,6 +1311,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
int predsH = (4 * this.mbh) + 1; int predsH = (4 * this.mbh) + 1;
int predsSize = predsW * predsH; int predsSize = predsW * predsH;
this.Preds.AsSpan(predsSize + this.predsWidth, this.mbw).Fill(0); this.Preds.AsSpan(predsSize + this.predsWidth, this.mbw).Fill(0);
this.TopDerr.AsSpan().Fill(0);
} }
private int Bit(uint nz, int n) private int Bit(uint nz, int n)

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

@ -4,7 +4,6 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.WebP.BitWriter; using SixLabors.ImageSharp.Formats.WebP.BitWriter;
@ -142,6 +141,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
// TODO: filterStrength is hardcoded, should be configurable. // TODO: filterStrength is hardcoded, should be configurable.
private const int FilterStrength = 60; private const int FilterStrength = 60;
// Diffusion weights. We under-correct a bit (15/16th of the error is actually
// diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0.
private const int C1 = 7; // fraction of error sent to the 4x4 block below
private const int C2 = 8; // fraction of error sent to the 4x4 block on the right
private const int DSHIFT = 4;
private const int DSCALE = 1; // storage descaling, needed to make the error fit byte
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Vp8Encoder"/> class. /// Initializes a new instance of the <see cref="Vp8Encoder"/> class.
/// </summary> /// </summary>
@ -175,7 +181,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.UvTop = new byte[this.mbw * 16 * 2]; this.UvTop = new byte[this.mbw * 16 * 2];
this.Nz = new 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; this.TopDerr = new sbyte[this.mbw * 4];
// TODO: make partition_limit configurable? // TODO: make partition_limit configurable?
int limit = 100; // original code: limit = 100 - config->partition_limit; int limit = 100; // original code: limit = 100 - config->partition_limit;
@ -196,6 +202,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
} }
this.filterHeader = new Vp8FilterHeader(); this.filterHeader = new Vp8FilterHeader();
int predSize = (((4 * this.mbw) + 1) * ((4 * this.mbh) + 1)) + this.predsWidth + 1;
this.proba = new Vp8EncProba(); this.proba = new Vp8EncProba();
this.Preds = new byte[predSize * 2]; // TODO: figure out how much mem we need here. This is too much. this.Preds = new 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;
@ -340,6 +347,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
/// </summary> /// </summary>
public byte[] Preds { get; } public byte[] Preds { get; }
/// <summary>
/// Gets the diffusion error.
/// </summary>
public sbyte[] TopDerr { get; }
/// <summary> /// <summary>
/// Gets a rough limit for header bits per MB. /// Gets a rough limit for header bits per MB.
/// </summary> /// </summary>
@ -364,7 +376,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
int yStride = width; int yStride = width;
int uvStride = (yStride + 1) >> 1; int uvStride = (yStride + 1) >> 1;
var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.mbw, this.mbh); var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.TopDerr, 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);
@ -487,7 +499,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.proba.FinalizeTokenProbas(); this.proba.FinalizeTokenProbas();
} }
this.proba.CalculateLevelCosts(); // finalize costs this.proba.CalculateLevelCosts(); // Finalize costs.
} }
private long OneStatPass(int width, int height, int yStride, int uvStride, Vp8RdLevel rdOpt, int nbMbs, PassStats stats) private long OneStatPass(int width, int height, int yStride, int uvStride, Vp8RdLevel rdOpt, int nbMbs, PassStats stats)
@ -495,7 +507,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.Nz, this.mbInfo, this.Preds, this.Mbw, this.Mbh); var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.mbInfo, this.Preds, this.TopDerr, this.Mbw, this.Mbh);
long size = 0; long size = 0;
long sizeP0 = 0; long sizeP0 = 0;
long distortion = 0; long distortion = 0;
@ -1277,11 +1289,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
tmp.AsSpan((n + 1) * 16, 16)); tmp.AsSpan((n + 1) * 16, 16));
} }
/* TODO: this.CorrectDCValues(it, dqm.Uv, tmp, rd);
if (it->top_derr_ != NULL)
{
CorrectDCValues(it, &dqm->uv_, tmp, rd);
}*/
for (n = 0; n < 8; n += 2) for (n = 0; n < 8; n += 2)
{ {
@ -1340,6 +1348,62 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
} }
} }
private void CorrectDCValues(Vp8EncIterator it, Vp8Matrix mtx, short[] tmp, Vp8ModeScore rd)
{
#pragma warning disable SA1005 // Single line comments should begin with single space
// | top[0] | top[1]
// --------+--------+---------
// left[0] | tmp[0] tmp[1] <-> err0 err1
// left[1] | tmp[2] tmp[3] err2 err3
//
// Final errors {err1,err2,err3} are preserved and later restored
// as top[]/left[] on the next block.
#pragma warning restore SA1005 // Single line comments should begin with single space
for (int ch = 0; ch <= 1; ++ch)
{
Span<sbyte> top = it.TopDerr.AsSpan((it.X * 4) + ch, 2);
Span<sbyte> left = it.LeftDerr.AsSpan(ch, 2);
int err0, err1, err2, err3;
Span<short> c = tmp.AsSpan(ch * 4 * 16, 4 * 16);
c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE));
err0 = QuantizeSingle(c, mtx);
c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE));
err1 = QuantizeSingle(c.Slice(1 * 16), mtx);
c[2 * 16] += (short)(((C1 * err0) + (C2 * left[1])) >> (DSHIFT - DSCALE));
err2 = QuantizeSingle(c.Slice(2 * 16), mtx);
c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE));
err3 = QuantizeSingle(c.Slice(3 * 16), mtx);
// TODO: set errors in rd
// rd->derr[ch][0] = (int8_t)err1;
// rd->derr[ch][1] = (int8_t)err2;
// rd->derr[ch][2] = (int8_t)err3;
}
}
// Quantize as usual, but also compute and return the quantization error.
// Error is already divided by DSHIFT.
private static int QuantizeSingle(Span<short> v, Vp8Matrix mtx)
{
int v0 = v[0];
bool sign = v0 < 0;
if (sign)
{
v0 = -v0;
}
if (v0 > (int)mtx.ZThresh[0])
{
int qV = QuantDiv((uint)v0, mtx.IQ[0], mtx.Bias[0]) * mtx.Q[0];
int err = v0 - qV;
v[0] = (short)(sign ? -qV : qV);
return (sign ? -err : err) >> DSCALE;
}
v[0] = 0;
return (sign ? -v0 : v0) >> DSCALE;
}
private void FTransformWht(Span<short> input, Span<short> output) private void FTransformWht(Span<short> input, Span<short> output)
{ {
var tmp = new int[16]; var tmp = new int[16];

3
tests/ImageSharp.Tests/TestImages.cs

@ -497,6 +497,9 @@ namespace SixLabors.ImageSharp.Tests
public static class WebP public static class WebP
{ {
// Reference image as png
public const string Peak = "WebP/Peak.png";
public static class Animated public static class Animated
{ {
public const string Animated1 = "WebP/animated-webp.webp"; public const string Animated1 = "WebP/animated-webp.webp";

3
tests/Images/Input/WebP/peak.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b9b56ed5c1278664222c77f9a452b824b4f9215c819502b3f6b0e0d44270e7e7
size 26456
Loading…
Cancel
Save