ori_graphics/
color.rs

1use std::{
2    fmt::Display,
3    ops::{Add, AddAssign, Mul},
4};
5
6use bytemuck::{Pod, Zeroable};
7
8#[repr(C)]
9#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
10pub struct Color {
11    pub r: f32,
12    pub g: f32,
13    pub b: f32,
14    pub a: f32,
15}
16
17impl Color {
18    pub const TRANSPARENT: Self = Self::rgba(0.0, 0.0, 0.0, 0.0);
19    pub const BLACK: Self = Self::rgb(0.0, 0.0, 0.0);
20    pub const WHITE: Self = Self::rgb(1.0, 1.0, 1.0);
21
22    pub const RED: Self = Self::rgb(1.0, 0.0, 0.0);
23    pub const GREEN: Self = Self::rgb(0.0, 1.0, 0.0);
24    pub const BLUE: Self = Self::rgb(0.0, 0.0, 1.0);
25
26    pub const YELLOW: Self = Self::rgb(1.0, 1.0, 0.0);
27    pub const CYAN: Self = Self::rgb(0.0, 1.0, 1.0);
28    pub const MAGENTA: Self = Self::rgb(1.0, 0.0, 1.0);
29
30    pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
31        Self { r, g, b, a }
32    }
33
34    pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
35        Self { r, g, b, a: 1.0 }
36    }
37
38    pub fn rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
39        Self::rgba(
40            r as f32 / 255.0,
41            g as f32 / 255.0,
42            b as f32 / 255.0,
43            a as f32 / 255.0,
44        )
45    }
46
47    pub fn rgb8(r: u8, g: u8, b: u8) -> Self {
48        Self::rgba8(r, g, b, 255)
49    }
50
51    pub fn try_hex(hex: &str) -> Option<Self> {
52        let hex = hex.trim_start_matches('#');
53
54        let mut color = Self::BLACK;
55
56        match hex.len() {
57            2 => {
58                color.r = u8::from_str_radix(hex, 16).ok()? as f32 / 255.0;
59                color.g = color.r;
60                color.b = color.r;
61            }
62            3 => {
63                color.r = u8::from_str_radix(&hex[0..1], 16).ok()? as f32 / 15.0;
64                color.g = u8::from_str_radix(&hex[1..2], 16).ok()? as f32 / 15.0;
65                color.b = u8::from_str_radix(&hex[2..3], 16).ok()? as f32 / 15.0;
66            }
67            4 => {
68                color.r = u8::from_str_radix(&hex[0..1], 16).ok()? as f32 / 15.0;
69                color.g = u8::from_str_radix(&hex[1..2], 16).ok()? as f32 / 15.0;
70                color.b = u8::from_str_radix(&hex[2..3], 16).ok()? as f32 / 15.0;
71                color.a = u8::from_str_radix(&hex[3..4], 16).ok()? as f32 / 15.0;
72            }
73            6 => {
74                color.r = u8::from_str_radix(&hex[0..2], 16).ok()? as f32 / 255.0;
75                color.g = u8::from_str_radix(&hex[2..4], 16).ok()? as f32 / 255.0;
76                color.b = u8::from_str_radix(&hex[4..6], 16).ok()? as f32 / 255.0;
77            }
78            8 => {
79                color.r = u8::from_str_radix(&hex[0..2], 16).ok()? as f32 / 255.0;
80                color.g = u8::from_str_radix(&hex[2..4], 16).ok()? as f32 / 255.0;
81                color.b = u8::from_str_radix(&hex[4..6], 16).ok()? as f32 / 255.0;
82                color.a = u8::from_str_radix(&hex[6..8], 16).ok()? as f32 / 255.0;
83            }
84            _ => return None,
85        }
86
87        Some(color)
88    }
89
90    pub fn hex(hex: &str) -> Self {
91        Self::try_hex(hex).expect("Invalid hex color")
92    }
93
94    pub fn to_hex(self) -> String {
95        format!(
96            "#{:02x}{:02x}{:02x}",
97            (self.r * 255.0) as u8,
98            (self.g * 255.0) as u8,
99            (self.b * 255.0) as u8,
100        )
101    }
102
103    pub fn is_translucent(self) -> bool {
104        self.a < 1.0
105    }
106
107    pub fn to_linear(self) -> [f32; 4] {
108        // https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation
109        fn linear_component(c: f32) -> f32 {
110            if c <= 0.04045 {
111                c / 12.92
112            } else {
113                ((c + 0.055) / 1.055).powf(2.4)
114            }
115        }
116
117        [
118            linear_component(self.r),
119            linear_component(self.g),
120            linear_component(self.b),
121            self.a,
122        ]
123    }
124}
125
126impl Into<[f32; 4]> for Color {
127    fn into(self) -> [f32; 4] {
128        [self.r, self.g, self.b, self.a]
129    }
130}
131
132impl From<[f32; 4]> for Color {
133    fn from([r, g, b, a]: [f32; 4]) -> Self {
134        Self { r, g, b, a }
135    }
136}
137
138impl Mul<f32> for Color {
139    type Output = Self;
140
141    fn mul(self, rhs: f32) -> Self::Output {
142        Self {
143            r: self.r * rhs,
144            g: self.g * rhs,
145            b: self.b * rhs,
146            a: self.a * rhs,
147        }
148    }
149}
150
151impl Mul for Color {
152    type Output = Self;
153
154    fn mul(self, rhs: Self) -> Self::Output {
155        Self {
156            r: self.r * rhs.r,
157            g: self.g * rhs.g,
158            b: self.b * rhs.b,
159            a: self.a * rhs.a,
160        }
161    }
162}
163
164impl Add for Color {
165    type Output = Self;
166
167    fn add(self, rhs: Self) -> Self::Output {
168        Self {
169            r: self.r + rhs.r,
170            g: self.g + rhs.g,
171            b: self.b + rhs.b,
172            a: self.a + rhs.a,
173        }
174    }
175}
176
177impl AddAssign for Color {
178    fn add_assign(&mut self, rhs: Self) {
179        self.r += rhs.r;
180        self.g += rhs.g;
181        self.b += rhs.b;
182        self.a += rhs.a;
183    }
184}
185
186impl Display for Color {
187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188        write!(f, "rgba({}, {}, {}, {})", self.r, self.g, self.b, self.a)
189    }
190}