Browse Source

Introduce Numerics and migrate ImageMaths methods

js/color-alpha-handling
James Jackson-South 6 years ago
parent
commit
e9f734a943
  1. 2
      src/ImageSharp/ColorSpaces/Companding/LCompanding.cs
  2. 6
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs
  3. 6
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs
  4. 2
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs
  5. 124
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  6. 417
      src/ImageSharp/Common/Helpers/Numerics.cs
  7. 2
      src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs
  8. 4
      src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs
  9. 4
      src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs
  10. 4
      src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs
  11. 24
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  12. 4
      src/ImageSharp/Common/Helpers/SimdUtils.cs
  13. 4
      src/ImageSharp/Common/Helpers/Vector4Utilities.cs
  14. 8
      src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
  15. 14
      src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
  16. 4
      src/ImageSharp/Formats/Png/Filters/SubFilter.cs
  17. 2
      src/ImageSharp/Formats/Png/Filters/UpFilter.cs
  18. 2
      src/ImageSharp/Primitives/ComplexVector4.cs
  19. 6
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs
  20. 4
      src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
  21. 2
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs
  22. 2
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs
  23. 2
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
  24. 42
      tests/ImageSharp.Benchmarks/General/BasicMath/ClampSpan.cs
  25. 2
      tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs
  26. 2
      tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs
  27. 138
      tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs
  28. 265
      tests/ImageSharp.Tests/Helpers/NumericsTests.cs

2
src/ImageSharp/ColorSpaces/Companding/LCompanding.cs

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
/// <returns>The <see cref="float"/> representing the linear channel value.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static float Expand(float channel)
=> channel <= 0.08F ? (100F * channel) / CieConstants.Kappa : ImageMaths.Pow3((channel + 0.16F) / 1.16F);
=> channel <= 0.08F ? (100F * channel) / CieConstants.Kappa : Numerics.Pow3((channel + 0.16F) / 1.16F);
/// <summary>
/// Compresses an uncompanded channel (linear) to its nonlinear equivalent.

6
src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs

@ -25,11 +25,11 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion
float fx = (a / 500F) + fy;
float fz = fy - (b / 200F);
float fx3 = ImageMaths.Pow3(fx);
float fz3 = ImageMaths.Pow3(fz);
float fx3 = Numerics.Pow3(fx);
float fz3 = Numerics.Pow3(fz);
float xr = fx3 > CieConstants.Epsilon ? fx3 : ((116F * fx) - 16F) / CieConstants.Kappa;
float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? ImageMaths.Pow3((l + 16F) / 116F) : l / CieConstants.Kappa;
float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? Numerics.Pow3((l + 16F) / 116F) : l / CieConstants.Kappa;
float zr = fz3 > CieConstants.Epsilon ? fz3 : ((116F * fz) - 16F) / CieConstants.Kappa;
var wxyz = new Vector3(input.WhitePoint.X, input.WhitePoint.Y, input.WhitePoint.Z);

6
src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion
float v0 = ComputeV0(input.WhitePoint);
float y = l > CieConstants.Kappa * CieConstants.Epsilon
? ImageMaths.Pow3((l + 16) / 116)
? Numerics.Pow3((l + 16) / 116)
: l / CieConstants.Kappa;
float a = ((52 * l / (u + (13 * l * u0))) - 1) / 3;
@ -71,4 +71,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion
private static float ComputeV0(in CieXyz input)
=> (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z));
}
}
}

2
src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs

@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion
float ka = ComputeKa(input.WhitePoint);
float kb = ComputeKb(input.WhitePoint);
float pow = ImageMaths.Pow2(l / 100F);
float pow = Numerics.Pow2(l / 100F);
float sqrtPow = MathF.Sqrt(pow);
float y = pow * yn;

124
src/ImageSharp/Common/Helpers/ImageMaths.cs

