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}