Browse Source

Merge branch 'master' into webp

pull/1552/head
Brian Popow 5 years ago
committed by GitHub
parent
commit
b971afec22
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 186
      src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs
  2. 40
      src/ImageSharp/Common/Helpers/Numerics.cs
  3. 4
      src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs
  4. 8
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
  5. 8
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
  6. 31
      tests/ImageSharp.Benchmarks/Processing/Resize.cs
  7. 125
      tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs
  8. 32
      tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

186
src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs

@ -5,6 +5,10 @@ using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.ColorSpaces.Companding
{
@ -18,21 +22,83 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
/// </remarks>
public static class SRgbCompanding
{
private const int Length = Scale + 2; // 256kb @ 16bit precision.
private const int Scale = (1 << 16) - 1;
private static readonly Lazy<float[]> LazyCompressTable = new Lazy<float[]>(
() =>
{
var result = new float[Length];
for (int i = 0; i < result.Length; i++)
{
double d = (double)i / Scale;
if (d <= (0.04045 / 12.92))
{
d *= 12.92;
}
else
{
d = (1.055 * Math.Pow(d, 1.0 / 2.4)) - 0.055;
}
result[i] = (float)d;
}
return result;
},
true);
private static readonly Lazy<float[]> LazyExpandTable = new Lazy<float[]>(
() =>
{
var result = new float[Length];
for (int i = 0; i < result.Length; i++)
{
double d = (double)i / Scale;
if (d <= 0.04045)
{
d /= 12.92;
}
else
{
d = Math.Pow((d + 0.055) / 1.055, 2.4);
}
result[i] = (float)d;
}
return result;
},
true);
private static float[] ExpandTable => LazyExpandTable.Value;
private static float[] CompressTable => LazyCompressTable.Value;
/// <summary>
/// Expands the companded vectors to their linear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Expand(Span<Vector4> vectors)
{
ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors);
ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, vectors.Length);
while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd))
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported && vectors.Length >= 2)
{
Expand(ref vectorsStart);
CompandAvx2(vectors, ExpandTable);
vectorsStart = ref Unsafe.Add(ref vectorsStart, 1);
if (Numerics.Modulo2(vectors.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
Expand(ref MemoryMarshal.GetReference(vectors.Slice(vectors.Length - 1)));
}
}
else
#endif
{
CompandScalar(vectors, ExpandTable);
}
}
@ -40,17 +106,24 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
/// Compresses the uncompanded vectors to their nonlinear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Compress(Span<Vector4> vectors)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void Compress(Span<Vector4> vectors)
{
ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors);
ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, vectors.Length);
while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd))
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported && vectors.Length >= 2)
{
Compress(ref vectorsStart);
CompandAvx2(vectors, CompressTable);
vectorsStart = ref Unsafe.Add(ref vectorsStart, 1);
if (Numerics.Modulo2(vectors.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
Compress(ref MemoryMarshal.GetReference(vectors.Slice(vectors.Length - 1)));
}
}
else
#endif
{
CompandScalar(vectors, CompressTable);
}
}
@ -58,9 +131,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
/// Expands a companded vector to its linear equivalent with respect to the energy.
/// </summary>
/// <param name="vector">The vector.</param>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Expand(ref Vector4 vector)
{
// Alpha is already a linear representation of opacity so we do not want to convert it.
vector.X = Expand(vector.X);
vector.Y = Expand(vector.Y);
vector.Z = Expand(vector.Z);
@ -70,9 +144,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
/// Compresses an uncompanded vector (linear) to its nonlinear equivalent.
/// </summary>
/// <param name="vector">The vector.</param>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Compress(ref Vector4 vector)
{
// Alpha is already a linear representation of opacity so we do not want to convert it.
vector.X = Compress(vector.X);
vector.Y = Compress(vector.Y);
vector.Z = Compress(vector.Z);
@ -83,15 +158,84 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
/// </summary>
/// <param name="channel">The channel value.</param>
/// <returns>The <see cref="float"/> representing the linear channel value.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static float Expand(float channel) => channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Expand(float channel)
=> channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F);
/// <summary>
/// Compresses an uncompanded channel (linear) to its nonlinear equivalent.
/// </summary>
/// <param name="channel">The channel value.</param>
/// <returns>The <see cref="float"/> representing the nonlinear channel value.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static float Compress(float channel) => channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Compress(float channel)
=> channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F;
#if SUPPORTS_RUNTIME_INTRINSICS
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void CompandAvx2(Span<Vector4> vectors, float[] table)
{
fixed (float* tablePointer = &table[0])
{
var scale = Vector256.Create((float)Scale);
Vector256<float> zero = Vector256<float>.Zero;
var offset = Vector256.Create(1);
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> vectorsBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(vectors));
ref Vector256<float> vectorsLast = ref Unsafe.Add(ref vectorsBase, (IntPtr)((uint)vectors.Length / 2u));
while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast))
{
Vector256<float> multiplied = Avx.Multiply(scale, vectorsBase);
multiplied = Avx.Min(Avx.Max(zero, multiplied), scale);
Vector256<int> truncated = Avx.ConvertToVector256Int32WithTruncation(multiplied);
Vector256<float> truncatedF = Avx.ConvertToVector256Single(truncated);
Vector256<float> low = Avx2.GatherVector256(tablePointer, truncated, sizeof(float));
Vector256<float> high = Avx2.GatherVector256(tablePointer, Avx2.Add(truncated, offset), sizeof(float));
// Alpha is already a linear representation of opacity so we do not want to convert it.
Vector256<float> companded = Numerics.Lerp(low, high, Avx.Subtract(multiplied, truncatedF));
vectorsBase = Avx.Blend(companded, vectorsBase, Numerics.BlendAlphaControl);
vectorsBase = ref Unsafe.Add(ref vectorsBase, 1);
}
}
}
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void CompandScalar(Span<Vector4> vectors, float[] table)
{
fixed (float* tablePointer = &table[0])
{
Vector4 zero = Vector4.Zero;
var scale = new Vector4(Scale);
ref Vector4 vectorsBase = ref MemoryMarshal.GetReference(vectors);
ref Vector4 vectorsLast = ref Unsafe.Add(ref vectorsBase, vectors.Length);
while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast))
{
Vector4 multiplied = Numerics.Clamp(vectorsBase * Scale, zero, scale);
float f0 = multiplied.X;
float f1 = multiplied.Y;
float f2 = multiplied.Z;
uint i0 = (uint)f0;
uint i1 = (uint)f1;
uint i2 = (uint)f2;
// Alpha is already a linear representation of opacity so we do not want to convert it.
vectorsBase.X = Numerics.Lerp(tablePointer[i0], tablePointer[i0 + 1], f0 - (int)i0);
vectorsBase.Y = Numerics.Lerp(tablePointer[i1], tablePointer[i1 + 1], f1 - (int)i1);
vectorsBase.Z = Numerics.Lerp(tablePointer[i2], tablePointer[i2 + 1], f2 - (int)i2);
vectorsBase = ref Unsafe.Add(ref vectorsBase, 1);
}
}
}
}
}

