1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2pub struct Color {
3 pub r: u8,
4 pub g: u8,
5 pub b: u8,
6 pub a: u8,
7}
8
9impl Color {
10 pub const fn new(r: u8, g: u8, b: u8) -> Self {
12 Self { r, g, b, a: 255 }
13 }
14
15 pub const fn with_alpha(r: u8, g: u8, b: u8, a: u8) -> Self {
17 Self { r, g, b, a }
18 }
19
20 pub fn from_hex(hex: &str) -> Self {
21 let hex = hex.trim_start_matches('#');
22 let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(255);
23 let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(255);
24 let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255);
25
26 if hex.len() == 8 {
27 let a = u8::from_str_radix(&hex[6..8], 16).unwrap_or(255);
28 Self::with_alpha(r, g, b, a)
29 } else {
30 Self::new(r, g, b)
31 }
32 }
33
34 pub fn to_u32(&self, include_alpha: bool) -> u32 {
35 let r = self.r as u32;
36 let g = self.g as u32;
37 let b = self.b as u32;
38 if include_alpha {
39 let a = self.a as u32;
40 return (a << 24) | (r << 16) | (g << 8) | b;
41 }
42 (r << 24) | (g << 16) | (b << 8)
43 }
44
45 pub fn from_u32(color: u32) -> Self {
46 let r = (color >> 24) & 0xFF;
47 let g = (color >> 16) & 0xFF;
48 let b = (color >> 8) & 0xFF;
49 Self::new(r as u8, g as u8, b as u8)
50 }
51
52 pub fn mix_alpha(&self, alpha: u8) -> Self {
53 Self::with_alpha(self.r, self.g, self.b, alpha)
54 }
55
56 pub fn mix(&self, other: &Color, alpha: u8) -> Self {
57 let r =
58 ((self.r as u16 * alpha as u16 + other.r as u16 * (255 - alpha) as u16) / 255) as u8;
59 let g =
60 ((self.g as u16 * alpha as u16 + other.g as u16 * (255 - alpha) as u16) / 255) as u8;
61 let b =
62 ((self.b as u16 * alpha as u16 + other.b as u16 * (255 - alpha) as u16) / 255) as u8;
63 let a = alpha;
64 Self { r, g, b, a }
65 }
66 pub fn invert(&self) -> Color {
67 Color::new(255 - self.r, 255 - self.g, 255 - self.b)
68 }
69}
70
71#[macro_export]
72macro_rules! color {
73 ($r:expr, $g:expr, $b:expr) => {
75 Color::new($r as u8, $g as u8, $b as u8)
76 };
77
78 ($r:expr, $g:expr, $b:expr, $a:expr) => {
80 Color::with_alpha($r as u8, $g as u8, $b as u8, $a as u8)
81 };
82
83 (#$hex:expr) => {
85 Color::from_hex($hex)
86 };
87}
88
89pub const BLACK: Color = color!(0, 0, 0);
91pub const WHITE: Color = color!(255, 255, 255);
92pub const RED: Color = color!(255, 0, 0);
93pub const GREEN: Color = color!(0, 255, 0);
94pub const BLUE: Color = color!(0, 0, 255);
95pub const YELLOW: Color = color!(255, 255, 0); pub const CYAN: Color = color!(0, 255, 255); pub const MAGENTA: Color = color!(255, 0, 255); pub const GRAY: Color = color!(128, 128, 128);
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test_case]
107 fn test_color_from_hex_rgb() {
108 let c = Color::from_hex("#ff8040");
109 assert_eq!(c.r, 255);
110 assert_eq!(c.g, 128);
111 assert_eq!(c.b, 64);
112 assert_eq!(c.a, 255);
113 }
114
115 #[test_case]
116 fn test_color_from_hex_rgba() {
117 let c = Color::from_hex("#ff804080");
118 assert_eq!(c.r, 255);
119 assert_eq!(c.g, 128);
120 assert_eq!(c.b, 64);
121 assert_eq!(c.a, 128);
122 }
123
124 #[test_case]
125 fn test_color_from_hex_without_hash() {
126 let c = Color::from_hex("ff8040");
127 assert_eq!(c.r, 255);
128 assert_eq!(c.g, 128);
129 assert_eq!(c.b, 64);
130 }
131
132 #[test_case]
133 fn test_color_from_hex_invalid() {
134 let c = Color::from_hex("invalid");
135 assert_eq!(c.r, 255);
136 assert_eq!(c.g, 255);
137 assert_eq!(c.b, 255);
138 }
139
140 #[test_case]
141 fn test_color_to_u32_with_alpha() {
142 let c = Color::with_alpha(0x12, 0x34, 0x56, 0x78);
143 let val = c.to_u32(true);
144 assert_eq!(val, 0x78123456);
145 }
146
147 #[test_case]
148 fn test_color_to_u32_without_alpha() {
149 let c = Color::new(0x12, 0x34, 0x56);
150 let val = c.to_u32(false);
151 assert_eq!(val, 0x12345600);
152 }
153
154 #[test_case]
155 fn test_color_from_u32() {
156 let val = 0x12345600;
157 let c = Color::from_u32(val);
158 assert_eq!(c.r, 0x12);
159 assert_eq!(c.g, 0x34);
160 assert_eq!(c.b, 0x56);
161 }
162
163 #[test_case]
164 fn test_color_mix_alpha() {
165 let c = Color::new(255, 128, 64);
166 let c = c.mix_alpha(128);
167 assert_eq!(c.r, 255);
168 assert_eq!(c.g, 128);
169 assert_eq!(c.b, 64);
170 assert_eq!(c.a, 128);
171 }
172
173 #[test_case]
174 fn test_color_mix_half() {
175 let c1 = Color::new(255, 0, 0);
176 let c2 = Color::new(0, 255, 0);
177 let result = c1.mix(&c2, 128);
178 assert_eq!(result.r, 128);
179 assert_eq!(result.g, 127);
180 assert_eq!(result.b, 0);
181 }
182
183 #[test_case]
184 fn test_color_mix_full_first() {
185 let c1 = Color::new(255, 0, 0);
186 let c2 = Color::new(0, 255, 0);
187 let result = c1.mix(&c2, 255);
188 assert_eq!(result.r, 255);
189 assert_eq!(result.g, 0);
190 assert_eq!(result.b, 0);
191 }
192
193 #[test_case]
194 fn test_color_mix_full_second() {
195 let c1 = Color::new(255, 0, 0);
196 let c2 = Color::new(0, 255, 0);
197 let result = c1.mix(&c2, 0);
198 assert_eq!(result.r, 0);
199 assert_eq!(result.g, 255);
200 assert_eq!(result.b, 0);
201 }
202
203 #[test_case]
204 fn test_color_invert() {
205 let c = Color::new(0, 128, 255);
206 let inverted = c.invert();
207 assert_eq!(inverted.r, 255);
208 assert_eq!(inverted.g, 127);
209 assert_eq!(inverted.b, 0);
210 }
211
212 #[test_case]
213 fn test_color_invert_white() {
214 let c = Color::new(255, 255, 255);
215 let inverted = c.invert();
216 assert_eq!(inverted, BLACK);
217 }
218
219 #[test_case]
220 fn test_color_invert_black() {
221 let c = Color::new(0, 0, 0);
222 let inverted = c.invert();
223 assert_eq!(inverted, WHITE);
224 }
225}