Coverage Report

Created: 2025-10-14 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.7.7/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::matan::is_curve_linear16;
31
use crate::math::m_clamp;
32
use crate::mlaf::{mlaf, neg_mlaf};
33
use crate::transform::PointeeSizeExpressible;
34
use crate::writer::FloatToFixedU8Fixed8;
35
use crate::{CmsError, ColorProfile, DataColorSpace, Rgb, TransferCharacteristics};
36
use num_traits::AsPrimitive;
37
use pxfm::{dirty_powf, f_pow, f_powf};
38
39
#[derive(Clone, Debug)]
40
pub enum ToneReprCurve {
41
    Lut(Vec<u16>),
42
    Parametric(Vec<f32>),
43
}
44
45
impl ToneReprCurve {
46
0
    pub fn inverse(&self) -> Result<ToneReprCurve, CmsError> {
47
0
        match self {
48
0
            ToneReprCurve::Lut(lut) => {
49
0
                let inverse_length = lut.len().max(256);
50
0
                Ok(ToneReprCurve::Lut(invert_lut(lut, inverse_length)))
51
            }
52
0
            ToneReprCurve::Parametric(parametric) => ParametricCurve::new(parametric)
53
0
                .and_then(|x| x.invert())
54
0
                .map(|x| ToneReprCurve::Parametric([x.g, x.a, x.b, x.c, x.d, x.e, x.f].to_vec()))
55
0
                .ok_or(CmsError::BuildTransferFunction),
56
        }
57
0
    }
58
59
    /// Creates tone curve evaluator
60
0
    pub fn make_linear_evaluator(
61
0
        &self,
62
0
    ) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> {
63
0
        match self {
64
0
            ToneReprCurve::Lut(lut) => {
65
0
                if lut.is_empty() {
66
0
                    return Ok(Box::new(ToneCurveEvaluatorLinear {}));
67
0
                }
68
0
                if lut.len() == 1 {
69
0
                    let gamma = u8_fixed_8number_to_float(lut[0]);
70
0
                    return Ok(Box::new(ToneCurveEvaluatorPureGamma { gamma }));
71
0
                }
72
0
                let converted_curve = lut.iter().map(|&x| x as f32 / 65535.0).collect::<Vec<_>>();
73
0
                Ok(Box::new(ToneCurveLutEvaluator {
74
0
                    lut: converted_curve,
75
0
                }))
76
            }
77
0
            ToneReprCurve::Parametric(parametric) => {
78
0
                let parametric_curve =
79
0
                    ParametricCurve::new(parametric).ok_or(CmsError::BuildTransferFunction)?;
80
0
                Ok(Box::new(ToneCurveParametricEvaluator {
81
0
                    parametric: parametric_curve,
82
0
                }))
83
            }
84
        }
85
0
    }
86
87
    /// Creates tone curve evaluator from transfer characteristics
88
0
    pub fn make_cicp_linear_evaluator(
89
0
        transfer_characteristics: TransferCharacteristics,
90
0
    ) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> {
91
0
        if !transfer_characteristics.has_transfer_curve() {
92
0
            return Err(CmsError::BuildTransferFunction);
93
0
        }
94
0
        Ok(Box::new(ToneCurveCicpLinearEvaluator {
95
0
            trc: transfer_characteristics,
96
0
        }))
97
0
    }
98
99
    /// Creates tone curve inverse evaluator
100
0
    pub fn make_gamma_evaluator(
101
0
        &self,
102
0
    ) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> {
103
0
        match self {
104
0
            ToneReprCurve::Lut(lut) => {
105
0
                if lut.is_empty() {
106
0
                    return Ok(Box::new(ToneCurveEvaluatorLinear {}));
107
0
                }
108
0
                if lut.len() == 1 {
109
0
                    let gamma = 1. / u8_fixed_8number_to_float(lut[0]);
110
0
                    return Ok(Box::new(ToneCurveEvaluatorPureGamma { gamma }));
111
0
                }
112
0
                let inverted_lut = invert_lut(lut, 16384);
113
0
                let converted_curve = inverted_lut
114
0
                    .iter()
115
0
                    .map(|&x| x as f32 / 65535.0)
116
0
                    .collect::<Vec<_>>();
117
0
                Ok(Box::new(ToneCurveLutEvaluator {
118
0
                    lut: converted_curve,
119
0
                }))
120
            }
121
0
            ToneReprCurve::Parametric(parametric) => {
122
0
                let parametric_curve = ParametricCurve::new(parametric)
123
0
                    .and_then(|x| x.invert())
124
0
                    .ok_or(CmsError::BuildTransferFunction)?;
125
0
                Ok(Box::new(ToneCurveParametricEvaluator {
126
0
                    parametric: parametric_curve,
127
0
                }))
128
            }
129
        }
130
0
    }
131
132
    /// Creates tone curve inverse evaluator from transfer characteristics
133
0
    pub fn make_cicp_gamma_evaluator(
134
0
        transfer_characteristics: TransferCharacteristics,
135
0
    ) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> {
136
0
        if !transfer_characteristics.has_transfer_curve() {
137
0
            return Err(CmsError::BuildTransferFunction);
138
0
        }
139
0
        Ok(Box::new(ToneCurveCicpGammaEvaluator {
140
0
            trc: transfer_characteristics,
141
0
        }))
142
0
    }
143
}
144
145
struct ToneCurveCicpLinearEvaluator {
146
    trc: TransferCharacteristics,
147
}
148
149
struct ToneCurveCicpGammaEvaluator {
150
    trc: TransferCharacteristics,
151
}
152
153
impl ToneCurveEvaluator for ToneCurveCicpLinearEvaluator {
154
0
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
155
0
        Rgb::new(
156
0
            self.trc.linearize(rgb.r as f64) as f32,
157
0
            self.trc.linearize(rgb.g as f64) as f32,
158
0
            self.trc.linearize(rgb.b as f64) as f32,
159
        )
160
0
    }
