1use crate::{
2 Alpha, ColorToComponents, Gray, Hue, Hwba, Lcha, LinearRgba, Mix, Srgba, StandardColor, Xyza,
3};
4use bevy_math::{Vec3, Vec4};
5use bevy_reflect::prelude::*;
6
7#[doc = include_str!("../docs/conversion.md")]
10#[doc = include_str!("../docs/diagrams/model_graph.svg")]
12#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
14#[reflect(PartialEq, Default)]
15#[cfg_attr(
16 feature = "serialize",
17 derive(serde::Serialize, serde::Deserialize),
18 reflect(Serialize, Deserialize)
19)]
20pub struct Hsva {
21 pub hue: f32,
23 pub saturation: f32,
25 pub value: f32,
27 pub alpha: f32,
29}
30
31impl StandardColor for Hsva {}
32
33impl Hsva {
34 pub const fn new(hue: f32, saturation: f32, value: f32, alpha: f32) -> Self {
43 Self {
44 hue,
45 saturation,
46 value,
47 alpha,
48 }
49 }
50
51 pub const fn hsv(hue: f32, saturation: f32, value: f32) -> Self {
59 Self::new(hue, saturation, value, 1.0)
60 }
61
62 pub const fn with_saturation(self, saturation: f32) -> Self {
64 Self { saturation, ..self }
65 }
66
67 pub const fn with_value(self, value: f32) -> Self {
69 Self { value, ..self }
70 }
71}
72
73impl Default for Hsva {
74 fn default() -> Self {
75 Self::new(0., 0., 1., 1.)
76 }
77}
78
79impl Mix for Hsva {
80 #[inline]
81 fn mix(&self, other: &Self, factor: f32) -> Self {
82 let n_factor = 1.0 - factor;
83 Self {
84 hue: crate::color_ops::lerp_hue(self.hue, other.hue, factor),
85 saturation: self.saturation * n_factor + other.saturation * factor,
86 value: self.value * n_factor + other.value * factor,
87 alpha: self.alpha * n_factor + other.alpha * factor,
88 }
89 }
90}
91
92impl Gray for Hsva {
93 const BLACK: Self = Self::new(0., 0., 0., 1.);
94 const WHITE: Self = Self::new(0., 0., 1., 1.);
95}
96
97impl Alpha for Hsva {
98 #[inline]
99 fn with_alpha(&self, alpha: f32) -> Self {
100 Self { alpha, ..*self }
101 }
102
103 #[inline]
104 fn alpha(&self) -> f32 {
105 self.alpha
106 }
107
108 #[inline]
109 fn set_alpha(&mut self, alpha: f32) {
110 self.alpha = alpha;
111 }
112}
113
114impl Hue for Hsva {
115 #[inline]
116 fn with_hue(&self, hue: f32) -> Self {
117 Self { hue, ..*self }
118 }
119
120 #[inline]
121 fn hue(&self) -> f32 {
122 self.hue
123 }
124
125 #[inline]
126 fn set_hue(&mut self, hue: f32) {
127 self.hue = hue;
128 }
129}
130
131impl From<Hsva> for Hwba {
132 fn from(
133 Hsva {
134 hue,
135 saturation,
136 value,
137 alpha,
138 }: Hsva,
139 ) -> Self {
140 let whiteness = (1. - saturation) * value;
142 let blackness = 1. - value;
143
144 Hwba::new(hue, whiteness, blackness, alpha)
145 }
146}
147
148impl From<Hwba> for Hsva {
149 fn from(
150 Hwba {
151 hue,
152 whiteness,
153 blackness,
154 alpha,
155 }: Hwba,
156 ) -> Self {
157 let value = 1. - blackness;
159 let saturation = 1. - (whiteness / value);
160
161 Hsva::new(hue, saturation, value, alpha)
162 }
163}
164
165impl ColorToComponents for Hsva {
166 fn to_f32_array(self) -> [f32; 4] {
167 [self.hue, self.saturation, self.value, self.alpha]
168 }
169
170 fn to_f32_array_no_alpha(self) -> [f32; 3] {
171 [self.hue, self.saturation, self.value]
172 }
173
174 fn to_vec4(self) -> Vec4 {
175 Vec4::new(self.hue, self.saturation, self.value, self.alpha)
176 }
177
178 fn to_vec3(self) -> Vec3 {
179 Vec3::new(self.hue, self.saturation, self.value)
180 }
181
182 fn from_f32_array(color: [f32; 4]) -> Self {
183 Self {
184 hue: color[0],
185 saturation: color[1],
186 value: color[2],
187 alpha: color[3],
188 }
189 }
190
191 fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
192 Self {
193 hue: color[0],
194 saturation: color[1],
195 value: color[2],
196 alpha: 1.0,
197 }
198 }
199
200 fn from_vec4(color: Vec4) -> Self {
201 Self {
202 hue: color[0],
203 saturation: color[1],
204 value: color[2],
205 alpha: color[3],
206 }
207 }
208
209 fn from_vec3(color: Vec3) -> Self {
210 Self {
211 hue: color[0],
212 saturation: color[1],
213 value: color[2],
214 alpha: 1.0,
215 }
216 }
217}
218
219impl From<Srgba> for Hsva {
222 fn from(value: Srgba) -> Self {
223 Hwba::from(value).into()
224 }
225}
226
227impl From<Hsva> for Srgba {
228 fn from(value: Hsva) -> Self {
229 Hwba::from(value).into()
230 }
231}
232
233impl From<LinearRgba> for Hsva {
234 fn from(value: LinearRgba) -> Self {
235 Hwba::from(value).into()
236 }
237}
238
239impl From<Hsva> for LinearRgba {
240 fn from(value: Hsva) -> Self {
241 Hwba::from(value).into()
242 }
243}
244
245impl From<Lcha> for Hsva {
246 fn from(value: Lcha) -> Self {
247 Hwba::from(value).into()
248 }
249}
250
251impl From<Hsva> for Lcha {
252 fn from(value: Hsva) -> Self {
253 Hwba::from(value).into()
254 }
255}
256
257impl From<Xyza> for Hsva {
258 fn from(value: Xyza) -> Self {
259 Hwba::from(value).into()
260 }
261}
262
263impl From<Hsva> for Xyza {
264 fn from(value: Hsva) -> Self {
265 Hwba::from(value).into()
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272 use crate::{
273 color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq,
274 };
275
276 #[test]
277 fn test_to_from_srgba() {
278 let hsva = Hsva::new(180., 0.5, 0.5, 1.0);
279 let srgba: Srgba = hsva.into();
280 let hsva2: Hsva = srgba.into();
281 assert_approx_eq!(hsva.hue, hsva2.hue, 0.001);
282 assert_approx_eq!(hsva.saturation, hsva2.saturation, 0.001);
283 assert_approx_eq!(hsva.value, hsva2.value, 0.001);
284 assert_approx_eq!(hsva.alpha, hsva2.alpha, 0.001);
285 }
286
287 #[test]
288 fn test_to_from_srgba_2() {
289 for color in TEST_COLORS.iter() {
290 let rgb2: Srgba = (color.hsv).into();
291 let hsv2: Hsva = (color.rgb).into();
292 assert!(
293 color.rgb.distance(&rgb2) < 0.00001,
294 "{}: {:?} != {:?}",
295 color.name,
296 color.rgb,
297 rgb2
298 );
299 assert_approx_eq!(color.hsv.hue, hsv2.hue, 0.001);
300 assert_approx_eq!(color.hsv.saturation, hsv2.saturation, 0.001);
301 assert_approx_eq!(color.hsv.value, hsv2.value, 0.001);
302 assert_approx_eq!(color.hsv.alpha, hsv2.alpha, 0.001);
303 }
304 }
305}