Browse Source

Added Png filter tests

pull/1630/head
TechPizza 5 years ago
parent
commit
29250ffbec
  1. 270
      tests/ImageSharp.Tests/Formats/Png/PngFilterTests.cs
  2. 229
      tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs

270
tests/ImageSharp.Tests/Formats/Png/PngFilterTests.cs

@ -0,0 +1,270 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Uncomment this to turn unit tests into benchmarks:
// #define BENCHMARKING
using System;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Tests.Formats.Png.Utils;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.Formats.Png
{
[Trait("Format", "Png")]
public partial class PngFilterTests : MeasureFixture
{
#if BENCHMARKING
public const int Times = 1000000;
#else
public const int Times = 1;
#endif
public PngFilterTests(ITestOutputHelper output)
: base(output)
{
}
public const int Size = 64;
[Fact]
public void Average()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Average, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.DisableSIMD);
}
[Fact]
public void AverageSse2()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Average, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSSE3);
}
[Fact]
public void AverageSsse3()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Average, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
}
[Fact]
public void AverageAvx2()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Average, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll);
}
[Fact]
public void Paeth()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Paeth, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.DisableSIMD);
}
[Fact]
public void PaethSimd()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Paeth, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll);
}
[Fact]
public void Up()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Up, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.DisableSIMD);
}
[Fact]
public void UpSimd()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Up, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll);
}
[Fact]
public void Sub()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Sub, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.DisableSIMD);
}
[Fact]
public void SubSimd()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Sub, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll);
}
public class TestData
{
private readonly PngFilterMethod filter;
private readonly int bpp;
private readonly byte[] previousScanline;
private readonly byte[] scanline;
private readonly byte[] expectedResult;
private readonly int expectedSum;
private readonly byte[] resultBuffer;
public TestData(PngFilterMethod filter, int size, int bpp = 4)
{
this.filter = filter;
this.bpp = bpp;
this.previousScanline = new byte[size * size * bpp];
this.scanline = new byte[size * size * bpp];
this.expectedResult = new byte[1 + (size * size * bpp)];
this.resultBuffer = new byte[1 + (size * size * bpp)];
var rng = new Random(12345678);
byte[] tmp = new byte[6];
for (int i = 0; i < this.previousScanline.Length; i += bpp)
{
rng.NextBytes(tmp);
this.previousScanline[i + 0] = tmp[0];
this.previousScanline[i + 1] = tmp[1];
this.previousScanline[i + 2] = tmp[2];
this.previousScanline[i + 3] = 255;
this.scanline[i + 0] = tmp[3];
this.scanline[i + 1] = tmp[4];
this.scanline[i + 2] = tmp[5];
this.scanline[i + 3] = 255;
}
switch (this.filter)
{
case PngFilterMethod.Sub:
ReferenceImplementations.EncodeSubFilter(
this.scanline, this.expectedResult, this.bpp, out this.expectedSum);
break;
case PngFilterMethod.Up:
ReferenceImplementations.EncodeUpFilter(
this.previousScanline, this.scanline, this.expectedResult, out this.expectedSum);
break;
case PngFilterMethod.Average:
ReferenceImplementations.EncodeAverageFilter(
this.previousScanline, this.scanline, this.expectedResult, this.bpp, out this.expectedSum);
break;
case PngFilterMethod.Paeth:
ReferenceImplementations.EncodePaethFilter(
this.previousScanline, this.scanline, this.expectedResult, this.bpp, out this.expectedSum);
break;
case PngFilterMethod.None:
case PngFilterMethod.Adaptive:
default:
throw new InvalidOperationException();
}
}
public void TestFilter()
{
int sum;
switch (this.filter)
{
case PngFilterMethod.Sub:
SubFilter.Encode(this.scanline, this.resultBuffer, this.bpp, out sum);
break;
case PngFilterMethod.Up:
UpFilter.Encode(this.previousScanline, this.scanline, this.resultBuffer, out sum);
break;
case PngFilterMethod.Average:
AverageFilter.Encode(this.previousScanline, this.scanline, this.resultBuffer, this.bpp, out sum);
break;
case PngFilterMethod.Paeth:
PaethFilter.Encode(this.previousScanline, this.scanline, this.resultBuffer, this.bpp, out sum);
break;
case PngFilterMethod.None:
case PngFilterMethod.Adaptive:
default:
throw new InvalidOperationException();
}
Assert.Equal(this.expectedSum, sum);
Assert.Equal(this.expectedResult, this.resultBuffer);
}
}
}
}

