Browse Source

Add PickBestIntra4

pull/1552/head
Brian Popow 5 years ago
parent
commit
caed173b38
  1. 49
      src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
  2. 154
      src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
  3. 20
      src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs
  4. 8
      src/ImageSharp/Formats/WebP/WebpConstants.cs

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

@ -454,29 +454,32 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return WebpLookupTables.Vp8FixedCostsI4[top, left]; return WebpLookupTables.Vp8FixedCostsI4[top, left];
} }
public void SetIntraUvMode(int mode) public int GetCostLuma4(short[] levels, Vp8EncProba proba)
{ {
this.CurrentMacroBlockInfo.UvMode = mode; int x = this.I4 & 3;
} int y = this.I4 >> 2;
var res = new Vp8Residual();
int R = 0;
int ctx;
public void SetSkip(bool skip) res.Init(0, 3, proba);
{ ctx = this.TopNz[x] + this.LeftNz[y];
this.CurrentMacroBlockInfo.Skip = skip; res.SetCoeffs(levels);
R += res.GetResidualCost(ctx);
return R;
} }
public void SetSegment(int segment) public void SetIntraUvMode(int mode) => this.CurrentMacroBlockInfo.UvMode = mode;
{
this.CurrentMacroBlockInfo.Segment = segment; public void SetSkip(bool skip) => this.CurrentMacroBlockInfo.Skip = skip;
}
public void SetSegment(int segment) => this.CurrentMacroBlockInfo.Segment = segment;
/// <summary> /// <summary>
/// Returns true if iteration is finished. /// Returns true if iteration is finished.
/// </summary> /// </summary>
/// <returns>True if iterator is finished.</returns> /// <returns>True if iterator is finished.</returns>
public bool IsDone() public bool IsDone() => this.CountDown <= 0;
{
return this.CountDown <= 0;
}
/// <summary> /// <summary>
/// Go to next macroblock. /// Go to next macroblock.
@ -588,7 +591,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
else else
{ {
this.Nz[this.nzIdx] &= 1 << 24; // Preserve the dc_nz bit. // Preserve the dc_nz bit.
this.Nz[this.nzIdx] &= 1 << 24;
} }
} }
@ -606,10 +610,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vp8Encoding.EncPredChroma8(this.YuvP, left, top); Vp8Encoding.EncPredChroma8(this.YuvP, left, top);
} }
public void MakeIntra4Preds() public void MakeIntra4Preds() => Vp8Encoding.EncPredLuma4(this.YuvP, this.I4Boundary, this.I4BoundaryIdx);
{
Vp8Encoding.EncPredLuma4(this.YuvP, this.I4Boundary, this.I4BoundaryIdx);
}
public void SwapOut() public void SwapOut()
{ {
@ -802,18 +803,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.TopDerr.AsSpan().Fill(0); this.TopDerr.AsSpan().Fill(0);
} }
private int Bit(uint nz, int n) private int Bit(uint nz, int n) => (nz & (1 << n)) != 0 ? 1 : 0;
{
return (nz & (1 << n)) != 0 ? 1 : 0;
}
/// <summary> /// <summary>
/// Set count down. /// Set count down.
/// </summary> /// </summary>
/// <param name="countDown">Number of iterations to go.</param> /// <param name="countDown">Number of iterations to go.</param>
private void SetCountDown(int countDown) private void SetCountDown(int countDown) => this.CountDown = countDown;
{
this.CountDown = countDown;
}
} }
} }

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

