#[must_use]
pub fn oetf(linear: f64) -> f64 {
let x = linear.clamp(0.0, 1.0);
if x <= 0.0031308 {
12.92 * x
} else {
1.055 * x.powf(1.0 / 2.4) - 0.055
}
}
#[must_use]
pub fn eotf(encoded: f64) -> f64 {
let x = encoded.clamp(0.0, 1.0);
if x <= 0.04045 {
x / 12.92
} else {
((x + 0.055) / 1.055).powf(2.4)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lut::bake_lut;
#[test]
fn endpoints() {
assert!((oetf(0.0) - 0.0).abs() < 1e-12);
assert!((oetf(1.0) - 1.0).abs() < 1e-12);
assert!((eotf(0.0) - 0.0).abs() < 1e-12);
assert!((eotf(1.0) - 1.0).abs() < 1e-12);
}
#[test]
fn bake_257_matches_shipped_tables() {
let o = bake_lut::<257>(oetf);
let e = bake_lut::<257>(eotf);
assert_eq!(o.max_err_lsb, ::ph_color::SRGB_OETF_MAX_ERR_LSB);
assert_eq!(e.max_err_lsb, ::ph_color::SRGB_EOTF_MAX_ERR_LSB);
assert_eq!(::ph_color::SRGB_OETF.max_err_lsb(), o.max_err_lsb);
assert_eq!(::ph_color::SRGB_EOTF.max_err_lsb(), e.max_err_lsb);
let mut oetf_err = 0u16;
let mut eotf_err = 0u16;
for x in 0..=u16::MAX {
let shipped_o = ::ph_color::SRGB_OETF
.lookup(::ph_color::Q0_16::from_raw(x))
.to_raw();
let shipped_e = ::ph_color::SRGB_EOTF
.lookup(::ph_color::Q0_16::from_raw(x))
.to_raw();
assert_eq!(shipped_o, crate::lut::lookup(&o.knots, x));
assert_eq!(shipped_e, crate::lut::lookup(&e.knots, x));
let unit = f64::from(x) / f64::from(u16::MAX);
oetf_err = oetf_err.max(shipped_o.abs_diff(crate::lut::u16_from_unit(oetf(unit))));
eotf_err = eotf_err.max(shipped_e.abs_diff(crate::lut::u16_from_unit(eotf(unit))));
}
assert!(oetf_err <= ::ph_color::SRGB_OETF_MAX_ERR_LSB);
assert!(eotf_err <= ::ph_color::SRGB_EOTF_MAX_ERR_LSB);
assert_eq!(oetf_err, o.max_err_lsb);
assert_eq!(eotf_err, e.max_err_lsb);
}
}