1#[cfg(feature = "gpui")]
4use gpui::Rgba;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
18pub struct D3Color {
19 pub r: f32,
21 pub g: f32,
23 pub b: f32,
25 pub a: f32,
27}
28
29impl D3Color {
30 pub fn rgb(r: u8, g: u8, b: u8) -> Self {
40 Self {
41 r: r as f32 / 255.0,
42 g: g as f32 / 255.0,
43 b: b as f32 / 255.0,
44 a: 1.0,
45 }
46 }
47
48 pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
58 Self {
59 r: r as f32 / 255.0,
60 g: g as f32 / 255.0,
61 b: b as f32 / 255.0,
62 a: a as f32 / 255.0,
63 }
64 }
65
66 pub fn from_hex(hex: u32) -> Self {
76 Self::rgb(
77 ((hex >> 16) & 0xFF) as u8,
78 ((hex >> 8) & 0xFF) as u8,
79 (hex & 0xFF) as u8,
80 )
81 }
82
83 pub fn from_rgb_f32(r: f32, g: f32, b: f32) -> Self {
93 Self { r, g, b, a: 1.0 }
94 }
95
96 pub fn from_rgba_f32(r: f32, g: f32, b: f32, a: f32) -> Self {
98 Self { r, g, b, a }
99 }
100
101 #[cfg(feature = "gpui")]
112 pub fn to_rgba(&self) -> Rgba {
113 Rgba {
114 r: self.r,
115 g: self.g,
116 b: self.b,
117 a: self.a,
118 }
119 }
120
121 #[cfg(feature = "gpui")]
123 pub fn from_rgba(rgba: Rgba) -> Self {
124 Self {
125 r: rgba.r,
126 g: rgba.g,
127 b: rgba.b,
128 a: rgba.a,
129 }
130 }
131
132 pub fn with_alpha(mut self, alpha: f32) -> Self {
142 self.a = alpha.clamp(0.0, 1.0);
143 self
144 }
145
146 pub fn interpolate(&self, other: &D3Color, t: f32) -> D3Color {
163 let t = t.clamp(0.0, 1.0);
164 D3Color {
165 r: self.r + (other.r - self.r) * t,
166 g: self.g + (other.g - self.g) * t,
167 b: self.b + (other.b - self.b) * t,
168 a: self.a + (other.a - self.a) * t,
169 }
170 }
171
172 pub fn lighten(&self, amount: f32) -> D3Color {
174 let white = D3Color::rgb(255, 255, 255);
175 self.interpolate(&white, amount.clamp(0.0, 1.0))
176 }
177
178 pub fn darken(&self, amount: f32) -> D3Color {
180 let black = D3Color::rgb(0, 0, 0);
181 self.interpolate(&black, amount.clamp(0.0, 1.0))
182 }
183
184 pub fn to_hex(&self) -> String {
195 let r = (self.r * 255.0).round() as u8;
196 let g = (self.g * 255.0).round() as u8;
197 let b = (self.b * 255.0).round() as u8;
198 format!("#{:02x}{:02x}{:02x}", r, g, b)
199 }
200
201 pub fn to_hex_alpha(&self) -> String {
212 let r = (self.r * 255.0).round() as u8;
213 let g = (self.g * 255.0).round() as u8;
214 let b = (self.b * 255.0).round() as u8;
215 let a = (self.a * 255.0).round() as u8;
216 format!("#{:02x}{:02x}{:02x}{:02x}", r, g, b, a)
217 }
218
219 pub fn luminance(&self) -> f32 {
234 fn to_linear(c: f32) -> f32 {
236 if c <= 0.03928 {
237 c / 12.92
238 } else {
239 ((c + 0.055) / 1.055).powf(2.4)
240 }
241 }
242 0.2126 * to_linear(self.r) + 0.7152 * to_linear(self.g) + 0.0722 * to_linear(self.b)
243 }
244
245 pub fn brighter(&self, k: f32) -> D3Color {
249 let k = 0.7_f32.powf(k);
250 D3Color {
251 r: (self.r / k).min(1.0),
252 g: (self.g / k).min(1.0),
253 b: (self.b / k).min(1.0),
254 a: self.a,
255 }
256 }
257
258 pub fn darker(&self, k: f32) -> D3Color {
262 let k = 0.7_f32.powf(k);
263 D3Color {
264 r: self.r * k,
265 g: self.g * k,
266 b: self.b * k,
267 a: self.a,
268 }
269 }
270
271 pub fn with_opacity(&self, opacity: f32) -> D3Color {
273 D3Color {
274 r: self.r,
275 g: self.g,
276 b: self.b,
277 a: opacity.clamp(0.0, 1.0),
278 }
279 }
280
281 pub fn opacity(&self) -> f32 {
283 self.a
284 }
285
286 pub fn from_hsl(h: f32, s: f32, l: f32) -> D3Color {
301 let h = h % 360.0;
302 let h = if h < 0.0 { h + 360.0 } else { h } / 360.0;
303 let s = s.clamp(0.0, 1.0);
304 let l = l.clamp(0.0, 1.0);
305
306 if s == 0.0 {
307 return D3Color::from_rgb_f32(l, l, l);
308 }
309
310 let q = if l < 0.5 {
311 l * (1.0 + s)
312 } else {
313 l + s - l * s
314 };
315 let p = 2.0 * l - q;
316
317 fn hue_to_rgb(p: f32, q: f32, mut t: f32) -> f32 {
318 if t < 0.0 {
319 t += 1.0;
320 }
321 if t > 1.0 {
322 t -= 1.0;
323 }
324 if t < 1.0 / 6.0 {
325 return p + (q - p) * 6.0 * t;
326 }
327 if t < 1.0 / 2.0 {
328 return q;
329 }
330 if t < 2.0 / 3.0 {
331 return p + (q - p) * (2.0 / 3.0 - t) * 6.0;
332 }
333 p
334 }
335
336 D3Color::from_rgb_f32(
337 hue_to_rgb(p, q, h + 1.0 / 3.0),
338 hue_to_rgb(p, q, h),
339 hue_to_rgb(p, q, h - 1.0 / 3.0),
340 )
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347 use approx::assert_relative_eq;
348
349 #[test]
350 fn test_rgb_creation() {
351 let color = D3Color::rgb(255, 128, 64);
352 assert_relative_eq!(color.r, 1.0);
353 assert_relative_eq!(color.g, 128.0 / 255.0, epsilon = 1e-6);
354 assert_relative_eq!(color.b, 64.0 / 255.0, epsilon = 1e-6);
355 assert_relative_eq!(color.a, 1.0);
356 }
357
358 #[test]
359 fn test_hex_conversion() {
360 let color = D3Color::from_hex(0xff8040);
361 assert_relative_eq!(color.r, 1.0, epsilon = 1e-6);
362 assert_relative_eq!(color.g, 128.0 / 255.0, epsilon = 1e-6);
363 assert_relative_eq!(color.b, 64.0 / 255.0, epsilon = 1e-6);
364 }
365
366 #[test]
367 fn test_interpolation() {
368 let red = D3Color::rgb(255, 0, 0);
369 let blue = D3Color::rgb(0, 0, 255);
370
371 let mid = red.interpolate(&blue, 0.5);
372 assert_relative_eq!(mid.r, 0.5, epsilon = 1e-6);
373 assert_relative_eq!(mid.g, 0.0, epsilon = 1e-6);
374 assert_relative_eq!(mid.b, 0.5, epsilon = 1e-6);
375
376 let at_start = red.interpolate(&blue, 0.0);
377 assert_relative_eq!(at_start.r, red.r, epsilon = 1e-6);
378
379 let at_end = red.interpolate(&blue, 1.0);
380 assert_relative_eq!(at_end.b, blue.b, epsilon = 1e-6);
381 }
382
383 #[test]
384 fn test_alpha_channel() {
385 let color = D3Color::rgba(255, 0, 0, 128);
386 assert_relative_eq!(color.a, 128.0 / 255.0, epsilon = 1e-6);
387
388 let with_alpha = D3Color::rgb(255, 0, 0).with_alpha(0.5);
389 assert_relative_eq!(with_alpha.a, 0.5);
390 }
391
392 #[test]
393 fn test_lighten_darken() {
394 let color = D3Color::rgb(128, 128, 128);
395
396 let lighter = color.lighten(0.5);
397 assert!(lighter.r > color.r);
398 assert!(lighter.g > color.g);
399 assert!(lighter.b > color.b);
400
401 let darker = color.darken(0.5);
402 assert!(darker.r < color.r);
403 assert!(darker.g < color.g);
404 assert!(darker.b < color.b);
405 }
406
407 #[test]
408 #[cfg(feature = "gpui")]
409 fn test_rgba_conversion() {
410 let color = D3Color::rgb(255, 128, 64);
411 let rgba = color.to_rgba();
412
413 assert_relative_eq!(rgba.r, 1.0);
414 assert_relative_eq!(rgba.g, 128.0 / 255.0, epsilon = 1e-6);
415 assert_relative_eq!(rgba.b, 64.0 / 255.0, epsilon = 1e-6);
416
417 let back = D3Color::from_rgba(rgba);
418 assert_relative_eq!(back.r, color.r);
419 assert_relative_eq!(back.g, color.g);
420 assert_relative_eq!(back.b, color.b);
421 }
422}