diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index c8f3997946..b618e1e657 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -18,6 +18,10 @@ jobs:
framework: netcoreapp3.1
runtime: -x64
codecov: true
+ - os: macos-latest
+ framework: netcoreapp3.1
+ runtime: -x64
+ codecov: false
- os: windows-latest
framework: netcoreapp3.1
runtime: -x64
diff --git a/.gitignore b/.gitignore
index a89cfcf104..475d6e76b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -221,3 +221,4 @@ artifacts/
# Tests
**/Images/ActualOutput
**/Images/ReferenceOutput
+.DS_Store
diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs
index 554fcb8354..72f16528a6 100644
--- a/src/ImageSharp/Color/Color.cs
+++ b/src/ImageSharp/Color/Color.cs
@@ -27,19 +27,19 @@ namespace SixLabors.ImageSharp
private Color(byte r, byte g, byte b, byte a)
{
this.data = new Rgba64(
- ImageMaths.UpscaleFrom8BitTo16Bit(r),
- ImageMaths.UpscaleFrom8BitTo16Bit(g),
- ImageMaths.UpscaleFrom8BitTo16Bit(b),
- ImageMaths.UpscaleFrom8BitTo16Bit(a));
+ ColorNumerics.UpscaleFrom8BitTo16Bit(r),
+ ColorNumerics.UpscaleFrom8BitTo16Bit(g),
+ ColorNumerics.UpscaleFrom8BitTo16Bit(b),
+ ColorNumerics.UpscaleFrom8BitTo16Bit(a));
}
[MethodImpl(InliningOptions.ShortMethod)]
private Color(byte r, byte g, byte b)
{
this.data = new Rgba64(
- ImageMaths.UpscaleFrom8BitTo16Bit(r),
- ImageMaths.UpscaleFrom8BitTo16Bit(g),
- ImageMaths.UpscaleFrom8BitTo16Bit(b),
+ ColorNumerics.UpscaleFrom8BitTo16Bit(r),
+ ColorNumerics.UpscaleFrom8BitTo16Bit(g),
+ ColorNumerics.UpscaleFrom8BitTo16Bit(b),
ushort.MaxValue);
}
diff --git a/src/ImageSharp/ColorSpaces/Cmyk.cs b/src/ImageSharp/ColorSpaces/Cmyk.cs
index 0aab295548..675f1f814c 100644
--- a/src/ImageSharp/ColorSpaces/Cmyk.cs
+++ b/src/ImageSharp/ColorSpaces/Cmyk.cs
@@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
[MethodImpl(InliningOptions.ShortMethod)]
public Cmyk(Vector4 vector)
{
- vector = Vector4Utilities.FastClamp(vector, Min, Max);
+ vector = Numerics.Clamp(vector, Min, Max);
this.C = vector.X;
this.M = vector.Y;
this.Y = vector.Z;
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/Extensions/ComparableExtensions.cs b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs
deleted file mode 100644
index ef3d1deac3..0000000000
--- a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Runtime.CompilerServices;
-
-namespace SixLabors.ImageSharp
-{
- ///
- /// Extension methods for classes that implement .
- ///
- internal static class ComparableExtensions
- {
- ///
- /// Restricts a to be within a specified range.
- ///
- /// The value to clamp.
- /// The minimum value. If value is less than min, min will be returned.
- /// The maximum value. If value is greater than max, max will be returned.
- ///
- /// The representing the clamped value.
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public static byte Clamp(this 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;
- }
-
- ///
- /// Restricts a to be within a specified range.
- ///
- /// The The value to clamp.
- /// The minimum value. If value is less than min, min will be returned.
- /// The maximum value. If value is greater than max, max will be returned.
- ///
- /// The representing the clamped value.
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public static uint Clamp(this uint value, uint min, uint max)
- {
- if (value >= max)
- {
- return max;
- }
-
- if (value <= min)
- {
- return min;
- }
-
- return value;
- }
-
- ///
- /// Restricts a to be within a specified range.
- ///
- /// The The value to clamp.
- /// The minimum value. If value is less than min, min will be returned.
- /// The maximum value. If value is greater than max, max will be returned.
- ///
- /// The representing the clamped value.
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public static int Clamp(this int value, int min, int max)
- {
- if (value >= max)
- {
- return max;
- }
-
- if (value <= min)
- {
- return min;
- }
-
- return value;
- }
-
- ///
- /// Restricts a to be within a specified range.
- ///
- /// The The value to clamp.
- /// The minimum value. If value is less than min, min will be returned.
- /// The maximum value. If value is greater than max, max will be returned.
- ///
- /// The representing the clamped value.
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public static float Clamp(this float value, float min, float max)
- {
- if (value >= max)
- {
- return max;
- }
-
- if (value <= min)
- {
- return min;
- }
-
- return value;
- }
-
- ///
- /// Restricts a to be within a specified range.
- ///
- /// The The value to clamp.
- /// The minimum value. If value is less than min, min will be returned.
- /// The maximum value. If value is greater than max, max will be returned.
- ///
- /// The representing the clamped value.
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public static double Clamp(this double value, double min, double max)
- {
- if (value >= max)
- {
- return max;
- }
-
- if (value <= min)
- {
- return min;
- }
-
- return value;
- }
- }
-}
diff --git a/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs
index f4811d6ca8..02a5afff7e 100644
--- a/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs
+++ b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs
@@ -50,8 +50,8 @@ namespace SixLabors.ImageSharp
for (int i = 0; i < kernelLength; i++)
{
- int offsetY = (row + i - radiusY).Clamp(minRow, maxRow);
- int offsetX = sourceOffsetColumnBase.Clamp(minColumn, maxColumn);
+ int offsetY = Numerics.Clamp(row + i - radiusY, minRow, maxRow);
+ int offsetX = Numerics.Clamp(sourceOffsetColumnBase, minColumn, maxColumn);
Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
var currentColor = sourceRowSpan[offsetX].ToVector4();
@@ -93,13 +93,13 @@ namespace SixLabors.ImageSharp
int radiusX = kernelLength >> 1;
int sourceOffsetColumnBase = column + minColumn;
- int offsetY = row.Clamp(minRow, maxRow);
+ int offsetY = Numerics.Clamp(row, minRow, maxRow);
ref ComplexVector4 sourceRef = ref MemoryMarshal.GetReference(sourceValues.GetRowSpan(offsetY));
ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel);
for (int x = 0; x < kernelLength; x++)
{
- int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn);
+ int offsetX = Numerics.Clamp(sourceOffsetColumnBase + x - radiusX, minColumn, maxColumn);
vector.Sum(Unsafe.Add(ref baseRef, x) * Unsafe.Add(ref sourceRef, offsetX));
}
diff --git a/src/ImageSharp/Common/Helpers/ColorNumerics.cs b/src/ImageSharp/Common/Helpers/ColorNumerics.cs
new file mode 100644
index 0000000000..6f225b1109
--- /dev/null
+++ b/src/ImageSharp/Common/Helpers/ColorNumerics.cs
@@ -0,0 +1,177 @@
+// 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 common mathematical functions specific
+ /// to color processing.
+ ///
+ internal static class ColorNumerics
+ {
+ ///
+ /// Vector for converting pixel to gray value as specified by
+ /// ITU-R Recommendation BT.709.
+ ///
+ private static readonly Vector4 Bt709 = new Vector4(.2126f, .7152f, .0722f, 0.0f);
+
+ ///
+ /// Convert a pixel value to grayscale using ITU-R Recommendation BT.709.
+ ///
+ /// The vector to get the luminance from.
+ ///
+ /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images).
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetBT709Luminance(ref Vector4 vector, int luminanceLevels)
+ => (int)MathF.Round(Vector4.Dot(vector, Bt709) * (luminanceLevels - 1));
+
+ ///
+ /// Gets the luminance from the rgb components using the formula
+ /// as specified by ITU-R Recommendation BT.709.
+ ///
+ /// The red component.
+ /// The green component.
+ /// The blue component.
+ /// The .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte Get8BitBT709Luminance(byte r, byte g, byte b)
+ => (byte)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F);
+
+ ///
+ /// Gets the luminance from the rgb components using the formula as
+ /// specified by ITU-R Recommendation BT.709.
+ ///
+ /// The red component.
+ /// The green component.
+ /// The blue component.
+ /// The .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ushort Get16BitBT709Luminance(ushort r, ushort g, ushort b)
+ => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F);
+
+ ///
+ /// Gets the luminance from the rgb components using the formula as specified
+ /// by ITU-R Recommendation BT.709.
+ ///
+ /// The red component.
+ /// The green component.
+ /// The blue component.
+ /// The .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ushort Get16BitBT709Luminance(float r, float g, float b)
+ => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F);
+
+ ///
+ /// Scales a value from a 16 bit to an
+ /// 8 bit equivalent.
+ ///
+ /// The 8 bit component value.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte DownScaleFrom16BitTo8Bit(ushort component)
+ {
+ // To scale to 8 bits From a 16-bit value V the required value (from the PNG specification) is:
+ //
+ // (V * 255) / 65535
+ //
+ // This reduces to round(V / 257), or floor((V + 128.5)/257)
+ //
+ // Represent V as the two byte value vhi.vlo. Make a guess that the
+ // result is the top byte of V, vhi, then the correction to this value
+ // is:
+ //
+ // error = floor(((V-vhi.vhi) + 128.5) / 257)
+ // = floor(((vlo-vhi) + 128.5) / 257)
+ //
+ // This can be approximated using integer arithmetic (and a signed
+ // shift):
+ //
+ // error = (vlo-vhi+128) >> 8;
+ //
+ // The approximate differs from the exact answer only when (vlo-vhi) is
+ // 128; it then gives a correction of +1 when the exact correction is
+ // 0. This gives 128 errors. The exact answer (correct for all 16-bit
+ // input values) is:
+ //
+ // error = (vlo-vhi+128)*65535 >> 24;
+ //
+ // An alternative arithmetic calculation which also gives no errors is:
+ //
+ // (V * 255 + 32895) >> 16
+ return (byte)(((component * 255) + 32895) >> 16);
+ }
+
+ ///
+ /// Scales a value from an 8 bit to
+ /// an 16 bit equivalent.
+ ///
+ /// The 8 bit component value.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ushort UpscaleFrom8BitTo16Bit(byte component)
+ => (ushort)(component * 257);
+
+ ///
+ /// Returns how many bits are required to store the specified number of colors.
+ /// Performs a Log2() on the value.
+ ///
+ /// The number of colors.
+ ///
+ /// The
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetBitsNeededForColorDepth(int colors)
+ => Math.Max(1, (int)Math.Ceiling(Math.Log(colors, 2)));
+
+ ///
+ /// Returns how many colors will be created by the specified number of bits.
+ ///
+ /// The bit depth.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetColorCountForBitDepth(int bitDepth)
+ => 1 << bitDepth;
+
+ ///
+ /// Transforms a vector by the given color matrix.
+ ///
+ /// The source vector.
+ /// The transformation color matrix.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Transform(ref Vector4 vector, ref ColorMatrix matrix)
+ {
+ float x = vector.X;
+ float y = vector.Y;
+ float z = vector.Z;
+ float w = vector.W;
+
+ vector.X = (x * matrix.M11) + (y * matrix.M21) + (z * matrix.M31) + (w * matrix.M41) + matrix.M51;
+ vector.Y = (x * matrix.M12) + (y * matrix.M22) + (z * matrix.M32) + (w * matrix.M42) + matrix.M52;
+ vector.Z = (x * matrix.M13) + (y * matrix.M23) + (z * matrix.M33) + (w * matrix.M43) + matrix.M53;
+ vector.W = (x * matrix.M14) + (y * matrix.M24) + (z * matrix.M34) + (w * matrix.M44) + matrix.M54;
+ }
+
+ ///
+ /// Bulk variant of .
+ ///
+ /// The span of vectors
+ /// The transformation color matrix.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Transform(Span vectors, ref ColorMatrix matrix)
+ {
+ ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors);
+
+ for (int i = 0; i < vectors.Length; i++)
+ {
+ ref Vector4 v = ref Unsafe.Add(ref baseRef, i);
+ Transform(ref v, ref matrix);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
index 61f90e23e1..f265bdd517 100644
--- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
+++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
@@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
vector.W = target.W;
- Vector4Utilities.UnPremultiply(ref vector);
+ Numerics.UnPremultiply(ref vector);
target = vector;
}
@@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp
out Vector4 vector);
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
- Vector4Utilities.UnPremultiply(ref vector);
+ Numerics.UnPremultiply(ref vector);
target = vector;
}
@@ -133,14 +133,14 @@ namespace SixLabors.ImageSharp
for (int y = 0; y < matrixHeight; y++)
{
- int offsetY = (row + y - radiusY).Clamp(minRow, maxRow);
+ int offsetY = Numerics.Clamp(row + y - radiusY, minRow, maxRow);
Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
for (int x = 0; x < matrixWidth; x++)
{
- int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn);
+ int offsetX = Numerics.Clamp(sourceOffsetColumnBase + x - radiusX, minColumn, maxColumn);
var currentColor = sourceRowSpan[offsetX].ToVector4();
- Vector4Utilities.Premultiply(ref currentColor);
+ Numerics.Premultiply(ref currentColor);
vectorX += matrixX[y, x] * currentColor;
vectorY += matrixY[y, x] * currentColor;
@@ -193,7 +193,7 @@ namespace SixLabors.ImageSharp
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
vector.W = target.W;
- Vector4Utilities.UnPremultiply(ref vector);
+ Numerics.UnPremultiply(ref vector);
target = vector;
}
@@ -238,7 +238,7 @@ namespace SixLabors.ImageSharp
ref vector);
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
- Vector4Utilities.UnPremultiply(ref vector);
+ Numerics.UnPremultiply(ref vector);
target = vector;
}
@@ -263,14 +263,14 @@ namespace SixLabors.ImageSharp
for (int y = 0; y < matrixHeight; y++)
{
- int offsetY = (row + y - radiusY).Clamp(minRow, maxRow);
+ int offsetY = Numerics.Clamp(row + y - radiusY, minRow, maxRow);
Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
for (int x = 0; x < matrixWidth; x++)
{
- int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn);
+ int offsetX = Numerics.Clamp(sourceOffsetColumnBase + x - radiusX, minColumn, maxColumn);
var currentColor = sourceRowSpan[offsetX].ToVector4();
- Vector4Utilities.Premultiply(ref currentColor);
+ Numerics.Premultiply(ref currentColor);
vector += matrix[y, x] * currentColor;
}
}
diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs
deleted file mode 100644
index d24230fe18..0000000000
--- a/src/ImageSharp/Common/Helpers/ImageMaths.cs
+++ /dev/null
@@ -1,379 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Numerics;
-using System.Runtime.CompilerServices;
-
-using SixLabors.ImageSharp.PixelFormats;
-
-namespace SixLabors.ImageSharp
-{
- ///
- /// Provides common mathematical methods.
- ///
- internal static class ImageMaths
- {
- ///
- /// Vector for converting pixel to gray value as specified by ITU-R Recommendation BT.709.
- ///
- private static readonly Vector4 Bt709 = new Vector4(.2126f, .7152f, .0722f, 0.0f);
-
- ///
- /// Convert a pixel value to grayscale using ITU-R Recommendation BT.709.
- ///
- /// The vector to get the luminance from.
- /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images)
- [MethodImpl(InliningOptions.ShortMethod)]
- public static int GetBT709Luminance(ref Vector4 vector, int luminanceLevels)
- => (int)MathF.Round(Vector4.Dot(vector, Bt709) * (luminanceLevels - 1));
-
- ///
- /// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709.
- ///
- /// The red component.
- /// The green component.
- /// The blue component.
- /// The .
- [MethodImpl(InliningOptions.ShortMethod)]
- public static byte Get8BitBT709Luminance(byte r, byte g, byte b) =>
- (byte)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F);
-
- ///
- /// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709.
- ///
- /// The red component.
- /// The green component.
- /// The blue component.
- /// The .
- [MethodImpl(InliningOptions.ShortMethod)]
- public static ushort Get16BitBT709Luminance(ushort r, ushort g, ushort b) =>
- (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F);
-
- ///
- /// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709.
- ///
- /// The red component.
- /// The green component.
- /// The blue component.
- /// The .
- [MethodImpl(InliningOptions.ShortMethod)]
- public static ushort Get16BitBT709Luminance(float r, float g, float b) =>
- (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F);
-
- ///
- /// Scales a value from a 16 bit to it's 8 bit equivalent.
- ///
- /// The 8 bit component value.
- /// The
- [MethodImpl(InliningOptions.ShortMethod)]
- public static byte DownScaleFrom16BitTo8Bit(ushort component)
- {
- // To scale to 8 bits From a 16-bit value V the required value (from the PNG specification) is:
- //
- // (V * 255) / 65535
- //
- // This reduces to round(V / 257), or floor((V + 128.5)/257)
- //
- // Represent V as the two byte value vhi.vlo. Make a guess that the
- // result is the top byte of V, vhi, then the correction to this value
- // is:
- //
- // error = floor(((V-vhi.vhi) + 128.5) / 257)
- // = floor(((vlo-vhi) + 128.5) / 257)
- //
- // This can be approximated using integer arithmetic (and a signed
- // shift):
- //
- // error = (vlo-vhi+128) >> 8;
- //
- // The approximate differs from the exact answer only when (vlo-vhi) is
- // 128; it then gives a correction of +1 when the exact correction is
- // 0. This gives 128 errors. The exact answer (correct for all 16-bit
- // input values) is:
- //
- // error = (vlo-vhi+128)*65535 >> 24;
- //
- // An alternative arithmetic calculation which also gives no errors is:
- //
- // (V * 255 + 32895) >> 16
- return (byte)(((component * 255) + 32895) >> 16);
- }
-
- ///
- /// Scales a value from an 8 bit to it's 16 bit equivalent.
- ///
- /// The 8 bit component value.
- /// The
- [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.
- ///
- /// The number of colors.
- ///
- /// The
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public static int GetBitsNeededForColorDepth(int colors) => Math.Max(1, (int)Math.Ceiling(Math.Log(colors, 2)));
-
- ///
- /// Returns how many colors will be created by the specified number of bits.
- ///
- /// The bit depth.
- /// The
- [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.
- ///
- ///
- /// The designating the top left position.
- ///
- ///
- /// The designating the bottom right position.
- ///
- ///
- /// The bounding .
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) => new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);
-
- ///
- /// Finds the bounding rectangle based on the first instance of any color component other
- /// than the given one.
- ///
- /// The pixel format.
- /// The to search within.
- /// The color component value to remove.
- /// The channel to test against.
- ///
- /// The .
- ///
- public static Rectangle GetFilteredBoundingRectangle(ImageFrame bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B)
- where TPixel : unmanaged, IPixel
- {
- int width = bitmap.Width;
- int height = bitmap.Height;
- Point topLeft = default;
- Point bottomRight = default;
-
- Func, int, int, float, bool> delegateFunc;
-
- // Determine which channel to check against
- switch (channel)
- {
- case RgbaComponent.R:
- delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().X - b) > Constants.Epsilon;
- break;
-
- case RgbaComponent.G:
- delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Y - b) > Constants.Epsilon;
- break;
-
- case RgbaComponent.B:
- delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Z - b) > Constants.Epsilon;
- break;
-
- default:
- delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().W - b) > Constants.Epsilon;
- break;
- }
-
- int GetMinY(ImageFrame pixels)
- {
- for (int y = 0; y < height; y++)
- {
- for (int x = 0; x < width; x++)
- {
- if (delegateFunc(pixels, x, y, componentValue))
- {
- return y;
- }
- }
- }
-
- return 0;
- }
-
- int GetMaxY(ImageFrame pixels)
- {
- for (int y = height - 1; y > -1; y--)
- {
- for (int x = 0; x < width; x++)
- {
- if (delegateFunc(pixels, x, y, componentValue))
- {
- return y;
- }
- }
- }
-
- return height;
- }
-
- int GetMinX(ImageFrame pixels)
- {
- for (int x = 0; x < width; x++)
- {
- for (int y = 0; y < height; y++)
- {
- if (delegateFunc(pixels, x, y, componentValue))
- {
- return x;
- }
- }
- }
-
- return 0;
- }
-
- int GetMaxX(ImageFrame pixels)
- {
- for (int x = width - 1; x > -1; x--)
- {
- for (int y = 0; y < height; y++)
- {
- if (delegateFunc(pixels, x, y, componentValue))
- {
- return x;
- }
- }
- }
-
- return width;
- }
-
- topLeft.Y = GetMinY(bitmap);
- topLeft.X = GetMinX(bitmap);
- bottomRight.Y = (GetMaxY(bitmap) + 1).Clamp(0, height);
- bottomRight.X = (GetMaxX(bitmap) + 1).Clamp(0, width);
-
- return GetBoundingRectangle(topLeft, bottomRight);
- }
- }
-}
diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs
new file mode 100644
index 0000000000..b2bedb87b4
--- /dev/null
+++ b/src/ImageSharp/Common/Helpers/Numerics.cs
@@ -0,0 +1,551 @@
+// 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;
+#if SUPPORTS_RUNTIME_INTRINSICS
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
+
+namespace SixLabors.ImageSharp
+{
+ ///
+ /// Provides optimized static methods for trigonometric, logarithmic,
+ /// and other common mathematical functions.
+ ///
+ internal static class Numerics
+ {
+#if SUPPORTS_RUNTIME_INTRINSICS
+ private const int BlendAlphaControl = 0b_10_00_10_00;
+ private const int ShuffleAlphaControl = 0b_11_11_11_11;
+#endif
+
+ ///
+ /// Determine the Greatest CommonDivisor (GCD) of two numbers.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ 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.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ 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;
+ }
+
+ ///
+ /// Returns the value clamped to the inclusive range of min and max.
+ /// 5x Faster than
+ /// on platforms < NET 5.
+ ///
+ /// The value to clamp.
+ /// The minimum inclusive value.
+ /// The maximum inclusive value.
+ /// The clamped .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector4 Clamp(Vector4 value, Vector4 min, Vector4 max)
+ => Vector4.Min(Vector4.Max(value, 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, byte min, byte max)
+ {
+ Span remainder = span.Slice(ClampReduce(span, min, max));
+
+ if (remainder.Length > 0)
+ {
+ for (int i = 0; i < remainder.Length; i++)
+ {
+ ref byte v = ref remainder[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)
+ {
+ Span remainder = span.Slice(ClampReduce(span, min, max));
+
+ if (remainder.Length > 0)
+ {
+ for (int i = 0; i < remainder.Length; i++)
+ {
+ ref uint v = ref remainder[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)
+ {
+ Span remainder = span.Slice(ClampReduce(span, min, max));
+
+ if (remainder.Length > 0)
+ {
+ for (int i = 0; i < remainder.Length; i++)
+ {
+ ref int v = ref remainder[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)
+ {
+ Span remainder = span.Slice(ClampReduce(span, min, max));
+
+ if (remainder.Length > 0)
+ {
+ for (int i = 0; i < remainder.Length; i++)
+ {
+ ref float v = ref remainder[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)
+ {
+ Span remainder = span.Slice(ClampReduce(span, min, max));
+
+ if (remainder.Length > 0)
+ {
+ for (int i = 0; i < remainder.Length; i++)
+ {
+ ref double v = ref remainder[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);
+ }
+ }
+ }
+
+ ///
+ /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact.
+ ///
+ /// The to premultiply
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Premultiply(ref Vector4 source)
+ {
+ float w = source.W;
+ source *= w;
+ source.W = w;
+ }
+
+ ///
+ /// Reverses the result of premultiplying a vector via .
+ ///
+ /// The to premultiply
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void UnPremultiply(ref Vector4 source)
+ {
+ float w = source.W;
+ source /= w;
+ source.W = w;
+ }
+
+ ///
+ /// Bulk variant of
+ ///
+ /// The span of vectors
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Premultiply(Span vectors)
+ {
+#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Avx2.IsSupported && vectors.Length >= 2)
+ {
+ ref Vector256 vectorsBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors));
+
+ // Divide by 2 as 4 elements per Vector4 and 8 per Vector256
+ ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (IntPtr)((uint)vectors.Length / 2u));
+
+ while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast))
+ {
+ Vector256 source = vectorsBase;
+ Vector256 multiply = Avx.Shuffle(source, source, ShuffleAlphaControl);
+ vectorsBase = Avx.Blend(Avx.Multiply(source, multiply), source, BlendAlphaControl);
+ vectorsBase = ref Unsafe.Add(ref vectorsBase, 1);
+ }
+
+ if (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)));
+ }
+ }
+ else
+#endif
+ {
+ ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors);
+
+ for (int i = 0; i < vectors.Length; i++)
+ {
+ ref Vector4 v = ref Unsafe.Add(ref baseRef, i);
+ Premultiply(ref v);
+ }
+ }
+ }
+
+ ///
+ /// Bulk variant of
+ ///
+ /// The span of vectors
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void UnPremultiply(Span vectors)
+ {
+#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Avx2.IsSupported && vectors.Length >= 2)
+ {
+ ref Vector256 vectorsBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors));
+
+ // Divide by 2 as 4 elements per Vector4 and 8 per Vector256
+ ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (IntPtr)((uint)vectors.Length / 2u));
+
+ while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast))
+ {
+ Vector256 source = vectorsBase;
+ Vector256 multiply = Avx.Shuffle(source, source, ShuffleAlphaControl);
+ vectorsBase = Avx.Blend(Avx.Divide(source, multiply), source, BlendAlphaControl);
+ vectorsBase = ref Unsafe.Add(ref vectorsBase, 1);
+ }
+
+ if (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)));
+ }
+ }
+ else
+#endif
+ {
+ ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors);
+
+ for (int i = 0; i < vectors.Length; i++)
+ {
+ ref Vector4 v = ref Unsafe.Add(ref baseRef, i);
+ UnPremultiply(ref v);
+ }
+ }
+ }
+ }
+}
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..15133770f6 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)
@@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp
Vector4 s = Unsafe.Add(ref sBase, i);
s *= maxBytes;
s += half;
- s = Vector4Utilities.FastClamp(s, Vector4.Zero, maxBytes);
+ s = Numerics.Clamp(s, Vector4.Zero, maxBytes);
ref ByteVector4 d = ref Unsafe.Add(ref dBase, i);
d.X = (byte)s.X;
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 df533cedf1..aaf6d405cf 100644
--- a/src/ImageSharp/Common/Helpers/SimdUtils.cs
+++ b/src/ImageSharp/Common/Helpers/SimdUtils.cs
@@ -25,6 +25,25 @@ namespace SixLabors.ImageSharp
public static bool HasVector8 { get; } =
Vector.IsHardwareAccelerated && Vector.Count == 8 && Vector.Count == 8;
+ ///
+ /// Gets a value indicating whether code is being JIT-ed to SSE instructions
+ /// where float and integer registers are of size 128 byte.
+ ///
+ public static bool HasVector4 { get; } =
+ Vector.IsHardwareAccelerated && Vector.Count == 4;
+
+ public static bool HasAvx2
+ {
+ get
+ {
+#if SUPPORTS_RUNTIME_INTRINSICS
+ return Avx2.IsSupported;
+#else
+ return false;
+#endif
+ }
+ }
+
///
/// Transform all scalars in 'v' in a way that converting them to would have rounding semantics.
///
@@ -32,7 +51,7 @@ namespace SixLabors.ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Vector4 PseudoRound(this Vector4 v)
{
- Vector4 sign = Vector4Utilities.FastClamp(v, new Vector4(-1), new Vector4(1));
+ Vector4 sign = Numerics.Clamp(v, new Vector4(-1), new Vector4(1));
return v + (sign * 0.5f);
}
@@ -171,7 +190,7 @@ namespace SixLabors.ImageSharp
}
[MethodImpl(InliningOptions.ShortMethod)]
- private static byte ConvertToByte(float f) => (byte)ComparableExtensions.Clamp((f * 255f) + 0.5f, 0, 255f);
+ private static byte ConvertToByte(float f) => (byte)Numerics.Clamp((f * 255F) + 0.5F, 0, 255F);
[Conditional("DEBUG")]
private static void VerifyHasVector8(string operation)
@@ -187,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}!");
}
@@ -197,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
deleted file mode 100644
index f617e9a3ea..0000000000
--- a/src/ImageSharp/Common/Helpers/Vector4Utilities.cs
+++ /dev/null
@@ -1,181 +0,0 @@
-// 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;
-#if SUPPORTS_RUNTIME_INTRINSICS
-using System.Runtime.Intrinsics;
-using System.Runtime.Intrinsics.X86;
-#endif
-
-namespace SixLabors.ImageSharp
-{
- ///
- /// Utility methods for the struct.
- ///
- internal static class Vector4Utilities
- {
- private const int BlendAlphaControl = 0b_10_00_10_00;
- private const int ShuffleAlphaControl = 0b_11_11_11_11;
-
- ///
- /// Restricts a vector between a minimum and a maximum value.
- /// 5x Faster then .
- ///
- /// The vector to restrict.
- /// The minimum value.
- /// The maximum value.
- /// The .
- [MethodImpl(InliningOptions.ShortMethod)]
- public static Vector4 FastClamp(Vector4 x, Vector4 min, Vector4 max)
- => Vector4.Min(Vector4.Max(x, min), max);
-
- ///
- /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact.
- ///
- /// The to premultiply
- [MethodImpl(InliningOptions.ShortMethod)]
- public static void Premultiply(ref Vector4 source)
- {
- float w = source.W;
- source *= w;
- source.W = w;
- }
-
- ///
- /// Reverses the result of premultiplying a vector via .
- ///
- /// The to premultiply
- [MethodImpl(InliningOptions.ShortMethod)]
- public static void UnPremultiply(ref Vector4 source)
- {
- float w = source.W;
- source /= w;
- source.W = w;
- }
-
- ///
- /// Bulk variant of
- ///
- /// The span of vectors
- [MethodImpl(InliningOptions.ShortMethod)]
- public static void Premultiply(Span vectors)
- {
-#if SUPPORTS_RUNTIME_INTRINSICS
- if (Avx2.IsSupported && vectors.Length >= 2)
- {
- ref Vector256 vectorsBase =
- ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors));
-
- // Divide by 2 as 4 elements per Vector4 and 8 per Vector256
- ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (IntPtr)((uint)vectors.Length / 2u));
-
- while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast))
- {
- Vector256 source = vectorsBase;
- Vector256 multiply = Avx.Shuffle(source, source, ShuffleAlphaControl);
- vectorsBase = Avx.Blend(Avx.Multiply(source, multiply), source, BlendAlphaControl);
- vectorsBase = ref Unsafe.Add(ref vectorsBase, 1);
- }
-
- if (ImageMaths.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)));
- }
- }
- else
-#endif
- {
- ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors);
-
- for (int i = 0; i < vectors.Length; i++)
- {
- ref Vector4 v = ref Unsafe.Add(ref baseRef, i);
- Premultiply(ref v);
- }
- }
- }
-
- ///
- /// Bulk variant of
- ///
- /// The span of vectors
- [MethodImpl(InliningOptions.ShortMethod)]
- public static void UnPremultiply(Span vectors)
- {
-#if SUPPORTS_RUNTIME_INTRINSICS
- if (Avx2.IsSupported && vectors.Length >= 2)
- {
- ref Vector256 vectorsBase =
- ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors));
-
- // Divide by 2 as 4 elements per Vector4 and 8 per Vector256
- ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (IntPtr)((uint)vectors.Length / 2u));
-
- while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast))
- {
- Vector256 source = vectorsBase;
- Vector256 multiply = Avx.Shuffle(source, source, ShuffleAlphaControl);
- vectorsBase = Avx.Blend(Avx.Divide(source, multiply), source, BlendAlphaControl);
- vectorsBase = ref Unsafe.Add(ref vectorsBase, 1);
- }
-
- if (ImageMaths.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)));
- }
- }
- else
-#endif
- {
- ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors);
-
- for (int i = 0; i < vectors.Length; i++)
- {
- ref Vector4 v = ref Unsafe.Add(ref baseRef, i);
- UnPremultiply(ref v);
- }
- }
- }
-
- ///
- /// Transforms a vector by the given matrix.
- ///
- /// The source vector.
- /// The transformation matrix.
- [MethodImpl(InliningOptions.ShortMethod)]
- public static void Transform(ref Vector4 vector, ref ColorMatrix matrix)
- {
- float x = vector.X;
- float y = vector.Y;
- float z = vector.Z;
- float w = vector.W;
-
- vector.X = (x * matrix.M11) + (y * matrix.M21) + (z * matrix.M31) + (w * matrix.M41) + matrix.M51;
- vector.Y = (x * matrix.M12) + (y * matrix.M22) + (z * matrix.M32) + (w * matrix.M42) + matrix.M52;
- vector.Z = (x * matrix.M13) + (y * matrix.M23) + (z * matrix.M33) + (w * matrix.M43) + matrix.M53;
- vector.W = (x * matrix.M14) + (y * matrix.M24) + (z * matrix.M34) + (w * matrix.M44) + matrix.M54;
- }
-
- ///
- /// Bulk variant of .
- ///
- /// The span of vectors
- /// The transformation matrix.
- [MethodImpl(InliningOptions.ShortMethod)]
- public static void Transform(Span vectors, ref ColorMatrix matrix)
- {
- ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors);
-
- for (int i = 0; i < vectors.Length; i++)
- {
- ref Vector4 v = ref Unsafe.Add(ref baseRef, i);
- Transform(ref v, ref matrix);
- }
- }
- }
-}
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 6f92236372..0be0385725 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -1385,7 +1385,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
case BmpFileMarkerType.Bitmap:
colorMapSizeBytes = this.fileHeader.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize;
- int colorCountForBitDepth = ImageMaths.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel);
+ int colorCountForBitDepth = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel);
bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth;
// Edge case for less-than-full-sized palette: bytesPerColorMapEntry should be at least 3.
@@ -1399,7 +1399,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
case BmpFileMarkerType.Pointer:
// OS/2 bitmaps always have 3 colors per color palette entry.
bytesPerColorMapEntry = 3;
- colorMapSizeBytes = ImageMaths.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel) * bytesPerColorMapEntry;
+ colorMapSizeBytes = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel) * bytesPerColorMapEntry;
break;
}
}
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
index 454440f634..01bdbd1c0b 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
@@ -6,7 +6,6 @@ using System.Buffers;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
-using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory;
@@ -342,20 +341,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
ReadOnlySpan quantizedColors = quantized.Palette.Span;
- var color = default(Rgba32);
-
- // TODO: Use bulk conversion here for better perf
- int idx = 0;
- foreach (TPixel quantizedColor in quantizedColors)
+ PixelOperations.Instance.ToBgra32(this.configuration, quantizedColors, MemoryMarshal.Cast(colorPalette));
+ Span colorPaletteAsUInt = MemoryMarshal.Cast(colorPalette);
+ for (int i = 0; i < colorPaletteAsUInt.Length; i++)
{
- quantizedColor.ToRgba32(ref color);
- colorPalette[idx] = color.B;
- colorPalette[idx + 1] = color.G;
- colorPalette[idx + 2] = color.R;
-
- // Padding byte, always 0.
- colorPalette[idx + 3] = 0;
- idx += 4;
+ colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0.
}
stream.Write(colorPalette);
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index 070864e603..9c1e95285c 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
// Get the number of bits.
- this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length);
+ this.bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length);
// Write the header.
this.WriteHeader(stream);
@@ -212,7 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
}
- this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length);
+ this.bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length);
this.WriteGraphicalControlExtension(frameMetadata, this.GetTransparentIndex(quantized), stream);
this.WriteImageDescriptor(frame, true, stream);
this.WriteColorTable(quantized, stream);
@@ -468,7 +468,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
where TPixel : unmanaged, IPixel
{
// The maximum number of colors for the bit depth
- int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * Unsafe.SizeOf();
+ int colorTableLength = ColorNumerics.GetColorCountForBitDepth(this.bitDepth) * Unsafe.SizeOf();
using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength, AllocationOptions.Clean);
PixelOperations.Instance.ToRgb24Bytes(
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
index 0efefc06b5..dd5d3f1960 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
@@ -19,22 +19,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
var CMax4 = new Vector4(maximum);
var COff4 = new Vector4(MathF.Ceiling(maximum / 2));
- this.V0L = Vector4Utilities.FastClamp(this.V0L + COff4, CMin4, CMax4);
- this.V0R = Vector4Utilities.FastClamp(this.V0R + COff4, CMin4, CMax4);
- this.V1L = Vector4Utilities.FastClamp(this.V1L + COff4, CMin4, CMax4);
- this.V1R = Vector4Utilities.FastClamp(this.V1R + COff4, CMin4, CMax4);
- this.V2L = Vector4Utilities.FastClamp(this.V2L + COff4, CMin4, CMax4);
- this.V2R = Vector4Utilities.FastClamp(this.V2R + COff4, CMin4, CMax4);
- this.V3L = Vector4Utilities.FastClamp(this.V3L + COff4, CMin4, CMax4);
- this.V3R = Vector4Utilities.FastClamp(this.V3R + COff4, CMin4, CMax4);
- this.V4L = Vector4Utilities.FastClamp(this.V4L + COff4, CMin4, CMax4);
- this.V4R = Vector4Utilities.FastClamp(this.V4R + COff4, CMin4, CMax4);
- this.V5L = Vector4Utilities.FastClamp(this.V5L + COff4, CMin4, CMax4);
- this.V5R = Vector4Utilities.FastClamp(this.V5R + COff4, CMin4, CMax4);
- this.V6L = Vector4Utilities.FastClamp(this.V6L + COff4, CMin4, CMax4);
- this.V6R = Vector4Utilities.FastClamp(this.V6R + COff4, CMin4, CMax4);
- this.V7L = Vector4Utilities.FastClamp(this.V7L + COff4, CMin4, CMax4);
- this.V7R = Vector4Utilities.FastClamp(this.V7R + COff4, CMin4, CMax4);
+ this.V0L = Numerics.Clamp(this.V0L + COff4, CMin4, CMax4);
+ this.V0R = Numerics.Clamp(this.V0R + COff4, CMin4, CMax4);
+ this.V1L = Numerics.Clamp(this.V1L + COff4, CMin4, CMax4);
+ this.V1R = Numerics.Clamp(this.V1R + COff4, CMin4, CMax4);
+ this.V2L = Numerics.Clamp(this.V2L + COff4, CMin4, CMax4);
+ this.V2R = Numerics.Clamp(this.V2R + COff4, CMin4, CMax4);
+ this.V3L = Numerics.Clamp(this.V3L + COff4, CMin4, CMax4);
+ this.V3R = Numerics.Clamp(this.V3R + COff4, CMin4, CMax4);
+ this.V4L = Numerics.Clamp(this.V4L + COff4, CMin4, CMax4);
+ this.V4R = Numerics.Clamp(this.V4R + COff4, CMin4, CMax4);
+ this.V5L = Numerics.Clamp(this.V5L + COff4, CMin4, CMax4);
+ this.V5R = Numerics.Clamp(this.V5R + COff4, CMin4, CMax4);
+ this.V6L = Numerics.Clamp(this.V6L + COff4, CMin4, CMax4);
+ this.V6R = Numerics.Clamp(this.V6R + COff4, CMin4, CMax4);
+ this.V7L = Numerics.Clamp(this.V7L + COff4, CMin4, CMax4);
+ this.V7R = Numerics.Clamp(this.V7R + COff4, CMin4, CMax4);
}
///
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt
index e5a62dc075..8897efbe00 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt
+++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt
@@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
for (int j = 0; j < 2; j++)
{
char side = j == 0 ? 'L' : 'R';
- Write($"this.V{i}{side} = Vector4Utilities.FastClamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n");
+ Write($"this.V{i}{side} = Numerics.Clamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n");
}
}
PopIndent();
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
index 0dbdadbeb4..fd4748fa9d 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
@@ -671,7 +671,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor)
{
// sign(dividend) = max(min(dividend, 1), -1)
- Vector4 sign = Vector4Utilities.FastClamp(dividend, NegativeOne, Vector4.One);
+ Vector4 sign = Numerics.Clamp(dividend, NegativeOne, Vector4.One);
// AlmostRound(dividend/divisor) = dividend/divisor + 0.5*sign(dividend)
return (dividend / divisor) + (sign * Offset);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Avx2JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Avx2JpegColorConverter.cs
new file mode 100644
index 0000000000..90ebce3b87
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Avx2JpegColorConverter.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
+{
+ internal abstract partial class JpegColorConverter
+ {
+ internal abstract class Avx2JpegColorConverter : VectorizedJpegColorConverter
+ {
+ protected Avx2JpegColorConverter(JpegColorSpace colorSpace, int precision)
+ : base(colorSpace, precision, 8)
+ {
+ }
+
+ protected sealed override bool IsAvailable => SimdUtils.HasAvx2;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.BasicJpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.BasicJpegColorConverter.cs
new file mode 100644
index 0000000000..ed2e2cd762
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.BasicJpegColorConverter.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
+{
+ internal abstract partial class JpegColorConverter
+ {
+ internal abstract class BasicJpegColorConverter : JpegColorConverter
+ {
+ protected BasicJpegColorConverter(JpegColorSpace colorSpace, int precision)
+ : base(colorSpace, precision)
+ {
+ }
+
+ protected override bool IsAvailable => true;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs
new file mode 100644
index 0000000000..f9334de73b
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs
@@ -0,0 +1,81 @@
+// 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;
+#if SUPPORTS_RUNTIME_INTRINSICS
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+using static SixLabors.ImageSharp.SimdUtils;
+#endif
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
+{
+ internal abstract partial class JpegColorConverter
+ {
+ internal sealed class FromCmykAvx2 : Avx2JpegColorConverter
+ {
+ public FromCmykAvx2(int precision)
+ : base(JpegColorSpace.Cmyk, precision)
+ {
+ }
+
+ protected override void ConvertCoreVectorized(in ComponentValues values, Span result)
+ {
+#if SUPPORTS_RUNTIME_INTRINSICS
+ ref Vector256 cBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
+ ref Vector256 mBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1));
+ ref Vector256 yBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2));
+ ref Vector256 kBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3));
+
+ ref Vector256 resultBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(result));
+
+ // Used for the color conversion
+ var scale = Vector256.Create(1 / this.MaximumValue);
+ var one = Vector256.Create(1F);
+
+ // Used for packing
+ ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
+ Vector256 vcontrol = Unsafe.As>(ref control);
+
+ int n = result.Length / 8;
+ for (int i = 0; i < n; i++)
+ {
+ Vector256 k = Avx2.PermuteVar8x32(Unsafe.Add(ref kBase, i), vcontrol);
+ Vector256 c = Avx2.PermuteVar8x32(Unsafe.Add(ref cBase, i), vcontrol);
+ Vector256 m = Avx2.PermuteVar8x32(Unsafe.Add(ref mBase, i), vcontrol);
+ Vector256 y = Avx2.PermuteVar8x32(Unsafe.Add(ref yBase, i), vcontrol);
+
+ k = Avx.Multiply(k, scale);
+
+ c = Avx.Multiply(Avx.Multiply(c, k), scale);
+ m = Avx.Multiply(Avx.Multiply(m, k), scale);
+ y = Avx.Multiply(Avx.Multiply(y, k), scale);
+
+ Vector256 cmLo = Avx.UnpackLow(c, m);
+ Vector256 yoLo = Avx.UnpackLow(y, one);
+ Vector256 cmHi = Avx.UnpackHigh(c, m);
+ Vector256 yoHi = Avx.UnpackHigh(y, one);
+
+ ref Vector256 destination = ref Unsafe.Add(ref resultBase, i * 4);
+
+ destination = Avx.Shuffle(cmLo, yoLo, 0b01_00_01_00);
+ Unsafe.Add(ref destination, 1) = Avx.Shuffle(cmLo, yoLo, 0b11_10_11_10);
+ Unsafe.Add(ref destination, 2) = Avx.Shuffle(cmHi, yoHi, 0b01_00_01_00);
+ Unsafe.Add(ref destination, 3) = Avx.Shuffle(cmHi, yoHi, 0b11_10_11_10);
+ }
+#endif
+ }
+
+ protected override void ConvertCore(in ComponentValues values, Span result) =>
+ FromCmykBasic.ConvertCore(values, result, this.MaximumValue);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs
similarity index 75%
rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs
rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs
index 7b257b37da..6cbd52ec3d 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs
@@ -8,16 +8,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
- internal sealed class FromCmyk : JpegColorConverter
+ internal sealed class FromCmykBasic : BasicJpegColorConverter
{
- public FromCmyk(int precision)
+ public FromCmykBasic(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
public override void ConvertToRgba(in ComponentValues values, Span result)
{
- // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()!
+ ConvertCore(values, result, this.MaximumValue);
+ }
+
+ internal static void ConvertCore(in ComponentValues values, Span result, float maxValue)
+ {
ReadOnlySpan cVals = values.Component0;
ReadOnlySpan mVals = values.Component1;
ReadOnlySpan yVals = values.Component2;
@@ -25,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
var v = new Vector4(0, 0, 0, 1F);
- var maximum = 1 / this.MaximumValue;
+ var maximum = 1 / maxValue;
var scale = new Vector4(maximum, maximum, maximum, 1F);
for (int i = 0; i < result.Length; i++)
@@ -33,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
float c = cVals[i];
float m = mVals[i];
float y = yVals[i];
- float k = kVals[i] / this.MaximumValue;
+ float k = kVals[i] / maxValue;
v.X = c * k;
v.Y = m * k;
@@ -47,4 +51,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs
new file mode 100644
index 0000000000..e75634b0fa
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs
@@ -0,0 +1,71 @@
+// 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;
+using SixLabors.ImageSharp.Tuples;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
+{
+ internal abstract partial class JpegColorConverter
+ {
+ internal sealed class FromCmykVector8 : Vector8JpegColorConverter
+ {
+ public FromCmykVector8(int precision)
+ : base(JpegColorSpace.Cmyk, precision)
+ {
+ }
+
+ protected override void ConvertCoreVectorized(in ComponentValues values, Span result)
+ {
+ ref Vector cBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
+ ref Vector mBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1));
+ ref Vector yBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2));
+ ref Vector kBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3));
+
+ ref Vector4Octet resultBase =
+ ref Unsafe.As(ref MemoryMarshal.GetReference(result));
+
+ Vector4Pair cc = default;
+ Vector4Pair mm = default;
+ Vector4Pair yy = default;
+ ref Vector ccRefAsVector = ref Unsafe.As>(ref cc);
+ ref Vector mmRefAsVector = ref Unsafe.As>(ref mm);
+ ref Vector yyRefAsVector = ref Unsafe.As>(ref yy);
+
+ var scale = new Vector(1 / this.MaximumValue);
+
+ // Walking 8 elements at one step:
+ int n = result.Length / 8;
+ for (int i = 0; i < n; i++)
+ {
+ Vector c = Unsafe.Add(ref cBase, i);
+ Vector m = Unsafe.Add(ref mBase, i);
+ Vector y = Unsafe.Add(ref yBase, i);
+ Vector k = Unsafe.Add(ref kBase, i) * scale;
+
+ c = (c * k) * scale;
+ m = (m * k) * scale;
+ y = (y * k) * scale;
+
+ ccRefAsVector = c;
+ mmRefAsVector = m;
+ yyRefAsVector = y;
+
+ // Collect (c0,c1...c8) (m0,m1...m8) (y0,y1...y8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
+ ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
+ destination.Pack(ref cc, ref mm, ref yy);
+ }
+ }
+
+ protected override void ConvertCore(in ComponentValues values, Span result) =>
+ FromCmykBasic.ConvertCore(values, result, this.MaximumValue);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs
new file mode 100644
index 0000000000..45846a6b56
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs
@@ -0,0 +1,63 @@
+// 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;
+#if SUPPORTS_RUNTIME_INTRINSICS
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+using static SixLabors.ImageSharp.SimdUtils;
+#endif
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
+{
+ internal abstract partial class JpegColorConverter
+ {
+ internal sealed class FromGrayscaleAvx2 : Avx2JpegColorConverter
+ {
+ public FromGrayscaleAvx2(int precision)
+ : base(JpegColorSpace.Grayscale, precision)
+ {
+ }
+
+ protected override void ConvertCoreVectorized(in ComponentValues values, Span result)
+ {
+#if SUPPORTS_RUNTIME_INTRINSICS
+ ref Vector256 gBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
+
+ ref Vector256 resultBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(result));
+
+ // Used for the color conversion
+ var scale = Vector256.Create(1 / this.MaximumValue);
+ var one = Vector256.Create(1F);
+
+ // Used for packing
+ ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
+ Vector256 vcontrol = Unsafe.As>(ref control);
+
+ int n = result.Length / 8;
+ for (int i = 0; i < n; i++)
+ {
+ Vector256 g = Avx.Multiply(Unsafe.Add(ref gBase, i), scale);
+
+ g = Avx2.PermuteVar8x32(g, vcontrol);
+
+ ref Vector256 destination = ref Unsafe.Add(ref resultBase, i * 4);
+
+ destination = Avx.Blend(Avx.Permute(g, 0b00_00_00_00), one, 0b1000_1000);
+ Unsafe.Add(ref destination, 1) = Avx.Blend(Avx.Shuffle(g, g, 0b01_01_01_01), one, 0b1000_1000);
+ Unsafe.Add(ref destination, 2) = Avx.Blend(Avx.Shuffle(g, g, 0b10_10_10_10), one, 0b1000_1000);
+ Unsafe.Add(ref destination, 3) = Avx.Blend(Avx.Shuffle(g, g, 0b11_11_11_11), one, 0b1000_1000);
+ }
+#endif
+ }
+
+ protected override void ConvertCore(in ComponentValues values, Span result) =>
+ FromGrayscaleBasic.ConvertCore(values, result, this.MaximumValue);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs
similarity index 74%
rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs
rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs
index cf0bc2c920..0b7a220d94 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs
@@ -10,16 +10,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
- internal sealed class FromGrayscale : JpegColorConverter
+ internal sealed class FromGrayscaleBasic : BasicJpegColorConverter
{
- public FromGrayscale(int precision)
+ public FromGrayscaleBasic(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
public override void ConvertToRgba(in ComponentValues values, Span result)
{
- var maximum = 1 / this.MaximumValue;
+ ConvertCore(values, result, this.MaximumValue);
+ }
+
+ internal static void ConvertCore(in ComponentValues values, Span result, float maxValue)
+ {
+ var maximum = 1 / maxValue;
var scale = new Vector4(maximum, maximum, maximum, 1F);
ref float sBase = ref MemoryMarshal.GetReference(values.Component0);
@@ -35,4 +40,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs
new file mode 100644
index 0000000000..8f04c91528
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs
@@ -0,0 +1,72 @@
+// 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;
+#if SUPPORTS_RUNTIME_INTRINSICS
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+using static SixLabors.ImageSharp.SimdUtils;
+#endif
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
+{
+ internal abstract partial class JpegColorConverter
+ {
+ internal sealed class FromRgbAvx2 : Avx2JpegColorConverter
+ {
+ public FromRgbAvx2(int precision)
+ : base(JpegColorSpace.RGB, precision)
+ {
+ }
+
+ protected override void ConvertCoreVectorized(in ComponentValues values, Span result)
+ {
+#if SUPPORTS_RUNTIME_INTRINSICS
+ ref Vector256 rBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
+ ref Vector256 gBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1));
+ ref Vector256 bBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2));
+
+ ref Vector256 resultBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(result));
+
+ // Used for the color conversion
+ var scale = Vector256.Create(1 / this.MaximumValue);
+ var one = Vector256.Create(1F);
+
+ // Used for packing
+ ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
+ Vector256 vcontrol = Unsafe.As>(ref control);
+
+ int n = result.Length / 8;
+ for (int i = 0; i < n; i++)
+ {
+ Vector256 r = Avx.Multiply(Avx2.PermuteVar8x32(Unsafe.Add(ref rBase, i), vcontrol), scale);
+ Vector256 g = Avx.Multiply(Avx2.PermuteVar8x32(Unsafe.Add(ref gBase, i), vcontrol), scale);
+ Vector256 b = Avx.Multiply(Avx2.PermuteVar8x32(Unsafe.Add(ref bBase, i), vcontrol), scale);
+
+ Vector256 rgLo = Avx.UnpackLow(r, g);
+ Vector256 boLo = Avx.UnpackLow(b, one);
+ Vector256 rgHi = Avx.UnpackHigh(r, g);
+ Vector256 boHi = Avx.UnpackHigh(b, one);
+
+ ref Vector256 destination = ref Unsafe.Add(ref resultBase, i * 4);
+
+ destination = Avx.Shuffle(rgLo, boLo, 0b01_00_01_00);
+ Unsafe.Add(ref destination, 1) = Avx.Shuffle(rgLo, boLo, 0b11_10_11_10);
+ Unsafe.Add(ref destination, 2) = Avx.Shuffle(rgHi, boHi, 0b01_00_01_00);
+ Unsafe.Add(ref destination, 3) = Avx.Shuffle(rgHi, boHi, 0b11_10_11_10);
+ }
+#endif
+ }
+
+ protected override void ConvertCore(in ComponentValues values, Span result) =>
+ FromRgbBasic.ConvertCore(values, result, this.MaximumValue);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs
similarity index 76%
rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs
rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs
index 25889a6dfc..ddca3fe2f6 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs
@@ -8,23 +8,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
- internal sealed class FromRgb : JpegColorConverter
+ internal sealed class FromRgbBasic : BasicJpegColorConverter
{
- public FromRgb(int precision)
+ public FromRgbBasic(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
public override void ConvertToRgba(in ComponentValues values, Span result)
{
- // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()!
+ ConvertCore(values, result, this.MaximumValue);
+ }
+
+ internal static void ConvertCore(in ComponentValues values, Span result, float maxValue)
+ {
ReadOnlySpan rVals = values.Component0;
ReadOnlySpan gVals = values.Component1;
ReadOnlySpan bVals = values.Component2;
var v = new Vector4(0, 0, 0, 1);
- var maximum = 1 / this.MaximumValue;
+ var maximum = 1 / maxValue;
var scale = new Vector4(maximum, maximum, maximum, 1F);
for (int i = 0; i < result.Length; i++)
@@ -44,4 +48,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs
new file mode 100644
index 0000000000..763064d1e0
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs
@@ -0,0 +1,67 @@
+// 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;
+using SixLabors.ImageSharp.Tuples;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
+{
+ internal abstract partial class JpegColorConverter
+ {
+ internal sealed class FromRgbVector8 : Vector8JpegColorConverter
+ {
+ public FromRgbVector8(int precision)
+ : base(JpegColorSpace.RGB, precision)
+ {
+ }
+
+ protected override void ConvertCoreVectorized(in ComponentValues values, Span result)
+ {
+ ref Vector rBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
+ ref Vector gBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1));
+ ref Vector bBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2));
+
+ ref Vector4Octet resultBase =
+ ref Unsafe.As(ref MemoryMarshal.GetReference(result));
+
+ Vector4Pair rr = default;
+ Vector4Pair gg = default;
+ Vector4Pair bb = default;
+ ref Vector rrRefAsVector = ref Unsafe.As>(ref rr);
+ ref Vector ggRefAsVector = ref Unsafe.As>(ref gg);
+ ref Vector bbRefAsVector = ref Unsafe.As>(ref bb);
+
+ var scale = new Vector(1 / this.MaximumValue);
+
+ // Walking 8 elements at one step:
+ int n = result.Length / 8;
+ for (int i = 0; i < n; i++)
+ {
+ Vector r = Unsafe.Add(ref rBase, i);
+ Vector g = Unsafe.Add(ref gBase, i);
+ Vector b = Unsafe.Add(ref bBase, i);
+ r *= scale;
+ g *= scale;
+ b *= scale;
+
+ rrRefAsVector = r;
+ ggRefAsVector = g;
+ bbRefAsVector = b;
+
+ // Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
+ ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
+ destination.Pack(ref rr, ref gg, ref bb);
+ }
+ }
+
+ protected override void ConvertCore(in ComponentValues values, Span result) =>
+ FromRgbBasic.ConvertCore(values, result, this.MaximumValue);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs
new file mode 100644
index 0000000000..f3a0636200
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs
@@ -0,0 +1,101 @@
+// 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;
+#if SUPPORTS_RUNTIME_INTRINSICS
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+using static SixLabors.ImageSharp.SimdUtils;
+#endif
+
+// ReSharper disable ImpureMethodCallOnReadonlyValueField
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
+{
+ internal abstract partial class JpegColorConverter
+ {
+ internal sealed class FromYCbCrAvx2 : Avx2JpegColorConverter
+ {
+ public FromYCbCrAvx2(int precision)
+ : base(JpegColorSpace.YCbCr, precision)
+ {
+ }
+
+ protected override void ConvertCoreVectorized(in ComponentValues values, Span result)
+ {
+ #if SUPPORTS_RUNTIME_INTRINSICS
+ ref Vector256 yBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
+ ref Vector256 cbBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1));
+ ref Vector256 crBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2));
+
+ ref Vector256 resultBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(result));
+
+ // Used for the color conversion
+ var chromaOffset = Vector256.Create(-this.HalfValue);
+ var scale = Vector256.Create(1 / this.MaximumValue);
+ var rCrMult = Vector256.Create(1.402F);
+ var gCbMult = Vector256.Create(-0.344136F);
+ var gCrMult = Vector256.Create(-0.714136F);
+ var bCbMult = Vector256.Create(1.772F);
+
+ // Used for packing.
+ var va = Vector256.Create(1F);
+ ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
+ Vector256 vcontrol = Unsafe.As>(ref control);
+
+ // Walking 8 elements at one step:
+ int n = result.Length / 8;
+ for (int i = 0; i < n; i++)
+ {
+ // y = yVals[i];
+ // cb = cbVals[i] - 128F;
+ // cr = crVals[i] - 128F;
+ Vector256 y = Unsafe.Add(ref yBase, i);
+ Vector256 cb = Avx.Add(Unsafe.Add(ref cbBase, i), chromaOffset);
+ Vector256 cr = Avx.Add(Unsafe.Add(ref crBase, i), chromaOffset);
+
+ y = Avx2.PermuteVar8x32(y, vcontrol);
+ cb = Avx2.PermuteVar8x32(cb, vcontrol);
+ cr = Avx2.PermuteVar8x32(cr, vcontrol);
+
+ // r = y + (1.402F * cr);
+ // g = y - (0.344136F * cb) - (0.714136F * cr);
+ // b = y + (1.772F * cb);
+ // Adding & multiplying 8 elements at one time:
+ Vector256 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
+ Vector256 g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
+ Vector256 b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
+
+ // TODO: We should be saving to RGBA not Vector4
+ r = Avx.Multiply(Avx.RoundToNearestInteger(r), scale);
+ g = Avx.Multiply(Avx.RoundToNearestInteger(g), scale);
+ b = Avx.Multiply(Avx.RoundToNearestInteger(b), scale);
+
+ Vector256 vte = Avx.UnpackLow(r, b);
+ Vector256 vto = Avx.UnpackLow(g, va);
+
+ ref Vector256 destination = ref Unsafe.Add(ref resultBase, i * 4);
+
+ destination = Avx.UnpackLow(vte, vto);
+ Unsafe.Add(ref destination, 1) = Avx.UnpackHigh(vte, vto);
+
+ vte = Avx.UnpackHigh(r, b);
+ vto = Avx.UnpackHigh(g, va);
+
+ Unsafe.Add(ref destination, 2) = Avx.UnpackLow(vte, vto);
+ Unsafe.Add(ref destination, 3) = Avx.UnpackHigh(vte, vto);
+ }
+#endif
+ }
+
+ protected override void ConvertCore(in ComponentValues values, Span result) =>
+ FromYCbCrBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs
index 31fc054619..352e4acb7e 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs
@@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
- internal sealed class FromYCbCrBasic : JpegColorConverter
+ internal sealed class FromYCbCrBasic : BasicJpegColorConverter
{
public FromYCbCrBasic(int precision)
: base(JpegColorSpace.YCbCr, precision)
@@ -48,4 +48,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
deleted file mode 100644
index 1319b56ee0..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
+++ /dev/null
@@ -1,182 +0,0 @@
-// 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;
-#if SUPPORTS_RUNTIME_INTRINSICS
-using System.Runtime.Intrinsics;
-using System.Runtime.Intrinsics.X86;
-using static SixLabors.ImageSharp.SimdUtils;
-#endif
-using SixLabors.ImageSharp.Tuples;
-
-// ReSharper disable ImpureMethodCallOnReadonlyValueField
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
-{
- internal abstract partial class JpegColorConverter
- {
- internal sealed class FromYCbCrSimdVector8 : JpegColorConverter
- {
- public FromYCbCrSimdVector8(int precision)
- : base(JpegColorSpace.YCbCr, precision)
- {
- }
-
- public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.HasVector8;
-
- public override void ConvertToRgba(in ComponentValues values, Span result)
- {
- int remainder = result.Length % 8;
- int simdCount = result.Length - remainder;
- if (simdCount > 0)
- {
- ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount), this.MaximumValue, this.HalfValue);
- }
-
- FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder), this.MaximumValue, this.HalfValue);
- }
-
- ///
- /// SIMD convert using buffers of sizes divisible by 8.
- ///
- internal static void ConvertCore(in ComponentValues values, Span result, float maxValue, float halfValue)
- {
- // This implementation is actually AVX specific.
- // An AVX register is capable of storing 8 float-s.
- if (!IsAvailable)
- {
- throw new InvalidOperationException(
- "JpegColorConverter.FromYCbCrSimd256 can be used only on architecture having 256 byte floating point SIMD registers!");
- }
-
-#if SUPPORTS_RUNTIME_INTRINSICS
- ref Vector256 yBase =
- ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
- ref Vector256 cbBase =
- ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1));
- ref Vector256 crBase =
- ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2));
-
- ref Vector256 resultBase =
- ref Unsafe.As>(ref MemoryMarshal.GetReference(result));
-
- // Used for the color conversion
- var chromaOffset = Vector256.Create(-halfValue);
- var scale = Vector256.Create(1 / maxValue);
- var rCrMult = Vector256.Create(1.402F);
- var gCbMult = Vector256.Create(-0.344136F);
- var gCrMult = Vector256.Create(-0.714136F);
- var bCbMult = Vector256.Create(1.772F);
-
- // Used for packing.
- var va = Vector256.Create(1F);
- ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
- Vector256 vcontrol = Unsafe.As>(ref control);
-
- // Walking 8 elements at one step:
- int n = result.Length / 8;
- for (int i = 0; i < n; i++)
- {
- // y = yVals[i];
- // cb = cbVals[i] - 128F;
- // cr = crVals[i] - 128F;
- Vector256 y = Unsafe.Add(ref yBase, i);
- Vector256 cb = Avx.Add(Unsafe.Add(ref cbBase, i), chromaOffset);
- Vector256