@ -4,13 +4,12 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Provides common mathematical methods.
/// Provides common mathematical methods used for image processing.
/// </summary>
internal static class ImageMaths
{
@ -108,85 +107,6 @@ namespace SixLabors.ImageSharp
[MethodImpl(InliningOptions.ShortMethod)]
public static ushort UpscaleFrom8BitTo16Bit(byte component) => (ushort)(component * 257);
/// <summary>
/// Determine the Greatest CommonDivisor (GCD) of two numbers.
/// </summary>
public static int GreatestCommonDivisor(int a, int b)
{
while (b != 0)
{
int temp = b;
b = a % b;
a = temp;
}
return a;
}
/// <summary>
/// Determine the Least Common Multiple (LCM) of two numbers.
/// </summary>
public static int LeastCommonMultiple(int a, int b)
{
// https://en.wikipedia.org/wiki/Least_common_multiple#Reduction_by_the_greatest_common_divisor
return (a / GreatestCommonDivisor(a, b)) * b;
}
/// <summary>
/// Calculates <paramref name="x"/> % 2
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public static int Modulo2(int x) => x & 1;
/// <summary>
/// Calculates <paramref name="x"/> % 4
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public static int Modulo4(int x) => x & 3;
/// <summary>
/// Calculates <paramref name="x"/> % 8
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public static int Modulo8(int x) => x & 7;
/// <summary>
/// Fast (x mod m) calculator, with the restriction that
/// <paramref name="m"/> should be power of 2.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public static int ModuloP2(int x, int m) => x & (m - 1);
/// <summary>
/// Returns the absolute value of a 32-bit signed integer. Uses bit shifting to speed up the operation.
/// </summary>
/// <param name="x">
/// A number that is greater than <see cref="int.MinValue"/>, but less than or equal to <see cref="int.MaxValue"/>
/// </param>
/// <returns>The <see cref="int"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static int FastAbs(int x)
{
int y = x >> 31;
return (x ^ y) - y;
}
/// <summary>
/// Returns a specified number raised to the power of 2
/// </summary>
/// <param name="x">A single-precision floating-point number</param>
/// <returns>The number <paramref name="x" /> raised to the power of 2.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static float Pow2(float x) => x * x;
/// <summary>
/// Returns a specified number raised to the power of 3
/// </summary>
/// <param name="x">A single-precision floating-point number</param>
/// <returns>The number <paramref name="x" /> raised to the power of 3.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static float Pow3(float x) => x * x * x;
/// <summary>
/// Returns how many bits are required to store the specified number of colors.
/// Performs a Log2() on the value.
@ -206,48 +126,6 @@ namespace SixLabors.ImageSharp
[MethodImpl(InliningOptions.ShortMethod)]
public static int GetColorCountForBitDepth(int bitDepth) => 1 << bitDepth;
/// <summary>
/// Implementation of 1D Gaussian G(x) function
/// </summary>
/// <param name="x">The x provided to G(x).</param>
/// <param name="sigma">The spread of the blur.</param>
/// <returns>The Gaussian G(x)</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static float Gaussian(float x, float sigma)
{
const float Numerator = 1.0f;
float denominator = MathF.Sqrt(2 * MathF.PI) * sigma;
float exponentNumerator = -x * x;
float exponentDenominator = 2 * Pow2(sigma);
float left = Numerator / denominator;
float right = MathF.Exp(exponentNumerator / exponentDenominator);
return left * right;
}
/// <summary>
/// Returns the result of a normalized sine cardinal function for the given value.
/// SinC(x) = sin(pi*x)/(pi*x).
/// </summary>
/// <param name="f">A single-precision floating-point number to calculate the result for.</param>
/// <returns>
/// The sine cardinal of <paramref name="f" />.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static float SinC(float f)
{
if (MathF.Abs(f) > Constants.Epsilon)
{
f *= MathF.PI;
float result = MathF.Sin(f) / f;
return MathF.Abs(result) < Constants.Epsilon ? 0F : result;
}
return 1F;
}
/// <summary>
/// Gets the bounding <see cref="Rectangle"/> from the given points.
/// </summary>

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

@ -0,0 +1,417 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Provides optimized static methods for trigonometric, logarithmic,
/// and other common mathematical functions.
/// </summary>
internal static class Numerics
{
/// <summary>
/// Determine the Greatest CommonDivisor (GCD) of two numbers.
/// </summary>
public static int GreatestCommonDivisor(int a, int b)
{
while (b != 0)
{
int temp = b;
b = a % b;
a = temp;
}
return a;
}
/// <summary>
/// Determine the Least Common Multiple (LCM) of two numbers.
/// </summary>
public static int LeastCommonMultiple(int a, int b)
{
// https://en.wikipedia.org/wiki/Least_common_multiple#Reduction_by_the_greatest_common_divisor
return (a / GreatestCommonDivisor(a, b)) * b;
}
/// <summary>
/// Calculates <paramref name="x"/> % 2
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Modulo2(int x) => x & 1;
/// <summary>
/// Calculates <paramref name="x"/> % 4
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Modulo4(int x) => x & 3;
/// <summary>
/// Calculates <paramref name="x"/> % 8
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Modulo8(int x) => x & 7;
/// <summary>
/// Fast (x mod m) calculator, with the restriction that
/// <paramref name="m"/> should be power of 2.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ModuloP2(int x, int m) => x & (m - 1);
/// <summary>
/// Returns the absolute value of a 32-bit signed integer.
/// Uses bit shifting to speed up the operation compared to <see cref="Math"/>.
/// </summary>
/// <param name="x">
/// A number that is greater than <see cref="int.MinValue"/>, but less than
/// or equal to <see cref="int.MaxValue"/>
/// </param>
/// <returns>The <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Abs(int x)
{
int y = x >> 31;
return (x ^ y) - y;
}
/// <summary>
/// Returns a specified number raised to the power of 2
/// </summary>
/// <param name="x">A single-precision floating-point number</param>
/// <returns>The number <paramref name="x" /> raised to the power of 2.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Pow2(float x) => x * x;
/// <summary>
/// Returns a specified number raised to the power of 3
/// </summary>
/// <param name="x">A single-precision floating-point number</param>
/// <returns>The number <paramref name="x" /> raised to the power of 3.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Pow3(float x) => x * x * x;
/// <summary>
/// Implementation of 1D Gaussian G(x) function
/// </summary>
/// <param name="x">The x provided to G(x).</param>
/// <param name="sigma">The spread of the blur.</param>
/// <returns>The Gaussian G(x)</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Gaussian(float x, float sigma)
{
const float Numerator = 1.0f;
float denominator = MathF.Sqrt(2 * MathF.PI) * sigma;
float exponentNumerator = -x * x;
float exponentDenominator = 2 * Pow2(sigma);
float left = Numerator / denominator;
float right = MathF.Exp(exponentNumerator / exponentDenominator);
return left * right;
}
/// <summary>
/// Returns the result of a normalized sine cardinal function for the given value.
/// SinC(x) = sin(pi*x)/(pi*x).
/// </summary>
/// <param name="f">A single-precision floating-point number to calculate the result for.</param>
/// <returns>
/// The sine cardinal of <paramref name="f" />.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float SinC(float f)
{
if (MathF.Abs(f) > Constants.Epsilon)
{
f *= MathF.PI;
float result = MathF.Sin(f) / f;
return MathF.Abs(result) < Constants.Epsilon ? 0F : result;
}
return 1F;
}
/// <summary>
/// Returns the value clamped to the inclusive range of min and max.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The minimum inclusive value.</param>
/// <param name="max">The maximum inclusive value.</param>
/// <returns>The clamped <see cref="byte"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte Clamp(byte value, byte min, byte max)
{
// Order is important here as someone might set min to higher than max.
if (value > max)
{
return max;
}
if (value < min)
{
return min;
}
return value;
}
/// <summary>
/// Returns the value clamped to the inclusive range of min and max.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The minimum inclusive value.</param>
/// <param name="max">The maximum inclusive value.</param>
/// <returns>The clamped <see cref="uint"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Clamp(uint value, uint min, uint max)
{
if (value > max)
{
return max;
}
if (value < min)
{
return min;
}
return value;
}
/// <summary>
/// Returns the value clamped to the inclusive range of min and max.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The minimum inclusive value.</param>
/// <param name="max">The maximum inclusive value.</param>
/// <returns>The clamped <see cref="uint"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Clamp(int value, int min, int max)
{
if (value > max)
{
return max;
}
if (value < min)
{
return min;
}
return value;
}
/// <summary>
/// Returns the value clamped to the inclusive range of min and max.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The minimum inclusive value.</param>
/// <param name="max">The maximum inclusive value.</param>
/// <returns>The clamped <see cref="float"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Clamp(float value, float min, float max)
{
if (value > max)
{
return max;
}
if (value < min)
{
return min;
}
return value;
}
/// <summary>
/// Returns the value clamped to the inclusive range of min and max.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The minimum inclusive value.</param>
/// <param name="max">The maximum inclusive value.</param>
/// <returns>The clamped <see cref="double"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Clamp(double value, double min, double max)
{
if (value > max)
{
return max;
}
if (value < min)
{
return min;
}
return value;
}
/// <summary>
/// Clamps the span values to the inclusive range of min and max.
/// </summary>
/// <param name="span">The span containing the values to clamp.</param>
/// <param name="min">The minimum inclusive value.</param>
/// <param name="max">The maximum inclusive value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clamp(Span<byte> span, byte min, byte max)
{
int reduced = ClampReduce(span, min, max);
if (reduced > 0)
{
for (int i = reduced; i < span.Length; i++)
{
ref byte v = ref span[i];
v = Clamp(v, min, max);
}
}
}
/// <summary>
/// Clamps the span values to the inclusive range of min and max.
/// </summary>
/// <param name="span">The span containing the values to clamp.</param>
/// <param name="min">The minimum inclusive value.</param>
/// <param name="max">The maximum inclusive value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clamp(Span<uint> span, uint min, uint max)
{
int reduced = ClampReduce(span, min, max);
if (reduced > 0)
{
for (int i = reduced; i < span.Length; i++)
{
ref uint v = ref span[i];
v = Clamp(v, min, max);
}
}
}
/// <summary>
/// Clamps the span values to the inclusive range of min and max.
/// </summary>
/// <param name="span">The span containing the values to clamp.</param>
/// <param name="min">The minimum inclusive value.</param>
/// <param name="max">The maximum inclusive value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clamp(Span<int> span, int min, int max)
{
int reduced = ClampReduce(span, min, max);
if (reduced > 0)
{
for (int i = reduced; i < span.Length; i++)
{
ref int v = ref span[i];
v = Clamp(v, min, max);
}
}
}
/// <summary>
/// Clamps the span values to the inclusive range of min and max.
/// </summary>
/// <param name="span">The span containing the values to clamp.</param>
/// <param name="min">The minimum inclusive value.</param>
/// <param name="max">The maximum inclusive value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clamp(Span<float> span, float min, float max)
{
int reduced = ClampReduce(span, min, max);
if (reduced > 0)
{
for (int i = reduced; i < span.Length; i++)
{
ref float v = ref span[i];
v = Clamp(v, min, max);
}
}
}
/// <summary>
/// Clamps the span values to the inclusive range of min and max.
/// </summary>
/// <param name="span">The span containing the values to clamp.</param>
/// <param name="min">The minimum inclusive value.</param>
/// <param name="max">The maximum inclusive value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clamp(Span<double> span, double min, double max)
{
int reduced = ClampReduce(span, min, max);
if (reduced > 0)
{
for (int i = reduced; i < span.Length; i++)
{
ref double v = ref span[i];
v = Clamp(v, min, max);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int ClampReduce<T>(Span<T> span, T min, T max)
where T : unmanaged
{
if (Vector.IsHardwareAccelerated && span.Length >= Vector<T>.Count)
{
int remainder = ModuloP2(span.Length, Vector<T>.Count);
int adjustedCount = span.Length - remainder;
if (adjustedCount > 0)
{
ClampImpl(span.Slice(0, adjustedCount), min, max);
}
return adjustedCount;
}
return 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ClampImpl<T>(Span<T> span, T min, T max)
where T : unmanaged
{
ref T sRef = ref MemoryMarshal.GetReference(span);
ref Vector<T> vsBase = ref Unsafe.As<T, Vector<T>>(ref MemoryMarshal.GetReference(span));
var vmin = new Vector<T>(min);
var vmax = new Vector<T>(max);
int n = span.Length / Vector<T>.Count;
int m = Modulo4(n);
int u = n - m;
for (int i = 0; i < u; i += 4)
{
ref Vector<T> vs0 = ref Unsafe.Add(ref vsBase, i);
ref Vector<T> vs1 = ref Unsafe.Add(ref vs0, 1);
ref Vector<T> vs2 = ref Unsafe.Add(ref vs0, 2);
ref Vector<T> vs3 = ref Unsafe.Add(ref vs0, 3);
vs0 = Vector.Min(Vector.Max(vmin, vs0), vmax);
vs1 = Vector.Min(Vector.Max(vmin, vs1), vmax);
vs2 = Vector.Min(Vector.Max(vmin, vs2), vmax);
vs3 = Vector.Min(Vector.Max(vmin, vs3), vmax);
}
if (m > 0)
{
for (int i = u; i < n; i++)
{
ref Vector<T> vs0 = ref Unsafe.Add(ref vsBase, i);
vs0 = Vector.Min(Vector.Max(vmin, vs0), vmax);
}
}
}
}
}

2
src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs

@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp
ref Byte3 dBase = ref Unsafe.As<byte, Byte3>(ref MemoryMarshal.GetReference(dest));
int n = source.Length / 4;
int m = ImageMaths.Modulo4(n);
int m = Numerics.Modulo4(n);
int u = n - m;
ref uint sLoopEnd = ref Unsafe.Add(ref sBase, u);

4
src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp
return;
}
int remainder = ImageMaths.Modulo8(source.Length);
int remainder = Numerics.Modulo8(source.Length);
int adjustedCount = source.Length - remainder;
if (adjustedCount > 0)
@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp
return;
}
int remainder = ImageMaths.Modulo8(source.Length);
int remainder = Numerics.Modulo8(source.Length);
int adjustedCount = source.Length - remainder;
if (adjustedCount > 0)

4
src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs

@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp
return;
}
int remainder = ImageMaths.ModuloP2(source.Length, Vector<byte>.Count);
int remainder = Numerics.ModuloP2(source.Length, Vector<byte>.Count);
int adjustedCount = source.Length - remainder;
if (adjustedCount > 0)
@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp
return;
}
int remainder = ImageMaths.ModuloP2(source.Length, Vector<byte>.Count);
int remainder = Numerics.ModuloP2(source.Length, Vector<byte>.Count);
int adjustedCount = source.Length - remainder;
if (adjustedCount > 0)

