Skip to content

Commit 72dbc13

Browse files
Complete conversion tests
1 parent bf13f00 commit 72dbc13

File tree

8 files changed

+224
-16
lines changed

8 files changed

+224
-16
lines changed

src/ImageSharp/ColorProfiles/CieLab.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ namespace SixLabors.ImageSharp.ColorProfiles;
2626
/// <param name="b">The b (blue - yellow) component.</param>
2727
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2828
public CieLab(float l, float a, float b)
29-
: this(new Vector3(l, a, b))
3029
{
30+
// Not clamping as documentation about this space only indicates "usual" ranges
31+
this.L = l;
32+
this.A = a;
33+
this.B = b;
3134
}
3235

3336
/// <summary>
@@ -38,7 +41,6 @@ public CieLab(float l, float a, float b)
3841
public CieLab(Vector3 vector)
3942
: this()
4043
{
41-
// Not clamping as documentation about this space only indicates "usual" ranges
4244
this.L = vector.X;
4345
this.A = vector.Y;
4446
this.B = vector.Z;

src/ImageSharp/ColorProfiles/CieLuv.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using System.Numerics;
45
using System.Runtime.CompilerServices;
56

67
namespace SixLabors.ImageSharp.ColorProfiles;
@@ -28,6 +29,19 @@ public CieLuv(float l, float u, float v)
2829
this.V = v;
2930
}
3031

32+
/// <summary>
33+
/// Initializes a new instance of the <see cref="CieLuv"/> struct.
34+
/// </summary>
35+
/// <param name="vector">The vector representing the l, u, v components.</param>
36+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
37+
public CieLuv(Vector3 vector)
38+
: this()
39+
{
40+
this.L = vector.X;
41+
this.U = vector.Y;
42+
this.V = vector.Z;
43+
}
44+
3145
/// <summary>
3246
/// Gets the lightness dimension
3347
/// <remarks>A value usually ranging between 0 and 100.</remarks>

src/ImageSharp/ColorProfiles/CieXyz.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Numerics;
55
using System.Runtime.CompilerServices;
6+
using System.Runtime.Intrinsics;
67

78
namespace SixLabors.ImageSharp.ColorProfiles;
89

@@ -20,8 +21,11 @@ namespace SixLabors.ImageSharp.ColorProfiles;
2021
/// <param name="z">Z is quasi-equal to blue stimulation, or the S cone of the human eye.</param>
2122
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2223
public CieXyz(float x, float y, float z)
23-
: this(new Vector3(x, y, z))
2424
{
25+
// Not clamping as documentation about this space only indicates "usual" ranges
26+
this.X = x;
27+
this.Y = y;
28+
this.Z = z;
2529
}
2630

