Coverage Report

Created: 2026-03-10 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.8.1/src/gamma.rs
Line
Count
Source
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
 */
29
use crate::mlaf::{fmla, mlaf};
30
use crate::transform::PointeeSizeExpressible;
31
use crate::*;
32
use num_traits::AsPrimitive;
33
use pxfm::*;
34
35
#[inline]
36
/// Linear transfer function for sRGB
37
0
fn srgb_to_linear(gamma: f64) -> f64 {
38
0
    if gamma < 0f64 {
39
0
        0f64
40
0
    } else if gamma < 12.92f64 * 0.0030412825601275209f64 {
41
0
        gamma * (1f64 / 12.92f64)
42
0
    } else if gamma < 1.0f64 {
43
0
        f_pow(
44
0
            (gamma + 0.0550107189475866f64) / 1.0550107189475866f64,
45
            2.4f64,
46
        )
47
    } else {
48
0
        1.0f64
49
    }
50
0
}
51
52
#[inline]
53
#[cfg(feature = "extended_range")]
54
/// Linear transfer function for sRGB
55
fn 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
65
0
fn srgb_from_linear(linear: f64) -> f64 {
66
0
    if linear < 0.0f64 {
67
0
        0.0f64
68
0
    } else if linear < 0.0030412825601275209f64 {
69
0
        linear * 12.92f64
70
0
    } else if linear < 1.0f64 {
71
0
        fmla(
72
            1.0550107189475866f64,
73
0
            f_pow(linear, 1.0f64 / 2.4f64),
74
            -0.0550107189475866f64,
75
        )
76
    } else {
77
0
        1.0f64
78
    }
79
0
}
80
81
/// Gamma transfer function for sRGB
82
#[cfg(feature = "extended_range")]
83
pub(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
97
0
fn rec709_to_linear(gamma: f64) -> f64 {
98
0
    if gamma < 0.0f64 {
99
0
        0.0f64
100
0
    } else if gamma < 4.5f64 * 0.018053968510807f64 {
101
0
        gamma * (1f64 / 4.5f64)
102
0
    } else if gamma < 1.0f64 {
103
0
        f_pow(
104
0
            (gamma + 0.09929682680944f64) / 1.09929682680944f64,
105
0
            1.0f64 / 0.45f64,
106
        )
107
    } else {
108
0
        1.0f64
109
    }
110
0
}
111
112
#[inline]
113
#[cfg(feature = "extended_range")]
114
/// Linear transfer function for Rec.709
115
fn 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
125
0
fn rec709_from_linear(linear: f64) -> f64 {
126
0
    if linear < 0.0f64 {
127
0
        0.0f64
128
0
    } else if linear < 0.018053968510807f64 {
129
0
        linear * 4.5f64
130
0
    } else if linear < 1.0f64 {
131
0
        fmla(
132
            1.09929682680944f64,
133
0
            f_pow(linear, 0.45f64),
134
            -0.09929682680944f64,
135
        )
136
    } else {
137
0
        1.0f64
138
    }
139
0
}
140
141
/// Gamma transfer function for Rec.709
142
#[cfg(feature = "extended_range")]
143
fn 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
156
0
pub(crate) fn smpte428_to_linear(gamma: f64) -> f64 {
157
    const SCALE: f64 = 1. / 0.91655527974030934f64;
158
0
    f_pow(gamma.max(0.).min(1f64), 2.6f64) * SCALE
159
0
}
160
161
#[cfg(feature = "extended_range")]
162
/// Linear transfer function for Smpte 428
163
pub(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
169
0
fn smpte428_from_linear(linear: f64) -> f64 {
170
    const POWER_VALUE: f64 = 1.0f64 / 2.6f64;
171
0
    f_pow(0.91655527974030934f64 * linear.max(0.), POWER_VALUE)
172
0
}
173
174
#[cfg(feature = "extended_range")]
175
/// Gamma transfer function for Smpte 428
176
fn 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
182
0
pub(crate) fn smpte240_to_linear(gamma: f64) -> f64 {
183
0
    if gamma < 0.0 {
184
0
        0.0
185
0
    } else if gamma < 4.0 * 0.022821585529445 {
186
0
        gamma / 4.0
187
0
    } else if gamma < 1.0 {
188
0
        f_pow((gamma + 0.111572195921731) / 1.111572195921731, 1.0 / 0.45)
189
    } else {
190
0
        1.0
191
    }
192
0
}
193
194
#[cfg(feature = "extended_range")]
195
/// Linear transfer function for Smpte 240
196
pub(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
205
0
fn smpte240_from_linear(linear: f64) -> f64 {
206
0
    if linear < 0.0 {
207
0
        0.0
208
0
    } else if linear < 0.022821585529445 {
209
0
        linear * 4.0
210
0
    } else if linear < 1.0 {
211
0
        fmla(1.111572195921731, f_pow(linear, 0.45), -0.111572195921731)
212
    } else {
213
0
        1.0
214
    }
215
0
}
216
217
#[cfg(feature = "extended_range")]
218
/// Gamma transfer function for Smpte 240
219
fn 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
229
0
fn log100_from_linear(linear: f64) -> f64 {
230
0
    if linear <= 0.01f64 {
231
0
        0.
232
    } else {
233
0
        1. + f_log10(linear.min(1.)) / 2.0
234
    }
235
0
}
236
237
#[cfg(feature = "extended_range")]
238
/// Gamma transfer function for Log100
239
fn 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
248
0
pub(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
0
    if gamma <= 0. {
252
0
        MID_INTERVAL
253
    } else {
254
0
        f_exp10(2. * (gamma.min(1.) - 1.))
255
    }
256
0
}
257
258
#[cfg(feature = "extended_range")]
259
/// Linear transfer function for Log100
260
pub(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
272
0
pub(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
0
    if gamma <= 0. {
276
0
        MID_INTERVAL
277
    } else {
278
0
        f_exp10(2.5 * (gamma.min(1.) - 1.))
279
    }
280
0
}
281
282
#[cfg(feature = "extended_range")]
283
/// Linear transfer function for Log100Sqrt10
284
pub(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
295
0
fn log100_sqrt10_from_linear(linear: f64) -> f64 {
296
0
    if linear <= 0.00316227766 {
297
0
        0.0
298
    } else {
299
0
        1.0 + f_log10(linear.min(1.)) / 2.5
300
    }
301
0
}
302
303
#[cfg(feature = "extended_range")]
304
/// Gamma transfer function for Log100Sqrt10
305
fn 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
314
0
fn bt1361_from_linear(linear: f64) -> f64 {
315
0
    if linear < -0.25 {
316
0
        -0.25
317
0
    } else if linear < 0.0 {
318
0
        fmla(
319
            -0.27482420670236,
320
0
            f_pow(-4.0 * linear, 0.45),
321
            0.02482420670236,
322
        )
323
0
    } else if linear < 0.018053968510807 {
324
0
        linear * 4.5
325
0
    } else if linear < 1.0 {
326
0
        fmla(1.09929682680944, f_pow(linear, 0.45), -0.09929682680944)
327
    } else {
328
0
        1.0
329
    }
330
0
}
331
332
#[cfg(feature = "extended_range")]
333
/// Gamma transfer function for Bt.1361
334
fn 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
357
0
pub(crate) fn bt1361_to_linear(gamma: f64) -> f64 {
358
0
    if gamma < -0.25f64 {
359
0
        -0.25f64
360
0
    } else if gamma < 0.0f64 {
361
0
        f_pow(
362
0
            (gamma - 0.02482420670236f64) / -0.27482420670236f64,
363
0
            1.0f64 / 0.45f64,
364
0
        ) / -4.0f64
365
0
    } else if gamma < 4.5 * 0.018053968510807 {
366
0
        gamma / 4.5
367
0
    } else if gamma < 1.0 {
368
0
        f_pow((gamma + 0.09929682680944) / 1.09929682680944, 1.0 / 0.45)
369
    } else {
370
0
        1.0f64
371
    }
372
0
}
373
374
#[cfg(feature = "extended_range")]
375
/// Linear transfer function for Bt.1361
376
fn 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
392
0
fn pure_gamma_function(x: f64, gamma: f64) -> f64 {
393
0
    if x <= 0f64 {
394
0
        0f64
395
0
    } else if x >= 1f64 {
396
0
        1f64
397
    } else {
398
0
        f_pow(x, gamma)
399
    }
400
0
}
401
402
#[cfg(feature = "extended_range")]
403
#[inline(always)]
404
/// Pure gamma transfer function for gamma 2.2
405
fn pure_gamma_function_f(x: f32, gamma: f32) -> f32 {
406
    if x <= 0. { 0. } else { dirty_powf(x, gamma) }
407
}
408
409
0
pub(crate) fn iec61966_to_linear(gamma: f64) -> f64 {
410
0
    if gamma < -4.5f64 * 0.018053968510807f64 {
411
0
        f_pow(
412
0
            (-gamma + 0.09929682680944f64) / -1.09929682680944f64,
413
0
            1.0 / 0.45,
414
        )
415
0
    } else if gamma < 4.5f64 * 0.018053968510807f64 {
416
0
        gamma / 4.5
417
    } else {
418
0
        f_pow(
419
0
            (gamma + 0.09929682680944f64) / 1.09929682680944f64,
420
0
            1.0 / 0.45,
421
        )
422
    }
423
0
}
424
425
#[cfg(feature = "extended_range")]
426
fn 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
436
0
fn iec61966_from_linear(v: f64) -> f64 {
437
0
    if v < -0.018053968510807f64 {
438
0
        fmla(-1.09929682680944f64, f_pow(-v, 0.45), 0.09929682680944f64)
439
0
    } else if v < 0.018053968510807f64 {
440
0
        v * 4.5f64
441
    } else {
442
0
        fmla(1.09929682680944f64, f_pow(v, 0.45), -0.09929682680944f64)
443
    }
444
0
}
445
446
#[cfg(feature = "extended_range")]
447
fn 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
459
0
fn gamma2p2_from_linear(linear: f64) -> f64 {
460
0
    pure_gamma_function(linear, 1f64 / 2.2f64)
461
0
}
462
463
#[cfg(feature = "extended_range")]
464
#[inline]
465
/// Pure gamma transfer function for gamma 2.2
466
fn 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
472
0
fn gamma2p2_to_linear(gamma: f64) -> f64 {
473
0
    pure_gamma_function(gamma, 2.2f64)
474
0
}
475
476
#[cfg(feature = "extended_range")]
477
#[inline]
478
/// Linear transfer function for gamma 2.2
479
fn 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
485
0
fn gamma2p8_from_linear(linear: f64) -> f64 {
486
0
    pure_gamma_function(linear, 1f64 / 2.8f64)
487
0
}
488
489
#[cfg(feature = "extended_range")]
490
#[inline]
491
/// Pure gamma transfer function for gamma 2.8
492
fn 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
498
0
fn gamma2p8_to_linear(gamma: f64) -> f64 {
499
0
    pure_gamma_function(gamma, 2.8f64)
500
0
}
501
502
#[cfg(feature = "extended_range")]
503
#[inline]
504
/// Linear transfer function for gamma 2.8
505
fn 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
511
0
pub(crate) fn pq_to_linear(gamma: f64) -> f64 {
512
0
    if gamma > 0.0 {
513
0
        let pow_gamma = f_pow(gamma, 1.0 / 78.84375);
514
0
        let num = (pow_gamma - 0.8359375).max(0.);
515
0
        let den = mlaf(18.8515625, -18.6875, pow_gamma).max(f64::MIN);
516
0
        f_pow(num / den, 1.0 / 0.1593017578125)
517
    } else {
518
0
        0.0
519
    }
520
0
}
521
522
/// Linear transfer function for PQ
523
0
pub(crate) fn pq_to_linearf(gamma: f32) -> f32 {
524
0
    if gamma > 0.0 {
525
0
        let pow_gamma = f_powf(gamma, 1.0 / 78.84375);
526
0
        let num = (pow_gamma - 0.8359375).max(0.);
527
0
        let den = mlaf(18.8515625, -18.6875, pow_gamma).max(f32::MIN);
528
0
        f_powf(num / den, 1.0 / 0.1593017578125)
529
    } else {
530
0
        0.0
531
    }
532
0
}
533
534
/// Gamma transfer function for PQ
535
0
fn pq_from_linear(linear: f64) -> f64 {
536
0
    if linear > 0.0 {
537
0
        let linear = linear.clamp(0., 1.);
538
0
        let pow_linear = f_pow(linear, 0.1593017578125);
539
0
        let num = fmla(0.1640625, pow_linear, -0.1640625);
540
0
        let den = mlaf(1.0, 18.6875, pow_linear);
541
0
        f_pow(1.0 + num / den, 78.84375)
542
    } else {
543
0
        0.0
544
    }
545
0
}
546
547
#[inline]
548
/// Gamma transfer function for PQ
549
0
pub(crate) fn pq_from_linearf(linear: f32) -> f32 {
550
0
    if linear > 0.0 {
551
0
        let linear = linear.max(0.);
552
0
        let pow_linear = f_powf(linear, 0.1593017578125);
553
0
        let num = fmla(0.1640625, pow_linear, -0.1640625);
554
0
        let den = mlaf(1.0, 18.6875, pow_linear);
555
0
        f_powf(1.0 + num / den, 78.84375)
556
    } else {
557
0
        0.0
558
    }
559
0
}
560
561
#[inline]
562
/// Linear transfer function for HLG
563
0
pub(crate) fn hlg_to_linear(gamma: f64) -> f64 {
564
0
    if gamma < 0.0 {
565
0
        return 0.0;
566
0
    }
567
0
    if gamma <= 0.5 {
568
0
        f_pow((gamma * gamma) * (1.0 / 3.0), 1.2)
569
    } else {
570
0
        f_pow(
571
0
            (f_exp((gamma - 0.55991073) / 0.17883277) + 0.28466892) / 12.0,
572
            1.2,
573
        )
574
    }
575
0
}
576
577
#[cfg(feature = "extended_range")]
578
/// Linear transfer function for HLG
579
pub(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
594
0
fn hlg_from_linear(linear: f64) -> f64 {
595
    // Scale from extended SDR range to [0.0, 1.0].
596
0
    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
0
    linear = f_pow(linear, 1.0 / 1.2);
599
0
    if linear < 0.0 {
600
0
        0.0
601
0
    } else if linear <= (1.0 / 12.0) {
602
0
        (3.0 * linear).sqrt()
603
    } else {
604
0
        fmla(
605
            0.17883277,
606
0
            f_log(fmla(12.0, linear, -0.28466892)),
607
            0.55991073,
608
        )
609
    }
610
0
}
611
612
#[cfg(feature = "extended_range")]
613
/// Gamma transfer function for HLG
614
fn 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]
629
0
fn trc_linear(v: f64) -> f64 {
630
0
    v.min(1.).max(0.)
631
0
}
632
633
impl TransferCharacteristics {
634
0
    pub fn linearize(self, v: f64) -> f64 {
635
0
        match self {
636
0
            TransferCharacteristics::Reserved => 0f64,
637
            TransferCharacteristics::Bt709
638
            | TransferCharacteristics::Bt601
639
            | TransferCharacteristics::Bt202010bit
640
0
            | TransferCharacteristics::Bt202012bit => rec709_to_linear(v),
641
0
            TransferCharacteristics::Unspecified => 0f64,
642
0
            TransferCharacteristics::Bt470M => gamma2p2_to_linear(v),
643
0
            TransferCharacteristics::Bt470Bg => gamma2p8_to_linear(v),
644
0
            TransferCharacteristics::Smpte240 => smpte240_to_linear(v),
645
0
            TransferCharacteristics::Linear => trc_linear(v),
646
0
            TransferCharacteristics::Log100 => log100_to_linear(v),
647
0
            TransferCharacteristics::Log100sqrt10 => log100_sqrt10_to_linear(v),
648
0
            TransferCharacteristics::Iec61966 => iec61966_to_linear(v),
649
0
            TransferCharacteristics::Bt1361 => bt1361_to_linear(v),
650
0
            TransferCharacteristics::Srgb => srgb_to_linear(v),
651
0
            TransferCharacteristics::Smpte2084 => pq_to_linear(v),
652
0
            TransferCharacteristics::Smpte428 => smpte428_to_linear(v),
653
0
            TransferCharacteristics::Hlg => hlg_to_linear(v),
654
        }
655
0
    }
656
657
0
    pub fn gamma(self, v: f64) -> f64 {
658
0
        match self {
659
0
            TransferCharacteristics::Reserved => 0f64,
660
            TransferCharacteristics::Bt709
661
            | TransferCharacteristics::Bt601
662
            | TransferCharacteristics::Bt202010bit
663
0
            | TransferCharacteristics::Bt202012bit => rec709_from_linear(v),
664
0
            TransferCharacteristics::Unspecified => 0f64,
665
0
            TransferCharacteristics::Bt470M => gamma2p2_from_linear(v),
666
0
            TransferCharacteristics::Bt470Bg => gamma2p8_from_linear(v),
667
0
            TransferCharacteristics::Smpte240 => smpte240_from_linear(v),
668
0
            TransferCharacteristics::Linear => trc_linear(v),
669
0
            TransferCharacteristics::Log100 => log100_from_linear(v),
670
0
            TransferCharacteristics::Log100sqrt10 => log100_sqrt10_from_linear(v),
671
0
            TransferCharacteristics::Iec61966 => iec61966_from_linear(v),
672
0
            TransferCharacteristics::Bt1361 => bt1361_from_linear(v),
673
0
            TransferCharacteristics::Srgb => srgb_from_linear(v),
674
0
            TransferCharacteristics::Smpte2084 => pq_from_linear(v),
675
0
            TransferCharacteristics::Smpte428 => smpte428_from_linear(v),
676
0
            TransferCharacteristics::Hlg => hlg_from_linear(v),
677
        }
678
0
    }
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
0
    pub(crate) fn make_linear_table<
917
0
        T: PointeeSizeExpressible,
918
0
        const N: usize,
919
0
        const BIT_DEPTH: usize,
920
0
    >(
921
0
        &self,
922
0
    ) -> Box<[f32; N]> {
923
0
        let mut gamma_table = Box::new([0f32; N]);
924
0
        let max_value = if T::FINITE {
925
0
            (1 << BIT_DEPTH) - 1
926
        } else {
927
0
            T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
928
        };
929
0
        let cap_values = if T::FINITE {
930
0
            (1u32 << BIT_DEPTH) as usize
931
        } else {
932
0
            T::NOT_FINITE_LINEAR_TABLE_SIZE
933
        };
934
0
        assert!(cap_values <= N, "Invalid lut table construction");
935
0
        let scale_value = 1f64 / max_value as f64;
936
0
        for (i, g) in gamma_table.iter_mut().enumerate().take(cap_values) {
937
0
            *g = self.linearize(i as f64 * scale_value) as f32;
938
0
        }
939
0
        gamma_table
940
0
    }
Unexecuted instantiation: <moxcms::cicp::TransferCharacteristics>::make_linear_table::<f64, 65536, 1>
Unexecuted instantiation: <moxcms::cicp::TransferCharacteristics>::make_linear_table::<f32, 65536, 1>
Unexecuted instantiation: <moxcms::cicp::TransferCharacteristics>::make_linear_table::<u8, 256, 8>
Unexecuted instantiation: <moxcms::cicp::TransferCharacteristics>::make_linear_table::<u16, 65536, 16>
Unexecuted instantiation: <moxcms::cicp::TransferCharacteristics>::make_linear_table::<u16, 65536, 10>
Unexecuted instantiation: <moxcms::cicp::TransferCharacteristics>::make_linear_table::<u16, 65536, 12>
941
942
0
    pub(crate) fn make_gamma_table<
943
0
        T: Default + Copy + 'static + PointeeSizeExpressible,
944
0
        const BUCKET: usize,
945
0
        const N: usize,
946
0
    >(
947
0
        &self,
948
0
        bit_depth: usize,
949
0
    ) -> Box<[T; BUCKET]>
950
0
    where
951
0
        f32: AsPrimitive<T>,
952
    {
953
0
        let mut table = Box::new([T::default(); BUCKET]);
954
0
        let max_range = 1f64 / (N - 1) as f64;
955
0
        let max_value = ((1 << bit_depth) - 1) as f64;
956
0
        if T::FINITE {
957
0
            for (v, output) in table.iter_mut().take(N).enumerate() {
958
0
                *output = ((self.gamma(v as f64 * max_range) * max_value) as f32)
959
0
                    .round()
960
0
                    .as_();
961
0
            }
962
        } else {
963
0
            for (v, output) in table.iter_mut().take(N).enumerate() {
964
0
                *output = (self.gamma(v as f64 * max_range) as f32).as_();
965
0
            }
966
        }
967
0
        table
968
0
    }
Unexecuted instantiation: <moxcms::cicp::TransferCharacteristics>::make_gamma_table::<f64, 65536, 65536>
Unexecuted instantiation: <moxcms::cicp::TransferCharacteristics>::make_gamma_table::<f32, 65536, 32768>
Unexecuted instantiation: <moxcms::cicp::TransferCharacteristics>::make_gamma_table::<u8, 65536, 4096>
Unexecuted instantiation: <moxcms::cicp::TransferCharacteristics>::make_gamma_table::<u16, 65536, 65536>
Unexecuted instantiation: <moxcms::cicp::TransferCharacteristics>::make_gamma_table::<u16, 65536, 8192>
Unexecuted instantiation: <moxcms::cicp::TransferCharacteristics>::make_gamma_table::<u16, 65536, 16384>
Unexecuted instantiation: <moxcms::cicp::TransferCharacteristics>::make_gamma_table::<u16, 65536, 4092>
969
}
970
971
#[cfg(test)]
972
mod 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
}