40
src/ImageSharp/Common/Helpers/Numerics.cs

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp
internal static class Numerics
{
#if SUPPORTS_RUNTIME_INTRINSICS
private const int BlendAlphaControl = 0b_10_00_10_00;
public const int BlendAlphaControl = 0b_10_00_10_00;
private const int ShuffleAlphaControl = 0b_11_11_11_11;
#endif
@ -710,5 +710,43 @@ namespace SixLabors.ImageSharp
}
}
}
#if SUPPORTS_RUNTIME_INTRINSICS
/// <summary>
/// Performs a linear interpolation between two values based on the given weighting.
/// </summary>
/// <param name="value1">The first value.</param>
/// <param name="value2">The second value.</param>
/// <param name="amount">Values between 0 and 1 that indicates the weight of <paramref name="value2"/>.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Lerp(
in Vector256<float> value1,
in Vector256<float> value2,
in Vector256<float> amount)
{
Vector256<float> diff = Avx.Subtract(value2, value1);
if (Fma.IsSupported)
{
return Fma.MultiplyAdd(diff, amount, value1);
}
else
{
return Avx.Add(Avx.Multiply(diff, amount), value1);
}
}
#endif
/// <summary>
/// Performs a linear interpolation between two values based on the given weighting.
/// </summary>
/// <param name="value1">The first value.</param>
/// <param name="value2">The second value.</param>
/// <param name="amount">A value between 0 and 1 that indicates the weight of <paramref name="value2"/>.</param>
/// <returns>The <see cref="float"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Lerp(float value1, float value2, float amount)
=> ((value2 - value1) * amount) + value1;
}
}

4
src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -45,4 +45,4 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils
}
}
}
}
}

8
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs

