Coverage Report

Created: 2026-07-16 07:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.8.1/src/trc.rs
Line
Count
Source
1
/*
2
 * // Copyright (c) Radzivon Bartoshyk 2/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::cicp::create_rec709_parametric;
30
use crate::math::m_clamp;
31
use crate::mlaf::{mlaf, neg_mlaf};
32
use crate::transform::PointeeSizeExpressible;
33
use crate::writer::FloatToFixedU8Fixed8;
34
use crate::{CmsError, ColorProfile, DataColorSpace, Rgb, TransferCharacteristics};
35
use num_traits::AsPrimitive;
36
use pxfm::{dirty_powf, f_pow, f_powf};
37
38
#[derive(Clone, Debug, PartialEq)]
39
pub enum ToneReprCurve {
40
    Lut(Vec<u16>),
41
    Parametric(Vec<f32>),
42
}
43
44
impl ToneReprCurve {
45
0
    pub fn inverse(&self) -> Result<ToneReprCurve, CmsError> {
46
0
        match self {
47
0
            ToneReprCurve::Lut(lut) => {
48
0
                let inverse_length = lut.len().max(256);
49
0
                Ok(ToneReprCurve::Lut(invert_lut(lut, inverse_length)))
50
            }
51
0
            ToneReprCurve::Parametric(parametric) => ParametricCurve::new(parametric)
52
0
                .and_then(|x| x.invert())
53
0
                .map(|x| ToneReprCurve::Parametric([x.g, x.a, x.b, x.c, x.d, x.e, x.f].to_vec()))
54
0
                .ok_or(CmsError::BuildTransferFunction),
55
        }
56
0
    }
57
58
    /// Creates tone curve evaluator
59
0
    pub fn make_linear_evaluator(
60
0
        &self,
61
0
    ) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> {
62
0
        match self {
63
0
            ToneReprCurve::Lut(lut) => {
64
0
                if lut.is_empty() {
65
0
                    return Ok(Box::new(ToneCurveEvaluatorLinear {}));
66
0
                }
67
0
                if lut.len() == 1 {
68
0
                    let gamma = u8_fixed_8number_to_float(lut[0]);
69
0
                    return Ok(Box::new(ToneCurveEvaluatorPureGamma { gamma }));
70
0
                }
71
0
                let converted_curve = lut.iter().map(|&x| x as f32 / 65535.0).collect::<Vec<_>>();
72
0
                Ok(Box::new(ToneCurveLutEvaluator {
73
0
                    lut: converted_curve,
74
0
                }))
75
            }
76
0
            ToneReprCurve::Parametric(parametric) => {
77
0
                let parametric_curve =
78
0
                    ParametricCurve::new(parametric).ok_or(CmsError::BuildTransferFunction)?;
79
0
                Ok(Box::new(ToneCurveParametricEvaluator {
80
0
                    parametric: parametric_curve,
81
0
                }))
82
            }
83
        }
84
0
    }
85
86
    /// Creates tone curve evaluator from transfer characteristics
87
0
    pub fn make_cicp_linear_evaluator(
88
0
        transfer_characteristics: TransferCharacteristics,
89
0
    ) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> {
90
0
        if !transfer_characteristics.has_transfer_curve() {
91
0
            return Err(CmsError::BuildTransferFunction);
92
0
        }
93
0
        Ok(Box::new(ToneCurveCicpLinearEvaluator {
94
0
            trc: transfer_characteristics,
95
0
        }))
96
0
    }
97
98
    /// Creates tone curve inverse evaluator
99
0
    pub fn make_gamma_evaluator(
100
0
        &self,
101
0
    ) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> {
102
0
        match self {
103
0
            ToneReprCurve::Lut(lut) => {
104
0
                if lut.is_empty() {
105
0
                    return Ok(Box::new(ToneCurveEvaluatorLinear {}));
106
0
                }
107
0
                if lut.len() == 1 {
108
0
                    let gamma = 1. / u8_fixed_8number_to_float(lut[0]);
109
0
                    return Ok(Box::new(ToneCurveEvaluatorPureGamma { gamma }));
110
0
                }
111
0
                let inverted_lut = invert_lut(lut, 16384);
112
0
                let converted_curve = inverted_lut
113
0
                    .iter()
114
0
                    .map(|&x| x as f32 / 65535.0)
115
0
                    .collect::<Vec<_>>();
116
0
                Ok(Box::new(ToneCurveLutEvaluator {
117
0
                    lut: converted_curve,
118
0
                }))
119
            }
120
0
            ToneReprCurve::Parametric(parametric) => {
121
0
                let parametric_curve = ParametricCurve::new(parametric)
122
0
                    .and_then(|x| x.invert())
123
0
                    .ok_or(CmsError::BuildTransferFunction)?;
124
0
                Ok(Box::new(ToneCurveParametricEvaluator {
125
0
                    parametric: parametric_curve,
126
0
                }))
127
            }
128
        }
129
0
    }
130
131
    /// Creates tone curve inverse evaluator from transfer characteristics
132
0
    pub fn make_cicp_gamma_evaluator(
133
0
        transfer_characteristics: TransferCharacteristics,
134
0
    ) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> {
135
0
        if !transfer_characteristics.has_transfer_curve() {
136
0
            return Err(CmsError::BuildTransferFunction);
137
0
        }
138
0
        Ok(Box::new(ToneCurveCicpGammaEvaluator {
139
0
            trc: transfer_characteristics,
140
0
        }))
141
0
    }
142
}
143
144
struct ToneCurveCicpLinearEvaluator {
145
    trc: TransferCharacteristics,
146
}
147
148
struct ToneCurveCicpGammaEvaluator {
149
    trc: TransferCharacteristics,
150
}
151
152
impl ToneCurveEvaluator for ToneCurveCicpLinearEvaluator {
153
0
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
154
0
        Rgb::new(
155
0
            self.trc.linearize(rgb.r as f64) as f32,
156
0
            self.trc.linearize(rgb.g as f64) as f32,
157
0
            self.trc.linearize(rgb.b as f64) as f32,
158
        )
159
0
    }
160
161
0
    fn evaluate_value(&self, value: f32) -> f32 {
162
0
        self.trc.linearize(value as f64) as f32
163
0
    }
164
}
165
166
impl ToneCurveEvaluator for ToneCurveCicpGammaEvaluator {
167
0
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
168
0
        Rgb::new(
169
0
            self.trc.gamma(rgb.r as f64) as f32,
170
0
            self.trc.gamma(rgb.g as f64) as f32,
171
0
            self.trc.gamma(rgb.b as f64) as f32,
172
        )
173
0
    }
174
175
0
    fn evaluate_value(&self, value: f32) -> f32 {
176
0
        self.trc.gamma(value as f64) as f32
177
0
    }
178
}
179
180
struct ToneCurveLutEvaluator {
181
    lut: Vec<f32>,
182
}
183
184
impl ToneCurveEvaluator for ToneCurveLutEvaluator {
185
0
    fn evaluate_value(&self, value: f32) -> f32 {
186
0
        lut_interp_linear_float(value, &self.lut)
187
0
    }
188
189
0
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
190
0
        Rgb::new(
191
0
            lut_interp_linear_float(rgb.r, &self.lut),
192
0
            lut_interp_linear_float(rgb.g, &self.lut),
193
0
            lut_interp_linear_float(rgb.b, &self.lut),
194
        )
195
0
    }
196
}
197
198
0
pub(crate) fn build_trc_table(num_entries: i32, eotf: impl Fn(f64) -> f64) -> Vec<u16> {
199
0
    let mut table = vec![0u16; num_entries as usize];
200
201
0
    for (i, table_value) in table.iter_mut().enumerate() {
202
0
        let x: f64 = i as f64 / (num_entries - 1) as f64;
203
0
        let y: f64 = eotf(x);
204
        let mut output: f64;
205
0
        output = y * 65535.0 + 0.5;
206
0
        if output > 65535.0 {
207
0
            output = 65535.0
208
0
        }
209
0
        if output < 0.0 {
210
0
            output = 0.0
211
0
        }
212
0
        *table_value = output.floor() as u16;
213
    }
214
0
    table
215
0
}
Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::pq_to_linear>
Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::hlg_to_linear>
Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::bt1361_to_linear>
Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::log100_to_linear>
Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::iec61966_to_linear>
Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::smpte240_to_linear>
Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::smpte428_to_linear>
Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::log100_sqrt10_to_linear>
216
217
/// Creates Tone Reproduction curve from gamma
218
0
pub fn curve_from_gamma(gamma: f32) -> ToneReprCurve {
219
0
    ToneReprCurve::Lut(vec![gamma.to_u8_fixed8()])
220
0
}
221
222
#[derive(Debug)]
223
pub struct ParametricCurve {
224
    pub g: f32,
225
    pub a: f32,
226
    pub b: f32,
227
    pub c: f32,
228
    pub d: f32,
229
    pub e: f32,
230
    pub f: f32,
231
}
232
233
impl ParametricCurve {
234
    #[allow(clippy::many_single_char_names)]
235
0
    pub fn new(params: &[f32]) -> Option<ParametricCurve> {
236
        // convert from the variable number of parameters
237
        // contained in profiles to a unified representation.
238
0
        let g: f32 = params[0];
239
0
        match params[1..] {
240
0
            [] => Some(ParametricCurve {
241
0
                g,
242
0
                a: 1.,
243
0
                b: 0.,
244
0
                c: 0.,
245
0
                d: 0.,
246
0
                e: 0.,
247
0
                f: 0.,
248
0
            }),
249
0
            [a, b] => Some(ParametricCurve {
250
0
                g,
251
0
                a,
252
0
                b,
253
0
                c: 0.,
254
0
                d: -b / a,
255
0
                e: 0.,
256
0
                f: 0.,
257
0
            }),
258
0
            [a, b, c] => Some(ParametricCurve {
259
0
                g,
260
0
                a,
261
0
                b,
262
0
                c: 0.,
263
0
                d: -b / a,
264
0
                e: c,
265
0
                f: c,
266
0
            }),
267
0
            [a, b, c, d] => Some(ParametricCurve {
268
0
                g,
269
0
                a,
270
0
                b,
271
0
                c,
272
0
                d,
273
0
                e: 0.,
274
0
                f: 0.,
275
0
            }),
276
0
            [a, b, c, d, e, f] => Some(ParametricCurve {
277
0
                g,
278
0
                a,
279
0
                b,
280
0
                c,
281
0
                d,
282
0
                e,
283
0
                f,
284
0
            }),
285
0
            _ => None,
286
        }
287
0
    }
288
289
    #[cfg(feature = "lut")]
290
    fn is_linear(&self) -> bool {
291
        (self.g - 1.0).abs() < 1e-5
292
            && (self.a - 1.0).abs() < 1e-5
293
            && self.b.abs() < 1e-5
294
            && self.c.abs() < 1e-5
295
    }
296
297
0
    pub fn eval(&self, x: f32) -> f32 {
298
0
        if x < self.d {
299
0
            self.c * x + self.f
300
        } else {
301
0
            f_powf(self.a * x + self.b, self.g) + self.e
302
        }
303
0
    }
304
305
    #[allow(dead_code)]
306
    #[allow(clippy::many_single_char_names)]
307
0
    pub fn invert(&self) -> Option<ParametricCurve> {
308
        // First check if the function is continuous at the cross-over point d.
309
0
        let d1 = f_powf(self.a * self.d + self.b, self.g) + self.e;
310
0
        let d2 = self.c * self.d + self.f;
311
312
0
        if (d1 - d2).abs() > 0.1 {
313
0
            return None;
314
0
        }
315
0
        let d = d1;
316
317
        // y = (a * x + b)^g + e
318
        // y - e = (a * x + b)^g
319
        // (y - e)^(1/g) = a*x + b
320
        // (y - e)^(1/g) - b = a*x
321
        // (y - e)^(1/g)/a - b/a = x
322
        // ((y - e)/a^g)^(1/g) - b/a = x
323
        // ((1/(a^g)) * y - e/(a^g))^(1/g) - b/a = x
324
0
        let a = 1. / f_powf(self.a, self.g);
325
0
        let b = -self.e / f_powf(self.a, self.g);
326
0
        let g = 1. / self.g;
327
0
        let e = -self.b / self.a;
328
329
        // y = c * x + f
330
        // y - f = c * x
331
        // y/c - f/c = x
332
        let (c, f);
333
0
        if d <= 0. {
334
0
            c = 1.;
335
0
            f = 0.;
336
0
        } else {
337
0
            c = 1. / self.c;
338
0
            f = -self.f / self.c;
339
0
        }
340
341
        // if self.d > 0. and self.c == 0 as is likely with type 1 and 2 parametric function
342
        // then c and f will not be finite.
343
0
        if !(g.is_finite()
344
0
            && a.is_finite()
345
0
            && b.is_finite()
346
0
            && c.is_finite()
347
0
            && d.is_finite()
348
0
            && e.is_finite()
349
0
            && f.is_finite())
350
        {
351
0
            return None;
352
0
        }
353
354
0
        Some(ParametricCurve {
355
0
            g,
356
0
            a,
357
0
            b,
358
0
            c,
359
0
            d,
360
0
            e,
361
0
            f,
362
0
        })
363
0
    }
364
}
365
366
#[inline]
367
0
pub(crate) fn u8_fixed_8number_to_float(x: u16) -> f32 {
368
    // 0x0000 = 0.
369
    // 0x0100 = 1.
370
    // 0xffff = 255  + 255/256
371
0
    (x as i32 as f64 / 256.0) as f32
372
0
}
373
374
0
fn passthrough_table<T: PointeeSizeExpressible, const N: usize, const BIT_DEPTH: usize>()
375
0
-> Box<[f32; N]> {
376
0
    let mut gamma_table = Box::new([0f32; N]);
377
0
    let max_value = if T::FINITE {
378
0
        (1 << BIT_DEPTH) - 1
379
    } else {
380
0
        T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
381
    };
382
0
    let cap_values = if T::FINITE {
383
0
        (1u32 << BIT_DEPTH) as usize
384
    } else {
385
0
        T::NOT_FINITE_LINEAR_TABLE_SIZE
386
    };
387
0
    assert!(cap_values <= N, "Invalid lut table construction");
388
0
    let scale_value = 1f64 / max_value as f64;
389
0
    for (i, g) in gamma_table.iter_mut().enumerate().take(cap_values) {
390
0
        *g = (i as f64 * scale_value) as f32;
391
0
    }
392
393
0
    gamma_table
394
0
}
Unexecuted instantiation: moxcms::trc::passthrough_table::<f64, 65536, 1>
Unexecuted instantiation: moxcms::trc::passthrough_table::<f32, 65536, 1>
Unexecuted instantiation: moxcms::trc::passthrough_table::<u8, 256, 8>
Unexecuted instantiation: moxcms::trc::passthrough_table::<u16, 65536, 16>
Unexecuted instantiation: moxcms::trc::passthrough_table::<u16, 65536, 10>
Unexecuted instantiation: moxcms::trc::passthrough_table::<u16, 65536, 12>
395
396
0
fn linear_forward_table<T: PointeeSizeExpressible, const N: usize, const BIT_DEPTH: usize>(
397
0
    gamma: u16,
398
0
) -> Box<[f32; N]> {
399
0
    let mut gamma_table = Box::new([0f32; N]);
400
0
    let gamma_float: f32 = u8_fixed_8number_to_float(gamma);
401
0
    let max_value = if T::FINITE {
402
0
        (1 << BIT_DEPTH) - 1
403
    } else {
404
0
        T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
405
    };
406
0
    let cap_values = if T::FINITE {
407
0
        (1u32 << BIT_DEPTH) as usize
408
    } else {
409
0
        T::NOT_FINITE_LINEAR_TABLE_SIZE
410
    };
411
0
    assert!(cap_values <= N, "Invalid lut table construction");
412
0
    let scale_value = 1f64 / max_value as f64;
413
0
    for (i, g) in gamma_table.iter_mut().enumerate().take(cap_values) {
414
0
        *g = f_pow(i as f64 * scale_value, gamma_float as f64) as f32;
415
0
    }
416
417
0
    gamma_table
418
0
}
Unexecuted instantiation: moxcms::trc::linear_forward_table::<f64, 65536, 1>
Unexecuted instantiation: moxcms::trc::linear_forward_table::<f32, 65536, 1>
Unexecuted instantiation: moxcms::trc::linear_forward_table::<u8, 256, 8>
Unexecuted instantiation: moxcms::trc::linear_forward_table::<u16, 65536, 16>
Unexecuted instantiation: moxcms::trc::linear_forward_table::<u16, 65536, 10>
Unexecuted instantiation: moxcms::trc::linear_forward_table::<u16, 65536, 12>
419
420
#[inline(always)]
421
0
pub(crate) fn lut_interp_linear_float(x: f32, table: &[f32]) -> f32 {
422
0
    let value = x.min(1.).max(0.) * (table.len() - 1) as f32;
423
424
0
    let upper: i32 = value.ceil() as i32;
425
0
    let lower: i32 = value.floor() as i32;
426
427
0
    let diff = upper as f32 - value;
428
0
    let tu = table[upper as usize];
429
0
    mlaf(neg_mlaf(tu, tu, diff), table[lower as usize], diff)
430
0
}
431
432
/// Lut interpolation float where values is already clamped
433
#[inline(always)]
434
#[allow(dead_code)]
435
0
pub(crate) fn lut_interp_linear_float_clamped(x: f32, table: &[f32]) -> f32 {
436
0
    let value = x * (table.len() - 1) as f32;
437
438
0
    let upper: i32 = value.ceil() as i32;
439
0
    let lower: i32 = value.floor() as i32;
440
441
0
    let diff = upper as f32 - value;
442
0
    let tu = table[upper as usize];
443
0
    mlaf(neg_mlaf(tu, tu, diff), table[lower as usize], diff)
444
0
}
445
446
#[inline]
447
0
pub(crate) fn lut_interp_linear(input_value: f64, table: &[u16]) -> f32 {
448
0
    let mut input_value = input_value;
449
0
    if table.is_empty() {
450
0
        return input_value as f32;
451
0
    }
452
453
0
    input_value *= (table.len() - 1) as f64;
454
455
0
    let upper: i32 = input_value.ceil() as i32;
456
0
    let lower: i32 = input_value.floor() as i32;
457
0
    let w0 = table[(upper as usize).min(table.len() - 1)] as f64;
458
0
    let w1 = 1. - (upper as f64 - input_value);
459
0
    let w2 = table[(lower as usize).min(table.len() - 1)] as f64;
460
0
    let w3 = upper as f64 - input_value;
461
0
    let value: f32 = mlaf(w2 * w3, w0, w1) as f32;
462
0
    value * (1.0 / 65535.0)
463
0
}
464
465
0
fn linear_lut_interpolate<T: PointeeSizeExpressible, const N: usize, const BIT_DEPTH: usize>(
466
0
    table: &[u16],
467
0
) -> Box<[f32; N]> {
468
0
    let mut gamma_table = Box::new([0f32; N]);
469
0
    let max_value = if T::FINITE {
470
0
        (1 << BIT_DEPTH) - 1
471
    } else {
472
0
        T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
473
    };
474
0
    let cap_values = if T::FINITE {
475
0
        (1u32 << BIT_DEPTH) as usize
476
    } else {
477
0
        T::NOT_FINITE_LINEAR_TABLE_SIZE
478
    };
479
0
    assert!(cap_values <= N, "Invalid lut table construction");
480
0
    let scale_value = 1f64 / max_value as f64;
481
0
    for (i, g) in gamma_table.iter_mut().enumerate().take(cap_values) {
482
0
        *g = lut_interp_linear(i as f64 * scale_value, table);
483
0
    }
484
0
    gamma_table
485
0
}
Unexecuted instantiation: moxcms::trc::linear_lut_interpolate::<f64, 65536, 1>
Unexecuted instantiation: moxcms::trc::linear_lut_interpolate::<f32, 65536, 1>
Unexecuted instantiation: moxcms::trc::linear_lut_interpolate::<u8, 256, 8>
Unexecuted instantiation: moxcms::trc::linear_lut_interpolate::<u16, 65536, 16>
Unexecuted instantiation: moxcms::trc::linear_lut_interpolate::<u16, 65536, 10>
Unexecuted instantiation: moxcms::trc::linear_lut_interpolate::<u16, 65536, 12>
486
487
0
fn linear_curve_parametric<T: PointeeSizeExpressible, const N: usize, const BIT_DEPTH: usize>(
488
0
    params: &[f32],
489
0
) -> Option<Box<[f32; N]>> {
490
0
    let params = ParametricCurve::new(params)?;
491
0
    let mut gamma_table = Box::new([0f32; N]);
492
0
    let max_value = if T::FINITE {
493
0
        (1 << BIT_DEPTH) - 1
494
    } else {
495
0
        T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
496
    };
497
0
    let cap_value = if T::FINITE {
498
0
        1 << BIT_DEPTH
499
    } else {
500
0
        T::NOT_FINITE_LINEAR_TABLE_SIZE
501
    };
502
0
    let scale_value = 1f32 / max_value as f32;
503
0
    for (i, g) in gamma_table.iter_mut().enumerate().take(cap_value) {
504
0
        let x = i as f32 * scale_value;
505
0
        *g = m_clamp(params.eval(x), 0.0, 1.0);
506
0
    }
507
0
    Some(gamma_table)
508
0
}
Unexecuted instantiation: moxcms::trc::linear_curve_parametric::<f64, 65536, 1>
Unexecuted instantiation: moxcms::trc::linear_curve_parametric::<f32, 65536, 1>
Unexecuted instantiation: moxcms::trc::linear_curve_parametric::<u8, 256, 8>
Unexecuted instantiation: moxcms::trc::linear_curve_parametric::<u16, 65536, 16>
Unexecuted instantiation: moxcms::trc::linear_curve_parametric::<u16, 65536, 10>
Unexecuted instantiation: moxcms::trc::linear_curve_parametric::<u16, 65536, 12>
509
510
0
fn linear_curve_parametric_s<const N: usize>(params: &[f32]) -> Option<Box<[f32; N]>> {
511
0
    let params = ParametricCurve::new(params)?;
512
0
    let mut gamma_table = Box::new([0f32; N]);
513
0
    let scale_value = 1f32 / (N - 1) as f32;
514
0
    for (i, g) in gamma_table.iter_mut().enumerate().take(N) {
515
0
        let x = i as f32 * scale_value;
516
0
        *g = m_clamp(params.eval(x), 0.0, 1.0);
517
0
    }
518
0
    Some(gamma_table)
519
0
}
Unexecuted instantiation: moxcms::trc::linear_curve_parametric_s::<65536>
Unexecuted instantiation: moxcms::trc::linear_curve_parametric_s::<4096>
Unexecuted instantiation: moxcms::trc::linear_curve_parametric_s::<8192>
Unexecuted instantiation: moxcms::trc::linear_curve_parametric_s::<16384>
Unexecuted instantiation: moxcms::trc::linear_curve_parametric_s::<32768>
Unexecuted instantiation: moxcms::trc::linear_curve_parametric_s::<4092>
520
521
0
pub(crate) fn make_gamma_linear_table<
522
0
    T: Default + Copy + 'static + PointeeSizeExpressible,
523
0
    const BUCKET: usize,
524
0
    const N: usize,
525
0
>(
526
0
    bit_depth: usize,
527
0
) -> Box<[T; BUCKET]>
528
0
where
529
0
    f32: AsPrimitive<T>,
530
{
531
0
    let mut table = Box::new([T::default(); BUCKET]);
532
0
    let max_range = if T::FINITE {
533
0
        (1f64 / ((N - 1) as f64 / (1 << bit_depth) as f64)) as f32
534
    } else {
535
0
        (1f64 / ((N - 1) as f64)) as f32
536
    };
537
0
    for (v, output) in table.iter_mut().take(N).enumerate() {
538
0
        if T::FINITE {
539
0
            *output = (v as f32 * max_range).round().as_();
540
0
        } else {
541
0
            *output = (v as f32 * max_range).as_();
542
0
        }
543
    }
544
0
    table
545
0
}
Unexecuted instantiation: moxcms::trc::make_gamma_linear_table::<f64, 65536, 65536>
Unexecuted instantiation: moxcms::trc::make_gamma_linear_table::<f32, 65536, 32768>
Unexecuted instantiation: moxcms::trc::make_gamma_linear_table::<u8, 65536, 4096>
Unexecuted instantiation: moxcms::trc::make_gamma_linear_table::<u16, 65536, 65536>
Unexecuted instantiation: moxcms::trc::make_gamma_linear_table::<u16, 65536, 8192>
Unexecuted instantiation: moxcms::trc::make_gamma_linear_table::<u16, 65536, 16384>
Unexecuted instantiation: moxcms::trc::make_gamma_linear_table::<u16, 65536, 4092>
546
547
#[inline]
548
0
fn lut_interp_linear_gamma_impl<
549
0
    T: Default + Copy + 'static + PointeeSizeExpressible,
550
0
    const N: usize,
551
0
    const BIT_DEPTH: usize,
552
0
>(
553
0
    input_value: u32,
554
0
    table: &[u16],
555
0
) -> T
556
0
where
557
0
    u32: AsPrimitive<T>,
558
{
559
    // Start scaling input_value to the length of the array: GAMMA_CAP*(length-1).
560
    // We'll divide out the GAMMA_CAP next
561
0
    let mut value: u32 = input_value * (table.len() - 1) as u32;
562
0
    let cap_value = N - 1;
563
    // equivalent to ceil(value/GAMMA_CAP)
564
0
    let upper: u32 = value.div_ceil(cap_value as u32);
565
    // equivalent to floor(value/GAMMA_CAP)
566
0
    let lower: u32 = value / cap_value as u32;
567
    // interp is the distance from upper to value scaled to 0..GAMMA_CAP
568
0
    let interp: u32 = value % cap_value as u32;
569
0
    let lw_value = table[lower as usize];
570
0
    let hw_value = table[upper as usize];
571
    // the table values range from 0..65535
572
0
    value = mlaf(
573
0
        hw_value as u32 * interp,
574
0
        lw_value as u32,
575
0
        (N - 1) as u32 - interp,
576
0
    ); // 0..(65535*GAMMA_CAP)
577
578
    // round and scale
579
0
    let max_colors = if T::FINITE { (1 << BIT_DEPTH) - 1 } else { 1 };
580
0
    value += (cap_value * 65535 / max_colors / 2) as u32; // scale to 0...max_colors
581
0
    value /= (cap_value * 65535 / max_colors) as u32;
582
0
    value.as_()
583
0
}
Unexecuted instantiation: moxcms::trc::lut_interp_linear_gamma_impl::<u8, 4096, 8>
Unexecuted instantiation: moxcms::trc::lut_interp_linear_gamma_impl::<u16, 65536, 16>
Unexecuted instantiation: moxcms::trc::lut_interp_linear_gamma_impl::<u16, 8192, 10>
Unexecuted instantiation: moxcms::trc::lut_interp_linear_gamma_impl::<u16, 16384, 12>
Unexecuted instantiation: moxcms::trc::lut_interp_linear_gamma_impl::<u16, 4092, 8>
584
585
#[inline]
586
0
fn lut_interp_linear_gamma_impl_f32<
587
0
    T: Default + Copy + 'static + PointeeSizeExpressible,
588
0
    const N: usize,
589
0
    const BIT_DEPTH: usize,
590
0
>(
591
0
    input_value: u32,
592
0
    table: &[u16],
593
0
) -> T
594
0
where
595
0
    f32: AsPrimitive<T>,
596
{
597
    // Start scaling input_value to the length of the array: GAMMA_CAP*(length-1).
598
    // We'll divide out the GAMMA_CAP next
599
0
    let guess: u32 = input_value * (table.len() - 1) as u32;
600
0
    let cap_value = N - 1;
601
    // equivalent to ceil(value/GAMMA_CAP)
602
0
    let upper: u32 = guess.div_ceil(cap_value as u32);
603
    // equivalent to floor(value/GAMMA_CAP)
604
0
    let lower: u32 = guess / cap_value as u32;
605
    // interp is the distance from upper to value scaled to 0..GAMMA_CAP
606
0
    let interp: u32 = guess % cap_value as u32;
607
0
    let lw_value = table[lower as usize];
608
0
    let hw_value = table[upper as usize];
609
    // the table values range from 0..65535
610
0
    let mut value = mlaf(
611
0
        hw_value as f32 * interp as f32,
612
0
        lw_value as f32,
613
0
        (N - 1) as f32 - interp as f32,
614
    ); // 0..(65535*GAMMA_CAP)
615
616
    // round and scale
617
0
    let max_colors = if T::FINITE { (1 << BIT_DEPTH) - 1 } else { 1 };
618
0
    value /= (cap_value * 65535 / max_colors) as f32;
619
0
    value.as_()
620
0
}
Unexecuted instantiation: moxcms::trc::lut_interp_linear_gamma_impl_f32::<f64, 65536, 1>
Unexecuted instantiation: moxcms::trc::lut_interp_linear_gamma_impl_f32::<f32, 32768, 1>
621
622
#[doc(hidden)]
623
pub trait GammaLutInterpolate {
624
    fn gamma_lut_interp<
625
        T: Default + Copy + 'static + PointeeSizeExpressible,
626
        const N: usize,
627
        const BIT_DEPTH: usize,
628
    >(
629
        input_value: u32,
630
        table: &[u16],
631
    ) -> T
632
    where
633
        u32: AsPrimitive<T>,
634
        f32: AsPrimitive<T>;
635
}
636
637
macro_rules! gamma_lut_interp_fixed {
638
    ($i_type: ident) => {
639
        impl GammaLutInterpolate for $i_type {
640
            #[inline]
641
0
            fn gamma_lut_interp<
642
0
                T: Default + Copy + 'static + PointeeSizeExpressible,
643
0
                const N: usize,
644
0
                const BIT_DEPTH: usize,
645
0
            >(
646
0
                input_value: u32,
647
0
                table: &[u16],
648
0
            ) -> T
649
0
            where
650
0
                u32: AsPrimitive<T>,
651
            {
652
0
                lut_interp_linear_gamma_impl::<T, N, BIT_DEPTH>(input_value, table)
653
0
            }
Unexecuted instantiation: <u8 as moxcms::trc::GammaLutInterpolate>::gamma_lut_interp::<u8, 4096, 8>
Unexecuted instantiation: <u16 as moxcms::trc::GammaLutInterpolate>::gamma_lut_interp::<u16, 65536, 16>
Unexecuted instantiation: <u16 as moxcms::trc::GammaLutInterpolate>::gamma_lut_interp::<u16, 8192, 10>
Unexecuted instantiation: <u16 as moxcms::trc::GammaLutInterpolate>::gamma_lut_interp::<u16, 16384, 12>
Unexecuted instantiation: <u16 as moxcms::trc::GammaLutInterpolate>::gamma_lut_interp::<u16, 4092, 8>
654
        }
655
    };
656
}
657
658
gamma_lut_interp_fixed!(u8);
659
gamma_lut_interp_fixed!(u16);
660
661
macro_rules! gammu_lut_interp_float {
662
    ($f_type: ident) => {
663
        impl GammaLutInterpolate for $f_type {
664
            #[inline]
665
0
            fn gamma_lut_interp<
666
0
                T: Default + Copy + 'static + PointeeSizeExpressible,
667
0
                const N: usize,
668
0
                const BIT_DEPTH: usize,
669
0
            >(
670
0
                input_value: u32,
671
0
                table: &[u16],
672
0
            ) -> T
673
0
            where
674
0
                f32: AsPrimitive<T>,
675
0
                u32: AsPrimitive<T>,
676
            {
677
0
                lut_interp_linear_gamma_impl_f32::<T, N, BIT_DEPTH>(input_value, table)
678
0
            }
Unexecuted instantiation: <f32 as moxcms::trc::GammaLutInterpolate>::gamma_lut_interp::<f32, 32768, 1>
Unexecuted instantiation: <f64 as moxcms::trc::GammaLutInterpolate>::gamma_lut_interp::<f64, 65536, 1>
679
        }
680
    };
681
}
682
683
gammu_lut_interp_float!(f32);
684
gammu_lut_interp_float!(f64);
685
686
0
pub(crate) fn make_gamma_lut<
687
0
    T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate,
688
0
    const BUCKET: usize,
689
0
    const N: usize,
690
0
    const BIT_DEPTH: usize,
691
0
>(
692
0
    table: &[u16],
693
0
) -> Box<[T; BUCKET]>
694
0
where
695
0
    u32: AsPrimitive<T>,
696
0
    f32: AsPrimitive<T>,
697
{
698
0
    let mut new_table = Box::new([T::default(); BUCKET]);
699
0
    for (v, output) in new_table.iter_mut().take(N).enumerate() {
700
0
        *output = T::gamma_lut_interp::<T, N, BIT_DEPTH>(v as u32, table);
701
0
    }
702
0
    new_table
703
0
}
Unexecuted instantiation: moxcms::trc::make_gamma_lut::<f64, 65536, 65536, 1>
Unexecuted instantiation: moxcms::trc::make_gamma_lut::<f32, 65536, 32768, 1>
Unexecuted instantiation: moxcms::trc::make_gamma_lut::<u8, 65536, 4096, 8>
Unexecuted instantiation: moxcms::trc::make_gamma_lut::<u16, 65536, 65536, 16>
Unexecuted instantiation: moxcms::trc::make_gamma_lut::<u16, 65536, 8192, 10>
Unexecuted instantiation: moxcms::trc::make_gamma_lut::<u16, 65536, 16384, 12>
Unexecuted instantiation: moxcms::trc::make_gamma_lut::<u16, 65536, 4092, 8>
704
705
#[inline]
706
0
pub(crate) fn lut_interp_linear16(input_value: u16, table: &[u16]) -> u16 {
707
    // Start scaling input_value to the length of the array: 65535*(length-1).
708
    // We'll divide out the 65535 next
709
0
    let mut value: u32 = input_value as u32 * (table.len() as u32 - 1);
710
0
    let upper: u16 = value.div_ceil(65535) as u16; // equivalent to ceil(value/65535)
711
0
    let lower: u16 = (value / 65535) as u16; // equivalent to floor(value/65535)
712
    // interp is the distance from upper to value scaled to 0..65535
713
0
    let interp: u32 = value % 65535; // 0..65535*65535
714
0
    value = (table[upper as usize] as u32 * interp
715
0
        + table[lower as usize] as u32 * (65535 - interp))
716
0
        / 65535;
717
0
    value as u16
718
0
}
719
720
#[inline]
721
0
pub(crate) fn lut_interp_linear16_boxed<const N: usize>(input_value: u16, table: &[u16; N]) -> u16 {
722
    // Start scaling input_value to the length of the array: 65535*(length-1).
723
    // We'll divide out the 65535 next
724
0
    let mut value: u32 = input_value as u32 * (table.len() as u32 - 1);
725
0
    let upper: u16 = value.div_ceil(65535) as u16; // equivalent to ceil(value/65535)
726
0
    let lower: u16 = (value / 65535) as u16; // equivalent to floor(value/65535)
727
    // interp is the distance from upper to value scaled to 0..65535
728
0
    let interp: u32 = value % 65535; // 0..65535*65535
729
0
    value = (table[upper as usize] as u32 * interp
730
0
        + table[lower as usize] as u32 * (65535 - interp))
731
0
        / 65535;
732
0
    value as u16
733
0
}
Unexecuted instantiation: moxcms::trc::lut_interp_linear16_boxed::<65536>
Unexecuted instantiation: moxcms::trc::lut_interp_linear16_boxed::<4096>
Unexecuted instantiation: moxcms::trc::lut_interp_linear16_boxed::<8192>
Unexecuted instantiation: moxcms::trc::lut_interp_linear16_boxed::<16384>
Unexecuted instantiation: moxcms::trc::lut_interp_linear16_boxed::<32768>
Unexecuted instantiation: moxcms::trc::lut_interp_linear16_boxed::<4092>
734
735
0
fn make_gamma_pow_table<
736
0
    T: Default + Copy + 'static + PointeeSizeExpressible,
737
0
    const BUCKET: usize,
738
0
    const N: usize,
739
0
>(
740
0
    gamma: f32,
741
0
    bit_depth: usize,
742
0
) -> Box<[T; BUCKET]>
743
0
where
744
0
    f32: AsPrimitive<T>,
745
{
746
0
    let mut table = Box::new([T::default(); BUCKET]);
747
0
    let scale = 1f32 / (N - 1) as f32;
748
0
    let cap = ((1 << bit_depth) - 1) as f32;
749
0
    if T::FINITE {
750
0
        for (v, output) in table.iter_mut().take(N).enumerate() {
751
0
            *output = (cap * f_powf(v as f32 * scale, gamma)).round().as_();
752
0
        }
753
    } else {
754
0
        for (v, output) in table.iter_mut().take(N).enumerate() {
755
0
            *output = (cap * f_powf(v as f32 * scale, gamma)).as_();
756
0
        }
757
    }
758
0
    table
759
0
}
Unexecuted instantiation: moxcms::trc::make_gamma_pow_table::<f64, 65536, 65536>
Unexecuted instantiation: moxcms::trc::make_gamma_pow_table::<f32, 65536, 32768>
Unexecuted instantiation: moxcms::trc::make_gamma_pow_table::<u8, 65536, 4096>
Unexecuted instantiation: moxcms::trc::make_gamma_pow_table::<u16, 65536, 65536>
Unexecuted instantiation: moxcms::trc::make_gamma_pow_table::<u16, 65536, 8192>
Unexecuted instantiation: moxcms::trc::make_gamma_pow_table::<u16, 65536, 16384>
Unexecuted instantiation: moxcms::trc::make_gamma_pow_table::<u16, 65536, 4092>
760
761
0
fn make_gamma_parametric_table<
762
0
    T: Default + Copy + 'static + PointeeSizeExpressible,
763
0
    const BUCKET: usize,
764
0
    const N: usize,
765
0
    const BIT_DEPTH: usize,
766
0
>(
767
0
    parametric_curve: ParametricCurve,
768
0
) -> Box<[T; BUCKET]>
769
0
where
770
0
    f32: AsPrimitive<T>,
771
{
772
0
    let mut table = Box::new([T::default(); BUCKET]);
773
0
    let scale = 1f32 / (N - 1) as f32;
774
0
    let cap = ((1 << BIT_DEPTH) - 1) as f32;
775
0
    if T::FINITE {
776
0
        for (v, output) in table.iter_mut().take(N).enumerate() {
777
0
            *output = (cap * parametric_curve.eval(v as f32 * scale))
778
0
                .round()
779
0
                .as_();
780
0
        }
781
    } else {
782
0
        for (v, output) in table.iter_mut().take(N).enumerate() {
783
0
            *output = (cap * parametric_curve.eval(v as f32 * scale)).as_();
784
0
        }
785
    }
786
0
    table
787
0
}
Unexecuted instantiation: moxcms::trc::make_gamma_parametric_table::<f64, 65536, 65536, 1>
Unexecuted instantiation: moxcms::trc::make_gamma_parametric_table::<f32, 65536, 32768, 1>
Unexecuted instantiation: moxcms::trc::make_gamma_parametric_table::<u8, 65536, 4096, 8>
Unexecuted instantiation: moxcms::trc::make_gamma_parametric_table::<u16, 65536, 65536, 16>
Unexecuted instantiation: moxcms::trc::make_gamma_parametric_table::<u16, 65536, 8192, 10>
Unexecuted instantiation: moxcms::trc::make_gamma_parametric_table::<u16, 65536, 16384, 12>
Unexecuted instantiation: moxcms::trc::make_gamma_parametric_table::<u16, 65536, 4092, 8>
788
789
#[inline]
790
0
fn compare_parametric(src: &[f32], dst: &[f32]) -> bool {
791
0
    for (src, dst) in src.iter().zip(dst.iter()) {
792
0
        if (src - dst).abs() > 1e-4 {
793
0
            return false;
794
0
        }
795
    }
796
0
    true
797
0
}
798
799
0
fn lut_inverse_interp16(value: u16, lut_table: &[u16]) -> u16 {
800
0
    let mut l: i32 = 1; // 'int' Give spacing for negative values
801
0
    let mut r: i32 = 0x10000;
802
0
    let mut x: i32 = 0;
803
    let mut res: i32;
804
0
    let length = lut_table.len() as i32;
805
806
0
    let mut num_zeroes: i32 = 0;
807
0
    for &item in lut_table.iter() {
808
0
        if item == 0 {
809
0
            num_zeroes += 1
810
        } else {
811
0
            break;
812
        }
813
    }
814
815
0
    if num_zeroes == 0 && value as i32 == 0 {
816
0
        return 0u16;
817
0
    }
818
0
    let mut num_of_polys: i32 = 0;
819
0
    for &item in lut_table.iter().rev() {
820
0
        if item == 0xffff {
821
0
            num_of_polys += 1
822
        } else {
823
0
            break;
824
        }
825
    }
826
    // Does the curve belong to this case?
827
0
    if num_zeroes > 1 || num_of_polys > 1 {
828
        let a_0: i32;
829
        let b_0: i32;
830
        // Identify if value fall downto 0 or FFFF zone
831
0
        if value as i32 == 0 {
832
0
            return 0u16;
833
0
        }
834
        // if (Value == 0xFFFF) return 0xFFFF;
835
        // else restrict to valid zone
836
0
        if num_zeroes > 1 {
837
0
            a_0 = (num_zeroes - 1) * 0xffff / (length - 1);
838
0
            l = a_0 - 1
839
0
        }
840
0
        if num_of_polys > 1 {
841
0
            b_0 = (length - 1 - num_of_polys) * 0xffff / (length - 1);
842
0
            r = b_0 + 1
843
0
        }
844
0
    }
845
0
    if r <= l {
846
        // If this happens LutTable is not invertible
847
0
        return 0u16;
848
0
    }
849
850
0
    while r > l {
851
0
        x = (l + r) / 2;
852
0
        res = lut_interp_linear16((x - 1) as u16, lut_table) as i32;
853
0
        if res == value as i32 {
854
            // Found exact match.
855
0
            return (x - 1) as u16;
856
0
        }
857
0
        if res > value as i32 {
858
0
            r = x - 1
859
        } else {
860
0
            l = x + 1
861
        }
862
    }
863
864
    // Not found, should we interpolate?
865
866
    // Get surrounding nodes
867
0
    debug_assert!(x >= 1);
868
869
0
    let val2: f64 = (length - 1) as f64 * ((x - 1) as f64 / 65535.0);
870
0
    let cell0: i32 = val2.floor() as i32;
871
0
    let cell1: i32 = val2.ceil() as i32;
872
0
    if cell0 == cell1 {
873
0
        return x as u16;
874
0
    }
875
876
0
    let y0: f64 = lut_table[cell0 as usize] as f64;
877
0
    let x0: f64 = 65535.0 * cell0 as f64 / (length - 1) as f64;
878
0
    let y1: f64 = lut_table[cell1 as usize] as f64;
879
0
    let x1: f64 = 65535.0 * cell1 as f64 / (length - 1) as f64;
880
0
    let a: f64 = (y1 - y0) / (x1 - x0);
881
0
    let b: f64 = mlaf(y0, -a, x0);
882
0
    if a.abs() < 0.01f64 {
883
0
        return x as u16;
884
0
    }
885
0
    let f: f64 = (value as i32 as f64 - b) / a;
886
0
    if f < 0.0 {
887
0
        return 0u16;
888
0
    }
889
0
    if f >= 65535.0 {
890
0
        return 0xffffu16;
891
0
    }
892
0
    (f + 0.5f64).floor() as u16
893
0
}
894
895
0
fn lut_inverse_interp16_boxed<const N: usize>(value: u16, lut_table: &[u16; N]) -> u16 {
896
0
    let mut l: i32 = 1; // 'int' Give spacing for negative values
897
0
    let mut r: i32 = 0x10000;
898
0
    let mut x: i32 = 0;
899
    let mut res: i32;
900
0
    let length = lut_table.len() as i32;
901
902
0
    let mut num_zeroes: i32 = 0;
903
0
    for &item in lut_table.iter() {
904
0
        if item == 0 {
905
0
            num_zeroes += 1
906
        } else {
907
0
            break;
908
        }
909
    }
910
911
0
    if num_zeroes == 0 && value as i32 == 0 {
912
0
        return 0u16;
913
0
    }
914
0
    let mut num_of_polys: i32 = 0;
915
0
    for &item in lut_table.iter().rev() {
916
0
        if item == 0xffff {
917
0
            num_of_polys += 1
918
        } else {
919
0
            break;
920
        }
921
    }
922
    // Does the curve belong to this case?
923
0
    if num_zeroes > 1 || num_of_polys > 1 {
924
        let a_0: i32;
925
        let b_0: i32;
926
        // Identify if value fall downto 0 or FFFF zone
927
0
        if value as i32 == 0 {
928
0
            return 0u16;
929
0
        }
930
        // if (Value == 0xFFFF) return 0xFFFF;
931
        // else restrict to valid zone
932
0
        if num_zeroes > 1 {
933
0
            a_0 = (num_zeroes - 1) * 0xffff / (length - 1);
934
0
            l = a_0 - 1
935
0
        }
936
0
        if num_of_polys > 1 {
937
0
            b_0 = (length - 1 - num_of_polys) * 0xffff / (length - 1);
938
0
            r = b_0 + 1
939
0
        }
940
0
    }
941
0
    if r <= l {
942
        // If this happens LutTable is not invertible
943
0
        return 0u16;
944
0
    }
945
946
0
    while r > l {
947
0
        x = (l + r) / 2;
948
0
        res = lut_interp_linear16_boxed((x - 1) as u16, lut_table) as i32;
949
0
        if res == value as i32 {
950
            // Found exact match.
951
0
            return (x - 1) as u16;
952
0
        }
953
0
        if res > value as i32 {
954
0
            r = x - 1
955
        } else {
956
0
            l = x + 1
957
        }
958
    }
959
960
    // Not found, should we interpolate?
961
962
    // Get surrounding nodes
963
0
    debug_assert!(x >= 1);
964
965
0
    let val2: f64 = (length - 1) as f64 * ((x - 1) as f64 / 65535.0);
966
0
    let cell0: i32 = val2.floor() as i32;
967
0
    let cell1: i32 = val2.ceil() as i32;
968
0
    if cell0 == cell1 {
969
0
        return x as u16;
970
0
    }
971
972
0
    let y0: f64 = lut_table[cell0 as usize] as f64;
973
0
    let x0: f64 = 65535.0 * cell0 as f64 / (length - 1) as f64;
974
0
    let y1: f64 = lut_table[cell1 as usize] as f64;
975
0
    let x1: f64 = 65535.0 * cell1 as f64 / (length - 1) as f64;
976
0
    let a: f64 = (y1 - y0) / (x1 - x0);
977
0
    let b: f64 = mlaf(y0, -a, x0);
978
0
    if a.abs() < 0.01f64 {
979
0
        return x as u16;
980
0
    }
981
0
    let f: f64 = (value as i32 as f64 - b) / a;
982
0
    if f < 0.0 {
983
0
        return 0u16;
984
0
    }
985
0
    if f >= 65535.0 {
986
0
        return 0xffffu16;
987
0
    }
988
0
    (f + 0.5f64).floor() as u16
989
0
}
Unexecuted instantiation: moxcms::trc::lut_inverse_interp16_boxed::<65536>
Unexecuted instantiation: moxcms::trc::lut_inverse_interp16_boxed::<4096>
Unexecuted instantiation: moxcms::trc::lut_inverse_interp16_boxed::<8192>
Unexecuted instantiation: moxcms::trc::lut_inverse_interp16_boxed::<16384>
Unexecuted instantiation: moxcms::trc::lut_inverse_interp16_boxed::<32768>
Unexecuted instantiation: moxcms::trc::lut_inverse_interp16_boxed::<4092>
990
991
0
fn invert_lut(table: &[u16], out_length: usize) -> Vec<u16> {
992
    // For now, we invert the lut by creating a lut of size out_length
993
    // and attempting to look up a value for each entry using lut_inverse_interp16
994
0
    let mut output = vec![0u16; out_length];
995
0
    let scale_value = 65535f64 / (out_length - 1) as f64;
996
0
    for (i, out) in output.iter_mut().enumerate() {
997
0
        let x: f64 = i as f64 * scale_value;
998
0
        let input: u16 = (x + 0.5f64).floor() as u16;
999
0
        *out = lut_inverse_interp16(input, table);
1000
0
    }
1001
0
    output
1002
0
}
1003
1004
0
fn invert_lut_boxed<const N: usize>(table: &[u16; N], out_length: usize) -> Vec<u16> {
1005
    // For now, we invert the lut by creating a lut of size out_length
1006
    // and attempting to look up a value for each entry using lut_inverse_interp16
1007
0
    let mut output = vec![0u16; out_length];
1008
0
    let scale_value = 65535f64 / (out_length - 1) as f64;
1009
0
    for (i, out) in output.iter_mut().enumerate() {
1010
0
        let x: f64 = i as f64 * scale_value;
1011
0
        let input: u16 = (x + 0.5f64).floor() as u16;
1012
0
        *out = lut_inverse_interp16_boxed(input, table);
1013
0
    }
1014
0
    output
1015
0
}
Unexecuted instantiation: moxcms::trc::invert_lut_boxed::<65536>
Unexecuted instantiation: moxcms::trc::invert_lut_boxed::<4096>
Unexecuted instantiation: moxcms::trc::invert_lut_boxed::<8192>
Unexecuted instantiation: moxcms::trc::invert_lut_boxed::<16384>
Unexecuted instantiation: moxcms::trc::invert_lut_boxed::<32768>
Unexecuted instantiation: moxcms::trc::invert_lut_boxed::<4092>
1016
1017
impl ToneReprCurve {
1018
    #[cfg(feature = "any_to_any")]
1019
    pub(crate) fn to_clut(&self) -> Result<Vec<f32>, CmsError> {
1020
        match self {
1021
            ToneReprCurve::Lut(lut) => {
1022
                if lut.is_empty() {
1023
                    let passthrough_table = passthrough_table::<f32, 16384, 1>();
1024
                    Ok(passthrough_table.to_vec())
1025
                } else {
1026
                    Ok(lut
1027
                        .iter()
1028
                        .map(|&x| x as f32 * (1. / 65535.))
1029
                        .collect::<Vec<_>>())
1030
                }
1031
            }
1032
            ToneReprCurve::Parametric(_) => {
1033
                let curve = self
1034
                    .build_linearize_table::<f32, 65535, 1>()
1035
                    .ok_or(CmsError::InvalidTrcCurve)?;
1036
                let max_value = f32::NOT_FINITE_LINEAR_TABLE_SIZE - 1;
1037
                let sliced = &curve[..max_value];
1038
                Ok(sliced.to_vec())
1039
            }
1040
        }
1041
    }
1042
1043
0
    pub(crate) fn build_linearize_table<
1044
0
        T: PointeeSizeExpressible,
1045
0
        const N: usize,
1046
0
        const BIT_DEPTH: usize,
1047
0
    >(
1048
0
        &self,
1049
0
    ) -> Option<Box<[f32; N]>> {
1050
0
        match self {
1051
0
            ToneReprCurve::Parametric(params) => linear_curve_parametric::<T, N, BIT_DEPTH>(params),
1052
0
            ToneReprCurve::Lut(data) => match data.len() {
1053
0
                0 => Some(passthrough_table::<T, N, BIT_DEPTH>()),
1054
0
                1 => Some(linear_forward_table::<T, N, BIT_DEPTH>(data[0])),
1055
0
                _ => Some(linear_lut_interpolate::<T, N, BIT_DEPTH>(data)),
1056
            },
1057
        }
1058
0
    }
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_linearize_table::<f64, 65536, 1>
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_linearize_table::<f32, 65536, 1>
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_linearize_table::<u8, 256, 8>
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_linearize_table::<u16, 65536, 16>
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_linearize_table::<u16, 65536, 10>
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_linearize_table::<u16, 65536, 12>
1059
1060
0
    pub(crate) fn build_gamma_table<
1061
0
        T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate,
1062
0
        const BUCKET: usize,
1063
0
        const N: usize,
1064
0
        const BIT_DEPTH: usize,
1065
0
    >(
1066
0
        &self,
1067
0
    ) -> Option<Box<[T; BUCKET]>>
1068
0
    where
1069
0
        f32: AsPrimitive<T>,
1070
0
        u32: AsPrimitive<T>,
1071
    {
1072
0
        match self {
1073
0
            ToneReprCurve::Parametric(params) => {
1074
0
                if params.len() == 5 {
1075
0
                    let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045];
1076
0
                    let rec709_params = create_rec709_parametric();
1077
1078
0
                    let mut lc_params: [f32; 5] = [0.; 5];
1079
0
                    for (dst, src) in lc_params.iter_mut().zip(params.iter()) {
1080
0
                        *dst = *src;
1081
0
                    }
1082
1083
0
                    if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) {
1084
0
                        return Some(
1085
0
                            TransferCharacteristics::Srgb
1086
0
                                .make_gamma_table::<T, BUCKET, N>(BIT_DEPTH),
1087
0
                        );
1088
0
                    }
1089
1090
0
                    if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) {
1091
0
                        return Some(
1092
0
                            TransferCharacteristics::Bt709
1093
0
                                .make_gamma_table::<T, BUCKET, N>(BIT_DEPTH),
1094
0
                        );
1095
0
                    }
1096
0
                }
1097
1098
0
                let parametric_curve = ParametricCurve::new(params);
1099
0
                if let Some(v) = parametric_curve?
1100
0
                    .invert()
1101
0
                    .map(|x| make_gamma_parametric_table::<T, BUCKET, N, BIT_DEPTH>(x))
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<f64, 65536, 65536, 1>::{closure#0}
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<f32, 65536, 32768, 1>::{closure#0}
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u8, 65536, 4096, 8>::{closure#0}
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 65536, 16>::{closure#0}
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 8192, 10>::{closure#0}
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 16384, 12>::{closure#0}
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 4092, 8>::{closure#0}
1102
                {
1103
0
                    return Some(v);
1104
0
                }
1105
1106
0
                let mut gamma_table_uint = Box::new([0; N]);
1107
1108
0
                let inverted_size: usize = N;
1109
0
                let gamma_table = linear_curve_parametric_s::<N>(params)?;
1110
0
                for (&src, dst) in gamma_table.iter().zip(gamma_table_uint.iter_mut()) {
1111
0
                    *dst = (src * 65535f32) as u16;
1112
0
                }
1113
0
                let inverted = invert_lut_boxed(&gamma_table_uint, inverted_size);
1114
0
                Some(make_gamma_lut::<T, BUCKET, N, BIT_DEPTH>(&inverted))
1115
            }
1116
0
            ToneReprCurve::Lut(data) => match data.len() {
1117
0
                0 => Some(make_gamma_linear_table::<T, BUCKET, N>(BIT_DEPTH)),
1118
0
                1 => Some(make_gamma_pow_table::<T, BUCKET, N>(
1119
0
                    1. / u8_fixed_8number_to_float(data[0]),
1120
0
                    BIT_DEPTH,
1121
0
                )),
1122
                _ => {
1123
0
                    let mut inverted_size = data.len();
1124
0
                    if inverted_size < 256 {
1125
0
                        inverted_size = 256
1126
0
                    }
1127
0
                    let inverted = invert_lut(data, inverted_size);
1128
0
                    Some(make_gamma_lut::<T, BUCKET, N, BIT_DEPTH>(&inverted))
1129
                }
1130
            },
1131
        }
1132
0
    }
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<f64, 65536, 65536, 1>
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<f32, 65536, 32768, 1>
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u8, 65536, 4096, 8>
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 65536, 16>
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 8192, 10>
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 16384, 12>
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 4092, 8>
1133
}
1134
1135
impl ColorProfile {
1136
    /// Produces LUT for 8 bit tone linearization
1137
0
    pub fn build_8bit_lin_table(
1138
0
        &self,
1139
0
        trc: &Option<ToneReprCurve>,
1140
0
    ) -> Result<Box<[f32; 256]>, CmsError> {
1141
0
        trc.as_ref()
1142
0
            .and_then(|trc| trc.build_linearize_table::<u8, 256, 8>())
1143
0
            .ok_or(CmsError::BuildTransferFunction)
1144
0
    }
1145
1146
    /// Produces LUT for Gray transfer curve with N depth
1147
0
    pub fn build_gray_linearize_table<
1148
0
        T: PointeeSizeExpressible,
1149
0
        const N: usize,
1150
0
        const BIT_DEPTH: usize,
1151
0
    >(
1152
0
        &self,
1153
0
    ) -> Result<Box<[f32; N]>, CmsError> {
1154
0
        self.gray_trc
1155
0
            .as_ref()
1156
0
            .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<f64, 65536, 1>::{closure#0}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<f32, 65536, 1>::{closure#0}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u8, 256, 8>::{closure#0}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u16, 65536, 16>::{closure#0}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u16, 65536, 10>::{closure#0}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u16, 65536, 12>::{closure#0}
1157
0
            .ok_or(CmsError::BuildTransferFunction)
1158
0
    }
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<f64, 65536, 1>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<f32, 65536, 1>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u8, 256, 8>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u16, 65536, 16>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u16, 65536, 10>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u16, 65536, 12>
1159
1160
    /// Produces LUT for Red transfer curve with N depth
1161
0
    pub fn build_r_linearize_table<
1162
0
        T: PointeeSizeExpressible,
1163
0
        const N: usize,
1164
0
        const BIT_DEPTH: usize,
1165
0
    >(
1166
0
        &self,
1167
0
        use_cicp: bool,
1168
0
    ) -> Result<Box<[f32; N]>, CmsError> {
1169
0
        if use_cicp {
1170
0
            if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1171
0
                if tc.has_transfer_curve() {
1172
0
                    return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>());
1173
0
                }
1174
0
            }
1175
0
        }
1176
0
        self.red_trc
1177
0
            .as_ref()
1178
0
            .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<f64, 65536, 1>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<f32, 65536, 1>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u8, 256, 8>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u16, 65536, 16>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u16, 65536, 10>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u16, 65536, 12>::{closure#1}
1179
0
            .ok_or(CmsError::BuildTransferFunction)
1180
0
    }
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<f64, 65536, 1>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<f32, 65536, 1>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u8, 256, 8>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u16, 65536, 16>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u16, 65536, 10>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u16, 65536, 12>
1181
1182
    /// Produces LUT for Green transfer curve with N depth
1183
0
    pub fn build_g_linearize_table<
1184
0
        T: PointeeSizeExpressible,
1185
0
        const N: usize,
1186
0
        const BIT_DEPTH: usize,
1187
0
    >(
1188
0
        &self,
1189
0
        use_cicp: bool,
1190
0
    ) -> Result<Box<[f32; N]>, CmsError> {
1191
0
        if use_cicp {
1192
0
            if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1193
0
                if tc.has_transfer_curve() {
1194
0
                    return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>());
1195
0
                }
1196
0
            }
1197
0
        }
1198
0
        self.green_trc
1199
0
            .as_ref()
1200
0
            .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<f64, 65536, 1>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<f32, 65536, 1>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u8, 256, 8>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u16, 65536, 16>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u16, 65536, 10>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u16, 65536, 12>::{closure#1}
1201
0
            .ok_or(CmsError::BuildTransferFunction)
1202
0
    }
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<f64, 65536, 1>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<f32, 65536, 1>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u8, 256, 8>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u16, 65536, 16>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u16, 65536, 10>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u16, 65536, 12>
1203
1204
    /// Produces LUT for Blue transfer curve with N depth
1205
0
    pub fn build_b_linearize_table<
1206
0
        T: PointeeSizeExpressible,
1207
0
        const N: usize,
1208
0
        const BIT_DEPTH: usize,
1209
0
    >(
1210
0
        &self,
1211
0
        use_cicp: bool,
1212
0
    ) -> Result<Box<[f32; N]>, CmsError> {
1213
0
        if use_cicp {
1214
0
            if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1215
0
                if tc.has_transfer_curve() {
1216
0
                    return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>());
1217
0
                }
1218
0
            }
1219
0
        }
1220
0
        self.blue_trc
1221
0
            .as_ref()
1222
0
            .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<f64, 65536, 1>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<f32, 65536, 1>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u8, 256, 8>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u16, 65536, 16>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u16, 65536, 10>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u16, 65536, 12>::{closure#1}
1223
0
            .ok_or(CmsError::BuildTransferFunction)
1224
0
    }
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<f64, 65536, 1>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<f32, 65536, 1>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u8, 256, 8>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u16, 65536, 16>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u16, 65536, 10>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u16, 65536, 12>
1225
1226
    /// Build gamma table for 8 bit depth
1227
    /// Only 4092 first bins are used and values scaled in 0..255
1228
0
    pub fn build_8bit_gamma_table(
1229
0
        &self,
1230
0
        trc: &Option<ToneReprCurve>,
1231
0
        use_cicp: bool,
1232
0
    ) -> Result<Box<[u16; 65536]>, CmsError> {
1233
0
        self.build_gamma_table::<u16, 65536, 4092, 8>(trc, use_cicp)
1234
0
    }
1235
1236
    /// Build gamma table for 10 bit depth
1237
    /// Only 8192 first bins are used and values scaled in 0..1023
1238
0
    pub fn build_10bit_gamma_table(
1239
0
        &self,
1240
0
        trc: &Option<ToneReprCurve>,
1241
0
        use_cicp: bool,
1242
0
    ) -> Result<Box<[u16; 65536]>, CmsError> {
1243
0
        self.build_gamma_table::<u16, 65536, 8192, 10>(trc, use_cicp)
1244
0
    }
1245
1246
    /// Build gamma table for 12 bit depth
1247
    /// Only 16384 first bins are used and values scaled in 0..4095
1248
0
    pub fn build_12bit_gamma_table(
1249
0
        &self,
1250
0
        trc: &Option<ToneReprCurve>,
1251
0
        use_cicp: bool,
1252
0
    ) -> Result<Box<[u16; 65536]>, CmsError> {
1253
0
        self.build_gamma_table::<u16, 65536, 16384, 12>(trc, use_cicp)
1254
0
    }
1255
1256
    /// Build gamma table for 16 bit depth
1257
    /// Only 16384 first bins are used and values scaled in 0..65535
1258
0
    pub fn build_16bit_gamma_table(
1259
0
        &self,
1260
0
        trc: &Option<ToneReprCurve>,
1261
0
        use_cicp: bool,
1262
0
    ) -> Result<Box<[u16; 65536]>, CmsError> {
1263
0
        self.build_gamma_table::<u16, 65536, 65536, 16>(trc, use_cicp)
1264
0
    }
1265
1266
    /// Builds gamma table checking CICP for Transfer characteristics first.
1267
0
    pub fn build_gamma_table<
1268
0
        T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate,
1269
0
        const BUCKET: usize,
1270
0
        const N: usize,
1271
0
        const BIT_DEPTH: usize,
1272
0
    >(
1273
0
        &self,
1274
0
        trc: &Option<ToneReprCurve>,
1275
0
        use_cicp: bool,
1276
0
    ) -> Result<Box<[T; BUCKET]>, CmsError>
1277
0
    where
1278
0
        f32: AsPrimitive<T>,
1279
0
        u32: AsPrimitive<T>,
1280
    {
1281
0
        if use_cicp {
1282
0
            if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1283
0
                if tc.has_transfer_curve() {
1284
0
                    return Ok(tc.make_gamma_table::<T, BUCKET, N>(BIT_DEPTH));
1285
0
                }
1286
0
            }
1287
0
        }
1288
0
        trc.as_ref()
1289
0
            .and_then(|trc| trc.build_gamma_table::<T, BUCKET, N, BIT_DEPTH>())
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<f64, 65536, 65536, 1>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<f32, 65536, 32768, 1>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u8, 65536, 4096, 8>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 65536, 16>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 8192, 10>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 16384, 12>::{closure#1}
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 4092, 8>::{closure#1}
1290
0
            .ok_or(CmsError::BuildTransferFunction)
1291
0
    }
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<f64, 65536, 65536, 1>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<f32, 65536, 32768, 1>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u8, 65536, 4096, 8>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 65536, 16>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 8192, 10>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 16384, 12>
Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 4092, 8>
1292
1293
    #[cfg(feature = "extended_range")]
1294
    /// Checks if profile gamma can work in extended precision and we have implementation for this
1295
    pub(crate) fn try_extended_gamma_evaluator(
1296
        &self,
1297
    ) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> {
1298
        if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1299
            if tc.has_transfer_curve() {
1300
                return Some(Box::new(ToneCurveCicpEvaluator {
1301
                    rgb_trc: tc.extended_gamma_tristimulus(),
1302
                    trc: tc.extended_gamma_single(),
1303
                }));
1304
            }
1305
        }
1306
        if !self.are_all_trc_the_same() {
1307
            return None;
1308
        }
1309
        let reference_trc = if self.color_space == DataColorSpace::Gray {
1310
            self.gray_trc.as_ref()
1311
        } else {
1312
            self.red_trc.as_ref()
1313
        };
1314
        if let Some(red_trc) = reference_trc {
1315
            return Self::make_gamma_evaluator_all_the_same(red_trc);
1316
        }
1317
        None
1318
    }
1319
1320
    #[cfg(feature = "extended_range")]
1321
    fn make_gamma_evaluator_all_the_same(
1322
        red_trc: &ToneReprCurve,
1323
    ) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> {
1324
        match red_trc {
1325
            ToneReprCurve::Lut(lut) => {
1326
                if lut.is_empty() {
1327
                    return Some(Box::new(ToneCurveEvaluatorLinear {}));
1328
                }
1329
                if lut.len() == 1 {
1330
                    let gamma = 1. / u8_fixed_8number_to_float(lut[0]);
1331
                    return Some(Box::new(ToneCurveEvaluatorPureGamma { gamma }));
1332
                }
1333
                None
1334
            }
1335
            ToneReprCurve::Parametric(params) => {
1336
                if params.len() == 5 {
1337
                    let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045];
1338
                    let rec709_params = create_rec709_parametric();
1339
1340
                    let mut lc_params: [f32; 5] = [0.; 5];
1341
                    for (dst, src) in lc_params.iter_mut().zip(params.iter()) {
1342
                        *dst = *src;
1343
                    }
1344
1345
                    #[cfg(feature = "extended_range")]
1346
                    if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) {
1347
                        return Some(Box::new(ToneCurveCicpEvaluator {
1348
                            rgb_trc: TransferCharacteristics::Srgb.extended_gamma_tristimulus(),
1349
                            trc: TransferCharacteristics::Srgb.extended_gamma_single(),
1350
                        }));
1351
                    }
1352
1353
                    #[cfg(feature = "extended_range")]
1354
                    if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) {
1355
                        return Some(Box::new(ToneCurveCicpEvaluator {
1356
                            rgb_trc: TransferCharacteristics::Bt709.extended_gamma_tristimulus(),
1357
                            trc: TransferCharacteristics::Bt709.extended_gamma_single(),
1358
                        }));
1359
                    }
1360
                }
1361
1362
                let parametric_curve = ParametricCurve::new(params);
1363
                if let Some(v) = parametric_curve?.invert() {
1364
                    return Some(Box::new(ToneCurveParametricEvaluator { parametric: v }));
1365
                }
1366
                None
1367
            }
1368
        }
1369
    }
1370
1371
    /// Check if all TRC are the same
1372
0
    pub(crate) fn are_all_trc_the_same(&self) -> bool {
1373
0
        if self.color_space == DataColorSpace::Gray {
1374
0
            return true;
1375
0
        }
1376
0
        if let (Some(red_trc), Some(green_trc), Some(blue_trc)) =
1377
0
            (&self.red_trc, &self.green_trc, &self.blue_trc)
1378
        {
1379
0
            if !matches!(
1380
0
                (red_trc, green_trc, blue_trc),
1381
                (
1382
                    ToneReprCurve::Lut(_),
1383
                    ToneReprCurve::Lut(_),
1384
                    ToneReprCurve::Lut(_),
1385
                ) | (
1386
                    ToneReprCurve::Parametric(_),
1387
                    ToneReprCurve::Parametric(_),
1388
                    ToneReprCurve::Parametric(_)
1389
                )
1390
            ) {
1391
0
                return false;
1392
0
            }
1393
0
            if let (ToneReprCurve::Lut(lut0), ToneReprCurve::Lut(lut1), ToneReprCurve::Lut(lut2)) =
1394
0
                (red_trc, green_trc, blue_trc)
1395
            {
1396
0
                if lut0 == lut1 || lut1 == lut2 {
1397
0
                    return true;
1398
0
                }
1399
0
            }
1400
            if let (
1401
0
                ToneReprCurve::Parametric(lut0),
1402
0
                ToneReprCurve::Parametric(lut1),
1403
0
                ToneReprCurve::Parametric(lut2),
1404
0
            ) = (red_trc, green_trc, blue_trc)
1405
            {
1406
0
                if lut0 == lut1 || lut1 == lut2 {
1407
0
                    return true;
1408
0
                }
1409
0
            }
1410
0
        }
1411
0
        false
1412
0
    }
1413
1414
    #[cfg(feature = "lut")]
1415
    /// Checks if profile is matrix shaper, have same TRC and TRC is linear.
1416
    pub(crate) fn is_linear_matrix_shaper(&self) -> bool {
1417
        if !self.is_matrix_shaper() {
1418
            return false;
1419
        }
1420
        if !self.are_all_trc_the_same() {
1421
            return false;
1422
        }
1423
        if let Some(red_trc) = &self.red_trc {
1424
            return match red_trc {
1425
                ToneReprCurve::Lut(lut) => {
1426
                    if lut.is_empty() {
1427
                        return true;
1428
                    }
1429
                    use crate::matan::is_curve_linear16;
1430
                    if is_curve_linear16(lut) {
1431
                        return true;
1432
                    }
1433
                    false
1434
                }
1435
                ToneReprCurve::Parametric(params) => {
1436
                    if let Some(curve) = ParametricCurve::new(params) {
1437
                        return curve.is_linear();
1438
                    }
1439
                    false
1440
                }
1441
            };
1442
        }
1443
        false
1444
    }
1445
1446
    #[cfg(feature = "extended_range")]
1447
    /// Checks if profile linearization can work in extended precision and we have implementation for this
1448
    pub(crate) fn try_extended_linearizing_evaluator(
1449
        &self,
1450
    ) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> {
1451
        if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1452
            if tc.has_transfer_curve() {
1453
                return Some(Box::new(ToneCurveCicpEvaluator {
1454
                    rgb_trc: tc.extended_linear_tristimulus(),
1455
                    trc: tc.extended_linear_single(),
1456
                }));
1457
            }
1458
        }
1459
        if !self.are_all_trc_the_same() {
1460
            return None;
1461
        }
1462
        let reference_trc = if self.color_space == DataColorSpace::Gray {
1463
            self.gray_trc.as_ref()
1464
        } else {
1465
            self.red_trc.as_ref()
1466
        };
1467
        if let Some(red_trc) = reference_trc {
1468
            if let Some(value) = Self::make_linear_curve_evaluator_all_the_same(red_trc) {
1469
                return value;
1470
            }
1471
        }
1472
        None
1473
    }
1474
1475
    #[cfg(feature = "extended_range")]
1476
    fn make_linear_curve_evaluator_all_the_same(
1477
        evaluator_curve: &ToneReprCurve,
1478
    ) -> Option<Option<Box<dyn ToneCurveEvaluator + Send + Sync>>> {
1479
        match evaluator_curve {
1480
            ToneReprCurve::Lut(lut) => {
1481
                if lut.is_empty() {
1482
                    return Some(Some(Box::new(ToneCurveEvaluatorLinear {})));
1483
                }
1484
                if lut.len() == 1 {
1485
                    let gamma = u8_fixed_8number_to_float(lut[0]);
1486
                    return Some(Some(Box::new(ToneCurveEvaluatorPureGamma { gamma })));
1487
                }
1488
            }
1489
            ToneReprCurve::Parametric(params) => {
1490
                if params.len() == 5 {
1491
                    let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045];
1492
                    let rec709_params = create_rec709_parametric();
1493
1494
                    let mut lc_params: [f32; 5] = [0.; 5];
1495
                    for (dst, src) in lc_params.iter_mut().zip(params.iter()) {
1496
                        *dst = *src;
1497
                    }
1498
1499
                    if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) {
1500
                        return Some(Some(Box::new(ToneCurveCicpEvaluator {
1501
                            rgb_trc: TransferCharacteristics::Srgb.extended_linear_tristimulus(),
1502
                            trc: TransferCharacteristics::Srgb.extended_linear_single(),
1503
                        })));
1504
                    }
1505
1506
                    if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) {
1507
                        return Some(Some(Box::new(ToneCurveCicpEvaluator {
1508
                            rgb_trc: TransferCharacteristics::Bt709.extended_linear_tristimulus(),
1509
                            trc: TransferCharacteristics::Bt709.extended_linear_single(),
1510
                        })));
1511
                    }
1512
                }
1513
1514
                let parametric_curve = ParametricCurve::new(params);
1515
                if let Some(v) = parametric_curve {
1516
                    return Some(Some(Box::new(ToneCurveParametricEvaluator {
1517
                        parametric: v,
1518
                    })));
1519
                }
1520
            }
1521
        }
1522
        None
1523
    }
1524
}
1525
1526
#[cfg(feature = "extended_range")]
1527
pub(crate) struct ToneCurveCicpEvaluator {
1528
    rgb_trc: fn(Rgb<f32>) -> Rgb<f32>,
1529
    trc: fn(f32) -> f32,
1530
}
1531
1532
pub(crate) struct ToneCurveParametricEvaluator {
1533
    parametric: ParametricCurve,
1534
}
1535
1536
pub(crate) struct ToneCurveEvaluatorPureGamma {
1537
    gamma: f32,
1538
}
1539
1540
pub(crate) struct ToneCurveEvaluatorLinear {}
1541
1542
#[cfg(feature = "extended_range")]
1543
impl ToneCurveEvaluator for ToneCurveCicpEvaluator {
1544
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1545
        (self.rgb_trc)(rgb)
1546
    }
1547
1548
    fn evaluate_value(&self, value: f32) -> f32 {
1549
        (self.trc)(value)
1550
    }
1551
}
1552
1553
impl ToneCurveEvaluator for ToneCurveParametricEvaluator {
1554
0
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1555
0
        Rgb::new(
1556
0
            self.parametric.eval(rgb.r),
1557
0
            self.parametric.eval(rgb.g),
1558
0
            self.parametric.eval(rgb.b),
1559
        )
1560
0
    }
1561
1562
0
    fn evaluate_value(&self, value: f32) -> f32 {
1563
0
        self.parametric.eval(value)
1564
0
    }
1565
}
1566
1567
impl ToneCurveEvaluator for ToneCurveEvaluatorPureGamma {
1568
0
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1569
0
        Rgb::new(
1570
0
            dirty_powf(rgb.r, self.gamma),
1571
0
            dirty_powf(rgb.g, self.gamma),
1572
0
            dirty_powf(rgb.b, self.gamma),
1573
        )
1574
0
    }
1575
1576
0
    fn evaluate_value(&self, value: f32) -> f32 {
1577
0
        dirty_powf(value, self.gamma)
1578
0
    }
1579
}
1580
1581
impl ToneCurveEvaluator for ToneCurveEvaluatorLinear {
1582
0
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1583
0
        rgb
1584
0
    }
1585
1586
0
    fn evaluate_value(&self, value: f32) -> f32 {
1587
0
        value
1588
0
    }
1589
}
1590
1591
pub trait ToneCurveEvaluator {
1592
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32>;
1593
    fn evaluate_value(&self, value: f32) -> f32;
1594
}