1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
use core::ops::{Add, Div}; use crate::component::Component; use crate::{from_f64, FromF64}; /// A trait for calculating relative contrast between two colors. /// /// W3C's Web Content Accessibility Guidelines (WCAG) 2.1 suggest a method /// to calculate accessible contrast ratios of text and background colors for /// those with low vision or color deficiencies, and for contrast of colors used /// in user interface graphics objects. /// /// These criteria are recommendations, not hard and fast rules. Most /// importantly, look at the colors in action and make sure they're clear and /// comfortable to read. A pair of colors may pass contrast guidelines but still /// be uncomfortable to look at. Favor readability over only satisfying the /// contrast ratio metric. It is recommended to verify the contrast ratio /// in the output format of the colors and not to assume the contrast ratio /// remains exactly the same across color formats. The following example checks /// the contrast ratio of two colors in RGB format. /// /// ```rust /// use std::str::FromStr; /// use palette::{Srgb, RelativeContrast}; /// # fn main() -> Result<(), palette::rgb::FromHexError> { /// /// // the rustdoc "DARK" theme background and text colors /// let my_background_rgb: Srgb<f32> = Srgb::from(0x353535).into_format(); /// let my_foreground_rgb = Srgb::from_str("#ddd")?.into_format(); /// /// assert!(my_background_rgb.has_enhanced_contrast_text(&my_foreground_rgb)); /// # Ok(()) /// # } /// ``` /// /// The possible range of contrast ratios is from 1:1 to 21:1. There is a /// Success Criterion for Contrast (Minimum) and a Success Criterion for /// Contrast (Enhanced), SC 1.4.3 and SC 1.4.6 respectively, which are concerned /// with text and images of text. SC 1.4.11 is a Success Criterion for "non-text /// contrast" such as user interface components and other graphics. The relative /// contrast is calculated by `(L1 + 0.05) / (L2 + 0.05)`, where `L1` is the /// luminance of the brighter color and `L2` is the luminance of the darker /// color both in sRGB linear space. A higher contrast ratio is generally /// desireable. /// /// For more details, visit the following links: /// /// [Success Criterion 1.4.3 Contrast (Minimum) (Level AA)](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum) /// /// [Success Criterion 1.4.6 Contrast (Enhanced) (Level AAA)](https://www.w3.org/WAI/WCAG21/Understanding/contrast-enhanced) /// /// [Success Criterion 1.4.11 Non-text Contrast (Level AA)](https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast.html) #[doc(alias = "wcag")] pub trait RelativeContrast { /// The type of the contrast ratio. type Scalar: FromF64 + PartialOrd; /// Calculate the contrast ratio between two colors. fn get_contrast_ratio(&self, other: &Self) -> Self::Scalar; /// Verify the contrast between two colors satisfies SC 1.4.3. Contrast /// is at least 4.5:1 (Level AA). fn has_min_contrast_text(&self, other: &Self) -> bool { self.get_contrast_ratio(other) >= from_f64(4.5) } /// Verify the contrast between two colors satisfies SC 1.4.3 for large /// text. Contrast is at least 3:1 (Level AA). fn has_min_contrast_large_text(&self, other: &Self) -> bool { self.get_contrast_ratio(other) >= from_f64(3.0) } /// Verify the contrast between two colors satisfies SC 1.4.6. Contrast /// is at least 7:1 (Level AAA). fn has_enhanced_contrast_text(&self, other: &Self) -> bool { self.get_contrast_ratio(other) >= from_f64(7.0) } /// Verify the contrast between two colors satisfies SC 1.4.6 for large /// text. Contrast is at least 4.5:1 (Level AAA). fn has_enhanced_contrast_large_text(&self, other: &Self) -> bool { self.has_min_contrast_text(other) } /// Verify the contrast between two colors satisfies SC 1.4.11 for graphical /// objects. Contrast is at least 3:1 (Level AA). fn has_min_contrast_graphics(&self, other: &Self) -> bool { self.has_min_contrast_large_text(other) } } /// Calculate the ratio between two `luma` values. pub fn contrast_ratio<T>(luma1: T, luma2: T) -> T where T: Component + FromF64 + Add<Output = T> + Div<Output = T>, { if luma1 > luma2 { (luma1 + from_f64(0.05)) / (luma2 + from_f64(0.05)) } else { (luma2 + from_f64(0.05)) / (luma1 + from_f64(0.05)) } } #[cfg(test)] mod test { use core::str::FromStr; use crate::RelativeContrast; use crate::Srgb; #[test] fn relative_contrast() { let white = Srgb::new(1.0, 1.0, 1.0); let black = Srgb::new(0.0, 0.0, 0.0); assert_relative_eq!(white.get_contrast_ratio(&white), 1.0); assert_relative_eq!(white.get_contrast_ratio(&black), 21.0); assert_relative_eq!( white.get_contrast_ratio(&black), black.get_contrast_ratio(&white) ); let c1 = Srgb::from_str("#600").unwrap().into_format(); assert_relative_eq!(c1.get_contrast_ratio(&white), 13.41, epsilon = 0.01); assert_relative_eq!(c1.get_contrast_ratio(&black), 1.56, epsilon = 0.01); assert!(c1.has_min_contrast_text(&white)); assert!(c1.has_min_contrast_large_text(&white)); assert!(c1.has_enhanced_contrast_text(&white)); assert!(c1.has_enhanced_contrast_large_text(&white)); assert!(c1.has_min_contrast_graphics(&white)); assert!(c1.has_min_contrast_text(&black) == false); assert!(c1.has_min_contrast_large_text(&black) == false); assert!(c1.has_enhanced_contrast_text(&black) == false); assert!(c1.has_enhanced_contrast_large_text(&black) == false); assert!(c1.has_min_contrast_graphics(&black) == false); let c1 = Srgb::from_str("#066").unwrap().into_format(); assert_relative_eq!(c1.get_contrast_ratio(&white), 6.79, epsilon = 0.01); assert_relative_eq!(c1.get_contrast_ratio(&black), 3.09, epsilon = 0.01); let c1 = Srgb::from_str("#9f9").unwrap().into_format(); assert_relative_eq!(c1.get_contrast_ratio(&white), 1.22, epsilon = 0.01); assert_relative_eq!(c1.get_contrast_ratio(&black), 17.11, epsilon = 0.01); } }