mirror of
https://codeberg.org/icewind/contrast.git
synced 2026-06-03 18:04:09 +02:00
initial implementation
This commit is contained in:
parent
e96b04e3ea
commit
767f8ca75e
2 changed files with 66 additions and 4 deletions
|
|
@ -4,6 +4,6 @@ version = "0.1.0"
|
||||||
authors = ["Robin Appelman <robin@icewind.nl>"]
|
authors = ["Robin Appelman <robin@icewind.nl>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
rgb = "0.8"
|
||||||
|
num-traits = "0.2"
|
||||||
66
src/lib.rs
66
src/lib.rs
|
|
@ -1,7 +1,69 @@
|
||||||
|
use num_traits::{cast, Bounded, Float, NumCast};
|
||||||
|
use rgb::RGB;
|
||||||
|
use std::ops::Div;
|
||||||
|
|
||||||
|
/// Convert sRGB color to perceptual luminance
|
||||||
|
///
|
||||||
|
/// See [Wikipedia: Converting color to Grayscale](https://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale)
|
||||||
|
pub fn luminance<T: Bounded + NumCast, F: Float + NumCast + Div>(color: RGB<T>) -> F {
|
||||||
|
let channels: [T; 3] = color.into();
|
||||||
|
let [r, g, b] = channels;
|
||||||
|
scale_channel::<T, F>(r) * cast(0.2126).unwrap()
|
||||||
|
+ scale_channel::<T, F>(g) * cast(0.7152).unwrap()
|
||||||
|
+ scale_channel::<T, F>(b) * cast(0.0722).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scale_channel<T: Bounded + NumCast, F: Float + NumCast + Div>(channel: T) -> F {
|
||||||
|
let float: F = cast(channel).expect("Failed to convert rgb channel into float");
|
||||||
|
let relative =
|
||||||
|
float / cast(T::max_value()).expect("Max value to rgb channel doesn't fit into float");
|
||||||
|
if relative < cast(0.03928).unwrap() {
|
||||||
|
relative / cast(12.92).unwrap()
|
||||||
|
} else {
|
||||||
|
F::powf(
|
||||||
|
(relative + cast(0.055).unwrap()) / cast(1.055).unwrap(),
|
||||||
|
cast(2.4).unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate contrast between two colors as specified by [WCAG 2](http://www.w3.org/TR/WCAG20#contrast-ratiodef)
|
||||||
|
pub fn contrast<T: Bounded + NumCast, F: Float + NumCast + Div>(a: RGB<T>, b: RGB<T>) -> F {
|
||||||
|
let luminance_a: F = luminance::<T, F>(a) + cast::<_, F>(0.05).unwrap();
|
||||||
|
let luminance_b: F = luminance::<T, F>(b) + cast::<_, F>(0.05).unwrap();
|
||||||
|
|
||||||
|
if luminance_a > luminance_b {
|
||||||
|
luminance_a / luminance_b
|
||||||
|
} else {
|
||||||
|
luminance_b / luminance_a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use rgb::RGB8;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_works() {
|
fn test_contrast() {
|
||||||
assert_eq!(2 + 2, 4);
|
let white = RGB8::from([255, 255, 255]);
|
||||||
|
let black = RGB8::from([0, 0, 0]);
|
||||||
|
let red = RGB8::from([255, 0, 0]);
|
||||||
|
let green = RGB8::from([0, 255, 0]);
|
||||||
|
let blue = RGB8::from([0, 0, 255]);
|
||||||
|
let yellow = RGB8::from([255, 255, 0]);
|
||||||
|
|
||||||
|
assert_eq!(luminance::<_, f32>(white), 1.0);
|
||||||
|
assert_eq!(luminance::<_, f32>(black), 0.0);
|
||||||
|
|
||||||
|
assert_eq!(contrast::<_, f32>(white, white), 1.0);
|
||||||
|
assert_eq!(contrast::<_, f32>(white, black), 20.999998);
|
||||||
|
assert_eq!(contrast::<_, f32>(black, white), 20.999998);
|
||||||
|
|
||||||
|
assert_eq!(contrast::<_, f32>(white, red), 3.9984765);
|
||||||
|
assert_eq!(contrast::<_, f32>(white, green), 1.3721902);
|
||||||
|
assert_eq!(contrast::<_, f32>(white, blue), 8.592471);
|
||||||
|
assert_eq!(contrast::<_, f32>(white, yellow), 1.0738392);
|
||||||
|
assert_eq!(contrast::<_, f32>(black, yellow), 19.556);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue