1/// Struct representing a color with red, green, blue, and alpha components.
2#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
3#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
4pub struct Color {
5 /// Red component of the color (0.0 to 1.0)
6 pub r: f32,
7 /// Green component of the color (0.0 to 1.0)
8 pub g: f32,
9 /// Blue component of the color (0.0 to 1.0)
10 pub b: f32,
11 /// Alpha (opacity) component of the color (0.0 to 1.0)
12 pub a: f32,
13}
14
15impl Color {
16 /// Creates a color from red, green, and blue u8 values. Alpha is set to 255.
17 pub fn rgb(r: u8, g: u8, b: u8) -> Self {
18 Self::rgbf(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0)
19 }
20
21 /// Creates a color from red, green, and blue f32 values. Alpha is set to 1.0.
22 pub const fn rgbf(r: f32, g: f32, b: f32) -> Self {
23 Self { r, g, b, a: 1.0 }
24 }
25
26 /// Creates a color from red, green, blue, and alpha u8 values.
27 pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
28 Self::rgbaf(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a as f32 / 255.0)
29 }
30
31 /// Creates a color from red, green, blue, and alpha f32 values.
32 pub const fn rgbaf(r: f32, g: f32, b: f32, a: f32) -> Self {
33 Self { r, g, b, a }
34 }
35
36 /// Creates a color from hue, saturation, and lightness f32 values. Alpha is set to 1.0.
37 /// All values are all in range [0..1].
38 pub fn hsl(h: f32, s: f32, l: f32) -> Self {
39 Self::hsla(h, s, l, 1.0)
40 }
41
42 /// Creates a color from hue, saturation, lightness, and alpha f32 values.
43 /// All values are all in range [0..1].
44 pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Self {
45 let mut h = h % 1.0;
46
47 if h < 0.0 {
48 h += 1.0;
49 }
50
51 let s = s.clamp(0.0, 1.0);
52 let l = l.clamp(0.0, 1.0);
53
54 let m2 = if l <= 0.5 { l * (1.0 + s) } else { l + s - l * s };
55 let m1 = 2.0 * l - m2;
56
57 Self {
58 r: hue(h + 1.0 / 3.0, m1, m2).clamp(0.0, 1.0),
59 g: hue(h, m1, m2).clamp(0.0, 1.0),
60 b: hue(h - 1.0 / 3.0, m1, m2).clamp(0.0, 1.0),
61 a,
62 }
63 }
64
65 /// Creates a color from a 6-digit (`RRGGBB`) or 8-digit (`RRGGBBAA`) HTML hexadecimal string.
66 /// Any other length produces `rgb(0,0,0)`.
67 /// The “#” is optional.
68 pub fn hex(raw_hex: &str) -> Self {
69 let hex = raw_hex.trim_start_matches('#');
70
71 if hex.len() == 8 {
72 Self::rgba(
73 hex_to_u8(&hex[0..2]),
74 hex_to_u8(&hex[2..4]),
75 hex_to_u8(&hex[4..6]),
76 hex_to_u8(&hex[6..8]),
77 )
78 } else if hex.len() == 6 {
79 Self::rgb(hex_to_u8(&hex[0..2]), hex_to_u8(&hex[2..4]), hex_to_u8(&hex[4..6]))
80 } else {
81 Self::rgb(0, 0, 0)
82 }
83 }
84
85 /// Returns a white color (1.0, 1.0, 1.0, 1.0)
86 pub const fn white() -> Self {
87 Self::rgbaf(1.0, 1.0, 1.0, 1.0)
88 }
89
90 /// Returns a black color (0.0, 0.0, 0.0, 1.0)
91 pub const fn black() -> Self {
92 Self::rgbaf(0.0, 0.0, 0.0, 1.0)
93 }
94
95 /// Sets the alpha (opacity) component of the color from a u8 value.
96 pub fn set_alpha(&mut self, a: u8) {
97 self.set_alphaf(a as f32 / 255.0);
98 }
99
100 /// Sets the alpha (opacity) component of the color from an f32 value.
101 pub fn set_alphaf(&mut self, a: f32) {
102 self.a = a;
103 }
104
105 /// Returns a color with premultiplied alpha components.
106 pub fn premultiplied(self) -> Self {
107 Self {
108 r: self.r * self.a,
109 g: self.g * self.a,
110 b: self.b * self.a,
111 a: self.a,
112 }
113 }
114
115 /// Converts the color to a [f32; 4] array.
116 pub const fn to_array(self) -> [f32; 4] {
117 [self.r, self.g, self.b, self.a]
118 }
119
120 /// Checks if the color is black (0.0, 0.0, 0.0, 0.0)
121 pub fn is_black(&self) -> bool {
122 self.r == 0.0 && self.g == 0.0 && self.b == 0.0 && self.a == 0.0
123 }
124}
125
126impl Default for Color {
127 fn default() -> Self {
128 Self {
129 r: 0.0,
130 g: 0.0,
131 b: 0.0,
132 a: 1.0,
133 }
134 }
135}
136
137fn hue(mut h: f32, m1: f32, m2: f32) -> f32 {
138 if h < 0.0 {
139 h += 1.0;
140 }
141 if h > 1.0 {
142 h -= 1.0;
143 }
144
145 if h < 1.0 / 6.0 {
146 return m1 + (m2 - m1) * h * 6.0;
147 }
148 if h < 3.0 / 6.0 {
149 return m2;
150 }
151 if h < 4.0 / 6.0 {
152 return m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6.0;
153 }
154
155 m1
156}
157
158// Convert a hex string to decimal. Eg. "00" -> 0. "FF" -> 255.
159fn hex_to_u8(hex_string: &str) -> u8 {
160 u8::from_str_radix(hex_string, 16).unwrap_or(default:0)
161}
162