1use std::fmt::{Debug, Display, Formatter};
2
3#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
5pub enum Error {
6 FromF32(String),
7 FromU8(String),
8 ToU8(String),
9}
10
11impl Display for Error {
12 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
13 match self {
14 Self::FromF32(msg) => write!(f, "Failed to convert `f32` to `Color`: {}", msg),
15 Self::FromU8(msg) => write!(f, "Failed to convert `u8` to `Color`: {}", msg),
16 Self::ToU8(msg) => write!(f, "Failed to convert `Color` to `Vec<u8>`: {}", msg),
17 }
18 }
19}
20
21impl std::error::Error for Error {}
22
23#[derive(Copy, Clone, PartialEq, Debug)]
25pub struct Color {
26 pub red: f32,
27 pub green: f32,
28 pub blue: f32,
29 pub alpha: f32,
30}
31
32impl Color {
33 pub fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Result<Self, Error> {
39 if !(0.0..=1.0).contains(&red)
40 || !(0.0..=1.0).contains(&green)
41 || !(0.0..=1.0).contains(&blue)
42 || !(0.0..=1.0).contains(&alpha)
43 {
44 Err(Error::FromF32(format!(
45 "Color values must be between 0.0 and 1.0, got: ({}, {}, {}, {})",
46 red, green, blue, alpha
47 )))
48 } else {
49 Ok(Self {
50 red,
51 green,
52 blue,
53 alpha,
54 })
55 }
56 }
57}
58
59impl Default for Color {
60 fn default() -> Self {
62 Self {
63 red: 1.0,
64 green: 1.0,
65 blue: 1.0,
66 alpha: 1.0,
67 }
68 }
69}
70
71impl From<Color> for Vec<f32> {
72 fn from(value: Color) -> Vec<f32> {
74 vec![value.red, value.green, value.blue, value.alpha]
75 }
76}
77
78#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
79impl TryFrom<Color> for Vec<u8> {
80 type Error = Error;
81
82 fn try_from(value: Color) -> Result<Vec<u8>, Error> {
88 if !(0.0..=1.0).contains(&value.red)
89 || !(0.0..=1.0).contains(&value.green)
90 || !(0.0..=1.0).contains(&value.blue)
91 || !(0.0..=1.0).contains(&value.alpha)
92 {
93 return Err(Error::ToU8(format!(
94 "Color values must be between 0.0 and 1.0, got: {:?}",
95 value
96 )));
97 }
98
99 Ok(vec![
100 (value.red * 255.0).round() as u8,
101 (value.green * 255.0).round() as u8,
102 (value.blue * 255.0).round() as u8,
103 (value.alpha * 255.0).round() as u8,
104 ])
105 }
106}
107
108impl TryFrom<Vec<f32>> for Color {
109 type Error = Error;
110
111 fn try_from(value: Vec<f32>) -> std::result::Result<Self, Self::Error> {
117 if 3 > value.len() || 4 < value.len() {
118 return Err(Self::Error::FromF32(format!(
119 "Invalid amount of arguments (expected: 3-4, actual: {})",
120 value.len()
121 )));
122 }
123
124 let alpha = if value.len() == 4 { value[3] } else { 1.0 };
125
126 Color::new(value[0], value[1], value[2], alpha)
127 }
128}
129
130impl TryFrom<Vec<u8>> for Color {
131 type Error = Error;
132
133 fn try_from(value: Vec<u8>) -> std::result::Result<Self, Self::Error> {
139 if 3 > value.len() || 4 < value.len() {
140 return Err(Self::Error::FromU8(format!(
141 "Invalid amount of arguments (expected: 3-4, actual: {})",
142 value.len()
143 )));
144 }
145
146 let alpha = if value.len() == 4 { value[3] } else { 255 };
147 let val = [value[0], value[1], value[2], alpha];
148
149 if !(0..=255).contains(&val[0])
150 || !(0..=255).contains(&val[1])
151 || !(0..=255).contains(&val[2])
152 || !(0..=255).contains(&val[3])
153 {
154 return Err(Error::FromU8(format!(
155 "Color values must be between 0 and 255, got: {:?}",
156 val
157 )));
158 }
159
160 Color::new(
161 f32::from(val[0]) / 255.0,
162 f32::from(val[1]) / 255.0,
163 f32::from(val[2]) / 255.0,
164 f32::from(val[3]) / 255.0,
165 )
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 #[allow(clippy::float_cmp)]
175 fn color() {
176 let color = Color::new(0.1, 0.2, 0.3, 0.4).unwrap();
177 assert_eq!(color.red, 0.1);
178 assert_eq!(color.green, 0.2);
179 assert_eq!(color.blue, 0.3);
180 assert_eq!(color.alpha, 0.4);
181 }
182
183 #[test]
184 fn color_fail() {
185 let color = Color::new(1.0, 2.0, 3.0, 4.0);
186 assert!(matches!(color, Err(Error::FromF32(_))));
187 }
188
189 #[test]
190 fn color_from() {
191 let color = Color::new(0.1, 0.2, 0.3, 0.4).unwrap();
192 assert_eq!(Vec::<f32>::from(color), vec![0.1, 0.2, 0.3, 0.4]);
193 }
194
195 #[test]
196 fn color_from_u8() {
197 let color = Color::new(0.5, 0.7, 0.0, 0.33331).unwrap();
198 assert_eq!(Vec::<u8>::try_from(color), Ok(vec![128, 179, 0, 85]));
199 }
200
201 #[test]
202 fn color_from_u8_fail() {
203 let color = Color {
204 red: 1.0,
205 green: 2.0,
206 blue: 3.0,
207 alpha: 4.0,
208 };
209 assert!(matches!(Vec::<u8>::try_from(color), Err(Error::ToU8(_))));
210 }
211
212 #[test]
213 fn try_from_color_rgb() {
214 let vec = vec![0.1, 0.2, 0.3, 0.4];
215 let color = Color::try_from(vec);
216 assert!(color.is_ok());
217 assert_eq!(color.unwrap(), Color::new(0.1, 0.2, 0.3, 0.4).unwrap());
218 }
219
220 #[test]
221 fn try_from_color_rgba() {
222 let vec = vec![0.1, 0.2, 0.3, 0.4];
223 let color = Color::try_from(vec);
224 assert!(color.is_ok());
225 assert_eq!(color.unwrap(), Color::new(0.1, 0.2, 0.3, 0.4).unwrap());
226 }
227
228 #[test]
229 fn try_from_color_err_too_little_arguments() {
230 let vec = vec![1.0, 2.0];
231 let color = Color::try_from(vec);
232 assert!(color.is_err());
233 assert!(matches!(color.unwrap_err(), Error::FromF32(_)));
234 }
235
236 #[test]
237 fn try_from_color_err_too_many_arguments() {
238 let vec = vec![1.0, 2.0, 3.0, 4.0, 5.0];
239 let color = Color::try_from(vec);
240 assert!(color.is_err());
241 assert!(matches!(color.unwrap_err(), Error::FromF32(_)));
242 }
243
244 #[test]
245 fn try_from_color_u8() {
246 let vec = vec![128, 255, 0, 255];
247 let color = Color::try_from(vec);
248 assert!(color.is_ok());
249 assert_eq!(
250 color.unwrap(),
251 Color::new(0.501_960_8, 1.0, 0.0, 1.0).unwrap()
252 );
253 }
254}