@ -61,9 +61,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <returns>The weighted sum</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public Vector4 Convolve(Span<Vector4> rowSpan)
{
return this.ConvolveCore(ref rowSpan[this.StartIndex]);
}
=> this.ConvolveCore(ref rowSpan[this.StartIndex]);
[MethodImpl(InliningOptions.ShortMethod)]
public Vector4 ConvolveCore(ref Vector4 rowStartRef)
@ -91,9 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal ResizeKernel AlterLeftValue(int left)
{
return new ResizeKernel(left, this.bufferPtr, this.Length);
}
=> new ResizeKernel(left, this.bufferPtr, this.Length);
internal void Fill(Span<double> values)
{

8
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs

@ -105,14 +105,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
[MethodImpl(InliningOptions.ShortMethod)]
public Span<Vector4> GetColumnSpan(int x, int startY)
{
return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min);
}
=> this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min);
public void Initialize()
{
this.CalculateFirstPassValues(this.currentWindow);
}
=> this.CalculateFirstPassValues(this.currentWindow);
public void FillDestinationPixels(RowInterval rowInterval, Buffer2D<TPixel> destination)
{

31
tests/ImageSharp.Benchmarks/Processing/Resize.cs

@ -4,10 +4,13 @@
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks
{
@ -15,32 +18,35 @@ namespace SixLabors.ImageSharp.Benchmarks
public abstract class Resize<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private byte[] bytes = null;
private Image<TPixel> sourceImage;
private Bitmap sourceBitmap;
private SDImage sourceBitmap;
protected Configuration Configuration { get; } = new Configuration(new JpegConfigurationModule());
[Params("3032-400")]
public virtual string SourceToDest { get; set; }
protected int SourceSize { get; private set; }
protected int DestSize { get; private set; }
[GlobalSetup]
public virtual void Setup()
{
string[] stuff = this.SourceToDest.Split('-');
this.SourceSize = int.Parse(stuff[0], CultureInfo.InvariantCulture);
this.DestSize = int.Parse(stuff[1], CultureInfo.InvariantCulture);
this.sourceImage = new Image<TPixel>(this.Configuration, this.SourceSize, this.SourceSize);
this.sourceBitmap = new Bitmap(this.SourceSize, this.SourceSize);
if (this.bytes is null)
{
this.bytes = File.ReadAllBytes(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Snake));
this.sourceImage = Image.Load<TPixel>(this.bytes);
var ms1 = new MemoryStream(this.bytes);
this.sourceBitmap = SDImage.FromStream(ms1);
this.DestSize = this.sourceBitmap.Width / 2;
}
}
[GlobalCleanup]
public void Cleanup()
{
this.bytes = null;
this.sourceImage.Dispose();
this.sourceBitmap.Dispose();
}
@ -127,9 +133,6 @@ namespace SixLabors.ImageSharp.Benchmarks
[Params(128, 512, 1024, 8 * 1024)]
public int WorkingBufferSizeHintInKilobytes { get; set; }
[Params("3032-400", "4000-300")]
public override string SourceToDest { get; set; }
public override void Setup()
{
this.Configuration.WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInKilobytes * 1024;

125
tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs

@ -160,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
(s, d) =>
{
Span<TPixel> destPixels = d.GetSpan();
this.Operations.FromVector4Destructive(this.Configuration, (Span<Vector4>)s, destPixels, PixelConversionModifiers.Scale);
this.Operations.FromVector4Destructive(this.Configuration, s, destPixels, PixelConversionModifiers.Scale);
});
}
@ -168,15 +168,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
[MemberData(nameof(ArraySizesData))]
public void FromCompandedScaledVector4(int count)
{
void SourceAction(ref Vector4 v)
{
SRgbCompanding.Expand(ref v);
}
void SourceAction(ref Vector4 v) => SRgbCompanding.Expand(ref v);
void ExpectedAction(ref Vector4 v)
{
SRgbCompanding.Compress(ref v);
}
void ExpectedAction(ref Vector4 v) => SRgbCompanding.Compress(ref v);
Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v));
TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v));
@ -188,7 +182,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
this.Configuration,
s,
d.GetSpan(),
PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale));
PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale),
false);
}
[Theory]
@ -219,7 +214,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
expected,
(s, d) =>
{
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None;
this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers);
@ -254,7 +250,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
expected,
(s, d) =>
{
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None;
this.Operations.FromVector4Destructive(
@ -297,7 +294,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
expected,
(s, d) =>
{
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None;
this.Operations.FromVector4Destructive(
@ -305,7 +303,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
s,
d.GetSpan(),
modifiers | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale);
});
},
false);
}
[Theory]
@ -343,7 +342,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
PixelConverterTests.ReferenceImplementations.To<TPixel, TDestPixel>(this.Configuration, source, expected);
TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, (ReadOnlySpan<TPixel>)s, d.GetSpan()));
TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan()));
}
[Theory]
@ -356,11 +355,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
TestOperation(
source,
expected,
(s, d) =>
{
Span<Vector4> destVectors = d.GetSpan();
this.Operations.ToVector4(this.Configuration, (ReadOnlySpan<TPixel>)s, destVectors, PixelConversionModifiers.Scale);
});
(s, d) => this.Operations.ToVector4(
this.Configuration,
s,
d.GetSpan(),
PixelConversionModifiers.Scale));
}
[Theory]
@ -369,13 +368,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
void SourceAction(ref Vector4 v)
{
SRgbCompanding.Compress(ref v);
}
void ExpectedAction(ref Vector4 v)
{
SRgbCompanding.Expand(ref v);
}
void ExpectedAction(ref Vector4 v) => SRgbCompanding.Expand(ref v);
TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v));
Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v));
@ -396,13 +391,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
void SourceAction(ref Vector4 v)
{
Numerics.UnPremultiply(ref v);
}
void ExpectedAction(ref Vector4 v)
{
Numerics.Premultiply(ref v);
}
void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v);
TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => SourceAction(ref v));
Vector4[] expected = CreateExpectedVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v));
@ -419,13 +410,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
void SourceAction(ref Vector4 v)
{
Numerics.UnPremultiply(ref v);
}
void ExpectedAction(ref Vector4 v)
{
Numerics.Premultiply(ref v);
}
void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v);
TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v));
Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v));
@ -446,8 +433,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
void SourceAction(ref Vector4 v)
{
Numerics.UnPremultiply(ref v);
SRgbCompanding.Compress(ref v);
}
void ExpectedAction(ref Vector4 v)
@ -1006,15 +991,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackFromRgbPlanes(int count)
{
SimdUtilsTests.TestPackFromRgbPlanes<TPixel>(
=> SimdUtilsTests.TestPackFromRgbPlanes<TPixel>(
count,
(
r,
g,
b,
actual) => PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.Configuration, r, g, b, actual));
}
(r, g, b, actual) => PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.Configuration, r, g, b, actual));
public delegate void RefAction<T1>(ref T1 arg1);
@ -1053,11 +1032,12 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
internal static void TestOperation<TSource, TDest>(
TSource[] source,
TDest[] expected,
Action<TSource[], IMemoryOwner<TDest>> action)
Action<TSource[], IMemoryOwner<TDest>> action,
bool preferExactComparison = true)
where TSource : struct
where TDest : struct
{
using (var buffers = new TestBuffers<TSource, TDest>(source, expected))
using (var buffers = new TestBuffers<TSource, TDest>(source, expected, preferExactComparison))
{
action(buffers.SourceBuffer, buffers.ActualDestBuffer);
buffers.Verify();
@ -1071,7 +1051,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
for (int i = 0; i < result.Length; i++)
{
Vector4 v = GetVector(rnd);
Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v);
result[i] = v;
@ -1088,7 +1068,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
for (int i = 0; i < result.Length; i++)
{
Vector4 v = GetVector(rnd);
Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v);
@ -1106,7 +1086,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
for (int i = 0; i < result.Length; i++)
{
Vector4 v = GetVector(rnd);
Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v);
@ -1129,10 +1109,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
return result;
}
internal static Vector4 GetVector(Random rnd)
{
return new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble());
}
internal static Vector4 GetScaledVector(Random rnd)
=> new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble());
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct OctetBytes
@ -1160,11 +1138,14 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
public TDest[] ExpectedDestBuffer { get; }
public TestBuffers(TSource[] source, TDest[] expectedDest)
public bool PreferExactComparison { get; }
public TestBuffers(TSource[] source, TDest[] expectedDest, bool preferExactComparison = true)
{
this.SourceBuffer = source;
this.ExpectedDestBuffer = expectedDest;
this.ActualDestBuffer = Configuration.Default.MemoryAllocator.Allocate<TDest>(expectedDest.Length);
this.PreferExactComparison = preferExactComparison;
}
public void Dispose() => this.ActualDestBuffer.Dispose();
@ -1177,26 +1158,54 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
Span<Vector4> expected = MemoryMarshal.Cast<TDest, Vector4>(this.ExpectedDestBuffer.AsSpan());
Span<Vector4> actual = MemoryMarshal.Cast<TDest, Vector4>(this.ActualDestBuffer.GetSpan());
var comparer = new ApproximateFloatComparer(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F);
var comparer = new ApproximateFloatComparer(0.001f);
for (int i = 0; i < count; i++)
{
// ReSharper disable PossibleNullReferenceException
Assert.Equal(expected[i], actual[i], comparer);
}
}
else if (!this.PreferExactComparison && typeof(IPixel).IsAssignableFrom(typeof(TDest)) && IsComplexPixel())
{
Span<TDest> expected = this.ExpectedDestBuffer.AsSpan();
Span<TDest> actual = this.ActualDestBuffer.GetSpan();
var comparer = new ApproximateFloatComparer(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F);
// ReSharper restore PossibleNullReferenceException
for (int i = 0; i < count; i++)
{
Assert.Equal((IPixel)expected[i], (IPixel)actual[i], comparer);
}
}
else
{
Span<TDest> expected = this.ExpectedDestBuffer.AsSpan();
Span<TDest> actual = this.ActualDestBuffer.GetSpan();
for (int i = 0; i < count; i++)
{
Assert.Equal(expected[i], actual[i]);
}
}
}
// TODO: We really need a PixelTypeInfo.BitsPerComponent property!!
private static bool IsComplexPixel()
{
switch (default(TDest))
{
case HalfSingle _:
case HalfVector2 _:
case L16 _:
case La32 _:
case NormalizedShort2 _:
case Rg32 _:
case Short2 _:
return true;
default:
return Unsafe.SizeOf<TDest>() > sizeof(int);
}
}
}
}
}

