1use crate::{
2 Color32, Rgba, gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_u8_from_linear_f32,
3};
4
5#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
8#[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(Color32::from_rgba_premultiplied(r, g, b, a))
33 }
34
35 #[inline]
37 pub fn from_srgba_unmultiplied([r, g, b, a]: [u8; 4]) -> Self {
38 Self::from(Color32::from_rgba_unmultiplied(r, g, b, a))
39 }
40
41 #[inline]
43 pub fn from_rgba_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
44 #![allow(clippy::many_single_char_names)]
45 if a <= 0.0 {
46 if r == 0.0 && b == 0.0 && a == 0.0 {
47 Self::default()
48 } else {
49 Self::from_additive_rgb([r, g, b])
50 }
51 } else {
52 let (h, s, v) = hsv_from_rgb([r / a, g / a, b / a]);
53 Self { h, s, v, a }
54 }
55 }
56
57 #[inline]
59 pub fn from_rgba_unmultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
60 #![allow(clippy::many_single_char_names)]
61 let (h, s, v) = hsv_from_rgb([r, g, b]);
62 Self { h, s, v, a }
63 }
64
65 #[inline]
66 pub fn from_additive_rgb(rgb: [f32; 3]) -> Self {
67 let (h, s, v) = hsv_from_rgb(rgb);
68 Self {
69 h,
70 s,
71 v,
72 a: -0.5, }
74 }
75
76 #[inline]
77 pub fn from_additive_srgb([r, g, b]: [u8; 3]) -> Self {
78 Self::from_additive_rgb([
79 linear_f32_from_gamma_u8(r),
80 linear_f32_from_gamma_u8(g),
81 linear_f32_from_gamma_u8(b),
82 ])
83 }
84
85 #[inline]
86 pub fn from_rgb(rgb: [f32; 3]) -> Self {
87 let (h, s, v) = hsv_from_rgb(rgb);
88 Self { h, s, v, a: 1.0 }
89 }
90
91 #[inline]
92 pub fn from_srgb([r, g, b]: [u8; 3]) -> Self {
93 Self::from_rgb([
94 linear_f32_from_gamma_u8(r),
95 linear_f32_from_gamma_u8(g),
96 linear_f32_from_gamma_u8(b),
97 ])
98 }
99
100 #[inline]
103 pub fn to_opaque(self) -> Self {
104 Self { a: 1.0, ..self }
105 }
106
107 #[inline]
108 pub fn to_rgb(&self) -> [f32; 3] {
109 rgb_from_hsv((self.h, self.s, self.v))
110 }
111
112 #[inline]
113 pub fn to_srgb(&self) -> [u8; 3] {
114 let [r, g, b] = self.to_rgb();
115 [
116 gamma_u8_from_linear_f32(r),
117 gamma_u8_from_linear_f32(g),
118 gamma_u8_from_linear_f32(b),
119 ]
120 }
121
122 #[inline]
123 pub fn to_rgba_premultiplied(&self) -> [f32; 4] {
124 let [r, g, b, a] = self.to_rgba_unmultiplied();
125 let additive = a < 0.0;
126 if additive {
127 [r, g, b, 0.0]
128 } else {
129 [a * r, a * g, a * b, a]
130 }
131 }
132
133 #[inline]
137 pub fn to_rgba_unmultiplied(&self) -> [f32; 4] {
138 let Self { h, s, v, a } = *self;
139 let [r, g, b] = rgb_from_hsv((h, s, v));
140 [r, g, b, a]
141 }
142
143 #[inline]
144 pub fn to_srgba_premultiplied(&self) -> [u8; 4] {
145 Color32::from(*self).to_array()
146 }
147
148 #[inline]
150 pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
151 let [r, g, b, a] = self.to_rgba_unmultiplied();
152 [
153 gamma_u8_from_linear_f32(r),
154 gamma_u8_from_linear_f32(g),
155 gamma_u8_from_linear_f32(b),
156 linear_u8_from_linear_f32(a.abs()),
157 ]
158 }
159}
160
161impl From<Hsva> for Rgba {
162 #[inline]
163 fn from(hsva: Hsva) -> Self {
164 Self(hsva.to_rgba_premultiplied())
165 }
166}
167
168impl From<Rgba> for Hsva {
169 #[inline]
170 fn from(rgba: Rgba) -> Self {
171 Self::from_rgba_premultiplied(rgba.0[0], rgba.0[1], rgba.0[2], rgba.0[3])
172 }
173}
174
175impl From<Hsva> for Color32 {
176 #[inline]
177 fn from(hsva: Hsva) -> Self {
178 Self::from(Rgba::from(hsva))
179 }
180}
181
182impl From<Color32> for Hsva {
183 #[inline]
184 fn from(srgba: Color32) -> Self {
185 Self::from(Rgba::from(srgba))
186 }
187}
188
189#[inline]
191pub fn hsv_from_rgb([r, g, b]: [f32; 3]) -> (f32, f32, f32) {
192 #![allow(clippy::many_single_char_names)]
193 let min = r.min(g.min(b));
194 let max = r.max(g.max(b)); let range = max - min;
197
198 let h = if max == min {
199 0.0 } else if max == r {
201 (g - b) / (6.0 * range)
202 } else if max == g {
203 (b - r) / (6.0 * range) + 1.0 / 3.0
204 } else {
205 (r - g) / (6.0 * range) + 2.0 / 3.0
207 };
208 let h = (h + 1.0).fract(); let s = if max == 0.0 { 0.0 } else { 1.0 - min / max };
210 (h, s, max)
211}
212
213#[inline]
215pub fn rgb_from_hsv((h, s, v): (f32, f32, f32)) -> [f32; 3] {
216 #![allow(clippy::many_single_char_names)]
217 let h = (h.fract() + 1.0).fract(); let s = s.clamp(0.0, 1.0);
219
220 let f = h * 6.0 - (h * 6.0).floor();
221 let p = v * (1.0 - s);
222 let q = v * (1.0 - f * s);
223 let t = v * (1.0 - (1.0 - f) * s);
224
225 match (h * 6.0).floor() as i32 % 6 {
226 0 => [v, t, p],
227 1 => [q, v, p],
228 2 => [p, v, t],
229 3 => [p, q, v],
230 4 => [t, p, v],
231 5 => [v, p, q],
232 _ => unreachable!(),
233 }
234}
235
236#[test]
237#[ignore] fn test_hsv_roundtrip() {
239 for r in 0..=255 {
240 for g in 0..=255 {
241 for b in 0..=255 {
242 let srgba = Color32::from_rgb(r, g, b);
243 let hsva = Hsva::from(srgba);
244 assert_eq!(srgba, Color32::from(hsva));
245 }
246 }
247 }
248}