pub fn from_linear(linear: [f32; 3]) -> f32 { from_floats(linear) }
pub fn from_normalised(normalised: [f32; 3]) -> f32 {
let y = from_linear(super::linear_from_normalised(normalised));
super::gamma::compress_normalised(y)
}
pub fn from_u8(encoded: [u8; 3]) -> u8 {
super::gamma::compress_u8(from_linear(super::linear_from_u8(encoded)))
}
pub mod approx_gamma {
pub fn from_normalised(normalised: [f32; 3]) -> f32 {
let [r, g, b] = normalised;
super::from_floats([r * r, g * g, b * b]).sqrt()
}
pub fn from_u8(encoded: [u8; 3]) -> u8 {
let [r, g, b] = encoded;
(super::from_floats([r as f32, g as f32, b as f32]) + 0.5) as u8
}
fn isqrt(n: u32) -> u8 {
let mut n = n;
let mut x = 0;
let mut b = 1 << 16;
while b > n {
b = b / 4;
}
while b != 0 {
if n >= x + b {
n = n - x - b;
x = x / 2 + b;
} else {
x = x / 2;
}
b = b / 4;
}
x as u8
}
pub fn from_u8_no_fpu(encoded: [u8; 3]) -> u8 {
let [r, g, b] = encoded;
isqrt(
(r as u32 * r as u32 * 13936 +
g as u32 * g as u32 * 46869 +
b as u32 * b as u32 * 4731) >>
16,
)
}
}
pub mod no_gamma {
pub fn from_normalised(normalised: [f32; 3]) -> f32 {
super::from_floats(normalised)
}
pub fn from_u8(encoded: [u8; 3]) -> u8 {
let [r, g, b] = encoded;
let y = 3567454 * r as u32 + 11998779 * g as u32 + 1210983 * b as u32;
((y + (1 << 23)) >> 24) as u8
}
}
fn from_floats(arr: [f32; 3]) -> f32 {
super::dot_product(&super::xyz::XYZ_FROM_SRGB_MATRIX[1], &arr)
}
#[cfg(test)]
mod test {
use approx::assert_ulps_eq;
#[test]
fn test_is_grey() {
for i in 0..=255 {
assert_eq!(i, super::from_u8([i, i, i]));
assert_eq!(i, super::approx_gamma::from_u8([i, i, i]));
assert_eq!(i, super::approx_gamma::from_u8_no_fpu([i, i, i]));
assert_eq!(i, super::no_gamma::from_u8([i, i, i]));
let i = i as f32 / 255.0;
assert_ulps_eq!(i, super::from_normalised([i, i, i]), max_ulps = 1);
assert_ulps_eq!(i, super::from_linear([i, i, i]), max_ulps = 1);
assert_ulps_eq!(
i,
super::approx_gamma::from_normalised([i, i, i]),
max_ulps = 1
);
assert_ulps_eq!(
i,
super::no_gamma::from_normalised([i, i, i]),
max_ulps = 1
);
}
}
fn distance(want_grey: f32, got_grey: f32) -> f64 {
fn l_from_y(y: f64) -> f64 {
if y > 216.0 / 24389.0 {
116.0 * y.powf(1.0 / 3.0) - 16.0
} else {
24389.0 / 27.0 * y
}
}
fn de(l1: f64, l2: f64) -> f64 {
let v = ((l1 + l2) / 2.0 - 50.0).powi(2);
let div = 1.0 + ((0.015 * v) / (20.0 + v).sqrt());
(l2 - l1).abs() / div
}
de(l_from_y(want_grey as f64), l_from_y(got_grey as f64))
}
#[derive(Debug, PartialEq)]
struct Bench(f64, f64);
impl approx::AbsDiffEq for Bench {
type Epsilon = <f64 as approx::AbsDiffEq>::Epsilon;
fn default_epsilon() -> Self::Epsilon { 0.000001 }
fn abs_diff_eq(&self, rhs: &Self, epsilon: Self::Epsilon) -> bool {
self.0.abs_diff_eq(&rhs.0, epsilon) &&
self.1.abs_diff_eq(&rhs.1, epsilon)
}
}
impl approx::UlpsEq for Bench {
fn default_max_ulps() -> u32 { f64::default_max_ulps() }
fn ulps_eq(
&self,
rhs: &Self,
epsilon: Self::Epsilon,
max_ulps: u32,
) -> bool {
self.0.ulps_eq(&rhs.0, epsilon, max_ulps) &&
self.1.ulps_eq(&rhs.1, epsilon, max_ulps)
}
}
fn accuracy(f: &dyn Fn([u8; 3]) -> f64) -> Bench {
let mut sum_err = 0.0;
let mut max_err = 0.0;
for i in 0..(1 << 24) {
let rgb = [(i >> 16) as u8, (i >> 8) as u8, (i) as u8];
let err = f(rgb);
max_err = err.max(max_err);
sum_err += err;
}
Bench(sum_err, max_err)
}
#[test]
fn test_from_normalised_accuracy() {
let cases: [(f64, f64, &dyn Fn([f32; 3]) -> f32); 3] = [
(0.0, 0.0, &crate::grey::from_normalised),
(
11278361.255885385,
3.860478819921189,
&crate::grey::approx_gamma::from_normalised,
),
(
69142133.11431149,
28.150428835752255,
&crate::grey::no_gamma::from_normalised,
),
];
for (want_sum, want_max, func) in cases.iter().copied() {
let got = accuracy(&|rgb| {
let rgb = crate::normalised_from_u8(rgb);
let want = crate::grey::from_normalised(rgb);
let got = func(rgb);
distance(
crate::gamma::expand_normalised(want),
crate::gamma::expand_normalised(got),
)
});
assert_ulps_eq!(Bench(want_sum, want_max), got);
}
}
#[test]
fn test_from_u8_accuracy() {
#[rustfmt::skip]
let cases: [(u64, u64, &dyn Fn([u8; 3]) -> u8); 4] = [
( 0, 0, &crate::grey::from_u8),
(207766613, 73, &crate::grey::approx_gamma::from_u8),
( 41742745, 11, &crate::grey::approx_gamma::from_u8_no_fpu),
(207766715, 73, &crate::grey::no_gamma::from_u8),
];
for (i, case) in cases.iter().copied().enumerate() {
let (want_sum, want_max, func) = case;
let got = accuracy(&|rgb| {
let lin = crate::grey::from_linear(crate::linear_from_u8(rgb));
let want = crate::gamma::compress_u8(lin);
let got = func(rgb);
(want as i32 - got as i32).abs() as f64
});
assert_eq!(
(want_sum, want_max),
(got.0 as u64, got.1 as u64),
" [Test Case #{}]",
i
);
}
}
}