Skip to main content

ling_graphics/
color.rs

1/// Linear RGBA color, all components in [0, 1].
2#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
3pub struct Color {
4    pub r: f32,
5    pub g: f32,
6    pub b: f32,
7    pub a: f32,
8}
9
10impl Color {
11    pub const BLACK:       Self = Self { r: 0.0, g: 0.0, b: 0.0, a: 1.0 };
12    pub const WHITE:       Self = Self { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
13    pub const RED:         Self = Self { r: 1.0, g: 0.0, b: 0.0, a: 1.0 };
14    pub const GREEN:       Self = Self { r: 0.0, g: 1.0, b: 0.0, a: 1.0 };
15    pub const BLUE:        Self = Self { r: 0.0, g: 0.0, b: 1.0, a: 1.0 };
16    pub const YELLOW:      Self = Self { r: 1.0, g: 1.0, b: 0.0, a: 1.0 };
17    pub const CYAN:        Self = Self { r: 0.0, g: 1.0, b: 1.0, a: 1.0 };
18    pub const MAGENTA:     Self = Self { r: 1.0, g: 0.0, b: 1.0, a: 1.0 };
19    pub const TRANSPARENT: Self = Self { r: 0.0, g: 0.0, b: 0.0, a: 0.0 };
20
21    pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self { Self { r, g, b, a } }
22    pub const fn rgb(r: f32, g: f32, b: f32) -> Self { Self { r, g, b, a: 1.0 } }
23
24    pub fn from_srgb(r: u8, g: u8, b: u8) -> Self {
25        Self::rgb(srgb_to_linear(r), srgb_to_linear(g), srgb_to_linear(b))
26    }
27
28    pub fn from_srgba(r: u8, g: u8, b: u8, a: u8) -> Self {
29        Self::new(srgb_to_linear(r), srgb_to_linear(g), srgb_to_linear(b), a as f32 / 255.0)
30    }
31
32    pub fn from_hex(hex: u32) -> Self {
33        let r = ((hex >> 16) & 0xFF) as u8;
34        let g = ((hex >> 8)  & 0xFF) as u8;
35        let b = ( hex        & 0xFF) as u8;
36        Self::from_srgb(r, g, b)
37    }
38
39    pub fn to_rgba_bytes(self) -> [u8; 4] {
40        [
41            (self.r.clamp(0.0, 1.0) * 255.0) as u8,
42            (self.g.clamp(0.0, 1.0) * 255.0) as u8,
43            (self.b.clamp(0.0, 1.0) * 255.0) as u8,
44            (self.a.clamp(0.0, 1.0) * 255.0) as u8,
45        ]
46    }
47
48    pub fn lerp(self, other: Self, t: f32) -> Self {
49        Self::new(
50            self.r + (other.r - self.r) * t,
51            self.g + (other.g - self.g) * t,
52            self.b + (other.b - self.b) * t,
53            self.a + (other.a - self.a) * t,
54        )
55    }
56
57    pub fn premultiply_alpha(self) -> Self {
58        Self::new(self.r * self.a, self.g * self.a, self.b * self.a, self.a)
59    }
60
61    pub fn with_alpha(self, a: f32) -> Self { Self { a, ..self } }
62
63    /// Clamp all components to [0, 1].
64    pub fn clamp(self) -> Self {
65        Self::new(
66            self.r.clamp(0.0, 1.0),
67            self.g.clamp(0.0, 1.0),
68            self.b.clamp(0.0, 1.0),
69            self.a.clamp(0.0, 1.0),
70        )
71    }
72
73    pub fn luminance(self) -> f32 { 0.2126 * self.r + 0.7152 * self.g + 0.0722 * self.b }
74}
75
76impl std::ops::Add for Color {
77    type Output = Self;
78    fn add(self, r: Self) -> Self { Self::new(self.r+r.r, self.g+r.g, self.b+r.b, self.a+r.a) }
79}
80impl std::ops::Mul for Color {
81    type Output = Self;
82    fn mul(self, r: Self) -> Self { Self::new(self.r*r.r, self.g*r.g, self.b*r.b, self.a*r.a) }
83}
84impl std::ops::Mul<f32> for Color {
85    type Output = Self;
86    fn mul(self, s: f32) -> Self { Self::new(self.r*s, self.g*s, self.b*s, self.a*s) }
87}
88
89fn srgb_to_linear(c: u8) -> f32 {
90    let f = c as f32 / 255.0;
91    if f <= 0.04045 { f / 12.92 } else { ((f + 0.055) / 1.055).powf(2.4) }
92}
93
94// ── Blend modes ───────────────────────────────────────────────────────────────
95
96#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
97pub enum BlendMode {
98    /// Porter-Duff "over" compositing.
99    Normal,
100    Additive,
101    Multiply,
102    Screen,
103    Overlay,
104    Subtract,
105}
106
107impl BlendMode {
108    pub fn blend(self, src: Color, dst: Color) -> Color {
109        let sa = src.a;
110        let da = dst.a;
111        match self {
112            BlendMode::Normal => Color::new(
113                src.r * sa + dst.r * (1.0 - sa),
114                src.g * sa + dst.g * (1.0 - sa),
115                src.b * sa + dst.b * (1.0 - sa),
116                sa + da * (1.0 - sa),
117            ),
118            BlendMode::Additive => Color::new(
119                (src.r * sa + dst.r).min(1.0),
120                (src.g * sa + dst.g).min(1.0),
121                (src.b * sa + dst.b).min(1.0),
122                (sa + da).min(1.0),
123            ),
124            BlendMode::Multiply => Color::new(
125                src.r * dst.r,
126                src.g * dst.g,
127                src.b * dst.b,
128                (sa * da).min(1.0),
129            ),
130            BlendMode::Screen => Color::new(
131                1.0 - (1.0 - src.r) * (1.0 - dst.r),
132                1.0 - (1.0 - src.g) * (1.0 - dst.g),
133                1.0 - (1.0 - src.b) * (1.0 - dst.b),
134                (sa + da - sa * da).min(1.0),
135            ),
136            BlendMode::Overlay => {
137                let ov = |s: f32, d: f32| {
138                    if d < 0.5 { 2.0 * s * d } else { 1.0 - 2.0 * (1.0 - s) * (1.0 - d) }
139                };
140                Color::new(ov(src.r, dst.r), ov(src.g, dst.g), ov(src.b, dst.b), (sa + da - sa * da).min(1.0))
141            }
142            BlendMode::Subtract => Color::new(
143                (dst.r - src.r * sa).max(0.0),
144                (dst.g - src.g * sa).max(0.0),
145                (dst.b - src.b * sa).max(0.0),
146                da,
147            ),
148        }
149    }
150}
151
152// ── Color gradient ────────────────────────────────────────────────────────────
153
154#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
155pub struct ColorGradient {
156    stops: Vec<(f32, Color)>,
157}
158
159impl ColorGradient {
160    pub fn new(stops: Vec<(f32, Color)>) -> Self {
161        let mut s = stops;
162        s.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
163        Self { stops: s }
164    }
165
166    pub fn sample(&self, t: f32) -> Color {
167        if self.stops.is_empty() { return Color::TRANSPARENT; }
168        if self.stops.len() == 1 { return self.stops[0].1; }
169        if t <= self.stops[0].0 { return self.stops[0].1; }
170        let last = self.stops.last().unwrap();
171        if t >= last.0 { return last.1; }
172        for i in 0..self.stops.len() - 1 {
173            let (t0, c0) = self.stops[i];
174            let (t1, c1) = self.stops[i + 1];
175            if t >= t0 && t <= t1 {
176                let local = (t - t0) / (t1 - t0);
177                return c0.lerp(c1, local);
178            }
179        }
180        self.stops.last().unwrap().1
181    }
182}