Browse Source

Merge pull request #2400 from stefannikolei/stefannikolei/arm/colorConvertercmyk

Add arm64 intrinsics for cmyk converter
pull/2412/head
Anton Firszov 3 years ago
committed by GitHub
parent
commit
d7cd46f503
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      README.md
  2. 95
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykArm64.cs
  3. 35
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterArm64.cs
  4. 5
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
  5. 8
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs
  6. 282
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

2
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/) - Using [Visual Studio 2022](https://visualstudio.microsoft.com/vs/)
- Make sure you have the latest version installed - 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**: Alternatively, you can work from command line and/or with a lightweight editor on **both Linux/Unix and Windows**:

95
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)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInplace(in ComponentValues values)
{
ref Vector128<float> c0Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> c1Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> c2Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector128<float> c3Base =
ref Unsafe.As<float, Vector128<float>>(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<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector128<float> c = ref Unsafe.Add(ref c0Base, i);
ref Vector128<float> m = ref Unsafe.Add(ref c1Base, i);
ref Vector128<float> y = ref Unsafe.Add(ref c2Base, i);
Vector128<float> 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);
}
}
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector128<float> destC =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> destM =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> destY =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector128<float> destK =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector128<float> srcR =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector128<float> srcG =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector128<float> srcB =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(bLane));
var scale = Vector128.Create(maxValue);
nint n = (nint)(uint)values.Component0.Length / Vector128<float>.Count;
for (nint i = 0; i < n; i++)
{
Vector128<float> ctmp = AdvSimd.Subtract(scale, Unsafe.Add(ref srcR, i));
Vector128<float> mtmp = AdvSimd.Subtract(scale, Unsafe.Add(ref srcG, i));
Vector128<float> ytmp = AdvSimd.Subtract(scale, Unsafe.Add(ref srcB, i));
Vector128<float> ktmp = AdvSimd.Min(ctmp, AdvSimd.Min(mtmp, ytmp));
Vector128<float> 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);
}
}
}
}

35
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
{
/// <summary>
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
/// based on <see cref="Avx"/> instructions.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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<float>.Count;
}
}

5
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs

@ -176,6 +176,11 @@ internal abstract partial class JpegColorConverterBase
return new CmykAvx(precision); return new CmykAvx(precision);
} }
if (JpegColorConverterArm64.IsSupported)
{
return new CmykArm64(precision);
}
if (JpegColorConverterVector.IsSupported) if (JpegColorConverterVector.IsSupported)
{ {
return new CmykVector(precision); return new CmykVector(precision);

8
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs

@ -37,4 +37,12 @@ public class CmykColorConversion : ColorConversionBenchmark
new JpegColorConverterBase.CmykAvx(8).ConvertToRgbInplace(values); new JpegColorConverterBase.CmykAvx(8).ConvertToRgbInplace(values);
} }
[Benchmark]
public void SimdVectorArm64()
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.CmykArm64(8).ConvertToRgbInplace(values);
}
} }

282
tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

