1use crate::{
2 color_difference::EuclideanDistance, impl_componentwise_vector_space, Alpha, ColorToComponents,
3 ColorToPacked, Gray, LinearRgba, Luminance, Mix, StandardColor, Xyza,
4};
5use bevy_math::{ops, Vec3, Vec4};
6#[cfg(feature = "bevy_reflect")]
7use bevy_reflect::prelude::*;
8use derive_more::derive::{Display, Error, From};
9
10#[doc = include_str!("../docs/conversion.md")]
12#[doc = include_str!("../docs/diagrams/model_graph.svg")]
14#[derive(Debug, Clone, Copy, PartialEq)]
16#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
17#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
18#[cfg_attr(
19 all(feature = "serialize", feature = "bevy_reflect"),
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 {
188 Self::from_u8_array_no_alpha([r, g, b])
189 }
190
191 pub fn rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self {
204 Self::from_u8_array([r, g, b, a])
205 }
206
207 pub fn gamma_function(value: f32) -> f32 {
209 if value <= 0.0 {
210 return value;
211 }
212 if value <= 0.04045 {
213 value / 12.92 } else {
215 ops::powf((value + 0.055) / 1.055, 2.4) }
217 }
218
219 pub fn gamma_function_inverse(value: f32) -> f32 {
221 if value <= 0.0 {
222 return value;
223 }
224
225 if value <= 0.0031308 {
226 value * 12.92 } else {
228 (1.055 * ops::powf(value, 1.0 / 2.4)) - 0.055 }
230 }
231}
232
233impl Default for Srgba {
234 fn default() -> Self {
235 Self::WHITE
236 }
237}
238
239impl Luminance for Srgba {
240 #[inline]
241 fn luminance(&self) -> f32 {
242 let linear: LinearRgba = (*self).into();
243 linear.luminance()
244 }
245
246 #[inline]
247 fn with_luminance(&self, luminance: f32) -> Self {
248 let linear: LinearRgba = (*self).into();
249 linear
250 .with_luminance(Srgba::gamma_function(luminance))
251 .into()
252 }
253
254 #[inline]
255 fn darker(&self, amount: f32) -> Self {
256 let linear: LinearRgba = (*self).into();
257 linear.darker(amount).into()
258 }
259
260 #[inline]
261 fn lighter(&self, amount: f32) -> Self {
262 let linear: LinearRgba = (*self).into();
263 linear.lighter(amount).into()
264 }
265}
266
267impl Mix for Srgba {
268 #[inline]
269 fn mix(&self, other: &Self, factor: f32) -> Self {
270 let n_factor = 1.0 - factor;
271 Self {
272 red: self.red * n_factor + other.red * factor,
273 green: self.green * n_factor + other.green * factor,
274 blue: self.blue * n_factor + other.blue * factor,
275 alpha: self.alpha * n_factor + other.alpha * factor,
276 }
277 }
278}
279
280impl Alpha for Srgba {
281 #[inline]
282 fn with_alpha(&self, alpha: f32) -> Self {
283 Self { alpha, ..*self }
284 }
285
286 #[inline]
287 fn alpha(&self) -> f32 {
288 self.alpha
289 }
290
291 #[inline]
292 fn set_alpha(&mut self, alpha: f32) {
293 self.alpha = alpha;
294 }
295}
296
297impl EuclideanDistance for Srgba {
298 #[inline]
299 fn distance_squared(&self, other: &Self) -> f32 {
300 let dr = self.red - other.red;
301 let dg = self.green - other.green;
302 let db = self.blue - other.blue;
303 dr * dr + dg * dg + db * db
304 }
305}
306
307impl Gray for Srgba {
308 const BLACK: Self = Self::BLACK;
309 const WHITE: Self = Self::WHITE;
310}
311
312impl ColorToComponents for Srgba {
313 fn to_f32_array(self) -> [f32; 4] {
314 [self.red, self.green, self.blue, self.alpha]
315 }
316
317 fn to_f32_array_no_alpha(self) -> [f32; 3] {
318 [self.red, self.green, self.blue]
319 }
320
321 fn to_vec4(self) -> Vec4 {
322 Vec4::new(self.red, self.green, self.blue, self.alpha)
323 }
324
325 fn to_vec3(self) -> Vec3 {
326 Vec3::new(self.red, self.green, self.blue)
327 }
328
329 fn from_f32_array(color: [f32; 4]) -> Self {
330 Self {
331 red: color[0],
332 green: color[1],
333 blue: color[2],
334 alpha: color[3],
335 }
336 }
337
338 fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
339 Self {
340 red: color[0],
341 green: color[1],
342 blue: color[2],
343 alpha: 1.0,
344 }
345 }
346
347 fn from_vec4(color: Vec4) -> Self {
348 Self {
349 red: color[0],
350 green: color[1],
351 blue: color[2],
352 alpha: color[3],
353 }
354 }
355
356 fn from_vec3(color: Vec3) -> Self {
357 Self {
358 red: color[0],
359 green: color[1],
360 blue: color[2],
361 alpha: 1.0,
362 }
363 }
364}
365
366impl ColorToPacked for Srgba {
367 fn to_u8_array(self) -> [u8; 4] {
368 [self.red, self.green, self.blue, self.alpha]
369 .map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
370 }
371
372 fn to_u8_array_no_alpha(self) -> [u8; 3] {
373 [self.red, self.green, self.blue].map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
374 }
375
376 fn from_u8_array(color: [u8; 4]) -> Self {
377 Self::from_f32_array(color.map(|u| u as f32 / 255.0))
378 }
379
380 fn from_u8_array_no_alpha(color: [u8; 3]) -> Self {
381 Self::from_f32_array_no_alpha(color.map(|u| u as f32 / 255.0))
382 }
383}
384
385impl From<LinearRgba> for Srgba {
386 #[inline]
387 fn from(value: LinearRgba) -> Self {
388 Self {
389 red: Srgba::gamma_function_inverse(value.red),
390 green: Srgba::gamma_function_inverse(value.green),
391 blue: Srgba::gamma_function_inverse(value.blue),
392 alpha: value.alpha,
393 }
394 }
395}
396
397impl From<Srgba> for LinearRgba {
398 #[inline]
399 fn from(value: Srgba) -> Self {
400 Self {
401 red: Srgba::gamma_function(value.red),
402 green: Srgba::gamma_function(value.green),
403 blue: Srgba::gamma_function(value.blue),
404 alpha: value.alpha,
405 }
406 }
407}
408
409impl From<Xyza> for Srgba {
412 fn from(value: Xyza) -> Self {
413 LinearRgba::from(value).into()
414 }
415}
416
417impl From<Srgba> for Xyza {
418 fn from(value: Srgba) -> Self {
419 LinearRgba::from(value).into()
420 }
421}
422
423#[derive(Debug, Error, Display, PartialEq, Eq, From)]
425pub enum HexColorError {
426 #[display("Invalid hex string")]
428 Parse(core::num::ParseIntError),
429 #[display("Unexpected length of hex string")]
431 Length,
432 #[display("Invalid hex char")]
434 #[error(ignore)]
435 Char(char),
436}
437
438#[cfg(test)]
439mod tests {
440 use crate::testing::assert_approx_eq;
441
442 use super::*;
443
444 #[test]
445 fn test_to_from_linear() {
446 let srgba = Srgba::new(0.0, 0.5, 1.0, 1.0);
447 let linear_rgba: LinearRgba = srgba.into();
448 assert_eq!(linear_rgba.red, 0.0);
449 assert_approx_eq!(linear_rgba.green, 0.2140, 0.0001);
450 assert_approx_eq!(linear_rgba.blue, 1.0, 0.0001);
451 assert_eq!(linear_rgba.alpha, 1.0);
452 let srgba2: Srgba = linear_rgba.into();
453 assert_eq!(srgba2.red, 0.0);
454 assert_approx_eq!(srgba2.green, 0.5, 0.0001);
455 assert_approx_eq!(srgba2.blue, 1.0, 0.0001);
456 assert_eq!(srgba2.alpha, 1.0);
457 }
458
459 #[test]
460 fn euclidean_distance() {
461 let a = Srgba::new(0.0, 0.0, 0.0, 1.0);
463 let b = Srgba::new(1.0, 1.0, 1.0, 1.0);
464 assert_eq!(a.distance_squared(&b), 3.0);
465
466 let a = Srgba::new(0.0, 0.0, 0.0, 1.0);
468 let b = Srgba::new(1.0, 1.0, 1.0, 0.0);
469 assert_eq!(a.distance_squared(&b), 3.0);
470
471 let a = Srgba::new(0.0, 0.0, 0.0, 1.0);
473 let b = Srgba::new(1.0, 0.0, 0.0, 1.0);
474 assert_eq!(a.distance_squared(&b), 1.0);
475 }
476
477 #[test]
478 fn darker_lighter() {
479 let color = Srgba::new(0.4, 0.5, 0.6, 1.0);
481 let darker1 = color.darker(0.1);
482 let darker2 = darker1.darker(0.1);
483 let twice_as_dark = color.darker(0.2);
484 assert!(darker2.distance_squared(&twice_as_dark) < 0.0001);
485
486 let lighter1 = color.lighter(0.1);
487 let lighter2 = lighter1.lighter(0.1);
488 let twice_as_light = color.lighter(0.2);
489 assert!(lighter2.distance_squared(&twice_as_light) < 0.0001);
490 }
491
492 #[test]
493 fn hex_color() {
494 assert_eq!(Srgba::hex("FFF"), Ok(Srgba::WHITE));
495 assert_eq!(Srgba::hex("FFFF"), Ok(Srgba::WHITE));
496 assert_eq!(Srgba::hex("FFFFFF"), Ok(Srgba::WHITE));
497 assert_eq!(Srgba::hex("FFFFFFFF"), Ok(Srgba::WHITE));
498 assert_eq!(Srgba::hex("000"), Ok(Srgba::BLACK));
499 assert_eq!(Srgba::hex("000F"), Ok(Srgba::BLACK));
500 assert_eq!(Srgba::hex("000000"), Ok(Srgba::BLACK));
501 assert_eq!(Srgba::hex("000000FF"), Ok(Srgba::BLACK));
502 assert_eq!(Srgba::hex("03a9f4"), Ok(Srgba::rgb_u8(3, 169, 244)));
503 assert_eq!(Srgba::hex("yy"), Err(HexColorError::Length));
504 assert_eq!(Srgba::hex("#f2a"), Ok(Srgba::rgb_u8(255, 34, 170)));
505 assert_eq!(Srgba::hex("#e23030"), Ok(Srgba::rgb_u8(226, 48, 48)));
506 assert_eq!(Srgba::hex("#ff"), Err(HexColorError::Length));
507 assert_eq!(Srgba::hex("11223344"), Ok(Srgba::rgba_u8(17, 34, 51, 68)));
508 assert_eq!(Srgba::hex("1234"), Ok(Srgba::rgba_u8(17, 34, 51, 68)));
509 assert_eq!(Srgba::hex("12345678"), Ok(Srgba::rgba_u8(18, 52, 86, 120)));
510 assert_eq!(Srgba::hex("4321"), Ok(Srgba::rgba_u8(68, 51, 34, 17)));
511
512 assert!(matches!(Srgba::hex("yyy"), Err(HexColorError::Parse(_))));
513 assert!(matches!(Srgba::hex("##fff"), Err(HexColorError::Parse(_))));
514 }
515}