Skip to repository content149 lines · 4.8 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T03:35:55.229Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
util.rs
1use gpui::{Hsla, Rgba};
2
3/// Produces a readable text color for a given background, subtly tinted by the
4/// background's own hue using the OKLCH color space.
5///
6/// The result keeps ~15% of the background's chroma so the text feels
7/// harmonious with its surroundings rather than a flat black or white.
8/// Lightness is set to ensure readable contrast against the background.
9pub fn text_color_for_background(background: Hsla) -> Hsla {
10 let rgba = Rgba::from(background);
11 let r_lin = srgb_to_linear(rgba.r);
12 let g_lin = srgb_to_linear(rgba.g);
13 let b_lin = srgb_to_linear(rgba.b);
14
15 let (_, ok_a, ok_b) = linear_rgb_to_oklab(r_lin, g_lin, b_lin);
16 let chroma = (ok_a * ok_a + ok_b * ok_b).sqrt();
17 let hue = ok_b.atan2(ok_a);
18
19 let bg_luminance = relative_luminance(rgba);
20 let text_l = if bg_luminance > 0.18 { 0.18 } else { 0.96 };
21 let text_c = chroma * 0.15;
22
23 let build = |c: f32| -> Rgba {
24 let (tr, tg, tb) = oklab_to_linear_rgb(text_l, c * hue.cos(), c * hue.sin());
25 Rgba {
26 r: linear_to_srgb(tr.clamp(0.0, 1.0)),
27 g: linear_to_srgb(tg.clamp(0.0, 1.0)),
28 b: linear_to_srgb(tb.clamp(0.0, 1.0)),
29 a: 1.0,
30 }
31 };
32
33 let meets_contrast =
34 |fg: Rgba| contrast_ratio_between(bg_luminance, relative_luminance(fg)) >= 4.5;
35
36 let candidate = build(text_c);
37 let result = if meets_contrast(candidate) {
38 candidate
39 } else {
40 // Binary search for the maximum chroma that still meets 4.5:1.
41 let mut lo = 0.0_f32;
42 let mut hi = text_c;
43 for _ in 0..16 {
44 let mid = (lo + hi) * 0.5;
45 if meets_contrast(build(mid)) {
46 lo = mid;
47 } else {
48 hi = mid;
49 }
50 }
51 let best = build(lo);
52 // Floating-point precision can leave the binary search result just
53 // below the 4.5:1 threshold. Fall back to pure black or white.
54 if meets_contrast(best) {
55 best
56 } else if bg_luminance > 0.18 {
57 Rgba {
58 r: 0.0,
59 g: 0.0,
60 b: 0.0,
61 a: 1.0,
62 }
63 } else {
64 Rgba {
65 r: 1.0,
66 g: 1.0,
67 b: 1.0,
68 a: 1.0,
69 }
70 }
71 };
72 Hsla::from(result)
73}
74
75fn srgb_to_linear(c: f32) -> f32 {
76 if c <= 0.04045 {
77 c / 12.92
78 } else {
79 ((c + 0.055) / 1.055).powf(2.4)
80 }
81}
82
83fn linear_to_srgb(c: f32) -> f32 {
84 if c <= 0.0031308 {
85 c * 12.92
86 } else {
87 1.055 * c.powf(1.0 / 2.4) - 0.055
88 }
89}
90
91fn linear_rgb_to_oklab(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
92 let l = (0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b).cbrt();
93 let m = (0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b).cbrt();
94 let s = (0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b).cbrt();
95 (
96 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,
97 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,
98 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s,
99 )
100}
101
102fn oklab_to_linear_rgb(l: f32, a: f32, b: f32) -> (f32, f32, f32) {
103 let l_ = l + 0.3963377774 * a + 0.2158037573 * b;
104 let m_ = l - 0.1055613458 * a - 0.0638541728 * b;
105 let s_ = l - 0.0894841775 * a - 1.2914855480 * b;
106 (
107 4.0767416621 * l_ * l_ * l_ - 3.3077115913 * m_ * m_ * m_ + 0.2309699292 * s_ * s_ * s_,
108 -1.2684380046 * l_ * l_ * l_ + 2.6097574011 * m_ * m_ * m_ - 0.3413193965 * s_ * s_ * s_,
109 -0.0041960863 * l_ * l_ * l_ - 0.7034186147 * m_ * m_ * m_ + 1.7076147010 * s_ * s_ * s_,
110 )
111}
112
113fn relative_luminance(c: Rgba) -> f32 {
114 0.2126 * srgb_to_linear(c.r) + 0.7152 * srgb_to_linear(c.g) + 0.0722 * srgb_to_linear(c.b)
115}
116
117fn contrast_ratio_between(luminance_a: f32, luminance_b: f32) -> f32 {
118 let (lighter, darker) = if luminance_a > luminance_b {
119 (luminance_a, luminance_b)
120 } else {
121 (luminance_b, luminance_a)
122 };
123 (lighter + 0.05) / (darker + 0.05)
124}
125
126#[cfg(test)]
127fn wcag_contrast_ratio(a: Rgba, b: Rgba) -> f32 {
128 contrast_ratio_between(relative_luminance(a), relative_luminance(b))
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use gpui::proptest::prelude::*;
135
136 #[gpui::property_test]
137 fn sufficient_contrast_for_any_opaque_background(
138 #[strategy = Hsla::opaque_strategy()] bg: Hsla,
139 ) -> Result<(), TestCaseError> {
140 let text = text_color_for_background(bg);
141 let ratio = wcag_contrast_ratio(Rgba::from(bg), Rgba::from(text));
142 prop_assert!(
143 ratio >= 4.5,
144 "WCAG AA contrast ratio {ratio:.2} < 4.5 for bg {bg:?} -> text {text:?}",
145 );
146 Ok(())
147 }
148}
149