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);
50 #[doc(alias = "transparent")]
53 pub const NONE: Srgba = Srgba::new(0.0, 0.0, 0.0, 0.0);
54 pub const WHITE: Srgba = Srgba::new(1.0, 1.0, 1.0, 1.0);
57 pub const RED: Srgba = Srgba::new(1.0, 0.0, 0.0, 1.0);
60 pub const GREEN: Srgba = Srgba::new(0.0, 1.0, 0.0, 1.0);
63 pub const BLUE: Srgba = Srgba::new(0.0, 0.0, 1.0, 1.0);
66
67 pub const fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
76 Self {
77 red,
78 green,
79 blue,
80 alpha,
81 }
82 }
83
84 pub const fn rgb(red: f32, green: f32, blue: f32) -> Self {
92 Self {
93 red,
94 green,
95 blue,
96 alpha: 1.0,
97 }
98 }
99
100 pub const fn with_red(self, red: f32) -> Self {
102 Self { red, ..self }
103 }
104
105 pub const fn with_green(self, green: f32) -> Self {
107 Self { green, ..self }
108 }
109
110 pub const fn with_blue(self, blue: f32) -> Self {
112 Self { blue, ..self }
113 }
114
115 pub fn hex<T: AsRef<str>>(hex: T) -> Result<Self, HexColorError> {
128 let hex = hex.as_ref();
129 let hex = hex.strip_prefix('#').unwrap_or(hex);
130
131 match hex.len() {
132 3 => {
134 let [l, b] = u16::from_str_radix(hex, 16)?.to_be_bytes();
135 let (r, g, b) = (l & 0x0F, (b & 0xF0) >> 4, b & 0x0F);
136 Ok(Self::rgb_u8((r << 4) | r, (g << 4) | g, (b << 4) | b))
137 }
138 4 => {
140 let [l, b] = u16::from_str_radix(hex, 16)?.to_be_bytes();
141 let (r, g, b, a) = ((l & 0xF0) >> 4, l & 0xF, (b & 0xF0) >> 4, b & 0x0F);
142 Ok(Self::rgba_u8(
143 (r << 4) | r,
144 (g << 4) | g,
145 (b << 4) | b,
146 (a << 4) | a,
147 ))
148 }
149 6 => {
151 let [_, r, g, b] = u32::from_str_radix(hex, 16)?.to_be_bytes();
152 Ok(Self::rgb_u8(r, g, b))
153 }
154 8 => {
156 let [r, g, b, a] = u32::from_str_radix(hex, 16)?.to_be_bytes();
157 Ok(Self::rgba_u8(r, g, b, a))
158 }
159 _ => Err(HexColorError::Length),
160 }
161 }
162
163 #[cfg(feature = "alloc")]
165 pub fn to_hex(&self) -> String {
166 let [r, g, b, a] = self.to_u8_array();
167 match a {
168 255 => format!("#{r:02X}{g:02X}{b:02X}"),
169 _ => format!("#{r:02X}{g:02X}{b:02X}{a:02X}"),
170 }
171 }
172
173 pub fn rgb_u8(r: u8, g: u8, b: u8) -> Self {
183 Self::from_u8_array_no_alpha([r, g, b])
184 }
185
186 pub fn rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self {
199 Self::from_u8_array([r, g, b, a])
200 }
201
202 pub fn gamma_function(value: f32) -> f32 {
204 if value <= 0.0 {
205 return value;
206 }
207 if value <= 0.04045 {
208 value / 12.92 } else {
210 ops::powf((value + 0.055) / 1.055, 2.4) }
212 }
213
214 pub fn gamma_function_inverse(value: f32) -> f32 {
216 if value <= 0.0 {
217 return value;
218 }
219
220 if value <= 0.0031308 {
221 value * 12.92 } else {
223 (1.055 * ops::powf(value, 1.0 / 2.4)) - 0.055 }
225 }
226}
227
228impl Default for Srgba {
229 fn default() -> Self {
230 Self::WHITE
231 }
232}
233
234impl Luminance for Srgba {
235 #[inline]
236 fn luminance(&self) -> f32 {
237 let linear: LinearRgba = (*self).into();
238 linear.luminance()
239 }
240
241 #[inline]
242 fn with_luminance(&self, luminance: f32) -> Self {
243 let linear: LinearRgba = (*self).into();
244 linear
245 .with_luminance(Srgba::gamma_function(luminance))
246 .into()
247 }
248
249 #[inline]
250 fn darker(&self, amount: f32) -> Self {
251 let linear: LinearRgba = (*self).into();
252 linear.darker(amount).into()
253 }
254
255 #[inline]
256 fn lighter(&self, amount: f32) -> Self {
257 let linear: LinearRgba = (*self).into();
258 linear.lighter(amount).into()
259 }
260}
261
262impl Mix for Srgba {
263 #[inline]
264 fn mix(&self, other: &Self, factor: f32) -> Self {
265 let n_factor = 1.0 - factor;
266 Self {
267 red: self.red * n_factor + other.red * factor,
268 green: self.green * n_factor + other.green * factor,
269 blue: self.blue * n_factor + other.blue * factor,
270 alpha: self.alpha * n_factor + other.alpha * factor,
271 }
272 }
273}
274
275impl Alpha for Srgba {
276 #[inline]
277 fn with_alpha(&self, alpha: f32) -> Self {
278 Self { alpha, ..*self }
279 }
280
281 #[inline]
282 fn alpha(&self) -> f32 {
283 self.alpha
284 }
285
286 #[inline]
287 fn set_alpha(&mut self, alpha: f32) {
288 self.alpha = alpha;
289 }
290}
291
292impl EuclideanDistance for Srgba {
293 #[inline]
294 fn distance_squared(&self, other: &Self) -> f32 {
295 let dr = self.red - other.red;
296 let dg = self.green - other.green;
297 let db = self.blue - other.blue;
298 dr * dr + dg * dg + db * db
299 }
300}
301
302impl Gray for Srgba {
303 const BLACK: Self = Self::BLACK;
304 const WHITE: Self = Self::WHITE;
305}
306
307impl ColorToComponents for Srgba {
308 fn to_f32_array(self) -> [f32; 4] {
309 [self.red, self.green, self.blue, self.alpha]
310 }
311
312 fn to_f32_array_no_alpha(self) -> [f32; 3] {
313 [self.red, self.green, self.blue]
314 }
315
316 fn to_vec4(self) -> Vec4 {
317 Vec4::new(self.red, self.green, self.blue, self.alpha)
318 }
319
320 fn to_vec3(self) -> Vec3 {
321 Vec3::new(self.red, self.green, self.blue)
322 }
323
324 fn from_f32_array(color: [f32; 4]) -> Self {
325 Self {
326 red: color[0],
327 green: color[1],
328 blue: color[2],
329 alpha: color[3],
330 }
331 }
332
333 fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
334 Self {
335 red: color[0],
336 green: color[1],
337 blue: color[2],
338 alpha: 1.0,
339 }
340 }
341
342 fn from_vec4(color: Vec4) -> Self {
343 Self {
344 red: color[0],
345 green: color[1],
346 blue: color[2],
347 alpha: color[3],
348 }
349 }
350
351 fn from_vec3(color: Vec3) -> Self {
352 Self {
353 red: color[0],
354 green: color[1],
355 blue: color[2],
356 alpha: 1.0,
357 }
358 }
359}
360
361impl ColorToPacked for Srgba {
362 fn to_u8_array(self) -> [u8; 4] {
363 [self.red, self.green, self.blue, self.alpha]
364 .map(|v| ops::round(v.clamp(0.0, 1.0) * 255.0) as u8)
365 }
366
367 fn to_u8_array_no_alpha(self) -> [u8; 3] {
368 [self.red, self.green, self.blue].map(|v| ops::round(v.clamp(0.0, 1.0) * 255.0) as u8)
369 }
370
371 fn from_u8_array(color: [u8; 4]) -> Self {
372 Self::from_f32_array(color.map(|u| u as f32 / 255.0))
373 }
374
375 fn from_u8_array_no_alpha(color: [u8; 3]) -> Self {
376 Self::from_f32_array_no_alpha(color.map(|u| u as f32 / 255.0))
377 }
378}
379
380impl From<LinearRgba> for Srgba {
381 #[inline]
382 fn from(value: LinearRgba) -> Self {
383 Self {
384 red: Srgba::gamma_function_inverse(value.red),
385 green: Srgba::gamma_function_inverse(value.green),
386 blue: Srgba::gamma_function_inverse(value.blue),
387 alpha: value.alpha,
388 }
389 }
390}
391
392impl From<Srgba> for LinearRgba {
393 #[inline]
394 fn from(value: Srgba) -> Self {
395 Self {
396 red: Srgba::gamma_function(value.red),
397 green: Srgba::gamma_function(value.green),
398 blue: Srgba::gamma_function(value.blue),
399 alpha: value.alpha,
400 }
401 }
402}
403
404impl From<Xyza> for Srgba {
407 fn from(value: Xyza) -> Self {
408 LinearRgba::from(value).into()
409 }
410}
411
412impl From<Srgba> for Xyza {
413 fn from(value: Srgba) -> Self {
414 LinearRgba::from(value).into()
415 }
416}
417
418#[cfg(feature = "wgpu-types")]
419impl From<Srgba> for wgpu_types::Color {
420 fn from(color: Srgba) -> Self {
421 wgpu_types::Color {
422 r: color.red as f64,
423 g: color.green as f64,
424 b: color.blue as f64,
425 a: color.alpha as f64,
426 }
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}