1use 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 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 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 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 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
218pub 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 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 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 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 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 !(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 (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#[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 let mut value: u32 = input_value * (table.len() - 1) as u32;
562 let cap_value = N - 1;
563 let upper: u32 = value.div_ceil(cap_value as u32);
565 let lower: u32 = value / cap_value as u32;
567 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 value = mlaf(
573 hw_value as u32 * interp,
574 lw_value as u32,
575 (N - 1) as u32 - interp,
576 ); let max_colors = if T::FINITE { (1 << BIT_DEPTH) - 1 } else { 1 };
580 value += (cap_value * 65535 / max_colors / 2) as u32; 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 let guess: u32 = input_value * (table.len() - 1) as u32;
600 let cap_value = N - 1;
601 let upper: u32 = guess.div_ceil(cap_value as u32);
603 let lower: u32 = guess / cap_value as u32;
605 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 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 ); 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 let mut value: u32 = input_value as u32 * (table.len() as u32 - 1);
710 let upper: u16 = value.div_ceil(65535) as u16; let lower: u16 = (value / 65535) as u16; let interp: u32 = value % 65535; 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 let mut value: u32 = input_value as u32 * (table.len() as u32 - 1);
725 let upper: u16 = value.div_ceil(65535) as u16; let lower: u16 = (value / 65535) as u16; let interp: u32 = value % 65535; 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; 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 if num_zeroes > 1 || num_of_polys > 1 {
828 let a_0: i32;
829 let b_0: i32;
830 if value as i32 == 0 {
832 return 0u16;
833 }
834 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 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 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 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; 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 if num_zeroes > 1 || num_of_polys > 1 {
924 let a_0: i32;
925 let b_0: i32;
926 if value as i32 == 0 {
928 return 0u16;
929 }
930 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 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 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 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 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 let mut output = vec![0u16; out_length];
1008 let scale_value = 65535f64 / (out_length - 1) as f64;
1009 for (i, out) in output.iter_mut().enumerate() {
1010 let x: f64 = i as f64 * scale_value;
1011 let input: u16 = (x + 0.5f64).floor() as u16;
1012 *out = lut_inverse_interp16_boxed(input, table);
1013 }
1014 output
1015}
1016
1017impl ToneReprCurve {
1018 pub(crate) fn to_clut(&self) -> Result<Vec<f32>, CmsError> {
1019 match self {
1020 ToneReprCurve::Lut(lut) => {
1021 if lut.is_empty() {
1022 let passthrough_table = passthrough_table::<f32, 16384, 1>();
1023 Ok(passthrough_table.to_vec())
1024 } else {
1025 Ok(lut
1026 .iter()
1027 .map(|&x| x as f32 * (1. / 65535.))
1028 .collect::<Vec<_>>())
1029 }
1030 }
1031 ToneReprCurve::Parametric(_) => {
1032 let curve = self
1033 .build_linearize_table::<f32, 65535, 1>()
1034 .ok_or(CmsError::InvalidTrcCurve)?;
1035 let max_value = f32::NOT_FINITE_LINEAR_TABLE_SIZE - 1;
1036 let sliced = &curve[..max_value];
1037 Ok(sliced.to_vec())
1038 }
1039 }
1040 }
1041
1042 pub(crate) fn build_linearize_table<
1043 T: PointeeSizeExpressible,
1044 const N: usize,
1045 const BIT_DEPTH: usize,
1046 >(
1047 &self,
1048 ) -> Option<Box<[f32; N]>> {
1049 match self {
1050 ToneReprCurve::Parametric(params) => linear_curve_parametric::<T, N, BIT_DEPTH>(params),
1051 ToneReprCurve::Lut(data) => match data.len() {
1052 0 => Some(passthrough_table::<T, N, BIT_DEPTH>()),
1053 1 => Some(linear_forward_table::<T, N, BIT_DEPTH>(data[0])),
1054 _ => Some(linear_lut_interpolate::<T, N, BIT_DEPTH>(data)),
1055 },
1056 }
1057 }
1058
1059 pub(crate) fn build_gamma_table<
1060 T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate,
1061 const BUCKET: usize,
1062 const N: usize,
1063 const BIT_DEPTH: usize,
1064 >(
1065 &self,
1066 ) -> Option<Box<[T; BUCKET]>>
1067 where
1068 f32: AsPrimitive<T>,
1069 u32: AsPrimitive<T>,
1070 {
1071 match self {
1072 ToneReprCurve::Parametric(params) => {
1073 if params.len() == 5 {
1074 let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045];
1075 let rec709_params = create_rec709_parametric();
1076
1077 let mut lc_params: [f32; 5] = [0.; 5];
1078 for (dst, src) in lc_params.iter_mut().zip(params.iter()) {
1079 *dst = *src;
1080 }
1081
1082 if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) {
1083 return Some(
1084 TransferCharacteristics::Srgb
1085 .make_gamma_table::<T, BUCKET, N>(BIT_DEPTH),
1086 );
1087 }
1088
1089 if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) {
1090 return Some(
1091 TransferCharacteristics::Bt709
1092 .make_gamma_table::<T, BUCKET, N>(BIT_DEPTH),
1093 );
1094 }
1095 }
1096
1097 let parametric_curve = ParametricCurve::new(params);
1098 if let Some(v) = parametric_curve?
1099 .invert()
1100 .map(|x| make_gamma_parametric_table::<T, BUCKET, N, BIT_DEPTH>(x))
1101 {
1102 return Some(v);
1103 }
1104
1105 let mut gamma_table_uint = Box::new([0; N]);
1106
1107 let inverted_size: usize = N;
1108 let gamma_table = linear_curve_parametric_s::<N>(params)?;
1109 for (&src, dst) in gamma_table.iter().zip(gamma_table_uint.iter_mut()) {
1110 *dst = (src * 65535f32) as u16;
1111 }
1112 let inverted = invert_lut_boxed(&gamma_table_uint, inverted_size);
1113 Some(make_gamma_lut::<T, BUCKET, N, BIT_DEPTH>(&inverted))
1114 }
1115 ToneReprCurve::Lut(data) => match data.len() {
1116 0 => Some(make_gamma_linear_table::<T, BUCKET, N>(BIT_DEPTH)),
1117 1 => Some(make_gamma_pow_table::<T, BUCKET, N>(
1118 1. / u8_fixed_8number_to_float(data[0]),
1119 BIT_DEPTH,
1120 )),
1121 _ => {
1122 let mut inverted_size = data.len();
1123 if inverted_size < 256 {
1124 inverted_size = 256
1125 }
1126 let inverted = invert_lut(data, inverted_size);
1127 Some(make_gamma_lut::<T, BUCKET, N, BIT_DEPTH>(&inverted))
1128 }
1129 },
1130 }
1131 }
1132}
1133
1134impl ColorProfile {
1135 pub fn build_8bit_lin_table(
1137 &self,
1138 trc: &Option<ToneReprCurve>,
1139 ) -> Result<Box<[f32; 256]>, CmsError> {
1140 trc.as_ref()
1141 .and_then(|trc| trc.build_linearize_table::<u8, 256, 8>())
1142 .ok_or(CmsError::BuildTransferFunction)
1143 }
1144
1145 pub fn build_gray_linearize_table<
1147 T: PointeeSizeExpressible,
1148 const N: usize,
1149 const BIT_DEPTH: usize,
1150 >(
1151 &self,
1152 ) -> Result<Box<[f32; N]>, CmsError> {
1153 self.gray_trc
1154 .as_ref()
1155 .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
1156 .ok_or(CmsError::BuildTransferFunction)
1157 }
1158
1159 pub fn build_r_linearize_table<
1161 T: PointeeSizeExpressible,
1162 const N: usize,
1163 const BIT_DEPTH: usize,
1164 >(
1165 &self,
1166 use_cicp: bool,
1167 ) -> Result<Box<[f32; N]>, CmsError> {
1168 if use_cicp {
1169 if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1170 if tc.has_transfer_curve() {
1171 return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>());
1172 }
1173 }
1174 }
1175 self.red_trc
1176 .as_ref()
1177 .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
1178 .ok_or(CmsError::BuildTransferFunction)
1179 }
1180
1181 pub fn build_g_linearize_table<
1183 T: PointeeSizeExpressible,
1184 const N: usize,
1185 const BIT_DEPTH: usize,
1186 >(
1187 &self,
1188 use_cicp: bool,
1189 ) -> Result<Box<[f32; N]>, CmsError> {
1190 if use_cicp {
1191 if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1192 if tc.has_transfer_curve() {
1193 return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>());
1194 }
1195 }
1196 }
1197 self.green_trc
1198 .as_ref()
1199 .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
1200 .ok_or(CmsError::BuildTransferFunction)
1201 }
1202
1203 pub fn build_b_linearize_table<
1205 T: PointeeSizeExpressible,
1206 const N: usize,
1207 const BIT_DEPTH: usize,
1208 >(
1209 &self,
1210 use_cicp: bool,
1211 ) -> Result<Box<[f32; N]>, CmsError> {
1212 if use_cicp {
1213 if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1214 if tc.has_transfer_curve() {
1215 return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>());
1216 }
1217 }
1218 }
1219 self.blue_trc
1220 .as_ref()
1221 .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
1222 .ok_or(CmsError::BuildTransferFunction)
1223 }
1224
1225 pub fn build_8bit_gamma_table(
1228 &self,
1229 trc: &Option<ToneReprCurve>,
1230 use_cicp: bool,
1231 ) -> Result<Box<[u16; 65536]>, CmsError> {
1232 self.build_gamma_table::<u16, 65536, 4092, 8>(trc, use_cicp)
1233 }
1234
1235 pub fn build_10bit_gamma_table(
1238 &self,
1239 trc: &Option<ToneReprCurve>,
1240 use_cicp: bool,
1241 ) -> Result<Box<[u16; 65536]>, CmsError> {
1242 self.build_gamma_table::<u16, 65536, 8192, 10>(trc, use_cicp)
1243 }
1244
1245 pub fn build_12bit_gamma_table(
1248 &self,
1249 trc: &Option<ToneReprCurve>,
1250 use_cicp: bool,
1251 ) -> Result<Box<[u16; 65536]>, CmsError> {
1252 self.build_gamma_table::<u16, 65536, 16384, 12>(trc, use_cicp)
1253 }
1254
1255 pub fn build_16bit_gamma_table(
1258 &self,
1259 trc: &Option<ToneReprCurve>,
1260 use_cicp: bool,
1261 ) -> Result<Box<[u16; 65536]>, CmsError> {
1262 self.build_gamma_table::<u16, 65536, 65536, 16>(trc, use_cicp)
1263 }
1264
1265 pub fn build_gamma_table<
1267 T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate,
1268 const BUCKET: usize,
1269 const N: usize,
1270 const BIT_DEPTH: usize,
1271 >(
1272 &self,
1273 trc: &Option<ToneReprCurve>,
1274 use_cicp: bool,
1275 ) -> Result<Box<[T; BUCKET]>, CmsError>
1276 where
1277 f32: AsPrimitive<T>,
1278 u32: AsPrimitive<T>,
1279 {
1280 if use_cicp {
1281 if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1282 if tc.has_transfer_curve() {
1283 return Ok(tc.make_gamma_table::<T, BUCKET, N>(BIT_DEPTH));
1284 }
1285 }
1286 }
1287 trc.as_ref()
1288 .and_then(|trc| trc.build_gamma_table::<T, BUCKET, N, BIT_DEPTH>())
1289 .ok_or(CmsError::BuildTransferFunction)
1290 }
1291
1292 pub(crate) fn try_extended_gamma_evaluator(
1294 &self,
1295 ) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> {
1296 if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1297 if tc.has_transfer_curve() {
1298 return Some(Box::new(ToneCurveCicpEvaluator {
1299 rgb_trc: tc.extended_gamma_tristimulus(),
1300 trc: tc.extended_gamma_single(),
1301 }));
1302 }
1303 }
1304 if !self.are_all_trc_the_same() {
1305 return None;
1306 }
1307 let reference_trc = if self.color_space == DataColorSpace::Gray {
1308 self.gray_trc.as_ref()
1309 } else {
1310 self.red_trc.as_ref()
1311 };
1312 if let Some(red_trc) = reference_trc {
1313 return Self::make_gamma_evaluator_all_the_same(red_trc);
1314 }
1315 None
1316 }
1317
1318 fn make_gamma_evaluator_all_the_same(
1319 red_trc: &ToneReprCurve,
1320 ) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> {
1321 match red_trc {
1322 ToneReprCurve::Lut(lut) => {
1323 if lut.is_empty() {
1324 return Some(Box::new(ToneCurveEvaluatorLinear {}));
1325 }
1326 if lut.len() == 1 {
1327 let gamma = 1. / u8_fixed_8number_to_float(lut[0]);
1328 return Some(Box::new(ToneCurveEvaluatorPureGamma { gamma }));
1329 }
1330 None
1331 }
1332 ToneReprCurve::Parametric(params) => {
1333 if params.len() == 5 {
1334 let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045];
1335 let rec709_params = create_rec709_parametric();
1336
1337 let mut lc_params: [f32; 5] = [0.; 5];
1338 for (dst, src) in lc_params.iter_mut().zip(params.iter()) {
1339 *dst = *src;
1340 }
1341
1342 if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) {
1343 return Some(Box::new(ToneCurveCicpEvaluator {
1344 rgb_trc: TransferCharacteristics::Srgb.extended_gamma_tristimulus(),
1345 trc: TransferCharacteristics::Srgb.extended_gamma_single(),
1346 }));
1347 }
1348
1349 if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) {
1350 return Some(Box::new(ToneCurveCicpEvaluator {
1351 rgb_trc: TransferCharacteristics::Bt709.extended_gamma_tristimulus(),
1352 trc: TransferCharacteristics::Bt709.extended_gamma_single(),
1353 }));
1354 }
1355 }
1356
1357 let parametric_curve = ParametricCurve::new(params);
1358 if let Some(v) = parametric_curve?.invert() {
1359 return Some(Box::new(ToneCurveParametricEvaluator { parametric: v }));
1360 }
1361 None
1362 }
1363 }
1364 }
1365
1366 pub(crate) fn are_all_trc_the_same(&self) -> bool {
1368 if self.color_space == DataColorSpace::Gray {
1369 return true;
1370 }
1371 if let (Some(red_trc), Some(green_trc), Some(blue_trc)) =
1372 (&self.red_trc, &self.green_trc, &self.blue_trc)
1373 {
1374 if !matches!(
1375 (red_trc, green_trc, blue_trc),
1376 (
1377 ToneReprCurve::Lut(_),
1378 ToneReprCurve::Lut(_),
1379 ToneReprCurve::Lut(_),
1380 ) | (
1381 ToneReprCurve::Parametric(_),
1382 ToneReprCurve::Parametric(_),
1383 ToneReprCurve::Parametric(_)
1384 )
1385 ) {
1386 return false;
1387 }
1388 if let (ToneReprCurve::Lut(lut0), ToneReprCurve::Lut(lut1), ToneReprCurve::Lut(lut2)) =
1389 (red_trc, green_trc, blue_trc)
1390 {
1391 if lut0 == lut1 || lut1 == lut2 {
1392 return true;
1393 }
1394 }
1395 if let (
1396 ToneReprCurve::Parametric(lut0),
1397 ToneReprCurve::Parametric(lut1),
1398 ToneReprCurve::Parametric(lut2),
1399 ) = (red_trc, green_trc, blue_trc)
1400 {
1401 if lut0 == lut1 || lut1 == lut2 {
1402 return true;
1403 }
1404 }
1405 }
1406 false
1407 }
1408
1409 pub(crate) fn is_linear_matrix_shaper(&self) -> bool {
1411 if !self.is_matrix_shaper() {
1412 return false;
1413 }
1414 if !self.are_all_trc_the_same() {
1415 return false;
1416 }
1417 if let Some(red_trc) = &self.red_trc {
1418 return match red_trc {
1419 ToneReprCurve::Lut(lut) => {
1420 if lut.is_empty() {
1421 return true;
1422 }
1423 if is_curve_linear16(lut) {
1424 return true;
1425 }
1426 false
1427 }
1428 ToneReprCurve::Parametric(params) => {
1429 if let Some(curve) = ParametricCurve::new(params) {
1430 return curve.is_linear();
1431 }
1432 false
1433 }
1434 };
1435 }
1436 false
1437 }
1438
1439 pub(crate) fn try_extended_linearizing_evaluator(
1441 &self,
1442 ) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> {
1443 if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
1444 if tc.has_transfer_curve() {
1445 return Some(Box::new(ToneCurveCicpEvaluator {
1446 rgb_trc: tc.extended_linear_tristimulus(),
1447 trc: tc.extended_linear_single(),
1448 }));
1449 }
1450 }
1451 if !self.are_all_trc_the_same() {
1452 return None;
1453 }
1454 let reference_trc = if self.color_space == DataColorSpace::Gray {
1455 self.gray_trc.as_ref()
1456 } else {
1457 self.red_trc.as_ref()
1458 };
1459 if let Some(red_trc) = reference_trc {
1460 if let Some(value) = Self::make_linear_curve_evaluator_all_the_same(red_trc) {
1461 return value;
1462 }
1463 }
1464 None
1465 }
1466
1467 fn make_linear_curve_evaluator_all_the_same(
1468 evaluator_curve: &ToneReprCurve,
1469 ) -> Option<Option<Box<dyn ToneCurveEvaluator + Send + Sync>>> {
1470 match evaluator_curve {
1471 ToneReprCurve::Lut(lut) => {
1472 if lut.is_empty() {
1473 return Some(Some(Box::new(ToneCurveEvaluatorLinear {})));
1474 }
1475 if lut.len() == 1 {
1476 let gamma = u8_fixed_8number_to_float(lut[0]);
1477 return Some(Some(Box::new(ToneCurveEvaluatorPureGamma { gamma })));
1478 }
1479 }
1480 ToneReprCurve::Parametric(params) => {
1481 if params.len() == 5 {
1482 let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045];
1483 let rec709_params = create_rec709_parametric();
1484
1485 let mut lc_params: [f32; 5] = [0.; 5];
1486 for (dst, src) in lc_params.iter_mut().zip(params.iter()) {
1487 *dst = *src;
1488 }
1489
1490 if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) {
1491 return Some(Some(Box::new(ToneCurveCicpEvaluator {
1492 rgb_trc: TransferCharacteristics::Srgb.extended_linear_tristimulus(),
1493 trc: TransferCharacteristics::Srgb.extended_linear_single(),
1494 })));
1495 }
1496
1497 if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) {
1498 return Some(Some(Box::new(ToneCurveCicpEvaluator {
1499 rgb_trc: TransferCharacteristics::Bt709.extended_linear_tristimulus(),
1500 trc: TransferCharacteristics::Bt709.extended_linear_single(),
1501 })));
1502 }
1503 }
1504
1505 let parametric_curve = ParametricCurve::new(params);
1506 if let Some(v) = parametric_curve {
1507 return Some(Some(Box::new(ToneCurveParametricEvaluator {
1508 parametric: v,
1509 })));
1510 }
1511 }
1512 }
1513 None
1514 }
1515}
1516
1517pub(crate) struct ToneCurveCicpEvaluator {
1518 rgb_trc: fn(Rgb<f32>) -> Rgb<f32>,
1519 trc: fn(f32) -> f32,
1520}
1521
1522pub(crate) struct ToneCurveParametricEvaluator {
1523 parametric: ParametricCurve,
1524}
1525
1526pub(crate) struct ToneCurveEvaluatorPureGamma {
1527 gamma: f32,
1528}
1529
1530pub(crate) struct ToneCurveEvaluatorLinear {}
1531
1532impl ToneCurveEvaluator for ToneCurveCicpEvaluator {
1533 fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1534 (self.rgb_trc)(rgb)
1535 }
1536
1537 fn evaluate_value(&self, value: f32) -> f32 {
1538 (self.trc)(value)
1539 }
1540}
1541
1542impl ToneCurveEvaluator for ToneCurveParametricEvaluator {
1543 fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1544 Rgb::new(
1545 self.parametric.eval(rgb.r),
1546 self.parametric.eval(rgb.g),
1547 self.parametric.eval(rgb.b),
1548 )
1549 }
1550
1551 fn evaluate_value(&self, value: f32) -> f32 {
1552 self.parametric.eval(value)
1553 }
1554}
1555
1556impl ToneCurveEvaluator for ToneCurveEvaluatorPureGamma {
1557 fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1558 Rgb::new(
1559 dirty_powf(rgb.r, self.gamma),
1560 dirty_powf(rgb.g, self.gamma),
1561 dirty_powf(rgb.b, self.gamma),
1562 )
1563 }
1564
1565 fn evaluate_value(&self, value: f32) -> f32 {
1566 dirty_powf(value, self.gamma)
1567 }
1568}
1569
1570impl ToneCurveEvaluator for ToneCurveEvaluatorLinear {
1571 fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
1572 rgb
1573 }
1574
1575 fn evaluate_value(&self, value: f32) -> f32 {
1576 value
1577 }
1578}
1579
1580pub trait ToneCurveEvaluator {
1581 fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32>;
1582 fn evaluate_value(&self, value: f32) -> f32;
1583}