161
162
0
    fn evaluate_value(&self, value: f32) -> f32 {
163
0
        self.trc.linearize(value as f64) as f32
164
0
    }
165
}
166
167
impl ToneCurveEvaluator for ToneCurveCicpGammaEvaluator {
168
0
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
169
0
        Rgb::new(
170
0
            self.trc.gamma(rgb.r as f64) as f32,
171
0
            self.trc.gamma(rgb.g as f64) as f32,
172
0
            self.trc.gamma(rgb.b as f64) as f32,
173
        )
174
0
    }
175
176
0
    fn evaluate_value(&self, value: f32) -> f32 {
177
0
        self.trc.gamma(value as f64) as f32
178
0
    }
179
}
180
181
struct ToneCurveLutEvaluator {
182
    lut: Vec<f32>,
183
}
184
185
impl ToneCurveEvaluator for ToneCurveLutEvaluator {
186
0
    fn evaluate_value(&self, value: f32) -> f32 {
187
0
        lut_interp_linear_float(value, &self.lut)
188
0
    }
189
190
0
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
191
0
        Rgb::new(
192
0
            lut_interp_linear_float(rgb.r, &self.lut),
193
0
            lut_interp_linear_float(rgb.g, &self.lut),
194
0
            lut_interp_linear_float(rgb.b, &self.lut),
195
        )
196
0
    }
