ph-color-bake 0.1.0

Host-side generator for auditable ph-color matrices, fixed-point LUTs, and golden vectors
Documentation
//! IEC 61966-2-1 sRGB transfer functions for host baking.

/// sRGB OETF: linear light → encoded (camera / encoding).
#[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
    }
}

/// sRGB EOTF: encoded → linear light (display / decoding).
#[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);
    }
}