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    const BIT_DEPTH: usize,
526>() -> Box<[T; BUCKET]>
527where
528    f32: AsPrimitive<T>,
529{
530    let mut table = Box::new([T::default(); BUCKET]);
531    let max_range = if T::FINITE {
532        (1f64 / ((N - 1) as f64 / (1 << BIT_DEPTH) as f64)) as f32
533    } else {
534        (1f64 / ((N - 1) as f64)) as f32
535    };
536    for (v, output) in table.iter_mut().take(N).enumerate() {
537        if T::FINITE {
538            *output = (v as f32 * max_range).round().as_();
539        } else {
540            *output = (v as f32 * max_range).as_();
541        }
542    }
543    table
544}
545
546#[inline]
547fn lut_interp_linear_gamma_impl<
548    T: Default + Copy + 'static + PointeeSizeExpressible,
549    const N: usize,
550    const BIT_DEPTH: usize,
551>(
552    input_value: u32,
553    table: &[u16],
554) -> T
555where
556    u32: AsPrimitive<T>,
557{
558    // Start scaling input_value to the length of the array: GAMMA_CAP*(length-1).
559    // We'll divide out the GAMMA_CAP next
560    let mut value: u32 = input_value * (table.len() - 1) as u32;
561    let cap_value = N - 1;
562    // equivalent to ceil(value/GAMMA_CAP)
563    let upper: u32 = value.div_ceil(cap_value as u32);
564    // equivalent to floor(value/GAMMA_CAP)
565    let lower: u32 = value / cap_value as u32;
566    // interp is the distance from upper to value scaled to 0..GAMMA_CAP
567    let interp: u32 = value % cap_value as u32;
568    let lw_value = table[lower as usize];
569    let hw_value = table[upper as usize];
570    // the table values range from 0..65535
571    value = mlaf(
572        hw_value as u32 * interp,
573        lw_value as u32,
574        (N - 1) as u32 - interp,
575    ); // 0..(65535*GAMMA_CAP)
576
577    // round and scale
578    let max_colors = if T::FINITE { (1 << BIT_DEPTH) - 1 } else { 1 };
579    value += (cap_value * 65535 / max_colors / 2) as u32; // scale to 0...max_colors
580    value /= (cap_value * 65535 / max_colors) as u32;
581    value.as_()
582}
583
584#[inline]
585fn lut_interp_linear_gamma_impl_f32<
586    T: Default + Copy + 'static + PointeeSizeExpressible,
587    const N: usize,
588    const BIT_DEPTH: usize,
589>(
590    input_value: u32,
591    table: &[u16],
592) -> T
593where
594    f32: AsPrimitive<T>,
595{
596    // Start scaling input_value to the length of the array: GAMMA_CAP*(length-1).
597    // We'll divide out the GAMMA_CAP next
598    let guess: u32 = input_value * (table.len() - 1) as u32;
599    let cap_value = N - 1;
600    // equivalent to ceil(value/GAMMA_CAP)
601    let upper: u32 = guess.div_ceil(cap_value as u32);
602    // equivalent to floor(value/GAMMA_CAP)
603    let lower: u32 = guess / cap_value as u32;
604    // interp is the distance from upper to value scaled to 0..GAMMA_CAP
605    let interp: u32 = guess % cap_value as u32;
606    let lw_value = table[lower as usize];
607    let hw_value = table[upper as usize];
608    // the table values range from 0..65535
609    let mut value = mlaf(
610        hw_value as f32 * interp as f32,
611        lw_value as f32,
612        (N - 1) as f32 - interp as f32,
613    ); // 0..(65535*GAMMA_CAP)
614
615    // round and scale
616    let max_colors = if T::FINITE { (1 << BIT_DEPTH) - 1 } else { 1 };
617    value /= (cap_value * 65535 / max_colors) as f32;
618    value.as_()
619}
620
621#[doc(hidden)]
622pub trait GammaLutInterpolate {
623    fn gamma_lut_interp<
624        T: Default + Copy + 'static + PointeeSizeExpressible,
625        const N: usize,
626        const BIT_DEPTH: usize,
627    >(
628        input_value: u32,
629        table: &[u16],
630    ) -> T
631    where
632        u32: AsPrimitive<T>,
633        f32: AsPrimitive<T>;
634}
635
636macro_rules! gamma_lut_interp_fixed {
637    ($i_type: ident) => {
638        impl GammaLutInterpolate for $i_type {
639            #[inline]
640            fn gamma_lut_interp<
641                T: Default + Copy + 'static + PointeeSizeExpressible,
642                const N: usize,
643                const BIT_DEPTH: usize,
644            >(
645                input_value: u32,
646                table: &[u16],
647            ) -> T
648            where
649                u32: AsPrimitive<T>,
650            {
651                lut_interp_linear_gamma_impl::<T, N, BIT_DEPTH>(input_value, table)
652            }
653        }
654    };
655}
656
657gamma_lut_interp_fixed!(u8);
658gamma_lut_interp_fixed!(u16);
659
660macro_rules! gammu_lut_interp_float {
661    ($f_type: ident) => {
662        impl GammaLutInterpolate for $f_type {
663            #[inline]
664            fn gamma_lut_interp<
665                T: Default + Copy + 'static + PointeeSizeExpressible,
666                const N: usize,
667                const BIT_DEPTH: usize,
668            >(
669                input_value: u32,
670                table: &[u16],
671            ) -> T
672            where
673                f32: AsPrimitive<T>,
674                u32: AsPrimitive<T>,
675            {
676                lut_interp_linear_gamma_impl_f32::<T, N, BIT_DEPTH>(input_value, table)
677            }
678        }
679    };
680}
681
682gammu_lut_interp_float!(f32);
683gammu_lut_interp_float!(f64);
684
685pub(crate) fn make_gamma_lut<
686    T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate,
687    const BUCKET: usize,
688    const N: usize,
689    const BIT_DEPTH: usize,
690>(
691    table: &[u16],
692) -> Box<[T; BUCKET]>
693where
694    u32: AsPrimitive<T>,
695    f32: AsPrimitive<T>,
696{
697    let mut new_table = Box::new([T::default(); BUCKET]);
698    for (v, output) in new_table.iter_mut().take(N).enumerate() {
699        *output = T::gamma_lut_interp::<T, N, BIT_DEPTH>(v as u32, table);
700    }
701    new_table
702}
703
704#[inline]
705pub(crate) fn lut_interp_linear16(input_value: u16, table: &[u16]) -> u16 {
706    // Start scaling input_value to the length of the array: 65535*(length-1).
707    // We'll divide out the 65535 next
708    let mut value: u32 = input_value as u32 * (table.len() as u32 - 1);
709    let upper: u16 = value.div_ceil(65535) as u16; // equivalent to ceil(value/65535)
710    let lower: u16 = (value / 65535) as u16; // equivalent to floor(value/65535)
711    // interp is the distance from upper to value scaled to 0..65535
712    let interp: u32 = value % 65535; // 0..65535*65535
713    value = (table[upper as usize] as u32 * interp
714        + table[lower as usize] as u32 * (65535 - interp))
715        / 65535;
716    value as u16
717}
718
719#[inline]
720pub(crate) fn lut_interp_linear16_boxed<const N: usize>(input_value: u16, table: &[u16; N]) -> u16 {
721    // Start scaling input_value to the length of the array: 65535*(length-1).
722    // We'll divide out the 65535 next
723    let mut value: u32 = input_value as u32 * (table.len() as u32 - 1);
724    let upper: u16 = value.div_ceil(65535) as u16; // equivalent to ceil(value/65535)
725    let lower: u16 = (value / 65535) as u16; // equivalent to floor(value/65535)
726    // interp is the distance from upper to value scaled to 0..65535
727    let interp: u32 = value % 65535; // 0..65535*65535
728    value = (table[upper as usize] as u32 * interp
729        + table[lower as usize] as u32 * (65535 - interp))
730        / 65535;
731    value as u16
732}
733
734fn make_gamma_pow_table<
735    T: Default + Copy + 'static + PointeeSizeExpressible,
736    const BUCKET: usize,
737    const N: usize,
738    const BIT_DEPTH: usize,
739>(
740    gamma: f32,
741) -> Box<[T; BUCKET]>
742where
743    f32: AsPrimitive<T>,
744{
745    let mut table = Box::new([T::default(); BUCKET]);
746    let scale = 1f32 / (N - 1) as f32;
747    let cap = ((1 << BIT_DEPTH) - 1) as f32;
748    if T::FINITE {
749        for (v, output) in table.iter_mut().take(N).enumerate() {
750            *output = (cap * f_powf(v as f32 * scale, gamma)).round().as_();
751        }
752    } else {
753        for (v, output) in table.iter_mut().take(N).enumerate() {
754            *output = (cap * f_powf(v as f32 * scale, gamma)).as_();
755        }
756    }
757    table
758}
759
760fn make_gamma_parametric_table<
761    T: Default + Copy + 'static + PointeeSizeExpressible,
762    const BUCKET: usize,
763    const N: usize,
764    const BIT_DEPTH: usize,
765>(
766    parametric_curve: ParametricCurve,
767) -> Box<[T; BUCKET]>
768where
769    f32: AsPrimitive<T>,
770{
771    let mut table = Box::new([T::default(); BUCKET]);
772    let scale = 1f32 / (N - 1) as f32;
773    let cap = ((1 << BIT_DEPTH) - 1) as f32;
774    if T::FINITE {
775        for (v, output) in table.iter_mut().take(N).enumerate() {
776            *output = (cap * parametric_curve.eval(v as f32 * scale))
777                .round()
778                .as_();
779        }
780    } else {
781        for (v, output) in table.iter_mut().take(N).enumerate() {
782            *output = (cap * parametric_curve.eval(v as f32 * scale)).as_();
783        }
784    }
785    table
786}
787
788#[inline]
789fn compare_parametric(src: &[f32], dst: &[f32]) -> bool {
790    for (src, dst) in src.iter().zip(dst.iter()) {
791        if (src - dst).abs() > 1e-4 {
792            return false;
793        }
794    }
795    true
796}
797
798fn lut_inverse_interp16(value: u16, lut_table: &[u16]) -> u16 {
799    let mut l: i32 = 1; // 'int' Give spacing for negative values
800    let mut r: i32 = 0x10000;
801    let mut x: i32 = 0;
802    let mut res: i32;
803    let length = lut_table.len() as i32;
804
805    let mut num_zeroes: i32 = 0;
806    for &item in lut_table.iter() {
807        if item == 0 {
808            num_zeroes += 1
809        } else {
810            break;
811        }
812    }
813
814    if num_zeroes == 0 && value as i32 == 0 {
815        return 0u16;
816    }
817    let mut num_of_polys: i32 = 0;
818    for &item in lut_table.iter().rev() {
819        if item == 0xffff {
820            num_of_polys += 1
821        } else {
822            break;
823        }
824    }
825    // Does the curve belong to this case?
826    if num_zeroes > 1 || num_of_polys > 1 {
827        let a_0: i32;
828        let b_0: i32;
829        // Identify if value fall downto 0 or FFFF zone
830        if value as i32 == 0 {
831            return 0u16;
832        }
833        // if (Value == 0xFFFF) return 0xFFFF;
834        // else restrict to valid zone
835        if num_zeroes > 1 {
836            a_0 = (num_zeroes - 1) * 0xffff / (length - 1);
837            l = a_0 - 1
838        }
839        if num_of_polys > 1 {
840            b_0 = (length - 1 - num_of_polys) * 0xffff / (length - 1);
841            r = b_0 + 1
842        }
843    }
844    if r <= l {
845        // If this happens LutTable is not invertible
846        return 0u16;
847    }
848
849    while r > l {
850        x = (l + r) / 2;
851        res = lut_interp_linear16((x - 1) as u16, lut_table) as i32;
852        if res == value as i32 {
853            // Found exact match.
854            return (x - 1) as u16;
855        }
856        if res > value as i32 {
857            r = x - 1
858        } else {
859            l = x + 1
860        }
861    }
862
863    // Not found, should we interpolate?
864
865    // Get surrounding nodes
866    debug_assert!(x >= 1);
867
868    let val2: f64 = (length - 1) as f64 * ((x - 1) as f64 / 65535.0);
869    let cell0: i32 = val2.floor() as i32;
870    let cell1: i32 = val2.ceil() as i32;
871    if cell0 == cell1 {
872        return x as u16;
873    }
874
875    let y0: f64 = lut_table[cell0 as usize] as f64;
876    let x0: f64 = 65535.0 * cell0 as f64 / (length - 1) as f64;
877    let y1: f64 = lut_table[cell1 as usize] as f64;
878    let x1: f64 = 65535.0 * cell1 as f64 / (length - 1) as f64;
879    let a: f64 = (y1 - y0) / (x1 - x0);
880    let b: f64 = mlaf(y0, -a, x0);
881    if a.abs() < 0.01f64 {
882        return x as u16;
883    }
884    let f: f64 = (value as i32 as f64 - b) / a;
885    if f < 0.0 {
886        return 0u16;
887    }
888    if f >= 65535.0 {
889        return 0xffffu16;
890    }
891    (f + 0.5f64).floor() as u16
892}
893
894fn lut_inverse_interp16_boxed<const N: usize>(value: u16, lut_table: &[u16; N]) -> u16 {
895    let mut l: i32 = 1; // 'int' Give spacing for negative values
896    let mut r: i32 = 0x10000;
897    let mut x: i32 = 0;
898    let mut res: i32;
899    let length = lut_table.len() as i32;
900
901    let mut num_zeroes: i32 = 0;
902    for &item in lut_table.iter() {
903        if item == 0 {
904            num_zeroes += 1
905        } else {
906            break;
907        }
908    }
909
910    if num_zeroes == 0 && value as i32 == 0 {
911        return 0u16;
912    }
913    let mut num_of_polys: i32 = 0;
914    for &item in lut_table.iter().rev() {
915        if item == 0xffff {
916            num_of_polys += 1
917        } else {
918            break;
919        }
920    }
921    // Does the curve belong to this case?
922    if num_zeroes > 1 || num_of_polys > 1 {
923        let a_0: i32;
924        let b_0: i32;
925        // Identify if value fall downto 0 or FFFF zone
926        if value as i32 == 0 {
927            return 0u16;
928        }
929        // if (Value == 0xFFFF) return 0xFFFF;
930        // else restrict to valid zone
931        if num_zeroes > 1 {
932            a_0 = (num_zeroes - 1) * 0xffff / (length - 1);
933            l = a_0 - 1
934        }
935        if num_of_polys > 1 {
936            b_0 = (length - 1 - num_of_polys) * 0xffff / (length - 1);
937            r = b_0 + 1
938        }
939    }
940    if r <= l {
941        // If this happens LutTable is not invertible
942        return 0u16;
943    }
944
945    while r > l {
946        x = (l + r) / 2;
947        res = lut_interp_linear16_boxed((x - 1) as u16, lut_table) as i32;
948        if res == value as i32 {
949            // Found exact match.
950            return (x - 1) as u16;
951        }
952        if res > value as i32 {
953            r = x - 1
954        } else {
955            l = x + 1
956        }
957    }
958
959    // Not found, should we interpolate?
960
961    // Get surrounding nodes
962    debug_assert!(x >= 1);
963
964    let val2: f64 = (length - 1) as f64 * ((x - 1) as f64 / 65535.0);
965    let cell0: i32 = val2.floor() as i32;
966    let cell1: i32 = val2.ceil() as i32;
967    if cell0 == cell1 {
968        return x as u16;
969    }
970
971    let y0: f64 = lut_table[cell0 as usize] as f64;
972    let x0: f64 = 65535.0 * cell0 as f64 / (length - 1) as f64;
973    let y1: f64 = lut_table[cell1 as usize] as f64;
974    let x1: f64 = 65535.0 * cell1 as f64 / (length - 1) as f64;
975    let a: f64 = (y1 - y0) / (x1 - x0);
976    let b: f64 = mlaf(y0, -a, x0);
977    if a.abs() < 0.01f64 {
978        return x as u16;
979    }
980    let f: f64 = (value as i32 as f64 - b) / a;
981    if f < 0.0 {
982        return 0u16;
983    }
984    if f >= 65535.0 {
985        return 0xffffu16;
986    }
987    (f + 0.5f64).floor() as u16
988}
989
990fn invert_lut(table: &[u16], out_length: usize) -> Vec<u16> {
991    // For now, we invert the lut by creating a lut of size out_length
992    // and attempting to look up a value for each entry using lut_inverse_interp16
993    let mut output = vec![0u16; out_length];
994    let scale_value = 65535f64 / (out_length - 1) as f64;
995    for (i, out) in output.iter_mut().enumerate() {
996        let x: f64 = i as f64 * scale_value;
997        let input: u16 = (x + 0.5f64).floor() as u16;
998        *out = lut_inverse_interp16(input, table);
999    }
1000    output
1001}
1002
1003fn invert_lut_boxed<const N: usize>(table: &[u16; N], out_length: usize) -> Vec<u16> {
1004    // For now, we invert the lut by creating a lut of size out_length
1005    // and attempting to look up a value for each entry using lut_inverse_interp16
1006    let mut output = vec![0u16; out_length];
1007    let scale_value = 65535f64 / (out_length - 1) as f64;
1008    for (i, out) in output.iter_mut().enumerate() {
1009        let x: f64 = i as f64 * scale_value;
1010        let input: u16 = (x + 0.5f64).floor() as u16;
1011        *out = lut_inverse_interp16_boxed(input, table);
1012    }
1013    output
1014}
1015
1016impl ToneReprCurve {
1017    pub(crate) fn to_clut(&self) -> Result<Vec<f32>, CmsError> {
1018        match self {
1019            ToneReprCurve::Lut(lut) => {
1020                if lut.is_empty() {
1021                    let passthrough_table = passthrough_table::<f32, 16384, 1>();
1022                    Ok(passthrough_table.to_vec())
1023                } else {
1024                    Ok(lut
1025                        .iter()
1026                        .map(|&x| x as f32 * (1. / 65535.))
1027                        .collect::<Vec<_>>())
1028                }
1029            }
1030            ToneReprCurve::Parametric(_) => {
1031                let curve = self
1032                    .build_linearize_table::<f32, 65535, 1>()
1033                    .ok_or(CmsError::InvalidTrcCurve)?;
1034                let max_value = f32::NOT_FINITE_LINEAR_TABLE_SIZE - 1;
1035                let sliced = &curve[..max_value];
1036                Ok(sliced.to_vec())
1037            }
1038        }
1039    }
1040
1041    pub(crate) fn build_linearize_table<
1042        T: PointeeSizeExpressible,
1043        const N: usize,
1044        const BIT_DEPTH: usize,
1045    >(
1046        &self,
1047    ) -> Option<Box<[f32; N]>> {
1048        match self {
1049            ToneReprCurve::Parametric(params) => linear_curve_parametric::<T, N, BIT_DEPTH>(params),
1050            ToneReprCurve::Lut(data) => match data.len() {
1051                0 => Some(passthrough_table::<T, N, BIT_DEPTH>()),
1052                1 => Some(linear_forward_table::<T, N, BIT_DEPTH>(data[0])),
1053                _ => Some(linear_lut_interpolate::<T, N, BIT_DEPTH>(data)),
1054            },
1055        }
1056    }
1057
1058    pub(crate) fn build_gamma_table<
1059        T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate,
1060        const BUCKET: usize,
1061        const N: usize,
1062        const BIT_DEPTH: usize,
1063    >(
1064        &self,
1065    ) -> Option<Box<[T; BUCKET]>>
1066    where
1067        f32: AsPrimitive<T>,
1068        u32: AsPrimitive<T>,
1069    {
1070        match self {
1071            ToneReprCurve::Parametric(params) => {
1072                if params.len() == 5 {
1073                    let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045];
1074                    let rec709_params = create_rec709_parametric();
1075
1076                    let mut lc_params: [f32; 5] = [0.; 5];
1077                    for (dst, src) in lc_params.iter_mut().zip(params.iter()) {
1078                        *dst = *src;
1079                    }
1080
1081                    if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) {
1082                        return Some(
1083                            TransferCharacteristics::Srgb
1084                                .make_gamma_table::<T, BUCKET, N, BIT_DEPTH>(),
1085                        );
1086                    }
1087
1088                    if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) {
1089                        return Some(
1090                            TransferCharacteristics::Bt709
1091                                .make_gamma_table::<T, BUCKET, N, BIT_DEPTH>(),
1092                        );
1093                    }
1094                }
1095
1096                let parametric_curve = ParametricCurve::new(params);
1097                if let Some(v) = parametric_curve?
1098                    .invert()
1099                    .map(|x| make_gamma_parametric_table::<T, BUCKET, N, BIT_DEPTH>(x))
1100                {
1101                    return Some(v);
1102                }
1103
1104                let mut gamma_table_uint = Box::new([0; N]);
1105
1106                let inverted_size: usize = N;
1107                let gamma_table = linear_curve_parametric_s::<N>(params)?;
1108                for (&src, dst) in gamma_table.iter().zip(gamma_table_uint.iter_mut()) {
1109                    *dst = (src * 65535f32) as u16;
1110                }
1111                let inverted = invert_lut_boxed(&gamma_table_uint, inverted_size);
1112                Some(make_gamma_lut::<T, BUCKET, N, BIT_DEPTH>(&inverted))
1113            }
1114            ToneReprCurve::Lut(data) => match data.len() {
1115                0 => Some(make_gamma_linear_table::<T, BUCKET, N, BIT_DEPTH>()),
1116                1 => Some(make_gamma_pow_table::<T, BUCKET, N, BIT_DEPTH>(
1117                    1. / u8_fixed_8number_to_float(data[0]),
1118                )),
1119                _ => {
1120                    let mut inverted_size = data.len();
1121                    if inverted_size < 256 {
1122                        inverted_size = 256
1123                    }
1124                    let inverted = invert_lut(data, inverted_size);
1125                    Some(make_gamma_lut::<T, BUCKET, N, BIT_DEPTH>(&inverted))
1126                }
1127            },
1128        }
1129    }
1130}
1131
1132impl ColorProfile {
1133    /// Produces LUT for 8 bit tone linearization
1134    pub fn build_8bit_lin_table(
1135        &self,
1136        trc: &Option<ToneReprCurve>,
1137    ) -> Result<Box<[f32; 256]>, CmsError> {
1138        trc.as_ref()
1139            .and_then(|trc| trc.build_linearize_table::<u8, 256, 8>())
1140            .ok_or(CmsError::BuildTransferFunction)
1141    }
1142
1143    /// Produces LUT for Gray transfer curve with N depth
1144    pub fn build_gray_linearize_table<
1145        T: PointeeSizeExpressible,
1146        const N: usize,
1147        const BIT_DEPTH: usize,
1148    >(
1149        &self,
1150    ) -> Result<Box<[f32; N]>, CmsError> {
1151        self.gray_trc
1152            .as_ref()
1153            .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
1154            .ok_or(CmsError::BuildTransferFunction)
1155    }
1156
1157    /// Produces LUT for Red transfer curve with N depth
1158    pub fn build_r_linearize_table<
1159        T: PointeeSizeExpressible,
1160        const N: usize,
1161        const BIT_DEPTH: usize,
1162    >(
1163        &self,
1164        use_cicp: bool,
1165    ) -> Result<Box<[f32; N]>, CmsError> {
1166        if use_cicp {
1167            if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1168                if tc.has_transfer_curve() {
1169                    return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>());
1170                }
1171            }
1172        }
1173        self.red_trc
1174            .as_ref()
1175            .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
1176            .ok_or(CmsError::BuildTransferFunction)
1177    }
1178
1179    /// Produces LUT for Green transfer curve with N depth
1180    pub fn build_g_linearize_table<
1181        T: PointeeSizeExpressible,
1182        const N: usize,
1183        const BIT_DEPTH: usize,
1184    >(
1185        &self,
1186        use_cicp: bool,
1187    ) -> Result<Box<[f32; N]>, CmsError> {
1188        if use_cicp {
1189            if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1190                if tc.has_transfer_curve() {
1191                    return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>());
1192                }
1193            }
1194        }
1195        self.green_trc
1196            .as_ref()
1197            .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
1198            .ok_or(CmsError::BuildTransferFunction)
1199    }
1200
1201    /// Produces LUT for Blue transfer curve with N depth
1202    pub fn build_b_linearize_table<
1203        T: PointeeSizeExpressible,
1204        const N: usize,
1205        const BIT_DEPTH: usize,
1206    >(
1207        &self,
1208        use_cicp: bool,
1209    ) -> Result<Box<[f32; N]>, CmsError> {
1210        if use_cicp {
1211            if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1212                if tc.has_transfer_curve() {
1213                    return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>());
1214                }
1215            }
1216        }
1217        self.blue_trc
1218            .as_ref()
1219            .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
1220            .ok_or(CmsError::BuildTransferFunction)
1221    }
1222
1223    /// Build gamma table for 8 bit depth
1224    /// Only 4092 first bins are used and values scaled in 0..255
1225    pub fn build_8bit_gamma_table(
1226        &self,
1227        trc: &Option<ToneReprCurve>,
1228        use_cicp: bool,
1229    ) -> Result<Box<[u16; 65536]>, CmsError> {
1230        self.build_gamma_table::<u16, 65536, 4092, 8>(trc, use_cicp)
1231    }
1232
1233    /// Build gamma table for 10 bit depth
1234    /// Only 8192 first bins are used and values scaled in 0..1023
1235    pub fn build_10bit_gamma_table(
1236        &self,
1237        trc: &Option<ToneReprCurve>,
1238        use_cicp: bool,
1239    ) -> Result<Box<[u16; 65536]>, CmsError> {
1240        self.build_gamma_table::<u16, 65536, 8192, 10>(trc, use_cicp)
1241    }
1242
1243    /// Build gamma table for 12 bit depth
1244    /// Only 16384 first bins are used and values scaled in 0..4095
1245    pub fn build_12bit_gamma_table(
1246        &self,
1247        trc: &Option<ToneReprCurve>,
1248        use_cicp: bool,
1249    ) -> Result<Box<[u16; 65536]>, CmsError> {
1250        self.build_gamma_table::<u16, 65536, 16384, 12>(trc, use_cicp)
1251    }
1252
1253    /// Build gamma table for 16 bit depth
1254    /// Only 16384 first bins are used and values scaled in 0..65535
1255    pub fn build_16bit_gamma_table(
1256        &self,
1257        trc: &Option<ToneReprCurve>,
1258        use_cicp: bool,
1259    ) -> Result<Box<[u16; 65536]>, CmsError> {
1260        self.build_gamma_table::<u16, 65536, 65536, 16>(trc, use_cicp)
1261    }
1262
1263    /// Builds gamma table checking CICP for Transfer characteristics first.
1264    pub fn build_gamma_table<
1265        T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate,
1266        const BUCKET: usize,
1267        const N: usize,
1268        const BIT_DEPTH: usize,
1269    >(
1270        &self,
1271        trc: &Option<ToneReprCurve>,
1272        use_cicp: bool,
1273    ) -> Result<Box<[T; BUCKET]>, CmsError>
1274    where
1275        f32: AsPrimitive<T>,
1276        u32: AsPrimitive<T>,
1277    {
1278        if use_cicp {
1279            if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1280                if tc.has_transfer_curve() {
1281                    return Ok(tc.make_gamma_table::<T, BUCKET, N, BIT_DEPTH>());
1282                }
1283            }
1284        }
1285        trc.as_ref()
1286            .and_then(|trc| trc.build_gamma_table::<T, BUCKET, N, BIT_DEPTH>())
1287            .ok_or(CmsError::BuildTransferFunction)
1288    }
1289
1290    /// Checks if profile gamma can work in extended precision and we have implementation for this
1291    pub(crate) fn try_extended_gamma_evaluator(
1292        &self,
1293    ) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> {
1294        if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1295            if tc.has_transfer_curve() {
1296                return Some(Box::new(ToneCurveCicpEvaluator {
1297                    rgb_trc: tc.extended_gamma_tristimulus(),
1298                    trc: tc.extended_gamma_single(),
1299                }));
1300            }
1301        }
1302        if !self.are_all_trc_the_same() {
1303            return None;
1304        }
1305        let reference_trc = if self.color_space == DataColorSpace::Gray {
1306            self.gray_trc.as_ref()
1307        } else {
1308            self.red_trc.as_ref()
1309        };
1310        if let Some(red_trc) = reference_trc {
1311            return Self::make_gamma_evaluator_all_the_same(red_trc);
1312        }
1313        None
1314    }
1315
1316    fn make_gamma_evaluator_all_the_same(
1317        red_trc: &ToneReprCurve,
1318    ) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> {
1319        match red_trc {
1320            ToneReprCurve::Lut(lut) => {
1321                if lut.is_empty() {
1322                    return Some(Box::new(ToneCurveEvaluatorLinear {}));
1323                }
1324                if lut.len() == 1 {
1325                    let gamma = 1. / u8_fixed_8number_to_float(lut[0]);
1326                    return Some(Box::new(ToneCurveEvaluatorPureGamma { gamma }));
1327                }
1328                None
1329            }
1330            ToneReprCurve::Parametric(params) => {
1331                if params.len() == 5 {
1332                    let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045];
1333                    let rec709_params = create_rec709_parametric();
1334
1335                    let mut lc_params: [f32; 5] = [0.; 5];
1336                    for (dst, src) in lc_params.iter_mut().zip(params.iter()) {
1337                        *dst = *src;
1338                    }
1339
1340                    if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) {
1341                        return Some(Box::new(ToneCurveCicpEvaluator {
1342                            rgb_trc: TransferCharacteristics::Srgb.extended_gamma_tristimulus(),
1343                            trc: TransferCharacteristics::Srgb.extended_gamma_single(),
1344                        }));
1345                    }
1346
1347                    if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) {
1348                        return Some(Box::new(ToneCurveCicpEvaluator {
1349                            rgb_trc: TransferCharacteristics::Bt709.extended_gamma_tristimulus(),
1350                            trc: TransferCharacteristics::Bt709.extended_gamma_single(),
1351                        }));
1352                    }
1353                }
1354
1355                let parametric_curve = ParametricCurve::new(params);
1356                if let Some(v) = parametric_curve?.invert() {
1357                    return Some(Box::new(ToneCurveParametricEvaluator { parametric: v }));
1358                }
1359                None
1360            }
1361        }
1362    }
1363
1364    /// Check if all TRC are the same
1365    pub(crate) fn are_all_trc_the_same(&self) -> bool {
1366        if self.color_space == DataColorSpace::Gray {
1367            return true;
1368        }
1369        if let (Some(red_trc), Some(green_trc), Some(blue_trc)) =
1370            (&self.red_trc, &self.green_trc, &self.blue_trc)
1371        {
1372            if !matches!(
1373                (red_trc, green_trc, blue_trc),
1374                (
1375                    ToneReprCurve::Lut(_),
1376                    ToneReprCurve::Lut(_),
1377                    ToneReprCurve::Lut(_),
1378                ) | (
1379                    ToneReprCurve::Parametric(_),
1380                    ToneReprCurve::Parametric(_),
1381                    ToneReprCurve::Parametric(_)
1382                )
1383            ) {
1384                return false;
1385            }
1386            if let (ToneReprCurve::Lut(lut0), ToneReprCurve::Lut(lut1), ToneReprCurve::Lut(lut2)) =
1387                (red_trc, green_trc, blue_trc)
1388            {
1389                if lut0 == lut1 || lut1 == lut2 {
1390                    return true;
1391                }
1392            }
1393            if let (
1394                ToneReprCurve::Parametric(lut0),
1395                ToneReprCurve::Parametric(lut1),
1396                ToneReprCurve::Parametric(lut2),
1397            ) = (red_trc, green_trc, blue_trc)
1398            {
1399                if lut0 == lut1 || lut1 == lut2 {
1400                    return true;
1401                }
1402            }
1403        }
1404        false
1405    }
1406
1407    /// Checks if profile is matrix shaper, have same TRC and TRC is linear.
1408    pub(crate) fn is_linear_matrix_shaper(&self) -> bool {
1409        if !self.is_matrix_shaper() {
1410            return false;
1411        }
1412        if !self.are_all_trc_the_same() {
1413            return false;
1414        }
1415        if let Some(red_trc) = &self.red_trc {
1416            return match red_trc {
1417                ToneReprCurve::Lut(lut) => {
1418                    if lut.is_empty() {
1419                        return true;
1420                    }
1421                    if is_curve_linear16(lut) {
1422                        return true;
1423                    }
1424                    false
1425                }
1426                ToneReprCurve::Parametric(params) => {
1427                    if let Some(curve) = ParametricCurve::new(params) {
1428                        return curve.is_linear();
1429                    }
1430                    false
1431                }
1432            };
1433        }
1434        false
1435    }
1436
1437    /// Checks if profile linearization can work in extended precision and we have implementation for this
1438    pub(crate) fn try_extended_linearizing_evaluator(
1439        &self,
1440    ) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> {
1441        if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1442            if tc.has_transfer_curve() {
1443                return Some(Box::new(ToneCurveCicpEvaluator {
1444                    rgb_trc: tc.extended_linear_tristimulus(),
1445                    trc: tc.extended_linear_single(),
1446                }));
1447            }
1448        }
1449        if !self.are_all_trc_the_same() {
1450            return None;
1451        }
1452        let reference_trc = if self.color_space == DataColorSpace::Gray {
1453            self.gray_trc.as_ref()
1454        } else {
1455            self.red_trc.as_ref()
1456        };
1457        if let Some(red_trc) = reference_trc {
1458            if let Some(value) = Self::make_linear_curve_evaluator_all_the_same(red_trc) {
1459                return value;
1460            }
1461        }
1462        None
1463    }
1464
1465    fn make_linear_curve_evaluator_all_the_same(
1466        evaluator_curve: &ToneReprCurve,
1467    ) -> Option<Option<Box<dyn ToneCurveEvaluator + Send + Sync>>> {
1468        match evaluator_curve {
1469            ToneReprCurve::Lut(lut) => {
1470                if lut.is_empty() {
1471                    return Some(Some(Box::new(ToneCurveEvaluatorLinear {})));
1472                }
1473                if lut.len() == 1 {
1474                    let gamma = u8_fixed_8number_to_float(lut[0]);
1475                    return Some(Some(Box::new(ToneCurveEvaluatorPureGamma { gamma })));
1476                }
1477            }
1478            ToneReprCurve::Parametric(params) => {
1479                if params.len() == 5 {
1480                    let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045];
1481                    let rec709_params = create_rec709_parametric();
1482
1483                    let mut lc_params: [f32; 5] = [0.; 5];
1484                    for (dst, src) in lc_params.iter_mut().zip(params.iter()) {
1485                        *dst = *src;
1486                    }
1487
1488                    if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) {
1489                        return Some(Some(Box::new(ToneCurveCicpEvaluator {
1490                            rgb_trc: TransferCharacteristics::Srgb.extended_linear_tristimulus(),
1491                            trc: TransferCharacteristics::Srgb.extended_linear_single(),
1492                        })));
1493                    }
1494
1495                    if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) {
1496                        return Some(Some(Box::new(ToneCurveCicpEvaluator {
1497                            rgb_trc: TransferCharacteristics::Bt709.extended_linear_tristimulus(),
1498                            trc: TransferCharacteristics::Bt709.extended_linear_single(),
1499                        })));
1500                    }
1501                }
1502
1503                let parametric_curve = ParametricCurve::new(params);
1504                if let Some(v) = parametric_curve {
1505                    return Some(Some(Box::new(ToneCurveParametricEvaluator {
1506                        parametric: v,
1507                    })));
1508                }
1509            }
1510        }
1511        None
1512    }
1513}
1514
1515pub(crate) struct ToneCurveCicpEvaluator {
1516    rgb_trc: fn(Rgb<f32>) -> Rgb<f32>,
1517    trc: fn(f32) -> f32,
1518}
1519
1520pub(crate) struct ToneCurveParametricEvaluator {
1521    parametric: ParametricCurve,
1522}
1523
1524pub(crate) struct ToneCurveEvaluatorPureGamma {
1525    gamma: f32,
1526}
1527
1528pub(crate) struct ToneCurveEvaluatorLinear {}
1529
1530impl ToneCurveEvaluator for ToneCurveCicpEvaluator {
1531    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1532        (self.rgb_trc)(rgb)
1533    }
1534
1535    fn evaluate_value(&self, value: f32) -> f32 {
1536        (self.trc)(value)
1537    }
1538}
1539
1540impl ToneCurveEvaluator for ToneCurveParametricEvaluator {
1541    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1542        Rgb::new(
1543            self.parametric.eval(rgb.r),
1544            self.parametric.eval(rgb.g),
1545            self.parametric.eval(rgb.b),
1546        )
1547    }
1548
1549    fn evaluate_value(&self, value: f32) -> f32 {
1550        self.parametric.eval(value)
1551    }
1552}
1553
1554impl ToneCurveEvaluator for ToneCurveEvaluatorPureGamma {
1555    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1556        Rgb::new(
1557            dirty_powf(rgb.r, self.gamma),
1558            dirty_powf(rgb.g, self.gamma),
1559            dirty_powf(rgb.b, self.gamma),
1560        )
1561    }
1562
1563    fn evaluate_value(&self, value: f32) -> f32 {
1564        dirty_powf(value, self.gamma)
1565    }
1566}
1567
1568impl ToneCurveEvaluator for ToneCurveEvaluatorLinear {
1569    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1570        rgb
1571    }
1572
1573    fn evaluate_value(&self, value: f32) -> f32 {
1574        value
1575    }
1576}
1577
1578pub trait ToneCurveEvaluator {
1579    fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32>;
1580    fn evaluate_value(&self, value: f32) -> f32;
1581}