Skip to content

Commit 23e000f

Browse files
Merge pull request #2739 from SixLabors/js/colorspace-converter
Simplify Color Space Conversion APIs
2 parents 59de13c + 71d49ae commit 23e000f

File tree

246 files changed

+7355
-15074
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

246 files changed

+7355
-15074
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.ColorProfiles;
5+
6+
/// <summary>
7+
/// Enumerate the possible sources of the white point used in chromatic adaptation.
8+
/// </summary>
9+
public enum ChromaticAdaptionWhitePointSource
10+
{
11+
/// <summary>
12+
/// The white point of the source color space.
13+
/// </summary>
14+
WhitePoint,
15+
16+
/// <summary>
17+
/// The white point of the source working space.
18+
/// </summary>
19+
RgbWorkingSpace
20+
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4-
namespace SixLabors.ImageSharp.ColorSpaces.Conversion;
4+
namespace SixLabors.ImageSharp.ColorProfiles;
55

66
/// <summary>
77
/// Constants use for Cie conversion calculations
@@ -12,10 +12,10 @@ internal static class CieConstants
1212
/// <summary>
1313
/// 216F / 24389F
1414
/// </summary>
15-
public const float Epsilon = 0.008856452F;
15+
public const float Epsilon = 216f / 24389f;
1616

1717
/// <summary>
1818
/// 24389F / 27F
1919
/// </summary>
20-
public const float Kappa = 903.2963F;
20+
public const float Kappa = 24389f / 27f;
2121
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Numerics;
5+
using System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices;
7+
8+
namespace SixLabors.ImageSharp.ColorProfiles;
9+
10+
/// <summary>
11+
/// Represents a CIE L*a*b* 1976 color.
12+
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space"/>
13+
/// </summary>
14+
[StructLayout(LayoutKind.Sequential)]
15+
public readonly struct CieLab : IProfileConnectingSpace<CieLab, CieXyz>
16+
{
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="CieLab"/> struct.
19+
/// </summary>
20+
/// <param name="l">The lightness dimension.</param>
21+
/// <param name="a">The a (green - magenta) component.</param>
22+
/// <param name="b">The b (blue - yellow) component.</param>
23+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
24+
public CieLab(float l, float a, float b)
25+
{
26+
// Not clamping as documentation about this space only indicates "usual" ranges
27+
this.L = l;
28+
this.A = a;
29+
this.B = b;
30+
}
31+
32+
/// <summary>
33+
/// Initializes a new instance of the <see cref="CieLab"/> struct.
34+
/// </summary>
35+
/// <param name="vector">The vector representing the l, a, b components.</param>
36+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
37+
public CieLab(Vector3 vector)
38+
: this()
39+
{
40+
this.L = vector.X;
41+
this.A = vector.Y;
42+
this.B = vector.Z;
43+
}
44+
45+
/// <summary>
46+
/// Gets the lightness dimension.
47+
/// <remarks>A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white).</remarks>
48+
/// </summary>
49+
public float L { get; }
50+
51+
/// <summary>
52+
/// Gets the a color component.
53+
/// <remarks>A value usually ranging from -100 to 100. Negative is green, positive magenta.</remarks>
54+
/// </summary>
55+
public float A { get; }
56+
57+
/// <summary>
58+
/// Gets the b color component.
59+
/// <remarks>A value usually ranging from -100 to 100. Negative is blue, positive is yellow</remarks>
60+
/// </summary>
61+
public float B { get; }
62+
63+
/// <summary>
64+
/// Compares two <see cref="CieLab"/> objects for equality.
65+
/// </summary>
66+
/// <param name="left">The <see cref="CieLab"/> on the left side of the operand.</param>
67+
/// <param name="right">The <see cref="CieLab"/> 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+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
72+
public static bool operator ==(CieLab left, CieLab right) => left.Equals(right);
73+
74+
/// <summary>
75+
/// Compares two <see cref="CieLab"/> objects for inequality
76+
/// </summary>
77+
/// <param name="left">The <see cref="CieLab"/> on the left side of the operand.</param>
78+
/// <param name="right">The <see cref="CieLab"/> on the right side of the operand.</param>
79+
/// <returns>
80+
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
81+
/// </returns>
82+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
83+
public static bool operator !=(CieLab left, CieLab right) => !left.Equals(right);
84+
85+
/// <inheritdoc/>
86+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
87+
public static CieLab FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source)
88+
{
89+
// Conversion algorithm described here:
90+
// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
91+
CieXyz whitePoint = options.TargetWhitePoint;
92+
float wx = whitePoint.X, wy = whitePoint.Y, wz = whitePoint.Z;
93+
94+
float xr = source.X / wx, yr = source.Y / wy, zr = source.Z / wz;
95+
96+
const float inv116 = 1 / 116F;
97+
98+
float fx = xr > CieConstants.Epsilon ? MathF.Pow(xr, 0.3333333F) : ((CieConstants.Kappa * xr) + 16F) * inv116;
99+
float fy = yr > CieConstants.Epsilon ? MathF.Pow(yr, 0.3333333F) : ((CieConstants.Kappa * yr) + 16F) * inv116;
100+
float fz = zr > CieConstants.Epsilon ? MathF.Pow(zr, 0.3333333F) : ((CieConstants.Kappa * zr) + 16F) * inv116;
101+
102+
float l = (116F * fy) - 16F;
103+
float a = 500F * (fx - fy);
104+
float b = 200F * (fy - fz);
105+
106+
return new CieLab(l, a, b);
107+
}
108+
109+
/// <inheritdoc/>
110+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
111+
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<CieLab> destination)
112+
{
113+
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
114+
115+
for (int i = 0; i < source.Length; i++)
116+
{
117+
CieXyz xyz = source[i];
118+
destination[i] = FromProfileConnectingSpace(options, in xyz);
119+
}
120+
}
121+
122+
/// <inheritdoc/>
123+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
124+
public CieXyz ToProfileConnectingSpace(ColorConversionOptions options)
125+
{
126+
// Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
127+
float l = this.L, a = this.A, b = this.B;
128+
float fy = (l + 16) / 116F;
129+
float fx = (a / 500F) + fy;
130+
float fz = fy - (b / 200F);
131+
132+
float fx3 = Numerics.Pow3(fx);
133+
float fz3 = Numerics.Pow3(fz);
134+
135+
float xr = fx3 > CieConstants.Epsilon ? fx3 : ((116F * fx) - 16F) / CieConstants.Kappa;
136+
float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? Numerics.Pow3((l + 16F) / 116F) : l / CieConstants.Kappa;
137+
float zr = fz3 > CieConstants.Epsilon ? fz3 : ((116F * fz) - 16F) / CieConstants.Kappa;
138+
139+
CieXyz whitePoint = options.WhitePoint;
140+
Vector3 wxyz = new(whitePoint.X, whitePoint.Y, whitePoint.Z);
141+
Vector3 xyzr = new(xr, yr, zr);
142+
143+
return new(xyzr * wxyz);
144+
}
145+
146+
/// <inheritdoc/>
147+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
148+
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieLab> source, Span<CieXyz> destination)
149+
{
150+
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
151+
152+
for (int i = 0; i < source.Length; i++)
153+
{
154+
CieLab 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+
public override int GetHashCode() => HashCode.Combine(this.L, this.A, this.B);
165+
166+
/// <inheritdoc/>
167+
public override string ToString() => FormattableString.Invariant($"CieLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})");
168+
169+
/// <inheritdoc/>
170+
public override bool Equals(object? obj) => obj is CieLab other && this.Equals(other);
171+
172+
/// <inheritdoc/>
173+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
174+
public bool Equals(CieLab other)
175+
=> this.AsVector3Unsafe() == other.AsVector3Unsafe();
176+
177+
private Vector3 AsVector3Unsafe() => Unsafe.As<CieLab, Vector3>(ref Unsafe.AsRef(in this));
178+
}

0 commit comments

Comments
 (0)