1use crate::{
2 gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_f32_from_linear_u8,
3 linear_u8_from_linear_f32, Color32, Rgba,
4};
5
6#[derive(Clone, Copy, Debug, Default, PartialEq)]
9pub struct Hsva {
10 pub h: f32,
12
13 pub s: f32,
15
16 pub v: f32,
18
19 pub a: f32,
21}
22
23impl Hsva {
24 #[inline]
25 pub fn new(h: f32, s: f32, v: f32, a: f32) -> Self {
26 Self { h, s, v, a }
27 }
28
29 #[inline]
31 pub fn from_srgba_premultiplied([r, g, b, a]: [u8; 4]) -> Self {
32 Self::from_rgba_premultiplied(
33 linear_f32_from_gamma_u8(r),
34 linear_f32_from_gamma_u8(g),
35 linear_f32_from_gamma_u8(b),
36 linear_f32_from_linear_u8(a),
37 )
38 }
39
40 #[inline]
42 pub fn from_srgba_unmultiplied([r, g, b, a]: [u8; 4]) -> Self {
43 Self::from_rgba_unmultiplied(
44 linear_f32_from_gamma_u8(r),
45 linear_f32_from_gamma_u8(g),
46 linear_f32_from_gamma_u8(b),
47 linear_f32_from_linear_u8(a),
48 )
49 }
50
51 #[inline]
53 pub fn from_rgba_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
54 #![allow(clippy::many_single_char_names)]
55 if a == 0.0 {
56 if r == 0.0 && b == 0.0 && a == 0.0 {
57 Self::default()
58 } else {
59 Self::from_additive_rgb([r, g, b])
60 }
61 } else {
62 let (h, s, v) = hsv_from_rgb([r / a, g / a, b / a]);
63 Self { h, s, v, a }
64 }
65 }
66
67 #[inline]
69 pub fn from_rgba_unmultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
70 #![allow(clippy::many_single_char_names)]
71 let (h, s, v) = hsv_from_rgb([r, g, b]);
72 Self { h, s, v, a }
73 }
74
75 #[inline]
76 pub fn from_additive_rgb(rgb: [f32; 3]) -> Self {
77 let (h, s, v) = hsv_from_rgb(rgb);
78 Self {
79 h,
80 s,
81 v,
82 a: -0.5, }
84 }
85
86 #[inline]
87 pub fn from_additive_srgb([r, g, b]: [u8; 3]) -> Self {
88 Self::from_additive_rgb([
89 linear_f32_from_gamma_u8(r),
90 linear_f32_from_gamma_u8(g),
91 linear_f32_from_gamma_u8(b),
92 ])
93 }
94
95 #[inline]
96 pub fn from_rgb(rgb: [f32; 3]) -> Self {
97 let (h, s, v) = hsv_from_rgb(rgb);
98 Self { h, s, v, a: 1.0 }
99 }
100
101 #[inline]
102 pub fn from_srgb([r, g, b]: [u8; 3]) -> Self {
103 Self::from_rgb([
104 linear_f32_from_gamma_u8(r),
105 linear_f32_from_gamma_u8(g),
106 linear_f32_from_gamma_u8(b),
107 ])
108 }
109
110 #[inline]
113 pub fn to_opaque(self) -> Self {
114 Self { a: 1.0, ..self }
115 }
116
117 #[inline]
118 pub fn to_rgb(&self) -> [f32; 3] {
119 rgb_from_hsv((self.h, self.s, self.v))
120 }
121
122 #[inline]
123 pub fn to_srgb(&self) -> [u8; 3] {
124 let [r, g, b] = self.to_rgb();
125 [
126 gamma_u8_from_linear_f32(r),
127 gamma_u8_from_linear_f32(g),
128 gamma_u8_from_linear_f32(b),
129 ]
130 }
131
132 #[inline]
133 pub fn to_rgba_premultiplied(&self) -> [f32; 4] {
134 let [r, g, b, a] = self.to_rgba_unmultiplied();
135 let additive = a < 0.0;
136 if additive {
137 [r, g, b, 0.0]
138 } else {
139 [a * r, a * g, a * b, a]
140 }
141 }
142
143 #[inline]
147 pub fn to_rgba_unmultiplied(&self) -> [f32; 4] {
148 let Self { h, s, v, a } = *self;
149 let [r, g, b] = rgb_from_hsv((h, s, v));
150 [r, g, b, a]
151 }
152
153 #[inline]
154 pub fn to_srgba_premultiplied(&self) -> [u8; 4] {
155 let [r, g, b, a] = self.to_rgba_premultiplied();
156 [
157 gamma_u8_from_linear_f32(r),
158 gamma_u8_from_linear_f32(g),
159 gamma_u8_from_linear_f32(b),
160 linear_u8_from_linear_f32(a),
161 ]
162 }
163
164 #[inline]
166 pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
167 let [r, g, b, a] = self.to_rgba_unmultiplied();
168 [
169 gamma_u8_from_linear_f32(r),
170 gamma_u8_from_linear_f32(g),
171 gamma_u8_from_linear_f32(b),
172 linear_u8_from_linear_f32(a.abs()),
173 ]
174 }
175}
176
177impl From<Hsva> for Rgba {
178 #[inline]
179 fn from(hsva: Hsva) -> Self {
180 Self(hsva.to_rgba_premultiplied())
181 }
182}
183
184impl From<Rgba> for Hsva {
185 #[inline]
186 fn from(rgba: Rgba) -> Self {
187 Self::from_rgba_premultiplied(rgba.0[0], rgba.0[1], rgba.0[2], rgba.0[3])
188 }
189}
190
191impl From<Hsva> for Color32 {
192 #[inline]
193 fn from(hsva: Hsva) -> Self {
194 Self::from(Rgba::from(hsva))
195 }
196}
197
198impl From<Color32> for Hsva {
199 #[inline]
200 fn from(srgba: Color32) -> Self {
201 Self::from(Rgba::from(srgba))
202 }
203}
204
205#[inline]
207pub fn hsv_from_rgb([r, g, b]: [f32; 3]) -> (f32, f32, f32) {
208 #![allow(clippy::many_single_char_names)]
209 let min = r.min(g.min(b));
210 let max = r.max(g.max(b)); let range = max - min;
213
214 let h = if max == min {
215 0.0 } else if max == r {
217 (g - b) / (6.0 * range)
218 } else if max == g {
219 (b - r) / (6.0 * range) + 1.0 / 3.0
220 } else {
221 (r - g) / (6.0 * range) + 2.0 / 3.0
223 };
224 let h = (h + 1.0).fract(); let s = if max == 0.0 { 0.0 } else { 1.0 - min / max };
226 (h, s, max)
227}
228
229#[inline]
231pub fn rgb_from_hsv((h, s, v): (f32, f32, f32)) -> [f32; 3] {
232 #![allow(clippy::many_single_char_names)]
233 let h = (h.fract() + 1.0).fract(); let s = s.clamp(0.0, 1.0);
235
236 let f = h * 6.0 - (h * 6.0).floor();
237 let p = v * (1.0 - s);
238 let q = v * (1.0 - f * s);
239 let t = v * (1.0 - (1.0 - f) * s);
240
241 match (h * 6.0).floor() as i32 % 6 {
242 0 => [v, t, p],
243 1 => [q, v, p],
244 2 => [p, v, t],
245 3 => [p, q, v],
246 4 => [t, p, v],
247 5 => [v, p, q],
248 _ => unreachable!(),
249 }
250}
251
252#[test]
253#[ignore] fn test_hsv_roundtrip() {
255 for r in 0..=255 {
256 for g in 0..=255 {
257 for b in 0..=255 {
258 let srgba = Color32::from_rgb(r, g, b);
259 let hsva = Hsva::from(srgba);
260 assert_eq!(srgba, Color32::from(hsva));
261 }
262 }
263 }
264}