Skip to main content

colr_types/
decode.rs

1//! Signal decoding (EOTF): encoded signal to linear light.
2
3use crate::math::Float;
4use crate::transfer::*;
5
6/// Decode a value from a stored signal to linear light (inverse OETF / EOTF).
7///
8/// Implement for scalar `F: Float` to get a transfer function at any precision.
9/// A blanket impl provides `Decode<[F; N]>` automatically for any `Decode<F>`.
10///
11/// Note: the HLG EOTF per ITU-R BT.2100 additionally applies an OOTF.
12/// This returns inverse-OETF scene-linear, correct for scene-referred
13/// pipelines that apply the OOTF separately.
14pub trait Decode<T>: TransferFunction {
15    /// Convert an encoded signal value to a linear-light value.
16    fn decode(v: T) -> T;
17}
18
19impl<F: Float, TF: Decode<F>, const N: usize> Decode<[F; N]> for TF {
20    #[inline(always)]
21    fn decode(v: [F; N]) -> [F; N] {
22        v.map(|x| TF::decode(x))
23    }
24}
25
26impl<F: Float> Decode<F> for Linear {
27    #[inline(always)]
28    fn decode(v: F) -> F {
29        v
30    }
31}
32
33impl<F: Float> Decode<F> for Srgb {
34    #[inline(always)]
35    fn decode(x: F) -> F {
36        let threshold = F::from_f64(Self::ENCODED_THRESHOLD as f64);
37        let slope = F::from_f64(Self::LINEAR_SLOPE as f64);
38        let alpha = F::from_f64(Self::ALPHA as f64);
39        let gamma = F::from_f64(Self::GAMMA as f64);
40        let one = F::ONE;
41        if x <= threshold {
42            x / slope
43        } else {
44            ((x + (alpha - one)) / alpha).powf(gamma)
45        }
46    }
47}
48
49impl<F: Float> Decode<F> for Rec709 {
50    #[inline(always)]
51    fn decode(x: F) -> F {
52        let threshold = F::from_f64(Self::ENCODED_THRESHOLD as f64);
53        let slope = F::from_f64(Self::LINEAR_SLOPE as f64);
54        let alpha = F::from_f64(Self::ALPHA as f64);
55        let inv_power = F::from_f64(1.0 / Self::POWER as f64);
56        let one = F::ONE;
57        if x <= threshold {
58            x / slope
59        } else {
60            ((x + (alpha - one)) / alpha).powf(inv_power)
61        }
62    }
63}
64
65impl<F: Float> Decode<F> for Pq {
66    #[inline(always)]
67    fn decode(x: F) -> F {
68        let zero = F::ZERO;
69        let one = F::ONE;
70        let m1 = F::from_f64(Self::M1 as f64);
71        let m2 = F::from_f64(Self::M2 as f64);
72        let c1 = F::from_f64(Self::C1 as f64);
73        let c2 = F::from_f64(Self::C2 as f64);
74        let c3 = F::from_f64(Self::C3 as f64);
75        let v_m2 = x.max(zero).powf(one / m2);
76        let num = (v_m2 - c1).max(zero);
77        let den = c2 - c3 * v_m2;
78        (num / den).powf(one / m1)
79    }
80}
81
82impl<F: Float> Decode<F> for Hlg {
83    #[inline(always)]
84    fn decode(x: F) -> F {
85        let zero = F::ZERO;
86        let one = F::ONE;
87        let neg_one = zero - one;
88        let threshold = F::from_f64(Self::ENCODED_THRESHOLD as f64);
89        let a = F::from_f64(Self::A as f64);
90        let b = F::from_f64(Self::B as f64);
91        let c = F::from_f64(Self::C as f64);
92        let three = F::from_f64(3.0);
93        let twelve = F::from_f64(12.0);
94        if x.abs() <= threshold {
95            x * x.abs() / three
96        } else {
97            let sign = if x < zero { neg_one } else { one };
98            sign * ((x.abs() - c) / a).exp() + b / twelve
99        }
100    }
101}
102
103impl<F: Float> Decode<F> for ProPhoto {
104    #[inline(always)]
105    fn decode(x: F) -> F {
106        let zero = F::ZERO;
107        let one = F::ONE;
108        let neg_one = zero - one;
109        let sign = if x < zero { neg_one } else { one };
110        let abs = x.abs();
111        let threshold = F::from_f64(Self::ENCODED_THRESHOLD as f64);
112        let slope = F::from_f64(Self::LINEAR_SLOPE as f64);
113        let gamma = F::from_f64(Self::GAMMA as f64);
114        sign * if abs <= threshold {
115            abs / slope
116        } else {
117            abs.powf(gamma)
118        }
119    }
120}
121
122impl<F: Float> Decode<F> for AcesCc {
123    #[inline(always)]
124    fn decode(x: F) -> F {
125        let cut1 = F::from_f64(Self::CUT1 as f64);
126        let scale = F::from_f64(Self::LOG_SCALE as f64);
127        let offset = F::from_f64(Self::LOG_OFFSET as f64);
128        let ln2 = F::from_f64(core::f64::consts::LN_2);
129        let neg16: F = F::from_f64(2.0f64.powi(-16));
130        let two = F::from_f64(2.0);
131        let linear = x * scale - offset;
132        if x < cut1 {
133            ((linear * ln2).exp() - neg16) * two
134        } else {
135            (linear * ln2).exp()
136        }
137    }
138}
139
140impl<F: Float> Decode<F> for AcesCct {
141    #[inline(always)]
142    fn decode(x: F) -> F {
143        let y_brk = F::from_f64(Self::Y_BRK as f64);
144        let a = F::from_f64(Self::A as f64);
145        let b = F::from_f64(Self::B as f64);
146        let scale = F::from_f64(Self::LOG_SCALE as f64);
147        let offset = F::from_f64(Self::LOG_OFFSET as f64);
148        let ln2 = F::from_f64(core::f64::consts::LN_2);
149        if x <= y_brk {
150            (x - b) / a
151        } else {
152            ((x * scale - offset) * ln2).exp()
153        }
154    }
155}
156
157impl<F: Float> Decode<F> for DciP3 {
158    #[inline(always)]
159    fn decode(x: F) -> F {
160        let gamma = F::from_f64(Self::GAMMA as f64);
161        x.max(F::ZERO).powf(gamma)
162    }
163}