197
}
198
199
0
pub(crate) fn build_trc_table(num_entries: i32, eotf: impl Fn(f64) -> f64) -> Vec<u16> {
200
0
    let mut table = vec![0u16; num_entries as usize];
201
202
0
    for (i, table_value) in table.iter_mut().enumerate() {
203
0
        let x: f64 = i as f64 / (num_entries - 1) as f64;
204
0
        let y: f64 = eotf(x);
205
        let mut output: f64;
206
0
        output = y * 65535.0 + 0.5;
207
0
        if output > 65535.0 {
208
0
            output = 65535.0
209
0
        }
210
0
        if output < 0.0 {
211
0
            output = 0.0
212
0
        }
213
0
        *table_value = output.floor() as u16;
214
    }
215
0
    table
216
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>
217
218
/// Creates Tone Reproduction curve from gamma
219
0
pub fn curve_from_gamma(gamma: f32) -> ToneReprCurve {
220
0
    ToneReprCurve::Lut(vec![gamma.to_u8_fixed8()])
221
0
}
222
223
#[derive(Debug)]
224
struct ParametricCurve {
225
    g: f32,
226
    a: f32,
227
    b: f32,
228
    c: f32,
229
    d: f32,
230
    e: f32,
231
    f: f32,
232
}
233
234
impl ParametricCurve {
235
    #[allow(clippy::many_single_char_names)]
236
0
    fn new(params: &[f32]) -> Option<ParametricCurve> {
237
        // convert from the variable number of parameters
238
        // contained in profiles to a unified representation.
239
0
        let g: f32 = params[0];
240
0
        match params[1..] {
241
0
            [] => Some(ParametricCurve {
242
0
                g,
243
0
                a: 1.,
244
0
                b: 0.,
245
0
                c: 1.,
246
0
                d: 0.,
247
0
                e: 0.,
248
0
                f: 0.,
249
0
            }),
250
0
            [a, b] => Some(ParametricCurve {
251
0
                g,
252
0
                a,
253
0
                b,
254
0
                c: 0.,
255
0
                d: -b / a,
256
0
                e: 0.,
257
0
                f: 0.,
258
0
            }),
259
0
            [a, b, c] => Some(ParametricCurve {
260
0
                g,
261
0
                a,
262
0
                b,
263
0
                c: 0.,
264
0
                d: -b / a,
265
0
                e: c,
266
0
                f: c,
267
0
            }),
268
0
            [a, b, c, d] => Some(ParametricCurve {
269
0
                g,
270
0
                a,
271
0
                b,
272
0
                c,
273
0
                d,
274
0
                e: 0.,
275
0
                f: 0.,
276
0
            }),
277
0
            [a, b, c, d, e, f] => Some(ParametricCurve {
278
0
                g,
279
0
                a,
280
0
                b,
281
0
                c,
282
0
                d,
283
0
                e,
284
0
                f,
285
0
            }),
286
0
            _ => None,
287
        }
288
0
    }
289
290
0
    fn is_linear(&self) -> bool {
291
0
        (self.g - 1.0).abs() < 1e-5
292
0
            && (self.a - 1.0).abs() < 1e-5
293
0
            && self.b.abs() < 1e-5
294
0
            && self.c.abs() < 1e-5
295
0
    }
296
297
0
    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
    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::<f32, 16384, 1>
Unexecuted instantiation: moxcms::trc::passthrough_table::<f32, 65535, 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>
Unexecuted instantiation: moxcms::trc::passthrough_table::<u16, 65536, 13>
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::<f32, 65535, 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>
Unexecuted instantiation: moxcms::trc::linear_forward_table::<u16, 65536, 13>
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::<f32, 65535, 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>
Unexecuted instantiation: moxcms::trc::linear_lut_interpolate::<u16, 65536, 13>
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::<f32, 65535, 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>
Unexecuted instantiation: moxcms::trc::linear_curve_parametric::<u16, 65536, 13>
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
0
    pub(crate) fn to_clut(&self) -> Result<Vec<f32>, CmsError> {
1019
0
        match self {
1020
0
            ToneReprCurve::Lut(lut) => {
1021
0
                if lut.is_empty() {
1022
0
                    let passthrough_table = passthrough_table::<f32, 16384, 1>();
1023
0
                    Ok(passthrough_table.to_vec())
1024
                } else {
1025
0
                    Ok(lut
1026
0
                        .iter()
1027
0
                        .map(|&x| x as f32 * (1. / 65535.))
1028
0
                        .collect::<Vec<_>>())
1029
                }
1030
            }
1031
            ToneReprCurve::Parametric(_) => {
1032
0
                let curve = self
1033
0
                    .build_linearize_table::<f32, 65535, 1>()
1034
0
                    .ok_or(CmsError::InvalidTrcCurve)?;
1035
0
                let max_value = f32::NOT_FINITE_LINEAR_TABLE_SIZE - 1;
1036
0
                let sliced = &curve[..max_value];
1037
0
                Ok(sliced.to_vec())
1038
            }
1039
        }
1040
0
    }
1041
1042
0
    pub(crate) fn build_linearize_table<
1043
0
        T: PointeeSizeExpressible,
1044
0
        const N: usize,
1045
0
        const BIT_DEPTH: usize,
1046
0
    >(
1047
0
        &self,
1048
0
    ) -> Option<Box<[f32; N]>> {
1049
0
        match self {
1050
0
            ToneReprCurve::Parametric(params) => linear_curve_parametric::<T, N, BIT_DEPTH>(params),
1051
0
            ToneReprCurve::Lut(data) => match data.len() {
1052
0
                0 => Some(passthrough_table::<T, N, BIT_DEPTH>()),
1053
0
                1 => Some(linear_forward_table::<T, N, BIT_DEPTH>(data[0])),
1054
0
                _ => Some(linear_lut_interpolate::<T, N, BIT_DEPTH>(data)),
1055
            },
1056
        }
1057
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::<f32, 65535, 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>
Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_linearize_table::<u16, 65536, 13>
1058
1059
0
    pub(crate) fn build_gamma_table<
1060
0
        T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate,
1061
0
        const BUCKET: usize,
1062
0
        const N: usize,
1063
0
        const BIT_DEPTH: usize,
1064
0
    >(
1065
0
        &self,
1066
0
    ) -> Option<Box<[T; BUCKET]>>
1067
0
    where
1068
0
        f32: AsPrimitive<T>,
1069
0
        u32: AsPrimitive<T>,
1070
    {
1071
0
        match self {
1072
0
            ToneReprCurve::Parametric(params) => {
1073
0
                if params.len() == 5 {
1074
0
                    let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045];
1075
0
                    let rec709_params = create_rec709_parametric();
1076
1077
0
                    let mut lc_params: [f32; 5] = [0.; 5];
1078
0
                    for (dst, src) in lc_params.iter_mut().zip(params.iter()) {
1079
0
                        *dst = *src;
1080
0
                    }
1081
1082
0
                    if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) {
1083
0
                        return Some(
1084
0
                            TransferCharacteristics::Srgb
1085
0
                                .make_gamma_table::<T, BUCKET, N>(BIT_DEPTH),
1086
0
                        );
1087
0
                    }
1088
1089
0
                    if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) {
1090
0
                        return Some(
1091
0
                            TransferCharacteristics::Bt709
1092
0
                                .make_gamma_table::<T, BUCKET, N>(BIT_DEPTH),
1093
0
                        );
1094
0
                    }
1095
0
                }
1096
1097
0
                let parametric_curve = ParametricCurve::new(params);
1098
0
                if let Some(v) = parametric_curve?
1099
0
                    .invert()
1100
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}
1101
                {
1102
0
                    return Some(v);
1103
0
                }
1104
1105
0
                let mut gamma_table_uint = Box::new([0; N]);
1106
1107
0
                let inverted_size: usize = N;
1108
0
                let gamma_table = linear_curve_parametric_s::<N>(params)?;
1109
0
                for (&src, dst) in gamma_table.iter().zip(gamma_table_uint.iter_mut()) {
1110
0
                    *dst = (src * 65535f32) as u16;
1111
0
                }
1112
0
                let inverted = invert_lut_boxed(&gamma_table_uint, inverted_size);
1113
0
                Some(make_gamma_lut::<T, BUCKET, N, BIT_DEPTH>(&inverted))
1114
            }
1115
0
            ToneReprCurve::Lut(data) => match data.len() {
1116
0
                0 => Some(make_gamma_linear_table::<T, BUCKET, N>(BIT_DEPTH)),
1117
0
                1 => Some(make_gamma_pow_table::<T, BUCKET, N>(
1118
0
                    1. / u8_fixed_8number_to_float(data[0]),
1119
0
                    BIT_DEPTH,
1120
0
                )),
1121
                _ => {
1122
0
                    let mut inverted_size = data.len();
1123
0
                    if inverted_size < 256 {
1124
0
                        inverted_size = 256
1125
0
                    }
1126
0
                    let inverted = invert_lut(data, inverted_size);
1127
0
                    Some(make_gamma_lut::<T, BUCKET, N, BIT_DEPTH>(&inverted))
1128
                }
1129
            },
1130
        }
