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<C: From<Rgba>>(hex: u32) -> C {
  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)]
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 {}
131unsafe impl Zeroable for Hsla {}
132unsafe impl Pod for Hsla {}
133
134pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
135    Hsla {
136        h: h.clamp(0., 1.),
137        s: s.clamp(0., 1.),
138        l: l.clamp(0., 1.),
139        a: a.clamp(0., 1.),
140    }
141}
142
143pub fn black() -> Hsla {
144    Hsla {
145        h: 0.,
146        s: 0.,
147        l: 0.,
148        a: 1.,
149    }
150}
151
152impl Hsla {
153    /// Returns true if the HSLA color is fully transparent, false otherwise.
154    pub fn is_transparent(&self) -> bool {
155        self.a == 0.0
156    }
157
158    /// 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.
159    ///
160    /// If `other`'s alpha value is 1.0 or greater, `other` color is fully opaque, thus `other` is returned as the output color.
161    /// If `other`'s alpha value is 0.0 or less, `other` color is fully transparent, thus `self` is returned as the output color.
162    /// Else, the output color is calculated as a blend of `self` and `other` based on their weighted alpha values.
163    ///
164    /// Assumptions:
165    /// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent.
166    /// - 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.
167    /// - RGB color components are contained in the range [0, 1].
168    /// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined.
169    pub fn blend(self, other: Hsla) -> Hsla {
170        let alpha = other.a;
171
172        if alpha >= 1.0 {
173            return other;
174        } else if alpha <= 0.0 {
175            return self;
176        } else {
177            let converted_self = Rgba::from(self);
178            let converted_other = Rgba::from(other);
179            let blended_rgb = converted_self.blend(converted_other);
180            return Hsla::from(blended_rgb);
181        }
182    }
183
184    /// Fade out the color by a given factor. This factor should be between 0.0 and 1.0.
185    /// Where 0.0 will leave the color unchanged, and 1.0 will completely fade out the color.
186    pub fn fade_out(&mut self, factor: f32) {
187        self.a *= 1.0 - factor.clamp(0., 1.);
188    }
189}
190
191impl From<Rgba> for Hsla {
192    fn from(color: Rgba) -> Self {
193        let r = color.r;
194        let g = color.g;
195        let b = color.b;
196
197        let max = r.max(g.max(b));
198        let min = r.min(g.min(b));
199        let delta = max - min;
200
201        let l = (max + min) / 2.0;
202        let s = if l == 0.0 || l == 1.0 {
203            0.0
204        } else if l < 0.5 {
205            delta / (2.0 * l)
206        } else {
207            delta / (2.0 - 2.0 * l)
208        };
209
210        let h = if delta == 0.0 {
211            0.0
212        } else if max == r {
213            ((g - b) / delta).rem_euclid(6.0) / 6.0
214        } else if max == g {
215            ((b - r) / delta + 2.0) / 6.0
216        } else {
217            ((r - g) / delta + 4.0) / 6.0
218        };
219
220        Hsla {
221            h,
222            s,
223            l,
224            a: color.a,
225        }
226    }
227}
228
229impl<'de> Deserialize<'de> for Hsla {
230    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
231    where
232        D: Deserializer<'de>,
233    {
234        // First, deserialize it into Rgba
235        let rgba = Rgba::deserialize(deserializer)?;
236
237        // Then, use the From<Rgba> for Hsla implementation to convert it
238        Ok(Hsla::from(rgba))
239    }
240}