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