diff --git a/README.md b/README.md
index 4962faf5b3..09262eb572 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,7 @@ If you prefer, you can compile ImageSharp yourself (please do and help!)
- Using [Visual Studio 2022](https://visualstudio.microsoft.com/vs/)
- Make sure you have the latest version installed
- - Make sure you have [the .NET 6 SDK](https://www.microsoft.com/net/core#windows) installed
+ - Make sure you have [the .NET 7 SDK](https://www.microsoft.com/net/core#windows) installed
Alternatively, you can work from command line and/or with a lightweight editor on **both Linux/Unix and Windows**:
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykArm64.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykArm64.cs
new file mode 100644
index 0000000000..11122d3b89
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykArm64.cs
@@ -0,0 +1,95 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.Arm;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
+
+internal abstract partial class JpegColorConverterBase
+{
+ internal sealed class CmykArm64 : JpegColorConverterArm64
+ {
+ public CmykArm64(int precision)
+ : base(JpegColorSpace.Cmyk, precision)
+ {
+ }
+
+ ///
+ public override void ConvertToRgbInplace(in ComponentValues values)
+ {
+ ref Vector128 c0Base =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
+ ref Vector128 c1Base =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1));
+ ref Vector128 c2Base =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2));
+ ref Vector128 c3Base =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3));
+
+ // Used for the color conversion
+ var scale = Vector128.Create(1 / (this.MaximumValue * this.MaximumValue));
+
+ nint n = (nint)(uint)values.Component0.Length / Vector128.Count;
+ for (nint i = 0; i < n; i++)
+ {
+ ref Vector128 c = ref Unsafe.Add(ref c0Base, i);
+ ref Vector128 m = ref Unsafe.Add(ref c1Base, i);
+ ref Vector128 y = ref Unsafe.Add(ref c2Base, i);
+ Vector128 k = Unsafe.Add(ref c3Base, i);
+
+ k = AdvSimd.Multiply(k, scale);
+ c = AdvSimd.Multiply(c, k);
+ m = AdvSimd.Multiply(m, k);
+ y = AdvSimd.Multiply(y, k);
+ }
+ }
+
+ ///
+ public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane)
+ => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
+
+ public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane)
+ {
+ ref Vector128 destC =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
+ ref Vector128 destM =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1));
+ ref Vector128 destY =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2));
+ ref Vector128 destK =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3));
+
+ ref Vector128 srcR =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane));
+ ref Vector128 srcG =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane));
+ ref Vector128 srcB =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane));
+
+ var scale = Vector128.Create(maxValue);
+
+ nint n = (nint)(uint)values.Component0.Length / Vector128.Count;
+ for (nint i = 0; i < n; i++)
+ {
+ Vector128 ctmp = AdvSimd.Subtract(scale, Unsafe.Add(ref srcR, i));
+ Vector128 mtmp = AdvSimd.Subtract(scale, Unsafe.Add(ref srcG, i));
+ Vector128 ytmp = AdvSimd.Subtract(scale, Unsafe.Add(ref srcB, i));
+ Vector128 ktmp = AdvSimd.Min(ctmp, AdvSimd.Min(mtmp, ytmp));
+
+ Vector128 kMask = AdvSimd.Not(AdvSimd.CompareEqual(ktmp, scale));
+
+ ctmp = AdvSimd.And(AdvSimd.Arm64.Divide(AdvSimd.Subtract(ctmp, ktmp), AdvSimd.Subtract(scale, ktmp)), kMask);
+ mtmp = AdvSimd.And(AdvSimd.Arm64.Divide(AdvSimd.Subtract(mtmp, ktmp), AdvSimd.Subtract(scale, ktmp)), kMask);
+ ytmp = AdvSimd.And(AdvSimd.Arm64.Divide(AdvSimd.Subtract(ytmp, ktmp), AdvSimd.Subtract(scale, ktmp)), kMask);
+
+ Unsafe.Add(ref destC, i) = AdvSimd.Subtract(scale, AdvSimd.Multiply(ctmp, scale));
+ Unsafe.Add(ref destM, i) = AdvSimd.Subtract(scale, AdvSimd.Multiply(mtmp, scale));
+ Unsafe.Add(ref destY, i) = AdvSimd.Subtract(scale, AdvSimd.Multiply(ytmp, scale));
+ Unsafe.Add(ref destK, i) = AdvSimd.Subtract(scale, ktmp);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterArm64.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterArm64.cs
new file mode 100644
index 0000000000..d6d4d6ef93
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterArm64.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.Arm;
+using System.Runtime.Intrinsics.X86;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
+
+internal abstract partial class JpegColorConverterBase
+{
+ ///
+ /// abstract base for implementations
+ /// based on instructions.
+ ///
+ ///
+ /// Converters of this family would expect input buffers lengths to be
+ /// divisible by 8 without a remainder.
+ /// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks.
+ /// DO NOT pass test data of invalid size to these converters as they
+ /// potentially won't do a bound check and return a false positive result.
+ ///
+ internal abstract class JpegColorConverterArm64 : JpegColorConverterBase
+ {
+ protected JpegColorConverterArm64(JpegColorSpace colorSpace, int precision)
+ : base(colorSpace, precision)
+ {
+ }
+
+ public static bool IsSupported => AdvSimd.Arm64.IsSupported;
+
+ public sealed override bool IsAvailable => IsSupported;
+
+ public sealed override int ElementsPerBatch => Vector128.Count;
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
index 66f0e9f5a5..c6ad623104 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
@@ -176,6 +176,11 @@ private static JpegColorConverterBase GetCmykConverter(int precision)
return new CmykAvx(precision);
}
+ if (JpegColorConverterArm64.IsSupported)
+ {
+ return new CmykArm64(precision);
+ }
+
if (JpegColorConverterVector.IsSupported)
{
return new CmykVector(precision);
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs
index 6ad20ce679..51cd02bc7a 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs
@@ -37,4 +37,12 @@ public void SimdVectorAvx()
new JpegColorConverterBase.CmykAvx(8).ConvertToRgbInplace(values);
}
+
+ [Benchmark]
+ public void SimdVectorArm64()
+ {
+ var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
+
+ new JpegColorConverterBase.CmykArm64(8).ConvertToRgbInplace(values);
+ }
}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
index a939f1b687..44675aaea2 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
@@ -20,7 +20,7 @@ public class JpegColorConverterTests
private const int TestBufferLength = 40;
- private const HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX;
+ private const HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2;
private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new(epsilon: Precision);
@@ -36,7 +36,7 @@ public JpegColorConverterTests(ITestOutputHelper output)
[Fact]
public void GetConverterThrowsExceptionOnInvalidColorSpace()
{
- var invalidColorSpace = (JpegColorSpace)(-1);
+ JpegColorSpace invalidColorSpace = (JpegColorSpace)(-1);
Assert.Throws(() => JpegColorConverterBase.GetConverter(invalidColorSpace, 8));
}
@@ -61,7 +61,7 @@ public void GetConverterThrowsExceptionOnInvalidPrecision()
[InlineData(JpegColorSpace.YCbCr, 12)]
internal void GetConverterReturnsValidConverter(JpegColorSpace colorSpace, int precision)
{
- var converter = JpegColorConverterBase.GetConverter(colorSpace, precision);
+ JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, precision);
Assert.NotNull(converter);
Assert.True(converter.IsAvailable);
@@ -75,10 +75,10 @@ internal void GetConverterReturnsValidConverter(JpegColorSpace colorSpace, int p
[InlineData(JpegColorSpace.Cmyk, 4)]
[InlineData(JpegColorSpace.RGB, 3)]
[InlineData(JpegColorSpace.YCbCr, 3)]
- internal void ConvertWithSelectedConverter(JpegColorSpace colorSpace, int componentCount)
+ internal void ConvertToRgbWithSelectedConverter(JpegColorSpace colorSpace, int componentCount)
{
- var converter = JpegColorConverterBase.GetConverter(colorSpace, 8);
- ValidateConversion(
+ JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, 8);
+ ValidateConversionToRgb(
converter,
componentCount,
1);
@@ -87,13 +87,13 @@ internal void ConvertWithSelectedConverter(JpegColorSpace colorSpace, int compon
[Theory]
[MemberData(nameof(Seeds))]
public void FromYCbCrBasic(int seed) =>
- this.TestConverter(new JpegColorConverterBase.YCbCrScalar(8), 3, seed);
+ this.TestConversionToRgb(new JpegColorConverterBase.YCbCrScalar(8), 3, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromYCbCrVector(int seed)
{
- var converter = new JpegColorConverterBase.YCbCrVector(8);
+ JpegColorConverterBase.YCbCrVector converter = new(8);
if (!converter.IsAvailable)
{
@@ -108,22 +108,23 @@ public void FromYCbCrVector(int seed)
IntrinsicsConfig);
static void RunTest(string arg) =>
- ValidateConversion(
+ ValidateConversionToRgb(
new JpegColorConverterBase.YCbCrVector(8),
3,
- FeatureTestRunner.Deserialize(arg));
+ FeatureTestRunner.Deserialize(arg),
+ new JpegColorConverterBase.YCbCrScalar(8));
}
[Theory]
[MemberData(nameof(Seeds))]
public void FromCmykBasic(int seed) =>
- this.TestConverter(new JpegColorConverterBase.CmykScalar(8), 4, seed);
+ this.TestConversionToRgb(new JpegColorConverterBase.CmykScalar(8), 4, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromCmykVector(int seed)
{
- var converter = new JpegColorConverterBase.CmykVector(8);
+ JpegColorConverterBase.CmykVector converter = new(8);
if (!converter.IsAvailable)
{
@@ -138,22 +139,23 @@ public void FromCmykVector(int seed)
IntrinsicsConfig);
static void RunTest(string arg) =>
- ValidateConversion(
+ ValidateConversionToRgb(
new JpegColorConverterBase.CmykVector(8),
4,
- FeatureTestRunner.Deserialize(arg));
+ FeatureTestRunner.Deserialize(arg),
+ new JpegColorConverterBase.CmykScalar(8));
}
[Theory]
[MemberData(nameof(Seeds))]
public void FromGrayscaleBasic(int seed) =>
- this.TestConverter(new JpegColorConverterBase.GrayscaleScalar(8), 1, seed);
+ this.TestConversionToRgb(new JpegColorConverterBase.GrayscaleScalar(8), 1, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromGrayscaleVector(int seed)
{
- var converter = new JpegColorConverterBase.GrayScaleVector(8);
+ JpegColorConverterBase.GrayScaleVector converter = new(8);
if (!converter.IsAvailable)
{
@@ -168,22 +170,23 @@ public void FromGrayscaleVector(int seed)
IntrinsicsConfig);
static void RunTest(string arg) =>
- ValidateConversion(
+ ValidateConversionToRgb(
new JpegColorConverterBase.GrayScaleVector(8),
1,
- FeatureTestRunner.Deserialize(arg));
+ FeatureTestRunner.Deserialize(arg),
+ new JpegColorConverterBase.GrayscaleScalar(8));
}
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbBasic(int seed) =>
- this.TestConverter(new JpegColorConverterBase.RgbScalar(8), 3, seed);
+ this.TestConversionToRgb(new JpegColorConverterBase.RgbScalar(8), 3, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbVector(int seed)
{
- var converter = new JpegColorConverterBase.RgbVector(8);
+ JpegColorConverterBase.RgbVector converter = new(8);
if (!converter.IsAvailable)
{
@@ -198,22 +201,23 @@ public void FromRgbVector(int seed)
IntrinsicsConfig);
static void RunTest(string arg) =>
- ValidateConversion(
+ ValidateConversionToRgb(
new JpegColorConverterBase.RgbVector(8),
3,
- FeatureTestRunner.Deserialize(arg));
+ FeatureTestRunner.Deserialize(arg),
+ new JpegColorConverterBase.RgbScalar(8));
}
[Theory]
[MemberData(nameof(Seeds))]
public void FromYccKBasic(int seed) =>
- this.TestConverter(new JpegColorConverterBase.YccKScalar(8), 4, seed);
+ this.TestConversionToRgb(new JpegColorConverterBase.YccKScalar(8), 4, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromYccKVector(int seed)
{
- var converter = new JpegColorConverterBase.YccKVector(8);
+ JpegColorConverterBase.YccKVector converter = new(8);
if (!converter.IsAvailable)
{
@@ -228,41 +232,119 @@ public void FromYccKVector(int seed)
IntrinsicsConfig);
static void RunTest(string arg) =>
- ValidateConversion(
+ ValidateConversionToRgb(
new JpegColorConverterBase.YccKVector(8),
4,
- FeatureTestRunner.Deserialize(arg));
+ FeatureTestRunner.Deserialize(arg),
+ new JpegColorConverterBase.YccKScalar(8));
}
[Theory]
[MemberData(nameof(Seeds))]
public void FromYCbCrAvx2(int seed) =>
- this.TestConverter(new JpegColorConverterBase.YCbCrAvx(8), 3, seed);
+ this.TestConversionToRgb(new JpegColorConverterBase.YCbCrAvx(8),
+ 3,
+ seed,
+ new JpegColorConverterBase.YCbCrScalar(8));
+
+ [Theory]
+ [MemberData(nameof(Seeds))]
+ public void FromRgbToYCbCrAvx2(int seed) =>
+ this.TestConversionFromRgb(new JpegColorConverterBase.YCbCrAvx(8),
+ 3,
+ seed,
+ new JpegColorConverterBase.YCbCrScalar(8),
+ precísion: 2);
[Theory]
[MemberData(nameof(Seeds))]
public void FromCmykAvx2(int seed) =>
- this.TestConverter(new JpegColorConverterBase.CmykAvx(8), 4, seed);
+ this.TestConversionToRgb(new JpegColorConverterBase.CmykAvx(8),
+ 4,
+ seed,
+ new JpegColorConverterBase.CmykScalar(8));
+
+ [Theory]
+ [MemberData(nameof(Seeds))]
+ public void FromRgbToCmykAvx2(int seed) =>
+ this.TestConversionFromRgb(new JpegColorConverterBase.CmykAvx(8),
+ 4,
+ seed,
+ new JpegColorConverterBase.CmykScalar(8),
+ precísion: 4);
+
+ [Theory]
+ [MemberData(nameof(Seeds))]
+ public void FromCmykArm(int seed) =>
+ this.TestConversionToRgb( new JpegColorConverterBase.CmykArm64(8),
+ 4,
+ seed,
+ new JpegColorConverterBase.CmykScalar(8));
+
+ [Theory]
+ [MemberData(nameof(Seeds))]
+ public void FromRgbToCmykArm(int seed) =>
+ this.TestConversionFromRgb(new JpegColorConverterBase.CmykArm64(8),
+ 4,
+ seed,
+ new JpegColorConverterBase.CmykScalar(8),
+ precísion: 4);
[Theory]
[MemberData(nameof(Seeds))]
public void FromGrayscaleAvx2(int seed) =>
- this.TestConverter(new JpegColorConverterBase.GrayscaleAvx(8), 1, seed);
+ this.TestConversionToRgb(new JpegColorConverterBase.GrayscaleAvx(8),
+ 1,
+ seed,
+ new JpegColorConverterBase.GrayscaleScalar(8));
+
+ [Theory]
+ [MemberData(nameof(Seeds))]
+ public void FromRgbToGrayscaleAvx2(int seed) =>
+ this.TestConversionFromRgb(new JpegColorConverterBase.GrayscaleAvx(8),
+ 1,
+ seed,
+ new JpegColorConverterBase.GrayscaleScalar(8),
+ precísion: 3);
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbAvx2(int seed) =>
- this.TestConverter(new JpegColorConverterBase.RgbAvx(8), 3, seed);
+ this.TestConversionToRgb(new JpegColorConverterBase.RgbAvx(8),
+ 3,
+ seed,
+ new JpegColorConverterBase.RgbScalar(8));
+
+ [Theory]
+ [MemberData(nameof(Seeds))]
+ public void FromRgbArm(int seed) =>
+ this.TestConversionToRgb(new JpegColorConverterBase.RgbArm(8),
+ 3,
+ seed,
+ new JpegColorConverterBase.RgbScalar(8));
[Theory]
[MemberData(nameof(Seeds))]
public void FromYccKAvx2(int seed) =>
- this.TestConverter(new JpegColorConverterBase.YccKAvx(8), 4, seed);
+ this.TestConversionToRgb( new JpegColorConverterBase.YccKAvx(8),
+ 4,
+ seed,
+ new JpegColorConverterBase.YccKScalar(8));
- private void TestConverter(
+ [Theory]
+ [MemberData(nameof(Seeds))]
+ public void FromRgbToYccKAvx2(int seed) =>
+ this.TestConversionFromRgb(new JpegColorConverterBase.YccKAvx(8),
+ 4,
+ seed,
+ new JpegColorConverterBase.YccKScalar(8),
+ precísion: 4);
+
+ private void TestConversionToRgb(
JpegColorConverterBase converter,
int componentCount,
- int seed)
+ int seed,
+ JpegColorConverterBase baseLineConverter = null)
{
if (!converter.IsAvailable)
{
@@ -271,10 +353,33 @@ private void TestConverter(
return;
}
- ValidateConversion(
+ ValidateConversionToRgb(
converter,
componentCount,
- seed);
+ seed,
+ baseLineConverter);
+ }
+
+ private void TestConversionFromRgb(
+ JpegColorConverterBase converter,
+ int componentCount,
+ int seed,
+ JpegColorConverterBase baseLineConverter,
+ int precísion)
+ {
+ if (!converter.IsAvailable)
+ {
+ this.Output.WriteLine(
+ $"Skipping test - {converter.GetType().Name} is not supported on current hardware.");
+ return;
+ }
+
+ ValidateConversionFromRgb(
+ converter,
+ componentCount,
+ seed,
+ baseLineConverter,
+ precísion);
}
private static JpegColorConverterBase.ComponentValues CreateRandomValues(
@@ -303,24 +408,117 @@ private static JpegColorConverterBase.ComponentValues CreateRandomValues(
return new JpegColorConverterBase.ComponentValues(buffers, 0);
}
- private static void ValidateConversion(
+ private static float[] CreateRandomValues(int length, Random rnd)
+ {
+ float[] values = new float[length];
+
+ for (int j = 0; j < values.Length; j++)
+ {
+ values[j] = (float)rnd.NextDouble() * MaxColorChannelValue;
+ }
+
+ return values;
+ }
+
+ private static void ValidateConversionToRgb(
JpegColorConverterBase converter,
int componentCount,
- int seed)
+ int seed,
+ JpegColorConverterBase baseLineConverter = null)
{
JpegColorConverterBase.ComponentValues original = CreateRandomValues(TestBufferLength, componentCount, seed);
- JpegColorConverterBase.ComponentValues values = new(
+ JpegColorConverterBase.ComponentValues actual = new(
original.ComponentCount,
original.Component0.ToArray(),
original.Component1.ToArray(),
original.Component2.ToArray(),
original.Component3.ToArray());
- converter.ConvertToRgbInplace(values);
+ converter.ConvertToRgbInplace(actual);
for (int i = 0; i < TestBufferLength; i++)
{
- Validate(converter.ColorSpace, original, values, i);
+ Validate(converter.ColorSpace, original, actual, i);
+ }
+
+ // Compare conversion result to a baseline, should be the scalar version.
+ if (baseLineConverter != null)
+ {
+ JpegColorConverterBase.ComponentValues expected = new(
+ original.ComponentCount,
+ original.Component0.ToArray(),
+ original.Component1.ToArray(),
+ original.Component2.ToArray(),
+ original.Component3.ToArray());
+ baseLineConverter.ConvertToRgbInplace(expected);
+ if (componentCount == 1)
+ {
+ Assert.True(expected.Component0.SequenceEqual(actual.Component0));
+ }
+
+ if (componentCount == 2)
+ {
+ Assert.True(expected.Component1.SequenceEqual(actual.Component1));
+ }
+
+ if (componentCount == 3)
+ {
+ Assert.True(expected.Component2.SequenceEqual(actual.Component2));
+ }
+
+ if (componentCount == 4)
+ {
+ Assert.True(expected.Component3.SequenceEqual(actual.Component3));
+ }
+ }
+ }
+
+ private static void ValidateConversionFromRgb(
+ JpegColorConverterBase converter,
+ int componentCount,
+ int seed,
+ JpegColorConverterBase baseLineConverter,
+ int precision = 4)
+ {
+ // arrange
+ JpegColorConverterBase.ComponentValues actual = CreateRandomValues(TestBufferLength, componentCount, seed);
+ JpegColorConverterBase.ComponentValues expected = CreateRandomValues(TestBufferLength, componentCount, seed);
+ Random rnd = new(seed);
+ float[] rLane = CreateRandomValues(TestBufferLength, rnd);
+ float[] gLane = CreateRandomValues(TestBufferLength, rnd);
+ float[] bLane = CreateRandomValues(TestBufferLength, rnd);
+
+ // act
+ converter.ConvertFromRgb(actual, rLane, gLane, bLane);
+ baseLineConverter.ConvertFromRgb(expected, rLane, gLane, bLane);
+
+ // assert
+ if (componentCount == 1)
+ {
+ CompareSequenceWithTolerance(expected.Component0, actual.Component0, precision);
+ }
+
+ if (componentCount == 2)
+ {
+ CompareSequenceWithTolerance(expected.Component1, actual.Component1, precision);
+ }
+
+ if (componentCount == 3)
+ {
+ CompareSequenceWithTolerance(expected.Component2, actual.Component2, precision);
+ }
+
+ if (componentCount == 4)
+ {
+ CompareSequenceWithTolerance(expected.Component3, actual.Component3, precision);
+ }
+ }
+
+ private static void CompareSequenceWithTolerance(Span expected, Span actual, int precision)
+ {
+ for (int i = 0; i < expected.Length; i++)
+ {
+ Assert.Equal(expected[i], actual[i], precision: precision);
}
}
@@ -358,9 +556,9 @@ private static void ValidateYCbCr(in JpegColorConverterBase.ComponentValues valu
float y = values.Component0[i];
float cb = values.Component1[i];
float cr = values.Component2[i];
- var expected = ColorSpaceConverter.ToRgb(new YCbCr(y, cb, cr));
+ Rgb expected = ColorSpaceConverter.ToRgb(new YCbCr(y, cb, cr));
- var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]);
+ Rgb actual = new(result.Component0[i], result.Component1[i], result.Component2[i]);
bool equal = ColorSpaceComparer.Equals(expected, actual);
Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}");