4
src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp
{
DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
int remainder = ImageMaths.Modulo4(source.Length);
int remainder = Numerics.Modulo4(source.Length);
int adjustedCount = source.Length - remainder;
if (adjustedCount > 0)
@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp
{
DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
int remainder = ImageMaths.Modulo4(source.Length);
int remainder = Numerics.Modulo4(source.Length);
int adjustedCount = source.Length - remainder;
if (adjustedCount > 0)

24
src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs

@ -38,8 +38,8 @@ namespace SixLabors.ImageSharp
if (Avx.IsSupported || Sse.IsSupported)
{
int remainder = Avx.IsSupported
? ImageMaths.ModuloP2(source.Length, Vector256<float>.Count)
: ImageMaths.ModuloP2(source.Length, Vector128<float>.Count);
? Numerics.ModuloP2(source.Length, Vector256<float>.Count)
: Numerics.ModuloP2(source.Length, Vector128<float>.Count);
int adjustedCount = source.Length - remainder;
@ -72,8 +72,8 @@ namespace SixLabors.ImageSharp
if (Avx2.IsSupported || Ssse3.IsSupported)
{
int remainder = Avx2.IsSupported
? ImageMaths.ModuloP2(source.Length, Vector256<byte>.Count)
: ImageMaths.ModuloP2(source.Length, Vector128<byte>.Count);
? Numerics.ModuloP2(source.Length, Vector256<byte>.Count)
: Numerics.ModuloP2(source.Length, Vector128<byte>.Count);
int adjustedCount = source.Length - remainder;
@ -203,7 +203,7 @@ namespace SixLabors.ImageSharp
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(dest));
int n = dest.Length / Vector256<float>.Count;
int m = ImageMaths.Modulo4(n);
int m = Numerics.Modulo4(n);
int u = n - m;
for (int i = 0; i < u; i += 4)
@ -235,7 +235,7 @@ namespace SixLabors.ImageSharp
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(dest));
int n = dest.Length / Vector128<float>.Count;
int m = ImageMaths.Modulo4(n);
int m = Numerics.Modulo4(n);
int u = n - m;
for (int i = 0; i < u; i += 4)
@ -288,7 +288,7 @@ namespace SixLabors.ImageSharp
ref Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(dest));
int n = dest.Length / Vector256<byte>.Count;
int m = ImageMaths.Modulo4(n);
int m = Numerics.Modulo4(n);
int u = n - m;
for (int i = 0; i < u; i += 4)
@ -324,7 +324,7 @@ namespace SixLabors.ImageSharp
ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(dest));
int n = dest.Length / Vector128<byte>.Count;
int m = ImageMaths.Modulo4(n);
int m = Numerics.Modulo4(n);
int u = n - m;
for (int i = 0; i < u; i += 4)
@ -550,11 +550,11 @@ namespace SixLabors.ImageSharp
int remainder;
if (Avx2.IsSupported)
{
remainder = ImageMaths.ModuloP2(source.Length, Vector256<byte>.Count);
remainder = Numerics.ModuloP2(source.Length, Vector256<byte>.Count);
}
else
{
remainder = ImageMaths.ModuloP2(source.Length, Vector128<byte>.Count);
remainder = Numerics.ModuloP2(source.Length, Vector128<byte>.Count);
}
int adjustedCount = source.Length - remainder;
@ -683,11 +683,11 @@ namespace SixLabors.ImageSharp
int remainder;
if (Avx2.IsSupported)
{
remainder = ImageMaths.ModuloP2(source.Length, Vector256<byte>.Count);
remainder = Numerics.ModuloP2(source.Length, Vector256<byte>.Count);
}
else
{
remainder = ImageMaths.ModuloP2(source.Length, Vector128<byte>.Count);
remainder = Numerics.ModuloP2(source.Length, Vector128<byte>.Count);
}
int adjustedCount = source.Length - remainder;