32
tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests
{
@ -12,6 +13,7 @@ namespace SixLabors.ImageSharp.Tests
internal readonly struct ApproximateFloatComparer :
IEqualityComparer<float>,
IEqualityComparer<Vector2>,
IEqualityComparer<IPixel>,
IEqualityComparer<Vector4>,
IEqualityComparer<ColorMatrix>
{
@ -32,30 +34,42 @@ namespace SixLabors.ImageSharp.Tests
}
/// <inheritdoc/>
public int GetHashCode(float obj) => obj.GetHashCode();
public int GetHashCode(float obj)
=> obj.GetHashCode();
/// <inheritdoc/>
public bool Equals(Vector2 x, Vector2 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y);
public bool Equals(Vector2 x, Vector2 y)
=> this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y);
/// <inheritdoc/>
public int GetHashCode(Vector2 obj) => obj.GetHashCode();
public int GetHashCode(Vector2 obj)
=> obj.GetHashCode();
/// <inheritdoc/>
public bool Equals(Vector4 x, Vector4 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z) && this.Equals(x.W, y.W);
public bool Equals(IPixel x, IPixel y)
=> this.Equals(x.ToScaledVector4(), y.ToScaledVector4());
public int GetHashCode(IPixel obj)
=> obj.ToScaledVector4().GetHashCode();
/// <inheritdoc/>
public bool Equals(Vector4 x, Vector4 y)
=> this.Equals(x.X, y.X)
&& this.Equals(x.Y, y.Y)
&& this.Equals(x.Z, y.Z)
&& this.Equals(x.W, y.W);
/// <inheritdoc/>
public int GetHashCode(Vector4 obj) => obj.GetHashCode();
public int GetHashCode(Vector4 obj)
=> obj.GetHashCode();
/// <inheritdoc/>
public bool Equals(ColorMatrix x, ColorMatrix y)
{
return
this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14)
=> this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14)
&& this.Equals(x.M21, y.M21) && this.Equals(x.M22, y.M22) && this.Equals(x.M23, y.M23) && this.Equals(x.M24, y.M24)
&& this.Equals(x.M31, y.M31) && this.Equals(x.M32, y.M32) && this.Equals(x.M33, y.M33) && this.Equals(x.M34, y.M34)
&& this.Equals(x.M41, y.M41) && this.Equals(x.M42, y.M42) && this.Equals(x.M43, y.M43) && this.Equals(x.M44, y.M44)
&& this.Equals(x.M51, y.M51) && this.Equals(x.M52, y.M52) && this.Equals(x.M53, y.M53) && this.Equals(x.M54, y.M54);
}
/// <inheritdoc/>
public int GetHashCode(ColorMatrix obj) => obj.GetHashCode();

Loading…
Cancel
Save