Skip to main content

moxcms/
gamma.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
3 * //
4 * // Redistribution and use in source and binary forms, with or without modification,
5 * // are permitted provided that the following conditions are met:
6 * //
7 * // 1.  Redistributions of source code must retain the above copyright notice, this
8 * // list of conditions and the following disclaimer.
9 * //
10 * // 2.  Redistributions in binary form must reproduce the above copyright notice,
11 * // this list of conditions and the following disclaimer in the documentation
12 * // and/or other materials provided with the distribution.
13 * //
14 * // 3.  Neither the name of the copyright holder nor the names of its
15 * // contributors may be used to endorse or promote products derived from
16 * // this software without specific prior written permission.
17 * //
18 * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29use crate::mlaf::{fmla, mlaf};
30use crate::transform::PointeeSizeExpressible;
31use crate::*;
32use num_traits::AsPrimitive;
33use pxfm::*;
34
35#[inline]
36/// Linear transfer function for sRGB
37fn srgb_to_linear(gamma: f64) -> f64 {
38    if gamma < 0f64 {
39        0f64
40    } else if gamma < 12.92f64 * 0.0030412825601275209f64 {
41        gamma * (1f64 / 12.92f64)
42    } else if gamma < 1.0f64 {
43        f_pow(
44            (gamma + 0.0550107189475866f64) / 1.0550107189475866f64,
45            2.4f64,
46        )
47    } else {
48        1.0f64
49    }
50}
51
52#[inline]
53#[cfg(feature = "extended_range")]
54/// Linear transfer function for sRGB
55fn srgb_to_linearf_extended(gamma: f32) -> f32 {
56    if gamma < 12.92 * 0.0030412825601275209 {
57        gamma * (1. / 12.92f32)
58    } else {
59        dirty_powf((gamma + 0.0550107189475866) / 1.0550107189475866, 2.4)
60    }
61}
62
63#[inline]
64/// Gamma transfer function for sRGB
65fn srgb_from_linear(linear: f64) -> f64 {
66    if linear < 0.0f64 {
67        0.0f64
68    } else if linear < 0.0030412825601275209f64 {
69        linear * 12.92f64
70    } else if linear < 1.0f64 {
71        fmla(
72            1.0550107189475866f64,
73            f_pow(linear, 1.0f64 / 2.4f64),
74            -0.0550107189475866f64,
75        )
76    } else {
77        1.0f64
78    }
79}
80
81/// Gamma transfer function for sRGB
82#[cfg(feature = "extended_range")]
83pub(crate) fn srgb_from_linear_extended(linear: f32) -> f32 {
84    if linear < 0.0030412825601275209f32 {
85        linear * 12.92f32
86    } else {
87        fmla(
88            1.0550107189475866f32,
89            dirty_powf(linear, 1.0f32 / 2.4f32),
90            -0.0550107189475866f32,
91        )
92    }
93}
94
95#[inline]
96/// Linear transfer function for Rec.709
97fn rec709_to_linear(gamma: f64) -> f64 {
98    if gamma < 0.0f64 {
99        0.0f64
100    } else if gamma < 4.5f64 * 0.018053968510807f64 {
101        gamma * (1f64 / 4.5f64)
102    } else if gamma < 1.0f64 {
103        f_pow(
104            (gamma + 0.09929682680944f64) / 1.09929682680944f64,
105            1.0f64 / 0.45f64,
106        )
107    } else {
108        1.0f64
109    }
110}
111
112#[inline]
113#[cfg(feature = "extended_range")]
114/// Linear transfer function for Rec.709
115fn rec709_to_linearf_extended(gamma: f32) -> f32 {
116    if gamma < 4.5 * 0.018053968510807 {
117        gamma * (1. / 4.5)
118    } else {
119        f_powf((gamma + 0.09929682680944) / 1.09929682680944, 1.0 / 0.45)
120    }
121}
122
123#[inline]
124/// Gamma transfer function for Rec.709
125fn rec709_from_linear(linear: f64) -> f64 {
126    if linear < 0.0f64 {
127        0.0f64
128    } else if linear < 0.018053968510807f64 {
129        linear * 4.5f64
130    } else if linear < 1.0f64 {
131        fmla(
132            1.09929682680944f64,
133            f_pow(linear, 0.45f64),
134            -0.09929682680944f64,
135        )
136    } else {
137        1.0f64
138    }
139}
140
141/// Gamma transfer function for Rec.709
142#[cfg(feature = "extended_range")]
143fn rec709_from_linearf_extended(linear: f32) -> f32 {
144    if linear < 0.018053968510807 {
145        linear * 4.5
146    } else {
147        fmla(
148            1.09929682680944,
149            dirty_powf(linear, 0.45),
150            -0.09929682680944,
151        )
152    }
153}
154
155/// Linear transfer function for Smpte 428
156pub(crate) fn smpte428_to_linear(gamma: f64) -> f64 {
157    const SCALE: f64 = 1. / 0.91655527974030934f64;
158    f_pow(gamma.max(0.).min(1f64), 2.6f64) * SCALE
159}
160
161#[cfg(feature = "extended_range")]
162/// Linear transfer function for Smpte 428
163pub(crate) fn smpte428_to_linearf_extended(gamma: f32) -> f32 {
164    const SCALE: f32 = 1. / 0.91655527974030934;
165    dirty_powf(gamma.max(0.), 2.6) * SCALE
166}
167
168/// Gamma transfer function for Smpte 428
169fn smpte428_from_linear(linear: f64) -> f64 {
170    const POWER_VALUE: f64 = 1.0f64 / 2.6f64;
171    f_pow(0.91655527974030934f64 * linear.max(0.), POWER_VALUE)
172}
173
174#[cfg(feature = "extended_range")]
175/// Gamma transfer function for Smpte 428
176fn smpte428_from_linearf(linear: f32) -> f32 {
177    const POWER_VALUE: f32 = 1.0 / 2.6;
178    dirty_powf(0.91655527974030934 * linear.max(0.), POWER_VALUE)
179}
180
181/// Linear transfer function for Smpte 240
182pub(crate) fn smpte240_to_linear(gamma: f64) -> f64 {
183    if gamma < 0.0 {
184        0.0
185    } else if gamma < 4.0 * 0.022821585529445 {
186        gamma / 4.0
187    } else if gamma < 1.0 {
188        f_pow((gamma + 0.111572195921731) / 1.111572195921731, 1.0 / 0.45)
189    } else {
190        1.0
191    }
192}
193
194#[cfg(feature = "extended_range")]
195/// Linear transfer function for Smpte 240
196pub(crate) fn smpte240_to_linearf_extended(gamma: f32) -> f32 {
197    if gamma < 4.0 * 0.022821585529445 {
198        gamma / 4.0
199    } else {
200        dirty_powf((gamma + 0.111572195921731) / 1.111572195921731, 1.0 / 0.45)
201    }
202}
203
204/// Gamma transfer function for Smpte 240
205fn smpte240_from_linear(linear: f64) -> f64 {
206    if linear < 0.0 {
207        0.0
208    } else if linear < 0.022821585529445 {
209        linear * 4.0
210    } else if linear < 1.0 {
211        fmla(1.111572195921731, f_pow(linear, 0.45), -0.111572195921731)
212    } else {
213        1.0
214    }
215}
216
217#[cfg(feature = "extended_range")]
218/// Gamma transfer function for Smpte 240
219fn smpte240_from_linearf_extended(linear: f32) -> f32 {
220    if linear < 0.022821585529445 {
221        linear * 4.0
222    } else {
223        fmla(1.111572195921731, f_powf(linear, 0.45), -0.111572195921731)
224    }
225}
226
227#[inline]
228/// Gamma transfer function for Log100
229fn log100_from_linear(linear: f64) -> f64 {
230    if linear <= 0.01f64 {
231        0.
232    } else {
233        1. + f_log10(linear.min(1.)) / 2.0
234    }
235}
236
237#[cfg(feature = "extended_range")]
238/// Gamma transfer function for Log100
239fn log100_from_linearf(linear: f32) -> f32 {
240    if linear <= 0.01 {
241        0.
242    } else {
243        1. + f_log10f(linear.min(1.)) / 2.0
244    }
245}
246
247/// Linear transfer function for Log100
248pub(crate) fn log100_to_linear(gamma: f64) -> f64 {
249    // The function is non-bijective so choose the middle of [0, 0.00316227766f].
250    const MID_INTERVAL: f64 = 0.01 / 2.;
251    if gamma <= 0. {
252        MID_INTERVAL
253    } else {
254        f_exp10(2. * (gamma.min(1.) - 1.))
255    }
256}
257
258#[cfg(feature = "extended_range")]
259/// Linear transfer function for Log100
260pub(crate) fn log100_to_linearf(gamma: f32) -> f32 {
261    // The function is non-bijective so choose the middle of [0, 0.00316227766f].
262    const MID_INTERVAL: f32 = 0.01 / 2.;
263    if gamma <= 0. {
264        MID_INTERVAL
265    } else {
266        f_exp10f(2. * (gamma.min(1.) - 1.))
267    }
268}
269
270#[inline]
271/// Linear transfer function for Log100Sqrt10
272pub(crate) fn log100_sqrt10_to_linear(gamma: f64) -> f64 {
273    // The function is non-bijective so choose the middle of [0, 0.00316227766f].
274    const MID_INTERVAL: f64 = 0.00316227766 / 2.;
275    if gamma <= 0. {
276        MID_INTERVAL
277    } else {
278        f_exp10(2.5 * (gamma.min(1.) - 1.))
279    }
280}
281
282#[cfg(feature = "extended_range")]
283/// Linear transfer function for Log100Sqrt10
284pub(crate) fn log100_sqrt10_to_linearf(gamma: f32) -> f32 {
285    // The function is non-bijective so choose the middle of [0, 0.00316227766f].
286    const MID_INTERVAL: f32 = 0.00316227766 / 2.;
287    if gamma <= 0. {
288        MID_INTERVAL
289    } else {
290        f_exp10f(2.5 * (gamma.min(1.) - 1.))
291    }
292}
293
294/// Gamma transfer function for Log100Sqrt10
295fn log100_sqrt10_from_linear(linear: f64) -> f64 {
296    if linear <= 0.00316227766 {
297        0.0
298    } else {
299        1.0 + f_log10(linear.min(1.)) / 2.5
300    }
301}
302
303#[cfg(feature = "extended_range")]
304/// Gamma transfer function for Log100Sqrt10
305fn log100_sqrt10_from_linearf(linear: f32) -> f32 {
306    if linear <= 0.00316227766 {
307        0.0
308    } else {
309        1.0 + f_log10f(linear.min(1.)) / 2.5
310    }
311}
312
313/// Gamma transfer function for Bt.1361
314fn bt1361_from_linear(linear: f64) -> f64 {
315    if linear < -0.25 {
316        -0.25
317    } else if linear < 0.0 {
318        fmla(
319            -0.27482420670236,
320            f_pow(-4.0 * linear, 0.45),
321            0.02482420670236,
322        )
323    } else if linear < 0.018053968510807 {
324        linear * 4.5
325    } else if linear < 1.0 {
326        fmla(1.09929682680944, f_pow(linear, 0.45), -0.09929682680944)
327    } else {
328        1.0
329    }
330}
331
332#[cfg(feature = "extended_range")]
333/// Gamma transfer function for Bt.1361
334fn bt1361_from_linearf(linear: f32) -> f32 {
335    if linear < -0.25 {
336        -0.25
337    } else if linear < 0.0 {
338        fmla(
339            -0.27482420670236,
340            dirty_powf(-4.0 * linear, 0.45),
341            0.02482420670236,
342        )
343    } else if linear < 0.018053968510807 {
344        linear * 4.5
345    } else if linear < 1.0 {
346        fmla(
347            1.09929682680944,
348            dirty_powf(linear, 0.45),
349            -0.09929682680944,
350        )
351    } else {
352        1.0
353    }
354}
355
356/// Linear transfer function for Bt.1361
357pub(crate) fn bt1361_to_linear(gamma: f64) -> f64 {
358    if gamma < -0.25f64 {
359        -0.25f64
360    } else if gamma < 0.0f64 {
361        f_pow(
362            (gamma - 0.02482420670236f64) / -0.27482420670236f64,
363            1.0f64 / 0.45f64,
364        ) / -4.0f64
365    } else if gamma < 4.5 * 0.018053968510807 {
366        gamma / 4.5
367    } else if gamma < 1.0 {
368        f_pow((gamma + 0.09929682680944) / 1.09929682680944, 1.0 / 0.45)
369    } else {
370        1.0f64
371    }
372}
373
374#[cfg(feature = "extended_range")]
375/// Linear transfer function for Bt.1361
376fn bt1361_to_linearf(gamma: f32) -> f32 {
377    if gamma < -0.25 {
378        -0.25
379    } else if gamma < 0.0 {
380        dirty_powf((gamma - 0.02482420670236) / -0.27482420670236, 1.0 / 0.45) / -4.0
381    } else if gamma < 4.5 * 0.018053968510807 {
382        gamma / 4.5
383    } else if gamma < 1.0 {
384        dirty_powf((gamma + 0.09929682680944) / 1.09929682680944, 1.0 / 0.45)
385    } else {
386        1.0
387    }
388}
389
390#[inline(always)]
391/// Pure gamma transfer function for gamma 2.2
392fn pure_gamma_function(x: f64, gamma: f64) -> f64 {
393    if x <= 0f64 {
394        0f64
395    } else if x >= 1f64 {
396        1f64
397    } else {
398        f_pow(x, gamma)
399    }
400}
401
402#[cfg(feature = "extended_range")]
403#[inline(always)]
404/// Pure gamma transfer function for gamma 2.2
405fn pure_gamma_function_f(x: f32, gamma: f32) -> f32 {
406    if x <= 0. { 0. } else { dirty_powf(x, gamma) }
407}
408
409pub(crate) fn iec61966_to_linear(gamma: f64) -> f64 {
410    if gamma < -4.5f64 * 0.018053968510807f64 {
411        f_pow(
412            (-gamma + 0.09929682680944f64) / -1.09929682680944f64,
413            1.0 / 0.45,
414        )
415    } else if gamma < 4.5f64 * 0.018053968510807f64 {
416        gamma / 4.5
417    } else {
418        f_pow(
419            (gamma + 0.09929682680944f64) / 1.09929682680944f64,
420            1.0 / 0.45,
421        )
422    }
423}
424
425#[cfg(feature = "extended_range")]
426fn iec61966_to_linearf(gamma: f32) -> f32 {
427    if gamma < -4.5 * 0.018053968510807 {
428        dirty_powf((-gamma + 0.09929682680944) / -1.09929682680944, 1.0 / 0.45)
429    } else if gamma < 4.5 * 0.018053968510807 {
430        gamma / 4.5
431    } else {
432        dirty_powf((gamma + 0.09929682680944) / 1.09929682680944, 1.0 / 0.45)
433    }
434}
435
436fn iec61966_from_linear(v: f64) -> f64 {
437    if v < -0.018053968510807f64 {
438        fmla(-1.09929682680944f64, f_pow(-v, 0.45), 0.09929682680944f64)
439    } else if v < 0.018053968510807f64 {
440        v * 4.5f64
441    } else {
442        fmla(1.09929682680944f64, f_pow(v, 0.45), -0.09929682680944f64)
443    }
444}
445
446#[cfg(feature = "extended_range")]
447fn iec61966_from_linearf(v: f32) -> f32 {
448    if v < -0.018053968510807 {
449        fmla(-1.09929682680944, dirty_powf(-v, 0.45), 0.09929682680944)
450    } else if v < 0.018053968510807 {
451        v * 4.5
452    } else {
453        fmla(1.09929682680944, dirty_powf(v, 0.45), -0.09929682680944)
454    }
455}
456
457#[inline]
458/// Pure gamma transfer function for gamma 2.2
459fn gamma2p2_from_linear(linear: f64) -> f64 {
460    pure_gamma_function(linear, 1f64 / 2.2f64)
461}
462
463#[cfg(feature = "extended_range")]
464#[inline]
465/// Pure gamma transfer function for gamma 2.2
466fn gamma2p2_from_linear_f(linear: f32) -> f32 {
467    pure_gamma_function_f(linear, 1. / 2.2)
468}
469
470#[inline]
471/// Linear transfer function for gamma 2.2
472fn gamma2p2_to_linear(gamma: f64) -> f64 {
473    pure_gamma_function(gamma, 2.2f64)
474}
475
476#[cfg(feature = "extended_range")]
477#[inline]
478/// Linear transfer function for gamma 2.2
479fn gamma2p2_to_linear_f(gamma: f32) -> f32 {
480    pure_gamma_function_f(gamma, 2.2)
481}
482
483#[inline]
484/// Pure gamma transfer function for gamma 2.8
485fn gamma2p8_from_linear(linear: f64) -> f64 {
486    pure_gamma_function(linear, 1f64 / 2.8f64)
487}
488
489#[cfg(feature = "extended_range")]
490#[inline]
491/// Pure gamma transfer function for gamma 2.8
492fn gamma2p8_from_linear_f(linear: f32) -> f32 {
493    pure_gamma_function_f(linear, 1. / 2.8)
494}
495
496#[inline]
497/// Linear transfer function for gamma 2.8
498fn gamma2p8_to_linear(gamma: f64) -> f64 {
499    pure_gamma_function(gamma, 2.8f64)
500}
501
502#[cfg(feature = "extended_range")]
503#[inline]
504/// Linear transfer function for gamma 2.8
505fn gamma2p8_to_linear_f(gamma: f32) -> f32 {
506    pure_gamma_function_f(gamma, 2.8)
507}
508
509#[inline]
510/// Linear transfer function for PQ
511pub(crate) fn pq_to_linear(gamma: f64) -> f64 {
512    if gamma > 0.0 {
513        let pow_gamma = f_pow(gamma, 1.0 / 78.84375);
514        let num = (pow_gamma - 0.8359375).max(0.);
515        let den = mlaf(18.8515625, -18.6875, pow_gamma).max(f64::MIN);
516        f_pow(num / den, 1.0 / 0.1593017578125)
517    } else {
518        0.0
519    }
520}
521
522/// Linear transfer function for PQ
523pub(crate) fn pq_to_linearf(gamma: f32) -> f32 {
524    if gamma > 0.0 {
525        let pow_gamma = f_powf(gamma, 1.0 / 78.84375);
526        let num = (pow_gamma - 0.8359375).max(0.);
527        let den = mlaf(18.8515625, -18.6875, pow_gamma).max(f32::MIN);
528        f_powf(num / den, 1.0 / 0.1593017578125)
529    } else {
530        0.0
531    }
532}
533
534/// Gamma transfer function for PQ
535fn pq_from_linear(linear: f64) -> f64 {
536    if linear > 0.0 {
537        let linear = linear.clamp(0., 1.);
538        let pow_linear = f_pow(linear, 0.1593017578125);
539        let num = fmla(0.1640625, pow_linear, -0.1640625);
540        let den = mlaf(1.0, 18.6875, pow_linear);
541        f_pow(1.0 + num / den, 78.84375)
542    } else {
543        0.0
544    }
545}
546
547#[inline]
548/// Gamma transfer function for PQ
549pub(crate) fn pq_from_linearf(linear: f32) -> f32 {
550    if linear > 0.0 {
551        let linear = linear.max(0.);
552        let pow_linear = f_powf(linear, 0.1593017578125);
553        let num = fmla(0.1640625, pow_linear, -0.1640625);
554        let den = mlaf(1.0, 18.6875, pow_linear);
555        f_powf(1.0 + num / den, 78.84375)
556    } else {
557        0.0
558    }
559}
560
561#[inline]
562/// Linear transfer function for HLG
563pub(crate) fn hlg_to_linear(gamma: f64) -> f64 {
564    if gamma < 0.0 {
565        return 0.0;
566    }
567    if gamma <= 0.5 {
568        f_pow((gamma * gamma) * (1.0 / 3.0), 1.2)
569    } else {
570        f_pow(
571            (f_exp((gamma - 0.55991073) / 0.17883277) + 0.28466892) / 12.0,
572            1.2,
573        )
574    }
575}
576
577#[cfg(feature = "extended_range")]
578/// Linear transfer function for HLG
579pub(crate) fn hlg_to_linearf(gamma: f32) -> f32 {
580    if gamma < 0.0 {
581        return 0.0;
582    }
583    if gamma <= 0.5 {
584        f_powf((gamma * gamma) * (1.0 / 3.0), 1.2)
585    } else {
586        f_powf(
587            (f_expf((gamma - 0.55991073) / 0.17883277) + 0.28466892) / 12.0,
588            1.2,
589        )
590    }
591}
592
593/// Gamma transfer function for HLG
594fn hlg_from_linear(linear: f64) -> f64 {
595    // Scale from extended SDR range to [0.0, 1.0].
596    let mut linear = linear.clamp(0., 1.);
597    // Inverse OOTF followed by OETF see Table 5 and Note 5i in ITU-R BT.2100-2 page 7-8.
598    linear = f_pow(linear, 1.0 / 1.2);
599    if linear < 0.0 {
600        0.0
601    } else if linear <= (1.0 / 12.0) {
602        (3.0 * linear).sqrt()
603    } else {
604        fmla(
605            0.17883277,
606            f_log(fmla(12.0, linear, -0.28466892)),
607            0.55991073,
608        )
609    }
610}
611
612#[cfg(feature = "extended_range")]
613/// Gamma transfer function for HLG
614fn hlg_from_linearf(linear: f32) -> f32 {
615    // Scale from extended SDR range to [0.0, 1.0].
616    let mut linear = linear.max(0.);
617    // Inverse OOTF followed by OETF see Table 5 and Note 5i in ITU-R BT.2100-2 page 7-8.
618    linear = f_powf(linear, 1.0 / 1.2);
619    if linear < 0.0 {
620        0.0
621    } else if linear <= (1.0 / 12.0) {
622        (3.0 * linear).sqrt()
623    } else {
624        0.17883277 * f_logf(12.0 * linear - 0.28466892) + 0.55991073
625    }
626}
627
628#[inline]
629fn trc_linear(v: f64) -> f64 {
630    v.min(1.).max(0.)
631}
632
633impl TransferCharacteristics {
634    pub fn linearize(self, v: f64) -> f64 {
635        match self {
636            TransferCharacteristics::Reserved => 0f64,
637            TransferCharacteristics::Bt709
638            | TransferCharacteristics::Bt601
639            | TransferCharacteristics::Bt202010bit
640            | TransferCharacteristics::Bt202012bit => rec709_to_linear(v),
641            TransferCharacteristics::Unspecified => 0f64,
642            TransferCharacteristics::Bt470M => gamma2p2_to_linear(v),
643            TransferCharacteristics::Bt470Bg => gamma2p8_to_linear(v),
644            TransferCharacteristics::Smpte240 => smpte240_to_linear(v),
645            TransferCharacteristics::Linear => trc_linear(v),
646            TransferCharacteristics::Log100 => log100_to_linear(v),
647            TransferCharacteristics::Log100sqrt10 => log100_sqrt10_to_linear(v),
648            TransferCharacteristics::Iec61966 => iec61966_to_linear(v),
649            TransferCharacteristics::Bt1361 => bt1361_to_linear(v),
650            TransferCharacteristics::Srgb => srgb_to_linear(v),
651            TransferCharacteristics::Smpte2084 => pq_to_linear(v),
652            TransferCharacteristics::Smpte428 => smpte428_to_linear(v),
653            TransferCharacteristics::Hlg => hlg_to_linear(v),
654        }
655    }
656
657    pub fn gamma(self, v: f64) -> f64 {
658        match self {
659            TransferCharacteristics::Reserved => 0f64,
660            TransferCharacteristics::Bt709
661            | TransferCharacteristics::Bt601
662            | TransferCharacteristics::Bt202010bit
663            | TransferCharacteristics::Bt202012bit => rec709_from_linear(v),
664            TransferCharacteristics::Unspecified => 0f64,
665            TransferCharacteristics::Bt470M => gamma2p2_from_linear(v),
666            TransferCharacteristics::Bt470Bg => gamma2p8_from_linear(v),
667            TransferCharacteristics::Smpte240 => smpte240_from_linear(v),
668            TransferCharacteristics::Linear => trc_linear(v),
669            TransferCharacteristics::Log100 => log100_from_linear(v),
670            TransferCharacteristics::Log100sqrt10 => log100_sqrt10_from_linear(v),
671            TransferCharacteristics::Iec61966 => iec61966_from_linear(v),
672            TransferCharacteristics::Bt1361 => bt1361_from_linear(v),
673            TransferCharacteristics::Srgb => srgb_from_linear(v),
674            TransferCharacteristics::Smpte2084 => pq_from_linear(v),
675            TransferCharacteristics::Smpte428 => smpte428_from_linear(v),
676            TransferCharacteristics::Hlg => hlg_from_linear(v),
677        }
678    }
679
680    #[cfg(feature = "extended_range")]
681    pub(crate) fn extended_gamma_tristimulus(self) -> fn(Rgb<f32>) -> Rgb<f32> {
682        match self {
683            TransferCharacteristics::Reserved => |x| Rgb::new(x.r, x.g, x.b),
684            TransferCharacteristics::Bt709
685            | TransferCharacteristics::Bt601
686            | TransferCharacteristics::Bt202010bit
687            | TransferCharacteristics::Bt202012bit => |x| {
688                Rgb::new(
689                    rec709_from_linearf_extended(x.r),
690                    rec709_from_linearf_extended(x.g),
691                    rec709_from_linearf_extended(x.b),
692                )
693            },
694            TransferCharacteristics::Unspecified => |x| Rgb::new(x.r, x.g, x.b),
695            TransferCharacteristics::Bt470M => |x| {
696                Rgb::new(
697                    gamma2p2_from_linear_f(x.r),
698                    gamma2p2_from_linear_f(x.g),
699                    gamma2p2_from_linear_f(x.b),
700                )
701            },
702            TransferCharacteristics::Bt470Bg => |x| {
703                Rgb::new(
704                    gamma2p8_from_linear_f(x.r),
705                    gamma2p8_from_linear_f(x.g),
706                    gamma2p8_from_linear_f(x.b),
707                )
708            },
709            TransferCharacteristics::Smpte240 => |x| {
710                Rgb::new(
711                    smpte240_from_linearf_extended(x.r),
712                    smpte240_from_linearf_extended(x.g),
713                    smpte240_from_linearf_extended(x.b),
714                )
715            },
716            TransferCharacteristics::Linear => |x| Rgb::new(x.r, x.g, x.b),
717            TransferCharacteristics::Log100 => |x| {
718                Rgb::new(
719                    log100_from_linearf(x.r),
720                    log100_from_linearf(x.g),
721                    log100_from_linearf(x.b),
722                )
723            },
724            TransferCharacteristics::Log100sqrt10 => |x| {
725                Rgb::new(
726                    log100_sqrt10_from_linearf(x.r),
727                    log100_sqrt10_from_linearf(x.g),
728                    log100_sqrt10_from_linearf(x.b),
729                )
730            },
731            TransferCharacteristics::Iec61966 => |x| {
732                Rgb::new(
733                    iec61966_from_linearf(x.r),
734                    iec61966_from_linearf(x.g),
735                    iec61966_from_linearf(x.b),
736                )
737            },
738            TransferCharacteristics::Bt1361 => |x| {
739                Rgb::new(
740                    bt1361_from_linearf(x.r),
741                    bt1361_from_linearf(x.g),
742                    bt1361_from_linearf(x.b),
743                )
744            },
745            TransferCharacteristics::Srgb => |x| {
746                Rgb::new(
747                    srgb_from_linear_extended(x.r),
748                    srgb_from_linear_extended(x.g),
749                    srgb_from_linear_extended(x.b),
750                )
751            },
752            TransferCharacteristics::Smpte2084 => |x| {
753                Rgb::new(
754                    pq_from_linearf(x.r),
755                    pq_from_linearf(x.g),
756                    pq_from_linearf(x.b),
757                )
758            },
759            TransferCharacteristics::Smpte428 => |x| {
760                Rgb::new(
761                    smpte428_from_linearf(x.r),
762                    smpte428_from_linearf(x.g),
763                    smpte428_from_linearf(x.b),
764                )
765            },
766            TransferCharacteristics::Hlg => |x| {
767                Rgb::new(
768                    hlg_from_linearf(x.r),
769                    hlg_from_linearf(x.g),
770                    hlg_from_linearf(x.b),
771                )
772            },
773        }
774    }
775
776    #[cfg(feature = "extended_range")]
777    pub(crate) fn extended_gamma_single(self) -> fn(f32) -> f32 {
778        match self {
779            TransferCharacteristics::Reserved => |x| x,
780            TransferCharacteristics::Bt709
781            | TransferCharacteristics::Bt601
782            | TransferCharacteristics::Bt202010bit
783            | TransferCharacteristics::Bt202012bit => |x| rec709_from_linearf_extended(x),
784            TransferCharacteristics::Unspecified => |x| x,
785            TransferCharacteristics::Bt470M => |x| gamma2p2_from_linear_f(x),
786            TransferCharacteristics::Bt470Bg => |x| gamma2p8_from_linear_f(x),
787            TransferCharacteristics::Smpte240 => |x| smpte240_from_linearf_extended(x),
788            TransferCharacteristics::Linear => |x| x,
789            TransferCharacteristics::Log100 => |x| log100_from_linearf(x),
790            TransferCharacteristics::Log100sqrt10 => |x| log100_sqrt10_from_linearf(x),
791            TransferCharacteristics::Iec61966 => |x| iec61966_from_linearf(x),
792            TransferCharacteristics::Bt1361 => |x| bt1361_from_linearf(x),
793            TransferCharacteristics::Srgb => |x| srgb_from_linear_extended(x),
794            TransferCharacteristics::Smpte2084 => |x| pq_from_linearf(x),
795            TransferCharacteristics::Smpte428 => |x| smpte428_from_linearf(x),
796            TransferCharacteristics::Hlg => |x| hlg_from_linearf(x),
797        }
798    }
799
800    #[cfg(feature = "extended_range")]
801    pub(crate) fn extended_linear_tristimulus(self) -> fn(Rgb<f32>) -> Rgb<f32> {
802        match self {
803            TransferCharacteristics::Reserved => |x| Rgb::new(x.r, x.g, x.b),
804            TransferCharacteristics::Bt709
805            | TransferCharacteristics::Bt601
806            | TransferCharacteristics::Bt202010bit
807            | TransferCharacteristics::Bt202012bit => |x| {
808                Rgb::new(
809                    rec709_to_linearf_extended(x.r),
810                    rec709_to_linearf_extended(x.g),
811                    rec709_to_linearf_extended(x.b),
812                )
813            },
814            TransferCharacteristics::Unspecified => |x| Rgb::new(x.r, x.g, x.b),
815            TransferCharacteristics::Bt470M => |x| {
816                Rgb::new(
817                    gamma2p2_to_linear_f(x.r),
818                    gamma2p2_to_linear_f(x.g),
819                    gamma2p2_to_linear_f(x.b),
820                )
821            },
822            TransferCharacteristics::Bt470Bg => |x| {
823                Rgb::new(
824                    gamma2p8_to_linear_f(x.r),
825                    gamma2p8_to_linear_f(x.g),
826                    gamma2p8_to_linear_f(x.b),
827                )
828            },
829            TransferCharacteristics::Smpte240 => |x| {
830                Rgb::new(
831                    smpte240_to_linearf_extended(x.r),
832                    smpte240_to_linearf_extended(x.g),
833                    smpte240_to_linearf_extended(x.b),
834                )
835            },
836            TransferCharacteristics::Linear => |x| Rgb::new(x.r, x.g, x.b),
837            TransferCharacteristics::Log100 => |x| {
838                Rgb::new(
839                    log100_to_linearf(x.r),
840                    log100_to_linearf(x.g),
841                    log100_to_linearf(x.b),
842                )
843            },
844            TransferCharacteristics::Log100sqrt10 => |x| {
845                Rgb::new(
846                    log100_sqrt10_to_linearf(x.r),
847                    log100_sqrt10_to_linearf(x.g),
848                    log100_sqrt10_to_linearf(x.b),
849                )
850            },
851            TransferCharacteristics::Iec61966 => |x| {
852                Rgb::new(
853                    iec61966_to_linearf(x.r),
854                    iec61966_to_linearf(x.g),
855                    iec61966_to_linearf(x.b),
856                )
857            },
858            TransferCharacteristics::Bt1361 => |x| {
859                Rgb::new(
860                    bt1361_to_linearf(x.r),
861                    bt1361_to_linearf(x.g),
862                    bt1361_to_linearf(x.b),
863                )
864            },
865            TransferCharacteristics::Srgb => |x| {
866                Rgb::new(
867                    srgb_to_linearf_extended(x.r),
868                    srgb_to_linearf_extended(x.g),
869                    srgb_to_linearf_extended(x.b),
870                )
871            },
872            TransferCharacteristics::Smpte2084 => {
873                |x| Rgb::new(pq_to_linearf(x.r), pq_to_linearf(x.g), pq_to_linearf(x.b))
874            }
875            TransferCharacteristics::Smpte428 => |x| {
876                Rgb::new(
877                    smpte428_to_linearf_extended(x.r),
878                    smpte428_to_linearf_extended(x.g),
879                    smpte428_to_linearf_extended(x.b),
880                )
881            },
882            TransferCharacteristics::Hlg => |x| {
883                Rgb::new(
884                    hlg_to_linearf(x.r),
885                    hlg_to_linearf(x.g),
886                    hlg_to_linearf(x.b),
887                )
888            },
889        }
890    }
891
892    #[cfg(feature = "extended_range")]
893    pub(crate) fn extended_linear_single(self) -> fn(f32) -> f32 {
894        match self {
895            TransferCharacteristics::Reserved => |x| x,
896            TransferCharacteristics::Bt709
897            | TransferCharacteristics::Bt601
898            | TransferCharacteristics::Bt202010bit
899            | TransferCharacteristics::Bt202012bit => |x| rec709_to_linearf_extended(x),
900            TransferCharacteristics::Unspecified => |x| x,
901            TransferCharacteristics::Bt470M => |x| gamma2p2_to_linear_f(x),
902            TransferCharacteristics::Bt470Bg => |x| gamma2p8_to_linear_f(x),
903            TransferCharacteristics::Smpte240 => |x| smpte240_to_linearf_extended(x),
904            TransferCharacteristics::Linear => |x| x,
905            TransferCharacteristics::Log100 => |x| log100_to_linearf(x),
906            TransferCharacteristics::Log100sqrt10 => |x| log100_sqrt10_to_linearf(x),
907            TransferCharacteristics::Iec61966 => |x| iec61966_to_linearf(x),
908            TransferCharacteristics::Bt1361 => |x| bt1361_to_linearf(x),
909            TransferCharacteristics::Srgb => |x| srgb_to_linearf_extended(x),
910            TransferCharacteristics::Smpte2084 => |x| pq_to_linearf(x),
911            TransferCharacteristics::Smpte428 => |x| smpte428_to_linearf_extended(x),
912            TransferCharacteristics::Hlg => |x| hlg_to_linearf(x),
913        }
914    }
915
916    pub(crate) fn make_linear_table<
917        T: PointeeSizeExpressible,
918        const N: usize,
919        const BIT_DEPTH: usize,
920    >(
921        &self,
922    ) -> Box<[f32; N]> {
923        let mut gamma_table = Box::new([0f32; N]);
924        let max_value = if T::FINITE {
925            (1 << BIT_DEPTH) - 1
926        } else {
927            T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
928        };
929        let cap_values = if T::FINITE {
930            (1u32 << BIT_DEPTH) as usize
931        } else {
932            T::NOT_FINITE_LINEAR_TABLE_SIZE
933        };
934        assert!(cap_values <= N, "Invalid lut table construction");
935        let scale_value = 1f64 / max_value as f64;
936        for (i, g) in gamma_table.iter_mut().enumerate().take(cap_values) {
937            *g = self.linearize(i as f64 * scale_value) as f32;
938        }
939        gamma_table
940    }
941
942    pub(crate) fn make_gamma_table<
943        T: Default + Copy + 'static + PointeeSizeExpressible,
944        const BUCKET: usize,
945        const N: usize,
946    >(
947        &self,
948        bit_depth: usize,
949    ) -> Box<[T; BUCKET]>
950    where
951        f32: AsPrimitive<T>,
952    {
953        let mut table = Box::new([T::default(); BUCKET]);
954        let max_range = 1f64 / (N - 1) as f64;
955        let max_value = ((1 << bit_depth) - 1) as f64;
956        if T::FINITE {
957            for (v, output) in table.iter_mut().take(N).enumerate() {
958                *output = ((self.gamma(v as f64 * max_range) * max_value) as f32)
959                    .round()
960                    .as_();
961            }
962        } else {
963            for (v, output) in table.iter_mut().take(N).enumerate() {
964                *output = (self.gamma(v as f64 * max_range) as f32).as_();
965            }
966        }
967        table
968    }
969}
970
971#[cfg(test)]
972mod tests {
973    use super::*;
974
975    #[test]
976    fn srgb_test() {
977        let srgb_0 = srgb_to_linear(0.5);
978        let srgb_1 = srgb_from_linear(srgb_0);
979        assert!((0.5 - srgb_1).abs() < 1e-9f64);
980    }
981
982    #[test]
983    fn log100_sqrt10_test() {
984        let srgb_0 = log100_sqrt10_to_linear(0.5);
985        let srgb_1 = log100_sqrt10_from_linear(srgb_0);
986        assert_eq!(0.5, srgb_1);
987    }
988
989    #[test]
990    fn log100_test() {
991        let srgb_0 = log100_to_linear(0.5);
992        let srgb_1 = log100_from_linear(srgb_0);
993        assert_eq!(0.5, srgb_1);
994    }
995
996    #[test]
997    fn iec61966_test() {
998        let srgb_0 = iec61966_to_linear(0.5);
999        let srgb_1 = iec61966_from_linear(srgb_0);
1000        assert!((0.5 - srgb_1).abs() < 1e-9f64);
1001    }
1002
1003    #[test]
1004    fn smpte240_test() {
1005        let srgb_0 = smpte240_to_linear(0.5);
1006        let srgb_1 = smpte240_from_linear(srgb_0);
1007        assert!((0.5 - srgb_1).abs() < 1e-9f64);
1008    }
1009
1010    #[test]
1011    fn smpte428_test() {
1012        let srgb_0 = smpte428_to_linear(0.5);
1013        let srgb_1 = smpte428_from_linear(srgb_0);
1014        assert!((0.5 - srgb_1).abs() < 1e-9f64);
1015    }
1016
1017    #[test]
1018    fn rec709_test() {
1019        let srgb_0 = rec709_to_linear(0.5);
1020        let srgb_1 = rec709_from_linear(srgb_0);
1021        assert!((0.5 - srgb_1).abs() < 1e-9f64);
1022    }
1023
1024    #[cfg(feature = "extended_range")]
1025    #[test]
1026    fn rec709f_test() {
1027        let srgb_0 = rec709_to_linearf_extended(0.5);
1028        let srgb_1 = rec709_from_linearf_extended(srgb_0);
1029        assert!((0.5 - srgb_1).abs() < 1e-5f32);
1030    }
1031
1032    #[cfg(feature = "extended_range")]
1033    #[test]
1034    fn srgbf_test() {
1035        let srgb_0 = srgb_to_linearf_extended(0.5);
1036        let srgb_1 = srgb_from_linear_extended(srgb_0);
1037        assert!((0.5 - srgb_1).abs() < 1e-5f32);
1038    }
1039
1040    #[test]
1041    fn hlg_test() {
1042        let z0 = hlg_to_linear(0.5);
1043        let z1 = hlg_from_linear(z0);
1044        assert!((0.5 - z1).abs() < 1e-5f64);
1045    }
1046
1047    #[test]
1048    fn pq_test() {
1049        let z0 = pq_to_linear(0.5);
1050        let z1 = pq_from_linear(z0);
1051        assert!((0.5 - z1).abs() < 1e-5f64);
1052    }
1053
1054    #[test]
1055    fn pqf_test() {
1056        let z0 = pq_to_linearf(0.5);
1057        let z1 = pq_from_linearf(z0);
1058        assert!((0.5 - z1).abs() < 1e-5f32);
1059    }
1060
1061    #[test]
1062    fn iec_test() {
1063        let z0 = iec61966_to_linear(0.5);
1064        let z1 = iec61966_from_linear(z0);
1065        assert!((0.5 - z1).abs() < 1e-5f64);
1066    }
1067
1068    #[test]
1069    fn bt1361_test() {
1070        let z0 = bt1361_to_linear(0.5);
1071        let z1 = bt1361_from_linear(z0);
1072        assert!((0.5 - z1).abs() < 1e-5f64);
1073    }
1074}