4
src/ImageSharp/Common/Helpers/SimdUtils.cs

@ -206,7 +206,7 @@ namespace SixLabors.ImageSharp
{
DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
DebugGuard.IsTrue(
ImageMaths.ModuloP2(dest.Length, shouldBeDivisibleBy) == 0,
Numerics.ModuloP2(dest.Length, shouldBeDivisibleBy) == 0,
nameof(source),
$"length should be divisible by {shouldBeDivisibleBy}!");
}
@ -216,7 +216,7 @@ namespace SixLabors.ImageSharp
{
DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
DebugGuard.IsTrue(
ImageMaths.ModuloP2(dest.Length, shouldBeDivisibleBy) == 0,
Numerics.ModuloP2(dest.Length, shouldBeDivisibleBy) == 0,
nameof(source),
$"length should be divisible by {shouldBeDivisibleBy}!");
}

4
src/ImageSharp/Common/Helpers/Vector4Utilities.cs

@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp
vectorsBase = ref Unsafe.Add(ref vectorsBase, 1);
}
if (ImageMaths.Modulo2(vectors.Length) != 0)
if (Numerics.Modulo2(vectors.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
Premultiply(ref MemoryMarshal.GetReference(vectors.Slice(vectors.Length - 1)));
@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp
vectorsBase = ref Unsafe.Add(ref vectorsBase, 1);
}
if (ImageMaths.Modulo2(vectors.Length) != 0)
if (Numerics.Modulo2(vectors.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
UnPremultiply(ref MemoryMarshal.GetReference(vectors.Slice(vectors.Length - 1)));

8
src/ImageSharp/Formats/Png/Filters/AverageFilter.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - (above >> 1));
sum += ImageMaths.FastAbs(unchecked((sbyte)res));
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 */)
@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - Average(left, above));
sum += ImageMaths.FastAbs(unchecked((sbyte)res));
sum += Numerics.Abs(unchecked((sbyte)res));
}
sum -= 3;
@ -102,4 +102,4 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Average(byte left, byte above) => (left + above) >> 1;
}
}
}

