1use gpui::{Hsla, hsla};
5
6pub fn neutral(lightness: f32) -> Hsla {
10 let [v, _, _] = oklch_to_srgb(lightness, 0.0, 0.0);
11 hsla(0.0, 0.0, v, 1.0)
12}
13
14pub fn grey(value: u8) -> Hsla {
17 hsla(0.0, 0.0, value as f32 / 255.0, 1.0)
18}
19
20pub fn oklch(l: f32, c: f32, h_deg: f32) -> Hsla {
22 let [r, g, b] = oklch_to_srgb(l, c, h_deg);
23 let (h, s, l) = rgb_to_hsl(r, g, b);
24 hsla(h, s, l, 1.0)
25}
26
27pub fn oklch_to_srgb(l: f32, c: f32, h_deg: f32) -> [f32; 3] {
30 let [r, g, b] = oklch_to_linear(l, c, h_deg);
31 [gamma_encode(r), gamma_encode(g), gamma_encode(b)]
32}
33
34fn oklch_to_linear(l: f32, c: f32, h_deg: f32) -> [f32; 3] {
37 let h = h_deg.to_radians();
38 let a = c * h.cos();
39 let b = c * h.sin();
40
41 let l_ = l + 0.396_337_78 * a + 0.215_803_76 * b;
43 let m_ = l - 0.105_561_346 * a - 0.063_854_17 * b;
44 let s_ = l - 0.089_484_18 * a - 1.291_485_5 * b;
45 let (l3, m3, s3) = (l_ * l_ * l_, m_ * m_ * m_, s_ * s_ * s_);
46
47 let r = 4.076_741_7 * l3 - 3.307_711_6 * m3 + 0.230_969_93 * s3;
49 let g = -1.268_438 * l3 + 2.609_757_4 * m3 - 0.341_319_4 * s3;
50 let b = -0.004_196_086_3 * l3 - 0.703_418_6 * m3 + 1.707_614_7 * s3;
51
52 [r, g, b]
53}
54
55fn gamma_encode(x: f32) -> f32 {
56 let x = x.clamp(0.0, 1.0);
57 if x <= 0.003_130_8 {
58 12.92 * x
59 } else {
60 1.055 * x.powf(1.0 / 2.4) - 0.055
61 }
62}
63
64fn gamma_decode(x: f32) -> f32 {
65 let x = x.clamp(0.0, 1.0);
66 if x <= 0.040_449_936 {
67 x / 12.92
68 } else {
69 ((x + 0.055) / 1.055).powf(2.4)
70 }
71}
72
73pub fn lightness(color: Hsla) -> f32 {
79 gamma_decode(color.l).cbrt()
80}
81
82fn fit_chroma(l: f32, chroma: f32, hue: f32) -> f32 {
90 let fits = |c: f32| {
91 oklch_to_linear(l, c, hue)
92 .iter()
93 .all(|x| (-1e-4..=1.0 + 1e-4).contains(x))
94 };
95 if fits(chroma) {
96 return chroma;
97 }
98 let (mut lo, mut hi) = (0.0, chroma);
99 for _ in 0..24 {
100 let mid = 0.5 * (lo + hi);
101 if fits(mid) { lo = mid } else { hi = mid }
102 }
103 lo
104}
105
106pub fn tint(color: Hsla, hue: f32, chroma: f32) -> Hsla {
109 if chroma <= 0.0 {
110 return color;
111 }
112 let l = lightness(color);
113 let mut out = oklch(l, fit_chroma(l, chroma, hue), hue);
114 out.a = color.a;
115 out
116}
117
118pub fn rgb_to_hsl(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
120 let max = r.max(g).max(b);
121 let min = r.min(g).min(b);
122 let l = (max + min) / 2.0;
123 let delta = max - min;
124 if delta < f32::EPSILON {
125 return (0.0, 0.0, l);
126 }
127 let s = if l > 0.5 {
128 delta / (2.0 - max - min)
129 } else {
130 delta / (max + min)
131 };
132 let h = if (max - r).abs() < f32::EPSILON {
133 ((g - b) / delta).rem_euclid(6.0)
134 } else if (max - g).abs() < f32::EPSILON {
135 (b - r) / delta + 2.0
136 } else {
137 (r - g) / delta + 4.0
138 } / 6.0;
139 (h, s, l)
140}
141
142pub fn hsl_to_rgb(h: f32, s: f32, l: f32) -> [f32; 3] {
144 if s <= f32::EPSILON {
145 return [l, l, l];
146 }
147 let q = if l < 0.5 {
148 l * (1.0 + s)
149 } else {
150 l + s - l * s
151 };
152 let p = 2.0 * l - q;
153 let hue = |mut t: f32| {
154 t = t.rem_euclid(1.0);
155 if t < 1.0 / 6.0 {
156 p + (q - p) * 6.0 * t
157 } else if t < 0.5 {
158 q
159 } else if t < 2.0 / 3.0 {
160 p + (q - p) * (2.0 / 3.0 - t) * 6.0
161 } else {
162 p
163 }
164 };
165 [hue(h + 1.0 / 3.0), hue(h), hue(h - 1.0 / 3.0)]
166}
167
168pub fn relative_luminance(color: Hsla) -> f32 {
170 let lin = |c: f32| {
171 if c <= 0.040_45 {
172 c / 12.92
173 } else {
174 ((c + 0.055) / 1.055).powf(2.4)
175 }
176 };
177 let [r, g, b] = hsl_to_rgb(color.h, color.s, color.l);
178 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b)
179}
180
181pub fn contrast_ratio(a: Hsla, b: Hsla) -> f32 {
186 let (la, lb) = (relative_luminance(a), relative_luminance(b));
187 let (hi, lo) = if la >= lb { (la, lb) } else { (lb, la) };
188 (hi + 0.05) / (lo + 0.05)
189}
190
191pub fn flatten(fg: Hsla, bg: Hsla) -> Hsla {
194 let a = fg.a.clamp(0.0, 1.0);
195 let [fr, fg_, fb] = hsl_to_rgb(fg.h, fg.s, fg.l);
196 let [br, bg_, bb] = hsl_to_rgb(bg.h, bg.s, bg.l);
197 let (h, s, l) = rgb_to_hsl(
198 fr * a + br * (1.0 - a),
199 fg_ * a + bg_ * (1.0 - a),
200 fb * a + bb * (1.0 - a),
201 );
202 hsla(h, s, l, 1.0)
203}
204
205pub fn mix(a: Hsla, b: Hsla, t: f32) -> Hsla {
207 let t = t.clamp(0.0, 1.0);
208 let lerp = |x: f32, y: f32| x + (y - x) * t;
209 hsla(
212 lerp(a.h, b.h),
213 lerp(a.s, b.s),
214 lerp(a.l, b.l),
215 lerp(a.a, b.a),
216 )
217}