@ -65,6 +65,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private const int NumMbSegments = 4; private const int NumMbSegments = 4;
private const int NumBModes = 10;
/// <summary>
/// The number of prediction modes.
/// </summary>
private const int NumPredModes = 4;
private const int MaxItersKMeans = 6; private const int MaxItersKMeans = 6;
// Convergence is considered reached if dq < DqLimit // Convergence is considered reached if dq < DqLimit
@ -81,11 +88,6 @@ 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;
/// <summary>
/// I16 mode (special case).
/// </summary>
private const int FlatenessLimitI16 = 0;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Vp8Encoder"/> class. /// Initializes a new instance of the <see cref="Vp8Encoder"/> class.
/// </summary> /// </summary>
@ -266,12 +268,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// </summary> /// </summary>
private int MbHeaderLimit { get; } private int MbHeaderLimit { get; }
/// <summary> private readonly ushort[] weightY = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 };
/// The number of prediction modes.
/// </summary>
private const int NumPredModes = 4;
private readonly ushort[] WeightY = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 };
/// <summary> /// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
@ -916,14 +913,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Measure RD-score. // Measure RD-score.
rdCur.D = Vp8Sse16X16(src, tmpDst); rdCur.D = Vp8Sse16X16(src, tmpDst);
rdCur.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto16x16(src, tmpDst, this.WeightY)) : 0; rdCur.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto16x16(src, tmpDst, this.weightY)) : 0;
rdCur.H = WebpConstants.Vp8FixedCostsI16[mode]; rdCur.H = WebpConstants.Vp8FixedCostsI16[mode];
rdCur.R = this.GetCostLuma16(it, rdCur); rdCur.R = this.GetCostLuma16(it, rdCur);
if (isFlat) if (isFlat)
{ {
// Refine the first impression (which was in pixel space). // Refine the first impression (which was in pixel space).
isFlat = IsFlat(rdCur.YAcLevels, numBlocks, FlatenessLimitI16); isFlat = IsFlat(rdCur.YAcLevels, numBlocks, WebpConstants.FlatnessLimitI16);
if (isFlat) if (isFlat)
{ {
// Block is very flat. We put emphasis on the distortion being very low! // Block is very flat. We put emphasis on the distortion being very low!
@ -962,9 +959,116 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
} }
private void PickBestIntra4(Vp8EncIterator it, Vp8ModeScore rd) private bool PickBestIntra4(Vp8EncIterator it, Vp8ModeScore rd)
{ {
Vp8SegmentInfo dqm = this.SegmentInfos[it.CurrentMacroBlockInfo.Segment];
int lambda = dqm.LambdaI16;
int tlambda = dqm.TLambda;
Span<byte> src0 = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc);
Span<byte> bestBlocks = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc);
int totalHeaderBits = 0;
var rdBest = new Vp8ModeScore();
IMemoryOwner<byte> scratchBuffer = this.memoryAllocator.Allocate<byte>(512);
if (this.maxI4HeaderBits == 0)
{
return false;
}
rdBest.InitScore();
rdBest.H = 211; // '211' is the value of VP8BitCost(0, 145)
rdBest.SetRdScore(dqm.LambdaMode);
it.StartI4();
do
{
int numBlocks = 1;
var rdi4 = new Vp8ModeScore();
int mode;
int bestMode = -1;
Span<byte> src = src0.Slice(WebpLookupTables.Vp8Scan[it.I4]);
short[] modeCosts = it.GetCostModeI4(rd.ModesI4);
Span<byte> bestBlock = bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4]);
Span<byte> tmpDst = scratchBuffer.GetSpan();
rdi4.InitScore();
it.MakeIntra4Preds();
for (mode = 0; mode < NumBModes; ++mode)
{
var rdTmp = new Vp8ModeScore();
short[] tmpLevels = new short[16];
// Reconstruct.
rdTmp.Nz = (uint)this.ReconstructIntra4(it, dqm, tmpLevels, src, tmpDst, mode);
// Compute RD-score.
rdTmp.D = Vp8Sse4X4(src, tmpDst);
rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, Vp8Disto4x4(src, tmpDst, this.weightY)) : 0;
rdTmp.H = modeCosts[mode];
// Add flatness penalty, to avoid flat area to be mispredicted by a complex mode.
if (mode > 0 && IsFlat(tmpLevels, numBlocks, WebpConstants.FlatnessLimitI4))
{
rdTmp.R = WebpConstants.FlatnessPenality * numBlocks;
}
else
{
rdTmp.R = 0;
}
// early-out check.
rdTmp.SetRdScore(lambda);
if (bestMode >= 0 && rdTmp.Score >= rdi4.Score)
{
continue;
}
// finish computing score.
rdTmp.R += it.GetCostLuma4(tmpLevels, this.Proba);
rdTmp.SetRdScore(lambda);
if (bestMode < 0 || rdTmp.Score < rdi4.Score)
{
rdi4.CopyScore(rdTmp);
bestMode = mode;
Span<byte> tmp = tmpDst;
tmpDst = bestBlock;
bestBlock = tmp;
tmpLevels.AsSpan(0, rdBest.YAcLevels[it.I4]).CopyTo(rdBest.YAcLevels);
}
}
rdi4.SetRdScore(lambda);
rdBest.AddScore(rdi4);
if (rdBest.Score >= rd.Score)
{
return false;
}
totalHeaderBits += (int)rdi4.H; // <- equal to modeCosts[bestMode];
if (totalHeaderBits > this.maxI4HeaderBits)
{
return false;
}
// Copy selected samples if not in the right place already.
if (bestBlock != bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4]))
{
Vp8Copy4x4(bestBlock, bestBlocks.Slice(WebpLookupTables.Vp8Scan[it.I4]));
}
rd.ModesI4[it.I4] = (byte)bestMode;
it.TopNz[it.I4 & 3] = it.LeftNz[it.I4 >> 2] = rdi4.Nz != 0 ? 1 : 0;
}
while (it.RotateI4(bestBlocks));
// Finalize state.
rd.CopyScore(rdBest);
it.SetIntra4Mode(rd.ModesI4);
it.SwapOut();
rdBest.YAcLevels.AsSpan().CopyTo(rd.YAcLevels);
// Select intra4x4 over intra16x16.
return true;
} }
private void PickBestUv(Vp8EncIterator it, Vp8ModeScore rd) private void PickBestUv(Vp8EncIterator it, Vp8ModeScore rd)
@ -972,6 +1076,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
// TODO: move to Vp8EncIterator
private int GetCostLuma16(Vp8EncIterator it, Vp8ModeScore rd) private int GetCostLuma16(Vp8EncIterator it, Vp8ModeScore rd)
{ {
var res = new Vp8Residual(); var res = new Vp8Residual();
@ -1019,7 +1124,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
long bitLimit = tryBothModes long bitLimit = tryBothModes
? this.MbHeaderLimit ? this.MbHeaderLimit
: Vp8ModeScore.MaxCost; // no early-out allowed. : Vp8ModeScore.MaxCost; // no early-out allowed.
const int numBModes = 10;
if (isI16) if (isI16)
{ {
@ -1072,7 +1176,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
short[] modeCosts = it.GetCostModeI4(rd.ModesI4); short[] modeCosts = it.GetCostModeI4(rd.ModesI4);
it.MakeIntra4Preds(); it.MakeIntra4Preds();
for (mode = 0; mode < numBModes; ++mode) for (mode = 0; mode < NumBModes; ++mode)
{ {
Span<byte> reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); Span<byte> reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]);
long score = (Vp8Sse4X4(src, reference) * WebpConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); long score = (Vp8Sse4X4(src, reference) * WebpConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4);
@ -1447,6 +1551,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return count; return count;
} }
[MethodImpl(InliningOptions.ShortMethod)]
private static void Vp8Copy4x4(Span<byte> src, Span<byte> dst) => Copy(src, dst, 4, 4);
[MethodImpl(InliningOptions.ShortMethod)]
private static void Copy(Span<byte> src, Span<byte> dst, int w, int h)
{
for (int y = 0; y < h; ++y)
{
src.Slice(0, w).CopyTo(dst);
src = src.Slice(WebpConstants.Bps);
dst = dst.Slice(WebpConstants.Bps);
}
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static int Vp8Disto16x16(Span<byte> a, Span<byte> b, Span<ushort> w) private static int Vp8Disto16x16(Span<byte> a, Span<byte> b, Span<ushort> w)
{ {
@ -1456,7 +1574,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{ {
for (x = 0; x < 16; x += 4) for (x = 0; x < 16; x += 4)
{ {
D += Disto4x4(a.Slice(x + y), b.Slice(x + y), w); D += Vp8Disto4x4(a.Slice(x + y), b.Slice(x + y), w);
} }
} }
@ -1464,7 +1582,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static int Disto4x4(Span<byte> a, Span<byte> b, Span<ushort> w) private static int Vp8Disto4x4(Span<byte> a, Span<byte> b, Span<ushort> w)
{ {
int sum1 = LossyUtils.TTransform(a, w); int sum1 = LossyUtils.TTransform(a, w);
int sum2 = LossyUtils.TTransform(b, w); int sum2 = LossyUtils.TTransform(b, w);

20
src/ImageSharp/Formats/WebP/Lossy/Vp8ModeScore.cs

@ -97,6 +97,26 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.Score = MaxCost; this.Score = MaxCost;
} }
public void CopyScore(Vp8ModeScore other)
{
this.D = other.D;
this.SD = other.SD;
this.R = other.R;
this.H = other.H;
this.Nz = other.Nz; // note that nz is not accumulated, but just copied.
this.Score = other.Score;
}
public void AddScore(Vp8ModeScore other)
{
this.D += other.D;
this.SD += other.SD;
this.R += other.R;
this.H += other.H;
this.Nz |= other.Nz; // here, new nz bits are accumulated.
this.Score += other.Score;
}
public void SetRdScore(int lambda) => this.Score = ((this.R + this.H) * lambda) + (RdDistoMult * (this.D + this.SD)); public void SetRdScore(int lambda) => this.Score = ((this.R + this.H) * lambda) + (RdDistoMult * (this.D + this.SD));
} }
} }

8
src/ImageSharp/Formats/WebP/WebpConstants.cs

@ -224,6 +224,14 @@ namespace SixLabors.ImageSharp.Formats.Webp
public const int MaxVariableLevel = 67; public const int MaxVariableLevel = 67;
public const int FlatnessLimitI16 = 0;
public const int FlatnessLimitIUv = 2;
public const int FlatnessLimitI4 = 3;
public const int FlatnessPenality = 140;
// This is the common stride for enc/dec. // This is the common stride for enc/dec.
public const int Bps = 32; public const int Bps = 32;

Loading…
Cancel
Save