14
src/ImageSharp/Formats/Png/Filters/PaethFilter.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - PaethPredictor(0, above, 0));
sum += ImageMaths.FastAbs(unchecked((sbyte)res));
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 */)
@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - PaethPredictor(left, above, upperLeft));
sum += ImageMaths.FastAbs(unchecked((sbyte)res));
sum += Numerics.Abs(unchecked((sbyte)res));
}
sum -= 4;
@ -111,9 +111,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
private static byte PaethPredictor(byte left, byte above, byte upperLeft)
{
int p = left + above - upperLeft;
int pa = ImageMaths.FastAbs(p - left);
int pb = ImageMaths.FastAbs(p - above);
int pc = ImageMaths.FastAbs(p - upperLeft);
int pa = Numerics.Abs(p - left);
int pb = Numerics.Abs(p - above);
int pc = Numerics.Abs(p - upperLeft);
if (pa <= pb && pa <= pc)
{
@ -128,4 +128,4 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
return upperLeft;
}
}
}
}

4
src/ImageSharp/Formats/Png/Filters/SubFilter.cs

@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = scan;
sum += ImageMaths.FastAbs(unchecked((sbyte)res));
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 */)
@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - prev);
sum += ImageMaths.FastAbs(unchecked((sbyte)res));
sum += Numerics.Abs(unchecked((sbyte)res));
}
sum -= 1;

