From e9f734a9439924c2ec1ff4caf360dd4a959f9b71 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 22 Nov 2020 23:48:31 +0000 Subject: [PATCH] Introduce Numerics and migrate ImageMaths methods --- .../ColorSpaces/Companding/LCompanding.cs | 2 +- .../Converters/CieLabToCieXyzConverter.cs | 6 +- .../Converters/CieLuvToCieXyzConverter.cs | 6 +- .../Converters/HunterLabToCieXyzConverter.cs | 2 +- src/ImageSharp/Common/Helpers/ImageMaths.cs | 124 +----- src/ImageSharp/Common/Helpers/Numerics.cs | 417 ++++++++++++++++++ .../Common/Helpers/Shuffle/IShuffle4Slice3.cs | 2 +- .../Helpers/SimdUtils.BasicIntrinsics256.cs | 4 +- .../Helpers/SimdUtils.ExtendedIntrinsics.cs | 4 +- .../SimdUtils.FallbackIntrinsics128.cs | 4 +- .../Common/Helpers/SimdUtils.HwIntrinsics.cs | 24 +- src/ImageSharp/Common/Helpers/SimdUtils.cs | 4 +- .../Common/Helpers/Vector4Utilities.cs | 4 +- .../Formats/Png/Filters/AverageFilter.cs | 8 +- .../Formats/Png/Filters/PaethFilter.cs | 14 +- .../Formats/Png/Filters/SubFilter.cs | 4 +- .../Formats/Png/Filters/UpFilter.cs | 2 +- src/ImageSharp/Primitives/ComplexVector4.cs | 2 +- .../ConvolutionProcessorHelpers.cs | 6 +- ...alizationSlidingWindowProcessor{TPixel}.cs | 4 +- .../Transforms/Resamplers/LanczosResampler.cs | 2 +- .../Transforms/Resamplers/WelchResampler.cs | 2 +- .../Transforms/Resize/ResizeKernelMap.cs | 2 +- .../General/BasicMath/ClampSpan.cs | 42 ++ .../BasicMath/ModuloPowerOfTwoConstant.cs | 2 +- .../BasicMath/ModuloPowerOfTwoVariable.cs | 2 +- .../Helpers/ImageMathsTests.cs | 138 ------ .../ImageSharp.Tests/Helpers/NumericsTests.cs | 265 +++++++++++ 28 files changed, 781 insertions(+), 317 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/Numerics.cs create mode 100644 tests/ImageSharp.Benchmarks/General/BasicMath/ClampSpan.cs create mode 100644 tests/ImageSharp.Tests/Helpers/NumericsTests.cs diff --git a/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs index 719565fd81..5cd89abfd6 100644 --- a/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// The representing the linear channel value. [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); /// /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs index 31c3f46330..34354efe54 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs +++ b/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); diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs index 7f15fc77d8..12c65105fc 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs +++ b/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)); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs index 4c3cdba224..f120d6f3dd 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs +++ b/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; diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index d24230fe18..55ac6f8227 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/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 { /// - /// Provides common mathematical methods. + /// Provides common mathematical methods used for image processing. /// internal static class ImageMaths { @@ -108,85 +107,6 @@ namespace SixLabors.ImageSharp [MethodImpl(InliningOptions.ShortMethod)] public static ushort UpscaleFrom8BitTo16Bit(byte component) => (ushort)(component * 257); - /// - /// Determine the Greatest CommonDivisor (GCD) of two numbers. - /// - public static int GreatestCommonDivisor(int a, int b) - { - while (b != 0) - { - int temp = b; - b = a % b; - a = temp; - } - - return a; - } - - /// - /// Determine the Least Common Multiple (LCM) of two numbers. - /// - 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; - } - - /// - /// Calculates % 2 - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static int Modulo2(int x) => x & 1; - - /// - /// Calculates % 4 - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static int Modulo4(int x) => x & 3; - - /// - /// Calculates % 8 - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static int Modulo8(int x) => x & 7; - - /// - /// Fast (x mod m) calculator, with the restriction that - /// should be power of 2. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static int ModuloP2(int x, int m) => x & (m - 1); - - /// - /// Returns the absolute value of a 32-bit signed integer. Uses bit shifting to speed up the operation. - /// - /// - /// A number that is greater than , but less than or equal to - /// - /// The - [MethodImpl(InliningOptions.ShortMethod)] - public static int FastAbs(int x) - { - int y = x >> 31; - return (x ^ y) - y; - } - - /// - /// Returns a specified number raised to the power of 2 - /// - /// A single-precision floating-point number - /// The number raised to the power of 2. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Pow2(float x) => x * x; - - /// - /// Returns a specified number raised to the power of 3 - /// - /// A single-precision floating-point number - /// The number raised to the power of 3. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Pow3(float x) => x * x * x; - /// /// 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; - /// - /// Implementation of 1D Gaussian G(x) function - /// - /// The x provided to G(x). - /// The spread of the blur. - /// The Gaussian G(x) - [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; - } - - /// - /// Returns the result of a normalized sine cardinal function for the given value. - /// SinC(x) = sin(pi*x)/(pi*x). - /// - /// A single-precision floating-point number to calculate the result for. - /// - /// The sine cardinal of . - /// - [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; - } - /// /// Gets the bounding from the given points. /// diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs new file mode 100644 index 0000000000..d4dfa12024 --- /dev/null +++ b/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 +{ + /// + /// Provides optimized static methods for trigonometric, logarithmic, + /// and other common mathematical functions. + /// + internal static class Numerics + { + /// + /// Determine the Greatest CommonDivisor (GCD) of two numbers. + /// + public static int GreatestCommonDivisor(int a, int b) + { + while (b != 0) + { + int temp = b; + b = a % b; + a = temp; + } + + return a; + } + + /// + /// Determine the Least Common Multiple (LCM) of two numbers. + /// + 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; + } + + /// + /// Calculates % 2 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Modulo2(int x) => x & 1; + + /// + /// Calculates % 4 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Modulo4(int x) => x & 3; + + /// + /// Calculates % 8 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Modulo8(int x) => x & 7; + + /// + /// Fast (x mod m) calculator, with the restriction that + /// should be power of 2. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ModuloP2(int x, int m) => x & (m - 1); + + /// + /// Returns the absolute value of a 32-bit signed integer. + /// Uses bit shifting to speed up the operation compared to . + /// + /// + /// A number that is greater than , but less than + /// or equal to + /// + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Abs(int x) + { + int y = x >> 31; + return (x ^ y) - y; + } + + /// + /// Returns a specified number raised to the power of 2 + /// + /// A single-precision floating-point number + /// The number raised to the power of 2. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Pow2(float x) => x * x; + + /// + /// Returns a specified number raised to the power of 3 + /// + /// A single-precision floating-point number + /// The number raised to the power of 3. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Pow3(float x) => x * x * x; + + /// + /// Implementation of 1D Gaussian G(x) function + /// + /// The x provided to G(x). + /// The spread of the blur. + /// The Gaussian G(x) + [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; + } + + /// + /// Returns the result of a normalized sine cardinal function for the given value. + /// SinC(x) = sin(pi*x)/(pi*x). + /// + /// A single-precision floating-point number to calculate the result for. + /// + /// The sine cardinal of . + /// + [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; + } + + /// + /// Returns the value clamped to the inclusive range of min and max. + /// + /// The value to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + /// The clamped . + [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; + } + + /// + /// Returns the value clamped to the inclusive range of min and max. + /// + /// The value to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + /// The clamped . + [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; + } + + /// + /// Returns the value clamped to the inclusive range of min and max. + /// + /// The value to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + /// The clamped . + [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; + } + + /// + /// Returns the value clamped to the inclusive range of min and max. + /// + /// The value to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + /// The clamped . + [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; + } + + /// + /// Returns the value clamped to the inclusive range of min and max. + /// + /// The value to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + /// The clamped . + [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; + } + + /// + /// Clamps the span values to the inclusive range of min and max. + /// + /// The span containing the values to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clamp(Span 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); + } + } + } + + /// + /// Clamps the span values to the inclusive range of min and max. + /// + /// The span containing the values to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clamp(Span 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); + } + } + } + + /// + /// Clamps the span values to the inclusive range of min and max. + /// + /// The span containing the values to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clamp(Span 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); + } + } + } + + /// + /// Clamps the span values to the inclusive range of min and max. + /// + /// The span containing the values to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clamp(Span 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); + } + } + } + + /// + /// Clamps the span values to the inclusive range of min and max. + /// + /// The span containing the values to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clamp(Span 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(Span span, T min, T max) + where T : unmanaged + { + if (Vector.IsHardwareAccelerated && span.Length >= Vector.Count) + { + int remainder = ModuloP2(span.Length, Vector.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(Span span, T min, T max) + where T : unmanaged + { + ref T sRef = ref MemoryMarshal.GetReference(span); + ref Vector vsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(span)); + var vmin = new Vector(min); + var vmax = new Vector(max); + + int n = span.Length / Vector.Count; + int m = Modulo4(n); + int u = n - m; + + for (int i = 0; i < u; i += 4) + { + ref Vector vs0 = ref Unsafe.Add(ref vsBase, i); + ref Vector vs1 = ref Unsafe.Add(ref vs0, 1); + ref Vector vs2 = ref Unsafe.Add(ref vs0, 2); + ref Vector 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 vs0 = ref Unsafe.Add(ref vsBase, i); + vs0 = Vector.Min(Vector.Max(vmin, vs0), vmax); + } + } + } + } +} diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs index 86e4174f11..3ecad3c5d9 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp ref Byte3 dBase = ref Unsafe.As(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); diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs index de6990db5b..75555f88a5 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs +++ b/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) diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs index bd35d1583e..0abc0e26da 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp return; } - int remainder = ImageMaths.ModuloP2(source.Length, Vector.Count); + int remainder = Numerics.ModuloP2(source.Length, Vector.Count); int adjustedCount = source.Length - remainder; if (adjustedCount > 0) @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp return; } - int remainder = ImageMaths.ModuloP2(source.Length, Vector.Count); + int remainder = Numerics.ModuloP2(source.Length, Vector.Count); int adjustedCount = source.Length - remainder; if (adjustedCount > 0) diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs index 1e89aaeb83..a97510113c 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs +++ b/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) diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs index 2ea7f2c9bd..b760301167 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ b/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.Count) - : ImageMaths.ModuloP2(source.Length, Vector128.Count); + ? Numerics.ModuloP2(source.Length, Vector256.Count) + : Numerics.ModuloP2(source.Length, Vector128.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.Count) - : ImageMaths.ModuloP2(source.Length, Vector128.Count); + ? Numerics.ModuloP2(source.Length, Vector256.Count) + : Numerics.ModuloP2(source.Length, Vector128.Count); int adjustedCount = source.Length - remainder; @@ -203,7 +203,7 @@ namespace SixLabors.ImageSharp ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); int n = dest.Length / Vector256.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>(ref MemoryMarshal.GetReference(dest)); int n = dest.Length / Vector128.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>(ref MemoryMarshal.GetReference(dest)); int n = dest.Length / Vector256.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>(ref MemoryMarshal.GetReference(dest)); int n = dest.Length / Vector128.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.Count); + remainder = Numerics.ModuloP2(source.Length, Vector256.Count); } else { - remainder = ImageMaths.ModuloP2(source.Length, Vector128.Count); + remainder = Numerics.ModuloP2(source.Length, Vector128.Count); } int adjustedCount = source.Length - remainder; @@ -683,11 +683,11 @@ namespace SixLabors.ImageSharp int remainder; if (Avx2.IsSupported) { - remainder = ImageMaths.ModuloP2(source.Length, Vector256.Count); + remainder = Numerics.ModuloP2(source.Length, Vector256.Count); } else { - remainder = ImageMaths.ModuloP2(source.Length, Vector128.Count); + remainder = Numerics.ModuloP2(source.Length, Vector128.Count); } int adjustedCount = source.Length - remainder; diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.cs b/src/ImageSharp/Common/Helpers/SimdUtils.cs index 7cbb5bfe35..7691cb9ad0 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.cs +++ b/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}!"); } diff --git a/src/ImageSharp/Common/Helpers/Vector4Utilities.cs b/src/ImageSharp/Common/Helpers/Vector4Utilities.cs index f617e9a3ea..464006570a 100644 --- a/src/ImageSharp/Common/Helpers/Vector4Utilities.cs +++ b/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))); diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index 8d9f6e4156..d1c214e3d6 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/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; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index 7b5c71a010..fab6788061 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/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; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index c448e71f43..cb4cfb471f 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/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; diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index 2a77bccb97..cf553cbb68 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/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; diff --git a/src/ImageSharp/Primitives/ComplexVector4.cs b/src/ImageSharp/Primitives/ComplexVector4.cs index 3a1d4ac460..07f074502f 100644 --- a/src/ImageSharp/Primitives/ComplexVector4.cs +++ b/src/ImageSharp/Primitives/ComplexVector4.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp /// /// The input to sum [MethodImpl(InliningOptions.ShortMethod)] - public void Sum(in ComplexVector4 value) + public void Sum(ComplexVector4 value) { this.Real += value.Real; this.Imaginary += value.Imaginary; diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs index 7b1ceff276..9844f99563 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs +++ b/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; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs index a61c68de3b..007a9a05de 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs +++ b/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++; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs index 7aefd8f6f1..8742db580a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs +++ b/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; diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs index 93c50af132..18859d1ada 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs +++ b/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; diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index 7cbda76a54..ab6040c17b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/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; diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampSpan.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampSpan.cs new file mode 100644 index 0000000000..a5b49721da --- /dev/null +++ b/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); + } + } +} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs index 55e26372b0..27ae787bca 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs +++ b/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); } } } diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs index 9da7b9fdf3..d336015a0a 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs +++ b/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: diff --git a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs index 7d16623877..656d8ce3b2 100644 --- a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs +++ b/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)] diff --git a/tests/ImageSharp.Tests/Helpers/NumericsTests.cs b/tests/ImageSharp.Tests/Helpers/NumericsTests.cs new file mode 100644 index 0000000000..f1678cfa1d --- /dev/null +++ b/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(Span 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( + int length, + T min, + T max, + SpanAction clampAction, + Func refClampFunc) + where T : unmanaged, IComparable + { + Span 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 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]); + } + } + } +}