1131
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>
1132
}
1133
1134
impl ColorProfile {
1135
    /// Produces LUT for 8 bit tone linearization
1136
0
    pub fn build_8bit_lin_table(
1137
0
        &self,
1138
0
        trc: &Option<ToneReprCurve>,
1139
0
    ) -> Result<Box<[f32; 256]>, CmsError> {
1140
0
        trc.as_ref()
1141
0
            .and_then(|trc| trc.build_linearize_table::<u8, 256, 8>())
1142
0
            .ok_or(CmsError::BuildTransferFunction)
1143
0
    }
1144
1145
    /// Produces LUT for Gray transfer curve with N depth
1146
0
    pub fn build_gray_linearize_table<
1147
0
        T: PointeeSizeExpressible,
1148
0
        const N: usize,
1149
0
        const BIT_DEPTH: usize,
1150
0
    >(
1151
0
        &self,
1152
0
    ) -> Result<Box<[f32; N]>, CmsError> {
1153
0
        self.gray_trc
1154
0
            .as_ref()
1155
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}
1156
0
            .ok_or(CmsError::BuildTransferFunction)
1157
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>
1158
1159
    /// Produces LUT for Red transfer curve with N depth
1160
0
    pub fn build_r_linearize_table<
1161
0
        T: PointeeSizeExpressible,
1162
0
        const N: usize,
1163
0
        const BIT_DEPTH: usize,
1164
0
    >(
1165
0
        &self,
1166
0
        use_cicp: bool,
1167
0
    ) -> Result<Box<[f32; N]>, CmsError> {
1168
0
        if use_cicp {
1169
0
            if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1170
0
                if tc.has_transfer_curve() {
1171
0
                    return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>());
1172
0
                }
1173
0
            }
1174
0
        }