2
src/ImageSharp/Formats/Png/Filters/UpFilter.cs

@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - above);
sum += ImageMaths.FastAbs(unchecked((sbyte)res));
sum += Numerics.Abs(unchecked((sbyte)res));
}
sum -= 2;

2
src/ImageSharp/Primitives/ComplexVector4.cs

@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="value">The input <see cref="ComplexVector4"/> to sum</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void Sum(in ComplexVector4 value)
public void Sum(ComplexVector4 value)
{
this.Real += value.Real;
this.Imaginary += value.Imaginary;

6
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
for (int i = 0; i < size; i++)
{
float x = i - midpoint;
float gx = ImageMaths.Gaussian(x, weight);
float gx = Numerics.Gaussian(x, weight);
sum += gx;
kernel[0, i] = gx;
}
@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
for (int i = 0; i < size; i++)
{
float x = i - midpoint;
float gx = ImageMaths.Gaussian(x, weight);
float gx = Numerics.Gaussian(x, weight);
sum += gx;
kernel[0, i] = gx;
}
@ -88,4 +88,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
return kernel;
}
}
}
}

4
src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs

@ -182,7 +182,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{
if (y < 0)
{
y = ImageMaths.FastAbs(y);
y = Numerics.Abs(y);
}
else if (y >= source.Height)
{
@ -197,7 +197,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
int idx = 0;
for (int dx = x; dx < x + tileWidth; dx++)
{
rowPixels[idx] = source[ImageMaths.FastAbs(dx), y].ToVector4();
rowPixels[idx] = source[Numerics.Abs(dx), y].ToVector4();
idx++;
}

2
src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs

@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
float radius = this.Radius;
if (x < radius)
{
return ImageMaths.SinC(x) * ImageMaths.SinC(x / radius);
return Numerics.SinC(x) * Numerics.SinC(x / radius);
}
return 0F;

2
src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs

@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (x < 3F)
{
return ImageMaths.SinC(x) * (1F - (x * x / 9F));
return Numerics.SinC(x) * (1F - (x * x / 9F));
}
return 0F;

2
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs

@ -132,7 +132,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// 'ratio' is a rational number.
// Multiplying it by LCM(sourceSize, destSize)/sourceSize will result in a whole number "again".
// This value is determining the length of the periods in repeating kernel map rows.
int period = ImageMaths.LeastCommonMultiple(sourceSize, destinationSize) / sourceSize;
int period = Numerics.LeastCommonMultiple(sourceSize, destinationSize) / sourceSize;
// the center position at i == 0:
double center0 = (ratio - 1) * 0.5;

42
tests/ImageSharp.Benchmarks/General/BasicMath/ClampSpan.cs

@ -0,0 +1,42 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using BenchmarkDotNet.Attributes;
namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath
{
public class ClampSpan
{
private static readonly int[] A = new int[2048];
private static readonly int[] B = new int[2048];
public void Setup()
{
var r = new Random();
for (int i = 0; i < A.Length; i++)
{
int x = r.Next();
A[i] = x;
B[i] = x;
}
}
[Benchmark(Baseline = true)]
public void ClampNoIntrinsics()
{
for (int i = 0; i < A.Length; i++)
{
ref int x = ref A[i];
x = x.Clamp(64, 128);
}
}
[Benchmark]
public void ClampVectorIntrinsics()
{
Numerics.Clamp(B, 64, 128);
}
}
}

2
tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath
[Benchmark]
public int Bitwise()
{
return ImageMaths.Modulo8(this.value);
return Numerics.Modulo8(this.value);
}
}
}

2
tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs

@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath
[Benchmark]
public int Bitwise()
{
return ImageMaths.ModuloP2(this.value, this.m);
return Numerics.ModuloP2(this.value, this.m);
}
// RESULTS:

138
tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs

