mirror of https://github.com/SixLabors/ImageSharp
2 changed files with 499 additions and 0 deletions
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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…
Reference in new issue