@ -20,7 +20,7 @@ public class JpegColorConverterTests
private const int TestBufferLength = 40; 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); private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new(epsilon: Precision);
@ -36,7 +36,7 @@ public class JpegColorConverterTests
[Fact] [Fact]
public void GetConverterThrowsExceptionOnInvalidColorSpace() public void GetConverterThrowsExceptionOnInvalidColorSpace()
{ {
var invalidColorSpace = (JpegColorSpace)(-1); JpegColorSpace invalidColorSpace = (JpegColorSpace)(-1);
Assert.Throws<InvalidImageContentException>(() => JpegColorConverterBase.GetConverter(invalidColorSpace, 8)); Assert.Throws<InvalidImageContentException>(() => JpegColorConverterBase.GetConverter(invalidColorSpace, 8));
} }
@ -61,7 +61,7 @@ public class JpegColorConverterTests
[InlineData(JpegColorSpace.YCbCr, 12)] [InlineData(JpegColorSpace.YCbCr, 12)]
internal void GetConverterReturnsValidConverter(JpegColorSpace colorSpace, int precision) internal void GetConverterReturnsValidConverter(JpegColorSpace colorSpace, int precision)
{ {
var converter = JpegColorConverterBase.GetConverter(colorSpace, precision); JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, precision);
Assert.NotNull(converter); Assert.NotNull(converter);
Assert.True(converter.IsAvailable); Assert.True(converter.IsAvailable);
@ -75,10 +75,10 @@ public class JpegColorConverterTests
[InlineData(JpegColorSpace.Cmyk, 4)] [InlineData(JpegColorSpace.Cmyk, 4)]
[InlineData(JpegColorSpace.RGB, 3)] [InlineData(JpegColorSpace.RGB, 3)]
[InlineData(JpegColorSpace.YCbCr, 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); JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, 8);
ValidateConversion( ValidateConversionToRgb(
converter, converter,
componentCount, componentCount,
1); 1);
@ -87,13 +87,13 @@ public class JpegColorConverterTests
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromYCbCrBasic(int seed) => public void FromYCbCrBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.YCbCrScalar(8), 3, seed); this.TestConversionToRgb(new JpegColorConverterBase.YCbCrScalar(8), 3, seed);
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromYCbCrVector(int seed) public void FromYCbCrVector(int seed)
{ {
var converter = new JpegColorConverterBase.YCbCrVector(8); JpegColorConverterBase.YCbCrVector converter = new(8);
if (!converter.IsAvailable) if (!converter.IsAvailable)
{ {
@ -108,22 +108,23 @@ public class JpegColorConverterTests
IntrinsicsConfig); IntrinsicsConfig);
static void RunTest(string arg) => static void RunTest(string arg) =>
ValidateConversion( ValidateConversionToRgb(
new JpegColorConverterBase.YCbCrVector(8), new JpegColorConverterBase.YCbCrVector(8),
3, 3,
FeatureTestRunner.Deserialize<int>(arg)); FeatureTestRunner.Deserialize<int>(arg),
new JpegColorConverterBase.YCbCrScalar(8));
} }
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromCmykBasic(int seed) => public void FromCmykBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.CmykScalar(8), 4, seed); this.TestConversionToRgb(new JpegColorConverterBase.CmykScalar(8), 4, seed);
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromCmykVector(int seed) public void FromCmykVector(int seed)
{ {
var converter = new JpegColorConverterBase.CmykVector(8); JpegColorConverterBase.CmykVector converter = new(8);
if (!converter.IsAvailable) if (!converter.IsAvailable)
{ {
@ -138,22 +139,23 @@ public class JpegColorConverterTests
IntrinsicsConfig); IntrinsicsConfig);
static void RunTest(string arg) => static void RunTest(string arg) =>
ValidateConversion( ValidateConversionToRgb(
new JpegColorConverterBase.CmykVector(8), new JpegColorConverterBase.CmykVector(8),
4, 4,
FeatureTestRunner.Deserialize<int>(arg)); FeatureTestRunner.Deserialize<int>(arg),
new JpegColorConverterBase.CmykScalar(8));
} }
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromGrayscaleBasic(int seed) => public void FromGrayscaleBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.GrayscaleScalar(8), 1, seed); this.TestConversionToRgb(new JpegColorConverterBase.GrayscaleScalar(8), 1, seed);
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromGrayscaleVector(int seed) public void FromGrayscaleVector(int seed)
{ {
var converter = new JpegColorConverterBase.GrayScaleVector(8); JpegColorConverterBase.GrayScaleVector converter = new(8);
if (!converter.IsAvailable) if (!converter.IsAvailable)
{ {
@ -168,22 +170,23 @@ public class JpegColorConverterTests
IntrinsicsConfig); IntrinsicsConfig);
static void RunTest(string arg) => static void RunTest(string arg) =>
ValidateConversion( ValidateConversionToRgb(
new JpegColorConverterBase.GrayScaleVector(8), new JpegColorConverterBase.GrayScaleVector(8),
1, 1,
FeatureTestRunner.Deserialize<int>(arg)); FeatureTestRunner.Deserialize<int>(arg),
new JpegColorConverterBase.GrayscaleScalar(8));
} }
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromRgbBasic(int seed) => public void FromRgbBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.RgbScalar(8), 3, seed); this.TestConversionToRgb(new JpegColorConverterBase.RgbScalar(8), 3, seed);
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromRgbVector(int seed) public void FromRgbVector(int seed)
{ {
var converter = new JpegColorConverterBase.RgbVector(8); JpegColorConverterBase.RgbVector converter = new(8);
if (!converter.IsAvailable) if (!converter.IsAvailable)
{ {
@ -198,22 +201,23 @@ public class JpegColorConverterTests
IntrinsicsConfig); IntrinsicsConfig);
static void RunTest(string arg) => static void RunTest(string arg) =>
ValidateConversion( ValidateConversionToRgb(
new JpegColorConverterBase.RgbVector(8), new JpegColorConverterBase.RgbVector(8),
3, 3,
FeatureTestRunner.Deserialize<int>(arg)); FeatureTestRunner.Deserialize<int>(arg),
new JpegColorConverterBase.RgbScalar(8));
} }
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromYccKBasic(int seed) => public void FromYccKBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.YccKScalar(8), 4, seed); this.TestConversionToRgb(new JpegColorConverterBase.YccKScalar(8), 4, seed);
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromYccKVector(int seed) public void FromYccKVector(int seed)
{ {
var converter = new JpegColorConverterBase.YccKVector(8); JpegColorConverterBase.YccKVector converter = new(8);
if (!converter.IsAvailable) if (!converter.IsAvailable)
{ {
@ -228,41 +232,119 @@ public class JpegColorConverterTests
IntrinsicsConfig); IntrinsicsConfig);
static void RunTest(string arg) => static void RunTest(string arg) =>
ValidateConversion( ValidateConversionToRgb(
new JpegColorConverterBase.YccKVector(8), new JpegColorConverterBase.YccKVector(8),
4, 4,
FeatureTestRunner.Deserialize<int>(arg)); FeatureTestRunner.Deserialize<int>(arg),
new JpegColorConverterBase.YccKScalar(8));
} }
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromYCbCrAvx2(int seed) => 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] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromCmykAvx2(int seed) => 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] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromGrayscaleAvx2(int seed) => 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] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromRgbAvx2(int seed) => 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] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromYccKAvx2(int seed) => 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, JpegColorConverterBase converter,
int componentCount, int componentCount,
int seed) int seed,
JpegColorConverterBase baseLineConverter = null)
{ {
if (!converter.IsAvailable) if (!converter.IsAvailable)
{ {
@ -271,10 +353,33 @@ public class JpegColorConverterTests
return; return;
} }
ValidateConversion( ValidateConversionToRgb(
converter, converter,
componentCount, 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( private static JpegColorConverterBase.ComponentValues CreateRandomValues(
@ -303,24 +408,117 @@ public class JpegColorConverterTests
return new JpegColorConverterBase.ComponentValues(buffers, 0); 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, JpegColorConverterBase converter,
int componentCount, int componentCount,
int seed) int seed,
JpegColorConverterBase baseLineConverter = null)
{ {
JpegColorConverterBase.ComponentValues original = CreateRandomValues(TestBufferLength, componentCount, seed); JpegColorConverterBase.ComponentValues original = CreateRandomValues(TestBufferLength, componentCount, seed);
JpegColorConverterBase.ComponentValues values = new( JpegColorConverterBase.ComponentValues actual = new(
original.ComponentCount, original.ComponentCount,
original.Component0.ToArray(), original.Component0.ToArray(),
original.Component1.ToArray(), original.Component1.ToArray(),
original.Component2.ToArray(), original.Component2.ToArray(),
original.Component3.ToArray()); original.Component3.ToArray());
converter.ConvertToRgbInplace(values); converter.ConvertToRgbInplace(actual);
for (int i = 0; i < TestBufferLength; i++) 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<float> expected, Span<float> actual, int precision)
{
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i], actual[i], precision: precision);
} }
} }
@ -358,9 +556,9 @@ public class JpegColorConverterTests
float y = values.Component0[i]; float y = values.Component0[i];
float cb = values.Component1[i]; float cb = values.Component1[i];
float cr = values.Component2[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); bool equal = ColorSpaceComparer.Equals(expected, actual);
Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}");

Loading…
Cancel
Save