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