iced_core/
color.rs

1use palette::rgb::{Srgb, Srgba};
2
3/// A color in the `sRGB` color space.
4#[derive(Debug, Clone, Copy, PartialEq, Default)]
5pub struct Color {
6    /// Red component, 0.0 - 1.0
7    pub r: f32,
8    /// Green component, 0.0 - 1.0
9    pub g: f32,
10    /// Blue component, 0.0 - 1.0
11    pub b: f32,
12    /// Transparency, 0.0 - 1.0
13    pub a: f32,
14}
15
16impl Color {
17    /// The black color.
18    pub const BLACK: Color = Color {
19        r: 0.0,
20        g: 0.0,
21        b: 0.0,
22        a: 1.0,
23    };
24
25    /// The white color.
26    pub const WHITE: Color = Color {
27        r: 1.0,
28        g: 1.0,
29        b: 1.0,
30        a: 1.0,
31    };
32
33    /// A color with no opacity.
34    pub const TRANSPARENT: Color = Color {
35        r: 0.0,
36        g: 0.0,
37        b: 0.0,
38        a: 0.0,
39    };
40
41    /// Creates a new [`Color`].
42    ///
43    /// In debug mode, it will panic if the values are not in the correct
44    /// range: 0.0 - 1.0
45    const fn new(r: f32, g: f32, b: f32, a: f32) -> Color {
46        debug_assert!(
47            r >= 0.0 && r <= 1.0,
48            "Red component must be in [0, 1] range."
49        );
50        debug_assert!(
51            g >= 0.0 && g <= 1.0,
52            "Green component must be in [0, 1] range."
53        );
54        debug_assert!(
55            b >= 0.0 && b <= 1.0,
56            "Blue component must be in [0, 1] range."
57        );
58
59        Color { r, g, b, a }
60    }
61
62    /// Creates a [`Color`] from its RGB components.
63    pub const fn from_rgb(r: f32, g: f32, b: f32) -> Color {
64        Color::from_rgba(r, g, b, 1.0f32)
65    }
66
67    /// Creates a [`Color`] from its RGBA components.
68    pub const fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Color {
69        Color::new(r, g, b, a)
70    }
71
72    /// Creates a [`Color`] from its RGB8 components.
73    pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Color {
74        Color::from_rgba8(r, g, b, 1.0)
75    }
76
77    /// Creates a [`Color`] from its RGB8 components and an alpha value.
78    pub const fn from_rgba8(r: u8, g: u8, b: u8, a: f32) -> Color {
79        Color::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a)
80    }
81
82    /// Creates a [`Color`] from its linear RGBA components.
83    pub fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
84        // As described in:
85        // https://en.wikipedia.org/wiki/SRGB
86        fn gamma_component(u: f32) -> f32 {
87            if u < 0.0031308 {
88                12.92 * u
89            } else {
90                1.055 * u.powf(1.0 / 2.4) - 0.055
91            }
92        }
93
94        Self {
95            r: gamma_component(r),
96            g: gamma_component(g),
97            b: gamma_component(b),
98            a,
99        }
100    }
101
102    /// Parses a [`Color`] from a hex string.
103    ///
104    /// Supported formats are `#rrggbb`, `#rrggbbaa`, `#rgb`, and `#rgba`.
105    /// The starting "#" is optional. Both uppercase and lowercase are supported.
106    ///
107    /// If you have a static color string, using the [`color!`] macro should be preferred
108    /// since it leverages hexadecimal literal notation and arithmetic directly.
109    ///
110    /// [`color!`]: crate::color!
111    pub fn parse(s: &str) -> Option<Color> {
112        let hex = s.strip_prefix('#').unwrap_or(s);
113
114        let parse_channel = |from: usize, to: usize| {
115            let num =
116                usize::from_str_radix(&hex[from..=to], 16).ok()? as f32 / 255.0;
117
118            // If we only got half a byte (one letter), expand it into a full byte (two letters)
119            Some(if from == to { num + num * 16.0 } else { num })
120        };
121
122        Some(match hex.len() {
123            3 => Color::from_rgb(
124                parse_channel(0, 0)?,
125                parse_channel(1, 1)?,
126                parse_channel(2, 2)?,
127            ),
128            4 => Color::from_rgba(
129                parse_channel(0, 0)?,
130                parse_channel(1, 1)?,
131                parse_channel(2, 2)?,
132                parse_channel(3, 3)?,
133            ),
134            6 => Color::from_rgb(
135                parse_channel(0, 1)?,
136                parse_channel(2, 3)?,
137                parse_channel(4, 5)?,
138            ),
139            8 => Color::from_rgba(
140                parse_channel(0, 1)?,
141                parse_channel(2, 3)?,
142                parse_channel(4, 5)?,
143                parse_channel(6, 7)?,
144            ),
145            _ => None?,
146        })
147    }
148
149    /// Converts the [`Color`] into its RGBA8 equivalent.
150    #[must_use]
151    pub fn into_rgba8(self) -> [u8; 4] {
152        [
153            (self.r * 255.0).round() as u8,
154            (self.g * 255.0).round() as u8,
155            (self.b * 255.0).round() as u8,
156            (self.a * 255.0).round() as u8,
157        ]
158    }
159
160    /// Converts the [`Color`] into its linear values.
161    pub fn into_linear(self) -> [f32; 4] {
162        // As described in:
163        // https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation
164        fn linear_component(u: f32) -> f32 {
165            if u < 0.04045 {
166                u / 12.92
167            } else {
168                ((u + 0.055) / 1.055).powf(2.4)
169            }
170        }
171
172        [
173            linear_component(self.r),
174            linear_component(self.g),
175            linear_component(self.b),
176            self.a,
177        ]
178    }
179
180    /// Inverts the [`Color`] in-place.
181    pub fn invert(&mut self) {
182        self.r = 1.0f32 - self.r;
183        self.b = 1.0f32 - self.g;
184        self.g = 1.0f32 - self.b;
185    }
186
187    /// Returns the inverted [`Color`].
188    pub fn inverse(self) -> Color {
189        Color::new(1.0f32 - self.r, 1.0f32 - self.g, 1.0f32 - self.b, self.a)
190    }
191
192    /// Scales the alpha channel of the [`Color`] by the given factor.
193    pub fn scale_alpha(self, factor: f32) -> Color {
194        Self {
195            a: self.a * factor,
196            ..self
197        }
198    }
199}
200
201impl From<[f32; 3]> for Color {
202    fn from([r, g, b]: [f32; 3]) -> Self {
203        Color::new(r, g, b, 1.0)
204    }
205}
206
207impl From<[f32; 4]> for Color {
208    fn from([r, g, b, a]: [f32; 4]) -> Self {
209        Color::new(r, g, b, a)
210    }
211}
212
213/// Creates a [`Color`] with shorter and cleaner syntax.
214///
215/// # Examples
216///
217/// ```
218/// # use iced_core::{Color, color};
219/// assert_eq!(color!(0, 0, 0), Color::BLACK);
220/// assert_eq!(color!(0, 0, 0, 0.0), Color::TRANSPARENT);
221/// assert_eq!(color!(0xffffff), Color::from_rgb(1.0, 1.0, 1.0));
222/// assert_eq!(color!(0xffffff, 0.), Color::from_rgba(1.0, 1.0, 1.0, 0.0));
223/// assert_eq!(color!(0x0000ff), Color::from_rgba(0.0, 0.0, 1.0, 1.0));
224/// ```
225#[macro_export]
226macro_rules! color {
227    ($r:expr, $g:expr, $b:expr) => {
228        $crate::Color::from_rgb8($r, $g, $b)
229    };
230    ($r:expr, $g:expr, $b:expr, $a:expr) => {{ $crate::Color::from_rgba8($r, $g, $b, $a) }};
231    ($hex:expr) => {{ $crate::color!($hex, 1.0) }};
232    ($hex:expr, $a:expr) => {{
233        let hex = $hex as u32;
234
235        debug_assert!(hex <= 0xffffff, "color! value must not exceed 0xffffff");
236
237        let r = (hex & 0xff0000) >> 16;
238        let g = (hex & 0xff00) >> 8;
239        let b = (hex & 0xff);
240
241        $crate::color!(r as u8, g as u8, b as u8, $a)
242    }};
243}
244
245/// Converts from palette's `Rgba` type to a [`Color`].
246impl From<Srgba> for Color {
247    fn from(rgba: Srgba) -> Self {
248        Color::new(rgba.red, rgba.green, rgba.blue, rgba.alpha)
249    }
250}
251
252/// Converts from [`Color`] to palette's `Rgba` type.
253impl From<Color> for Srgba {
254    fn from(c: Color) -> Self {
255        Srgba::new(c.r, c.g, c.b, c.a)
256    }
257}
258
259/// Converts from palette's `Rgb` type to a [`Color`].
260impl From<Srgb> for Color {
261    fn from(rgb: Srgb) -> Self {
262        Color::new(rgb.red, rgb.green, rgb.blue, 1.0)
263    }
264}
265
266/// Converts from [`Color`] to palette's `Rgb` type.
267impl From<Color> for Srgb {
268    fn from(c: Color) -> Self {
269        Srgb::new(c.r, c.g, c.b)
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276    use palette::blend::Blend;
277
278    #[test]
279    fn srgba_traits() {
280        let c = Color::from_rgb(0.5, 0.4, 0.3);
281        // Round-trip conversion to the palette::Srgba type
282        let s: Srgba = c.into();
283        let r: Color = s.into();
284        assert_eq!(c, r);
285    }
286
287    #[test]
288    fn color_manipulation() {
289        use approx::assert_relative_eq;
290
291        let c1 = Color::from_rgb(0.5, 0.4, 0.3);
292        let c2 = Color::from_rgb(0.2, 0.5, 0.3);
293
294        // Convert to linear color for manipulation
295        let l1 = Srgba::from(c1).into_linear();
296        let l2 = Srgba::from(c2).into_linear();
297
298        // Take the lighter of each of the sRGB components
299        let lighter = l1.lighten(l2);
300
301        // Convert back to our Color
302        let result: Color = Srgba::from_linear(lighter).into();
303
304        assert_relative_eq!(result.r, 0.5);
305        assert_relative_eq!(result.g, 0.5);
306        assert_relative_eq!(result.b, 0.3);
307        assert_relative_eq!(result.a, 1.0);
308    }
309
310    #[test]
311    fn parse() {
312        let tests = [
313            ("#ff0000", [255, 0, 0, 255]),
314            ("00ff0080", [0, 255, 0, 128]),
315            ("#F80", [255, 136, 0, 255]),
316            ("#00f1", [0, 0, 255, 17]),
317        ];
318
319        for (arg, expected) in tests {
320            assert_eq!(
321                Color::parse(arg).expect("color must parse").into_rgba8(),
322                expected
323            );
324        }
325
326        assert!(Color::parse("invalid").is_none());
327    }
328}