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