color.rs

  1#![allow(dead_code)]
  2
  3use std::{num::ParseIntError, ops::Range};
  4
  5use smallvec::SmallVec;
  6
  7pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
  8    let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
  9    let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
 10    let b = (hex & 0xFF) as f32 / 255.0;
 11    Rgba { r, g, b, a: 1.0 }.into()
 12}
 13
 14#[derive(Clone, Copy, Default, Debug)]
 15pub struct Rgba {
 16    pub r: f32,
 17    pub g: f32,
 18    pub b: f32,
 19    pub a: f32,
 20}
 21
 22pub trait Lerp {
 23    fn lerp(&self, level: f32) -> Hsla;
 24}
 25
 26impl Lerp for Range<Hsla> {
 27    fn lerp(&self, level: f32) -> Hsla {
 28        let level = level.clamp(0., 1.);
 29        Hsla {
 30            h: self.start.h + (level * (self.end.h - self.start.h)),
 31            s: self.start.s + (level * (self.end.s - self.start.s)),
 32            l: self.start.l + (level * (self.end.l - self.start.l)),
 33            a: self.start.a + (level * (self.end.a - self.start.a)),
 34        }
 35    }
 36}
 37
 38impl From<gpui::color::Color> for Rgba {
 39    fn from(value: gpui::color::Color) -> Self {
 40        Self {
 41            r: value.0.r as f32 / 255.0,
 42            g: value.0.g as f32 / 255.0,
 43            b: value.0.b as f32 / 255.0,
 44            a: value.0.a as f32 / 255.0,
 45        }
 46    }
 47}
 48
 49impl From<Hsla> for Rgba {
 50    fn from(color: Hsla) -> Self {
 51        let h = color.h;
 52        let s = color.s;
 53        let l = color.l;
 54
 55        let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
 56        let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
 57        let m = l - c / 2.0;
 58        let cm = c + m;
 59        let xm = x + m;
 60
 61        let (r, g, b) = match (h * 6.0).floor() as i32 {
 62            0 | 6 => (cm, xm, m),
 63            1 => (xm, cm, m),
 64            2 => (m, cm, xm),
 65            3 => (m, xm, cm),
 66            4 => (xm, m, cm),
 67            _ => (cm, m, xm),
 68        };
 69
 70        Rgba {
 71            r,
 72            g,
 73            b,
 74            a: color.a,
 75        }
 76    }
 77}
 78
 79impl TryFrom<&'_ str> for Rgba {
 80    type Error = ParseIntError;
 81
 82    fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
 83        let r = u8::from_str_radix(&value[1..3], 16)? as f32 / 255.0;
 84        let g = u8::from_str_radix(&value[3..5], 16)? as f32 / 255.0;
 85        let b = u8::from_str_radix(&value[5..7], 16)? as f32 / 255.0;
 86        let a = if value.len() > 7 {
 87            u8::from_str_radix(&value[7..9], 16)? as f32 / 255.0
 88        } else {
 89            1.0
 90        };
 91
 92        Ok(Rgba { r, g, b, a })
 93    }
 94}
 95
 96impl Into<gpui::color::Color> for Rgba {
 97    fn into(self) -> gpui::color::Color {
 98        gpui::color::rgba(self.r, self.g, self.b, self.a)
 99    }
100}
101
102#[derive(Default, Copy, Clone, Debug, PartialEq)]
103pub struct Hsla {
104    pub h: f32,
105    pub s: f32,
106    pub l: f32,
107    pub a: f32,
108}
109
110pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
111    Hsla {
112        h: h.clamp(0., 1.),
113        s: s.clamp(0., 1.),
114        l: l.clamp(0., 1.),
115        a: a.clamp(0., 1.),
116    }
117}
118
119pub fn black() -> Hsla {
120    Hsla {
121        h: 0.,
122        s: 0.,
123        l: 0.,
124        a: 1.,
125    }
126}
127
128impl From<Rgba> for Hsla {
129    fn from(color: Rgba) -> Self {
130        let r = color.r;
131        let g = color.g;
132        let b = color.b;
133
134        let max = r.max(g.max(b));
135        let min = r.min(g.min(b));
136        let delta = max - min;
137
138        let l = (max + min) / 2.0;
139        let s = if l == 0.0 || l == 1.0 {
140            0.0
141        } else if l < 0.5 {
142            delta / (2.0 * l)
143        } else {
144            delta / (2.0 - 2.0 * l)
145        };
146
147        let h = if delta == 0.0 {
148            0.0
149        } else if max == r {
150            ((g - b) / delta).rem_euclid(6.0) / 6.0
151        } else if max == g {
152            ((b - r) / delta + 2.0) / 6.0
153        } else {
154            ((r - g) / delta + 4.0) / 6.0
155        };
156
157        Hsla {
158            h,
159            s,
160            l,
161            a: color.a,
162        }
163    }
164}
165
166impl Hsla {
167    /// Scales the saturation and lightness by the given values, clamping at 1.0.
168    pub fn scale_sl(mut self, s: f32, l: f32) -> Self {
169        self.s = (self.s * s).clamp(0., 1.);
170        self.l = (self.l * l).clamp(0., 1.);
171        self
172    }
173
174    /// Increases the saturation of the color by a certain amount, with a max
175    /// value of 1.0.
176    pub fn saturate(mut self, amount: f32) -> Self {
177        self.s += amount;
178        self.s = self.s.clamp(0.0, 1.0);
179        self
180    }
181
182    /// Decreases the saturation of the color by a certain amount, with a min
183    /// value of 0.0.
184    pub fn desaturate(mut self, amount: f32) -> Self {
185        self.s -= amount;
186        self.s = self.s.max(0.0);
187        if self.s < 0.0 {
188            self.s = 0.0;
189        }
190        self
191    }
192
193    /// Brightens the color by increasing the lightness by a certain amount,
194    /// with a max value of 1.0.
195    pub fn brighten(mut self, amount: f32) -> Self {
196        self.l += amount;
197        self.l = self.l.clamp(0.0, 1.0);
198        self
199    }
200
201    /// Darkens the color by decreasing the lightness by a certain amount,
202    /// with a max value of 0.0.
203    pub fn darken(mut self, amount: f32) -> Self {
204        self.l -= amount;
205        self.l = self.l.clamp(0.0, 1.0);
206        self
207    }
208}
209
210impl From<gpui::color::Color> for Hsla {
211    fn from(value: gpui::color::Color) -> Self {
212        Rgba::from(value).into()
213    }
214}
215
216impl Into<gpui::color::Color> for Hsla {
217    fn into(self) -> gpui::color::Color {
218        Rgba::from(self).into()
219    }
220}
221
222pub struct ColorScale {
223    colors: SmallVec<[Hsla; 2]>,
224    positions: SmallVec<[f32; 2]>,
225}
226
227pub fn scale<I, C>(colors: I) -> ColorScale
228where
229    I: IntoIterator<Item = C>,
230    C: Into<Hsla>,
231{
232    let mut scale = ColorScale {
233        colors: colors.into_iter().map(Into::into).collect(),
234        positions: SmallVec::new(),
235    };
236    let num_colors: f32 = scale.colors.len() as f32 - 1.0;
237    scale.positions = (0..scale.colors.len())
238        .map(|i| i as f32 / num_colors)
239        .collect();
240    scale
241}
242
243impl ColorScale {
244    fn at(&self, t: f32) -> Hsla {
245        // Ensure that the input is within [0.0, 1.0]
246        debug_assert!(
247            0.0 <= t && t <= 1.0,
248            "t value {} is out of range. Expected value in range 0.0 to 1.0",
249            t
250        );
251
252        let position = match self
253            .positions
254            .binary_search_by(|a| a.partial_cmp(&t).unwrap())
255        {
256            Ok(index) | Err(index) => index,
257        };
258        let lower_bound = position.saturating_sub(1);
259        let upper_bound = position.min(self.colors.len() - 1);
260        let lower_color = &self.colors[lower_bound];
261        let upper_color = &self.colors[upper_bound];
262
263        match upper_bound.checked_sub(lower_bound) {
264            Some(0) | None => *lower_color,
265            Some(_) => {
266                let interval_t = (t - self.positions[lower_bound])
267                    / (self.positions[upper_bound] - self.positions[lower_bound]);
268                let h = lower_color.h + interval_t * (upper_color.h - lower_color.h);
269                let s = lower_color.s + interval_t * (upper_color.s - lower_color.s);
270                let l = lower_color.l + interval_t * (upper_color.l - lower_color.l);
271                let a = lower_color.a + interval_t * (upper_color.a - lower_color.a);
272                Hsla { h, s, l, a }
273            }
274        }
275    }
276}