1175
0
        self.red_trc
1176
0
            .as_ref()
1177
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}
1178
0
            .ok_or(CmsError::BuildTransferFunction)
1179
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>
1180
1181
    /// Produces LUT for Green transfer curve with N depth
1182
0
    pub fn build_g_linearize_table<
1183
0
        T: PointeeSizeExpressible,
1184
0
        const N: usize,
1185
0
        const BIT_DEPTH: usize,
1186
0
    >(
1187
0
        &self,
1188
0
        use_cicp: bool,
1189
0
    ) -> Result<Box<[f32; N]>, CmsError> {
1190
0
        if use_cicp {
1191
0
            if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1192
0
                if tc.has_transfer_curve() {
1193
0
                    return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>());
1194
0
                }
1195
0
            }
1196
0
        }
1197
0
        self.green_trc
1198
0
            .as_ref()
1199
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}
1200
0
            .ok_or(CmsError::BuildTransferFunction)
1201
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>
1202
1203
    /// Produces LUT for Blue transfer curve with N depth
1204
0
    pub fn build_b_linearize_table<
1205
0
        T: PointeeSizeExpressible,
1206
0
        const N: usize,
1207
0
        const BIT_DEPTH: usize,
1208
0
    >(
1209
0
        &self,
1210
0
        use_cicp: bool,
1211
0
    ) -> Result<Box<[f32; N]>, CmsError> {
1212
0
        if use_cicp {
1213
0
            if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1214
0
                if tc.has_transfer_curve() {
1215
0
                    return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>());
1216
0
                }
1217
0
            }
1218
0
        }
1219
0
        self.blue_trc
1220
0
            .as_ref()
1221
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}
1222
0
            .ok_or(CmsError::BuildTransferFunction)
1223
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>
1224
1225
    /// Build gamma table for 8 bit depth
1226
    /// Only 4092 first bins are used and values scaled in 0..255
1227
0
    pub fn build_8bit_gamma_table(
1228
0
        &self,
1229
0
        trc: &Option<ToneReprCurve>,
1230
0
        use_cicp: bool,
1231
0
    ) -> Result<Box<[u16; 65536]>, CmsError> {
1232
0
        self.build_gamma_table::<u16, 65536, 4092, 8>(trc, use_cicp)
1233
0
    }
1234
1235
    /// Build gamma table for 10 bit depth
1236
    /// Only 8192 first bins are used and values scaled in 0..1023
1237
0
    pub fn build_10bit_gamma_table(
1238
0
        &self,
1239
0
        trc: &Option<ToneReprCurve>,
1240
0
        use_cicp: bool,
1241
0
    ) -> Result<Box<[u16; 65536]>, CmsError> {
1242
0
        self.build_gamma_table::<u16, 65536, 8192, 10>(trc, use_cicp)
1243
0
    }
1244
1245
    /// Build gamma table for 12 bit depth
1246
    /// Only 16384 first bins are used and values scaled in 0..4095
1247
0
    pub fn build_12bit_gamma_table(
1248
0
        &self,
1249
0
        trc: &Option<ToneReprCurve>,
1250
0
        use_cicp: bool,
1251
0
    ) -> Result<Box<[u16; 65536]>, CmsError> {
1252
0
        self.build_gamma_table::<u16, 65536, 16384, 12>(trc, use_cicp)
1253
0
    }
1254
1255
    /// Build gamma table for 16 bit depth
1256
    /// Only 16384 first bins are used and values scaled in 0..65535
1257
0
    pub fn build_16bit_gamma_table(
1258
0
        &self,
1259
0
        trc: &Option<ToneReprCurve>,
1260
0
        use_cicp: bool,
1261
0
    ) -> Result<Box<[u16; 65536]>, CmsError> {
1262
0
        self.build_gamma_table::<u16, 65536, 65536, 16>(trc, use_cicp)
1263
0
    }
