Skip to main content

yog_graphics/
color.rs

1//! Colours: `0xAARRGGBB`, straight alpha — what every Yog format and API
2//! passes.
3
4/// A colour, `0xAARRGGBB`.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
6pub struct Color(pub u32);
7
8impl Color {
9    pub const TRANSPARENT: Color = Color(0);
10    pub const WHITE: Color = Color(0xffff_ffff);
11    pub const BLACK: Color = Color(0xff00_0000);
12
13    pub const fn argb(a: u8, r: u8, g: u8, b: u8) -> Color {
14        Color((a as u32) << 24 | (r as u32) << 16 | (g as u32) << 8 | b as u32)
15    }
16
17    /// Opaque `0xRRGGBB`.
18    pub const fn rgb(rgb: u32) -> Color {
19        Color(0xff00_0000 | rgb)
20    }
21
22    pub const fn a(self) -> u8 {
23        (self.0 >> 24) as u8
24    }
25    pub const fn r(self) -> u8 {
26        (self.0 >> 16) as u8
27    }
28    pub const fn g(self) -> u8 {
29        (self.0 >> 8) as u8
30    }
31    pub const fn b(self) -> u8 {
32        self.0 as u8
33    }
34
35    /// `[r, g, b, a]` bytes, as an RGBA image keeps them.
36    pub const fn to_rgba8(self) -> [u8; 4] {
37        [self.r(), self.g(), self.b(), self.a()]
38    }
39
40    pub const fn from_rgba8(p: [u8; 4]) -> Color {
41        Color::argb(p[3], p[0], p[1], p[2])
42    }
43
44    /// `[r, g, b, a]` in `0..=1`.
45    pub fn to_f32(self) -> [f32; 4] {
46        [self.r(), self.g(), self.b(), self.a()].map(|v| v as f32 / 255.0)
47    }
48
49    pub fn from_f32(c: [f32; 4]) -> Color {
50        let [r, g, b, a] = c.map(|v| (v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8);
51        Color::argb(a, r, g, b)
52    }
53
54    /// Each channel multiplied by `o`'s (a tint).
55    pub fn multiply(self, o: Color) -> Color {
56        let (a, b) = (self.to_f32(), o.to_f32());
57        Color::from_f32([a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]])
58    }
59
60    /// `#RRGGBB` / `#AARRGGBB` (or `0x…`, `_` allowed), or a decimal
61    /// `0xAARRGGBB`.
62    pub fn parse(value: &str) -> Option<Color> {
63        let v = value.trim();
64        if !v.is_empty() && v.bytes().all(|b| b.is_ascii_digit()) {
65            return v.parse().ok().map(Color);
66        }
67        let hex = v.strip_prefix('#').or_else(|| v.strip_prefix("0x")).or_else(|| v.strip_prefix("0X"))?.replace('_', "");
68        let n = u32::from_str_radix(&hex, 16).ok()?;
69        match hex.len() {
70            6 => Some(Color::rgb(n)),
71            8 => Some(Color(n)),
72            _ => None,
73        }
74    }
75
76    /// With alpha `a`.
77    pub const fn with_alpha(self, a: u8) -> Color {
78        Color(self.0 & 0x00ff_ffff | (a as u32) << 24)
79    }
80}
81
82impl From<u32> for Color {
83    fn from(v: u32) -> Self {
84        Color(v)
85    }
86}
87
88/// In Yog Data, a colour value: `#RRGGBB`/`#AARRGGBB` in text.
89impl yog_data::Schema for Color {
90    fn shape() -> yog_data::schema::Shape {
91        yog_data::schema::Shape::Color
92    }
93    fn read(v: &yog_data::ValueRef<'_>, cx: &mut yog_data::schema::Reader) -> Option<Self> {
94        match v {
95            yog_data::ValueRef::Color(c) => Some(Color(*c)),
96            yog_data::ValueRef::Str(s) if s.starts_with('#') => yog_data::typed::parse_color(s).map(Color).or_else(|| {
97                cx.problem(format!("`{s}` isn't a color (#RRGGBB or #AARRGGBB)"));
98                None
99            }),
100            other => {
101                cx.expected("a color (#RRGGBB or #AARRGGBB)", other);
102                None
103            }
104        }
105    }
106    fn write(&self) -> yog_data::Value {
107        yog_data::Value::Color(self.0)
108    }
109}