Browse Source

Merge pull request #2432 from SixLabors/bp/fixCalcResidualCosts

Fix Vp8Residual costs calculation
pull/2440/head
James Jackson-South 3 years ago
committed by GitHub
parent
commit
6ab4b10e5e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs
  2. 235
      tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs
  3. 3
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  4. BIN
      tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.bin
  5. 1
      tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.json

24
src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs

@ -156,7 +156,7 @@ internal class Vp8Residual
return LossyUtils.Vp8BitCost(0, (byte)p0);
}
if (Avx2.IsSupported)
if (Sse2.IsSupported)
{
Span<byte> scratch = stackalloc byte[32];
Span<byte> ctxs = scratch.Slice(0, 16);
@ -165,19 +165,23 @@ internal class Vp8Residual
// Precompute clamped levels and contexts, packed to 8b.
ref short outputRef = ref MemoryMarshal.GetReference<short>(this.Coeffs);
Vector256<short> c0 = Unsafe.As<short, Vector256<byte>>(ref outputRef).AsInt16();
Vector256<short> d0 = Avx2.Subtract(Vector256<short>.Zero, c0);
Vector256<short> e0 = Avx2.Max(c0, d0); // abs(v), 16b
Vector256<sbyte> f = Avx2.PackSignedSaturate(e0, e0);
Vector256<byte> g = Avx2.Min(f.AsByte(), Vector256.Create((byte)2));
Vector256<byte> h = Avx2.Min(f.AsByte(), Vector256.Create((byte)67)); // clampLevel in [0..67]
Vector128<short> c0 = Unsafe.As<short, Vector128<byte>>(ref outputRef).AsInt16();
Vector128<short> c1 = Unsafe.As<short, Vector128<byte>>(ref Unsafe.Add(ref outputRef, 8)).AsInt16();
Vector128<short> d0 = Sse2.Subtract(Vector128<short>.Zero, c0);
Vector128<short> d1 = Sse2.Subtract(Vector128<short>.Zero, c1);
Vector128<short> e0 = Sse2.Max(c0, d0); // abs(v), 16b
Vector128<short> e1 = Sse2.Max(c1, d1);
Vector128<sbyte> f = Sse2.PackSignedSaturate(e0, e1);
Vector128<byte> g = Sse2.Min(f.AsByte(), Vector128.Create((byte)2)); // context = 0, 1, 2
Vector128<byte> h = Sse2.Min(f.AsByte(), Vector128.Create((byte)67)); // clampLevel in [0..67]
ref byte ctxsRef = ref MemoryMarshal.GetReference(ctxs);
ref byte levelsRef = ref MemoryMarshal.GetReference(levels);
ref ushort absLevelsRef = ref MemoryMarshal.GetReference(absLevels);
Unsafe.As<byte, Vector128<byte>>(ref ctxsRef) = g.GetLower();
Unsafe.As<byte, Vector128<byte>>(ref levelsRef) = h.GetLower();
Unsafe.As<ushort, Vector256<ushort>>(ref absLevelsRef) = e0.AsUInt16();
Unsafe.As<byte, Vector128<byte>>(ref ctxsRef) = g;
Unsafe.As<byte, Vector128<byte>>(ref levelsRef) = h;
Unsafe.As<ushort, Vector128<ushort>>(ref absLevelsRef) = e0.AsUInt16();
Unsafe.As<ushort, Vector128<ushort>>(ref Unsafe.Add(ref absLevelsRef, 8)) = e1.AsUInt16();
int level;
int flevel;

235
tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Text;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Tests.TestUtilities;
@ -9,10 +11,234 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp;
[Trait("Format", "Webp")]
public class Vp8ResidualTests
{
private static void WriteVp8Residual(string filename, Vp8Residual residual)
{
using FileStream stream = File.Open(filename, FileMode.Create);
using BinaryWriter writer = new(stream, Encoding.UTF8, false);
writer.Write(residual.First);
writer.Write(residual.Last);
writer.Write(residual.CoeffType);
for (int i = 0; i < residual.Coeffs.Length; i++)
{
writer.Write(residual.Coeffs[i]);
}
for (int i = 0; i < residual.Prob.Length; i++)
{
for (int j = 0; j < residual.Prob[i].Probabilities.Length; j++)
{
writer.Write(residual.Prob[i].Probabilities[j].Probabilities);
}
}
for (int i = 0; i < residual.Costs.Length; i++)
{
Vp8Costs costs = residual.Costs[i];
Vp8CostArray[] costsArray = costs.Costs;
for (int j = 0; j < costsArray.Length; j++)
{
for (int k = 0; k < costsArray[j].Costs.Length; k++)
{
writer.Write(costsArray[j].Costs[k]);
}
}
}
for (int i = 0; i < residual.Stats.Length; i++)
{
for (int j = 0; j < residual.Stats[i].Stats.Length; j++)
{
for (int k = 0; k < residual.Stats[i].Stats[j].Stats.Length; k++)
{
writer.Write(residual.Stats[i].Stats[j].Stats[k]);
}
}
}
writer.Flush();
}
private static Vp8Residual ReadVp8Residual(string fileName)
{
using FileStream stream = File.Open(fileName, FileMode.Open);
using BinaryReader reader = new(stream, Encoding.UTF8, false);
Vp8Residual residual = new()
{
First = reader.ReadInt32(),
Last = reader.ReadInt32(),
CoeffType = reader.ReadInt32()
};
for (int i = 0; i < residual.Coeffs.Length; i++)
{
residual.Coeffs[i] = reader.ReadInt16();
}
Vp8BandProbas[] bandProbas = new Vp8BandProbas[8];
for (int i = 0; i < bandProbas.Length; i++)
{
bandProbas[i] = new Vp8BandProbas();
for (int j = 0; j < bandProbas[i].Probabilities.Length; j++)
{
for (int k = 0; k < 11; k++)
{
bandProbas[i].Probabilities[j].Probabilities[k] = reader.ReadByte();
}
}
}
residual.Prob = bandProbas;
residual.Costs = new Vp8Costs[16];
for (int i = 0; i < residual.Costs.Length; i++)
{
residual.Costs[i] = new Vp8Costs();
Vp8CostArray[] costsArray = residual.Costs[i].Costs;
for (int j = 0; j < costsArray.Length; j++)
{
for (int k = 0; k < costsArray[j].Costs.Length; k++)
{
costsArray[j].Costs[k] = reader.ReadUInt16();
}
}
}
residual.Stats = new Vp8Stats[8];
for (int i = 0; i < residual.Stats.Length; i++)
{
residual.Stats[i] = new Vp8Stats();
for (int j = 0; j < residual.Stats[i].Stats.Length; j++)
{
for (int k = 0; k < residual.Stats[i].Stats[j].Stats.Length; k++)
{
residual.Stats[i].Stats[j].Stats[k] = reader.ReadUInt32();
}
}
}
return residual;
}
[Fact]
public void Vp8Residual_Serialization_Works()
{
// arrange
Vp8Residual expected = new();
Vp8EncProba encProb = new();
Random rand = new(439);
CreateRandomProbas(encProb, rand);
CreateCosts(encProb, rand);
expected.Init(1, 0, encProb);
for (int i = 0; i < expected.Coeffs.Length; i++)
{
expected.Coeffs[i] = (byte)rand.Next(255);
}
// act
string fileName = "Vp8SerializationTest.bin";
WriteVp8Residual(fileName, expected);
Vp8Residual actual = ReadVp8Residual(fileName);
File.Delete(fileName);
// assert
Assert.Equal(expected.CoeffType, actual.CoeffType);
Assert.Equal(expected.Coeffs, actual.Coeffs);
Assert.Equal(expected.Costs.Length, actual.Costs.Length);
Assert.Equal(expected.First, actual.First);
Assert.Equal(expected.Last, actual.Last);
Assert.Equal(expected.Stats.Length, actual.Stats.Length);
for (int i = 0; i < expected.Stats.Length; i++)
{
Vp8StatsArray[] expectedStats = expected.Stats[i].Stats;
Vp8StatsArray[] actualStats = actual.Stats[i].Stats;
Assert.Equal(expectedStats.Length, actualStats.Length);
for (int j = 0; j < expectedStats.Length; j++)
{
Assert.Equal(expectedStats[j].Stats, actualStats[j].Stats);
}
}
Assert.Equal(expected.Prob.Length, actual.Prob.Length);
for (int i = 0; i < expected.Prob.Length; i++)
{
Vp8ProbaArray[] expectedProbabilities = expected.Prob[i].Probabilities;
Vp8ProbaArray[] actualProbabilities = actual.Prob[i].Probabilities;
Assert.Equal(expectedProbabilities.Length, actualProbabilities.Length);
for (int j = 0; j < expectedProbabilities.Length; j++)
{
Assert.Equal(expectedProbabilities[j].Probabilities, actualProbabilities[j].Probabilities);
}
}
for (int i = 0; i < expected.Costs.Length; i++)
{
Vp8CostArray[] expectedCosts = expected.Costs[i].Costs;
Vp8CostArray[] actualCosts = actual.Costs[i].Costs;
Assert.Equal(expectedCosts.Length, actualCosts.Length);
for (int j = 0; j < expectedCosts.Length; j++)
{
Assert.Equal(expectedCosts[j].Costs, actualCosts[j].Costs);
}
}
}
[Fact]
public void GetResidualCost_Works()
{
// arrange
int ctx0 = 0;
int expected = 20911;
Vp8Residual residual = ReadVp8Residual(Path.Combine("TestDataWebp", "Vp8Residual.bin"));
// act
int actual = residual.GetResidualCost(ctx0);
// assert
Assert.Equal(expected, actual);
}
private static void CreateRandomProbas(Vp8EncProba probas, Random rand)
{
for (int t = 0; t < WebpConstants.NumTypes; ++t)
{
for (int b = 0; b < WebpConstants.NumBands; ++b)
{
for (int c = 0; c < WebpConstants.NumCtx; ++c)
{
for (int p = 0; p < WebpConstants.NumProbas; ++p)
{
probas.Coeffs[t][b].Probabilities[c].Probabilities[p] = (byte)rand.Next(255);
}
}
}
}
}
private static void CreateCosts(Vp8EncProba probas, Random rand)
{
for (int i = 0; i < probas.RemappedCosts.Length; i++)
{
for (int j = 0; j < probas.RemappedCosts[i].Length; j++)
{
for (int k = 0; k < probas.RemappedCosts[i][j].Costs.Length; k++)
{
ushort[] costs = probas.RemappedCosts[i][j].Costs[k].Costs;
for (int m = 0; m < costs.Length; m++)
{
costs[m] = (byte)rand.Next(255);
}
}
}
}
}
private static void RunSetCoeffsTest()
{
// arrange
var residual = new Vp8Residual();
Vp8Residual residual = new();
short[] coeffs = { 110, 0, -2, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0 };
// act
@ -23,11 +249,8 @@ public class Vp8ResidualTests
}
[Fact]
public void RunSetCoeffsTest_Works() => RunSetCoeffsTest();
[Fact]
public void RunSetCoeffsTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.AllowAll);
public void SetCoeffsTest_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.AllowAll);
[Fact]
public void RunSetCoeffsTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.DisableHWIntrinsic);
public void SetCoeffsTest_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.DisableSSE2);
}

3
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -56,6 +56,9 @@
<None Update="TestFonts\SixLaborsSampleAB.woff">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestDataWebp\Vp8Residual.bin">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

BIN
tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.bin

Binary file not shown.

1
tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.json

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save