Coverage Report

Created: 2025-12-11 07:11

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