Skip to content

Commit 698d76e

Browse files
Create HunterLab.cs
1 parent bb6502c commit 698d76e

1 file changed

Lines changed: 202 additions & 0 deletions

File tree

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Numerics;
5+
using System.Runtime.CompilerServices;
6+
7+
namespace SixLabors.ImageSharp.ColorProfiles;
8+
9+
/// <summary>
10+
/// Represents an Hunter LAB color.
11+
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space"/>.
12+
/// </summary>
13+
public readonly struct HunterLab : IColorProfile<HunterLab, CieXyz>
14+
{
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="HunterLab"/> struct.
17+
/// </summary>
18+
/// <param name="l">The lightness dimension.</param>
19+
/// <param name="a">The a (green - magenta) component.</param>
20+
/// <param name="b">The b (blue - yellow) component.</param>
21+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
22+
public HunterLab(float l, float a, float b)
23+
: this(new Vector3(l, a, b))
24+
{
25+
}
26+
27+
/// <summary>
28+
/// Initializes a new instance of the <see cref="HunterLab"/> struct.
29+
/// </summary>
30+
/// <param name="vector">The vector representing the l a b components.</param>
31+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
32+
public HunterLab(Vector3 vector)
33+
{
34+
// Not clamping as documentation about this space only indicates "usual" ranges
35+
this.L = vector.X;
36+
this.A = vector.Y;
37+
this.B = vector.Z;
38+
}
39+
40+
/// <summary>
41+
/// Gets the lightness dimension.
42+
/// <remarks>A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white).</remarks>
43+
/// </summary>
44+
public readonly float L { get; }
45+
46+
/// <summary>
47+
/// Gets the a color component.
48+
/// <remarks>A value usually ranging from -100 to 100. Negative is green, positive magenta.</remarks>
49+
/// </summary>
50+
public readonly float A { get; }
51+
52+
/// <summary>
53+
/// Gets the b color component.
54+
/// <remarks>A value usually ranging from -100 to 100. Negative is blue, positive is yellow</remarks>
55+
/// </summary>
56+
public readonly float B { get; }
57+
58+
/// <summary>
59+
/// Gets the reference white point of this color.
60+
/// </summary>
61+
public readonly CieXyz WhitePoint { get; }
62+
63+
/// <summary>
64+
/// Compares two <see cref="HunterLab"/> objects for equality.
65+
/// </summary>
66+
/// <param name="left">The <see cref="HunterLab"/> on the left side of the operand.</param>
67+
/// <param name="right">The <see cref="HunterLab"/> on the right side of the operand.</param>
68+
/// <returns>
69+
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
70+
/// </returns>
71+
public static bool operator ==(HunterLab left, HunterLab right) => left.Equals(right);
72+
73+
/// <summary>
74+
/// Compares two <see cref="HunterLab"/> objects for inequality
75+
/// </summary>
76+
/// <param name="left">The <see cref="HunterLab"/> on the left side of the operand.</param>
77+
/// <param name="right">The <see cref="HunterLab"/> on the right side of the operand.</param>
78+
/// <returns>
79+
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
80+
/// </returns>
81+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
82+
public static bool operator !=(HunterLab left, HunterLab right) => !left.Equals(right);
83+
84+
/// <inheritdoc/>
85+
public static HunterLab FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source)
86+
{
87+
// Conversion algorithm described here:
88+
// http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab
89+
CieXyz whitePoint = options.TargetWhitePoint;
90+
float x = source.X, y = source.Y, z = source.Z;
91+
float xn = whitePoint.X, yn = whitePoint.Y, zn = whitePoint.Z;
92+
93+
float ka = ComputeKa(in whitePoint);
94+
float kb = ComputeKb(in whitePoint);
95+
96+
float yByYn = y / yn;
97+
float sqrtYbyYn = MathF.Sqrt(yByYn);
98+
float l = 100 * sqrtYbyYn;
99+
float a = ka * (((x / xn) - yByYn) / sqrtYbyYn);
100+
float b = kb * ((yByYn - (z / zn)) / sqrtYbyYn);
101+
102+
if (float.IsNaN(a))
103+
{
104+
a = 0;
105+
}
106+
107+
if (float.IsNaN(b))
108+
{
109+
b = 0;
110+
}
111+
112+
return new HunterLab(l, a, b);
113+
}
114+
115+
/// <inheritdoc/>
116+
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<HunterLab> destination)
117+
{
118+
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
119+
for (int i = 0; i < source.Length; i++)
120+
{
121+
CieXyz xyz = source[i];
122+
destination[i] = FromProfileConnectingSpace(options, in xyz);
123+
}
124+
}
125+
126+
/// <inheritdoc/>
127+
public CieXyz ToProfileConnectingSpace(ColorConversionOptions options)
128+
{
129+
// Conversion algorithm described here:
130+
// http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab
131+
CieXyz whitePoint = options.WhitePoint;
132+
float l = this.L, a = this.A, b = this.B;
133+
float xn = whitePoint.X, yn = whitePoint.Y, zn = whitePoint.Z;
134+
135+
float ka = ComputeKa(whitePoint);
136+
float kb = ComputeKb(whitePoint);
137+
138+
float pow = Numerics.Pow2(l / 100F);
139+
float sqrtPow = MathF.Sqrt(pow);
140+
float y = pow * yn;
141+
142+
float x = (((a / ka) * sqrtPow) + pow) * xn;
143+
float z = (((b / kb) * sqrtPow) - pow) * (-zn);
144+
145+
return new CieXyz(x, y, z);
146+
}
147+
148+
/// <inheritdoc/>
149+
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<HunterLab> source, Span<CieXyz> destination)
150+
{
151+
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
152+
for (int i = 0; i < source.Length; i++)
153+
{
154+
HunterLab lab = source[i];
155+
destination[i] = lab.ToProfileConnectingSpace(options);
156+
}
157+
}
158+
159+
/// <inheritdoc/>
160+
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource()
161+
=> ChromaticAdaptionWhitePointSource.WhitePoint;
162+
163+
/// <inheritdoc/>
164+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
165+
public override int GetHashCode() => HashCode.Combine(this.L, this.A, this.B, this.WhitePoint);
166+
167+
/// <inheritdoc/>
168+
public override string ToString() => FormattableString.Invariant($"HunterLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})");
169+
170+
/// <inheritdoc/>
171+
public override bool Equals(object? obj) => obj is HunterLab other && this.Equals(other);
172+
173+
/// <inheritdoc/>
174+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
175+
public bool Equals(HunterLab other)
176+
=> this.L.Equals(other.L)
177+
&& this.A.Equals(other.A)
178+
&& this.B.Equals(other.B)
179+
&& this.WhitePoint.Equals(other.WhitePoint);
180+
181+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
182+
private static float ComputeKa(in CieXyz whitePoint)
183+
{
184+
if (whitePoint.Equals(Illuminants.C))
185+
{
186+
return 175F;
187+
}
188+
189+
return 100F * (175F / 198.04F) * (whitePoint.X + whitePoint.Y);
190+
}
191+
192+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
193+
private static float ComputeKb(in CieXyz whitePoint)
194+
{
195+
if (whitePoint == Illuminants.C)
196+
{
197+
return 70F;
198+
}
199+
200+
return 100F * (70F / 218.11F) * (whitePoint.Y + whitePoint.Z);
201+
}
202+
}

0 commit comments

Comments
 (0)