1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
6pub struct Color(pub u32);
7
8const ALPHA_SHIFT: u32 = 24;
10const ALPHA_BITS: u32 = 0xff00_0000;
11const RGB_BITS: u32 = 0x00ff_ffff;
12const CHANNEL_MAX: f32 = u8::MAX as f32;
14
15impl Color {
16 pub const TRANSPARENT: Color = Color(0);
17 pub const WHITE: Color = Color(0xffff_ffff);
18 pub const BLACK: Color = Color(0xff00_0000);
19
20 pub const fn argb(a: u8, r: u8, g: u8, b: u8) -> Color {
21 Color((a as u32) << 24 | (r as u32) << 16 | (g as u32) << 8 | b as u32)
22 }
23
24 pub const fn rgb(rgb: u32) -> Color {
26 Color(ALPHA_BITS | rgb)
27 }
28
29 pub const fn a(self) -> u8 {
30 (self.0 >> ALPHA_SHIFT) as u8
31 }
32 pub const fn r(self) -> u8 {
33 (self.0 >> 16) as u8
34 }
35 pub const fn g(self) -> u8 {
36 (self.0 >> 8) as u8
37 }
38 pub const fn b(self) -> u8 {
39 self.0 as u8
40 }
41
42 pub const fn to_rgba8(self) -> crate::Rgba8 {
44 [self.r(), self.g(), self.b(), self.a()]
45 }
46
47 pub const fn from_rgba8(p: crate::Rgba8) -> Color {
48 Color::argb(p[3], p[0], p[1], p[2])
49 }
50
51 pub fn to_f32(self) -> [f32; 4] {
53 [self.r(), self.g(), self.b(), self.a()].map(|v| v as f32 / CHANNEL_MAX)
54 }
55
56 pub fn from_f32(c: [f32; 4]) -> Color {
57 let [r, g, b, a] = c.map(|v| (v.clamp(0.0, 1.0) * CHANNEL_MAX).round() as u8);
58 Color::argb(a, r, g, b)
59 }
60
61 pub fn multiply(self, o: Color) -> Color {
63 let (a, b) = (self.to_f32(), o.to_f32());
64 Color::from_f32([a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]])
65 }
66
67 pub fn parse(value: &str) -> Option<Color> {
70 let v = value.trim();
71 if !v.is_empty() && v.bytes().all(|b| b.is_ascii_digit()) {
72 return v.parse().ok().map(Color);
73 }
74 let hex = v
75 .strip_prefix('#')
76 .or_else(|| v.strip_prefix("0x"))
77 .or_else(|| v.strip_prefix("0X"))?
78 .replace('_', "");
79 let n = u32::from_str_radix(&hex, 16).ok()?;
80 match hex.len() {
81 6 => Some(Color::rgb(n)),
82 8 => Some(Color(n)),
83 _ => None,
84 }
85 }
86
87 pub const fn with_alpha(self, a: u8) -> Color {
89 Color(self.0 & RGB_BITS | (a as u32) << ALPHA_SHIFT)
90 }
91}
92
93impl From<u32> for Color {
94 fn from(v: u32) -> Self {
95 Color(v)
96 }
97}
98
99impl yog_data::Schema for Color {
101 fn shape() -> yog_data::schema::Shape {
102 yog_data::schema::Shape::Color
103 }
104 fn read(v: &yog_data::ValueRef<'_>, cx: &mut yog_data::schema::Reader) -> Option<Self> {
105 match v {
106 yog_data::ValueRef::Color(c) => Some(Color(*c)),
107 yog_data::ValueRef::Str(s) if s.starts_with('#') => {
108 yog_data::typed::parse_color(s).map(Color).or_else(|| {
109 cx.problem(format!("`{s}` isn't a color (#RRGGBB or #AARRGGBB)"));
110 None
111 })
112 }
113 other => {
114 cx.expected("a color (#RRGGBB or #AARRGGBB)", other);
115 None
116 }
117 }
118 }
119 fn write(&self) -> yog_data::Value {
120 yog_data::Value::Color(self.0)
121 }
122}