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