1use crate::channel::cast::ChannelFormatCast;
4use crate::channel::{
5 AngularChannel, AngularChannelScalar, ChannelCast, ColorChannel, PosNormalBoundedChannel,
6 PosNormalChannelScalar,
7};
8use crate::color;
9use crate::color::{Bounded, Color, FromTuple, Invert, Lerp, PolarColor};
10use crate::convert;
11use crate::encoding::EncodableColor;
12use crate::rgb;
13use crate::tags::HsvTag;
14use angle;
15use angle::{Angle, Deg, FromAngle, IntoAngle};
16#[cfg(feature = "approx")]
17use approx;
18use num_traits;
19use num_traits::cast;
20use std::fmt;
21use std::ops;
22
23#[repr(C)]
41#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Hash)]
42pub struct Hsv<T, A = Deg<T>> {
43 hue: AngularChannel<A>,
44 saturation: PosNormalBoundedChannel<T>,
45 value: PosNormalBoundedChannel<T>,
46}
47
48impl<T, A> Hsv<T, A>
49where
50 T: PosNormalChannelScalar,
51 A: AngularChannelScalar,
52{
53 pub fn new(hue: A, saturation: T, value: T) -> Self {
55 Hsv {
56 hue: AngularChannel::new(hue),
57 saturation: PosNormalBoundedChannel::new(saturation),
58 value: PosNormalBoundedChannel::new(value),
59 }
60 }
61
62 impl_color_color_cast_angular!(
63 Hsv {
64 hue,
65 saturation,
66 value
67 },
68 chan_traits = { PosNormalChannelScalar }
69 );
70
71 pub fn hue(&self) -> A {
73 self.hue.0.clone()
74 }
75 pub fn saturation(&self) -> T {
77 self.saturation.0.clone()
78 }
79 pub fn value(&self) -> T {
81 self.value.0.clone()
82 }
83 pub fn hue_mut(&mut self) -> &mut A {
85 &mut self.hue.0
86 }
87 pub fn saturation_mut(&mut self) -> &mut T {
89 &mut self.saturation.0
90 }
91 pub fn value_mut(&mut self) -> &mut T {
93 &mut self.value.0
94 }
95 pub fn set_hue(&mut self, val: A) {
97 self.hue.0 = val;
98 }
99 pub fn set_saturation(&mut self, val: T) {
101 self.saturation.0 = val;
102 }
103 pub fn set_value(&mut self, val: T) {
105 self.value.0 = val;
106 }
107}
108
109impl<T, A> PolarColor for Hsv<T, A>
110where
111 T: PosNormalChannelScalar,
112 A: AngularChannelScalar,
113{
114 type Angular = A;
115 type Cartesian = T;
116}
117
118impl<T, A> Color for Hsv<T, A>
119where
120 T: PosNormalChannelScalar,
121 A: AngularChannelScalar,
122{
123 type Tag = HsvTag;
124 type ChannelsTuple = (A, T, T);
125
126 fn num_channels() -> u32 {
127 3
128 }
129 fn to_tuple(self) -> Self::ChannelsTuple {
130 (self.hue.0, self.saturation.0, self.value.0)
131 }
132}
133
134impl<T, A> FromTuple for Hsv<T, A>
135where
136 T: PosNormalChannelScalar,
137 A: AngularChannelScalar,
138{
139 fn from_tuple(values: Self::ChannelsTuple) -> Self {
140 Hsv::new(values.0, values.1, values.2)
141 }
142}
143
144impl<T, A> Invert for Hsv<T, A>
145where
146 T: PosNormalChannelScalar,
147 A: AngularChannelScalar,
148{
149 impl_color_invert!(Hsv {
150 hue,
151 saturation,
152 value
153 });
154}
155
156impl<T, A> Lerp for Hsv<T, A>
157where
158 T: PosNormalChannelScalar + color::Lerp,
159 A: AngularChannelScalar + color::Lerp,
160{
161 type Position = A::Position;
162
163 impl_color_lerp_angular!(Hsv<T> {hue, saturation, value});
164}
165
166impl<T, A> Bounded for Hsv<T, A>
167where
168 T: PosNormalChannelScalar,
169 A: AngularChannelScalar,
170{
171 impl_color_bounded!(Hsv {
172 hue,
173 saturation,
174 value
175 });
176}
177
178impl<T, A> EncodableColor for Hsv<T, A>
179where
180 T: PosNormalChannelScalar + num_traits::Float,
181 A: AngularChannelScalar + Angle<Scalar = T> + FromAngle<angle::Turns<T>>,
182{
183}
184
185#[cfg(feature = "approx")]
186impl<T, A> approx::AbsDiffEq for Hsv<T, A>
187where
188 T: PosNormalChannelScalar + approx::AbsDiffEq<Epsilon = A::Epsilon>,
189 A: AngularChannelScalar + approx::AbsDiffEq,
190 A::Epsilon: Clone + num_traits::Float,
191{
192 impl_abs_diff_eq!({hue, saturation, value});
193}
194#[cfg(feature = "approx")]
195impl<T, A> approx::RelativeEq for Hsv<T, A>
196where
197 T: PosNormalChannelScalar + approx::RelativeEq<Epsilon = A::Epsilon>,
198 A: AngularChannelScalar + approx::RelativeEq,
199 A::Epsilon: Clone + num_traits::Float,
200{
201 impl_rel_eq!({hue, saturation, value});
202}
203#[cfg(feature = "approx")]
204impl<T, A> approx::UlpsEq for Hsv<T, A>
205where
206 T: PosNormalChannelScalar + approx::UlpsEq<Epsilon = A::Epsilon>,
207 A: AngularChannelScalar + approx::UlpsEq,
208 A::Epsilon: Clone + num_traits::Float,
209{
210 impl_ulps_eq!({hue, saturation, value});
211}
212
213impl<T, A> Default for Hsv<T, A>
214where
215 T: PosNormalChannelScalar + num_traits::Zero,
216 A: AngularChannelScalar + num_traits::Zero,
217{
218 impl_color_default!(Hsv {
219 hue: AngularChannel,
220 saturation: PosNormalBoundedChannel,
221 value: PosNormalBoundedChannel
222 });
223}
224
225impl<T, A> fmt::Display for Hsv<T, A>
226where
227 T: PosNormalChannelScalar + fmt::Display,
228 A: AngularChannelScalar + fmt::Display,
229{
230 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
231 write!(f, "Hsv({}, {}, {})", self.hue, self.saturation, self.value)
232 }
233}
234
235impl<T, A> convert::GetChroma for Hsv<T, A>
236where
237 T: PosNormalChannelScalar + ops::Mul<T, Output = T>,
238 A: AngularChannelScalar,
239{
240 type ChromaType = T;
241 fn get_chroma(&self) -> T {
242 self.saturation.0.clone() * self.value.0.clone()
243 }
244}
245impl<T, A> convert::GetHue for Hsv<T, A>
246where
247 T: PosNormalChannelScalar,
248 A: AngularChannelScalar,
249{
250 impl_color_get_hue_angular!(Hsv);
251}
252
253impl<T, A> convert::FromColor<Hsv<T, A>> for rgb::Rgb<T>
254where
255 T: PosNormalChannelScalar + num_traits::Float,
256 A: AngularChannelScalar,
257{
258 fn from_color(from: &Hsv<T, A>) -> Self {
259 let (hue_seg, hue_frac) = convert::decompose_hue_segment(from);
260 let one: T = cast(1.0).unwrap();
261 let hue_frac_t: T = cast(hue_frac).unwrap();
262
263 let channel_min = from.value() * (one - from.saturation());
264 let channel_max = from.value();
265
266 match hue_seg {
267 0 => {
268 let g = from.value() * (one - from.saturation() * (one - hue_frac_t));
269 rgb::Rgb::new(channel_max, g, channel_min)
270 }
271 1 => {
272 let r = from.value() * (one - from.saturation() * hue_frac_t);
273 rgb::Rgb::new(r, channel_max, channel_min)
274 }
275 2 => {
276 let b = from.value() * (one - from.saturation() * (one - hue_frac_t));
277 rgb::Rgb::new(channel_min, channel_max, b)
278 }
279 3 => {
280 let g = from.value() * (one - from.saturation() * hue_frac_t);
281 rgb::Rgb::new(channel_min, g, channel_max)
282 }
283 4 => {
284 let r = from.value() * (one - from.saturation() * (one - hue_frac_t));
285 rgb::Rgb::new(r, channel_min, channel_max)
286 }
287 5 => {
288 let b = from.value() * (one - from.saturation() * hue_frac_t);
289 rgb::Rgb::new(channel_max, channel_min, b)
290 }
291 _ => unreachable!(),
292 }
293 }
294}
295
296#[cfg(test)]
297mod test {
298 use super::*;
299 use crate::convert::*;
300 use crate::rgb;
301 use angle::*;
302 use approx::*;
303 use std::f32::consts;
304
305 use crate::test;
306
307 #[test]
308 fn test_construct() {
309 let c1 = Hsv::new(Deg(50.0), 0.5, 0.3);
310
311 assert_ulps_eq!(c1.hue(), Deg(50.0));
312 assert_ulps_eq!(c1.saturation(), 0.5);
313 assert_ulps_eq!(c1.value(), 0.3);
314
315 let mut c2 = Hsv::new(Turns(0.9), 0.5, 0.75);
316 assert_ulps_eq!(c2.hue(), Turns(0.9));
317 c2.set_saturation(0.33);
318 assert_ulps_eq!(c2, Hsv::new(Turns(0.9), 0.33, 0.75));
319
320 let c3 = Hsv::from_tuple((Deg(50.0), 0.33, 0.66));
321 assert_eq!(c3.to_tuple(), (Deg(50.0), 0.33, 0.66));
322 }
323
324 #[test]
325 fn test_invert() {
326 let c1 = Hsv::new(Deg(30.0), 0.3, 0.6);
327 assert_ulps_eq!(c1.invert(), Hsv::new(Deg(210.0), 0.7, 0.4));
328
329 let c2 = Hsv::new(Deg(320.0), 0.5, 0.0);
330 assert_ulps_eq!(c2.invert(), Hsv::new(Deg(140.0), 0.5, 1.0));
331 }
332
333 #[test]
334 fn test_lerp() {
335 let c1 = Hsv::new(Rad(0.5), 0.0, 0.25);
336 let c2 = Hsv::new(Rad(1.5), 0.5, 0.25);
337 assert_ulps_eq!(c1.lerp(&c2, 0.0), c1);
338 assert_ulps_eq!(c1.lerp(&c2, 1.0), c2);
339 assert_ulps_eq!(c1.lerp(&c2, 0.25), Hsv::new(Rad(0.75), 0.125, 0.25));
340 assert_ulps_eq!(c1.lerp(&c2, 0.75), Hsv::new(Rad(1.25), 0.375, 0.25));
341
342 let c3 = Hsv::new(Deg(320.0), 0.0, 1.0);
343 let c4 = Hsv::new(Deg(100.0), 1.0, 0.0);
344 assert_ulps_eq!(c3.lerp(&c4, 0.0), c3);
345 assert_ulps_eq!(c3.lerp(&c4, 1.0).normalize(), c4);
346 assert_ulps_eq!(c3.lerp(&c4, 0.5).normalize(), Hsv::new(Deg(30.0), 0.5, 0.5));
347 }
348
349 #[test]
350 fn test_normalize() {
351 let c1 = Hsv::new(Deg(-120.0), 0.25, 0.75);
352 assert!(!c1.is_normalized());
353 assert_ulps_eq!(c1.normalize(), Hsv::new(Deg(240.0), 0.25, 0.75));
354
355 let c2 = Hsv::new(Turns(11.25), -1.11, 1.11);
356 assert_ulps_eq!(c2.normalize(), Hsv::new(Turns(0.25), 0.0, 1.0));
357 }
358
359 #[test]
360 fn test_chroma() {
361 let test_data = test::build_hs_test_data();
362
363 for item in test_data.iter() {
364 assert_relative_eq!(item.hsv.get_chroma(), item.chroma, epsilon = 1e-3);
365 }
366
367 let c1 = Hsv::new(Deg(100.0), 0.5, 0.5);
368 assert_ulps_eq!(c1.get_chroma(), 0.25);
369 assert_relative_eq!(
370 Hsv::new(Deg(240.50), 0.316, 0.721).get_chroma(),
371 0.228,
372 epsilon = 1e-3
373 );
374 assert_relative_eq!(
375 Hsv::new(Deg(120.0), 0.0, 0.0).get_chroma(),
376 0.0,
377 epsilon = 1e-3
378 );
379 }
380
381 #[test]
382 fn test_get_hue() {
383 assert_ulps_eq!(Hsv::new(Deg(120.0), 0.25, 0.75).get_hue(), Deg(120.0));
384 assert_ulps_eq!(
385 Hsv::new(Deg(180.0_f32), 0.35, 0.55).get_hue(),
386 Rad(consts::PI)
387 );
388 assert_ulps_eq!(Hsv::new(Turns(0.0), 0.00, 0.00).get_hue(), Rad(0.0));
389 }
390
391 #[test]
392 fn test_rgb_from_hsv() {
393 let test_data = test::build_hs_test_data();
394
395 for item in test_data.iter() {
396 let rgb = rgb::Rgb::from_color(&item.hsv);
397 assert_relative_eq!(rgb, item.rgb, epsilon = 1e-3);
398 }
399 }
400
401 #[test]
402 fn test_cast() {
403 let c1 = Hsv::new(Deg(180.0_f32), 0.5_f32, 0.3);
404 assert_relative_eq!(
405 c1.color_cast(),
406 Hsv::new(Rad(consts::PI), 0.5_f32, 0.3),
407 epsilon = 1e-6
408 );
409
410 let c2 = Hsv::new(Deg(55.0), 0.3, 0.2);
411 assert_relative_eq!(c2.color_cast(), Hsv::new(Deg(55.0_f32), 0.3_f32, 0.2_f32));
412
413 let c3 = Hsv::new(Rad(2.0), 0.88, 0.66);
414 assert_relative_eq!(c3.color_cast(), c3);
415 }
416}