1use alloc::sync::Arc;
2use alloc::vec::Vec;
3use core::time::Duration;
4#[cfg(not(feature = "std"))]
5use num_traits::float::FloatCore as _;
6
7struct EpochNow;
10
11impl moxcms::Now for EpochNow {
12 fn now() -> Duration {
13 Duration::from_secs(0)
14 }
15}
16
17use crate::{
20 color::FromPrimitive,
21 error::{ParameterError, ParameterErrorKind},
22 math::multiply_accumulate,
23 traits::{
24 private::{LayoutWithColor, SealedPixelWithColorType},
25 PixelWithColorType,
26 },
27 utils::vec_try_with_capacity,
28 DynamicImage, ImageError, Pixel, Primitive,
29};
30
31#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
33pub struct Cicp {
34 pub primaries: CicpColorPrimaries,
36 pub transfer: CicpTransferCharacteristics,
38 pub matrix: CicpMatrixCoefficients,
42 pub full_range: CicpVideoFullRangeFlag,
48}
49
50#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
52pub(crate) struct CicpRgb {
53 pub(crate) primaries: CicpColorPrimaries,
54 pub(crate) transfer: CicpTransferCharacteristics,
55 pub(crate) luminance: DerivedLuminance,
56}
57
58#[repr(u8)]
65#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
66#[non_exhaustive]
67pub enum CicpColorPrimaries {
68 SRgb = 1,
70 Unspecified = 2,
72 RgbM = 4,
74 RgbB = 5,
76 Bt601 = 6,
79 Rgb240m = 7,
82 GenericFilm = 8,
84 Rgb2020 = 9,
87 Xyz = 10,
91 SmpteRp431 = 11,
93 SmpteRp432 = 12,
95 Industry22 = 22,
105}
106
107impl CicpColorPrimaries {
108 fn to_moxcms(self) -> moxcms::CicpColorPrimaries {
109 use moxcms::CicpColorPrimaries as M;
110
111 match self {
112 CicpColorPrimaries::SRgb => M::Bt709,
113 CicpColorPrimaries::Unspecified => M::Unspecified,
114 CicpColorPrimaries::RgbM => M::Bt470M,
115 CicpColorPrimaries::RgbB => M::Bt470Bg,
116 CicpColorPrimaries::Bt601 => M::Bt601,
117 CicpColorPrimaries::Rgb240m => M::Smpte240,
118 CicpColorPrimaries::GenericFilm => M::GenericFilm,
119 CicpColorPrimaries::Rgb2020 => M::Bt2020,
120 CicpColorPrimaries::Xyz => M::Xyz,
121 CicpColorPrimaries::SmpteRp431 => M::Smpte431,
122 CicpColorPrimaries::SmpteRp432 => M::Smpte432,
123 CicpColorPrimaries::Industry22 => M::Ebu3213,
124 }
125 }
126}
127
128#[repr(u8)]
133#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
134#[non_exhaustive]
135pub enum CicpTransferCharacteristics {
136 Bt709 = 1,
140 Unspecified = 2,
142 Bt470M = 4,
149 Bt470BG = 5,
151 Bt601 = 6,
156 Smpte240m = 7,
158 Linear = 8,
160 Log100 = 9,
162 LogSqrt = 10,
164 Iec61966_2_4 = 11,
166 Bt1361 = 12,
168 SRgb = 13,
171 Bt2020_10bit = 14,
174 Bt2020_12bit = 15,
177 Smpte2084 = 16,
180 Smpte428 = 17,
182 Bt2100Hlg = 18,
185}
186
187impl CicpTransferCharacteristics {
188 fn to_moxcms(self) -> moxcms::TransferCharacteristics {
189 use moxcms::TransferCharacteristics as T;
190
191 match self {
192 CicpTransferCharacteristics::Bt709 => T::Bt709,
193 CicpTransferCharacteristics::Unspecified => T::Unspecified,
194 CicpTransferCharacteristics::Bt470M => T::Bt470M,
195 CicpTransferCharacteristics::Bt470BG => T::Bt470Bg,
196 CicpTransferCharacteristics::Bt601 => T::Bt601,
197 CicpTransferCharacteristics::Smpte240m => T::Smpte240,
198 CicpTransferCharacteristics::Linear => T::Linear,
199 CicpTransferCharacteristics::Log100 => T::Log100,
200 CicpTransferCharacteristics::LogSqrt => T::Log100sqrt10,
201 CicpTransferCharacteristics::Iec61966_2_4 => T::Iec61966,
202 CicpTransferCharacteristics::Bt1361 => T::Bt1361,
203 CicpTransferCharacteristics::SRgb => T::Srgb,
204 CicpTransferCharacteristics::Bt2020_10bit => T::Bt202010bit,
205 CicpTransferCharacteristics::Bt2020_12bit => T::Bt202012bit,
206 CicpTransferCharacteristics::Smpte2084 => T::Smpte2084,
207 CicpTransferCharacteristics::Smpte428 => T::Smpte428,
208 CicpTransferCharacteristics::Bt2100Hlg => T::Hlg,
209 }
210 }
211}
212
213#[repr(u8)]
216#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
217#[non_exhaustive]
218pub enum CicpMatrixCoefficients {
219 Identity = 0,
224 Bt709 = 1,
229 Unspecified = 2,
231 UsFCC = 4,
233 Bt470BG = 5,
241 Smpte170m = 6,
243 Smpte240m = 7,
245 YCgCo = 8,
247 Bt2020NonConstant = 9,
250 Bt2020Constant = 10,
252 Smpte2085 = 11,
254 ChromaticityDerivedNonConstant = 12,
256 ChromaticityDerivedConstant = 13,
258 Bt2100 = 14,
260 IptPqC2 = 15,
262 YCgCoRe = 16,
264 YCgCoRo = 17,
266}
267
268impl CicpMatrixCoefficients {
269 fn to_moxcms(self) -> Option<moxcms::MatrixCoefficients> {
270 use moxcms::MatrixCoefficients as M;
271
272 Some(match self {
273 CicpMatrixCoefficients::Identity => M::Identity,
274 CicpMatrixCoefficients::Unspecified => M::Unspecified,
275 CicpMatrixCoefficients::Bt709 => M::Bt709,
276 CicpMatrixCoefficients::UsFCC => M::Fcc,
277 CicpMatrixCoefficients::Bt470BG => M::Bt470Bg,
278 CicpMatrixCoefficients::Smpte170m => M::Smpte170m,
279 CicpMatrixCoefficients::Smpte240m => M::Smpte240m,
280 CicpMatrixCoefficients::YCgCo => M::YCgCo,
281 CicpMatrixCoefficients::Bt2020NonConstant => M::Bt2020Ncl,
282 CicpMatrixCoefficients::Bt2020Constant => M::Bt2020Cl,
283 CicpMatrixCoefficients::Smpte2085 => M::Smpte2085,
284 CicpMatrixCoefficients::ChromaticityDerivedNonConstant => M::ChromaticityDerivedNCL,
285 CicpMatrixCoefficients::ChromaticityDerivedConstant => M::ChromaticityDerivedCL,
286 CicpMatrixCoefficients::Bt2100 => M::ICtCp,
287 CicpMatrixCoefficients::IptPqC2
288 | CicpMatrixCoefficients::YCgCoRe
289 | CicpMatrixCoefficients::YCgCoRo => return None,
290 })
291 }
292}
293
294#[repr(u8)]
296#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
297#[non_exhaustive]
298pub enum CicpVideoFullRangeFlag {
299 NarrowRange = 0,
303 FullRange = 1,
305}
306
307#[repr(u8)]
308#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
309pub(crate) enum DerivedLuminance {
310 #[allow(dead_code)] Constant,
315 NonConstant,
318}
319
320#[derive(Clone)]
326pub struct CicpTransform {
327 from: Cicp,
328 into: Cicp,
329 u8: RgbTransforms<u8>,
330 u16: RgbTransforms<u16>,
331 f32: RgbTransforms<f32>,
332 output_coefs: [f32; 3],
334}
335
336pub(crate) type CicpApplicable<'lt, C> = dyn Fn(&[C], &mut [C]) + Send + Sync + 'lt;
337
338#[derive(Clone)]
339struct RgbTransforms<C> {
340 slices: [Arc<CicpApplicable<'static, C>>; 4],
341 luma_rgb: [Arc<CicpApplicable<'static, C>>; 4],
342 rgb_luma: [Arc<CicpApplicable<'static, C>>; 4],
343 luma_luma: [Arc<CicpApplicable<'static, C>>; 4],
344}
345
346impl CicpTransform {
347 pub fn new(from: Cicp, into: Cicp) -> Option<Self> {
360 if !from.qualify_stability() || !into.qualify_stability() {
361 return None;
365 }
366
367 let _input_coefs = from.into_rgb().derived_luminance()?;
371 let output_coefs = into.into_rgb().derived_luminance()?;
372
373 let mox_from = from.to_moxcms_compute_profile()?;
374 let mox_into = into.to_moxcms_compute_profile()?;
375
376 let opt = moxcms::TransformOptions::default();
377
378 let f32_fallback = {
379 let try_f32 = Self::LAYOUTS.map(|(from_layout, into_layout)| {
380 let (from, from_layout) = mox_from.map_layout(from_layout);
381 let (into, into_layout) = mox_into.map_layout(into_layout);
382
383 from.create_transform_f32(from_layout, into, into_layout, opt)
384 .ok()
385 });
386
387 if try_f32.iter().any(Option::is_none) {
388 return None;
389 }
390
391 try_f32.map(Option::unwrap)
392 };
393
394 Some(CicpTransform {
396 from,
397 into,
398 u8: Self::build_transforms(
399 Self::LAYOUTS.map(|(from_layout, into_layout)| {
400 let (from, from_layout) = mox_from.map_layout(from_layout);
401 let (into, into_layout) = mox_into.map_layout(into_layout);
402
403 from.create_transform_8bit(from_layout, into, into_layout, opt)
404 .ok()
405 }),
406 f32_fallback.clone(),
407 output_coefs,
408 )?,
409 u16: Self::build_transforms(
410 Self::LAYOUTS.map(|(from_layout, into_layout)| {
411 let (from, from_layout) = mox_from.map_layout(from_layout);
412 let (into, into_layout) = mox_into.map_layout(into_layout);
413
414 from.create_transform_16bit(from_layout, into, into_layout, opt)
415 .ok()
416 }),
417 f32_fallback.clone(),
418 output_coefs,
419 )?,
420 f32: Self::build_transforms(
421 f32_fallback.clone().map(Some),
422 f32_fallback.clone(),
423 output_coefs,
424 )?,
425 output_coefs,
426 })
427 }
428
429 pub(crate) fn supported_transform_fn<From: PixelWithColorType, Into: PixelWithColorType>(
438 &self,
439 ) -> &'_ CicpApplicable<'_, From::Subpixel> {
440 use crate::traits::private::double_dispatch_transform_from_sealed;
441 double_dispatch_transform_from_sealed::<From, Into>(self)
442 }
443
444 pub(crate) fn check_applicable(&self, from: Cicp, into: Cicp) -> Result<(), ImageError> {
446 let check_expectation = |expected, found| {
447 if expected == found {
448 Ok(())
449 } else {
450 Err(ParameterError::from_kind(
451 ParameterErrorKind::CicpMismatch { expected, found },
452 ))
453 }
454 };
455
456 check_expectation(self.from, from).map_err(ImageError::Parameter)?;
457 check_expectation(self.into, into).map_err(ImageError::Parameter)?;
458
459 Ok(())
460 }
461
462 fn build_transforms<P: ColorComponentForCicp + Default + 'static>(
463 trs: [Option<Arc<dyn moxcms::TransformExecutor<P> + Send + Sync>>; 4],
464 f32: [Arc<dyn moxcms::TransformExecutor<f32> + Send + Sync>; 4],
465 output_coef: [f32; 3],
466 ) -> Option<RgbTransforms<P>> {
467 if trs.iter().any(Option::is_none) {
469 return None;
470 }
471
472 let trs = trs.map(Option::unwrap);
473
474 let slices = trs.clone().map(|tr| {
476 Arc::new(move |input: &[P], output: &mut [P]| {
477 tr.transform(input, output).expect("transform failed")
478 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>
479 });
480
481 const N: usize = 256;
482
483 let luma_rgb = {
485 let [tr33, tr34, tr43, tr44] = f32.clone();
486
487 [
488 Arc::new(move |input: &[P], output: &mut [P]| {
489 let mut ibuffer = [0.0f32; 3 * N];
490 let mut obuffer = [0.0f32; 3 * N];
491
492 for (luma, output) in input.chunks(N).zip(output.chunks_mut(3 * N)) {
493 let n = luma.len();
494 let ibuffer = &mut ibuffer[..3 * n];
495 let obuffer = &mut obuffer[..3 * n];
496 Self::expand_luma_rgb(luma, ibuffer);
497 tr33.transform(ibuffer, obuffer).expect("transform failed");
498 Self::clamp_rgb(obuffer, output);
499 }
500 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
501 Arc::new(move |input: &[P], output: &mut [P]| {
502 let mut ibuffer = [0.0f32; 3 * N];
503 let mut obuffer = [0.0f32; 4 * N];
504
505 for (luma, output) in input.chunks(N).zip(output.chunks_mut(4 * N)) {
506 let n = luma.len();
507 let ibuffer = &mut ibuffer[..3 * n];
508 let obuffer = &mut obuffer[..4 * n];
509 Self::expand_luma_rgb(luma, ibuffer);
510 tr34.transform(ibuffer, obuffer).expect("transform failed");
511 Self::clamp_rgba(obuffer, output);
512 }
513 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
514 Arc::new(move |input: &[P], output: &mut [P]| {
515 let mut ibuffer = [0.0f32; 4 * N];
516 let mut obuffer = [0.0f32; 3 * N];
517
518 for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(3 * N)) {
519 let n = luma.len() / 2;
520 let ibuffer = &mut ibuffer[..4 * n];
521 let obuffer = &mut obuffer[..3 * n];
522 Self::expand_luma_rgba(luma, ibuffer);
523 tr43.transform(ibuffer, obuffer).expect("transform failed");
524 Self::clamp_rgb(obuffer, output);
525 }
526 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
527 Arc::new(move |input: &[P], output: &mut [P]| {
528 let mut ibuffer = [0.0f32; 4 * N];
529 let mut obuffer = [0.0f32; 4 * N];
530
531 for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(4 * N)) {
532 let n = luma.len() / 2;
533 let ibuffer = &mut ibuffer[..4 * n];
534 let obuffer = &mut obuffer[..4 * n];
535 Self::expand_luma_rgba(luma, ibuffer);
536 tr44.transform(ibuffer, obuffer).expect("transform failed");
537 Self::clamp_rgba(obuffer, output);
538 }
539 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
540 ]
541 };
542
543 let rgb_luma = {
545 let [tr33, tr34, tr43, tr44] = f32.clone();
546
547 [
548 Arc::new(move |input: &[P], output: &mut [P]| {
549 debug_assert_eq!(input.len() / 3, output.len());
550
551 let mut ibuffer = [0.0f32; 3 * N];
552 let mut obuffer = [0.0f32; 3 * N];
553
554 for (rgb, output) in input.chunks(3 * N).zip(output.chunks_mut(N)) {
555 let n = output.len();
556 let ibuffer = &mut ibuffer[..3 * n];
557 let obuffer = &mut obuffer[..3 * n];
558 Self::expand_rgb(rgb, ibuffer);
559 tr33.transform(ibuffer, obuffer).expect("transform failed");
560 Self::clamp_rgb_luma(obuffer, output, output_coef);
561 }
562 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
563 Arc::new(move |input: &[P], output: &mut [P]| {
564 debug_assert_eq!(input.len() / 3, output.len() / 2);
565
566 let mut ibuffer = [0.0f32; 3 * N];
567 let mut obuffer = [0.0f32; 4 * N];
568
569 for (rgb, output) in input.chunks(4 * N).zip(output.chunks_mut(2 * N)) {
570 let n = output.len() / 2;
571 let ibuffer = &mut ibuffer[..3 * n];
572 let obuffer = &mut obuffer[..4 * n];
573 Self::expand_rgb(rgb, ibuffer);
574 tr34.transform(ibuffer, obuffer).expect("transform failed");
575 Self::clamp_rgba_luma(obuffer, output, output_coef);
576 }
577 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
578 Arc::new(move |input: &[P], output: &mut [P]| {
579 debug_assert_eq!(input.len() / 4, output.len());
580
581 let mut ibuffer = [0.0f32; 4 * N];
582 let mut obuffer = [0.0f32; 3 * N];
583
584 for (rgba, output) in input.chunks(4 * N).zip(output.chunks_mut(N)) {
585 let n = output.len();
586 let ibuffer = &mut ibuffer[..4 * n];
587 let obuffer = &mut obuffer[..3 * n];
588 Self::expand_rgba(rgba, ibuffer);
589 tr43.transform(ibuffer, obuffer).expect("transform failed");
590 Self::clamp_rgb_luma(obuffer, output, output_coef);
591 }
592 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
593 Arc::new(move |input: &[P], output: &mut [P]| {
594 debug_assert_eq!(input.len() / 4, output.len() / 2);
595
596 let mut ibuffer = [0.0f32; 4 * N];
597 let mut obuffer = [0.0f32; 4 * N];
598
599 for (rgba, output) in input.chunks(4 * N).zip(output.chunks_mut(2 * N)) {
600 let n = output.len() / 2;
601 let ibuffer = &mut ibuffer[..4 * n];
602 let obuffer = &mut obuffer[..4 * n];
603 Self::expand_rgba(rgba, ibuffer);
604 tr44.transform(ibuffer, obuffer).expect("transform failed");
605 Self::clamp_rgba_luma(obuffer, output, output_coef);
606 }
607 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
608 ]
609 };
610
611 let luma_luma = {
613 let [tr33, tr34, tr43, tr44] = f32.clone();
614
615 [
616 Arc::new(move |input: &[P], output: &mut [P]| {
617 debug_assert_eq!(input.len(), output.len());
618 let mut ibuffer = [0.0f32; 3 * N];
619 let mut obuffer = [0.0f32; 3 * N];
620
621 for (luma, output) in input.chunks(N).zip(output.chunks_mut(N)) {
622 let n = luma.len();
623 let ibuffer = &mut ibuffer[..3 * n];
624 let obuffer = &mut obuffer[..3 * n];
625 Self::expand_luma_rgb(luma, ibuffer);
626 tr33.transform(ibuffer, obuffer).expect("transform failed");
627 Self::clamp_rgb_luma(obuffer, output, output_coef);
628 }
629 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
630 Arc::new(move |input: &[P], output: &mut [P]| {
631 debug_assert_eq!(input.len(), output.len() / 2);
632 let mut ibuffer = [0.0f32; 3 * N];
633 let mut obuffer = [0.0f32; 4 * N];
634
635 for (luma, output) in input.chunks(N).zip(output.chunks_mut(2 * N)) {
636 let n = luma.len();
637 let ibuffer = &mut ibuffer[..3 * n];
638 let obuffer = &mut obuffer[..4 * n];
639 Self::expand_luma_rgb(luma, ibuffer);
640 tr34.transform(ibuffer, obuffer).expect("transform failed");
641 Self::clamp_rgba_luma(obuffer, output, output_coef);
642 }
643 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
644 Arc::new(move |input: &[P], output: &mut [P]| {
645 debug_assert_eq!(input.len() / 2, output.len());
646 let mut ibuffer = [0.0f32; 4 * N];
647 let mut obuffer = [0.0f32; 3 * N];
648
649 for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(N)) {
650 let n = luma.len() / 2;
651 let ibuffer = &mut ibuffer[..4 * n];
652 let obuffer = &mut obuffer[..3 * n];
653 Self::expand_luma_rgba(luma, ibuffer);
654 tr43.transform(ibuffer, obuffer).expect("transform failed");
655 Self::clamp_rgb_luma(obuffer, output, output_coef);
656 }
657 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
658 Arc::new(move |input: &[P], output: &mut [P]| {
659 debug_assert_eq!(input.len() / 2, output.len() / 2);
660 let mut ibuffer = [0.0f32; 4 * N];
661 let mut obuffer = [0.0f32; 4 * N];
662
663 for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(2 * N)) {
664 let n = luma.len() / 2;
665 let ibuffer = &mut ibuffer[..4 * n];
666 let obuffer = &mut obuffer[..4 * n];
667 Self::expand_luma_rgba(luma, ibuffer);
668 tr44.transform(ibuffer, obuffer).expect("transform failed");
669 Self::clamp_rgba_luma(obuffer, output, output_coef);
670 }
671 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
672 ]
673 };
674
675 Some(RgbTransforms {
676 slices,
677 luma_rgb,
678 rgb_luma,
679 luma_luma,
680 })
681 }
682
683 pub(crate) fn transform_dynamic(&self, lhs: &mut DynamicImage, rhs: &DynamicImage) {
684 const STEP: usize = 256;
685
686 let mut ibuffer = [0.0f32; 4 * STEP];
687 let mut obuffer = [0.0f32; 4 * STEP];
688
689 let pixels = (u64::from(lhs.width()) * u64::from(lhs.height())) as usize;
690
691 let input_samples;
692 let output_samples;
693
694 let inner_transform = match (
695 LayoutWithColor::from(lhs.color()),
696 LayoutWithColor::from(rhs.color()),
697 ) {
698 (
699 LayoutWithColor::Luma | LayoutWithColor::Rgb,
700 LayoutWithColor::Luma | LayoutWithColor::Rgb,
701 ) => {
702 output_samples = 3;
703 input_samples = 3;
704 &*self.f32.slices[0]
705 }
706 (
707 LayoutWithColor::LumaAlpha | LayoutWithColor::Rgba,
708 LayoutWithColor::Luma | LayoutWithColor::Rgb,
709 ) => {
710 output_samples = 4;
711 input_samples = 3;
712 &*self.f32.slices[1]
713 }
714 (
715 LayoutWithColor::Luma | LayoutWithColor::Rgb,
716 LayoutWithColor::LumaAlpha | LayoutWithColor::Rgba,
717 ) => {
718 output_samples = 3;
719 input_samples = 4;
720 &*self.f32.slices[2]
721 }
722 (
723 LayoutWithColor::LumaAlpha | LayoutWithColor::Rgba,
724 LayoutWithColor::LumaAlpha | LayoutWithColor::Rgba,
725 ) => {
726 output_samples = 4;
727 input_samples = 4;
728 &*self.f32.slices[3]
729 }
730 };
731
732 for start_idx in (0..pixels).step_by(STEP) {
733 let end_idx = (start_idx + STEP).min(pixels);
734 let count = end_idx - start_idx;
735
736 match rhs {
739 DynamicImage::ImageLuma8(buf) => {
740 CicpTransform::expand_luma_rgb(
741 &buf.inner_pixels()[start_idx..end_idx],
742 &mut ibuffer[..3 * count],
743 );
744 }
745 DynamicImage::ImageLumaA8(buf) => {
746 CicpTransform::expand_luma_rgba(
747 &buf.inner_pixels()[2 * start_idx..2 * end_idx],
748 &mut ibuffer[..4 * count],
749 );
750 }
751 DynamicImage::ImageRgb8(buf) => {
752 CicpTransform::expand_rgb(
753 &buf.inner_pixels()[3 * start_idx..3 * end_idx],
754 &mut ibuffer[..3 * count],
755 );
756 }
757 DynamicImage::ImageRgba8(buf) => {
758 CicpTransform::expand_rgba(
759 &buf.inner_pixels()[4 * start_idx..4 * end_idx],
760 &mut ibuffer[..4 * count],
761 );
762 }
763 DynamicImage::ImageLuma16(buf) => {
764 CicpTransform::expand_luma_rgb(
765 &buf.inner_pixels()[start_idx..end_idx],
766 &mut ibuffer[..3 * count],
767 );
768 }
769 DynamicImage::ImageLumaA16(buf) => {
770 CicpTransform::expand_luma_rgba(
771 &buf.inner_pixels()[2 * start_idx..2 * end_idx],
772 &mut ibuffer[..4 * count],
773 );
774 }
775 DynamicImage::ImageRgb16(buf) => {
776 CicpTransform::expand_rgb(
777 &buf.inner_pixels()[3 * start_idx..3 * end_idx],
778 &mut ibuffer[..3 * count],
779 );
780 }
781
782 DynamicImage::ImageRgba16(buf) => {
783 CicpTransform::expand_rgba(
784 &buf.inner_pixels()[4 * start_idx..4 * end_idx],
785 &mut ibuffer[..4 * count],
786 );
787 }
788 DynamicImage::ImageRgb32F(buf) => {
789 CicpTransform::expand_rgb(
790 &buf.inner_pixels()[3 * start_idx..3 * end_idx],
791 &mut ibuffer[..3 * count],
792 );
793 }
794 DynamicImage::ImageRgba32F(buf) => {
795 CicpTransform::expand_rgba(
796 &buf.inner_pixels()[4 * start_idx..4 * end_idx],
797 &mut ibuffer[..4 * count],
798 );
799 }
800 }
801
802 let islice = &ibuffer[..input_samples * count];
803 let oslice = &mut obuffer[..output_samples * count];
804
805 inner_transform(islice, oslice);
806
807 match lhs {
808 DynamicImage::ImageLuma8(buf) => {
809 CicpTransform::clamp_rgb_luma(
810 &obuffer[..3 * count],
811 &mut buf.inner_pixels_mut()[start_idx..end_idx],
812 self.output_coefs,
813 );
814 }
815 DynamicImage::ImageLumaA8(buf) => {
816 CicpTransform::clamp_rgba_luma(
817 &obuffer[..4 * count],
818 &mut buf.inner_pixels_mut()[2 * start_idx..2 * end_idx],
819 self.output_coefs,
820 );
821 }
822 DynamicImage::ImageRgb8(buf) => {
823 CicpTransform::clamp_rgb(
824 &obuffer[..3 * count],
825 &mut buf.inner_pixels_mut()[3 * start_idx..3 * end_idx],
826 );
827 }
828 DynamicImage::ImageRgba8(buf) => {
829 CicpTransform::clamp_rgba(
830 &obuffer[..4 * count],
831 &mut buf.inner_pixels_mut()[4 * start_idx..4 * end_idx],
832 );
833 }
834 DynamicImage::ImageLuma16(buf) => {
835 CicpTransform::clamp_rgb_luma(
836 &obuffer[..3 * count],
837 &mut buf.inner_pixels_mut()[start_idx..end_idx],
838 self.output_coefs,
839 );
840 }
841 DynamicImage::ImageLumaA16(buf) => {
842 CicpTransform::clamp_rgba_luma(
843 &obuffer[..4 * count],
844 &mut buf.inner_pixels_mut()[2 * start_idx..2 * end_idx],
845 self.output_coefs,
846 );
847 }
848 DynamicImage::ImageRgb16(buf) => {
849 CicpTransform::clamp_rgba(
850 &obuffer[..3 * count],
851 &mut buf.inner_pixels_mut()[3 * start_idx..3 * end_idx],
852 );
853 }
854
855 DynamicImage::ImageRgba16(buf) => {
856 CicpTransform::clamp_rgba(
857 &obuffer[..4 * count],
858 &mut buf.inner_pixels_mut()[4 * start_idx..4 * end_idx],
859 );
860 }
861 DynamicImage::ImageRgb32F(buf) => {
862 CicpTransform::clamp_rgb(
863 &obuffer[..3 * count],
864 &mut buf.inner_pixels_mut()[3 * start_idx..3 * end_idx],
865 );
866 }
867 DynamicImage::ImageRgba32F(buf) => {
868 CicpTransform::clamp_rgba(
869 &obuffer[..4 * count],
870 &mut buf.inner_pixels_mut()[4 * start_idx..4 * end_idx],
871 );
872 }
873 }
874 }
875 }
876
877 pub(crate) fn select_transform_u8<P: SealedPixelWithColorType<TransformableSubpixel = u8>>(
884 &self,
885 into: LayoutWithColor,
886 ) -> &Arc<CicpApplicable<'static, u8>> {
887 self.u8.select_transform::<P>(into)
888 }
889
890 pub(crate) fn select_transform_u16<O: SealedPixelWithColorType<TransformableSubpixel = u16>>(
891 &self,
892 into: LayoutWithColor,
893 ) -> &Arc<CicpApplicable<'static, u16>> {
894 self.u16.select_transform::<O>(into)
895 }
896
897 pub(crate) fn select_transform_f32<O: SealedPixelWithColorType<TransformableSubpixel = f32>>(
898 &self,
899 into: LayoutWithColor,
900 ) -> &Arc<CicpApplicable<'static, f32>> {
901 self.f32.select_transform::<O>(into)
902 }
903
904 const LAYOUTS: [(LayoutWithColor, LayoutWithColor); 4] = [
905 (LayoutWithColor::Rgb, LayoutWithColor::Rgb),
906 (LayoutWithColor::Rgb, LayoutWithColor::Rgba),
907 (LayoutWithColor::Rgba, LayoutWithColor::Rgb),
908 (LayoutWithColor::Rgba, LayoutWithColor::Rgba),
909 ];
910
911 pub(crate) fn expand_luma_rgb<P: ColorComponentForCicp>(luma: &[P], rgb: &mut [f32]) {
912 for (&pix, rgb) in luma.iter().zip(rgb.as_chunks_mut::<3>().0.iter_mut()) {
913 let luma = pix.expand_to_f32();
914 rgb[0] = luma;
915 rgb[1] = luma;
916 rgb[2] = luma;
917 }
918 }
919
920 pub(crate) fn expand_luma_rgba<P: ColorComponentForCicp>(luma: &[P], rgb: &mut [f32]) {
921 let luma_chunks = luma.as_chunks::<2>().0.iter();
922 let rgb_chunks = rgb.as_chunks_mut::<4>().0.iter_mut();
923 for (pix, rgb) in luma_chunks.zip(rgb_chunks) {
924 let luma = pix[0].expand_to_f32();
925 rgb[0] = luma;
926 rgb[1] = luma;
927 rgb[2] = luma;
928 rgb[3] = pix[1].expand_to_f32();
929 }
930 }
931
932 pub(crate) fn expand_rgb<P: ColorComponentForCicp>(input: &[P], output: &mut [f32]) {
933 for (&component, val) in input.iter().zip(output) {
934 *val = component.expand_to_f32();
935 }
936 }
937
938 pub(crate) fn expand_rgba<P: ColorComponentForCicp>(input: &[P], output: &mut [f32]) {
939 for (&component, val) in input.iter().zip(output) {
940 *val = component.expand_to_f32();
941 }
942 }
943
944 pub(crate) fn clamp_rgb<P: ColorComponentForCicp>(input: &[f32], output: &mut [P]) {
945 for (&component, val) in input.iter().zip(output) {
947 *val = P::clamp_from_f32(component);
948 }
949 }
950
951 pub(crate) fn clamp_rgba<P: ColorComponentForCicp>(input: &[f32], output: &mut [P]) {
952 for (&component, val) in input.iter().zip(output) {
953 *val = P::clamp_from_f32(component);
954 }
955 }
956
957 pub(crate) fn clamp_rgb_luma<P: ColorComponentForCicp>(
958 input: &[f32],
959 output: &mut [P],
960 coef: [f32; 3],
961 ) {
962 for (rgb, pix) in input.as_chunks::<3>().0.iter().zip(output) {
963 let mut luma = 0.0;
964
965 for (&component, coef) in rgb.iter().zip(coef) {
966 luma = multiply_accumulate(luma, component, coef);
967 }
968
969 *pix = P::clamp_from_f32(luma);
970 }
971 }
972
973 pub(crate) fn clamp_rgba_luma<P: ColorComponentForCicp>(
974 input: &[f32],
975 output: &mut [P],
976 coef: [f32; 3],
977 ) {
978 let input_chunks = input.as_chunks::<4>().0.iter();
979 let output_chunks = output.as_chunks_mut::<2>().0.iter_mut();
980 for (rgba, pix) in input_chunks.zip(output_chunks) {
981 let mut luma = 0.0;
982
983 for (&component, coef) in rgba[..3].iter().zip(coef) {
984 luma = multiply_accumulate(luma, component, coef);
985 }
986
987 pix[0] = P::clamp_from_f32(luma);
988 pix[1] = P::clamp_from_f32(rgba[3]);
989 }
990 }
991}
992
993impl CicpRgb {
994 pub(crate) fn cast_pixels<FromColor, IntoColor>(
998 &self,
999 buffer: &[FromColor::Subpixel],
1000 color_space_fallback: &dyn Fn() -> [f32; 3],
1003 ) -> Vec<IntoColor::Subpixel>
1004 where
1005 FromColor: Pixel + SealedPixelWithColorType<TransformableSubpixel = FromColor::Subpixel>,
1006 IntoColor: Pixel,
1007 IntoColor: CicpPixelCast<FromColor>,
1008 FromColor::Subpixel: ColorComponentForCicp,
1009 IntoColor::Subpixel: ColorComponentForCicp + FromPrimitive<FromColor::Subpixel>,
1010 {
1011 use crate::traits::private::PrivateToken;
1012 let from_layout = <FromColor as SealedPixelWithColorType>::layout(PrivateToken);
1013 let into_layout = <IntoColor as SealedPixelWithColorType>::layout(PrivateToken);
1014 self.cast_pixels_by_layout(buffer, color_space_fallback, from_layout, into_layout)
1019 }
1020
1021 fn cast_pixels_by_layout<FromSubpixel, IntoSubpixel>(
1022 &self,
1023 buffer: &[FromSubpixel],
1024 color_space_fallback: &dyn Fn() -> [f32; 3],
1027 from_layout: LayoutWithColor,
1028 into_layout: LayoutWithColor,
1029 ) -> Vec<IntoSubpixel>
1030 where
1031 FromSubpixel: ColorComponentForCicp + Primitive,
1032 IntoSubpixel: ColorComponentForCicp + FromPrimitive<FromSubpixel> + Primitive,
1033 {
1034 let mut output = match self.cast_pixels_from_subpixels(buffer, from_layout, into_layout) {
1035 Ok(ok) => return ok,
1036 Err(buffer) => buffer,
1037 };
1038
1039 let color_space_coefs = self
1041 .derived_luminance()
1042 .unwrap_or_else(color_space_fallback);
1046
1047 let pixels = buffer.len() / from_layout.channels();
1048
1049 Self::create_output::<IntoSubpixel>(&mut output, pixels, into_layout);
1053
1054 Self::cast_pixels_by_fallback(
1055 buffer,
1056 output.as_mut_slice(),
1057 from_layout,
1058 into_layout,
1059 color_space_coefs,
1060 );
1061
1062 output
1063 }
1064
1065 fn create_output<Into: Primitive>(
1066 output: &mut Vec<Into>,
1067 pixels: usize,
1068 into_layout: LayoutWithColor,
1069 ) {
1070 output.resize(pixels * into_layout.channels(), Into::DEFAULT_MIN_VALUE);
1071 }
1072
1073 fn cast_pixels_by_fallback<
1074 From: Primitive + ColorComponentForCicp,
1075 Into: ColorComponentForCicp,
1076 >(
1077 buffer: &[From],
1078 output: &mut [Into],
1079 from_layout: LayoutWithColor,
1080 into_layout: LayoutWithColor,
1081 color_space_coefs: [f32; 3],
1082 ) {
1083 use LayoutWithColor as Layout;
1084
1085 const STEP: usize = 256;
1086 let pixels = buffer.len() / from_layout.channels();
1087
1088 let mut ibuffer = [0.0f32; 4 * STEP];
1089 let mut obuffer = [0.0f32; 4 * STEP];
1090
1091 let ibuf_step = match from_layout {
1092 Layout::Rgb | Layout::Luma => 3,
1093 Layout::Rgba | Layout::LumaAlpha => 4,
1094 };
1095
1096 let obuf_step = match into_layout {
1097 Layout::Rgb | Layout::Luma => 3,
1098 Layout::Rgba | Layout::LumaAlpha => 4,
1099 };
1100
1101 for start_idx in (0..pixels).step_by(STEP) {
1102 let end_idx = (start_idx + STEP).min(pixels);
1103 let count = end_idx - start_idx;
1104
1105 let ibuffer = &mut ibuffer[..ibuf_step * count];
1106
1107 match from_layout {
1108 Layout::Rgb => {
1109 CicpTransform::expand_rgb(&buffer[3 * start_idx..3 * end_idx], ibuffer)
1110 }
1111 Layout::Rgba => {
1112 CicpTransform::expand_rgba(&buffer[4 * start_idx..4 * end_idx], ibuffer)
1113 }
1114 Layout::Luma => {
1115 CicpTransform::expand_luma_rgb(&buffer[start_idx..end_idx], ibuffer)
1116 }
1117 Layout::LumaAlpha => {
1118 CicpTransform::expand_luma_rgba(&buffer[2 * start_idx..2 * end_idx], ibuffer)
1119 }
1120 }
1121
1122 let obuffer = match (ibuf_step, obuf_step) {
1126 (3, 4) => {
1127 let ibuffer_chunks = ibuffer.as_chunks::<3>().0.iter();
1128 let obuffer_chunks = obuffer.as_chunks_mut::<4>().0.iter_mut();
1129 for (rgb, rgba) in ibuffer_chunks.zip(obuffer_chunks).take(count) {
1130 rgba[0] = rgb[0];
1131 rgba[1] = rgb[1];
1132 rgba[2] = rgb[2];
1133 rgba[3] = 1.0;
1134 }
1135
1136 &obuffer[..4 * count]
1137 }
1138 (4, 3) => {
1139 let ibuffer_chunks = ibuffer.as_chunks::<4>().0.iter();
1140 let obuffer_chunks = obuffer.as_chunks_mut::<3>().0.iter_mut();
1141 for (rgba, rgb) in ibuffer_chunks.zip(obuffer_chunks).take(count) {
1142 rgb[0] = rgba[0];
1143 rgb[1] = rgba[1];
1144 rgb[2] = rgba[2];
1145 }
1146
1147 &obuffer[..3 * count]
1148 }
1149 (n, m) => {
1150 debug_assert_eq!(n, m);
1151 &ibuffer[..m * count]
1152 }
1153 };
1154
1155 match into_layout {
1156 Layout::Rgb => {
1157 CicpTransform::clamp_rgb(obuffer, &mut output[3 * start_idx..3 * end_idx]);
1158 }
1159 Layout::Rgba => {
1160 CicpTransform::clamp_rgba(obuffer, &mut output[4 * start_idx..4 * end_idx]);
1161 }
1162 Layout::Luma => {
1163 CicpTransform::clamp_rgb_luma(
1164 obuffer,
1165 &mut output[start_idx..end_idx],
1166 color_space_coefs,
1167 );
1168 }
1169 Layout::LumaAlpha => {
1170 CicpTransform::clamp_rgba_luma(
1171 obuffer,
1172 &mut output[2 * start_idx..2 * end_idx],
1173 color_space_coefs,
1174 );
1175 }
1176 }
1177 }
1178 }
1179
1180 pub(crate) fn cast_pixels_from_subpixels<FromSubpixel, IntoSubpixel>(
1183 &self,
1184 buffer: &[FromSubpixel],
1185 from_layout: LayoutWithColor,
1186 into_layout: LayoutWithColor,
1187 ) -> Result<Vec<IntoSubpixel>, Vec<IntoSubpixel>>
1188 where
1189 FromSubpixel: ColorComponentForCicp,
1190 IntoSubpixel: ColorComponentForCicp + FromPrimitive<FromSubpixel> + Primitive,
1191 {
1192 use crate::traits::private::LayoutWithColor as Layout;
1193
1194 assert!(buffer.len().is_multiple_of(from_layout.channels()));
1195 let pixels = buffer.len() / from_layout.channels();
1196
1197 let mut output: Vec<IntoSubpixel> = vec_try_with_capacity(pixels * into_layout.channels())
1198 .expect("input layout already allocated with appropriate layout");
1201
1202 let map_channel = <IntoSubpixel as FromPrimitive<FromSubpixel>>::from_primitive;
1216
1217 match (from_layout, into_layout) {
1218 (Layout::Rgb, Layout::Rgb)
1220 | (Layout::Rgba, Layout::Rgba)
1221 | (Layout::Luma, Layout::Luma)
1222 | (Layout::LumaAlpha, Layout::LumaAlpha) => {
1223 output.extend(buffer.iter().copied().map(map_channel));
1226 }
1227 (Layout::Rgb, Layout::Rgba) => {
1228 Self::subpixel_cast_rgb_to_rgba(&mut output, buffer);
1229 }
1230 (Layout::Rgba, Layout::Rgb) => {
1231 Self::subpixel_cast_rgba_to_rgb(&mut output, buffer);
1232 }
1233 (Layout::Luma, Layout::LumaAlpha) => {
1234 Self::subpixel_cast_luma_to_luma_alpha(&mut output, buffer);
1235 }
1236 (Layout::LumaAlpha, Layout::Luma) => {
1237 Self::subpixel_cast_luma_alpha_to_luma(&mut output, buffer);
1238 }
1239 _ => return Err(output),
1240 }
1241
1242 Ok(output)
1243 }
1244
1245 fn subpixel_cast_rgb_to_rgba<FromSubpixel, IntoSubpixel>(
1246 output: &mut Vec<IntoSubpixel>,
1247 buffer: &[FromSubpixel],
1248 ) where
1249 FromSubpixel: ColorComponentForCicp,
1250 IntoSubpixel: ColorComponentForCicp + FromPrimitive<FromSubpixel> + Primitive,
1251 {
1252 let pixels = buffer.len() / LayoutWithColor::Rgb.channels();
1253 Self::create_output::<IntoSubpixel>(output, pixels, LayoutWithColor::Rgba);
1254
1255 let map_channel = <IntoSubpixel as FromPrimitive<FromSubpixel>>::from_primitive;
1256 let default_alpha = <IntoSubpixel as Primitive>::DEFAULT_MAX_VALUE;
1257
1258 let buffer_chunks = buffer.as_chunks::<3>().0;
1259 let output_chunks = output.as_chunks_mut::<4>().0;
1260
1261 for (&[r, g, b], out) in buffer_chunks.iter().zip(output_chunks) {
1262 *out = [
1263 map_channel(r),
1264 map_channel(g),
1265 map_channel(b),
1266 default_alpha,
1267 ];
1268 }
1269 }
1270
1271 fn subpixel_cast_rgba_to_rgb<FromSubpixel, IntoSubpixel>(
1272 output: &mut Vec<IntoSubpixel>,
1273 buffer: &[FromSubpixel],
1274 ) where
1275 FromSubpixel: ColorComponentForCicp,
1276 IntoSubpixel: ColorComponentForCicp + FromPrimitive<FromSubpixel> + Primitive,
1277 {
1278 let pixels = buffer.len() / LayoutWithColor::Rgba.channels();
1279 Self::create_output::<IntoSubpixel>(output, pixels, LayoutWithColor::Rgb);
1280
1281 let map_channel = <IntoSubpixel as FromPrimitive<FromSubpixel>>::from_primitive;
1282
1283 let buffer_chunks = buffer.as_chunks::<4>().0;
1284 let output_chunks = output.as_chunks_mut::<3>().0;
1285
1286 for (&[r, g, b, _], out) in buffer_chunks.iter().zip(output_chunks) {
1287 *out = [map_channel(r), map_channel(g), map_channel(b)];
1288 }
1289 }
1290
1291 fn subpixel_cast_luma_to_luma_alpha<FromSubpixel, IntoSubpixel>(
1294 output: &mut Vec<IntoSubpixel>,
1295 buffer: &[FromSubpixel],
1296 ) where
1297 FromSubpixel: ColorComponentForCicp,
1298 IntoSubpixel: ColorComponentForCicp + FromPrimitive<FromSubpixel> + Primitive,
1299 {
1300 let map_channel = <IntoSubpixel as FromPrimitive<FromSubpixel>>::from_primitive;
1301
1302 output.extend(buffer.iter().copied().flat_map(|l| {
1303 [
1304 map_channel(l),
1305 <IntoSubpixel as Primitive>::DEFAULT_MAX_VALUE,
1308 ]
1309 }));
1310 }
1311
1312 fn subpixel_cast_luma_alpha_to_luma<FromSubpixel, IntoSubpixel>(
1313 output: &mut Vec<IntoSubpixel>,
1314 buffer: &[FromSubpixel],
1315 ) where
1316 FromSubpixel: ColorComponentForCicp,
1317 IntoSubpixel: ColorComponentForCicp + FromPrimitive<FromSubpixel> + Primitive,
1318 {
1319 let map_channel = <IntoSubpixel as FromPrimitive<FromSubpixel>>::from_primitive;
1320
1321 let buffer_chunks = buffer.as_chunks::<2>().0;
1322
1323 output.extend(buffer_chunks.iter().map(|&[l, _]| map_channel(l)));
1324 }
1325}
1326
1327pub(crate) trait CicpPixelCast<FromColor>
1335where
1336 Self: Pixel + SealedPixelWithColorType<TransformableSubpixel = <Self as Pixel>::Subpixel>,
1339 FromColor:
1340 Pixel + SealedPixelWithColorType<TransformableSubpixel = <FromColor as Pixel>::Subpixel>,
1341 Self::Subpixel: ColorComponentForCicp + FromPrimitive<FromColor::Subpixel>,
1342 FromColor::Subpixel: ColorComponentForCicp,
1343{
1344}
1345
1346impl<FromColor, IntoColor> CicpPixelCast<FromColor> for IntoColor
1347where
1348 IntoColor: Pixel + SealedPixelWithColorType<TransformableSubpixel = IntoColor::Subpixel>,
1349 FromColor: Pixel + SealedPixelWithColorType<TransformableSubpixel = FromColor::Subpixel>,
1350 IntoColor::Subpixel: ColorComponentForCicp + FromPrimitive<FromColor::Subpixel>,
1351 FromColor::Subpixel: ColorComponentForCicp,
1352{
1353}
1354
1355pub(crate) trait ColorComponentForCicp: Copy {
1356 fn expand_to_f32(self) -> f32;
1357
1358 fn clamp_from_f32(val: f32) -> Self;
1359}
1360
1361impl ColorComponentForCicp for u8 {
1362 fn expand_to_f32(self) -> f32 {
1363 const R: f32 = 1.0 / u8::MAX as f32;
1364 self as f32 * R
1365 }
1366
1367 #[inline]
1368 fn clamp_from_f32(val: f32) -> Self {
1369 (val * Self::MAX as f32).round() as u8
1371 }
1372}
1373
1374impl ColorComponentForCicp for u16 {
1375 fn expand_to_f32(self) -> f32 {
1376 const R: f32 = 1.0 / u16::MAX as f32;
1377 self as f32 * R
1378 }
1379
1380 #[inline]
1381 fn clamp_from_f32(val: f32) -> Self {
1382 (val * Self::MAX as f32).round() as u16
1384 }
1385}
1386
1387impl ColorComponentForCicp for f32 {
1388 fn expand_to_f32(self) -> f32 {
1389 self
1390 }
1391
1392 fn clamp_from_f32(val: f32) -> Self {
1393 val
1394 }
1395}
1396
1397impl<P> RgbTransforms<P> {
1398 fn select_transform<O: SealedPixelWithColorType>(
1399 &self,
1400 into: LayoutWithColor,
1401 ) -> &Arc<CicpApplicable<'static, P>> {
1402 use crate::traits::private::{LayoutWithColor as Layout, PrivateToken};
1403 let from = O::layout(PrivateToken);
1404
1405 match (from, into) {
1406 (Layout::Rgb, Layout::Rgb) => &self.slices[0],
1407 (Layout::Rgb, Layout::Rgba) => &self.slices[1],
1408 (Layout::Rgba, Layout::Rgb) => &self.slices[2],
1409 (Layout::Rgba, Layout::Rgba) => &self.slices[3],
1410 (Layout::Rgb, Layout::Luma) => &self.rgb_luma[0],
1411 (Layout::Rgb, Layout::LumaAlpha) => &self.rgb_luma[1],
1412 (Layout::Rgba, Layout::Luma) => &self.rgb_luma[2],
1413 (Layout::Rgba, Layout::LumaAlpha) => &self.rgb_luma[3],
1414 (Layout::Luma, Layout::Rgb) => &self.luma_rgb[0],
1415 (Layout::Luma, Layout::Rgba) => &self.luma_rgb[1],
1416 (Layout::LumaAlpha, Layout::Rgb) => &self.luma_rgb[2],
1417 (Layout::LumaAlpha, Layout::Rgba) => &self.luma_rgb[3],
1418 (Layout::Luma, Layout::Luma) => &self.luma_luma[0],
1419 (Layout::Luma, Layout::LumaAlpha) => &self.luma_luma[1],
1420 (Layout::LumaAlpha, Layout::Luma) => &self.luma_luma[2],
1421 (Layout::LumaAlpha, Layout::LumaAlpha) => &self.luma_luma[3],
1422 }
1423 }
1424}
1425
1426impl Cicp {
1427 pub const SRGB: Self = Cicp {
1429 primaries: CicpColorPrimaries::SRgb,
1430 transfer: CicpTransferCharacteristics::SRgb,
1431 matrix: CicpMatrixCoefficients::Identity,
1432 full_range: CicpVideoFullRangeFlag::FullRange,
1433 };
1434
1435 pub const SRGB_LINEAR: Self = Cicp {
1437 primaries: CicpColorPrimaries::SRgb,
1438 transfer: CicpTransferCharacteristics::Linear,
1439 matrix: CicpMatrixCoefficients::Identity,
1440 full_range: CicpVideoFullRangeFlag::FullRange,
1441 };
1442
1443 pub const DISPLAY_P3: Self = Cicp {
1449 primaries: CicpColorPrimaries::SmpteRp432,
1450 transfer: CicpTransferCharacteristics::SRgb,
1451 matrix: CicpMatrixCoefficients::Identity,
1452 full_range: CicpVideoFullRangeFlag::FullRange,
1453 };
1454
1455 fn to_moxcms_compute_profile(self) -> Option<ColorProfile> {
1486 let mut rgb = moxcms::ColorProfile::new_srgb::<EpochNow>();
1487
1488 rgb.update_rgb_colorimetry_from_cicp(moxcms::CicpProfile {
1489 color_primaries: self.primaries.to_moxcms(),
1490 transfer_characteristics: self.transfer.to_moxcms(),
1491 matrix_coefficients: self.matrix.to_moxcms()?,
1492 full_range: match self.full_range {
1493 CicpVideoFullRangeFlag::NarrowRange => false,
1494 CicpVideoFullRangeFlag::FullRange => true,
1495 },
1496 });
1497
1498 Some(ColorProfile { rgb })
1499 }
1500
1501 pub(crate) const fn qualify_stability(&self) -> bool {
1514 const _: () = {
1515 assert!(Cicp::SRGB.qualify_stability());
1517 assert!(Cicp::SRGB_LINEAR.qualify_stability());
1518 assert!(Cicp::DISPLAY_P3.qualify_stability());
1519 };
1520
1521 matches!(self.full_range, CicpVideoFullRangeFlag::FullRange)
1522 && matches!(
1523 self.matrix,
1524 CicpMatrixCoefficients::Identity
1526 | CicpMatrixCoefficients::ChromaticityDerivedNonConstant
1528 )
1529 && matches!(
1530 self.primaries,
1531 CicpColorPrimaries::SRgb
1532 | CicpColorPrimaries::SmpteRp431
1533 | CicpColorPrimaries::SmpteRp432
1534 | CicpColorPrimaries::Bt601
1535 | CicpColorPrimaries::Rgb240m
1536 )
1537 && matches!(
1538 self.transfer,
1539 CicpTransferCharacteristics::SRgb
1540 | CicpTransferCharacteristics::Bt709
1541 | CicpTransferCharacteristics::Bt601
1542 | CicpTransferCharacteristics::Linear
1543 )
1544 }
1545
1546 pub(crate) const fn into_rgb(self) -> CicpRgb {
1548 CicpRgb {
1549 primaries: self.primaries,
1550 transfer: self.transfer,
1551 luminance: DerivedLuminance::NonConstant,
1558 }
1559 }
1560
1561 pub(crate) fn try_into_rgb(self) -> Result<CicpRgb, ImageError> {
1562 if Cicp::from(self.into_rgb()) != self {
1563 Err(ImageError::Parameter(ParameterError::from_kind(
1564 ParameterErrorKind::RgbCicpRequired(self),
1565 )))
1566 } else {
1567 Ok(self.into_rgb())
1568 }
1569 }
1570}
1571
1572impl CicpRgb {
1573 pub(crate) fn derived_luminance(&self) -> Option<[f32; 3]> {
1577 let primaries = match self.primaries {
1578 CicpColorPrimaries::SRgb => moxcms::ColorPrimaries::BT_709,
1579 CicpColorPrimaries::RgbM => moxcms::ColorPrimaries::BT_470M,
1580 CicpColorPrimaries::RgbB => moxcms::ColorPrimaries::BT_470BG,
1581 CicpColorPrimaries::Bt601 => moxcms::ColorPrimaries::BT_601,
1582 CicpColorPrimaries::Rgb240m => moxcms::ColorPrimaries::SMPTE_240,
1583 CicpColorPrimaries::GenericFilm => moxcms::ColorPrimaries::GENERIC_FILM,
1584 CicpColorPrimaries::Rgb2020 => moxcms::ColorPrimaries::BT_2020,
1585 CicpColorPrimaries::Xyz => moxcms::ColorPrimaries::XYZ,
1586 CicpColorPrimaries::SmpteRp431 => moxcms::ColorPrimaries::DISPLAY_P3,
1587 CicpColorPrimaries::SmpteRp432 => moxcms::ColorPrimaries::DISPLAY_P3,
1588 CicpColorPrimaries::Industry22 => moxcms::ColorPrimaries::EBU_3213,
1589 CicpColorPrimaries::Unspecified => return None,
1590 };
1591
1592 const ILLUMINANT_C: moxcms::Chromaticity = moxcms::Chromaticity::new(0.310, 0.316);
1593
1594 let whitepoint = match self.primaries {
1595 CicpColorPrimaries::SRgb => moxcms::Chromaticity::D65,
1596 CicpColorPrimaries::RgbM => ILLUMINANT_C,
1597 CicpColorPrimaries::RgbB => moxcms::Chromaticity::D65,
1598 CicpColorPrimaries::Bt601 => moxcms::Chromaticity::D65,
1599 CicpColorPrimaries::Rgb240m => moxcms::Chromaticity::D65,
1600 CicpColorPrimaries::GenericFilm => ILLUMINANT_C,
1601 CicpColorPrimaries::Rgb2020 => moxcms::Chromaticity::D65,
1602 CicpColorPrimaries::Xyz => moxcms::Chromaticity::new(1. / 3., 1. / 3.),
1603 CicpColorPrimaries::SmpteRp431 => moxcms::Chromaticity::new(0.314, 0.351),
1604 CicpColorPrimaries::SmpteRp432 => moxcms::Chromaticity::D65,
1605 CicpColorPrimaries::Industry22 => moxcms::Chromaticity::D65,
1606 CicpColorPrimaries::Unspecified => return None,
1607 };
1608
1609 let matrix = primaries.transform_to_xyz(whitepoint);
1610
1611 Some(matrix.v[1])
1613 }
1614}
1615
1616impl From<CicpRgb> for Cicp {
1617 fn from(cicp: CicpRgb) -> Self {
1618 Cicp {
1619 primaries: cicp.primaries,
1620 transfer: cicp.transfer,
1621 matrix: CicpMatrixCoefficients::Identity,
1622 full_range: CicpVideoFullRangeFlag::FullRange,
1623 }
1624 }
1625}
1626
1627struct ColorProfile {
1635 rgb: moxcms::ColorProfile,
1636}
1637
1638impl ColorProfile {
1639 fn map_layout(&self, layout: LayoutWithColor) -> (&moxcms::ColorProfile, moxcms::Layout) {
1640 match layout {
1641 LayoutWithColor::Rgb => (&self.rgb, moxcms::Layout::Rgb),
1642 LayoutWithColor::Rgba => (&self.rgb, moxcms::Layout::Rgba),
1643 LayoutWithColor::Luma | LayoutWithColor::LumaAlpha => unreachable!(),
1645 }
1646 }
1647}
1648
1649#[cfg(test)]
1650#[test]
1651fn moxcms() {
1652 let l = moxcms::TransferCharacteristics::Linear;
1653 assert_eq!(l.linearize(1.0), 1.0);
1654 assert_eq!(l.gamma(1.0), 1.0);
1655
1656 assert_eq!(l.gamma(0.5), 0.5);
1657}
1658
1659#[cfg(test)]
1660#[test]
1661fn derived_luminance() {
1662 let luminance = Cicp::SRGB.into_rgb().derived_luminance();
1663 let [kr, kg, kb] = luminance.unwrap();
1664 assert!((kr - 0.2126).abs() < 1e-4);
1665 assert!((kg - 0.7152).abs() < 1e-4);
1666 assert!((kb - 0.0722).abs() < 1e-4);
1667}
1668
1669#[cfg(test)]
1670mod tests {
1671 use super::{Cicp, CicpTransform};
1672 use crate::{Luma, LumaA, Rgb, Rgba};
1673
1674 #[test]
1675 fn can_create_transforms() {
1676 assert!(CicpTransform::new(Cicp::SRGB, Cicp::SRGB).is_some());
1677 assert!(CicpTransform::new(Cicp::SRGB, Cicp::DISPLAY_P3).is_some());
1678 assert!(CicpTransform::new(Cicp::DISPLAY_P3, Cicp::SRGB).is_some());
1679 assert!(CicpTransform::new(Cicp::DISPLAY_P3, Cicp::DISPLAY_P3).is_some());
1680 }
1681
1682 fn no_coefficient_fallback() -> [f32; 3] {
1683 panic!("Fallback coefficients required")
1684 }
1685
1686 #[test]
1687 fn transform_pixels_srgb() {
1688 let data = [255, 0, 0, 255];
1691 let color = Cicp::SRGB.into_rgb();
1692 let rgba = color.cast_pixels::<Rgba<u8>, Rgb<u8>>(&data, &no_coefficient_fallback);
1693 assert_eq!(rgba, [255, 0, 0]);
1694 let luma = color.cast_pixels::<Rgba<u8>, Luma<u8>>(&data, &no_coefficient_fallback);
1695 assert_eq!(luma, [54]); let luma_a = color.cast_pixels::<Rgba<u8>, LumaA<u8>>(&data, &no_coefficient_fallback);
1697 assert_eq!(luma_a, [54, 255]);
1698 }
1699
1700 #[test]
1701 fn transform_pixels_srgb_16() {
1702 let data = [u16::MAX / 2];
1705 let color = Cicp::SRGB.into_rgb();
1706 let rgba = color.cast_pixels::<Luma<u16>, Rgb<u8>>(&data, &no_coefficient_fallback);
1707 assert_eq!(rgba, [127; 3]);
1708 let luma = color.cast_pixels::<Luma<u16>, Luma<u8>>(&data, &no_coefficient_fallback);
1709 assert_eq!(luma, [127]);
1710 let luma_a = color.cast_pixels::<Luma<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1711 assert_eq!(luma_a, [127, 255]);
1712
1713 let data = [u16::MAX / 2 + 1];
1714 let color = Cicp::SRGB.into_rgb();
1715 let rgba = color.cast_pixels::<Luma<u16>, Rgb<u8>>(&data, &no_coefficient_fallback);
1716 assert_eq!(rgba, [128; 3]);
1717 let luma = color.cast_pixels::<Luma<u16>, Luma<u8>>(&data, &no_coefficient_fallback);
1718 assert_eq!(luma, [128]);
1719 let luma_a = color.cast_pixels::<Luma<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1720 assert_eq!(luma_a, [128, 255]);
1721 }
1722
1723 #[test]
1724 fn transform_pixels_srgb_luma_alpha() {
1725 let data = [u16::MAX / 2, u16::MAX];
1728 let color = Cicp::SRGB.into_rgb();
1729 let rgba = color.cast_pixels::<LumaA<u16>, Rgb<u8>>(&data, &no_coefficient_fallback);
1730 assert_eq!(rgba, [127; 3]);
1731 let luma = color.cast_pixels::<LumaA<u16>, Luma<u8>>(&data, &no_coefficient_fallback);
1732 assert_eq!(luma, [127]);
1733 let luma = color.cast_pixels::<LumaA<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1734 assert_eq!(luma, [127, u8::MAX]);
1735 let luma_a = color.cast_pixels::<LumaA<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1736 assert_eq!(luma_a, [127, 255]);
1737
1738 let data = [u16::MAX / 2 + 1, u16::MAX];
1739 let color = Cicp::SRGB.into_rgb();
1740 let rgba = color.cast_pixels::<LumaA<u16>, Rgb<u8>>(&data, &no_coefficient_fallback);
1741 assert_eq!(rgba, [128; 3]);
1742 let luma = color.cast_pixels::<LumaA<u16>, Luma<u8>>(&data, &no_coefficient_fallback);
1743 assert_eq!(luma, [128]);
1744 let luma = color.cast_pixels::<LumaA<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1745 assert_eq!(luma, [128, u8::MAX]);
1746 let luma_a = color.cast_pixels::<LumaA<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1747 assert_eq!(luma_a, [128, 255]);
1748 }
1749}