moxcms/
trc.rs

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