ezk_image/color/
transfer.rs

1use crate::vector::Vector;
2
3/// Image opto-electronic transfer characteristics
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum ColorTransfer {
6    /// Linear
7    Linear,
8    /// Gamma of 2.2
9    Gamma22,
10    /// Gamma of 2.8
11    Gamma28,
12    /// SRGB (Gamma of 2.2 with linear part)
13    SRGB,
14    /// BT.601 BT.709 BT.2020
15    SDR,
16    /// BT.2100 perceptual quantization (PQ)
17    BT2100PQ,
18    /// BT.2100 hybrid log-gamma (HLG)
19    BT2100HLG,
20}
21
22impl ColorTransfer {
23    pub fn linear_to_scaled(&self, mut i: f32) -> f32 {
24        // Safety: f32 is not a SIMD type
25        unsafe { self.linear_to_scaled_v(&mut [&mut i]) }
26
27        i
28    }
29
30    pub fn scaled_to_linear(&self, mut i: f32) -> f32 {
31        // Safety: f32 is not a SIMD type
32        unsafe { self.scaled_to_linear_v(&mut [&mut i]) }
33
34        i
35    }
36
37    #[inline(always)]
38    pub(crate) unsafe fn linear_to_scaled_v<const N: usize, V: Vector>(&self, i: &mut [&mut V; N]) {
39        match self {
40            ColorTransfer::Linear => {}
41            ColorTransfer::Gamma22 => {
42                for v in i {
43                    **v = gamma::linear_to_scaled::<22, V>(**v)
44                }
45            }
46            ColorTransfer::Gamma28 => {
47                for v in i {
48                    **v = gamma::linear_to_scaled::<28, V>(**v)
49                }
50            }
51            ColorTransfer::SRGB => {
52                for v in i {
53                    **v = srgb::linear_to_scaled(**v)
54                }
55            }
56            ColorTransfer::SDR => {
57                for v in i {
58                    **v = sdr::linear_to_scaled(**v)
59                }
60            }
61            ColorTransfer::BT2100PQ => {
62                for v in i {
63                    **v = bt2100_pq::linear_to_scaled(**v)
64                }
65            }
66            ColorTransfer::BT2100HLG => {
67                for v in i {
68                    **v = bt2100_hlg::linear_to_scaled(**v)
69                }
70            }
71        }
72    }
73
74    #[inline(always)]
75    pub(crate) unsafe fn scaled_to_linear_v<const N: usize, V: Vector>(&self, i: &mut [&mut V; N]) {
76        match self {
77            ColorTransfer::Linear => {}
78            ColorTransfer::Gamma22 => {
79                for v in i {
80                    **v = gamma::scaled_to_linear::<22, V>(**v)
81                }
82            }
83            ColorTransfer::Gamma28 => {
84                for v in i {
85                    **v = gamma::scaled_to_linear::<28, V>(**v)
86                }
87            }
88            ColorTransfer::SRGB => {
89                for v in i {
90                    **v = srgb::scaled_to_linear(**v)
91                }
92            }
93            ColorTransfer::SDR => {
94                for v in i {
95                    **v = sdr::scaled_to_linear(**v)
96                }
97            }
98            ColorTransfer::BT2100PQ => {
99                for v in i {
100                    **v = bt2100_pq::scaled_to_linear(**v)
101                }
102            }
103            ColorTransfer::BT2100HLG => {
104                for v in i {
105                    **v = bt2100_hlg::scaled_to_linear(**v)
106                }
107            }
108        }
109    }
110}
111
112mod gamma {
113    use crate::vector::Vector;
114
115    #[inline(always)]
116    pub(super) unsafe fn linear_to_scaled<const GAMMA: u32, V: Vector>(i: V) -> V {
117        i.vpowf(1.0 / (GAMMA as f32 / 10.0))
118    }
119
120    #[inline(always)]
121    pub(super) unsafe fn scaled_to_linear<const GAMMA: u32, V: Vector>(i: V) -> V {
122        i.vpowf(GAMMA as f32 / 10.0)
123    }
124}
125
126mod srgb {
127    use crate::vector::Vector;
128
129    #[inline(always)]
130    pub(super) unsafe fn linear_to_scaled<V: Vector>(i: V) -> V {
131        let mask = i.lef(0.0031308);
132
133        // a = i * 12.92
134        let a = i.vmulf(12.92);
135
136        // b = 1.055 * i.powf(1.0 / 2.4) - 0.055
137        let b = V::splat(1.055).vmul(i.vpowf(1.0 / 2.4)).vsubf(0.055);
138
139        V::select(a, b, mask)
140    }
141
142    #[inline(always)]
143    pub(super) unsafe fn scaled_to_linear<V: Vector>(i: V) -> V {
144        let mask = i.lef(0.04045);
145
146        // a = i / 12.92
147        let a = i.vdivf(12.92);
148
149        // b = ((i + 0.055) / 1.055).powf(2.4)
150        let b = i.vaddf(0.055).vdivf(1.055).vpowf(2.4);
151
152        V::select(a, b, mask)
153    }
154}
155
156mod sdr {
157    use crate::vector::Vector;
158
159    #[inline(always)]
160    pub(crate) unsafe fn linear_to_scaled<V: Vector>(i: V) -> V {
161        let mask = i.lt(V::splat(0.018_053_97));
162
163        // a = 4.5 * i
164        let a = V::splat(4.5).vmul(i);
165
166        // b = 1.099 * i.powf(0.45) - 0.099
167        let b = V::splat(1.099).vmul(i.vpowf(0.45)).vsubf(0.099);
168
169        V::select(a, b, mask)
170    }
171
172    #[inline(always)]
173    pub(crate) unsafe fn scaled_to_linear<V: Vector>(i: V) -> V {
174        let mask = i.ltf(0.081490956);
175
176        // a = i / 4.5
177        let a = i.vdivf(4.5);
178
179        // b = ((i + 0.0993) / 1.099).powf(1.0 / 0.45)
180        let b = i.vaddf(0.0993).vdivf(1.099).vpowf(1.0 / 0.45);
181
182        V::select(a, b, mask)
183    }
184}
185
186pub(crate) mod bt2100_pq {
187    use crate::vector::Vector;
188
189    const M1: f32 = 0.15930176;
190    const M2: f32 = 78.84375;
191
192    const C1: f32 = 0.8359375;
193    const C2: f32 = 18.851563;
194    const C3: f32 = 18.6875;
195
196    const L: f32 = 10000.0;
197
198    /// PQ inverse EOTF
199    #[inline(always)]
200    pub(crate) unsafe fn linear_to_scaled<V: Vector>(i: V) -> V {
201        // Avoid producing NaN for negative numbers
202        let i = i.vmaxf(0.0);
203
204        let i = i.vdivf(L);
205        let ym1 = i.vpowf(M1);
206
207        let a = ym1.vmulf(C2).vaddf(C1);
208        let b = ym1.vmulf(C3).vaddf(1.0);
209
210        a.vdiv(b).vpowf(M2)
211    }
212
213    /// PQ EOTF
214    #[inline(always)]
215    pub(crate) unsafe fn scaled_to_linear<V: Vector>(i: V) -> V {
216        // Avoid producing NaN for negative numbers
217        let i = i.vmaxf(0.0);
218
219        let epow1dm2 = i.vpowf(1.0 / M2);
220
221        let a = epow1dm2.vsubf(C1).vmaxf(0.0);
222        let b = V::splat(C2).vsub(epow1dm2.vmulf(C3));
223
224        a.vdiv(b).vpowf(1.0 / M1).vmulf(L)
225    }
226}
227
228pub(crate) mod bt2100_hlg {
229    use crate::vector::Vector;
230    use std::f32::consts::E;
231
232    const A: f32 = 0.178_832_77;
233    const B: f32 = 0.284_668_92;
234    const C: f32 = 0.559_910_7;
235
236    #[inline(always)]
237    pub(crate) unsafe fn linear_to_scaled<V: Vector>(i: V) -> V {
238        // Avoid producing NaN for negative numbers
239        let i = i.vmaxf(0.0);
240
241        let mask = i.lef(1.0 / 12.0);
242
243        // a = (3.0 * i).sqrt()
244        let a = i.vmulf(3.0).vsqrt();
245
246        // b = A * (12.0 * i - B).ln() + C
247        let b = V::splat(A)
248            .vmul(V::splat(12.0).vmul(i).vsubf(B).vln())
249            .vaddf(C);
250
251        V::select(a, b, mask)
252    }
253
254    #[inline(always)]
255    pub(crate) unsafe fn scaled_to_linear<V: Vector>(i: V) -> V {
256        // Avoid producing NaN for negative numbers
257        let i = i.vmaxf(0.0);
258
259        let mask = i.lef(0.5);
260
261        // a = i.powf(2.0) / 3.0
262        let a = i.vpowf(2.0).vdivf(3.0);
263
264        // b = (E.powf((i - C) / A) + B) / 12.0
265        let b = V::splat(E).vpow(i.vsubf(C).vdivf(A)).vaddf(B).vdivf(12.0);
266
267        V::select(a, b, mask)
268    }
269}