1264
1265
    /// Builds gamma table checking CICP for Transfer characteristics first.
1266
0
    pub fn build_gamma_table<
1267
0
        T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate,
1268
0
        const BUCKET: usize,
1269
0
        const N: usize,
1270
0
        const BIT_DEPTH: usize,
1271
0
    >(
1272
0
        &self,
1273
0
        trc: &Option<ToneReprCurve>,
1274
0
        use_cicp: bool,
1275
0
    ) -> Result<Box<[T; BUCKET]>, CmsError>
1276
0
    where
1277
0
        f32: AsPrimitive<T>,
1278
0
        u32: AsPrimitive<T>,
1279
    {
1280
0
        if use_cicp {
1281
0
            if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1282
0
                if tc.has_transfer_curve() {
1283
0
                    return Ok(tc.make_gamma_table::<T, BUCKET, N>(BIT_DEPTH));
1284
0
                }
1285
0
            }
1286
0
        }
1287
0
        trc.as_ref()
1288
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}
1289
0
            .ok_or(CmsError::BuildTransferFunction)
1290
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>
1291
1292
    /// Checks if profile gamma can work in extended precision and we have implementation for this
1293
0
    pub(crate) fn try_extended_gamma_evaluator(
1294
0
        &self,
1295
0
    ) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> {
1296
0
        if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1297
0
            if tc.has_transfer_curve() {
1298
0
                return Some(Box::new(ToneCurveCicpEvaluator {
1299
0
                    rgb_trc: tc.extended_gamma_tristimulus(),
1300
0
                    trc: tc.extended_gamma_single(),
1301
0
                }));
1302
0
            }
1303
0
        }
1304
0
        if !self.are_all_trc_the_same() {
1305
0
            return None;
1306
0
        }
1307
0
        let reference_trc = if self.color_space == DataColorSpace::Gray {
1308
0
            self.gray_trc.as_ref()
1309
        } else {
1310
0
            self.red_trc.as_ref()
1311
        };
1312
0
        if let Some(red_trc) = reference_trc {
1313
0
            return Self::make_gamma_evaluator_all_the_same(red_trc);
1314
0
        }
1315
0
        None
1316
0
    }
1317
1318
0
    fn make_gamma_evaluator_all_the_same(
1319
0
        red_trc: &ToneReprCurve,
1320
0
    ) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> {
1321
0
        match red_trc {
1322
0
            ToneReprCurve::Lut(lut) => {
1323
0
                if lut.is_empty() {
1324
0
                    return Some(Box::new(ToneCurveEvaluatorLinear {}));
1325
0
                }
1326
0
                if lut.len() == 1 {
1327
0
                    let gamma = 1. / u8_fixed_8number_to_float(lut[0]);
1328
0
                    return Some(Box::new(ToneCurveEvaluatorPureGamma { gamma }));
1329
0
                }
1330
0
                None
1331
            }
1332
0
            ToneReprCurve::Parametric(params) => {
1333
0
                if params.len() == 5 {
1334
0
                    let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045];
1335
0
                    let rec709_params = create_rec709_parametric();
1336
1337
0
                    let mut lc_params: [f32; 5] = [0.; 5];
1338
0
                    for (dst, src) in lc_params.iter_mut().zip(params.iter()) {
1339
0
                        *dst = *src;
1340
0
                    }
1341
1342
0
                    if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) {
1343
0
                        return Some(Box::new(ToneCurveCicpEvaluator {
1344
0
                            rgb_trc: TransferCharacteristics::Srgb.extended_gamma_tristimulus(),
1345
0
                            trc: TransferCharacteristics::Srgb.extended_gamma_single(),
1346
0
                        }));
1347
0
                    }
1348
1349
0
                    if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) {
1350
0
                        return Some(Box::new(ToneCurveCicpEvaluator {
1351
0
                            rgb_trc: TransferCharacteristics::Bt709.extended_gamma_tristimulus(),
1352
0
                            trc: TransferCharacteristics::Bt709.extended_gamma_single(),
1353
0
                        }));
1354
0
                    }
1355
0
                }
1356
1357
0
                let parametric_curve = ParametricCurve::new(params);
1358
0
                if let Some(v) = parametric_curve?.invert() {
1359
0
                    return Some(Box::new(ToneCurveParametricEvaluator { parametric: v }));
1360
0
                }
1361
0
                None
1362
            }
1363
        }
1364
0
    }