229
tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs

@ -0,0 +1,229 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Png.Utils
{
/// <summary>
/// This class contains reference implementations to produce verification data for unit tests
/// </summary>
internal static partial class ReferenceImplementations
{
/// <summary>
/// Encodes the scanline
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EncodePaethFilter(Span<byte> scanline, Span<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result);
sum = 0;
// Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp))
resultBaseRef = 4;
int x = 0;
for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref prevBaseRef, x);
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - PaethPredictor(0, above, 0));
sum += Numerics.Abs(unchecked((sbyte)res));
}
for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte left = Unsafe.Add(ref scanBaseRef, xLeft);
byte above = Unsafe.Add(ref prevBaseRef, x);
byte upperLeft = Unsafe.Add(ref prevBaseRef, xLeft);
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - PaethPredictor(left, above, upperLeft));
sum += Numerics.Abs(unchecked((sbyte)res));
}
sum -= 4;
}
/// <summary>
/// Encodes the scanline
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EncodeSubFilter(Span<byte> scanline, Span<byte> result, int bytesPerPixel, out int sum)
{
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result);
sum = 0;
// Sub(x) = Raw(x) - Raw(x-bpp)
resultBaseRef = 1;
int x = 0;
for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = scan;
sum += Numerics.Abs(unchecked((sbyte)res));
}
for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte prev = Unsafe.Add(ref scanBaseRef, xLeft);
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - prev);
sum += Numerics.Abs(unchecked((sbyte)res));
}
sum -= 1;
}
/// <summary>
/// Encodes the scanline
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EncodeUpFilter(Span<byte> scanline, Span<byte> previousScanline, Span<byte> result, out int sum)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result);
sum = 0;
// Up(x) = Raw(x) - Prior(x)
resultBaseRef = 2;
int x = 0;
for (; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref prevBaseRef, x);
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - above);
sum += Numerics.Abs(unchecked((sbyte)res));
}
sum -= 2;
}
/// <summary>
/// Encodes the scanline
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EncodeAverageFilter(Span<byte> scanline, Span<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result);
sum = 0;
// Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2)
resultBaseRef = 3;
int x = 0;
for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref prevBaseRef, x);
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - (above >> 1));
sum += Numerics.Abs(unchecked((sbyte)res));
}
for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte left = Unsafe.Add(ref scanBaseRef, xLeft);
byte above = Unsafe.Add(ref prevBaseRef, x);
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - Average(left, above));
sum += Numerics.Abs(unchecked((sbyte)res));
}
sum -= 3;
}
/// <summary>
/// Calculates the average value of two bytes
/// </summary>
/// <param name="left">The left byte</param>
/// <param name="above">The above byte</param>
/// <returns>The <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Average(byte left, byte above) => (left + above) >> 1;
/// <summary>
/// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses
/// as predictor the neighboring pixel closest to the computed value.
/// </summary>
/// <param name="left">The left neighbor pixel.</param>
/// <param name="above">The above neighbor pixel.</param>
/// <param name="upperLeft">The upper left neighbor pixel.</param>
/// <returns>
/// The <see cref="byte"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte PaethPredictor(byte left, byte above, byte upperLeft)
{
int p = left + above - upperLeft;
int pa = Numerics.Abs(p - left);
int pb = Numerics.Abs(p - above);
int pc = Numerics.Abs(p - upperLeft);
if (pa <= pb && pa <= pc)
{
return left;
}
if (pb <= pc)
{
return above;
}
return upperLeft;
}
}
}
Loading…
Cancel
Save