@ -10,144 +10,6 @@ namespace SixLabors.ImageSharp.Tests.Helpers
{
public class ImageMathsTests
{
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(4)]
[InlineData(100)]
[InlineData(123)]
[InlineData(53436353)]
public void Modulo2(int x)
{
int actual = ImageMaths.Modulo2(x);
Assert.Equal(x % 2, actual);
}
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(4)]
[InlineData(100)]
[InlineData(123)]
[InlineData(53436353)]
public void Modulo4(int x)
{
int actual = ImageMaths.Modulo4(x);
Assert.Equal(x % 4, actual);
}
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(6)]
[InlineData(7)]
[InlineData(8)]
[InlineData(100)]
[InlineData(123)]
[InlineData(53436353)]
[InlineData(975)]
public void Modulo8(int x)
{
int actual = ImageMaths.Modulo8(x);
Assert.Equal(x % 8, actual);
}
[Theory]
[InlineData(0, 2)]
[InlineData(1, 2)]
[InlineData(2, 2)]
[InlineData(0, 4)]
[InlineData(3, 4)]
[InlineData(5, 4)]
[InlineData(5, 8)]
[InlineData(8, 8)]
[InlineData(8, 16)]
[InlineData(15, 16)]
[InlineData(17, 16)]
[InlineData(17, 32)]
[InlineData(31, 32)]
[InlineData(32, 32)]
[InlineData(33, 32)]
public void Modulo2P(int x, int m)
{
int actual = ImageMaths.ModuloP2(x, m);
Assert.Equal(x % m, actual);
}
[Theory]
[InlineData(0, 0, 0, 0)]
[InlineData(0.5f, 0, 1, 0.5f)]
[InlineData(-0.5f, -0.1f, 10, -0.1f)]
[InlineData(-0.05f, -0.1f, 10, -0.05f)]
[InlineData(9.9f, -0.1f, 10, 9.9f)]
[InlineData(10f, -0.1f, 10, 10f)]
[InlineData(10.1f, -0.1f, 10, 10f)]
public void Clamp(float x, float min, float max, float expected)
{
float actual = x.Clamp(min, max);
Assert.Equal(expected, actual);
}
[Fact]
public void FasAbsResultMatchesMath()
{
const int X = -33;
int expected = Math.Abs(X);
Assert.Equal(expected, ImageMaths.FastAbs(X));
}
[Fact]
public void Pow2ResultMatchesMath()
{
const float X = -33;
float expected = (float)Math.Pow(X, 2);
Assert.Equal(expected, ImageMaths.Pow2(X));
}
[Fact]
public void Pow3ResultMatchesMath()
{
const float X = -33;
float expected = (float)Math.Pow(X, 3);
Assert.Equal(expected, ImageMaths.Pow3(X));
}
[Theory]
[InlineData(1, 1, 1)]
[InlineData(1, 42, 1)]
[InlineData(10, 8, 2)]
[InlineData(12, 18, 6)]
[InlineData(4536, 1000, 8)]
[InlineData(1600, 1024, 64)]
public void GreatestCommonDivisor(int a, int b, int expected)
{
int actual = ImageMaths.GreatestCommonDivisor(a, b);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(1, 1, 1)]
[InlineData(1, 42, 42)]
[InlineData(3, 4, 12)]
[InlineData(6, 4, 12)]
[InlineData(1600, 1024, 25600)]
[InlineData(3264, 100, 81600)]
public void LeastCommonMultiple(int a, int b, int expected)
{
int actual = ImageMaths.LeastCommonMultiple(a, b);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(0.2f, 0.7f, 0.1f, 256, 140)]
[InlineData(0.5f, 0.5f, 0.5f, 256, 128)]

265
tests/ImageSharp.Tests/Helpers/NumericsTests.cs

