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 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}