Skip to content

Commit bb6502c

Browse files
Create Hsv.cs
1 parent 05b6cfb commit bb6502c

1 file changed

Lines changed: 229 additions & 0 deletions

File tree

  • src/ImageSharp/ColorProfiles
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
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 a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness).
11+
/// </summary>
12+
public readonly struct Hsv : IColorProfile<Hsv, Rgb>
13+
{
14+
private static readonly Vector3 Min = Vector3.Zero;
15+
private static readonly Vector3 Max = new(360, 1, 1);
16+
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="Hsv"/> struct.
19+
/// </summary>
20+
/// <param name="h">The h hue component.</param>
21+
/// <param name="s">The s saturation component.</param>
22+
/// <param name="v">The v value (brightness) component.</param>
23+
[MethodImpl(InliningOptions.ShortMethod)]
24+
public Hsv(float h, float s, float v)
25+
: this(new Vector3(h, s, v))
26+
{
27+
}
28+
29+
/// <summary>
30+
/// Initializes a new instance of the <see cref="Hsv"/> struct.
31+
/// </summary>
32+
/// <param name="vector">The vector representing the h, s, v components.</param>
33+
[MethodImpl(InliningOptions.ShortMethod)]
34+
public Hsv(Vector3 vector)
35+
{
36+
vector = Vector3.Clamp(vector, Min, Max);
37+
this.H = vector.X;
38+
this.S = vector.Y;
39+
this.V = vector.Z;
40+
}
41+
42+
/// <summary>
43+
/// Gets the hue component.
44+
/// <remarks>A value ranging between 0 and 360.</remarks>
45+
/// </summary>
46+
public readonly float H { get; }
47+
48+
/// <summary>
49+
/// Gets the saturation component.
50+
/// <remarks>A value ranging between 0 and 1.</remarks>
51+
/// </summary>
52+
public readonly float S { get; }
53+
54+
/// <summary>
55+
/// Gets the value (brightness) component.
56+
/// <remarks>A value ranging between 0 and 1.</remarks>
57+
/// </summary>
58+
public readonly float V { get; }
59+
60+
/// <summary>
61+
/// Compares two <see cref="Hsv"/> objects for equality.
62+
/// </summary>
63+
/// <param name="left">The <see cref="Hsv"/> on the left side of the operand.</param>
64+
/// <param name="right">The <see cref="Hsv"/> on the right side of the operand.</param>
65+
/// <returns>
66+
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
67+
/// </returns>
68+
[MethodImpl(InliningOptions.ShortMethod)]
69+
public static bool operator ==(Hsv left, Hsv right) => left.Equals(right);
70+
71+
/// <summary>
72+
/// Compares two <see cref="Hsv"/> objects for inequality.
73+
/// </summary>
74+
/// <param name="left">The <see cref="Hsv"/> on the left side of the operand.</param>
75+
/// <param name="right">The <see cref="Hsv"/> on the right side of the operand.</param>
76+
/// <returns>
77+
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
78+
/// </returns>
79+
[MethodImpl(InliningOptions.ShortMethod)]
80+
public static bool operator !=(Hsv left, Hsv right) => !left.Equals(right);
81+
82+
/// <inheritdoc/>
83+
public static Hsv FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
84+
{
85+
float r = source.R;
86+
float g = source.G;
87+
float b = source.B;
88+
89+
float max = MathF.Max(r, MathF.Max(g, b));
90+
float min = MathF.Min(r, MathF.Min(g, b));
91+
float chroma = max - min;
92+
float h = 0;
93+
float s = 0;
94+
float v = max;
95+
96+
if (MathF.Abs(chroma) < Constants.Epsilon)
97+
{
98+
return new Hsv(0, s, v);
99+
}
100+
101+
if (MathF.Abs(r - max) < Constants.Epsilon)
102+
{
103+
h = (g - b) / chroma;
104+
}
105+
else if (MathF.Abs(g - max) < Constants.Epsilon)
106+
{
107+
h = 2 + ((b - r) / chroma);
108+
}
109+
else if (MathF.Abs(b - max) < Constants.Epsilon)
110+
{
111+
h = 4 + ((r - g) / chroma);
112+
}
113+
114+
h *= 60;
115+
if (h < 0.0)
116+
{
117+
h += 360;
118+
}
119+
120+
s = chroma / v;
121+
122+
return new Hsv(h, s, v);
123+
}
124+
125+
/// <inheritdoc/>
126+
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Rgb> source, Span<Hsv> destination)
127+
{
128+
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
129+
for (int i = 0; i < source.Length; i++)
130+
{
131+
Rgb rgb = source[i];
132+
destination[i] = FromProfileConnectingSpace(options, in rgb);
133+
}
134+
}
135+
136+
/// <inheritdoc/>
137+
public Rgb ToProfileConnectingSpace(ColorConversionOptions options)
138+
{
139+
float s = this.S;
140+
float v = this.V;
141+
142+
if (MathF.Abs(s) < Constants.Epsilon)
143+
{
144+
return new Rgb(v, v, v);
145+
}
146+
147+
float h = (MathF.Abs(this.H - 360) < Constants.Epsilon) ? 0 : this.H / 60;
148+
int i = (int)Math.Truncate(h);
149+
float f = h - i;
150+
151+
float p = v * (1F - s);
152+
float q = v * (1F - (s * f));
153+
float t = v * (1F - (s * (1F - f)));
154+
155+
float r, g, b;
156+
switch (i)
157+
{
158+
case 0:
159+
r = v;
160+
g = t;
161+
b = p;
162+
break;
163+
164+
case 1:
165+
r = q;
166+
g = v;
167+
b = p;
168+
break;
169+
170+
case 2:
171+
r = p;
172+
g = v;
173+
b = t;
174+
break;
175+
176+
case 3:
177+
r = p;
178+
g = q;
179+
b = v;
180+
break;
181+
182+
case 4:
183+
r = t;
184+
g = p;
185+
b = v;
186+
break;
187+
188+
default:
189+
r = v;
190+
g = p;
191+
b = q;
192+
break;
193+
}
194+
195+
return new Rgb(r, g, b);
196+
}
197+
198+
/// <inheritdoc/>
199+
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Hsv> source, Span<Rgb> destination)
200+
{
201+
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
202+
for (int i = 0; i < source.Length; i++)
203+
{
204+
Hsv hsv = source[i];
205+
destination[i] = hsv.ToProfileConnectingSpace(options);
206+
}
207+
}
208+
209+
/// <inheritdoc/>
210+
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource()
211+
=> ChromaticAdaptionWhitePointSource.RgbWorkingSpace;
212+
213+
/// <inheritdoc/>
214+
[MethodImpl(InliningOptions.ShortMethod)]
215+
public override int GetHashCode() => HashCode.Combine(this.H, this.S, this.V);
216+
217+
/// <inheritdoc/>
218+
public override string ToString() => FormattableString.Invariant($"Hsv({this.H:#0.##}, {this.S:#0.##}, {this.V:#0.##})");
219+
220+
/// <inheritdoc/>
221+
public override bool Equals(object? obj) => obj is Hsv other && this.Equals(other);
222+
223+
/// <inheritdoc/>
224+
[MethodImpl(InliningOptions.ShortMethod)]
225+
public bool Equals(Hsv other)
226+
=> this.H.Equals(other.H)
227+
&& this.S.Equals(other.S)
228+
&& this.V.Equals(other.V);
229+
}

0 commit comments

Comments
 (0)