wde_renderer/utils/
color.rs

1//! Lightweight color helpers used by render assets and materials.
2//!
3//! The enum wraps either linear or sRGB encoded values and provides cheap conversions so render code can stay explicit about color space. All values are expected to be in the 0.0–1.0 range and are left un-clamped so callers can decide how to handle HDR or out-of-gamut data.
4
5use bevy::reflect::Reflect;
6use wde_wgpu::passes::WgpuColor;
7
8#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
9pub enum Color {
10    /// Linear RGBA color expressed in normalized `[0.0, 1.0]` space.
11    LinearRgba(f32, f32, f32, f32),
12    /// sRGB RGBA color expressed in normalized `[0.0, 1.0]` space.
13    Srgba(f32, f32, f32, f32)
14}
15impl Color {
16    // Default colors
17    pub const BLACK: Color = Color::LinearRgba(0.0, 0.0, 0.0, 1.0);
18    pub const WHITE: Color = Color::LinearRgba(1.0, 1.0, 1.0, 1.0);
19    pub const RED: Color = Color::LinearRgba(1.0, 0.0, 0.0, 1.0);
20    pub const GREEN: Color = Color::LinearRgba(0.0, 1.0, 0.0, 1.0);
21    pub const BLUE: Color = Color::LinearRgba(0.0, 0.0, 1.0, 1.0);
22    pub const TRANSPARENT: Color = Color::LinearRgba(0.0, 0.0, 0.0, 0.0);
23
24    /// Create a linear RGBA color.
25    pub fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
26        Color::LinearRgba(r, g, b, a)
27    }
28    /// Create an sRGB RGBA color.
29    pub fn from_srgba(r: f32, g: f32, b: f32, a: f32) -> Self {
30        Color::Srgba(r, g, b, a)
31    }
32
33    /// Convert the color to linear RGBA (gamma ≈ 2.2).
34    pub fn to_linear_rgba(&self) -> Self {
35        match self {
36            Color::LinearRgba(_, _, _, _) => *self,
37            Color::Srgba(r, g, b, a) => {
38                let r = r.powf(2.2);
39                let g = g.powf(2.2);
40                let b = b.powf(2.2);
41                Color::LinearRgba(r, g, b, *a)
42            }
43        }
44    }
45    /// Convert the color to sRGB RGBA (inverse gamma ≈ 2.2).
46    pub fn to_srgba(&self) -> Self {
47        match self {
48            Color::Srgba(_, _, _, _) => *self,
49            Color::LinearRgba(r, g, b, a) => {
50                let r = r.powf(1.0 / 2.2);
51                let g = g.powf(1.0 / 2.2);
52                let b = b.powf(1.0 / 2.2);
53                Color::Srgba(r, g, b, *a)
54            }
55        }
56    }
57
58    /// Get the red component without converting color space.
59    pub fn r(&self) -> f32 {
60        match self {
61            Color::LinearRgba(r, _, _, _) => *r,
62            Color::Srgba(r, _, _, _) => *r
63        }
64    }
65    /// Get the green component without converting color space.
66    pub fn g(&self) -> f32 {
67        match self {
68            Color::LinearRgba(_, g, _, _) => *g,
69            Color::Srgba(_, g, _, _) => *g
70        }
71    }
72    /// Get the blue component without converting color space.
73    pub fn b(&self) -> f32 {
74        match self {
75            Color::LinearRgba(_, _, b, _) => *b,
76            Color::Srgba(_, _, b, _) => *b
77        }
78    }
79    /// Get the alpha component without converting color space.
80    pub fn a(&self) -> f32 {
81        match self {
82            Color::LinearRgba(_, _, _, a) => *a,
83            Color::Srgba(_, _, _, a) => *a
84        }
85    }
86}
87impl From<Color> for WgpuColor {
88    fn from(value: Color) -> Self {
89        match value {
90            Color::LinearRgba(r, g, b, a) => WgpuColor {
91                r: r.into(),
92                g: g.into(),
93                b: b.into(),
94                a: a.into()
95            },
96            Color::Srgba(r, g, b, a) => {
97                let r = r.powf(2.2);
98                let g = g.powf(2.2);
99                let b = b.powf(2.2);
100                WgpuColor {
101                    r: r.into(),
102                    g: g.into(),
103                    b: b.into(),
104                    a: a.into()
105                }
106            }
107        }
108    }
109}