Skip to main content

contrast/
lib.rs

1use num_traits::{cast, Bounded, Float, NumCast};
2use rgb::RGB;
3use std::ops::Div;
4
5/// Convert sRGB color to perceptual luminance
6///
7/// See [Wikipedia: Converting color to Grayscale](https://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale)
8pub fn luminance<T: Bounded + NumCast, F: Float + NumCast + Div>(color: RGB<T>) -> F {
9    let channels: [T; 3] = color.into();
10    let [r, g, b] = channels;
11    scale_channel::<T, F>(r) * cast(0.2126).unwrap()
12        + scale_channel::<T, F>(g) * cast(0.7152).unwrap()
13        + scale_channel::<T, F>(b) * cast(0.0722).unwrap()
14}
15
16fn scale_channel<T: Bounded + NumCast, F: Float + NumCast + Div>(channel: T) -> F {
17    let float: F = cast(channel).expect("Failed to convert rgb channel into float");
18    let relative =
19        float / cast(T::max_value()).expect("Max value to rgb channel doesn't fit into float");
20    if relative < cast(0.03928).unwrap() {
21        relative / cast(12.92).unwrap()
22    } else {
23        F::powf(
24            (relative + cast(0.055).unwrap()) / cast(1.055).unwrap(),
25            cast(2.4).unwrap(),
26        )
27    }
28}
29
30/// Calculate contrast between two colors as specified by [WCAG 2](http://www.w3.org/TR/WCAG20#contrast-ratiodef)
31///
32/// # Usage
33///
34/// ```rust
35/// # use contrast::contrast;
36/// # use rgb::RGB8;
37/// #
38/// let contrast: f32 = contrast(RGB8::from([255, 255, 255]), RGB8::from([255, 255, 0]));
39/// assert_eq!(contrast, 1.0738392);
40/// ```
41pub fn contrast<T: Bounded + NumCast, F: Float + NumCast + Div>(a: RGB<T>, b: RGB<T>) -> F {
42    let luminance_a: F = luminance::<T, F>(a) + cast::<_, F>(0.05).unwrap();
43    let luminance_b: F = luminance::<T, F>(b) + cast::<_, F>(0.05).unwrap();
44
45    if luminance_a > luminance_b {
46        luminance_a / luminance_b
47    } else {
48        luminance_b / luminance_a
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use rgb::RGB8;
56
57    #[test]
58    fn test_contrast() {
59        let white = RGB8::from([255, 255, 255]);
60        let black = RGB8::from([0, 0, 0]);
61        let red = RGB8::from([255, 0, 0]);
62        let green = RGB8::from([0, 255, 0]);
63        let blue = RGB8::from([0, 0, 255]);
64        let yellow = RGB8::from([255, 255, 0]);
65
66        assert_eq!(luminance::<_, f32>(white), 1.0);
67        assert_eq!(luminance::<_, f32>(black), 0.0);
68
69        assert_eq!(contrast::<_, f32>(white, white), 1.0);
70        assert_eq!(contrast::<_, f32>(white, black), 20.999998);
71        assert_eq!(contrast::<_, f32>(black, white), 20.999998);
72
73        assert_eq!(contrast::<_, f32>(white, red), 3.9984765);
74        assert_eq!(contrast::<_, f32>(white, green), 1.3721902);
75        assert_eq!(contrast::<_, f32>(white, blue), 8.592471);
76        assert_eq!(contrast::<_, f32>(white, yellow), 1.0738392);
77        assert_eq!(contrast::<_, f32>(black, yellow), 19.556);
78    }
79}