1use crate::elements::{Color, RgbColor};
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct ColorValue {
14 pub r: u8,
15 pub g: u8,
16 pub b: u8,
17 pub a: u8, }
19
20impl ColorValue {
21 pub fn rgb(r: u8, g: u8, b: u8) -> Self {
23 Self { r, g, b, a: 255 }
24 }
25
26 pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
28 Self { r, g, b, a }
29 }
30
31 pub fn from_hex(hex: &str) -> Self {
33 let hex = hex.trim_start_matches('#');
34 if hex.len() == 6 {
35 let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
36 let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
37 let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
38 Self::rgb(r, g, b)
39 } else if hex.len() == 8 {
40 let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
41 let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
42 let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
43 let a = u8::from_str_radix(&hex[6..8], 16).unwrap_or(255);
44 Self::rgba(r, g, b, a)
45 } else {
46 Self::rgb(0, 0, 0)
47 }
48 }
49
50 pub fn to_hex(&self) -> String {
52 format!("{:02X}{:02X}{:02X}", self.r, self.g, self.b)
53 }
54
55 pub fn to_hex_alpha(&self) -> String {
57 format!("{:02X}{:02X}{:02X}{:02X}", self.r, self.g, self.b, self.a)
58 }
59
60 pub fn lighter(&self, amount: f32) -> Self {
62 let amount = amount.clamp(0.0, 1.0);
63 let r = (self.r as f32 + (255.0 - self.r as f32) * amount) as u8;
64 let g = (self.g as f32 + (255.0 - self.g as f32) * amount) as u8;
65 let b = (self.b as f32 + (255.0 - self.b as f32) * amount) as u8;
66 Self::rgba(r, g, b, self.a)
67 }
68
69 pub fn darker(&self, amount: f32) -> Self {
71 let amount = amount.clamp(0.0, 1.0);
72 let r = (self.r as f32 * (1.0 - amount)) as u8;
73 let g = (self.g as f32 * (1.0 - amount)) as u8;
74 let b = (self.b as f32 * (1.0 - amount)) as u8;
75 Self::rgba(r, g, b, self.a)
76 }
77
78 pub fn opacity(&self, alpha: f32) -> Self {
80 let alpha = (alpha.clamp(0.0, 1.0) * 255.0) as u8;
81 Self::rgba(self.r, self.g, self.b, alpha)
82 }
83
84 pub fn transparent(&self, percent: u8) -> Self {
86 let percent = percent.min(100);
87 let alpha = ((100 - percent) as f32 / 100.0 * 255.0) as u8;
88 Self::rgba(self.r, self.g, self.b, alpha)
89 }
90
91 pub fn mix(&self, other: &ColorValue, ratio: f32) -> Self {
93 let ratio = ratio.clamp(0.0, 1.0);
94 let r = (self.r as f32 * (1.0 - ratio) + other.r as f32 * ratio) as u8;
95 let g = (self.g as f32 * (1.0 - ratio) + other.g as f32 * ratio) as u8;
96 let b = (self.b as f32 * (1.0 - ratio) + other.b as f32 * ratio) as u8;
97 let a = (self.a as f32 * (1.0 - ratio) + other.a as f32 * ratio) as u8;
98 Self::rgba(r, g, b, a)
99 }
100
101 pub fn grayscale(&self) -> Self {
103 let gray = (0.299 * self.r as f32 + 0.587 * self.g as f32 + 0.114 * self.b as f32) as u8;
104 Self::rgba(gray, gray, gray, self.a)
105 }
106
107 pub fn invert(&self) -> Self {
109 Self::rgba(255 - self.r, 255 - self.g, 255 - self.b, self.a)
110 }
111
112 pub fn to_color(&self) -> Color {
114 Color::Rgb(RgbColor::new(self.r, self.g, self.b))
115 }
116}
117
118pub fn red() -> ColorValue { ColorValue::rgb(255, 0, 0) }
120pub fn green() -> ColorValue { ColorValue::rgb(0, 255, 0) }
121pub fn blue() -> ColorValue { ColorValue::rgb(0, 0, 255) }
122pub fn yellow() -> ColorValue { ColorValue::rgb(255, 255, 0) }
123pub fn cyan() -> ColorValue { ColorValue::rgb(0, 255, 255) }
124pub fn magenta() -> ColorValue { ColorValue::rgb(255, 0, 255) }
125pub fn white() -> ColorValue { ColorValue::rgb(255, 255, 255) }
126pub fn black() -> ColorValue { ColorValue::rgb(0, 0, 0) }
127pub fn gray() -> ColorValue { ColorValue::rgb(128, 128, 128) }
128pub fn grey() -> ColorValue { ColorValue::rgb(128, 128, 128) }
129
130pub fn light_gray() -> ColorValue { ColorValue::rgb(211, 211, 211) }
132pub fn light_grey() -> ColorValue { ColorValue::rgb(211, 211, 211) }
133pub fn dark_gray() -> ColorValue { ColorValue::rgb(64, 64, 64) }
134pub fn dark_grey() -> ColorValue { ColorValue::rgb(64, 64, 64) }
135pub fn silver() -> ColorValue { ColorValue::rgb(192, 192, 192) }
136
137pub fn orange() -> ColorValue { ColorValue::rgb(255, 165, 0) }
139pub fn purple() -> ColorValue { ColorValue::rgb(128, 0, 128) }
140pub fn pink() -> ColorValue { ColorValue::rgb(255, 192, 203) }
141pub fn brown() -> ColorValue { ColorValue::rgb(165, 42, 42) }
142pub fn navy() -> ColorValue { ColorValue::rgb(0, 0, 128) }
143pub fn teal() -> ColorValue { ColorValue::rgb(0, 128, 128) }
144pub fn olive() -> ColorValue { ColorValue::rgb(128, 128, 0) }
145pub fn maroon() -> ColorValue { ColorValue::rgb(128, 0, 0) }
146pub fn lime() -> ColorValue { ColorValue::rgb(0, 255, 0) }
147pub fn aqua() -> ColorValue { ColorValue::rgb(0, 255, 255) }
148
149pub fn material_red() -> ColorValue { ColorValue::from_hex("F44336") }
151pub fn material_pink() -> ColorValue { ColorValue::from_hex("E91E63") }
152pub fn material_purple() -> ColorValue { ColorValue::from_hex("9C27B0") }
153pub fn material_indigo() -> ColorValue { ColorValue::from_hex("3F51B5") }
154pub fn material_blue() -> ColorValue { ColorValue::from_hex("2196F3") }
155pub fn material_cyan() -> ColorValue { ColorValue::from_hex("00BCD4") }
156pub fn material_teal() -> ColorValue { ColorValue::from_hex("009688") }
157pub fn material_green() -> ColorValue { ColorValue::from_hex("4CAF50") }
158pub fn material_lime() -> ColorValue { ColorValue::from_hex("CDDC39") }
159pub fn material_amber() -> ColorValue { ColorValue::from_hex("FFC107") }
160pub fn material_orange() -> ColorValue { ColorValue::from_hex("FF9800") }
161pub fn material_brown() -> ColorValue { ColorValue::from_hex("795548") }
162pub fn material_gray() -> ColorValue { ColorValue::from_hex("9E9E9E") }
163pub fn material_grey() -> ColorValue { ColorValue::from_hex("9E9E9E") }
164
165pub fn corporate_blue() -> ColorValue { ColorValue::from_hex("1565C0") }
167pub fn corporate_green() -> ColorValue { ColorValue::from_hex("2E7D32") }
168pub fn corporate_red() -> ColorValue { ColorValue::from_hex("C62828") }
169pub fn corporate_orange() -> ColorValue { ColorValue::from_hex("EF6C00") }
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn test_rgb_creation() {
177 let color = ColorValue::rgb(255, 128, 64);
178 assert_eq!(color.r, 255);
179 assert_eq!(color.g, 128);
180 assert_eq!(color.b, 64);
181 assert_eq!(color.a, 255);
182 }
183
184 #[test]
185 fn test_hex_conversion() {
186 let color = ColorValue::from_hex("#FF8040");
187 assert_eq!(color.r, 255);
188 assert_eq!(color.g, 128);
189 assert_eq!(color.b, 64);
190 assert_eq!(color.to_hex(), "FF8040");
191 }
192
193 #[test]
194 fn test_lighter() {
195 let color = ColorValue::rgb(100, 100, 100);
196 let lighter = color.lighter(0.5);
197 assert!(lighter.r > color.r);
198 assert!(lighter.g > color.g);
199 assert!(lighter.b > color.b);
200 }
201
202 #[test]
203 fn test_darker() {
204 let color = ColorValue::rgb(200, 200, 200);
205 let darker = color.darker(0.5);
206 assert!(darker.r < color.r);
207 assert!(darker.g < color.g);
208 assert!(darker.b < color.b);
209 }
210
211 #[test]
212 fn test_opacity() {
213 let color = ColorValue::rgb(255, 0, 0);
214 let semi = color.opacity(0.5);
215 assert_eq!(semi.a, 127);
216 }
217
218 #[test]
219 fn test_transparent() {
220 let color = ColorValue::rgb(255, 0, 0);
221 let trans = color.transparent(50);
222 assert_eq!(trans.a, 127);
223 }
224
225 #[test]
226 fn test_mix() {
227 let red = ColorValue::rgb(255, 0, 0);
228 let blue = ColorValue::rgb(0, 0, 255);
229 let purple = red.mix(&blue, 0.5);
230 assert_eq!(purple.r, 127);
231 assert_eq!(purple.b, 127);
232 }
233
234 #[test]
235 fn test_grayscale() {
236 let color = ColorValue::rgb(255, 128, 64);
237 let gray = color.grayscale();
238 assert_eq!(gray.r, gray.g);
239 assert_eq!(gray.g, gray.b);
240 }
241
242 #[test]
243 fn test_invert() {
244 let color = ColorValue::rgb(100, 150, 200);
245 let inverted = color.invert();
246 assert_eq!(inverted.r, 155);
247 assert_eq!(inverted.g, 105);
248 assert_eq!(inverted.b, 55);
249 }
250
251 #[test]
252 fn test_color_aliases() {
253 assert_eq!(red().to_hex(), "FF0000");
254 assert_eq!(green().to_hex(), "00FF00");
255 assert_eq!(blue().to_hex(), "0000FF");
256 assert_eq!(white().to_hex(), "FFFFFF");
257 assert_eq!(black().to_hex(), "000000");
258 }
259}