From 1d27038551bc5054b908edbbc83d4940ac172fdb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 9 May 2025 23:00:07 +1000 Subject: [PATCH] Add luminance color profile type and cleanup --- src/ImageSharp/ColorProfiles/CieLab.cs | 2 - src/ImageSharp/ColorProfiles/CieLch.cs | 14 +- src/ImageSharp/ColorProfiles/CieLchuv.cs | 14 +- src/ImageSharp/ColorProfiles/CieLuv.cs | 1 - src/ImageSharp/ColorProfiles/CieXyy.cs | 2 - src/ImageSharp/ColorProfiles/CieXyz.cs | 1 - src/ImageSharp/ColorProfiles/Cmyk.cs | 15 +- .../ColorProfiles/ColorConversionOptions.cs | 5 + src/ImageSharp/ColorProfiles/Hsl.cs | 13 +- src/ImageSharp/ColorProfiles/Hsv.cs | 13 +- src/ImageSharp/ColorProfiles/HunterLab.cs | 1 - src/ImageSharp/ColorProfiles/IColorProfile.cs | 2 +- .../ColorProfiles/KnownYCoefficients.cs | 27 ++++ src/ImageSharp/ColorProfiles/Lms.cs | 1 - src/ImageSharp/ColorProfiles/Rgb.cs | 1 - src/ImageSharp/ColorProfiles/Y.cs | 136 ++++++++++++++++++ src/ImageSharp/ColorProfiles/YCbCr.cs | 16 ++- .../ApproximateColorProfileComparer.cs | 7 +- .../ColorProfiles/RbgAndYConversionTests.cs | 113 +++++++++++++++ .../ImageSharp.Tests/ColorProfiles/YTests.cs | 42 ++++++ 20 files changed, 400 insertions(+), 26 deletions(-) create mode 100644 src/ImageSharp/ColorProfiles/KnownYCoefficients.cs create mode 100644 src/ImageSharp/ColorProfiles/Y.cs create mode 100644 tests/ImageSharp.Tests/ColorProfiles/RbgAndYConversionTests.cs create mode 100644 tests/ImageSharp.Tests/ColorProfiles/YTests.cs diff --git a/src/ImageSharp/ColorProfiles/CieLab.cs b/src/ImageSharp/ColorProfiles/CieLab.cs index 98cb2e613a..ca72dd745a 100644 --- a/src/ImageSharp/ColorProfiles/CieLab.cs +++ b/src/ImageSharp/ColorProfiles/CieLab.cs @@ -4,7 +4,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.ColorProfiles; @@ -36,7 +35,6 @@ public readonly struct CieLab : IProfileConnectingSpace /// The vector representing the l, a, b components. [MethodImpl(MethodImplOptions.AggressiveInlining)] public CieLab(Vector3 vector) - : this() { this.L = vector.X; this.A = vector.Y; diff --git a/src/ImageSharp/ColorProfiles/CieLch.cs b/src/ImageSharp/ColorProfiles/CieLch.cs index 2e495b9a77..e62aa2ba23 100644 --- a/src/ImageSharp/ColorProfiles/CieLch.cs +++ b/src/ImageSharp/ColorProfiles/CieLch.cs @@ -4,7 +4,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.ColorProfiles; @@ -43,6 +42,17 @@ public readonly struct CieLch : IColorProfile this.H = vector.Z; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + private CieLch(Vector3 vector, bool _) +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter + { + vector = Vector3.Clamp(vector, Min, Max); + this.L = vector.X; + this.C = vector.Y; + this.H = vector.Z; + } + /// /// Gets the lightness dimension. /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). @@ -99,7 +109,7 @@ public readonly struct CieLch : IColorProfile Vector3 v3 = source.AsVector3(); v3 *= new Vector3(100, 400, 360); v3 -= new Vector3(0, 200, 0); - return new CieLch(v3); + return new CieLch(v3, true); } /// diff --git a/src/ImageSharp/ColorProfiles/CieLchuv.cs b/src/ImageSharp/ColorProfiles/CieLchuv.cs index c131d4bc64..5478752ddc 100644 --- a/src/ImageSharp/ColorProfiles/CieLchuv.cs +++ b/src/ImageSharp/ColorProfiles/CieLchuv.cs @@ -4,7 +4,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.ColorProfiles; @@ -36,7 +35,6 @@ public readonly struct CieLchuv : IColorProfile /// The vector representing the l, c, h components. [MethodImpl(MethodImplOptions.AggressiveInlining)] public CieLchuv(Vector3 vector) - : this() { vector = Vector3.Clamp(vector, Min, Max); this.L = vector.X; @@ -44,6 +42,16 @@ public readonly struct CieLchuv : IColorProfile this.H = vector.Z; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + private CieLchuv(Vector3 vector, bool _) +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter + { + this.L = vector.X; + this.C = vector.Y; + this.H = vector.Z; + } + /// /// Gets the lightness dimension. /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). @@ -98,7 +106,7 @@ public readonly struct CieLchuv : IColorProfile Vector3 v3 = source.AsVector3(); v3 *= new Vector3(100, 400, 360); v3 -= new Vector3(0, 200, 0); - return new CieLchuv(v3); + return new CieLchuv(v3, true); } /// diff --git a/src/ImageSharp/ColorProfiles/CieLuv.cs b/src/ImageSharp/ColorProfiles/CieLuv.cs index bcc958cb48..b17c433313 100644 --- a/src/ImageSharp/ColorProfiles/CieLuv.cs +++ b/src/ImageSharp/ColorProfiles/CieLuv.cs @@ -37,7 +37,6 @@ public readonly struct CieLuv : IColorProfile /// The vector representing the l, u, v components. [MethodImpl(MethodImplOptions.AggressiveInlining)] public CieLuv(Vector3 vector) - : this() { this.L = vector.X; this.U = vector.Y; diff --git a/src/ImageSharp/ColorProfiles/CieXyy.cs b/src/ImageSharp/ColorProfiles/CieXyy.cs index 7fa943e9ea..744b6195e9 100644 --- a/src/ImageSharp/ColorProfiles/CieXyy.cs +++ b/src/ImageSharp/ColorProfiles/CieXyy.cs @@ -4,7 +4,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.ColorProfiles; @@ -36,7 +35,6 @@ public readonly struct CieXyy : IColorProfile /// The vector representing the x, y, Y components. [MethodImpl(MethodImplOptions.AggressiveInlining)] public CieXyy(Vector3 vector) - : this() { // Not clamping as documentation about this space only indicates "usual" ranges this.X = vector.X; diff --git a/src/ImageSharp/ColorProfiles/CieXyz.cs b/src/ImageSharp/ColorProfiles/CieXyz.cs index a74f03ba30..393cd57ba2 100644 --- a/src/ImageSharp/ColorProfiles/CieXyz.cs +++ b/src/ImageSharp/ColorProfiles/CieXyz.cs @@ -34,7 +34,6 @@ public readonly struct CieXyz : IProfileConnectingSpace /// /// The vector representing the x, y, z components. public CieXyz(Vector3 vector) - : this() { this.X = vector.X; this.Y = vector.Y; diff --git a/src/ImageSharp/ColorProfiles/Cmyk.cs b/src/ImageSharp/ColorProfiles/Cmyk.cs index 4359fa4a96..647d96089a 100644 --- a/src/ImageSharp/ColorProfiles/Cmyk.cs +++ b/src/ImageSharp/ColorProfiles/Cmyk.cs @@ -36,7 +36,18 @@ public readonly struct Cmyk : IColorProfile [MethodImpl(MethodImplOptions.AggressiveInlining)] public Cmyk(Vector4 vector) { - vector = Numerics.Clamp(vector, Min, Max); + vector = Vector4.Clamp(vector, Min, Max); + this.C = vector.X; + this.M = vector.Y; + this.Y = vector.Z; + this.K = vector.W; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + private Cmyk(Vector4 vector, bool _) +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter + { this.C = vector.X; this.M = vector.Y; this.Y = vector.Z; @@ -99,7 +110,7 @@ public readonly struct Cmyk : IColorProfile /// public static Cmyk FromScaledVector4(Vector4 source) - => new(source); + => new(source, true); /// public static void ToScaledVector4(ReadOnlySpan source, Span destination) diff --git a/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs b/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs index 0f05c7e403..1b0ecd4590 100644 --- a/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs +++ b/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs @@ -45,6 +45,11 @@ public class ColorConversionOptions /// public RgbWorkingSpace TargetRgbWorkingSpace { get; init; } = KnownRgbWorkingSpaces.SRgb; + /// + /// Gets the Y (luma) coefficients to use in conversions from RGB. + /// + public Vector3 YCoefficients { get; init; } = KnownYCoefficients.BT709; + /// /// Gets the source ICC profile. /// diff --git a/src/ImageSharp/ColorProfiles/Hsl.cs b/src/ImageSharp/ColorProfiles/Hsl.cs index 6b84f955b8..7a9365fb75 100644 --- a/src/ImageSharp/ColorProfiles/Hsl.cs +++ b/src/ImageSharp/ColorProfiles/Hsl.cs @@ -4,7 +4,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.ColorProfiles; @@ -42,6 +41,16 @@ public readonly struct Hsl : IColorProfile this.L = vector.Z; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + private Hsl(Vector3 vector, bool _) +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter + { + this.H = vector.X; + this.S = vector.Y; + this.L = vector.Z; + } + /// /// Gets the hue component. /// A value ranging between 0 and 360. @@ -90,7 +99,7 @@ public readonly struct Hsl : IColorProfile /// public static Hsl FromScaledVector4(Vector4 source) - => new(source.AsVector3() * 360F); + => new(source.AsVector3() * 360F, true); /// public static void ToScaledVector4(ReadOnlySpan source, Span destination) diff --git a/src/ImageSharp/ColorProfiles/Hsv.cs b/src/ImageSharp/ColorProfiles/Hsv.cs index a7735bb195..1e013fe1fb 100644 --- a/src/ImageSharp/ColorProfiles/Hsv.cs +++ b/src/ImageSharp/ColorProfiles/Hsv.cs @@ -4,7 +4,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.ColorProfiles; @@ -42,6 +41,16 @@ public readonly struct Hsv : IColorProfile this.V = vector.Z; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + private Hsv(Vector3 vector, bool _) +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter + { + this.H = vector.X; + this.S = vector.Y; + this.V = vector.Z; + } + /// /// Gets the hue component. /// A value ranging between 0 and 360. @@ -88,7 +97,7 @@ public readonly struct Hsv : IColorProfile /// public static Hsv FromScaledVector4(Vector4 source) - => new(source.AsVector3() * 360F); + => new(source.AsVector3() * 360F, true); /// public static void ToScaledVector4(ReadOnlySpan source, Span destination) diff --git a/src/ImageSharp/ColorProfiles/HunterLab.cs b/src/ImageSharp/ColorProfiles/HunterLab.cs index d53d4c8134..e978c6de22 100644 --- a/src/ImageSharp/ColorProfiles/HunterLab.cs +++ b/src/ImageSharp/ColorProfiles/HunterLab.cs @@ -4,7 +4,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.ColorProfiles; diff --git a/src/ImageSharp/ColorProfiles/IColorProfile.cs b/src/ImageSharp/ColorProfiles/IColorProfile.cs index f678fb2c96..425e030300 100644 --- a/src/ImageSharp/ColorProfiles/IColorProfile.cs +++ b/src/ImageSharp/ColorProfiles/IColorProfile.cs @@ -30,7 +30,7 @@ public interface IColorProfile : IColorProfile, IEquatable /// The vector components are typically expanded in least to greatest significance order. /// /// The . - Vector4 ToScaledVector4(); + public Vector4 ToScaledVector4(); #pragma warning disable CA1000 // Do not declare static members on generic types /// diff --git a/src/ImageSharp/ColorProfiles/KnownYCoefficients.cs b/src/ImageSharp/ColorProfiles/KnownYCoefficients.cs new file mode 100644 index 0000000000..3189f92505 --- /dev/null +++ b/src/ImageSharp/ColorProfiles/KnownYCoefficients.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.ImageSharp.ColorProfiles; + +/// +/// Provides standard Y (luma) coefficient sets for weighted RGB conversions. +/// +public static class KnownYCoefficients +{ + /// + /// ITU-R BT.601 (SD video standard). + /// + public static readonly Vector3 BT601 = new(0.299F, 0.587F, 0.114F); + + /// + /// ITU-R BT.709 (HD video, sRGB standard). + /// + public static readonly Vector3 BT709 = new(0.2126F, 0.7152F, 0.0722F); + + /// + /// ITU-R BT.2020 (UHD/4K video standard). + /// + public static readonly Vector3 BT2020 = new(0.2627F, 0.6780F, 0.0593F); +} diff --git a/src/ImageSharp/ColorProfiles/Lms.cs b/src/ImageSharp/ColorProfiles/Lms.cs index b3d29c9c9b..fc1e60632e 100644 --- a/src/ImageSharp/ColorProfiles/Lms.cs +++ b/src/ImageSharp/ColorProfiles/Lms.cs @@ -4,7 +4,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.ColorProfiles; diff --git a/src/ImageSharp/ColorProfiles/Rgb.cs b/src/ImageSharp/ColorProfiles/Rgb.cs index eb98318b55..da3af3544c 100644 --- a/src/ImageSharp/ColorProfiles/Rgb.cs +++ b/src/ImageSharp/ColorProfiles/Rgb.cs @@ -4,7 +4,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; namespace SixLabors.ImageSharp.ColorProfiles; diff --git a/src/ImageSharp/ColorProfiles/Y.cs b/src/ImageSharp/ColorProfiles/Y.cs new file mode 100644 index 0000000000..634eeb8582 --- /dev/null +++ b/src/ImageSharp/ColorProfiles/Y.cs @@ -0,0 +1,136 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.ColorProfiles; + +/// +/// Represents a Y (luminance) color. +/// +[StructLayout(LayoutKind.Sequential)] +public readonly struct Y : IColorProfile +{ + /// + /// Initializes a new instance of the struct. + /// + /// The luminance component. + public Y(float l) => this.L = l; + + /// + /// Gets the luminance component. + /// + /// A value ranging between 0 and 1. + public float L { get; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Y left, Y right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Y left, Y right) => !left.Equals(right); + + /// + public static Y FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) + { + Vector3 weights = options.YCoefficients; + float l = (weights.X * source.R) + (weights.Y * source.G) + (weights.Z * source.B); + return new Y(l); + } + + /// + public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // TODO: We can optimize this by using SIMD + for (int i = 0; i < source.Length; i++) + { + Rgb rgb = source[i]; + destination[i] = FromProfileConnectingSpace(options, in rgb); + } + } + + /// + public Vector4 ToScaledVector4() => new(this.L); + + /// + public static Y FromScaledVector4(Vector4 source) => new(source.X); + + /// + public static void ToScaledVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // TODO: Optimize via SIMD + for (int i = 0; i < source.Length; i++) + { + destination[i] = source[i].ToScaledVector4(); + } + } + + /// + public static void FromScaledVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // TODO: Optimize via SIMD + for (int i = 0; i < source.Length; i++) + { + destination[i] = FromScaledVector4(source[i]); + } + } + + /// + public Rgb ToProfileConnectingSpace(ColorConversionOptions options) + => new(this.L, this.L, this.L); + + /// + public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // TODO: We can optimize this by using SIMD + for (int i = 0; i < source.Length; i++) + { + destination[i] = source[i].ToProfileConnectingSpace(options); + } + } + + /// + public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() + => ChromaticAdaptionWhitePointSource.RgbWorkingSpace; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() + => this.L.GetHashCode(); + + /// + public override string ToString() + => FormattableString.Invariant($"Y({this.L:#0.##})"); + + /// + public override bool Equals(object? obj) + => obj is Y other && this.Equals(other); + + /// + public bool Equals(Y other) => this.L == other.L; +} diff --git a/src/ImageSharp/ColorProfiles/YCbCr.cs b/src/ImageSharp/ColorProfiles/YCbCr.cs index dfd9351dcc..20c898f966 100644 --- a/src/ImageSharp/ColorProfiles/YCbCr.cs +++ b/src/ImageSharp/ColorProfiles/YCbCr.cs @@ -4,7 +4,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.ColorProfiles; @@ -44,6 +43,16 @@ public readonly struct YCbCr : IColorProfile this.Cr = vector.Z; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + private YCbCr(Vector3 vector, bool _) +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter + { + this.Y = vector.X; + this.Cb = vector.Y; + this.Cr = vector.Z; + } + /// /// Gets the Y luminance component. /// A value ranging between 0 and 255. @@ -97,7 +106,7 @@ public readonly struct YCbCr : IColorProfile { Vector3 v3 = source.AsVector3(); v3 *= Max; - return new YCbCr(v3); + return new YCbCr(v3, true); } /// @@ -174,8 +183,7 @@ public readonly struct YCbCr : IColorProfile // TODO: We can optimize this by using SIMD for (int i = 0; i < source.Length; i++) { - YCbCr ycbcr = source[i]; - destination[i] = ycbcr.ToProfileConnectingSpace(options); + destination[i] = source[i].ToProfileConnectingSpace(options); } } diff --git a/tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs b/tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs index 56d495a87a..7376ede6e5 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs @@ -22,7 +22,8 @@ internal readonly struct ApproximateColorProfileComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, - IEqualityComparer + IEqualityComparer, + IEqualityComparer { private readonly float epsilon; @@ -58,6 +59,8 @@ internal readonly struct ApproximateColorProfileComparer : public bool Equals(HunterLab x, HunterLab y) => this.Equals(x.L, y.L) && this.Equals(x.A, y.A) && this.Equals(x.B, y.B); + public bool Equals(Y x, Y y) => this.Equals(x.L, y.L); + public int GetHashCode([DisallowNull] CieLab obj) => obj.GetHashCode(); public int GetHashCode([DisallowNull] CieXyz obj) => obj.GetHashCode(); @@ -84,6 +87,8 @@ internal readonly struct ApproximateColorProfileComparer : public int GetHashCode([DisallowNull] HunterLab obj) => obj.GetHashCode(); + public int GetHashCode([DisallowNull] Y obj) => obj.GetHashCode(); + private bool Equals(float x, float y) { float d = x - y; diff --git a/tests/ImageSharp.Tests/ColorProfiles/RbgAndYConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/RbgAndYConversionTests.cs new file mode 100644 index 0000000000..aacabc8baa --- /dev/null +++ b/tests/ImageSharp.Tests/ColorProfiles/RbgAndYConversionTests.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.ColorProfiles; + +namespace SixLabors.ImageSharp.Tests.ColorProfiles; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated mathematically +/// +public class RbgAndYConversionTests +{ + private static readonly ApproximateColorProfileComparer Comparer = new(.001F); + + [Theory] + [InlineData(0F, 0F, 0F, 0F)] + [InlineData(0.5F, 0.5F, 0.5F, 0.5F)] + [InlineData(1F, 1F, 1F, 1F)] + public void Convert_Y_To_Rgb(float y, float r, float g, float b) + { + // Arrange + Y input = new(y); + Rgb expected = new(r, g, b); + ColorProfileConverter converter = new(); + + Span inputSpan = new Y[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(0F, 0F, 0F, 0F)] + [InlineData(0.5F, 0.5F, 0.5F, 0.5F)] + [InlineData(1F, 1F, 1F, 1F)] + public void Convert_Rgb_To_Y_BT601(float r, float g, float b, float y) + { + ColorConversionOptions options = new() + { + YCoefficients = KnownYCoefficients.BT601 + }; + + Convert_Rgb_To_Y_Core(r, g, b, y, options); + } + + [Theory] + [InlineData(0F, 0F, 0F, 0F)] + [InlineData(0.5F, 0.5F, 0.5F, 0.5F)] + [InlineData(1F, 1F, 1F, 1F)] + public void Convert_Rgb_To_Y_BT709(float r, float g, float b, float y) + { + ColorConversionOptions options = new() + { + YCoefficients = KnownYCoefficients.BT709 + }; + + Convert_Rgb_To_Y_Core(r, g, b, y, options); + } + + [Theory] + [InlineData(0F, 0F, 0F, 0F)] + [InlineData(0.5F, 0.5F, 0.5F, 0.49999997F)] + [InlineData(1F, 1F, 1F, 0.99999994F)] + public void Convert_Rgb_To_Y_BT2020(float r, float g, float b, float y) + { + ColorConversionOptions options = new() + { + YCoefficients = KnownYCoefficients.BT2020 + }; + + Convert_Rgb_To_Y_Core(r, g, b, y, options); + } + + private static void Convert_Rgb_To_Y_Core(float r, float g, float b, float y, ColorConversionOptions options) + { + // Arrange + Rgb input = new(r, g, b); + Y expected = new(y); + ColorProfileConverter converter = new(options); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new Y[5]; + + // Act + Y 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); + } + } +} diff --git a/tests/ImageSharp.Tests/ColorProfiles/YTests.cs b/tests/ImageSharp.Tests/ColorProfiles/YTests.cs new file mode 100644 index 0000000000..7a1799a62d --- /dev/null +++ b/tests/ImageSharp.Tests/ColorProfiles/YTests.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + + +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.ColorProfiles; + +namespace SixLabors.ImageSharp.Tests.ColorProfiles; + +/// +/// Tests the struct. +/// +[Trait("Color", "Conversion")] +public class YTests +{ + [Fact] + public void YConstructorAssignsFields() + { + const float y = .75F; + Y yValue = new(y); + + Assert.Equal(y, yValue.L); + } + + [Fact] + public void YEquality() + { + Y x = default; + Y y = new(1F); + Assert.True(default == default(Y)); + Assert.False(default != default(Y)); + Assert.Equal(default, default(Y)); + Assert.Equal(new Y(1), new Y(1)); + + Assert.Equal(new Y(.5F), new Y(.5F)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } +}