color_contrast.rs

 1use gpui::{Hsla, Rgba};
 2
 3/// Calculates the contrast ratio between two colors according to WCAG 2.0 standards.
 4///
 5/// The formula used is:
 6/// (L1 + 0.05) / (L2 + 0.05), where L1 is the lighter of the two luminances and L2 is the darker.
 7///
 8/// Returns a float representing the contrast ratio. A higher value indicates more contrast.
 9/// The range of the returned value is 1 to 21 (commonly written as 1:1 to 21:1).
10pub fn calculate_contrast_ratio(fg: Hsla, bg: Hsla) -> f32 {
11    let l1 = relative_luminance(fg);
12    let l2 = relative_luminance(bg);
13
14    let (lighter, darker) = if l1 > l2 { (l1, l2) } else { (l2, l1) };
15
16    (lighter + 0.05) / (darker + 0.05)
17}
18
19/// Calculates the relative luminance of a color.
20///
21/// The relative luminance is the relative brightness of any point in a colorspace,
22/// normalized to 0 for darkest black and 1 for lightest white.
23fn relative_luminance(color: Hsla) -> f32 {
24    let rgba: Rgba = color.into();
25    let r = linearize(rgba.r);
26    let g = linearize(rgba.g);
27    let b = linearize(rgba.b);
28
29    0.2126 * r + 0.7152 * g + 0.0722 * b
30}
31
32/// Linearizes an RGB component.
33fn linearize(component: f32) -> f32 {
34    if component <= 0.03928 {
35        component / 12.92
36    } else {
37        ((component + 0.055) / 1.055).powf(2.4)
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use gpui::hsla;
44
45    use super::*;
46
47    // Test the contrast ratio formula with some common color combinations to
48    // prevent regressions in either the color conversions or the formula itself.
49    #[test]
50    fn test_contrast_ratio_formula() {
51        // White on Black (should be close to 21:1)
52        let white = hsla(0.0, 0.0, 1.0, 1.0);
53        let black = hsla(0.0, 0.0, 0.0, 1.0);
54        assert!((calculate_contrast_ratio(white, black) - 21.0).abs() < 0.1);
55
56        // Black on White (should be close to 21:1)
57        assert!((calculate_contrast_ratio(black, white) - 21.0).abs() < 0.1);
58
59        // Mid-gray on Black (should be close to 5.32:1)
60        let mid_gray = hsla(0.0, 0.0, 0.5, 1.0);
61        assert!((calculate_contrast_ratio(mid_gray, black) - 5.32).abs() < 0.1);
62
63        // White on Mid-gray (should be close to 3.95:1)
64        assert!((calculate_contrast_ratio(white, mid_gray) - 3.95).abs() < 0.1);
65
66        // Same color (should be 1:1)
67        let red = hsla(0.0, 1.0, 0.5, 1.0);
68        assert!((calculate_contrast_ratio(red, red) - 1.0).abs() < 0.01);
69    }
70}