Skip to main content

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