Skip to content

Commit b8c73e1

Browse files
Add CMYK
1 parent 7da1cae commit b8c73e1

8 files changed

Lines changed: 502 additions & 6 deletions

src/ImageSharp/ColorProfiles/CieXyy.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.ColorProfiles;
1818
/// <param name="x">The x chroma component.</param>
1919
/// <param name="y">The y chroma component.</param>
2020
/// <param name="yl">The y luminance component.</param>
21-
[MethodImpl(InliningOptions.ShortMethod)]
21+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2222
public CieXyy(float x, float y, float yl)
2323
{
2424
// Not clamping as documentation about this space only indicates "usual" ranges
@@ -31,7 +31,7 @@ public CieXyy(float x, float y, float yl)
3131
/// Initializes a new instance of the <see cref="CieXyy"/> struct.
3232
/// </summary>
3333
/// <param name="vector">The vector representing the x, y, Y components.</param>
34-
[MethodImpl(InliningOptions.ShortMethod)]
34+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3535
public CieXyy(Vector3 vector)
3636
: this()
3737
{
@@ -67,7 +67,7 @@ public CieXyy(Vector3 vector)
6767
/// <returns>
6868
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
6969
/// </returns>
70-
[MethodImpl(InliningOptions.ShortMethod)]
70+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
7171
public static bool operator ==(CieXyy left, CieXyy right) => left.Equals(right);
7272

7373
/// <summary>
@@ -78,7 +78,7 @@ public CieXyy(Vector3 vector)
7878
/// <returns>
7979
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
8080
/// </returns>
81-
[MethodImpl(InliningOptions.ShortMethod)]
81+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
8282
public static bool operator !=(CieXyy left, CieXyy right) => !left.Equals(right);
8383

8484
/// <inheritdoc/>
@@ -148,7 +148,7 @@ public override string ToString()
148148
public override bool Equals(object? obj) => obj is CieXyy other && this.Equals(other);
149149

150150
/// <inheritdoc/>
151-
[MethodImpl(InliningOptions.ShortMethod)]
151+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
152152
public bool Equals(CieXyy other)
153153
=> this.X.Equals(other.X)
154154
&& this.Y.Equals(other.Y)
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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 CMYK (cyan, magenta, yellow, keyline) color.
11+
/// </summary>
12+
public readonly struct Cmyk : IColorProfile<Cmyk, Rgb>
13+
{
14+
private static readonly Vector4 Min = Vector4.Zero;
15+
private static readonly Vector4 Max = Vector4.One;
16+
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="Cmyk"/> struct.
19+
/// </summary>
20+
/// <param name="c">The cyan component.</param>
21+
/// <param name="m">The magenta component.</param>
22+
/// <param name="y">The yellow component.</param>
23+
/// <param name="k">The keyline black component.</param>
24+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
25+
public Cmyk(float c, float m, float y, float k)
26+
: this(new Vector4(c, m, y, k))
27+
{
28+
}
29+
30+
/// <summary>
31+
/// Initializes a new instance of the <see cref="Cmyk"/> struct.
32+
/// </summary>
33+
/// <param name="vector">The vector representing the c, m, y, k components.</param>
34+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
35+
public Cmyk(Vector4 vector)
36+
{
37+
vector = Numerics.Clamp(vector, Min, Max);
38+
this.C = vector.X;
39+
this.M = vector.Y;
40+
this.Y = vector.Z;
41+
this.K = vector.W;
42+
}
43+
44+
/// <summary>
45+
/// Gets the cyan color component.
46+
/// <remarks>A value ranging between 0 and 1.</remarks>
47+
/// </summary>
48+
public readonly float C { get; }
49+
50+
/// <summary>
51+
/// Gets the magenta color component.
52+
/// <remarks>A value ranging between 0 and 1.</remarks>
53+
/// </summary>
54+
public readonly float M { get; }
55+
56+
/// <summary>
57+
/// Gets the yellow color component.
58+
/// <remarks>A value ranging between 0 and 1.</remarks>
59+
/// </summary>
60+
public readonly float Y { get; }
61+
62+
/// <summary>
63+
/// Gets the keyline black color component.
64+
/// <remarks>A value ranging between 0 and 1.</remarks>
65+
/// </summary>
66+
public readonly float K { get; }
67+
68+
/// <summary>
69+
/// Compares two <see cref="Cmyk"/> objects for equality.
70+
/// </summary>
71+
/// <param name="left">The <see cref="Cmyk"/> on the left side of the operand.</param>
72+
/// <param name="right">The <see cref="Cmyk"/> on the right side of the operand.</param>
73+
/// <returns>
74+
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
75+
/// </returns>
76+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
77+
public static bool operator ==(Cmyk left, Cmyk right) => left.Equals(right);
78+
79+
/// <summary>
80+
/// Compares two <see cref="Cmyk"/> objects for inequality.
81+
/// </summary>
82+
/// <param name="left">The <see cref="Cmyk"/> on the left side of the operand.</param>
83+
/// <param name="right">The <see cref="Cmyk"/> on the right side of the operand.</param>
84+
/// <returns>
85+
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
86+
/// </returns>
87+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
88+
public static bool operator !=(Cmyk left, Cmyk right) => !left.Equals(right);
89+
90+
/// <inheritdoc/>
91+
public static Cmyk FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
92+
{
93+
// To CMY
94+
Vector3 cmy = Vector3.One - source.ToScaledVector3();
95+
96+
// To CMYK
97+
Vector3 k = new(MathF.Min(cmy.X, MathF.Min(cmy.Y, cmy.Z)));
98+
99+
if (MathF.Abs(k.X - 1F) < Constants.Epsilon)
100+
{
101+
return new Cmyk(0, 0, 0, 1F);
102+
}
103+
104+
cmy = (cmy - k) / (Vector3.One - k);
105+
106+
return new Cmyk(cmy.X, cmy.Y, cmy.Z, k.X);
107+
}
108+
109+
/// <inheritdoc/>
110+
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Rgb> source, Span<Cmyk> destination)
111+
{
112+
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
113+
114+
// TODO: We can optimize this by using SIMD
115+
for (int i = 0; i < source.Length; i++)
116+
{
117+
Rgb rgb = source[i];
118+
destination[i] = FromProfileConnectingSpace(options, in rgb);
119+
}
120+
}
121+
122+
/// <inheritdoc/>
123+
public Rgb ToProfileConnectingSpace(ColorConversionOptions options)
124+
{
125+
Vector3 rgb = (Vector3.One - new Vector3(this.C, this.M, this.Y)) * (Vector3.One - new Vector3(this.K));
126+
return Rgb.FromScaledVector3(rgb);
127+
}
128+
129+
/// <inheritdoc/>
130+
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Cmyk> source, Span<Rgb> destination)
131+
{
132+
// TODO: We can possibly optimize this by using SIMD
133+
for (int i = 0; i < source.Length; i++)
134+
{
135+
Cmyk cmyk = source[i];
136+
destination[i] = cmyk.ToProfileConnectingSpace(options);
137+
}
138+
}
139+
140+
/// <inheritdoc/>
141+
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource()
142+
=> ChromaticAdaptionWhitePointSource.RgbWorkingSpace;
143+
144+
/// <inheritdoc/>
145+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
146+
public override int GetHashCode()
147+
=> HashCode.Combine(this.C, this.M, this.Y, this.K);
148+
149+
/// <inheritdoc/>
150+
public override string ToString()
151+
=> FormattableString.Invariant($"Cmyk({this.C:#0.##}, {this.M:#0.##}, {this.Y:#0.##}, {this.K:#0.##})");
152+
153+
/// <inheritdoc/>
154+
public override bool Equals(object? obj)
155+
=> obj is Cmyk other && this.Equals(other);
156+
157+
/// <inheritdoc/>
158+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
159+
public bool Equals(Cmyk other)
160+
=> this.C.Equals(other.C)
161+
&& this.M.Equals(other.M)
162+
&& this.Y.Equals(other.Y)
163+
&& this.K.Equals(other.K);
164+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Buffers;
5+
using SixLabors.ImageSharp.Memory;
6+
7+
namespace SixLabors.ImageSharp.ColorProfiles;
8+
9+
internal static class ColorProfileConverterExtensionsCieLabCieLab
10+
{
11+
public static TTo Convert<TFrom, TTo>(this ColorProfileConverter converter, TFrom source)
12+
where TFrom : struct, IColorProfile<TFrom, CieLab>
13+
where TTo : struct, IColorProfile<TTo, CieLab>
14+
{
15+
ColorConversionOptions options = converter.Options;
16+
17+
// Convert to input PCS
18+
CieLab pcsFromA = source.ToProfileConnectingSpace(options);
19+
CieXyz pcsFromB = pcsFromA.ToProfileConnectingSpace(options);
20+
21+
// Adapt to target white point
22+
pcsFromB = VonKriesChromaticAdaptation.Transform<TFrom, TTo>(options, in pcsFromB);
23+
24+
// Convert between PCS
25+
CieLab pcsTo = CieLab.FromProfileConnectingSpace(options, in pcsFromB);
26+
27+
// Convert to output from PCS
28+
return TTo.FromProfileConnectingSpace(options, pcsTo);
29+
}
30+
31+
public static void Convert<TFrom, TTo>(this ColorProfileConverter converter, ReadOnlySpan<TFrom> source, Span<TTo> destination)
32+
where TFrom : struct, IColorProfile<TFrom, CieLab>
33+
where TTo : struct, IColorProfile<TTo, CieLab>
34+
{
35+
ColorConversionOptions options = converter.Options;
36+
37+
// Convert to input PCS.
38+
using IMemoryOwner<CieLab> pcsFromToOwner = options.MemoryAllocator.Allocate<CieLab>(source.Length);
39+
Span<CieLab> pcsFromTo = pcsFromToOwner.GetSpan();
40+
TFrom.ToProfileConnectionSpace(options, source, pcsFromTo);
41+
42+
using IMemoryOwner<CieXyz> pcsFromOwner = options.MemoryAllocator.Allocate<CieXyz>(source.Length);
43+
Span<CieXyz> pcsFrom = pcsFromOwner.GetSpan();
44+
CieLab.ToProfileConnectionSpace(options, pcsFromTo, pcsFrom);
45+
46+
// Adapt to target white point
47+
VonKriesChromaticAdaptation.Transform<TFrom, TTo>(options, pcsFrom, pcsFrom);
48+
49+
// Convert between PCS.
50+
CieLab.FromProfileConnectionSpace(options, pcsFrom, pcsFromTo);
51+
52+
// Convert to output from PCS
53+
TTo.FromProfileConnectionSpace(options, pcsFromTo, destination);
54+
}
55+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Buffers;
5+
using SixLabors.ImageSharp.Memory;
6+
7+
namespace SixLabors.ImageSharp.ColorProfiles;
8+
9+
internal static class ColorProfileConverterExtensionsRgbRgb
10+
{
11+
public static TTo Convert<TFrom, TTo>(this ColorProfileConverter converter, TFrom source)
12+
where TFrom : struct, IColorProfile<TFrom, Rgb>
13+
where TTo : struct, IColorProfile<TTo, Rgb>
14+
{
15+
ColorConversionOptions options = converter.Options;
16+
17+
// Convert to input PCS
18+
Rgb pcsFromA = source.ToProfileConnectingSpace(options);
19+
CieXyz pcsFromB = pcsFromA.ToProfileConnectingSpace(options);
20+
21+
// Adapt to target white point
22+
pcsFromB = VonKriesChromaticAdaptation.Transform<TFrom, TTo>(options, in pcsFromB);
23+
24+
// Convert between PCS
25+
Rgb pcsTo = Rgb.FromProfileConnectingSpace(options, in pcsFromB);
26+
27+
// Convert to output from PCS
28+
return TTo.FromProfileConnectingSpace(options, pcsTo);
29+
}
30+
31+
public static void Convert<TFrom, TTo>(this ColorProfileConverter converter, ReadOnlySpan<TFrom> source, Span<TTo> destination)
32+
where TFrom : struct, IColorProfile<TFrom, Rgb>
33+
where TTo : struct, IColorProfile<TTo, Rgb>
34+
{
35+
ColorConversionOptions options = converter.Options;
36+
37+
// Convert to input PCS.
38+
using IMemoryOwner<Rgb> pcsFromToOwner = options.MemoryAllocator.Allocate<Rgb>(source.Length);
39+
Span<Rgb> pcsFromTo = pcsFromToOwner.GetSpan();
40+
TFrom.ToProfileConnectionSpace(options, source, pcsFromTo);
41+
42+
using IMemoryOwner<CieXyz> pcsFromOwner = options.MemoryAllocator.Allocate<CieXyz>(source.Length);
43+
Span<CieXyz> pcsFrom = pcsFromOwner.GetSpan();
44+
Rgb.ToProfileConnectionSpace(options, pcsFromTo, pcsFrom);
45+
46+
// Adapt to target white point
47+
VonKriesChromaticAdaptation.Transform<TFrom, TTo>(options, pcsFrom, pcsFrom);
48+
49+
// Convert between PCS.
50+
Rgb.FromProfileConnectionSpace(options, pcsFrom, pcsFromTo);
51+
52+
// Convert to output from PCS
53+
TTo.FromProfileConnectionSpace(options, pcsFromTo, destination);
54+
}
55+
}

tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ namespace SixLabors.ImageSharp.Tests.ColorProfiles;
1818
IEqualityComparer<YCbCr>,
1919
IEqualityComparer<CieLchuv>,
2020
IEqualityComparer<CieLuv>,
21-
IEqualityComparer<CieXyy>
21+
IEqualityComparer<CieXyy>,
22+
IEqualityComparer<Cmyk>
2223
{
2324
private readonly float epsilon;
2425

@@ -46,6 +47,8 @@ namespace SixLabors.ImageSharp.Tests.ColorProfiles;
4647

4748
public bool Equals(CieXyy x, CieXyy y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Yl, y.Yl);
4849

50+
public bool Equals(Cmyk x, Cmyk y) => this.Equals(x.C, y.C) && this.Equals(x.M, y.M) && this.Equals(x.Y, y.Y) && this.Equals(x.K, y.K);
51+
4952
public int GetHashCode([DisallowNull] CieLab obj) => obj.GetHashCode();
5053

5154
public int GetHashCode([DisallowNull] CieXyz obj) => obj.GetHashCode();
@@ -64,6 +67,8 @@ namespace SixLabors.ImageSharp.Tests.ColorProfiles;
6467

6568
public int GetHashCode([DisallowNull] CieXyy obj) => obj.GetHashCode();
6669

70+
public int GetHashCode([DisallowNull] Cmyk obj) => obj.GetHashCode();
71+
6772
private bool Equals(float x, float y)
6873
{
6974
float d = x - y;

0 commit comments

Comments
 (0)