1365
1366
    /// Check if all TRC are the same
1367
0
    pub(crate) fn are_all_trc_the_same(&self) -> bool {
1368
0
        if self.color_space == DataColorSpace::Gray {
1369
0
            return true;
1370
0
        }
1371
0
        if let (Some(red_trc), Some(green_trc), Some(blue_trc)) =
1372
0
            (&self.red_trc, &self.green_trc, &self.blue_trc)
1373
        {
1374
0
            if !matches!(
1375
0
                (red_trc, green_trc, blue_trc),
1376
                (
1377
                    ToneReprCurve::Lut(_),
1378
                    ToneReprCurve::Lut(_),
1379
                    ToneReprCurve::Lut(_),
1380
                ) | (
1381
                    ToneReprCurve::Parametric(_),
1382
                    ToneReprCurve::Parametric(_),
1383
                    ToneReprCurve::Parametric(_)
1384
                )
1385
            ) {
1386
0
                return false;
1387
0
            }
1388
0
            if let (ToneReprCurve::Lut(lut0), ToneReprCurve::Lut(lut1), ToneReprCurve::Lut(lut2)) =
1389
0
                (red_trc, green_trc, blue_trc)
1390
            {
1391
0
                if lut0 == lut1 || lut1 == lut2 {
1392
0
                    return true;
1393
0
                }
1394
0
            }
1395
            if let (
1396
0
                ToneReprCurve::Parametric(lut0),
1397
0
                ToneReprCurve::Parametric(lut1),
1398
0
                ToneReprCurve::Parametric(lut2),
1399
0
            ) = (red_trc, green_trc, blue_trc)
1400
            {
1401
0
                if lut0 == lut1 || lut1 == lut2 {
1402
0
                    return true;
1403
0
                }
1404
0
            }
1405
0
        }
1406
0
        false
1407
0
    }
1408
1409
    /// Checks if profile is matrix shaper, have same TRC and TRC is linear.
1410
0
    pub(crate) fn is_linear_matrix_shaper(&self) -> bool {
1411
0
        if !self.is_matrix_shaper() {
1412
0
            return false;
1413
0
        }
1414
0
        if !self.are_all_trc_the_same() {
1415
0
            return false;
1416
0
        }
1417
0
        if let Some(red_trc) = &self.red_trc {
1418
0
            return match red_trc {
1419
0
                ToneReprCurve::Lut(lut) => {
1420
0
                    if lut.is_empty() {
1421
0
                        return true;
1422
0
                    }
1423
0
                    if is_curve_linear16(lut) {
1424
0
                        return true;
1425
0
                    }
1426
0
                    false
1427
                }
1428
0
                ToneReprCurve::Parametric(params) => {
1429
0
                    if let Some(curve) = ParametricCurve::new(params) {
1430
0
                        return curve.is_linear();
1431
0
                    }
1432
0
                    false
1433
                }
1434
            };
1435
0
        }
1436
0
        false
1437
0
    }
1438
1439
    /// Checks if profile linearization can work in extended precision and we have implementation for this
1440
0
    pub(crate) fn try_extended_linearizing_evaluator(
1441
0
        &self,
1442
0
    ) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> {
1443
0
        if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1444
0
            if tc.has_transfer_curve() {
1445
0
                return Some(Box::new(ToneCurveCicpEvaluator {
1446
0
                    rgb_trc: tc.extended_linear_tristimulus(),
1447
0
                    trc: tc.extended_linear_single(),
1448
0
                }));
1449
0
            }
1450
0
        }
1451
0
        if !self.are_all_trc_the_same() {
1452
0
            return None;
1453
0
        }
1454
0
        let reference_trc = if self.color_space == DataColorSpace::Gray {
1455
0
            self.gray_trc.as_ref()
1456
        } else {
1457
0
            self.red_trc.as_ref()
1458
        };
1459
0
        if let Some(red_trc) = reference_trc {
1460
0
            if let Some(value) = Self::make_linear_curve_evaluator_all_the_same(red_trc) {
1461
0
                return value;
1462
0
            }
1463
0
        }
1464
0
        None
1465
0
    }
