diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs index 8860777cd..cf9f91801 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs @@ -21,7 +21,7 @@ internal static class ColorProfileConverterExtensionsCieLabCieXyz CieXyz pcsTo = pcsFrom.ToProfileConnectingSpace(options); // Adapt to target white point - VonKriesChromaticAdaptation.Transform(options, in pcsTo); + pcsTo = VonKriesChromaticAdaptation.Transform(options, in pcsTo); // Convert to output from PCS return TTo.FromProfileConnectingSpace(options, pcsTo); diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs index 50317311c..a73677129 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs @@ -18,7 +18,7 @@ internal static class ColorProfileConverterExtensionsCieXyzCieLab CieXyz pcsFrom = source.ToProfileConnectingSpace(options); // Adapt to target white point - VonKriesChromaticAdaptation.Transform(options, in pcsFrom); + pcsFrom = VonKriesChromaticAdaptation.Transform(options, in pcsFrom); // Convert between PCS CieLab pcsTo = CieLab.FromProfileConnectingSpace(options, in pcsFrom); diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs index 2380bb15e..fd6ff21e4 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs @@ -18,13 +18,10 @@ internal static class ColorProfileConverterExtensionsCieXyzCieXyz CieXyz pcsFrom = source.ToProfileConnectingSpace(options); // Adapt to target white point - VonKriesChromaticAdaptation.Transform(options, in pcsFrom); - - // Convert between PCS - CieXyz pcsTo = CieXyz.FromProfileConnectingSpace(options, in pcsFrom); + pcsFrom = VonKriesChromaticAdaptation.Transform(options, in pcsFrom); // Convert to output from PCS - return TTo.FromProfileConnectingSpace(options, pcsTo); + return TTo.FromProfileConnectingSpace(options, pcsFrom); } public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination) @@ -41,12 +38,7 @@ internal static class ColorProfileConverterExtensionsCieXyzCieXyz // Adapt to target white point VonKriesChromaticAdaptation.Transform(options, pcsFrom, pcsFrom); - // Convert between PCS. - using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsTo = pcsToOwner.GetSpan(); - CieXyz.FromProfileConnectionSpace(options, pcsFrom, pcsTo); - // Convert to output from PCS - TTo.FromProfileConnectionSpace(options, pcsTo, destination); + TTo.FromProfileConnectionSpace(options, pcsFrom, destination); } } diff --git a/src/ImageSharp/ColorProfiles/Companding/CompandingUtilities.cs b/src/ImageSharp/ColorProfiles/Companding/CompandingUtilities.cs index 44dae35a9..1970e2d94 100644 --- a/src/ImageSharp/ColorProfiles/Companding/CompandingUtilities.cs +++ b/src/ImageSharp/ColorProfiles/Companding/CompandingUtilities.cs @@ -17,17 +17,28 @@ public static class CompandingUtilities { private const int Length = Scale + 2; // 256kb @ 16bit precision. private const int Scale = (1 << 16) - 1; - private static readonly ConcurrentDictionary<(Type, double), Lazy> LookupTables = new(); + private static readonly ConcurrentDictionary<(Type, double), float[]> CompressLookupTables = new(); + private static readonly ConcurrentDictionary<(Type, double), float[]> ExpandLookupTables = new(); /// - /// Lazily creates and stores a companding lookup table using the given function and modifier. + /// Lazily creates and stores a companding compression lookup table using the given function and modifier. /// /// The type of companding function. /// The companding function. /// A modifier to pass to the function. /// The array. - public static Lazy GetLookupTable(Func compandingFunction, double modifier = 0) - => LookupTables.GetOrAdd((typeof(T), modifier), args => new(() => CreateLookupTableImpl(compandingFunction, args.Item2), true)); + public static float[] GetCompressLookupTable(Func compandingFunction, double modifier = 0) + => CompressLookupTables.GetOrAdd((typeof(T), modifier), args => CreateLookupTableImpl(compandingFunction, args.Item2)); + + /// + /// Lazily creates and stores a companding expanding lookup table using the given function and modifier. + /// + /// The type of companding function. + /// The companding function. + /// A modifier to pass to the function. + /// The array. + public static float[] GetExpandLookupTable(Func compandingFunction, double modifier = 0) + => ExpandLookupTables.GetOrAdd((typeof(T), modifier), args => CreateLookupTableImpl(compandingFunction, args.Item2)); /// /// Creates a companding lookup table using the given function. diff --git a/src/ImageSharp/ColorProfiles/Companding/GammaCompanding.cs b/src/ImageSharp/ColorProfiles/Companding/GammaCompanding.cs index 28e9a4641..34ca8bf5e 100644 --- a/src/ImageSharp/ColorProfiles/Companding/GammaCompanding.cs +++ b/src/ImageSharp/ColorProfiles/Companding/GammaCompanding.cs @@ -24,7 +24,7 @@ public static class GammaCompanding /// The span of vectors. /// The gamma value. public static void Compress(Span vectors, double gamma) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable(CompressFunction, gamma).Value); + => CompandingUtilities.Compand(vectors, CompandingUtilities.GetCompressLookupTable(CompressFunction, gamma)); /// /// Expands the nonlinear vectors to their linear equivalents with respect to the energy. @@ -32,7 +32,7 @@ public static class GammaCompanding /// The span of vectors. /// The gamma value. public static void Expand(Span vectors, double gamma) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable(ExpandFunction, gamma).Value); + => CompandingUtilities.Compand(vectors, CompandingUtilities.GetExpandLookupTable(ExpandFunction, gamma)); /// /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. @@ -41,16 +41,16 @@ public static class GammaCompanding /// The gamma value. /// The . public static Vector4 Compress(Vector4 vector, double gamma) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable(CompressFunction, gamma).Value); + => CompandingUtilities.Compand(vector, CompandingUtilities.GetCompressLookupTable(CompressFunction, gamma)); /// - /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. + /// Expands the nonlinear vector to its linear equivalent with respect to the energy. /// /// The vector. /// The gamma value. /// The . public static Vector4 Expand(Vector4 vector, double gamma) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable(ExpandFunction, gamma).Value); + => CompandingUtilities.Compand(vector, CompandingUtilities.GetExpandLookupTable(ExpandFunction, gamma)); private class GammaCompandingKey; } diff --git a/src/ImageSharp/ColorProfiles/Companding/LCompanding.cs b/src/ImageSharp/ColorProfiles/Companding/LCompanding.cs index 79349d1c5..4f5383038 100644 --- a/src/ImageSharp/ColorProfiles/Companding/LCompanding.cs +++ b/src/ImageSharp/ColorProfiles/Companding/LCompanding.cs @@ -42,14 +42,14 @@ public static class LCompanding /// /// The span of vectors. public static void Compress(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable(CompressFunction).Value); + => CompandingUtilities.Compand(vectors, CompandingUtilities.GetCompressLookupTable(CompressFunction)); /// /// Expands the nonlinear vectors to their linear equivalents with respect to the energy. /// /// The span of vectors. public static void Expand(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable(ExpandFunction).Value); + => CompandingUtilities.Compand(vectors, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); /// /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. @@ -57,15 +57,15 @@ public static class LCompanding /// The vector. /// The . public static Vector4 Compress(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable(CompressFunction).Value); + => CompandingUtilities.Compand(vector, CompandingUtilities.GetCompressLookupTable(CompressFunction)); /// - /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. + /// Expands the nonlinear vector to its linear equivalent with respect to the energy. /// /// The vector. /// The . public static Vector4 Expand(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable(ExpandFunction).Value); + => CompandingUtilities.Compand(vector, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); private class LCompandingKey; } diff --git a/src/ImageSharp/ColorProfiles/Companding/Rec2020Companding.cs b/src/ImageSharp/ColorProfiles/Companding/Rec2020Companding.cs index 9beab3a86..1901e6471 100644 --- a/src/ImageSharp/ColorProfiles/Companding/Rec2020Companding.cs +++ b/src/ImageSharp/ColorProfiles/Companding/Rec2020Companding.cs @@ -46,14 +46,14 @@ public static class Rec2020Companding /// /// The span of vectors. public static void Compress(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable(CompressFunction).Value); + => CompandingUtilities.Compand(vectors, CompandingUtilities.GetCompressLookupTable(CompressFunction)); /// /// Expands the nonlinear vectors to their linear equivalents with respect to the energy. /// /// The span of vectors. public static void Expand(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable(ExpandFunction).Value); + => CompandingUtilities.Compand(vectors, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); /// /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. @@ -61,15 +61,15 @@ public static class Rec2020Companding /// The vector. /// The . public static Vector4 Compress(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable(CompressFunction).Value); + => CompandingUtilities.Compand(vector, CompandingUtilities.GetCompressLookupTable(CompressFunction)); /// - /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. + /// Expands the nonlinear vector to its linear equivalent with respect to the energy. /// /// The vector. /// The . public static Vector4 Expand(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable(ExpandFunction).Value); + => CompandingUtilities.Compand(vector, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); private class Rec2020CompandingKey; } diff --git a/src/ImageSharp/ColorProfiles/Companding/Rec709Companding.cs b/src/ImageSharp/ColorProfiles/Companding/Rec709Companding.cs index 93a32672c..94b17d8d0 100644 --- a/src/ImageSharp/ColorProfiles/Companding/Rec709Companding.cs +++ b/src/ImageSharp/ColorProfiles/Companding/Rec709Companding.cs @@ -42,14 +42,14 @@ public static class Rec709Companding /// /// The span of vectors. public static void Compress(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable(CompressFunction).Value); + => CompandingUtilities.Compand(vectors, CompandingUtilities.GetCompressLookupTable(CompressFunction)); /// /// Expands the nonlinear vectors to their linear equivalents with respect to the energy. /// /// The span of vectors. public static void Expand(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable(ExpandFunction).Value); + => CompandingUtilities.Compand(vectors, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); /// /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. @@ -57,15 +57,15 @@ public static class Rec709Companding /// The vector. /// The . public static Vector4 Compress(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable(CompressFunction).Value); + => CompandingUtilities.Compand(vector, CompandingUtilities.GetCompressLookupTable(CompressFunction)); /// - /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. + /// Expands the nonlinear vector to its linear equivalent with respect to the energy. /// /// The vector. /// The . public static Vector4 Expand(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable(ExpandFunction).Value); + => CompandingUtilities.Compand(vector, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); private class Rec2020CompandingKey; } diff --git a/src/ImageSharp/ColorProfiles/Companding/SRgbCompanding.cs b/src/ImageSharp/ColorProfiles/Companding/SRgbCompanding.cs index 04e4007a8..ab2710230 100644 --- a/src/ImageSharp/ColorProfiles/Companding/SRgbCompanding.cs +++ b/src/ImageSharp/ColorProfiles/Companding/SRgbCompanding.cs @@ -42,14 +42,14 @@ public static class SRgbCompanding /// /// The span of vectors. public static void Compress(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable(CompressFunction).Value); + => CompandingUtilities.Compand(vectors, CompandingUtilities.GetCompressLookupTable(CompressFunction)); /// /// Expands the nonlinear vectors to their linear equivalents with respect to the energy. /// /// The span of vectors. public static void Expand(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable(ExpandFunction).Value); + => CompandingUtilities.Compand(vectors, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); /// /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. @@ -57,15 +57,15 @@ public static class SRgbCompanding /// The vector. /// The . public static Vector4 Compress(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable(CompressFunction).Value); + => CompandingUtilities.Compand(vector, CompandingUtilities.GetCompressLookupTable(CompressFunction)); /// - /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. + /// Expands the nonlinear vector to its linear equivalent with respect to the energy. /// /// The vector. /// The . public static Vector4 Expand(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable(ExpandFunction).Value); + => CompandingUtilities.Compand(vector, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); private class SRgbCompandingKey; } diff --git a/tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs b/tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs index 5c2f9afbd..84319b66c 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs @@ -13,7 +13,8 @@ internal readonly struct ApproximateColorProfileComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, - IEqualityComparer + IEqualityComparer, + IEqualityComparer { private readonly float epsilon; @@ -31,6 +32,8 @@ internal readonly struct ApproximateColorProfileComparer : public bool Equals(CieLch x, CieLch y) => this.Equals(x.L, y.L) && this.Equals(x.C, y.C) && this.Equals(x.H, y.H); + public bool Equals(Rgb x, Rgb y) => this.Equals(x.R, y.R) && this.Equals(x.G, y.G) && this.Equals(x.B, y.B); + public int GetHashCode([DisallowNull] CieLab obj) => obj.GetHashCode(); public int GetHashCode([DisallowNull] CieXyz obj) => obj.GetHashCode(); @@ -39,6 +42,8 @@ internal readonly struct ApproximateColorProfileComparer : public int GetHashCode([DisallowNull] CieLch obj) => obj.GetHashCode(); + public int GetHashCode([DisallowNull] Rgb obj) => obj.GetHashCode(); + private bool Equals(float x, float y) { float d = x - y; diff --git a/tests/ImageSharp.Tests/ColorProfiles/RgbAndCieXyzConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/RgbAndCieXyzConversionTest.cs new file mode 100644 index 000000000..5e867178a --- /dev/null +++ b/tests/ImageSharp.Tests/ColorProfiles/RgbAndCieXyzConversionTest.cs @@ -0,0 +1,151 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles; + +namespace SixLabors.ImageSharp.Tests.ColorProfiles.Conversion; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated using: +/// +/// +public class RgbAndCieXyzConversionTest +{ + private static readonly ApproximateColorProfileComparer Comparer = new(.0001F); + + [Theory] + [InlineData(0.96422, 1.00000, 0.82521, 1, 1, 1)] + [InlineData(0.00000, 1.00000, 0.00000, 0, 1, 0)] + [InlineData(0.96422, 0.00000, 0.00000, 1, 0, 0.292064)] + [InlineData(0.00000, 0.00000, 0.82521, 0, 0.181415, 1)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.297676, 0.267854, 0.045504, 0.720315, 0.509999, 0.168112)] + public void Convert_XYZ_D50_to_SRGB(float x, float y, float z, float r, float g, float b) + { + // Arrange + CieXyz input = new(x, y, z); + ColorConversionOptions options = new() { WhitePoint = Illuminants.D50, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + ColorProfileConverter converter = new(options); + Rgb expected = new(r, g, b); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + Rgb actual = converter.Convert(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, Comparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], Comparer); + } + } + + [Theory] + [InlineData(0.950470, 1.000000, 1.088830, 1, 1, 1)] + [InlineData(0, 1.000000, 0, 0, 1, 0)] + [InlineData(0.950470, 0, 0, 1, 0, 0.254967)] + [InlineData(0, 0, 1.088830, 0, 0.235458, 1)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.297676, 0.267854, 0.045504, 0.754903, 0.501961, 0.099998)] + public void Convert_XYZ_D65_to_SRGB(float x, float y, float z, float r, float g, float b) + { + // Arrange + CieXyz input = new(x, y, z); + ColorConversionOptions options = new() { WhitePoint = Illuminants.D65, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + ColorProfileConverter converter = new(options); + Rgb expected = new(r, g, b); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + Rgb actual = converter.Convert(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, Comparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], Comparer); + } + } + + [Theory] + [InlineData(1, 1, 1, 0.964220, 1.000000, 0.825210)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 0, 0, 0.436075, 0.222504, 0.013932)] + [InlineData(0, 1, 0, 0.385065, 0.716879, 0.0971045)] + [InlineData(0, 0, 1, 0.143080, 0.060617, 0.714173)] + [InlineData(0.754902, 0.501961, 0.100000, 0.315757, 0.273323, 0.035506)] + public void Convert_SRGB_to_XYZ_D50(float r, float g, float b, float x, float y, float z) + { + // Arrange + Rgb input = new(r, g, b); + ColorConversionOptions options = new() { TargetWhitePoint = Illuminants.D50, RgbWorkingSpace = RgbWorkingSpaces.SRgb }; + ColorProfileConverter converter = new(options); + CieXyz expected = new(x, y, z); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + CieXyz actual = converter.Convert(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, Comparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], Comparer); + } + } + + [Theory] + [InlineData(1, 1, 1, 0.950470, 1.000000, 1.088830)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 0, 0, 0.412456, 0.212673, 0.019334)] + [InlineData(0, 1, 0, 0.357576, 0.715152, 0.119192)] + [InlineData(0, 0, 1, 0.1804375, 0.072175, 0.950304)] + [InlineData(0.754902, 0.501961, 0.100000, 0.297676, 0.267854, 0.045504)] + public void Convert_SRGB_to_XYZ_D65(float r, float g, float b, float x, float y, float z) + { + // Arrange + Rgb input = new(r, g, b); + ColorConversionOptions options = new() { TargetWhitePoint = Illuminants.D65, RgbWorkingSpace = RgbWorkingSpaces.SRgb }; + ColorProfileConverter converter = new(options); + CieXyz expected = new(x, y, z); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + CieXyz actual = converter.Convert(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, Comparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], Comparer); + } + } +}