1#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
12pub struct Hsl {
13 pub alpha: u8,
14 pub hue: u8,
15 pub sat: u8,
16 pub lum: u8,
17}
18
19impl Hsl {
20 pub fn new(alpha: u8, hue: u8, sat: u8, lum: u8) -> Self {
21 Self {
22 alpha,
23 hue,
24 sat,
25 lum,
26 }
27 }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct Color32 {
36 pub color: u32,
37}
38
39impl Color32 {
40 pub fn from_argb(a: u8, r: u8, g: u8, b: u8) -> Self {
42 let color = (a as u32) << 24 | (r as u32) << 16 | (g as u32) << 8 | (b as u32);
43 Self { color }
44 }
45
46 pub fn alpha(self) -> u8 {
48 (self.color >> 24) as u8
49 }
50
51 pub fn red(self) -> u8 {
53 (self.color >> 16) as u8
54 }
55
56 pub fn green(self) -> u8 {
58 (self.color >> 8) as u8
59 }
60
61 pub fn blue(self) -> u8 {
63 self.color as u8
64 }
65}
66
67pub fn hsl_to_rgb(hsl: Hsl) -> Color32 {
71 let c = ((255 - (2 * hsl.lum as i32 - 255).abs()) * hsl.sat as i32) >> 8;
72 let a = 252 - (hsl.hue as i32 % 85) * 6;
73 let x = (c * (255 - a.abs())) >> 8;
74 let m = hsl.lum as i32 - c / 2;
75
76 let (r, g, b) = match (hsl.hue as i32 * 6) >> 8 {
77 0 => (c + m, x + m, m),
78 1 => (x + m, c + m, m),
79 2 => (m, c + m, x + m),
80 3 => (m, x + m, c + m),
81 4 => (x + m, m, c + m),
82 5 => (c + m, m, x + m),
83 _ => (m, m, m),
84 };
85
86 Color32::from_argb(
87 hsl.alpha,
88 r.clamp(0, 255) as u8,
89 g.clamp(0, 255) as u8,
90 b.clamp(0, 255) as u8,
91 )
92}
93
94pub fn rainbow_color(frac: f64, luminance: u8, alpha: u8) -> u32 {
103 let frac = frac - frac.floor();
104 let hsl = Hsl::new(alpha, (frac * 255.0) as u8, 255, luminance);
105 hsl_to_rgb(hsl).color
106}
107
108pub fn rainbow_color_default(frac: f64) -> u32 {
110 rainbow_color(frac, 128, 255)
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_hsl_default() {
119 let h = Hsl::default();
120 assert_eq!(h.alpha, 0);
121 assert_eq!(h.hue, 0);
122 assert_eq!(h.sat, 0);
123 assert_eq!(h.lum, 0);
124 }
125
126 #[test]
127 fn test_hsl_new() {
128 let h = Hsl::new(255, 128, 200, 100);
129 assert_eq!(h.alpha, 255);
130 assert_eq!(h.hue, 128);
131 assert_eq!(h.sat, 200);
132 assert_eq!(h.lum, 100);
133 }
134
135 #[test]
136 fn test_color32_from_argb() {
137 let c = Color32::from_argb(0xFF, 0x12, 0x34, 0x56);
138 assert_eq!(c.alpha(), 0xFF);
139 assert_eq!(c.red(), 0x12);
140 assert_eq!(c.green(), 0x34);
141 assert_eq!(c.blue(), 0x56);
142 assert_eq!(c.color, 0xFF123456);
143 }
144
145 #[test]
146 fn test_hsl_to_rgb_zero_sat() {
147 let hsl = Hsl::new(255, 0, 0, 128);
149 let rgb = hsl_to_rgb(hsl);
150 assert_eq!(rgb.alpha(), 255);
151 assert_eq!(rgb.red(), rgb.green());
153 assert_eq!(rgb.green(), rgb.blue());
154 }
155
156 #[test]
157 fn test_hsl_to_rgb_full_saturation_red() {
158 let hsl = Hsl::new(255, 0, 255, 128);
160 let rgb = hsl_to_rgb(hsl);
161 assert_eq!(rgb.alpha(), 255);
162 assert!(rgb.red() > rgb.green());
163 assert!(rgb.red() > rgb.blue());
164 }
165
166 #[test]
167 fn test_rainbow_color_returns_opaque() {
168 let c = rainbow_color(0.0, 128, 255);
169 assert_eq!((c >> 24) & 0xFF, 255);
170 }
171
172 #[test]
173 fn test_rainbow_color_wraps() {
174 let c1 = rainbow_color(0.25, 128, 255);
176 let c2 = rainbow_color(1.25, 128, 255);
177 assert_eq!(c1, c2);
178 }
179
180 #[test]
181 fn test_rainbow_color_different_positions() {
182 let c1 = rainbow_color(0.0, 128, 255);
183 let c2 = rainbow_color(0.5, 128, 255);
184 assert_ne!(c1, c2);
185 }
186
187 #[test]
188 fn test_rainbow_color_default() {
189 let c = rainbow_color_default(0.3);
190 assert_eq!((c >> 24) & 0xFF, 255); }
192}