1use crate::prelude::{RGB, RGBA};
2use std::convert::From;
3
4#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5#[derive(PartialEq, Copy, Clone, Default, Debug)]
6pub struct HSV {
10 pub h: f32,
12 pub s: f32,
14 pub v: f32,
16}
17
18impl From<RGB> for HSV {
20 fn from(rgb: RGB) -> Self {
21 rgb.to_hsv()
22 }
23}
24
25impl From<RGBA> for HSV {
27 fn from(rgba: RGBA) -> Self {
28 rgba.to_rgb().to_hsv()
29 }
30}
31
32impl HSV {
33 #[must_use]
35 pub fn new() -> Self {
36 Self {
37 h: 0.0,
38 s: 0.0,
39 v: 0.0,
40 }
41 }
42
43 #[inline]
51 #[must_use]
52 pub const fn from_f32(h: f32, s: f32, v: f32) -> Self {
53 Self { h, s, v }
54 }
55
56 #[inline]
58 #[must_use]
59 pub fn to_rgba(&self, alpha: f32) -> RGBA {
60 self.to_rgb().to_rgba(alpha)
61 }
62
63 #[allow(clippy::many_single_char_names)] #[allow(clippy::cast_precision_loss)]
66 #[allow(clippy::cast_possible_truncation)]
67 #[inline]
68 #[must_use]
69 pub fn to_rgb(&self) -> RGB {
70 let h = self.h;
71 let s = self.s;
72 let v = self.v;
73
74 let mut r: f32 = 0.0;
75 let mut g: f32 = 0.0;
76 let mut b: f32 = 0.0;
77
78 let i = f32::floor(h * 6.0) as i32;
79 let f = h * 6.0 - i as f32;
80 let p = v * (1.0 - s);
81 let q = v * (1.0 - f * s);
82 let t = v * (1.0 - (1.0 - f) * s);
83
84 match i % 6 {
85 0 => {
86 r = v;
87 g = t;
88 b = p;
89 }
90 1 => {
91 r = q;
92 g = v;
93 b = p;
94 }
95 2 => {
96 r = p;
97 g = v;
98 b = t;
99 }
100 3 => {
101 r = p;
102 g = q;
103 b = v;
104 }
105 4 => {
106 r = t;
107 g = p;
108 b = v;
109 }
110 5 => {
111 r = v;
112 g = p;
113 b = q;
114 }
115 _ => {}
117 }
118
119 RGB::from_f32(r, g, b)
120 }
121
122 #[inline]
138 #[must_use]
139 pub fn lerp(&self, color: Self, percent: f32) -> Self {
140 let range = (color.h - self.h, color.s - self.s, color.v - self.v);
141 Self {
142 h: self.h + range.0 * percent,
143 s: self.s + range.1 * percent,
144 v: self.v + range.2 * percent,
145 }
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use crate::prelude::*;
152
153 #[test]
154 fn make_hsv_minimal() {
156 let black = HSV::new();
157 assert!(black.h < std::f32::EPSILON);
158 assert!(black.s < std::f32::EPSILON);
159 assert!(black.v < std::f32::EPSILON);
160 }
161
162 #[test]
163 fn convert_red_to_hsv() {
165 let red = RGB::from_f32(1.0, 0.0, 0.0);
166 let hsv = red.to_hsv();
167 assert!(hsv.h < std::f32::EPSILON);
168 assert!(f32::abs(hsv.s - 1.0) < std::f32::EPSILON);
169 assert!(f32::abs(hsv.v - 1.0) < std::f32::EPSILON);
170 }
171
172 #[test]
173 fn convert_green_to_hsv() {
175 let green = RGB::from_f32(0.0, 1.0, 0.0);
176 let hsv = green.to_hsv();
177 assert!(f32::abs(hsv.h - 120.0 / 360.0) < std::f32::EPSILON);
178 assert!(f32::abs(hsv.s - 1.0) < std::f32::EPSILON);
179 assert!(f32::abs(hsv.v - 1.0) < std::f32::EPSILON);
180 }
181
182 #[test]
183 fn convert_blue_to_hsv() {
185 let blue = RGB::from_f32(0.0, 0.0, 1.0);
186 let hsv = blue.to_hsv();
187 assert!(f32::abs(hsv.h - 240.0 / 360.0) < std::f32::EPSILON);
188 assert!(f32::abs(hsv.s - 1.0) < std::f32::EPSILON);
189 assert!(f32::abs(hsv.v - 1.0) < std::f32::EPSILON);
190 }
191
192 #[test]
193 fn convert_olive_to_hsv() {
195 let grey = RGB::from_u8(128, 128, 0);
196 let hsv = grey.to_hsv();
197 assert!(f32::abs(hsv.h - 60.0 / 360.0) < std::f32::EPSILON);
198 assert!(f32::abs(hsv.s - 1.0) < std::f32::EPSILON);
199 assert!(f32::abs(hsv.v - 0.5019_608) < std::f32::EPSILON);
200 }
201
202 #[test]
203 fn test_lerp() {
205 let black = RGB::named(BLACK).to_hsv();
206 let white = RGB::named(WHITE).to_hsv();
207 assert!(black.lerp(white, 0.0) == black);
208 assert!(black.lerp(white, 1.0) == white);
209 }
210}