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