1#[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 BLUE: Self = Self { r: 0.0, g: 0.0, b: 1.0, a: 1.0 };
13 pub const CYAN: Self = Self { r: 0.0, g: 1.0, b: 1.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 MAGENTA: Self = Self { r: 1.0, g: 0.0, b: 1.0, a: 1.0 };
16 pub const RED: Self = Self { r: 1.0, g: 0.0, b: 0.0, a: 1.0 };
17 pub const TRANSPARENT: Self = Self { r: 0.0, g: 0.0, b: 0.0, a: 0.0 };
18 pub const WHITE: Self = Self { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
19 pub const YELLOW: Self = Self { r: 1.0, g: 1.0, b: 0.0, a: 1.0 };
20
21 pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
22 Self { r, g, b, a }
23 }
24
25 pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
26 Self { r, g, b, a: 1.0 }
27 }
28
29 pub fn from_srgb(r: u8, g: u8, b: u8) -> Self {
30 Self::rgb(srgb_to_linear(r), srgb_to_linear(g), srgb_to_linear(b))
31 }
32
33 pub fn from_srgba(r: u8, g: u8, b: u8, a: u8) -> Self {
34 Self::new(
35 srgb_to_linear(r),
36 srgb_to_linear(g),
37 srgb_to_linear(b),
38 a as f32 / 255.0,
39 )
40 }
41
42 pub fn from_hex(hex: u32) -> Self {
43 let r = ((hex >> 16) & 0xFF) as u8;
44 let g = ((hex >> 8) & 0xFF) as u8;
45 let b = (hex & 0xFF) as u8;
46 Self::from_srgb(r, g, b)
47 }
48
49 pub fn to_rgba_bytes(self) -> [u8; 4] {
50 [
51 (self.r.clamp(0.0, 1.0) * 255.0) as u8,
52 (self.g.clamp(0.0, 1.0) * 255.0) as u8,
53 (self.b.clamp(0.0, 1.0) * 255.0) as u8,
54 (self.a.clamp(0.0, 1.0) * 255.0) as u8,
55 ]
56 }
57
58 pub fn lerp(self, other: Self, t: f32) -> Self {
59 Self::new(
60 self.r + (other.r - self.r) * t,
61 self.g + (other.g - self.g) * t,
62 self.b + (other.b - self.b) * t,
63 self.a + (other.a - self.a) * t,
64 )
65 }
66
67 pub fn premultiply_alpha(self) -> Self {
68 Self::new(self.r * self.a, self.g * self.a, self.b * self.a, self.a)
69 }
70
71 pub fn with_alpha(self, a: f32) -> Self {
72 Self { a, ..self }
73 }
74
75 pub fn clamp(self) -> Self {
77 Self::new(
78 self.r.clamp(0.0, 1.0),
79 self.g.clamp(0.0, 1.0),
80 self.b.clamp(0.0, 1.0),
81 self.a.clamp(0.0, 1.0),
82 )
83 }
84
85 pub fn luminance(self) -> f32 {
86 0.2126 * self.r + 0.7152 * self.g + 0.0722 * self.b
87 }
88}
89
90impl std::ops::Add for Color {
91 type Output = Self;
92
93 fn add(self, r: Self) -> Self {
94 Self::new(self.r + r.r, self.g + r.g, self.b + r.b, self.a + r.a)
95 }
96}
97impl std::ops::Mul for Color {
98 type Output = Self;
99
100 fn mul(self, r: Self) -> Self {
101 Self::new(self.r * r.r, self.g * r.g, self.b * r.b, self.a * r.a)
102 }
103}
104impl std::ops::Mul<f32> for Color {
105 type Output = Self;
106
107 fn mul(self, s: f32) -> Self {
108 Self::new(self.r * s, self.g * s, self.b * s, self.a * s)
109 }
110}
111
112fn srgb_to_linear(c: u8) -> f32 {
113 let f = c as f32 / 255.0;
114 if f <= 0.04045 {
115 f / 12.92
116 } else {
117 ((f + 0.055) / 1.055).powf(2.4)
118 }
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
124pub enum BlendMode {
125 Normal,
127 Additive,
128 Multiply,
129 Screen,
130 Overlay,
131 Subtract,
132}
133
134impl BlendMode {
135 pub fn blend(self, src: Color, dst: Color) -> Color {
136 let sa = src.a;
137 let da = dst.a;
138 match self {
139 BlendMode::Normal => Color::new(
140 src.r * sa + dst.r * (1.0 - sa),
141 src.g * sa + dst.g * (1.0 - sa),
142 src.b * sa + dst.b * (1.0 - sa),
143 sa + da * (1.0 - sa),
144 ),
145 BlendMode::Additive => Color::new(
146 (src.r * sa + dst.r).min(1.0),
147 (src.g * sa + dst.g).min(1.0),
148 (src.b * sa + dst.b).min(1.0),
149 (sa + da).min(1.0),
150 ),
151 BlendMode::Multiply => Color::new(
152 src.r * dst.r,
153 src.g * dst.g,
154 src.b * dst.b,
155 (sa * da).min(1.0),
156 ),
157 BlendMode::Screen => Color::new(
158 1.0 - (1.0 - src.r) * (1.0 - dst.r),
159 1.0 - (1.0 - src.g) * (1.0 - dst.g),
160 1.0 - (1.0 - src.b) * (1.0 - dst.b),
161 (sa + da - sa * da).min(1.0),
162 ),
163 BlendMode::Overlay => {
164 let ov = |s: f32, d: f32| {
165 if d < 0.5 {
166 2.0 * s * d
167 } else {
168 1.0 - 2.0 * (1.0 - s) * (1.0 - d)
169 }
170 };
171 Color::new(
172 ov(src.r, dst.r),
173 ov(src.g, dst.g),
174 ov(src.b, dst.b),
175 (sa + da - sa * da).min(1.0),
176 )
177 },
178 BlendMode::Subtract => Color::new(
179 (dst.r - src.r * sa).max(0.0),
180 (dst.g - src.g * sa).max(0.0),
181 (dst.b - src.b * sa).max(0.0),
182 da,
183 ),
184 }
185 }
186}
187
188#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
191pub struct ColorGradient {
192 stops: Vec<(f32, Color)>,
193}
194
195impl ColorGradient {
196 pub fn new(stops: Vec<(f32, Color)>) -> Self {
197 let mut s = stops;
198 s.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
199 Self { stops: s }
200 }
201
202 pub fn sample(&self, t: f32) -> Color {
203 if self.stops.is_empty() {
204 return Color::TRANSPARENT;
205 }
206 if self.stops.len() == 1 {
207 return self.stops[0].1;
208 }
209 if t <= self.stops[0].0 {
210 return self.stops[0].1;
211 }
212 let last = self.stops.last().unwrap();
213 if t >= last.0 {
214 return last.1;
215 }
216 for i in 0..self.stops.len() - 1 {
217 let (t0, c0) = self.stops[i];
218 let (t1, c1) = self.stops[i + 1];
219 if t >= t0 && t <= t1 {
220 let local = (t - t0) / (t1 - t0);
221 return c0.lerp(c1, local);
222 }
223 }
224 self.stops.last().unwrap().1
225 }
226}