2731
/// <summary>
@@ -31,7 +35,6 @@ public CieXyz(float x, float y, float z)
3135
public CieXyz(Vector3 vector)
3236
: this()
3337
{
34-
// Not clamping as documentation about this space only indicates "usual" ranges
3538
this.X = vector.X;
3639
this.Y = vector.Y;
3740
this.Z = vector.Z;

src/ImageSharp/ColorProfiles/Rgb.cs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ namespace SixLabors.ImageSharp.ColorProfiles;
2121
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2222
public Rgb(float r, float g, float b)
2323
{
24+
// Not clamping as this space can exceed "usual" ranges
2425
this.R = r;
2526
this.G = g;
2627
this.B = b;
@@ -31,7 +32,7 @@ public Rgb(float r, float g, float b)
3132
/// </summary>
3233
/// <param name="source">The vector representing the r, g, b components.</param>
3334
[MethodImpl(MethodImplOptions.AggressiveInlining)]
34-
private Rgb(Vector3 source)
35+
public Rgb(Vector3 source)
3536
{
3637
this.R = source.X;
3738
this.G = source.Y;
@@ -149,20 +150,32 @@ public static void ToProfileConnectionSpace(ColorConversionOptions options, Read
149150
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() => ChromaticAdaptionWhitePointSource.RgbWorkingSpace;
150151

151152
/// <summary>
152-
/// Initializes the pixel instance from a generic scaled <see cref="Vector3"/>.
153+
/// Initializes the color instance from a generic scaled <see cref="Vector3"/>.
153154
/// </summary>
154-
/// <param name="source">The vector to load the pixel from.</param>
155+
/// <param name="source">The vector to load the color from.</param>
155156
/// <returns>The <see cref="Rgb"/>.</returns>
156157
[MethodImpl(MethodImplOptions.AggressiveInlining)]
157-
public static Rgb FromScaledVector3(Vector3 source) => new(source);
158+
public static Rgb FromScaledVector3(Vector3 source) => new(Vector3.Clamp(source, Vector3.Zero, Vector3.One));
158159

159160
/// <summary>
160-
/// Initializes the pixel instance from a generic scaled <see cref="Vector4"/>.
161+
/// Initializes the color instance from a generic scaled <see cref="Vector4"/>.
161162
/// </summary>
162-
/// <param name="source">The vector to load the pixel from.</param>
163+
/// <param name="source">The vector to load the color from.</param>
163164
/// <returns>The <see cref="Rgb"/>.</returns>
164165
[MethodImpl(MethodImplOptions.AggressiveInlining)]
165-
public static Rgb FromScaledVector4(Vector4 source) => new(source.X, source.Y, source.Z);
166+
public static Rgb FromScaledVector4(Vector4 source)
167+
{
168+
source = Vector4.Clamp(source, Vector4.Zero, Vector4.One);
169+
return new(source.X, source.Y, source.Z);
170+
}
171+
172+
/// <summary>
173+
/// Initializes the color instance for a source clamped between <value>0</value> and <value>1</value>
174+
/// </summary>
175+
/// <param name="source">The source to load the color from.</param>
176+
/// <returns>The <see cref="Rgb"/>.</returns>
177+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
178+
public static Rgb Clamp(Rgb source) => new(Vector3.Clamp(new(source.R, source.G, source.B), Vector3.Zero, Vector3.One));
166179

167180
/// <summary>
168181
/// Expands the color into a generic ("scaled") <see cref="Vector3"/> representation
@@ -171,7 +184,15 @@ public static void ToProfileConnectionSpace(ColorConversionOptions options, Read
171184
/// </summary>
172185
/// <returns>The <see cref="Vector3"/>.</returns>
173186
[MethodImpl(MethodImplOptions.AggressiveInlining)]
174-
public Vector3 ToScaledVector3() => new(this.R, this.G, this.B);
187+
public Vector3 ToScaledVector3() => Clamp(this).ToVector3();
188+
189+
/// <summary>
190+
/// Expands the color into a generic <see cref="Vector3"/> representation.
191+
/// The vector components are typically expanded in least to greatest significance order.
192+
/// </summary>
193+
/// <returns>The <see cref="Vector3"/>.</returns>
194+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
195+
public Vector3 ToVector3() => new(this.R, this.G, this.B);
175196

176197
/// <summary>
177198
/// Expands the color into a generic ("scaled") <see cref="Vector4"/> representation
@@ -180,7 +201,7 @@ public static void ToProfileConnectionSpace(ColorConversionOptions options, Read
180201
/// </summary>
181202
/// <returns>The <see cref="Vector4"/>.</returns>
182203
[MethodImpl(MethodImplOptions.AggressiveInlining)]
183-
public Vector4 ToScaledVector4() => new(this.R, this.G, this.B, 1f);
204+
public Vector4 ToScaledVector4() => new(this.ToScaledVector3(), 1f);
184205

185206
private static Matrix4x4 GetCieXyzToRgbMatrix(RgbWorkingSpace workingSpace)
186207
{

tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHunterLabConversionTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class CieLuvAndHunterLabConversionTests
1515
[Theory]
1616
[InlineData(0, 0, 0, 0, 0, 0)]
1717
[InlineData(36.0555, 93.6901, 10.01514, 30.59289, 48.55542, 9.80487)]
18-
public void Convert_CieLuv_to_HunterLab(float l, float u, float v, float l2, float a, float b)
18+
public void Convert_CieLuv_To_HunterLab(float l, float u, float v, float l2, float a, float b)
1919
{
2020
// Arrange
2121
CieLuv input = new(l, u, v);
@@ -44,7 +44,7 @@ public void Convert_CieLuv_to_HunterLab(float l, float u, float v, float l2, flo
4444
[Theory]
4545
[InlineData(0, 0, 0, 0, 0, 0)]
4646
[InlineData(30.59289, 48.55542, 9.80487, 36.0555, 93.6901, 10.01514)]
47-
public void Convert_HunterLab_to_CieLuv(float l2, float a, float b, float l, float u, float v)
47+
public void Convert_HunterLab_To_CieLuv(float l2, float a, float b, float l, float u, float v)
4848
{
4949
// Arrange
5050
HunterLab input = new(l2, a, b);
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Numerics;
5+
using SixLabors.ImageSharp.ColorProfiles.Companding;
6+
7+
namespace SixLabors.ImageSharp.Tests.ColorProfiles;
8+
9+
/// <summary>
10+
/// Tests various companding algorithms. Expanded numbers are hand calculated from formulas online.
11+
/// </summary>
12+
public class CompandingTests
13+
{
14+
private static readonly ApproximateFloatComparer Comparer = new(.000001F);
15+
16+
[Fact]
17+
public void Rec2020Companding_IsCorrect()
18+
{
19+
Vector4 input = new(.667F);
20+
Vector4 e = Rec2020Companding.Expand(input);
21+
Vector4 c = Rec2020Companding.Compress(e);
22+
CompandingIsCorrectImpl(e, c, .44847462F, input);
23+
}
24+
25+
[Fact]
26+
public void Rec709Companding_IsCorrect()
27+
{
28+
Vector4 input = new(.667F);
29+
Vector4 e = Rec709Companding.Expand(input);
30+
Vector4 c = Rec709Companding.Compress(e);
31+
CompandingIsCorrectImpl(e, c, .4483577F, input);
32+
}
33+
34+
[Fact]
35+
public void SRgbCompanding_IsCorrect()
36+
{
37+
Vector4 input = new(.667F);
38+
Vector4 e = SRgbCompanding.Expand(input);
39+
Vector4 c = SRgbCompanding.Compress(e);
40+
CompandingIsCorrectImpl(e, c, .40242353F, input);
41+
}
42+
43+
[Theory]
44+
[InlineData(0)]
45+
[InlineData(1)]
46+
[InlineData(30)]
47+
public void SRgbCompanding_Expand_VectorSpan(int length)
48+
{
49+
Random rnd = new(42);
50+
Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1);
51+
Vector4[] expected = new Vector4[source.Length];
52+
53+
for (int i = 0; i < source.Length; i++)
54+
{
55+
expected[i] = SRgbCompanding.Expand(source[i]);
56+
}
57+
58+
SRgbCompanding.Expand(source);
59+
60+
Assert.Equal(expected, source, Comparer);
61+
}
62+
63+
[Theory]
64+
[InlineData(0)]
65+
[InlineData(1)]
66+
[InlineData(30)]
67+
public void SRgbCompanding_Compress_VectorSpan(int length)
68+
{
69+
Random rnd = new(42);
70+
Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1);
71+
Vector4[] expected = new Vector4[source.Length];
72+
73+
for (int i = 0; i < source.Length; i++)
74+
{
75+
expected[i] = SRgbCompanding.Compress(source[i]);
76+
}
77+
78+
SRgbCompanding.Compress(source);
79+
80+
Assert.Equal(expected, source, Comparer);
81+
}
82+
83+
[Fact]
84+
public void GammaCompanding_IsCorrect()
85+
{
86+
const double gamma = 2.2;
87+
Vector4 input = new(.667F);
88+
Vector4 e = GammaCompanding.Expand(input, gamma);
89+
Vector4 c = GammaCompanding.Compress(e, gamma);
90+
CompandingIsCorrectImpl(e, c, .41027668F, input);
91+
}
92+
93+
[Fact]
94+
public void LCompanding_IsCorrect()
95+
{
96+
Vector4 input = new(.667F);
97+
Vector4 e = LCompanding.Expand(input);
98+
Vector4 c = LCompanding.Compress(e);
99+
CompandingIsCorrectImpl(e, c, .36236193F, input);
100+
}
101+
102+
private static void CompandingIsCorrectImpl(Vector4 e, Vector4 c, float expanded, Vector4 compressed)
103+
{
104+
// W (alpha) is already the linear representation of the color.
105+
Assert.Equal(new Vector4(expanded, expanded, expanded, e.W), e, Comparer);
106+
Assert.Equal(compressed, c, Comparer);
107+
}
108+
}

tests/ImageSharp.Tests/ColorProfiles/RgbAndHslConversionTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
using SixLabors.ImageSharp.ColorProfiles;
55

6-
namespace SixLabors.ImageSharp.Tests.ColorProfiles.Conversion;
6+
namespace SixLabors.ImageSharp.Tests.ColorProfiles;
77

88
/// <summary>
99
/// Tests <see cref="Rgb"/>-<see cref="Hsl"/> conversions.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Numerics;
5+
using SixLabors.ImageSharp.ColorProfiles;
6+
7+
namespace SixLabors.ImageSharp.Tests.ColorProfiles;
8+
9+
public class StringRepresentationTests
10+
{
11+
private static readonly Vector3 One = new(1);
12+
private static readonly Vector3 Zero = new(0);
13+
private static readonly Vector3 Random = new(42.4F, 94.5F, 83.4F);
14+
15+
public static readonly TheoryData<object, string> TestData = new()
16+
{
17+
{ new CieLab(Zero), "CieLab(0, 0, 0)" },
18+
{ new CieLch(Zero), "CieLch(0, 0, 0)" },
19+
{ new CieLchuv(Zero), "CieLchuv(0, 0, 0)" },
20+
{ new CieLuv(Zero), "CieLuv(0, 0, 0)" },
21+
{ new CieXyz(Zero), "CieXyz(0, 0, 0)" },
22+
{ new CieXyy(Zero), "CieXyy(0, 0, 0)" },
23+
{ new HunterLab(Zero), "HunterLab(0, 0, 0)" },
24+
{ new Lms(Zero), "Lms(0, 0, 0)" },
25+
{ new Rgb(Zero), "Rgb(0, 0, 0)" },
26+
{ new Hsl(Zero), "Hsl(0, 0, 0)" },
27+
{ new Hsv(Zero), "Hsv(0, 0, 0)" },
28+
{ new YCbCr(Zero), "YCbCr(0, 0, 0)" },
29+
{ new CieLab(One), "CieLab(1, 1, 1)" },
30+
{ new CieLch(One), "CieLch(1, 1, 1)" },
31+
{ new CieLchuv(One), "CieLchuv(1, 1, 1)" },
32+
{ new CieLuv(One), "CieLuv(1, 1, 1)" },
33+
{ new CieXyz(One), "CieXyz(1, 1, 1)" },
34+
{ new CieXyy(One), "CieXyy(1, 1, 1)" },
35+
{ new HunterLab(One), "HunterLab(1, 1, 1)" },
36+
{ new Lms(One), "Lms(1, 1, 1)" },
37+
{ new Rgb(One), "Rgb(1, 1, 1)" },
38+
{ new Hsl(One), "Hsl(1, 1, 1)" },
39+
{ new Hsv(One), "Hsv(1, 1, 1)" },
40+
{ new YCbCr(One), "YCbCr(1, 1, 1)" },
41+
{ new CieXyChromaticityCoordinates(1, 1), "CieXyChromaticityCoordinates(1, 1)" },
42+
{ new CieLab(Random), "CieLab(42.4, 94.5, 83.4)" },
43+
{ new CieLch(Random), "CieLch(42.4, 94.5, 83.4)" },
44+
{ new CieLchuv(Random), "CieLchuv(42.4, 94.5, 83.4)" },
45+
{ new CieLuv(Random), "CieLuv(42.4, 94.5, 83.4)" },
46+
{ new CieXyz(Random), "CieXyz(42.4, 94.5, 83.4)" },
47+
{ new CieXyy(Random), "CieXyy(42.4, 94.5, 83.4)" },
48+
{ new HunterLab(Random), "HunterLab(42.4, 94.5, 83.4)" },
49+
{ new Lms(Random), "Lms(42.4, 94.5, 83.4)" },
50+
{ new Rgb(Random), "Rgb(42.4, 94.5, 83.4)" },
51+
{ Rgb.Clamp(new Rgb(Random)), "Rgb(1, 1, 1)" },
52+
{ new Hsl(Random), "Hsl(42.4, 1, 1)" }, // clamping to 1 is expected
53+
{ new Hsv(Random), "Hsv(42.4, 1, 1)" }, // clamping to 1 is expected
54+
{ new YCbCr(Random), "YCbCr(42.4, 94.5, 83.4)" },
55+
};
56+
57+
[Theory]
58+
[MemberData(nameof(TestData))]
59+
public void StringRepresentationsAreCorrect(object color, string text) => Assert.Equal(text, color.ToString());
60+
}

0 commit comments

Comments
 (0)