Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:45:25.908Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

util.rs

149 lines · 4.8 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.