use crate::core::{convert, ColorSpace};
use crate::Float;
#[inline]
pub(crate) fn scale_lightness(
space: ColorSpace,
coordinates: &[Float; 3],
factor: Float,
) -> [Float; 3] {
let [lr, c, h] = convert(space, ColorSpace::Oklrch, coordinates);
[factor * lr, c, h]
}
const SRGB_CONTRAST: &[Float; 3] = &[0.2126729, 0.7151522, 0.0721750];
#[allow(clippy::excessive_precision)]
const P3_CONTRAST: &[Float; 3] = &[0.2289829594805780, 0.6917492625852380, 0.0792677779341829];
fn to_contrast_luminance(coefficients: &[Float; 3], coordinates: &[Float; 3]) -> Float {
fn linearize(value: Float) -> Float {
let magnitude = value.abs();
magnitude.powf(2.4).copysign(value)
}
let [c1, c2, c3] = *coefficients;
let [r, g, b] = *coordinates;
linearize(r).mul_add(c1, linearize(g).mul_add(c2, linearize(b) * c3))
}
pub(crate) fn to_contrast_luminance_srgb(coordinates: &[Float; 3]) -> Float {
to_contrast_luminance(SRGB_CONTRAST, coordinates)
}
pub(crate) fn to_contrast_luminance_p3(coordinates: &[Float; 3]) -> Float {
to_contrast_luminance(P3_CONTRAST, coordinates)
}
const BLACK_THRESHOLD: Float = 0.022;
const BLACK_EXPONENT: Float = 1.414;
const INPUT_CLAMP: Float = 0.0005;
const SCALE: Float = 1.14;
const OFFSET: Float = 0.027;
const OUTPUT_CLAMP: Float = 0.1;
pub(crate) fn to_contrast(text_luminance: Float, background_luminance: Float) -> Float {
if text_luminance.is_nan()
|| !(0.0..=1.1).contains(&text_luminance)
|| background_luminance.is_nan()
|| !(0.0..=1.1).contains(&background_luminance)
{
return 0.0;
}
let text_luminance = if text_luminance < BLACK_THRESHOLD {
text_luminance + (BLACK_THRESHOLD - text_luminance).powf(BLACK_EXPONENT)
} else {
text_luminance
};
let background_luminance = if background_luminance < BLACK_THRESHOLD {
background_luminance + (BLACK_THRESHOLD - background_luminance).powf(BLACK_EXPONENT)
} else {
background_luminance
};
if (text_luminance - background_luminance).abs() < INPUT_CLAMP {
return 0.0;
};
if text_luminance < background_luminance {
let contrast = SCALE * (background_luminance.powf(0.56) - text_luminance.powf(0.57));
if contrast < OUTPUT_CLAMP {
0.0
} else {
contrast - OFFSET
}
} else {
let contrast = SCALE * (background_luminance.powf(0.65) - text_luminance.powf(0.62));
if -OUTPUT_CLAMP < contrast {
0.0
} else {
contrast + OFFSET
}
}
}
#[cfg(test)]
mod test {
use super::{to_contrast, to_contrast_luminance_srgb};
use crate::assert_close_enough;
#[test]
fn test_contrast() {
let blue = to_contrast_luminance_srgb(&[104.0 / 255.0, 114.0 / 255.0, 1.0]);
assert_close_enough!(to_contrast(0.0, blue), 0.38390416110716424);
assert_close_enough!(to_contrast(1.0, blue), -0.7119199952225724);
}
}