Skip to main content

flake/draw_state/shapes/
color.rs

1use crate::prelude::{Vector3, Vector4};
2use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
3
4#[repr(C)]
5#[derive(Clone, Copy, Debug, bytemuck::Zeroable, bytemuck::Pod, PartialEq)]
6pub struct Color {
7    pub r: f32,
8    pub g: f32,
9    pub b: f32,
10    pub a: f32,
11}
12
13impl Default for Color {
14    fn default() -> Self {
15        BLACK
16    }
17}
18
19#[allow(unused)]
20impl Color {
21    /// Create a color using r, g, b, a. All values go from 0-1
22    pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
23        Self { r, g, b, a }
24    }
25
26    /// Get the same color a different transparency/alpha.
27    pub const fn with_alpha(&self, alpha: f32) -> Self {
28        Self::new(self.r, self.g, self.b, alpha)
29    }
30
31    /// Create a color where RGA is a single number.
32    pub const fn splat_alpha(v: f32, alpha: f32) -> Self {
33        Self::new(v, v, v, alpha)
34    }
35
36    /// Create a color where RGA is a single number. (Alpha = 1)
37    pub const fn splat(v: f32) -> Self {
38        Self::splat_alpha(v, 1.0)
39    }
40
41    /// Linearly interpolate to another color using 0-1.
42    pub fn lerp(self, other: Color, t: f32) -> Self {
43        self + (other - self) * t
44    }
45
46    // Clamps the color from 0-1
47    pub const fn clamped(&self) -> Self {
48        Self::new(
49            self.r.clamp(0.0, 1.0),
50            self.g.clamp(0.0, 1.0),
51            self.b.clamp(0.0, 1.0),
52            self.a.clamp(0.0, 1.0),
53        )
54    }
55
56    /// Create a color using RGBA. Each value goes from 0-255.
57    pub const fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
58        Self::new(
59            r as f32 / 255.0,
60            g as f32 / 255.0,
61            b as f32 / 255.0,
62            a as f32 / 255.0,
63        )
64    }
65
66    /// Create a color using RGB. Each value goes from 0-255. (Alpha = 1)
67    pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Self {
68        Self::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0)
69    }
70
71    /// Create a color using hue-saturation-value-alpha. All values go from 0-1.
72    pub fn from_hsv(h: f32, s: f32, v: f32) -> Self {
73        Self::from_hsva(h, s, v, 1.0)
74    }
75
76    /// Create a color using hue-saturation-value-alpha. All values go from 0-1.
77    pub fn from_hsva(mut h: f32, s: f32, v: f32, a: f32) -> Self {
78        h *= 360.0;
79        let h = h.rem_euclid(360.0);
80        let c = v * s;
81        let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
82        let m = v - c;
83
84        let (r, g, b) = match h / 60.0 {
85            h_prime if h_prime < 1.0 => (c, x, 0.0),
86            h_prime if h_prime < 2.0 => (x, c, 0.0),
87            h_prime if h_prime < 3.0 => (0.0, c, x),
88            h_prime if h_prime < 4.0 => (0.0, x, c),
89            h_prime if h_prime < 5.0 => (x, 0.0, c),
90            _ => (c, 0.0, x),
91        };
92
93        Self::new(r + m, g + m, b + m, a)
94    }
95
96    /// Returns (h, s, v) all values from 0-1.
97    pub fn to_hsv(&self) -> (f32, f32, f32) {
98        let max = self.r.max(self.g).max(self.b);
99        let min = self.r.min(self.g).min(self.b);
100        let delta = max - min;
101
102        let h = if delta == 0.0 {
103            0.0
104        } else if max == self.r {
105            60.0 * (((self.g - self.b) / delta).rem_euclid(6.0))
106        } else if max == self.g {
107            60.0 * (((self.b - self.r) / delta) + 2.0)
108        } else {
109            60.0 * (((self.r - self.g) / delta) + 4.0)
110        };
111
112        let s = if max == 0.0 { 0.0 } else { delta / max };
113        let v = max;
114
115        (h / 360.0, s, v)
116    }
117
118    /// Returns (h, s, v, a) all values from 0-1.
119    pub fn to_hsva(&self) -> (f32, f32, f32, f32) {
120        let (h, s, v) = self.to_hsv();
121        (h, s, v, self.a)
122    }
123
124    /// Accepts "RRGGBB", "RGB", "RRGGBBAA", "RGBA", with or without leading '#'
125    pub fn from_hex(hex: &str) -> Option<Self> {
126        let hex = hex.trim_start_matches('#');
127
128        let expand = |c: char| -> Option<u8> {
129            let d = c.to_digit(16)? as u8;
130            Some((d << 4) | d)
131        };
132
133        match hex.len() {
134            3 => {
135                let mut chars = hex.chars();
136                let r = expand(chars.next()?)?;
137                let g = expand(chars.next()?)?;
138                let b = expand(chars.next()?)?;
139                Some(Self::from_rgb8(r, g, b))
140            }
141            4 => {
142                let mut chars = hex.chars();
143                let r = expand(chars.next()?)?;
144                let g = expand(chars.next()?)?;
145                let b = expand(chars.next()?)?;
146                let a = expand(chars.next()?)?;
147                Some(Self::from_rgba8(r, g, b, a))
148            }
149            6 => {
150                let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
151                let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
152                let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
153                Some(Self::from_rgb8(r, g, b))
154            }
155            8 => {
156                let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
157                let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
158                let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
159                let a = u8::from_str_radix(&hex[6..8], 16).ok()?;
160                Some(Self::from_rgba8(r, g, b, a))
161            }
162            _ => None,
163        }
164    }
165
166    /// Returns "#RRGGBB"
167    pub fn to_hex(&self) -> String {
168        format!(
169            "#{:02X}{:02X}{:02X}",
170            (self.r.clamp(0.0, 1.0) * 255.0).round() as u8,
171            (self.g.clamp(0.0, 1.0) * 255.0).round() as u8,
172            (self.b.clamp(0.0, 1.0) * 255.0).round() as u8,
173        )
174    }
175
176    /// Returns "#RRGGBBAA"
177    pub fn to_hex_alpha(&self) -> String {
178        format!(
179            "#{:02X}{:02X}{:02X}{:02X}",
180            (self.r.clamp(0.0, 1.0) * 255.0).round() as u8,
181            (self.g.clamp(0.0, 1.0) * 255.0).round() as u8,
182            (self.b.clamp(0.0, 1.0) * 255.0).round() as u8,
183            (self.a.clamp(0.0, 1.0) * 255.0).round() as u8,
184        )
185    }
186
187    /// Returns a tuple of (r, g, b) from 0-255.
188    pub const fn to_rgb(&self) -> (u8, u8, u8) {
189        (
190            (self.r.clamp(0.0, 1.0) * 255.0).round() as u8,
191            (self.g.clamp(0.0, 1.0) * 255.0).round() as u8,
192            (self.b.clamp(0.0, 1.0) * 255.0).round() as u8,
193        )
194    }
195
196    /// Returns a tuple of `(r, g, b, a)` from 0-255.
197    pub const fn to_rgba(&self) -> (u8, u8, u8, u8) {
198        (
199            (self.r.clamp(0.0, 1.0) * 255.0).round() as u8,
200            (self.g.clamp(0.0, 1.0) * 255.0).round() as u8,
201            (self.b.clamp(0.0, 1.0) * 255.0).round() as u8,
202            (self.a.clamp(0.0, 1.0) * 255.0).round() as u8,
203        )
204    }
205
206    /// Create a color from a `Vector4<f32>` where `x -> r`, `y -> g`, `z -> b`, `w -> a`
207    pub const fn from_vec4(vec: Vector4) -> Self {
208        Self::new(vec.x, vec.y, vec.z, vec.w)
209    }
210
211    /// Create a `Vector4<f32>` from a color where `r -> x`, `g -> y`, `b -> z`, `a -> w`
212    pub const fn to_vec4(&self) -> Vector4 {
213        Vector4::new(self.r, self.g, self.b, self.a)
214    }
215
216    /// Create a color from a `Vector3<f32>` where `x -> r`, `y -> g`, `z -> b`
217    pub const fn from_vec3(vec: Vector3) -> Self {
218        Self::new(vec.x, vec.y, vec.z, 1.0)
219    }
220
221    /// Create a `Vector3<f32>` from a color where `r -> x`, `g -> y`, `b -> z`
222    pub const fn to_vec3(&self) -> Vector3 {
223        Vector3::new(self.r, self.g, self.b)
224    }
225
226    /// Convert color to an array of [r, g, b, a]
227    pub const fn to_array(&self) -> [f32; 4] {
228        [self.r, self.g, self.b, self.a]
229    }
230
231    /// Convert color to a `wgpu::Color`
232    pub const fn to_wgpu(&self) -> wgpu::Color {
233        wgpu::Color {
234            r: self.r as f64,
235            g: self.g as f64,
236            b: self.b as f64,
237            a: self.a as f64,
238        }
239    }
240}
241
242impl From<Color> for [f32; 4] {
243    fn from(c: Color) -> Self {
244        c.to_array()
245    }
246}
247
248impl From<[f32; 4]> for Color {
249    fn from(arr: [f32; 4]) -> Self {
250        Self::new(arr[0], arr[1], arr[2], arr[3])
251    }
252}
253
254impl Add<Color> for Color {
255    type Output = Self;
256
257    fn add(self, rhs: Color) -> Self::Output {
258        Color::new(
259            self.r + rhs.r,
260            self.g + rhs.g,
261            self.b + rhs.b,
262            self.a + rhs.a,
263        )
264    }
265}
266
267impl Sub<Color> for Color {
268    type Output = Self;
269
270    fn sub(self, rhs: Color) -> Self::Output {
271        Color::new(
272            self.r - rhs.r,
273            self.g - rhs.g,
274            self.b - rhs.b,
275            self.a - rhs.a,
276        )
277    }
278}
279
280impl Mul<Color> for Color {
281    type Output = Self;
282
283    fn mul(self, rhs: Color) -> Self::Output {
284        Color::new(
285            self.r * rhs.r,
286            self.g * rhs.g,
287            self.b * rhs.b,
288            self.a * rhs.a,
289        )
290    }
291}
292
293impl Div<Color> for Color {
294    type Output = Self;
295
296    fn div(self, rhs: Color) -> Self::Output {
297        Color::new(
298            self.r / rhs.r,
299            self.g / rhs.g,
300            self.b / rhs.b,
301            self.a / rhs.a,
302        )
303    }
304}
305
306impl AddAssign<Color> for Color {
307    fn add_assign(&mut self, rhs: Color) {
308        self.r += rhs.r;
309        self.g += rhs.g;
310        self.b += rhs.b;
311        self.a += rhs.a;
312    }
313}
314
315impl SubAssign<Color> for Color {
316    fn sub_assign(&mut self, rhs: Color) {
317        self.r -= rhs.r;
318        self.g -= rhs.g;
319        self.b -= rhs.b;
320        self.a -= rhs.a;
321    }
322}
323
324impl MulAssign<Color> for Color {
325    fn mul_assign(&mut self, rhs: Color) {
326        self.r *= rhs.r;
327        self.g *= rhs.g;
328        self.b *= rhs.b;
329        self.a *= rhs.a;
330    }
331}
332
333impl DivAssign<Color> for Color {
334    fn div_assign(&mut self, rhs: Color) {
335        self.r /= rhs.r;
336        self.g /= rhs.g;
337        self.b /= rhs.b;
338        self.a /= rhs.a;
339    }
340}
341
342// Scalar variants
343impl Mul<f32> for Color {
344    type Output = Self;
345
346    fn mul(self, rhs: f32) -> Self::Output {
347        Color::new(self.r * rhs, self.g * rhs, self.b * rhs, self.a * rhs)
348    }
349}
350
351impl Div<f32> for Color {
352    type Output = Self;
353
354    fn div(self, rhs: f32) -> Self::Output {
355        Color::new(self.r / rhs, self.g / rhs, self.b / rhs, self.a / rhs)
356    }
357}
358
359impl MulAssign<f32> for Color {
360    fn mul_assign(&mut self, rhs: f32) {
361        self.r *= rhs;
362        self.g *= rhs;
363        self.b *= rhs;
364        self.a *= rhs;
365    }
366}
367
368impl DivAssign<f32> for Color {
369    fn div_assign(&mut self, rhs: f32) {
370        self.r /= rhs;
371        self.g /= rhs;
372        self.b /= rhs;
373        self.a /= rhs;
374    }
375}
376
377macro_rules! define_colors {
378    ($($name:ident = ($r:expr, $g:expr, $b:expr, $a:expr)),* $(,)?) => {
379        impl Color {
380            $(pub const $name: Self = Self::new($r, $g, $b, $a);)*
381        }
382        $(pub const $name: Color = Color::$name;)*
383    };
384}
385
386define_colors! {
387    TRANSPARENT = (0.0, 0.0, 0.0, 0.0),
388    WHITE       = (1.0, 1.0, 1.0, 1.0),
389    BLACK       = (0.0, 0.0, 0.0, 1.0),
390    LIGHT_BLACK = (0.05, 0.05, 0.05, 1.0),
391
392    GRAY        = (0.2, 0.2, 0.2, 1.0),
393    DARK_GRAY   = (0.1, 0.1, 0.1, 1.0),
394    LIGHT_GRAY  = (0.3, 0.3, 0.3, 1.0),
395
396    RED         = (1.0, 0.03, 0.04, 1.0),
397    DARK_RED    = (0.7, 0.03, 0.04, 1.0),
398    LIGHT_RED   = (1.0, 0.2, 0.2, 1.0),
399
400    GREEN       = (0.0, 1.0, 0.0, 1.0),
401    DARK_GREEN  = (0.0, 0.3, 0.0, 1.0),
402    LIGHT_GREEN = (0.4, 1.0, 0.3, 1.0),
403
404    BLUE        = (0.03, 0.04, 1.0, 1.0),
405    DARK_BLUE   = (0.02, 0.03, 0.7, 1.0),
406    LIGHT_BLUE  = (0.2, 0.4, 1.0, 1.0),
407
408    YELLOW      = (1.0, 1.0, 0.03, 1.0),
409    DARK_YELLOW = (0.7, 0.7, 0.02, 1.0),
410    LIGHT_YELLOW= (1.0, 1.0, 0.2, 1.0),
411
412    MAGENTA       = (1.0, 0.03, 1.0, 1.0),
413    DARK_MAGENTA  = (0.7, 0.02, 0.7, 1.0),
414    LIGHT_MAGENTA = (1.0, 0.2, 1.0, 1.0),
415
416    PURPLE       = (0.3, 0.03, 1.0, 1.0),
417    DARK_PURPLE  = (0.2, 0.02, 0.7, 1.0),
418    LIGHT_PURPLE = (0.45, 0.2, 1.0, 1.0),
419
420    CYAN        = (0.03, 1.0, 1.0, 1.0),
421    DARK_CYAN   = (0.02, 0.7, 0.7, 1.0),
422    LIGHT_CYAN  = (0.5, 1.0, 1.0, 1.0),
423
424    ORANGE      = (1.0, 0.3, 0.03, 1.0),
425    DARK_ORANGE = (0.7, 0.2, 0.02, 1.0),
426    LIGHT_ORANGE= (1.0, 0.45, 0.2, 1.0),
427}