1use 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 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 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 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 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
217pub 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 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 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 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}