1466
1467
0
    fn make_linear_curve_evaluator_all_the_same(
1468
0
        evaluator_curve: &ToneReprCurve,
1469
0
    ) -> Option<Option<Box<dyn ToneCurveEvaluator + Send + Sync>>> {
1470
0
        match evaluator_curve {
1471
0
            ToneReprCurve::Lut(lut) => {
1472
0
                if lut.is_empty() {
1473
0
                    return Some(Some(Box::new(ToneCurveEvaluatorLinear {})));
1474
0
                }
1475
0
                if lut.len() == 1 {
1476
0
                    let gamma = u8_fixed_8number_to_float(lut[0]);
1477
0
                    return Some(Some(Box::new(ToneCurveEvaluatorPureGamma { gamma })));
1478
0
                }
1479
            }
1480
0
            ToneReprCurve::Parametric(params) => {
1481
0
                if params.len() == 5 {
1482
0
                    let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045];
1483
0
                    let rec709_params = create_rec709_parametric();
1484
1485
0
                    let mut lc_params: [f32; 5] = [0.; 5];
1486
0
                    for (dst, src) in lc_params.iter_mut().zip(params.iter()) {
1487
0
                        *dst = *src;
1488
0
                    }
1489
1490
0
                    if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) {
1491
0
                        return Some(Some(Box::new(ToneCurveCicpEvaluator {
1492
0
                            rgb_trc: TransferCharacteristics::Srgb.extended_linear_tristimulus(),
1493
0
                            trc: TransferCharacteristics::Srgb.extended_linear_single(),
1494
0
                        })));
1495
0
                    }
1496
1497
0
                    if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) {
1498
0
                        return Some(Some(Box::new(ToneCurveCicpEvaluator {
1499
0
                            rgb_trc: TransferCharacteristics::Bt709.extended_linear_tristimulus(),
1500
0
                            trc: TransferCharacteristics::Bt709.extended_linear_single(),
1501
0
                        })));
1502
0
                    }
1503
0
                }
1504
1505
0
                let parametric_curve = ParametricCurve::new(params);
1506
0
                if let Some(v) = parametric_curve {
1507
0
                    return Some(Some(Box::new(ToneCurveParametricEvaluator {
1508
0
                        parametric: v,
1509
0
                    })));
1510
0
                }
1511
            }
1512
        }
1513
0
        None
1514
0
    }
1515
}
1516
1517
pub(crate) struct ToneCurveCicpEvaluator {
1518
    rgb_trc: fn(Rgb<f32>) -> Rgb<f32>,
1519
    trc: fn(f32) -> f32,
1520
}
1521
1522
pub(crate) struct ToneCurveParametricEvaluator {
1523
    parametric: ParametricCurve,
1524
}
1525
1526
pub(crate) struct ToneCurveEvaluatorPureGamma {
1527
    gamma: f32,
1528
}
1529
1530
pub(crate) struct ToneCurveEvaluatorLinear {}
1531
1532
impl ToneCurveEvaluator for ToneCurveCicpEvaluator {
1533
0
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1534
0
        (self.rgb_trc)(rgb)
1535
0
    }
1536
1537
0
    fn evaluate_value(&self, value: f32) -> f32 {
1538
0
        (self.trc)(value)
1539
0
    }
1540
}
1541
1542
impl ToneCurveEvaluator for ToneCurveParametricEvaluator {
1543
0
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1544
0
        Rgb::new(
1545
0
            self.parametric.eval(rgb.r),
1546
0
            self.parametric.eval(rgb.g),
1547
0
            self.parametric.eval(rgb.b),
1548
        )
1549
0
    }
1550
1551
0
    fn evaluate_value(&self, value: f32) -> f32 {
1552
0
        self.parametric.eval(value)
1553
0
    }
1554
}
1555
1556
impl ToneCurveEvaluator for ToneCurveEvaluatorPureGamma {
1557
0
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1558
0
        Rgb::new(
1559
0
            dirty_powf(rgb.r, self.gamma),
1560
0
            dirty_powf(rgb.g, self.gamma),
1561
0
            dirty_powf(rgb.b, self.gamma),
1562
        )
1563
0
    }
1564
1565
0
    fn evaluate_value(&self, value: f32) -> f32 {
1566
0
        dirty_powf(value, self.gamma)
1567
0
    }
1568
}
1569
1570
impl ToneCurveEvaluator for ToneCurveEvaluatorLinear {
1571
0
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1572
0
        rgb
1573
0
    }
1574
1575
0
    fn evaluate_value(&self, value: f32) -> f32 {
1576
0
        value
1577
0
    }
1578
}
1579
1580
pub trait ToneCurveEvaluator {
1581
    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32>;
1582
    fn evaluate_value(&self, value: f32) -> f32;
1583
}