@ -0,0 +1,265 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Helpers
{
public class NumericsTests
{
public delegate void SpanAction<T, in TArg, in TArg1>(Span<T> span, TArg arg, TArg1 arg1);
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(4)]
[InlineData(100)]
[InlineData(123)]
[InlineData(53436353)]
public void Modulo2(int x)
{
int actual = Numerics.Modulo2(x);
Assert.Equal(x % 2, actual);
}
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(4)]
[InlineData(100)]
[InlineData(123)]
[InlineData(53436353)]
public void Modulo4(int x)
{
int actual = Numerics.Modulo4(x);
Assert.Equal(x % 4, actual);
}
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(6)]
[InlineData(7)]
[InlineData(8)]
[InlineData(100)]
[InlineData(123)]
[InlineData(53436353)]
[InlineData(975)]
public void Modulo8(int x)
{
int actual = Numerics.Modulo8(x);
Assert.Equal(x % 8, actual);
}
[Theory]
[InlineData(0, 2)]
[InlineData(1, 2)]
[InlineData(2, 2)]
[InlineData(0, 4)]
[InlineData(3, 4)]
[InlineData(5, 4)]
[InlineData(5, 8)]
[InlineData(8, 8)]
[InlineData(8, 16)]
[InlineData(15, 16)]
[InlineData(17, 16)]
[InlineData(17, 32)]
[InlineData(31, 32)]
[InlineData(32, 32)]
[InlineData(33, 32)]
public void Modulo2P(int x, int m)
{
int actual = Numerics.ModuloP2(x, m);
Assert.Equal(x % m, actual);
}
[Theory]
[InlineData(-5)]
[InlineData(-17)]
[InlineData(-12856)]
[InlineData(-32)]
[InlineData(-7425)]
[InlineData(5)]
[InlineData(17)]
[InlineData(12856)]
[InlineData(32)]
[InlineData(7425)]
public void Abs(int x)
{
int expected = Math.Abs(x);
Assert.Equal(expected, Numerics.Abs(x));
}
[Theory]
[InlineData(-5)]
[InlineData(-17)]
[InlineData(-12856)]
[InlineData(-32)]
[InlineData(-7425)]
[InlineData(5)]
[InlineData(17)]
[InlineData(12856)]
[InlineData(32)]
[InlineData(7425)]
public void Pow2(float x)
{
float expected = (float)Math.Pow(x, 2);
Assert.Equal(expected, Numerics.Pow2(x));
}
[Theory]
[InlineData(-5)]
[InlineData(-17)]
[InlineData(-12856)]
[InlineData(-32)]
[InlineData(5)]
[InlineData(17)]
[InlineData(12856)]
[InlineData(32)]
public void Pow3(float x)
{
float expected = (float)Math.Pow(x, 3);
Assert.Equal(expected, Numerics.Pow3(x));
}
[Theory]
[InlineData(1, 1, 1)]
[InlineData(1, 42, 1)]
[InlineData(10, 8, 2)]
[InlineData(12, 18, 6)]
[InlineData(4536, 1000, 8)]
[InlineData(1600, 1024, 64)]
public void GreatestCommonDivisor(int a, int b, int expected)
{
int actual = Numerics.GreatestCommonDivisor(a, b);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(1, 1, 1)]
[InlineData(1, 42, 42)]
[InlineData(3, 4, 12)]
[InlineData(6, 4, 12)]
[InlineData(1600, 1024, 25600)]
[InlineData(3264, 100, 81600)]
public void LeastCommonMultiple(int a, int b, int expected)
{
int actual = Numerics.LeastCommonMultiple(a, b);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(64, 36, 96)]
[InlineData(128, 16, 196)]
[InlineData(567, 18, 142)]
[InlineData(1024, 0, 255)]
public void ClampByte(int length, byte min, byte max)
{
TestClampSpan(
length,
min,
max,
(s, m1, m2) => Numerics.Clamp(s, m1, m2),
(v, m1, m2) => Numerics.Clamp(v, m1, m2));
}
[Theory]
[InlineData(64, 36, 96)]
[InlineData(128, 16, 196)]
[InlineData(567, 18, 142)]
[InlineData(1024, 0, 255)]
public void ClampInt(int length, int min, int max)
{
TestClampSpan(
length,
min,
max,
(s, m1, m2) => Numerics.Clamp(s, m1, m2),
(v, m1, m2) => Numerics.Clamp(v, m1, m2));
}
[Theory]
[InlineData(64, 36, 96)]
[InlineData(128, 16, 196)]
[InlineData(567, 18, 142)]
[InlineData(1024, 0, 255)]
public void ClampUInt(int length, uint min, uint max)
{
TestClampSpan(
length,
min,
max,
(s, m1, m2) => Numerics.Clamp(s, m1, m2),
(v, m1, m2) => Numerics.Clamp(v, m1, m2));
}
[Theory]
[InlineData(64, 36, 96)]
[InlineData(128, 16, 196)]
[InlineData(567, 18, 142)]
[InlineData(1024, 0, 255)]
public void ClampFloat(int length, float min, float max)
{
TestClampSpan(
length,
min,
max,
(s, m1, m2) => Numerics.Clamp(s, m1, m2),
(v, m1, m2) => Numerics.Clamp(v, m1, m2));
}
[Theory]
[InlineData(64, 36, 96)]
[InlineData(128, 16, 196)]
[InlineData(567, 18, 142)]
[InlineData(1024, 0, 255)]
public void ClampDouble(int length, double min, double max)
{
TestClampSpan(
length,
min,
max,
(s, m1, m2) => Numerics.Clamp(s, m1, m2),
(v, m1, m2) => Numerics.Clamp(v, m1, m2));
}
private static void TestClampSpan<T>(
int length,
T min,
T max,
SpanAction<T, T, T> clampAction,
Func<T, T, T, T> refClampFunc)
where T : unmanaged, IComparable<T>
{
Span<T> actual = new T[length];
var r = new Random();
for (int i = 0; i < length; i++)
{
actual[i] = (T)Convert.ChangeType(r.Next(byte.MinValue, byte.MaxValue), typeof(T));
}
Span<T> expected = new T[length];
actual.CopyTo(expected);
for (int i = 0; i < expected.Length; i++)
{
ref T v = ref expected[i];
v = refClampFunc(v, min, max);
}
clampAction(actual, min, max);
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i], actual[i]);
}
}
}
}
Loading…
Cancel
Save