color.rs

  1#![allow(dead_code)]
  2
  3use bytemuck::{Pod, Zeroable};
  4use serde::de::{self, Deserialize, Deserializer, Visitor};
  5use std::fmt;
  6use std::num::ParseIntError;
  7
  8pub fn rgb(hex: u32) -> Rgba {
  9    let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
 10    let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
 11    let b = (hex & 0xFF) as f32 / 255.0;
 12    Rgba { r, g, b, a: 1.0 }.into()
 13}
 14
 15#[derive(Clone, Copy, Default, Debug)]
 16pub struct Rgba {
 17    pub r: f32,
 18    pub g: f32,
 19    pub b: f32,
 20    pub a: f32,
 21}
 22
 23impl Rgba {
 24    pub fn blend(&self, other: Rgba) -> Self {
 25        if other.a >= 1.0 {
 26            return other;
 27        } else if other.a <= 0.0 {
 28            return *self;
 29        } else {
 30            return Rgba {
 31                r: (self.r * (1.0 - other.a)) + (other.r * other.a),
 32                g: (self.g * (1.0 - other.a)) + (other.g * other.a),
 33                b: (self.b * (1.0 - other.a)) + (other.b * other.a),
 34                a: self.a,
 35            };
 36        }
 37    }
 38}
 39
 40struct RgbaVisitor;
 41
 42impl<'de> Visitor<'de> for RgbaVisitor {
 43    type Value = Rgba;
 44
 45    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
 46        formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
 47    }
 48
 49    fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
 50        if value.len() == 7 || value.len() == 9 {
 51            let r = u8::from_str_radix(&value[1..3], 16).unwrap() as f32 / 255.0;
 52            let g = u8::from_str_radix(&value[3..5], 16).unwrap() as f32 / 255.0;
 53            let b = u8::from_str_radix(&value[5..7], 16).unwrap() as f32 / 255.0;
 54            let a = if value.len() == 9 {
 55                u8::from_str_radix(&value[7..9], 16).unwrap() as f32 / 255.0
 56            } else {
 57                1.0
 58            };
 59            Ok(Rgba { r, g, b, a })
 60        } else {
 61            Err(E::custom(
 62                "Bad format for RGBA. Expected #rrggbb or #rrggbbaa.",
 63            ))
 64        }
 65    }
 66}
 67
 68impl<'de> Deserialize<'de> for Rgba {
 69    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
 70        deserializer.deserialize_str(RgbaVisitor)
 71    }
 72}
 73
 74impl From<Hsla> for Rgba {
 75    fn from(color: Hsla) -> Self {
 76        let h = color.h;
 77        let s = color.s;
 78        let l = color.l;
 79
 80        let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
 81        let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
 82        let m = l - c / 2.0;
 83        let cm = c + m;
 84        let xm = x + m;
 85
 86        let (r, g, b) = match (h * 6.0).floor() as i32 {
 87            0 | 6 => (cm, xm, m),
 88            1 => (xm, cm, m),
 89            2 => (m, cm, xm),
 90            3 => (m, xm, cm),
 91            4 => (xm, m, cm),
 92            _ => (cm, m, xm),
 93        };
 94
 95        Rgba {
 96            r,
 97            g,
 98            b,
 99            a: color.a,
100        }
101    }
102}
103
104impl TryFrom<&'_ str> for Rgba {
105    type Error = ParseIntError;
106
107    fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
108        let r = u8::from_str_radix(&value[1..3], 16)? as f32 / 255.0;
109        let g = u8::from_str_radix(&value[3..5], 16)? as f32 / 255.0;
110        let b = u8::from_str_radix(&value[5..7], 16)? as f32 / 255.0;
111        let a = if value.len() > 7 {
112            u8::from_str_radix(&value[7..9], 16)? as f32 / 255.0
113        } else {
114            1.0
115        };
116
117        Ok(Rgba { r, g, b, a })
118    }
119}
120
121#[derive(Default, Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
122#[repr(C)]
123pub struct Hsla {
124    pub h: f32,
125    pub s: f32,
126    pub l: f32,
127    pub a: f32,
128}
129
130impl Eq for Hsla {}
131
132pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
133    Hsla {
134        h: h.clamp(0., 1.),
135        s: s.clamp(0., 1.),
136        l: l.clamp(0., 1.),
137        a: a.clamp(0., 1.),
138    }
139}
140
141pub fn black() -> Hsla {
142    Hsla {
143        h: 0.,
144        s: 0.,
145        l: 0.,
146        a: 1.,
147    }
148}
149
150impl Hsla {
151    /// Returns true if the HSLA color is fully transparent, false otherwise.
152    pub fn is_transparent(&self) -> bool {
153        self.a == 0.0
154    }
155
156    /// Blends `other` on top of `self` based on `other`'s alpha value. The resulting color is a combination of `self`'s and `other`'s colors.
157    ///
158    /// If `other`'s alpha value is 1.0 or greater, `other` color is fully opaque, thus `other` is returned as the output color.
159    /// If `other`'s alpha value is 0.0 or less, `other` color is fully transparent, thus `self` is returned as the output color.
160    /// Else, the output color is calculated as a blend of `self` and `other` based on their weighted alpha values.
161    ///
162    /// Assumptions:
163    /// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent.
164    /// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s  alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing it's own alpha value.
165    /// - RGB color components are contained in the range [0, 1].
166    /// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined.
167    pub fn blend(self, other: Hsla) -> Hsla {
168        let alpha = other.a;
169
170        if alpha >= 1.0 {
171            return other;
172        } else if alpha <= 0.0 {
173            return self;
174        } else {
175            let converted_self = Rgba::from(self);
176            let converted_other = Rgba::from(other);
177            let blended_rgb = converted_self.blend(converted_other);
178            return Hsla::from(blended_rgb);
179        }
180    }
181
182    /// Fade out the color by a given factor. This factor should be between 0.0 and 1.0.
183    /// Where 0.0 will leave the color unchanged, and 1.0 will completely fade out the color.
184    pub fn fade_out(&mut self, factor: f32) {
185        self.a *= 1.0 - factor.clamp(0., 1.);
186    }
187}
188
189impl From<Rgba> for Hsla {
190    fn from(color: Rgba) -> Self {
191        let r = color.r;
192        let g = color.g;
193        let b = color.b;
194
195        let max = r.max(g.max(b));
196        let min = r.min(g.min(b));
197        let delta = max - min;
198
199        let l = (max + min) / 2.0;
200        let s = if l == 0.0 || l == 1.0 {
201            0.0
202        } else if l < 0.5 {
203            delta / (2.0 * l)
204        } else {
205            delta / (2.0 - 2.0 * l)
206        };
207
208        let h = if delta == 0.0 {
209            0.0
210        } else if max == r {
211            ((g - b) / delta).rem_euclid(6.0) / 6.0
212        } else if max == g {
213            ((b - r) / delta + 2.0) / 6.0
214        } else {
215            ((r - g) / delta + 4.0) / 6.0
216        };
217
218        Hsla {
219            h,
220            s,
221            l,
222            a: color.a,
223        }
224    }
225}
226
227impl<'de> Deserialize<'de> for Hsla {
228    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
229    where
230        D: Deserializer<'de>,
231    {
232        // First, deserialize it into Rgba
233        let rgba = Rgba::deserialize(deserializer)?;
234
235        // Then, use the From<Rgba> for Hsla implementation to convert it
236        Ok(Hsla::from(rgba))
237    }
238}