1use crate::color_difference::EuclideanDistance;
2use crate::{
3 impl_componentwise_vector_space, Alpha, ColorToComponents, ColorToPacked, Gray, LinearRgba,
4 Luminance, Mix, StandardColor, Xyza,
5};
6use bevy_math::{Vec3, Vec4};
7use bevy_reflect::prelude::*;
8use thiserror::Error;
9
10#[doc = include_str!("../docs/conversion.md")]
12#[doc = include_str!("../docs/diagrams/model_graph.svg")]
14#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
16#[reflect(PartialEq, Default)]
17#[cfg_attr(
18 feature = "serialize",
19 derive(serde::Serialize, serde::Deserialize),
20 reflect(Serialize, Deserialize)
21)]
22pub struct Srgba {
23 pub red: f32,
25 pub green: f32,
27 pub blue: f32,
29 pub alpha: f32,
31}
32
33impl StandardColor for Srgba {}
34
35impl_componentwise_vector_space!(Srgba, [red, green, blue, alpha]);
36
37impl Srgba {
38 pub const BLACK: Srgba = Srgba::new(0.0, 0.0, 0.0, 1.0);
43 #[doc(alias = "transparent")]
45 pub const NONE: Srgba = Srgba::new(0.0, 0.0, 0.0, 0.0);
46 pub const WHITE: Srgba = Srgba::new(1.0, 1.0, 1.0, 1.0);
48
49 pub const RED: Self = Self {
51 red: 1.0,
52 green: 0.0,
53 blue: 0.0,
54 alpha: 1.0,
55 };
56
57 pub const GREEN: Self = Self {
59 red: 0.0,
60 green: 1.0,
61 blue: 0.0,
62 alpha: 1.0,
63 };
64
65 pub const BLUE: Self = Self {
67 red: 0.0,
68 green: 0.0,
69 blue: 1.0,
70 alpha: 1.0,
71 };
72
73 pub const fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
82 Self {
83 red,
84 green,
85 blue,
86 alpha,
87 }
88 }
89
90 pub const fn rgb(red: f32, green: f32, blue: f32) -> Self {
98 Self {
99 red,
100 green,
101 blue,
102 alpha: 1.0,
103 }
104 }
105
106 pub const fn with_red(self, red: f32) -> Self {
108 Self { red, ..self }
109 }
110
111 pub const fn with_green(self, green: f32) -> Self {
113 Self { green, ..self }
114 }
115
116 pub const fn with_blue(self, blue: f32) -> Self {
118 Self { blue, ..self }
119 }
120
121 pub fn hex<T: AsRef<str>>(hex: T) -> Result<Self, HexColorError> {
134 let hex = hex.as_ref();
135 let hex = hex.strip_prefix('#').unwrap_or(hex);
136
137 match hex.len() {
138 3 => {
140 let [l, b] = u16::from_str_radix(hex, 16)?.to_be_bytes();
141 let (r, g, b) = (l & 0x0F, (b & 0xF0) >> 4, b & 0x0F);
142 Ok(Self::rgb_u8(r << 4 | r, g << 4 | g, b << 4 | b))
143 }
144 4 => {
146 let [l, b] = u16::from_str_radix(hex, 16)?.to_be_bytes();
147 let (r, g, b, a) = ((l & 0xF0) >> 4, l & 0xF, (b & 0xF0) >> 4, b & 0x0F);
148 Ok(Self::rgba_u8(
149 r << 4 | r,
150 g << 4 | g,
151 b << 4 | b,
152 a << 4 | a,
153 ))
154 }
155 6 => {
157 let [_, r, g, b] = u32::from_str_radix(hex, 16)?.to_be_bytes();
158 Ok(Self::rgb_u8(r, g, b))
159 }
160 8 => {
162 let [r, g, b, a] = u32::from_str_radix(hex, 16)?.to_be_bytes();
163 Ok(Self::rgba_u8(r, g, b, a))
164 }
165 _ => Err(HexColorError::Length),
166 }
167 }
168
169 pub fn to_hex(&self) -> String {
171 let [r, g, b, a] = self.to_u8_array();
172 match a {
173 255 => format!("#{:02X}{:02X}{:02X}", r, g, b),
174 _ => format!("#{:02X}{:02X}{:02X}{:02X}", r, g, b, a),
175 }
176 }
177
178 pub fn rgb_u8(r: u8, g: u8, b: u8) -> Self {
189 Self::from_u8_array_no_alpha([r, g, b])
190 }
191
192 pub fn rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self {
206 Self::from_u8_array([r, g, b, a])
207 }
208
209 pub fn gamma_function(value: f32) -> f32 {
211 if value <= 0.0 {
212 return value;
213 }
214 if value <= 0.04045 {
215 value / 12.92 } else {
217 ((value + 0.055) / 1.055).powf(2.4) }
219 }
220
221 pub fn gamma_function_inverse(value: f32) -> f32 {
223 if value <= 0.0 {
224 return value;
225 }
226
227 if value <= 0.0031308 {
228 value * 12.92 } else {
230 (1.055 * value.powf(1.0 / 2.4)) - 0.055 }
232 }
233}
234
235impl Default for Srgba {
236 fn default() -> Self {
237 Self::WHITE
238 }
239}
240
241impl Luminance for Srgba {
242 #[inline]
243 fn luminance(&self) -> f32 {
244 let linear: LinearRgba = (*self).into();
245 linear.luminance()
246 }
247
248 #[inline]
249 fn with_luminance(&self, luminance: f32) -> Self {
250 let linear: LinearRgba = (*self).into();
251 linear
252 .with_luminance(Srgba::gamma_function(luminance))
253 .into()
254 }
255
256 #[inline]
257 fn darker(&self, amount: f32) -> Self {
258 let linear: LinearRgba = (*self).into();
259 linear.darker(amount).into()
260 }
261
262 #[inline]
263 fn lighter(&self, amount: f32) -> Self {
264 let linear: LinearRgba = (*self).into();
265 linear.lighter(amount).into()
266 }
267}
268
269impl Mix for Srgba {
270 #[inline]
271 fn mix(&self, other: &Self, factor: f32) -> Self {
272 let n_factor = 1.0 - factor;
273 Self {
274 red: self.red * n_factor + other.red * factor,
275 green: self.green * n_factor + other.green * factor,
276 blue: self.blue * n_factor + other.blue * factor,
277 alpha: self.alpha * n_factor + other.alpha * factor,
278 }
279 }
280}
281
282impl Alpha for Srgba {
283 #[inline]
284 fn with_alpha(&self, alpha: f32) -> Self {
285 Self { alpha, ..*self }
286 }
287
288 #[inline]
289 fn alpha(&self) -> f32 {
290 self.alpha
291 }
292
293 #[inline]
294 fn set_alpha(&mut self, alpha: f32) {
295 self.alpha = alpha;
296 }
297}
298
299impl EuclideanDistance for Srgba {
300 #[inline]
301 fn distance_squared(&self, other: &Self) -> f32 {
302 let dr = self.red - other.red;
303 let dg = self.green - other.green;
304 let db = self.blue - other.blue;
305 dr * dr + dg * dg + db * db
306 }
307}
308
309impl Gray for Srgba {
310 const BLACK: Self = Self::BLACK;
311 const WHITE: Self = Self::WHITE;
312}
313
314impl ColorToComponents for Srgba {
315 fn to_f32_array(self) -> [f32; 4] {
316 [self.red, self.green, self.blue, self.alpha]
317 }
318
319 fn to_f32_array_no_alpha(self) -> [f32; 3] {
320 [self.red, self.green, self.blue]
321 }
322
323 fn to_vec4(self) -> Vec4 {
324 Vec4::new(self.red, self.green, self.blue, self.alpha)
325 }
326
327 fn to_vec3(self) -> Vec3 {
328 Vec3::new(self.red, self.green, self.blue)
329 }
330
331 fn from_f32_array(color: [f32; 4]) -> Self {
332 Self {
333 red: color[0],
334 green: color[1],
335 blue: color[2],
336 alpha: color[3],
337 }
338 }
339
340 fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
341 Self {
342 red: color[0],
343 green: color[1],
344 blue: color[2],
345 alpha: 1.0,
346 }
347 }
348
349 fn from_vec4(color: Vec4) -> Self {
350 Self {
351 red: color[0],
352 green: color[1],
353 blue: color[2],
354 alpha: color[3],
355 }
356 }
357
358 fn from_vec3(color: Vec3) -> Self {
359 Self {
360 red: color[0],
361 green: color[1],
362 blue: color[2],
363 alpha: 1.0,
364 }
365 }
366}
367
368impl ColorToPacked for Srgba {
369 fn to_u8_array(self) -> [u8; 4] {
370 [self.red, self.green, self.blue, self.alpha]
371 .map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
372 }
373
374 fn to_u8_array_no_alpha(self) -> [u8; 3] {
375 [self.red, self.green, self.blue].map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
376 }
377
378 fn from_u8_array(color: [u8; 4]) -> Self {
379 Self::from_f32_array(color.map(|u| u as f32 / 255.0))
380 }
381
382 fn from_u8_array_no_alpha(color: [u8; 3]) -> Self {
383 Self::from_f32_array_no_alpha(color.map(|u| u as f32 / 255.0))
384 }
385}
386
387impl From<LinearRgba> for Srgba {
388 #[inline]
389 fn from(value: LinearRgba) -> Self {
390 Self {
391 red: Srgba::gamma_function_inverse(value.red),
392 green: Srgba::gamma_function_inverse(value.green),
393 blue: Srgba::gamma_function_inverse(value.blue),
394 alpha: value.alpha,
395 }
396 }
397}
398
399impl From<Srgba> for LinearRgba {
400 #[inline]
401 fn from(value: Srgba) -> Self {
402 Self {
403 red: Srgba::gamma_function(value.red),
404 green: Srgba::gamma_function(value.green),
405 blue: Srgba::gamma_function(value.blue),
406 alpha: value.alpha,
407 }
408 }
409}
410
411impl From<Xyza> for Srgba {
414 fn from(value: Xyza) -> Self {
415 LinearRgba::from(value).into()
416 }
417}
418
419impl From<Srgba> for Xyza {
420 fn from(value: Srgba) -> Self {
421 LinearRgba::from(value).into()
422 }
423}
424
425#[derive(Debug, Error, PartialEq, Eq)]
427pub enum HexColorError {
428 #[error("Invalid hex string")]
430 Parse(#[from] std::num::ParseIntError),
431 #[error("Unexpected length of hex string")]
433 Length,
434 #[error("Invalid hex char")]
436 Char(char),
437}
438
439#[cfg(test)]
440mod tests {
441 use crate::testing::assert_approx_eq;
442
443 use super::*;
444
445 #[test]
446 fn test_to_from_linear() {
447 let srgba = Srgba::new(0.0, 0.5, 1.0, 1.0);
448 let linear_rgba: LinearRgba = srgba.into();
449 assert_eq!(linear_rgba.red, 0.0);
450 assert_approx_eq!(linear_rgba.green, 0.2140, 0.0001);
451 assert_approx_eq!(linear_rgba.blue, 1.0, 0.0001);
452 assert_eq!(linear_rgba.alpha, 1.0);
453 let srgba2: Srgba = linear_rgba.into();
454 assert_eq!(srgba2.red, 0.0);
455 assert_approx_eq!(srgba2.green, 0.5, 0.0001);
456 assert_approx_eq!(srgba2.blue, 1.0, 0.0001);
457 assert_eq!(srgba2.alpha, 1.0);
458 }
459
460 #[test]
461 fn euclidean_distance() {
462 let a = Srgba::new(0.0, 0.0, 0.0, 1.0);
464 let b = Srgba::new(1.0, 1.0, 1.0, 1.0);
465 assert_eq!(a.distance_squared(&b), 3.0);
466
467 let a = Srgba::new(0.0, 0.0, 0.0, 1.0);
469 let b = Srgba::new(1.0, 1.0, 1.0, 0.0);
470 assert_eq!(a.distance_squared(&b), 3.0);
471
472 let a = Srgba::new(0.0, 0.0, 0.0, 1.0);
474 let b = Srgba::new(1.0, 0.0, 0.0, 1.0);
475 assert_eq!(a.distance_squared(&b), 1.0);
476 }
477
478 #[test]
479 fn darker_lighter() {
480 let color = Srgba::new(0.4, 0.5, 0.6, 1.0);
482 let darker1 = color.darker(0.1);
483 let darker2 = darker1.darker(0.1);
484 let twice_as_dark = color.darker(0.2);
485 assert!(darker2.distance_squared(&twice_as_dark) < 0.0001);
486
487 let lighter1 = color.lighter(0.1);
488 let lighter2 = lighter1.lighter(0.1);
489 let twice_as_light = color.lighter(0.2);
490 assert!(lighter2.distance_squared(&twice_as_light) < 0.0001);
491 }
492
493 #[test]
494 fn hex_color() {
495 assert_eq!(Srgba::hex("FFF"), Ok(Srgba::WHITE));
496 assert_eq!(Srgba::hex("FFFF"), Ok(Srgba::WHITE));
497 assert_eq!(Srgba::hex("FFFFFF"), Ok(Srgba::WHITE));
498 assert_eq!(Srgba::hex("FFFFFFFF"), Ok(Srgba::WHITE));
499 assert_eq!(Srgba::hex("000"), Ok(Srgba::BLACK));
500 assert_eq!(Srgba::hex("000F"), Ok(Srgba::BLACK));
501 assert_eq!(Srgba::hex("000000"), Ok(Srgba::BLACK));
502 assert_eq!(Srgba::hex("000000FF"), Ok(Srgba::BLACK));
503 assert_eq!(Srgba::hex("03a9f4"), Ok(Srgba::rgb_u8(3, 169, 244)));
504 assert_eq!(Srgba::hex("yy"), Err(HexColorError::Length));
505 assert_eq!(Srgba::hex("#f2a"), Ok(Srgba::rgb_u8(255, 34, 170)));
506 assert_eq!(Srgba::hex("#e23030"), Ok(Srgba::rgb_u8(226, 48, 48)));
507 assert_eq!(Srgba::hex("#ff"), Err(HexColorError::Length));
508 assert_eq!(Srgba::hex("11223344"), Ok(Srgba::rgba_u8(17, 34, 51, 68)));
509 assert_eq!(Srgba::hex("1234"), Ok(Srgba::rgba_u8(17, 34, 51, 68)));
510 assert_eq!(Srgba::hex("12345678"), Ok(Srgba::rgba_u8(18, 52, 86, 120)));
511 assert_eq!(Srgba::hex("4321"), Ok(Srgba::rgba_u8(68, 51, 34, 17)));
512
513 assert!(matches!(Srgba::hex("yyy"), Err(HexColorError::Parse(_))));
514 assert!(matches!(Srgba::hex("##fff"), Err(HexColorError::Parse(_))));
515 }
516}