colr-types 0.3.1

Color model ZSTs and marker traits for colr.
Documentation
//! Signal decoding (EOTF): encoded signal to linear light.

use crate::math::Float;
use crate::transfer::*;

/// Decode a value from a stored signal to linear light (inverse OETF / EOTF).
///
/// Implement for scalar `F: Float` to get a transfer function at any precision.
/// A blanket impl provides `Decode<[F; N]>` automatically for any `Decode<F>`.
///
/// Note: the HLG EOTF per ITU-R BT.2100 additionally applies an OOTF.
/// This returns inverse-OETF scene-linear, correct for scene-referred
/// pipelines that apply the OOTF separately.
pub trait Decode<T>: TransferFunction {
    /// Convert an encoded signal value to a linear-light value.
    fn decode(v: T) -> T;
}

impl<F: Float, TF: Decode<F>, const N: usize> Decode<[F; N]> for TF {
    #[inline(always)]
    fn decode(v: [F; N]) -> [F; N] {
        v.map(|x| TF::decode(x))
    }
}

impl<F: Float> Decode<F> for Linear {
    #[inline(always)]
    fn decode(v: F) -> F {
        v
    }
}

impl<F: Float> Decode<F> for Srgb {
    #[inline(always)]
    fn decode(x: F) -> F {
        let threshold = F::from_f64(Self::ENCODED_THRESHOLD as f64);
        let slope = F::from_f64(Self::LINEAR_SLOPE as f64);
        let alpha = F::from_f64(Self::ALPHA as f64);
        let gamma = F::from_f64(Self::GAMMA as f64);
        let one = F::ONE;
        if x <= threshold {
            x / slope
        } else {
            ((x + (alpha - one)) / alpha).powf(gamma)
        }
    }
}

impl<F: Float> Decode<F> for Rec709 {
    #[inline(always)]
    fn decode(x: F) -> F {
        let threshold = F::from_f64(Self::ENCODED_THRESHOLD as f64);
        let slope = F::from_f64(Self::LINEAR_SLOPE as f64);
        let alpha = F::from_f64(Self::ALPHA as f64);
        let inv_power = F::from_f64(1.0 / Self::POWER as f64);
        let one = F::ONE;
        if x <= threshold {
            x / slope
        } else {
            ((x + (alpha - one)) / alpha).powf(inv_power)
        }
    }
}

impl<F: Float> Decode<F> for Pq {
    #[inline(always)]
    fn decode(x: F) -> F {
        let zero = F::ZERO;
        let one = F::ONE;
        let m1 = F::from_f64(Self::M1 as f64);
        let m2 = F::from_f64(Self::M2 as f64);
        let c1 = F::from_f64(Self::C1 as f64);
        let c2 = F::from_f64(Self::C2 as f64);
        let c3 = F::from_f64(Self::C3 as f64);
        let v_m2 = x.max(zero).powf(one / m2);
        let num = (v_m2 - c1).max(zero);
        let den = c2 - c3 * v_m2;
        (num / den).powf(one / m1)
    }
}

impl<F: Float> Decode<F> for Hlg {
    #[inline(always)]
    fn decode(x: F) -> F {
        let zero = F::ZERO;
        let one = F::ONE;
        let neg_one = zero - one;
        let threshold = F::from_f64(Self::ENCODED_THRESHOLD as f64);
        let a = F::from_f64(Self::A as f64);
        let b = F::from_f64(Self::B as f64);
        let c = F::from_f64(Self::C as f64);
        let three = F::from_f64(3.0);
        let twelve = F::from_f64(12.0);
        if x.abs() <= threshold {
            x * x.abs() / three
        } else {
            let sign = if x < zero { neg_one } else { one };
            sign * ((x.abs() - c) / a).exp() + b / twelve
        }
    }
}

impl<F: Float> Decode<F> for ProPhoto {
    #[inline(always)]
    fn decode(x: F) -> F {
        let zero = F::ZERO;
        let one = F::ONE;
        let neg_one = zero - one;
        let sign = if x < zero { neg_one } else { one };
        let abs = x.abs();
        let threshold = F::from_f64(Self::ENCODED_THRESHOLD as f64);
        let slope = F::from_f64(Self::LINEAR_SLOPE as f64);
        let gamma = F::from_f64(Self::GAMMA as f64);
        sign * if abs <= threshold {
            abs / slope
        } else {
            abs.powf(gamma)
        }
    }
}

impl<F: Float> Decode<F> for AcesCc {
    #[inline(always)]
    fn decode(x: F) -> F {
        let cut1 = F::from_f64(Self::CUT1 as f64);
        let scale = F::from_f64(Self::LOG_SCALE as f64);
        let offset = F::from_f64(Self::LOG_OFFSET as f64);
        let ln2 = F::from_f64(core::f64::consts::LN_2);
        let neg16: F = F::from_f64(2.0f64.powi(-16));
        let two = F::from_f64(2.0);
        let linear = x * scale - offset;
        if x < cut1 {
            ((linear * ln2).exp() - neg16) * two
        } else {
            (linear * ln2).exp()
        }
    }
}

impl<F: Float> Decode<F> for AcesCct {
    #[inline(always)]
    fn decode(x: F) -> F {
        let y_brk = F::from_f64(Self::Y_BRK as f64);
        let a = F::from_f64(Self::A as f64);
        let b = F::from_f64(Self::B as f64);
        let scale = F::from_f64(Self::LOG_SCALE as f64);
        let offset = F::from_f64(Self::LOG_OFFSET as f64);
        let ln2 = F::from_f64(core::f64::consts::LN_2);
        if x <= y_brk {
            (x - b) / a
        } else {
            ((x * scale - offset) * ln2).exp()
        }
    }
}

impl<F: Float> Decode<F> for DciP3 {
    #[inline(always)]
    fn decode(x: F) -> F {
        let gamma = F::from_f64(Self::GAMMA as f64);
        x.max(F::ZERO).powf(gamma)
    }
}