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 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 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#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
97pub enum BlendMode {
98 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#[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}