Skip to main content

ppt_rs/helpers/
colors.rs

1//! Enhanced color utilities with aliases, adjustments, and conversions
2//!
3//! This module provides a rich color API with:
4//! - Popular color aliases (red, blue, green, etc.)
5//! - RGB color creation and manipulation
6//! - Color adjustment methods (lighter, darker, opacity)
7//! - Color conversion utilities
8
9use crate::elements::{Color, RgbColor};
10
11/// A color value that can be manipulated and converted
12#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct ColorValue {
14    pub r: u8,
15    pub g: u8,
16    pub b: u8,
17    pub a: u8, // Alpha channel (0-255, 255 = opaque)
18}
19
20impl ColorValue {
21    /// Create a new color from RGB values
22    pub fn rgb(r: u8, g: u8, b: u8) -> Self {
23        Self { r, g, b, a: 255 }
24    }
25
26    /// Create a new color from RGBA values
27    pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
28        Self { r, g, b, a }
29    }
30
31    /// Create a color from a hex string (with or without #)
32    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    /// Convert to hex string (RRGGBB format)
51    pub fn to_hex(&self) -> String {
52        format!("{:02X}{:02X}{:02X}", self.r, self.g, self.b)
53    }
54
55    /// Convert to hex string with alpha (RRGGBBAA format)
56    pub fn to_hex_alpha(&self) -> String {
57        format!("{:02X}{:02X}{:02X}{:02X}", self.r, self.g, self.b, self.a)
58    }
59
60    /// Make the color lighter by a percentage (0.0 - 1.0)
61    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    /// Make the color darker by a percentage (0.0 - 1.0)
70    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    /// Adjust the opacity (0.0 = transparent, 1.0 = opaque)
79    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    /// Set transparency percentage (0 = opaque, 100 = transparent)
85    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    /// Mix this color with another color
92    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    /// Convert to grayscale
102    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    /// Invert the color
108    pub fn invert(&self) -> Self {
109        Self::rgba(255 - self.r, 255 - self.g, 255 - self.b, self.a)
110    }
111
112    /// Convert to Color enum for use in the library
113    pub fn to_color(&self) -> Color {
114        Color::Rgb(RgbColor::new(self.r, self.g, self.b))
115    }
116}
117
118// Popular color aliases
119pub 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
130// Shades of gray
131pub 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
137// Common web colors
138pub 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
149// Material Design colors
150pub 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
165// Corporate/Professional colors
166pub 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}