1use std::sync::Arc;
2
3use crate::{
6 color::FromPrimitive,
7 error::{ParameterError, ParameterErrorKind},
8 math::multiply_accumulate,
9 traits::{
10 private::{LayoutWithColor, SealedPixelWithColorType},
11 PixelWithColorType,
12 },
13 utils::vec_try_with_capacity,
14 DynamicImage, ImageError, Pixel, Primitive,
15};
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
19pub struct Cicp {
20 pub primaries: CicpColorPrimaries,
22 pub transfer: CicpTransferCharacteristics,
24 pub matrix: CicpMatrixCoefficients,
28 pub full_range: CicpVideoFullRangeFlag,
34}
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
38pub(crate) struct CicpRgb {
39 pub(crate) primaries: CicpColorPrimaries,
40 pub(crate) transfer: CicpTransferCharacteristics,
41 pub(crate) luminance: DerivedLuminance,
42}
43
44#[repr(u8)]
51#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
52#[non_exhaustive]
53pub enum CicpColorPrimaries {
54 SRgb = 1,
56 Unspecified = 2,
58 RgbM = 4,
60 RgbB = 5,
62 Bt601 = 6,
65 Rgb240m = 7,
68 GenericFilm = 8,
70 Rgb2020 = 9,
73 Xyz = 10,
77 SmpteRp431 = 11,
79 SmpteRp432 = 12,
81 Industry22 = 22,
91}
92
93impl CicpColorPrimaries {
94 fn to_moxcms(self) -> moxcms::CicpColorPrimaries {
95 use moxcms::CicpColorPrimaries as M;
96
97 match self {
98 CicpColorPrimaries::SRgb => M::Bt709,
99 CicpColorPrimaries::Unspecified => M::Unspecified,
100 CicpColorPrimaries::RgbM => M::Bt470M,
101 CicpColorPrimaries::RgbB => M::Bt470Bg,
102 CicpColorPrimaries::Bt601 => M::Bt601,
103 CicpColorPrimaries::Rgb240m => M::Smpte240,
104 CicpColorPrimaries::GenericFilm => M::GenericFilm,
105 CicpColorPrimaries::Rgb2020 => M::Bt2020,
106 CicpColorPrimaries::Xyz => M::Xyz,
107 CicpColorPrimaries::SmpteRp431 => M::Smpte431,
108 CicpColorPrimaries::SmpteRp432 => M::Smpte432,
109 CicpColorPrimaries::Industry22 => M::Ebu3213,
110 }
111 }
112}
113
114#[repr(u8)]
119#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
120#[non_exhaustive]
121pub enum CicpTransferCharacteristics {
122 Bt709 = 1,
126 Unspecified = 2,
128 Bt470M = 4,
135 Bt470BG = 5,
137 Bt601 = 6,
142 Smpte240m = 7,
144 Linear = 8,
146 Log100 = 9,
148 LogSqrt = 10,
150 Iec61966_2_4 = 11,
152 Bt1361 = 12,
154 SRgb = 13,
157 Bt2020_10bit = 14,
160 Bt2020_12bit = 15,
163 Smpte2084 = 16,
166 Smpte428 = 17,
168 Bt2100Hlg = 18,
171}
172
173impl CicpTransferCharacteristics {
174 fn to_moxcms(self) -> moxcms::TransferCharacteristics {
175 use moxcms::TransferCharacteristics as T;
176
177 match self {
178 CicpTransferCharacteristics::Bt709 => T::Bt709,
179 CicpTransferCharacteristics::Unspecified => T::Unspecified,
180 CicpTransferCharacteristics::Bt470M => T::Bt470M,
181 CicpTransferCharacteristics::Bt470BG => T::Bt470Bg,
182 CicpTransferCharacteristics::Bt601 => T::Bt601,
183 CicpTransferCharacteristics::Smpte240m => T::Smpte240,
184 CicpTransferCharacteristics::Linear => T::Linear,
185 CicpTransferCharacteristics::Log100 => T::Log100,
186 CicpTransferCharacteristics::LogSqrt => T::Log100sqrt10,
187 CicpTransferCharacteristics::Iec61966_2_4 => T::Iec61966,
188 CicpTransferCharacteristics::Bt1361 => T::Bt1361,
189 CicpTransferCharacteristics::SRgb => T::Srgb,
190 CicpTransferCharacteristics::Bt2020_10bit => T::Bt202010bit,
191 CicpTransferCharacteristics::Bt2020_12bit => T::Bt202012bit,
192 CicpTransferCharacteristics::Smpte2084 => T::Smpte2084,
193 CicpTransferCharacteristics::Smpte428 => T::Smpte428,
194 CicpTransferCharacteristics::Bt2100Hlg => T::Hlg,
195 }
196 }
197}
198
199#[repr(u8)]
202#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
203#[non_exhaustive]
204pub enum CicpMatrixCoefficients {
205 Identity = 0,
210 Bt709 = 1,
215 Unspecified = 2,
217 UsFCC = 4,
219 Bt470BG = 5,
227 Smpte170m = 6,
229 Smpte240m = 7,
231 YCgCo = 8,
233 Bt2020NonConstant = 9,
236 Bt2020Constant = 10,
238 Smpte2085 = 11,
240 ChromaticityDerivedNonConstant = 12,
242 ChromaticityDerivedConstant = 13,
244 Bt2100 = 14,
246 IptPqC2 = 15,
248 YCgCoRe = 16,
250 YCgCoRo = 17,
252}
253
254impl CicpMatrixCoefficients {
255 fn to_moxcms(self) -> Option<moxcms::MatrixCoefficients> {
256 use moxcms::MatrixCoefficients as M;
257
258 Some(match self {
259 CicpMatrixCoefficients::Identity => M::Identity,
260 CicpMatrixCoefficients::Unspecified => M::Unspecified,
261 CicpMatrixCoefficients::Bt709 => M::Bt709,
262 CicpMatrixCoefficients::UsFCC => M::Fcc,
263 CicpMatrixCoefficients::Bt470BG => M::Bt470Bg,
264 CicpMatrixCoefficients::Smpte170m => M::Smpte170m,
265 CicpMatrixCoefficients::Smpte240m => M::Smpte240m,
266 CicpMatrixCoefficients::YCgCo => M::YCgCo,
267 CicpMatrixCoefficients::Bt2020NonConstant => M::Bt2020Ncl,
268 CicpMatrixCoefficients::Bt2020Constant => M::Bt2020Cl,
269 CicpMatrixCoefficients::Smpte2085 => M::Smpte2085,
270 CicpMatrixCoefficients::ChromaticityDerivedNonConstant => M::ChromaticityDerivedNCL,
271 CicpMatrixCoefficients::ChromaticityDerivedConstant => M::ChromaticityDerivedCL,
272 CicpMatrixCoefficients::Bt2100 => M::ICtCp,
273 CicpMatrixCoefficients::IptPqC2
274 | CicpMatrixCoefficients::YCgCoRe
275 | CicpMatrixCoefficients::YCgCoRo => return None,
276 })
277 }
278}
279
280#[repr(u8)]
282#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
283#[non_exhaustive]
284pub enum CicpVideoFullRangeFlag {
285 NarrowRange = 0,
289 FullRange = 1,
291}
292
293#[repr(u8)]
294#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
295pub(crate) enum DerivedLuminance {
296 #[allow(dead_code)] Constant,
301 NonConstant,
304}
305
306#[derive(Clone)]
312pub struct CicpTransform {
313 from: Cicp,
314 into: Cicp,
315 u8: RgbTransforms<u8>,
316 u16: RgbTransforms<u16>,
317 f32: RgbTransforms<f32>,
318 output_coefs: [f32; 3],
320}
321
322pub(crate) type CicpApplicable<'lt, C> = dyn Fn(&[C], &mut [C]) + Send + Sync + 'lt;
323
324#[derive(Clone)]
325struct RgbTransforms<C> {
326 slices: [Arc<CicpApplicable<'static, C>>; 4],
327 luma_rgb: [Arc<CicpApplicable<'static, C>>; 4],
328 rgb_luma: [Arc<CicpApplicable<'static, C>>; 4],
329 luma_luma: [Arc<CicpApplicable<'static, C>>; 4],
330}
331
332impl CicpTransform {
333 pub fn new(from: Cicp, into: Cicp) -> Option<Self> {
346 if !from.qualify_stability() || !into.qualify_stability() {
347 return None;
351 }
352
353 let _input_coefs = from.into_rgb().derived_luminance()?;
357 let output_coefs = into.into_rgb().derived_luminance()?;
358
359 let mox_from = from.to_moxcms_compute_profile()?;
360 let mox_into = into.to_moxcms_compute_profile()?;
361
362 let opt = moxcms::TransformOptions::default();
363
364 let f32_fallback = {
365 let try_f32 = Self::LAYOUTS.map(|(from_layout, into_layout)| {
366 let (from, from_layout) = mox_from.map_layout(from_layout);
367 let (into, into_layout) = mox_into.map_layout(into_layout);
368
369 from.create_transform_f32(from_layout, into, into_layout, opt)
370 .ok()
371 });
372
373 if try_f32.iter().any(Option::is_none) {
374 return None;
375 }
376
377 try_f32.map(Option::unwrap)
378 };
379
380 Some(CicpTransform {
382 from,
383 into,
384 u8: Self::build_transforms(
385 Self::LAYOUTS.map(|(from_layout, into_layout)| {
386 let (from, from_layout) = mox_from.map_layout(from_layout);
387 let (into, into_layout) = mox_into.map_layout(into_layout);
388
389 from.create_transform_8bit(from_layout, into, into_layout, opt)
390 .ok()
391 }),
392 f32_fallback.clone(),
393 output_coefs,
394 )?,
395 u16: Self::build_transforms(
396 Self::LAYOUTS.map(|(from_layout, into_layout)| {
397 let (from, from_layout) = mox_from.map_layout(from_layout);
398 let (into, into_layout) = mox_into.map_layout(into_layout);
399
400 from.create_transform_16bit(from_layout, into, into_layout, opt)
401 .ok()
402 }),
403 f32_fallback.clone(),
404 output_coefs,
405 )?,
406 f32: Self::build_transforms(
407 f32_fallback.clone().map(Some),
408 f32_fallback.clone(),
409 output_coefs,
410 )?,
411 output_coefs,
412 })
413 }
414
415 pub(crate) fn supported_transform_fn<From: PixelWithColorType, Into: PixelWithColorType>(
424 &self,
425 ) -> &'_ CicpApplicable<'_, From::Subpixel> {
426 use crate::traits::private::double_dispatch_transform_from_sealed;
427 double_dispatch_transform_from_sealed::<From, Into>(self)
428 }
429
430 pub(crate) fn check_applicable(&self, from: Cicp, into: Cicp) -> Result<(), ImageError> {
432 let check_expectation = |expected, found| {
433 if expected == found {
434 Ok(())
435 } else {
436 Err(ParameterError::from_kind(
437 ParameterErrorKind::CicpMismatch { expected, found },
438 ))
439 }
440 };
441
442 check_expectation(self.from, from).map_err(ImageError::Parameter)?;
443 check_expectation(self.into, into).map_err(ImageError::Parameter)?;
444
445 Ok(())
446 }
447
448 fn build_transforms<P: ColorComponentForCicp + Default + 'static>(
449 trs: [Option<Arc<dyn moxcms::TransformExecutor<P> + Send + Sync>>; 4],
450 f32: [Arc<dyn moxcms::TransformExecutor<f32> + Send + Sync>; 4],
451 output_coef: [f32; 3],
452 ) -> Option<RgbTransforms<P>> {
453 if trs.iter().any(Option::is_none) {
455 return None;
456 }
457
458 let trs = trs.map(Option::unwrap);
459
460 let slices = trs.clone().map(|tr| {
462 Arc::new(move |input: &[P], output: &mut [P]| {
463 tr.transform(input, output).expect("transform failed")
464 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>
465 });
466
467 const N: usize = 256;
468
469 let luma_rgb = {
471 let [tr33, tr34, tr43, tr44] = f32.clone();
472
473 [
474 Arc::new(move |input: &[P], output: &mut [P]| {
475 let mut ibuffer = [0.0f32; 3 * N];
476 let mut obuffer = [0.0f32; 3 * N];
477
478 for (luma, output) in input.chunks(N).zip(output.chunks_mut(3 * N)) {
479 let n = luma.len();
480 let ibuffer = &mut ibuffer[..3 * n];
481 let obuffer = &mut obuffer[..3 * n];
482 Self::expand_luma_rgb(luma, ibuffer);
483 tr33.transform(ibuffer, obuffer).expect("transform failed");
484 Self::clamp_rgb(obuffer, output);
485 }
486 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
487 Arc::new(move |input: &[P], output: &mut [P]| {
488 let mut ibuffer = [0.0f32; 3 * N];
489 let mut obuffer = [0.0f32; 4 * N];
490
491 for (luma, output) in input.chunks(N).zip(output.chunks_mut(4 * N)) {
492 let n = luma.len();
493 let ibuffer = &mut ibuffer[..3 * n];
494 let obuffer = &mut obuffer[..4 * n];
495 Self::expand_luma_rgb(luma, ibuffer);
496 tr34.transform(ibuffer, obuffer).expect("transform failed");
497 Self::clamp_rgba(obuffer, output);
498 }
499 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
500 Arc::new(move |input: &[P], output: &mut [P]| {
501 let mut ibuffer = [0.0f32; 4 * N];
502 let mut obuffer = [0.0f32; 3 * N];
503
504 for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(3 * N)) {
505 let n = luma.len() / 2;
506 let ibuffer = &mut ibuffer[..4 * n];
507 let obuffer = &mut obuffer[..3 * n];
508 Self::expand_luma_rgba(luma, ibuffer);
509 tr43.transform(ibuffer, obuffer).expect("transform failed");
510 Self::clamp_rgb(obuffer, output);
511 }
512 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
513 Arc::new(move |input: &[P], output: &mut [P]| {
514 let mut ibuffer = [0.0f32; 4 * N];
515 let mut obuffer = [0.0f32; 4 * N];
516
517 for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(4 * N)) {
518 let n = luma.len() / 2;
519 let ibuffer = &mut ibuffer[..4 * n];
520 let obuffer = &mut obuffer[..4 * n];
521 Self::expand_luma_rgba(luma, ibuffer);
522 tr44.transform(ibuffer, obuffer).expect("transform failed");
523 Self::clamp_rgba(obuffer, output);
524 }
525 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
526 ]
527 };
528
529 let rgb_luma = {
531 let [tr33, tr34, tr43, tr44] = f32.clone();
532
533 [
534 Arc::new(move |input: &[P], output: &mut [P]| {
535 debug_assert_eq!(input.len() / 3, output.len());
536
537 let mut ibuffer = [0.0f32; 3 * N];
538 let mut obuffer = [0.0f32; 3 * N];
539
540 for (rgb, output) in input.chunks(3 * N).zip(output.chunks_mut(N)) {
541 let n = output.len();
542 let ibuffer = &mut ibuffer[..3 * n];
543 let obuffer = &mut obuffer[..3 * n];
544 Self::expand_rgb(rgb, ibuffer);
545 tr33.transform(ibuffer, obuffer).expect("transform failed");
546 Self::clamp_rgb_luma(obuffer, output, output_coef);
547 }
548 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
549 Arc::new(move |input: &[P], output: &mut [P]| {
550 debug_assert_eq!(input.len() / 3, output.len() / 2);
551
552 let mut ibuffer = [0.0f32; 3 * N];
553 let mut obuffer = [0.0f32; 4 * N];
554
555 for (rgb, output) in input.chunks(4 * N).zip(output.chunks_mut(2 * N)) {
556 let n = output.len() / 2;
557 let ibuffer = &mut ibuffer[..3 * n];
558 let obuffer = &mut obuffer[..4 * n];
559 Self::expand_rgb(rgb, ibuffer);
560 tr34.transform(ibuffer, obuffer).expect("transform failed");
561 Self::clamp_rgba_luma(obuffer, output, output_coef);
562 }
563 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
564 Arc::new(move |input: &[P], output: &mut [P]| {
565 debug_assert_eq!(input.len() / 4, output.len());
566
567 let mut ibuffer = [0.0f32; 4 * N];
568 let mut obuffer = [0.0f32; 3 * N];
569
570 for (rgba, output) in input.chunks(4 * N).zip(output.chunks_mut(N)) {
571 let n = output.len();
572 let ibuffer = &mut ibuffer[..4 * n];
573 let obuffer = &mut obuffer[..3 * n];
574 Self::expand_rgba(rgba, ibuffer);
575 tr43.transform(ibuffer, obuffer).expect("transform failed");
576 Self::clamp_rgb_luma(obuffer, output, output_coef);
577 }
578 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
579 Arc::new(move |input: &[P], output: &mut [P]| {
580 debug_assert_eq!(input.len() / 4, output.len() / 2);
581
582 let mut ibuffer = [0.0f32; 4 * N];
583 let mut obuffer = [0.0f32; 4 * N];
584
585 for (rgba, output) in input.chunks(4 * N).zip(output.chunks_mut(2 * N)) {
586 let n = output.len() / 2;
587 let ibuffer = &mut ibuffer[..4 * n];
588 let obuffer = &mut obuffer[..4 * n];
589 Self::expand_rgba(rgba, ibuffer);
590 tr44.transform(ibuffer, obuffer).expect("transform failed");
591 Self::clamp_rgba_luma(obuffer, output, output_coef);
592 }
593 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
594 ]
595 };
596
597 let luma_luma = {
599 let [tr33, tr34, tr43, tr44] = f32.clone();
600
601 [
602 Arc::new(move |input: &[P], output: &mut [P]| {
603 debug_assert_eq!(input.len(), output.len());
604 let mut ibuffer = [0.0f32; 3 * N];
605 let mut obuffer = [0.0f32; 3 * N];
606
607 for (luma, output) in input.chunks(N).zip(output.chunks_mut(N)) {
608 let n = luma.len();
609 let ibuffer = &mut ibuffer[..3 * n];
610 let obuffer = &mut obuffer[..3 * n];
611 Self::expand_luma_rgb(luma, ibuffer);
612 tr33.transform(ibuffer, obuffer).expect("transform failed");
613 Self::clamp_rgb_luma(obuffer, output, output_coef);
614 }
615 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
616 Arc::new(move |input: &[P], output: &mut [P]| {
617 debug_assert_eq!(input.len(), output.len() / 2);
618 let mut ibuffer = [0.0f32; 3 * N];
619 let mut obuffer = [0.0f32; 4 * N];
620
621 for (luma, output) in input.chunks(N).zip(output.chunks_mut(2 * N)) {
622 let n = luma.len();
623 let ibuffer = &mut ibuffer[..3 * n];
624 let obuffer = &mut obuffer[..4 * n];
625 Self::expand_luma_rgb(luma, ibuffer);
626 tr34.transform(ibuffer, obuffer).expect("transform failed");
627 Self::clamp_rgba_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() / 2, output.len());
632 let mut ibuffer = [0.0f32; 4 * N];
633 let mut obuffer = [0.0f32; 3 * N];
634
635 for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(N)) {
636 let n = luma.len() / 2;
637 let ibuffer = &mut ibuffer[..4 * n];
638 let obuffer = &mut obuffer[..3 * n];
639 Self::expand_luma_rgba(luma, ibuffer);
640 tr43.transform(ibuffer, obuffer).expect("transform failed");
641 Self::clamp_rgb_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() / 2);
646 let mut ibuffer = [0.0f32; 4 * N];
647 let mut obuffer = [0.0f32; 4 * N];
648
649 for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(2 * N)) {
650 let n = luma.len() / 2;
651 let ibuffer = &mut ibuffer[..4 * n];
652 let obuffer = &mut obuffer[..4 * n];
653 Self::expand_luma_rgba(luma, ibuffer);
654 tr44.transform(ibuffer, obuffer).expect("transform failed");
655 Self::clamp_rgba_luma(obuffer, output, output_coef);
656 }
657 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
658 ]
659 };
660
661 Some(RgbTransforms {
662 slices,
663 luma_rgb,
664 rgb_luma,
665 luma_luma,
666 })
667 }
668
669 pub(crate) fn transform_dynamic(&self, lhs: &mut DynamicImage, rhs: &DynamicImage) {
670 const STEP: usize = 256;
671
672 let mut ibuffer = [0.0f32; 4 * STEP];
673 let mut obuffer = [0.0f32; 4 * STEP];
674
675 let pixels = (u64::from(lhs.width()) * u64::from(lhs.height())) as usize;
676
677 let input_samples;
678 let output_samples;
679
680 let inner_transform = match (
681 LayoutWithColor::from(lhs.color()),
682 LayoutWithColor::from(rhs.color()),
683 ) {
684 (
685 LayoutWithColor::Luma | LayoutWithColor::Rgb,
686 LayoutWithColor::Luma | LayoutWithColor::Rgb,
687 ) => {
688 output_samples = 3;
689 input_samples = 3;
690 &*self.f32.slices[0]
691 }
692 (
693 LayoutWithColor::LumaAlpha | LayoutWithColor::Rgba,
694 LayoutWithColor::Luma | LayoutWithColor::Rgb,
695 ) => {
696 output_samples = 4;
697 input_samples = 3;
698 &*self.f32.slices[1]
699 }
700 (
701 LayoutWithColor::Luma | LayoutWithColor::Rgb,
702 LayoutWithColor::LumaAlpha | LayoutWithColor::Rgba,
703 ) => {
704 output_samples = 3;
705 input_samples = 4;
706 &*self.f32.slices[2]
707 }
708 (
709 LayoutWithColor::LumaAlpha | LayoutWithColor::Rgba,
710 LayoutWithColor::LumaAlpha | LayoutWithColor::Rgba,
711 ) => {
712 output_samples = 4;
713 input_samples = 4;
714 &*self.f32.slices[3]
715 }
716 };
717
718 for start_idx in (0..pixels).step_by(STEP) {
719 let end_idx = (start_idx + STEP).min(pixels);
720 let count = end_idx - start_idx;
721
722 match rhs {
725 DynamicImage::ImageLuma8(buf) => {
726 CicpTransform::expand_luma_rgb(
727 &buf.inner_pixels()[start_idx..end_idx],
728 &mut ibuffer[..3 * count],
729 );
730 }
731 DynamicImage::ImageLumaA8(buf) => {
732 CicpTransform::expand_luma_rgba(
733 &buf.inner_pixels()[2 * start_idx..2 * end_idx],
734 &mut ibuffer[..4 * count],
735 );
736 }
737 DynamicImage::ImageRgb8(buf) => {
738 CicpTransform::expand_rgb(
739 &buf.inner_pixels()[3 * start_idx..3 * end_idx],
740 &mut ibuffer[..3 * count],
741 );
742 }
743 DynamicImage::ImageRgba8(buf) => {
744 CicpTransform::expand_rgba(
745 &buf.inner_pixels()[4 * start_idx..4 * end_idx],
746 &mut ibuffer[..4 * count],
747 );
748 }
749 DynamicImage::ImageLuma16(buf) => {
750 CicpTransform::expand_luma_rgb(
751 &buf.inner_pixels()[start_idx..end_idx],
752 &mut ibuffer[..3 * count],
753 );
754 }
755 DynamicImage::ImageLumaA16(buf) => {
756 CicpTransform::expand_luma_rgba(
757 &buf.inner_pixels()[2 * start_idx..2 * end_idx],
758 &mut ibuffer[..4 * count],
759 );
760 }
761 DynamicImage::ImageRgb16(buf) => {
762 CicpTransform::expand_rgb(
763 &buf.inner_pixels()[3 * start_idx..3 * end_idx],
764 &mut ibuffer[..3 * count],
765 );
766 }
767
768 DynamicImage::ImageRgba16(buf) => {
769 CicpTransform::expand_rgba(
770 &buf.inner_pixels()[4 * start_idx..4 * end_idx],
771 &mut ibuffer[..4 * count],
772 );
773 }
774 DynamicImage::ImageRgb32F(buf) => {
775 CicpTransform::expand_rgb(
776 &buf.inner_pixels()[3 * start_idx..3 * end_idx],
777 &mut ibuffer[..3 * count],
778 );
779 }
780 DynamicImage::ImageRgba32F(buf) => {
781 CicpTransform::expand_rgba(
782 &buf.inner_pixels()[4 * start_idx..4 * end_idx],
783 &mut ibuffer[..4 * count],
784 );
785 }
786 }
787
788 let islice = &ibuffer[..input_samples * count];
789 let oslice = &mut obuffer[..output_samples * count];
790
791 inner_transform(islice, oslice);
792
793 match lhs {
794 DynamicImage::ImageLuma8(buf) => {
795 CicpTransform::clamp_rgb_luma(
796 &obuffer[..3 * count],
797 &mut buf.inner_pixels_mut()[start_idx..end_idx],
798 self.output_coefs,
799 );
800 }
801 DynamicImage::ImageLumaA8(buf) => {
802 CicpTransform::clamp_rgba_luma(
803 &obuffer[..4 * count],
804 &mut buf.inner_pixels_mut()[2 * start_idx..2 * end_idx],
805 self.output_coefs,
806 );
807 }
808 DynamicImage::ImageRgb8(buf) => {
809 CicpTransform::clamp_rgb(
810 &obuffer[..3 * count],
811 &mut buf.inner_pixels_mut()[3 * start_idx..3 * end_idx],
812 );
813 }
814 DynamicImage::ImageRgba8(buf) => {
815 CicpTransform::clamp_rgba(
816 &obuffer[..4 * count],
817 &mut buf.inner_pixels_mut()[4 * start_idx..4 * end_idx],
818 );
819 }
820 DynamicImage::ImageLuma16(buf) => {
821 CicpTransform::clamp_rgb_luma(
822 &obuffer[..3 * count],
823 &mut buf.inner_pixels_mut()[start_idx..end_idx],
824 self.output_coefs,
825 );
826 }
827 DynamicImage::ImageLumaA16(buf) => {
828 CicpTransform::clamp_rgba_luma(
829 &obuffer[..4 * count],
830 &mut buf.inner_pixels_mut()[2 * start_idx..2 * end_idx],
831 self.output_coefs,
832 );
833 }
834 DynamicImage::ImageRgb16(buf) => {
835 CicpTransform::clamp_rgba(
836 &obuffer[..3 * count],
837 &mut buf.inner_pixels_mut()[3 * start_idx..3 * end_idx],
838 );
839 }
840
841 DynamicImage::ImageRgba16(buf) => {
842 CicpTransform::clamp_rgba(
843 &obuffer[..4 * count],
844 &mut buf.inner_pixels_mut()[4 * start_idx..4 * end_idx],
845 );
846 }
847 DynamicImage::ImageRgb32F(buf) => {
848 CicpTransform::clamp_rgb(
849 &obuffer[..3 * count],
850 &mut buf.inner_pixels_mut()[3 * start_idx..3 * end_idx],
851 );
852 }
853 DynamicImage::ImageRgba32F(buf) => {
854 CicpTransform::clamp_rgba(
855 &obuffer[..4 * count],
856 &mut buf.inner_pixels_mut()[4 * start_idx..4 * end_idx],
857 );
858 }
859 }
860 }
861 }
862
863 pub(crate) fn select_transform_u8<P: SealedPixelWithColorType<TransformableSubpixel = u8>>(
870 &self,
871 into: LayoutWithColor,
872 ) -> &Arc<CicpApplicable<'static, u8>> {
873 self.u8.select_transform::<P>(into)
874 }
875
876 pub(crate) fn select_transform_u16<O: SealedPixelWithColorType<TransformableSubpixel = u16>>(
877 &self,
878 into: LayoutWithColor,
879 ) -> &Arc<CicpApplicable<'static, u16>> {
880 self.u16.select_transform::<O>(into)
881 }
882
883 pub(crate) fn select_transform_f32<O: SealedPixelWithColorType<TransformableSubpixel = f32>>(
884 &self,
885 into: LayoutWithColor,
886 ) -> &Arc<CicpApplicable<'static, f32>> {
887 self.f32.select_transform::<O>(into)
888 }
889
890 const LAYOUTS: [(LayoutWithColor, LayoutWithColor); 4] = [
891 (LayoutWithColor::Rgb, LayoutWithColor::Rgb),
892 (LayoutWithColor::Rgb, LayoutWithColor::Rgba),
893 (LayoutWithColor::Rgba, LayoutWithColor::Rgb),
894 (LayoutWithColor::Rgba, LayoutWithColor::Rgba),
895 ];
896
897 pub(crate) fn expand_luma_rgb<P: ColorComponentForCicp>(luma: &[P], rgb: &mut [f32]) {
898 for (&pix, rgb) in luma.iter().zip(rgb.as_chunks_mut::<3>().0.iter_mut()) {
899 let luma = pix.expand_to_f32();
900 rgb[0] = luma;
901 rgb[1] = luma;
902 rgb[2] = luma;
903 }
904 }
905
906 pub(crate) fn expand_luma_rgba<P: ColorComponentForCicp>(luma: &[P], rgb: &mut [f32]) {
907 let luma_chunks = luma.as_chunks::<2>().0.iter();
908 let rgb_chunks = rgb.as_chunks_mut::<4>().0.iter_mut();
909 for (pix, rgb) in luma_chunks.zip(rgb_chunks) {
910 let luma = pix[0].expand_to_f32();
911 rgb[0] = luma;
912 rgb[1] = luma;
913 rgb[2] = luma;
914 rgb[3] = pix[1].expand_to_f32();
915 }
916 }
917
918 pub(crate) fn expand_rgb<P: ColorComponentForCicp>(input: &[P], output: &mut [f32]) {
919 for (&component, val) in input.iter().zip(output) {
920 *val = component.expand_to_f32();
921 }
922 }
923
924 pub(crate) fn expand_rgba<P: ColorComponentForCicp>(input: &[P], output: &mut [f32]) {
925 for (&component, val) in input.iter().zip(output) {
926 *val = component.expand_to_f32();
927 }
928 }
929
930 pub(crate) fn clamp_rgb<P: ColorComponentForCicp>(input: &[f32], output: &mut [P]) {
931 for (&component, val) in input.iter().zip(output) {
933 *val = P::clamp_from_f32(component);
934 }
935 }
936
937 pub(crate) fn clamp_rgba<P: ColorComponentForCicp>(input: &[f32], output: &mut [P]) {
938 for (&component, val) in input.iter().zip(output) {
939 *val = P::clamp_from_f32(component);
940 }
941 }
942
943 pub(crate) fn clamp_rgb_luma<P: ColorComponentForCicp>(
944 input: &[f32],
945 output: &mut [P],
946 coef: [f32; 3],
947 ) {
948 for (rgb, pix) in input.as_chunks::<3>().0.iter().zip(output) {
949 let mut luma = 0.0;
950
951 for (&component, coef) in rgb.iter().zip(coef) {
952 luma = multiply_accumulate(luma, component, coef);
953 }
954
955 *pix = P::clamp_from_f32(luma);
956 }
957 }
958
959 pub(crate) fn clamp_rgba_luma<P: ColorComponentForCicp>(
960 input: &[f32],
961 output: &mut [P],
962 coef: [f32; 3],
963 ) {
964 let input_chunks = input.as_chunks::<4>().0.iter();
965 let output_chunks = output.as_chunks_mut::<2>().0.iter_mut();
966 for (rgba, pix) in input_chunks.zip(output_chunks) {
967 let mut luma = 0.0;
968
969 for (&component, coef) in rgba[..3].iter().zip(coef) {
970 luma = multiply_accumulate(luma, component, coef);
971 }
972
973 pix[0] = P::clamp_from_f32(luma);
974 pix[1] = P::clamp_from_f32(rgba[3]);
975 }
976 }
977}
978
979impl CicpRgb {
980 pub(crate) fn cast_pixels<FromColor, IntoColor>(
984 &self,
985 buffer: &[FromColor::Subpixel],
986 color_space_fallback: &dyn Fn() -> [f32; 3],
989 ) -> Vec<IntoColor::Subpixel>
990 where
991 FromColor: Pixel + SealedPixelWithColorType<TransformableSubpixel = FromColor::Subpixel>,
992 IntoColor: Pixel,
993 IntoColor: CicpPixelCast<FromColor>,
994 FromColor::Subpixel: ColorComponentForCicp,
995 IntoColor::Subpixel: ColorComponentForCicp + FromPrimitive<FromColor::Subpixel>,
996 {
997 use crate::traits::private::PrivateToken;
998 let from_layout = <FromColor as SealedPixelWithColorType>::layout(PrivateToken);
999 let into_layout = <IntoColor as SealedPixelWithColorType>::layout(PrivateToken);
1000 self.cast_pixels_by_layout(buffer, color_space_fallback, from_layout, into_layout)
1005 }
1006
1007 fn cast_pixels_by_layout<FromSubpixel, IntoSubpixel>(
1008 &self,
1009 buffer: &[FromSubpixel],
1010 color_space_fallback: &dyn Fn() -> [f32; 3],
1013 from_layout: LayoutWithColor,
1014 into_layout: LayoutWithColor,
1015 ) -> Vec<IntoSubpixel>
1016 where
1017 FromSubpixel: ColorComponentForCicp + Primitive,
1018 IntoSubpixel: ColorComponentForCicp + FromPrimitive<FromSubpixel> + Primitive,
1019 {
1020 let mut output = match self.cast_pixels_from_subpixels(buffer, from_layout, into_layout) {
1021 Ok(ok) => return ok,
1022 Err(buffer) => buffer,
1023 };
1024
1025 let color_space_coefs = self
1027 .derived_luminance()
1028 .unwrap_or_else(color_space_fallback);
1032
1033 let pixels = buffer.len() / from_layout.channels();
1034
1035 Self::create_output::<IntoSubpixel>(&mut output, pixels, into_layout);
1039
1040 Self::cast_pixels_by_fallback(
1041 buffer,
1042 output.as_mut_slice(),
1043 from_layout,
1044 into_layout,
1045 color_space_coefs,
1046 );
1047
1048 output
1049 }
1050
1051 fn create_output<Into: Primitive>(
1052 output: &mut Vec<Into>,
1053 pixels: usize,
1054 into_layout: LayoutWithColor,
1055 ) {
1056 output.resize(pixels * into_layout.channels(), Into::DEFAULT_MIN_VALUE);
1057 }
1058
1059 fn cast_pixels_by_fallback<
1060 From: Primitive + ColorComponentForCicp,
1061 Into: ColorComponentForCicp,
1062 >(
1063 buffer: &[From],
1064 output: &mut [Into],
1065 from_layout: LayoutWithColor,
1066 into_layout: LayoutWithColor,
1067 color_space_coefs: [f32; 3],
1068 ) {
1069 use LayoutWithColor as Layout;
1070
1071 const STEP: usize = 256;
1072 let pixels = buffer.len() / from_layout.channels();
1073
1074 let mut ibuffer = [0.0f32; 4 * STEP];
1075 let mut obuffer = [0.0f32; 4 * STEP];
1076
1077 let ibuf_step = match from_layout {
1078 Layout::Rgb | Layout::Luma => 3,
1079 Layout::Rgba | Layout::LumaAlpha => 4,
1080 };
1081
1082 let obuf_step = match into_layout {
1083 Layout::Rgb | Layout::Luma => 3,
1084 Layout::Rgba | Layout::LumaAlpha => 4,
1085 };
1086
1087 for start_idx in (0..pixels).step_by(STEP) {
1088 let end_idx = (start_idx + STEP).min(pixels);
1089 let count = end_idx - start_idx;
1090
1091 let ibuffer = &mut ibuffer[..ibuf_step * count];
1092
1093 match from_layout {
1094 Layout::Rgb => {
1095 CicpTransform::expand_rgb(&buffer[3 * start_idx..3 * end_idx], ibuffer)
1096 }
1097 Layout::Rgba => {
1098 CicpTransform::expand_rgba(&buffer[4 * start_idx..4 * end_idx], ibuffer)
1099 }
1100 Layout::Luma => {
1101 CicpTransform::expand_luma_rgb(&buffer[start_idx..end_idx], ibuffer)
1102 }
1103 Layout::LumaAlpha => {
1104 CicpTransform::expand_luma_rgba(&buffer[2 * start_idx..2 * end_idx], ibuffer)
1105 }
1106 }
1107
1108 let obuffer = match (ibuf_step, obuf_step) {
1112 (3, 4) => {
1113 let ibuffer_chunks = ibuffer.as_chunks::<3>().0.iter();
1114 let obuffer_chunks = obuffer.as_chunks_mut::<4>().0.iter_mut();
1115 for (rgb, rgba) in ibuffer_chunks.zip(obuffer_chunks).take(count) {
1116 rgba[0] = rgb[0];
1117 rgba[1] = rgb[1];
1118 rgba[2] = rgb[2];
1119 rgba[3] = 1.0;
1120 }
1121
1122 &obuffer[..4 * count]
1123 }
1124 (4, 3) => {
1125 let ibuffer_chunks = ibuffer.as_chunks::<4>().0.iter();
1126 let obuffer_chunks = obuffer.as_chunks_mut::<3>().0.iter_mut();
1127 for (rgba, rgb) in ibuffer_chunks.zip(obuffer_chunks).take(count) {
1128 rgb[0] = rgba[0];
1129 rgb[1] = rgba[1];
1130 rgb[2] = rgba[2];
1131 }
1132
1133 &obuffer[..3 * count]
1134 }
1135 (n, m) => {
1136 debug_assert_eq!(n, m);
1137 &ibuffer[..m * count]
1138 }
1139 };
1140
1141 match into_layout {
1142 Layout::Rgb => {
1143 CicpTransform::clamp_rgb(obuffer, &mut output[3 * start_idx..3 * end_idx]);
1144 }
1145 Layout::Rgba => {
1146 CicpTransform::clamp_rgba(obuffer, &mut output[4 * start_idx..4 * end_idx]);
1147 }
1148 Layout::Luma => {
1149 CicpTransform::clamp_rgb_luma(
1150 obuffer,
1151 &mut output[start_idx..end_idx],
1152 color_space_coefs,
1153 );
1154 }
1155 Layout::LumaAlpha => {
1156 CicpTransform::clamp_rgba_luma(
1157 obuffer,
1158 &mut output[2 * start_idx..2 * end_idx],
1159 color_space_coefs,
1160 );
1161 }
1162 }
1163 }
1164 }
1165
1166 pub(crate) fn cast_pixels_from_subpixels<FromSubpixel, IntoSubpixel>(
1169 &self,
1170 buffer: &[FromSubpixel],
1171 from_layout: LayoutWithColor,
1172 into_layout: LayoutWithColor,
1173 ) -> Result<Vec<IntoSubpixel>, Vec<IntoSubpixel>>
1174 where
1175 FromSubpixel: ColorComponentForCicp,
1176 IntoSubpixel: ColorComponentForCicp + FromPrimitive<FromSubpixel> + Primitive,
1177 {
1178 use crate::traits::private::LayoutWithColor as Layout;
1179
1180 assert!(buffer.len().is_multiple_of(from_layout.channels()));
1181 let pixels = buffer.len() / from_layout.channels();
1182
1183 let mut output: Vec<IntoSubpixel> = vec_try_with_capacity(pixels * into_layout.channels())
1184 .expect("input layout already allocated with appropriate layout");
1187
1188 let map_channel = <IntoSubpixel as FromPrimitive<FromSubpixel>>::from_primitive;
1202
1203 match (from_layout, into_layout) {
1204 (Layout::Rgb, Layout::Rgb)
1206 | (Layout::Rgba, Layout::Rgba)
1207 | (Layout::Luma, Layout::Luma)
1208 | (Layout::LumaAlpha, Layout::LumaAlpha) => {
1209 output.extend(buffer.iter().copied().map(map_channel));
1212 }
1213 (Layout::Rgb, Layout::Rgba) => {
1214 Self::subpixel_cast_rgb_to_rgba(&mut output, buffer);
1215 }
1216 (Layout::Rgba, Layout::Rgb) => {
1217 Self::subpixel_cast_rgba_to_rgb(&mut output, buffer);
1218 }
1219 (Layout::Luma, Layout::LumaAlpha) => {
1220 Self::subpixel_cast_luma_to_luma_alpha(&mut output, buffer);
1221 }
1222 (Layout::LumaAlpha, Layout::Luma) => {
1223 Self::subpixel_cast_luma_alpha_to_luma(&mut output, buffer);
1224 }
1225 _ => return Err(output),
1226 }
1227
1228 Ok(output)
1229 }
1230
1231 fn subpixel_cast_rgb_to_rgba<FromSubpixel, IntoSubpixel>(
1232 output: &mut Vec<IntoSubpixel>,
1233 buffer: &[FromSubpixel],
1234 ) where
1235 FromSubpixel: ColorComponentForCicp,
1236 IntoSubpixel: ColorComponentForCicp + FromPrimitive<FromSubpixel> + Primitive,
1237 {
1238 let pixels = buffer.len() / LayoutWithColor::Rgb.channels();
1239 Self::create_output::<IntoSubpixel>(output, pixels, LayoutWithColor::Rgba);
1240
1241 let map_channel = <IntoSubpixel as FromPrimitive<FromSubpixel>>::from_primitive;
1242 let default_alpha = <IntoSubpixel as Primitive>::DEFAULT_MAX_VALUE;
1243
1244 let buffer_chunks = buffer.as_chunks::<3>().0;
1245 let output_chunks = output.as_chunks_mut::<4>().0;
1246
1247 for (&[r, g, b], out) in buffer_chunks.iter().zip(output_chunks) {
1248 *out = [
1249 map_channel(r),
1250 map_channel(g),
1251 map_channel(b),
1252 default_alpha,
1253 ];
1254 }
1255 }
1256
1257 fn subpixel_cast_rgba_to_rgb<FromSubpixel, IntoSubpixel>(
1258 output: &mut Vec<IntoSubpixel>,
1259 buffer: &[FromSubpixel],
1260 ) where
1261 FromSubpixel: ColorComponentForCicp,
1262 IntoSubpixel: ColorComponentForCicp + FromPrimitive<FromSubpixel> + Primitive,
1263 {
1264 let pixels = buffer.len() / LayoutWithColor::Rgba.channels();
1265 Self::create_output::<IntoSubpixel>(output, pixels, LayoutWithColor::Rgb);
1266
1267 let map_channel = <IntoSubpixel as FromPrimitive<FromSubpixel>>::from_primitive;
1268
1269 let buffer_chunks = buffer.as_chunks::<4>().0;
1270 let output_chunks = output.as_chunks_mut::<3>().0;
1271
1272 for (&[r, g, b, _], out) in buffer_chunks.iter().zip(output_chunks) {
1273 *out = [map_channel(r), map_channel(g), map_channel(b)];
1274 }
1275 }
1276
1277 fn subpixel_cast_luma_to_luma_alpha<FromSubpixel, IntoSubpixel>(
1280 output: &mut Vec<IntoSubpixel>,
1281 buffer: &[FromSubpixel],
1282 ) where
1283 FromSubpixel: ColorComponentForCicp,
1284 IntoSubpixel: ColorComponentForCicp + FromPrimitive<FromSubpixel> + Primitive,
1285 {
1286 let map_channel = <IntoSubpixel as FromPrimitive<FromSubpixel>>::from_primitive;
1287
1288 output.extend(buffer.iter().copied().flat_map(|l| {
1289 [
1290 map_channel(l),
1291 <IntoSubpixel as Primitive>::DEFAULT_MAX_VALUE,
1294 ]
1295 }));
1296 }
1297
1298 fn subpixel_cast_luma_alpha_to_luma<FromSubpixel, IntoSubpixel>(
1299 output: &mut Vec<IntoSubpixel>,
1300 buffer: &[FromSubpixel],
1301 ) where
1302 FromSubpixel: ColorComponentForCicp,
1303 IntoSubpixel: ColorComponentForCicp + FromPrimitive<FromSubpixel> + Primitive,
1304 {
1305 let map_channel = <IntoSubpixel as FromPrimitive<FromSubpixel>>::from_primitive;
1306
1307 let buffer_chunks = buffer.as_chunks::<2>().0;
1308
1309 output.extend(buffer_chunks.iter().map(|&[l, _]| map_channel(l)));
1310 }
1311}
1312
1313pub(crate) trait CicpPixelCast<FromColor>
1321where
1322 Self: Pixel + SealedPixelWithColorType<TransformableSubpixel = <Self as Pixel>::Subpixel>,
1325 FromColor:
1326 Pixel + SealedPixelWithColorType<TransformableSubpixel = <FromColor as Pixel>::Subpixel>,
1327 Self::Subpixel: ColorComponentForCicp + FromPrimitive<FromColor::Subpixel>,
1328 FromColor::Subpixel: ColorComponentForCicp,
1329{
1330}
1331
1332impl<FromColor, IntoColor> CicpPixelCast<FromColor> for IntoColor
1333where
1334 IntoColor: Pixel + SealedPixelWithColorType<TransformableSubpixel = IntoColor::Subpixel>,
1335 FromColor: Pixel + SealedPixelWithColorType<TransformableSubpixel = FromColor::Subpixel>,
1336 IntoColor::Subpixel: ColorComponentForCicp + FromPrimitive<FromColor::Subpixel>,
1337 FromColor::Subpixel: ColorComponentForCicp,
1338{
1339}
1340
1341pub(crate) trait ColorComponentForCicp: Copy {
1342 fn expand_to_f32(self) -> f32;
1343
1344 fn clamp_from_f32(val: f32) -> Self;
1345}
1346
1347impl ColorComponentForCicp for u8 {
1348 fn expand_to_f32(self) -> f32 {
1349 const R: f32 = 1.0 / u8::MAX as f32;
1350 self as f32 * R
1351 }
1352
1353 #[inline]
1354 fn clamp_from_f32(val: f32) -> Self {
1355 (val * Self::MAX as f32).round() as u8
1357 }
1358}
1359
1360impl ColorComponentForCicp for u16 {
1361 fn expand_to_f32(self) -> f32 {
1362 const R: f32 = 1.0 / u16::MAX as f32;
1363 self as f32 * R
1364 }
1365
1366 #[inline]
1367 fn clamp_from_f32(val: f32) -> Self {
1368 (val * Self::MAX as f32).round() as u16
1370 }
1371}
1372
1373impl ColorComponentForCicp for f32 {
1374 fn expand_to_f32(self) -> f32 {
1375 self
1376 }
1377
1378 fn clamp_from_f32(val: f32) -> Self {
1379 val
1380 }
1381}
1382
1383impl<P> RgbTransforms<P> {
1384 fn select_transform<O: SealedPixelWithColorType>(
1385 &self,
1386 into: LayoutWithColor,
1387 ) -> &Arc<CicpApplicable<'static, P>> {
1388 use crate::traits::private::{LayoutWithColor as Layout, PrivateToken};
1389 let from = O::layout(PrivateToken);
1390
1391 match (from, into) {
1392 (Layout::Rgb, Layout::Rgb) => &self.slices[0],
1393 (Layout::Rgb, Layout::Rgba) => &self.slices[1],
1394 (Layout::Rgba, Layout::Rgb) => &self.slices[2],
1395 (Layout::Rgba, Layout::Rgba) => &self.slices[3],
1396 (Layout::Rgb, Layout::Luma) => &self.rgb_luma[0],
1397 (Layout::Rgb, Layout::LumaAlpha) => &self.rgb_luma[1],
1398 (Layout::Rgba, Layout::Luma) => &self.rgb_luma[2],
1399 (Layout::Rgba, Layout::LumaAlpha) => &self.rgb_luma[3],
1400 (Layout::Luma, Layout::Rgb) => &self.luma_rgb[0],
1401 (Layout::Luma, Layout::Rgba) => &self.luma_rgb[1],
1402 (Layout::LumaAlpha, Layout::Rgb) => &self.luma_rgb[2],
1403 (Layout::LumaAlpha, Layout::Rgba) => &self.luma_rgb[3],
1404 (Layout::Luma, Layout::Luma) => &self.luma_luma[0],
1405 (Layout::Luma, Layout::LumaAlpha) => &self.luma_luma[1],
1406 (Layout::LumaAlpha, Layout::Luma) => &self.luma_luma[2],
1407 (Layout::LumaAlpha, Layout::LumaAlpha) => &self.luma_luma[3],
1408 }
1409 }
1410}
1411
1412impl Cicp {
1413 pub const SRGB: Self = Cicp {
1415 primaries: CicpColorPrimaries::SRgb,
1416 transfer: CicpTransferCharacteristics::SRgb,
1417 matrix: CicpMatrixCoefficients::Identity,
1418 full_range: CicpVideoFullRangeFlag::FullRange,
1419 };
1420
1421 pub const SRGB_LINEAR: Self = Cicp {
1423 primaries: CicpColorPrimaries::SRgb,
1424 transfer: CicpTransferCharacteristics::Linear,
1425 matrix: CicpMatrixCoefficients::Identity,
1426 full_range: CicpVideoFullRangeFlag::FullRange,
1427 };
1428
1429 pub const DISPLAY_P3: Self = Cicp {
1435 primaries: CicpColorPrimaries::SmpteRp432,
1436 transfer: CicpTransferCharacteristics::SRgb,
1437 matrix: CicpMatrixCoefficients::Identity,
1438 full_range: CicpVideoFullRangeFlag::FullRange,
1439 };
1440
1441 fn to_moxcms_compute_profile(self) -> Option<ColorProfile> {
1472 let mut rgb = moxcms::ColorProfile::new_srgb();
1473
1474 rgb.update_rgb_colorimetry_from_cicp(moxcms::CicpProfile {
1475 color_primaries: self.primaries.to_moxcms(),
1476 transfer_characteristics: self.transfer.to_moxcms(),
1477 matrix_coefficients: self.matrix.to_moxcms()?,
1478 full_range: match self.full_range {
1479 CicpVideoFullRangeFlag::NarrowRange => false,
1480 CicpVideoFullRangeFlag::FullRange => true,
1481 },
1482 });
1483
1484 Some(ColorProfile { rgb })
1485 }
1486
1487 pub(crate) const fn qualify_stability(&self) -> bool {
1500 const _: () = {
1501 assert!(Cicp::SRGB.qualify_stability());
1503 assert!(Cicp::SRGB_LINEAR.qualify_stability());
1504 assert!(Cicp::DISPLAY_P3.qualify_stability());
1505 };
1506
1507 matches!(self.full_range, CicpVideoFullRangeFlag::FullRange)
1508 && matches!(
1509 self.matrix,
1510 CicpMatrixCoefficients::Identity
1512 | CicpMatrixCoefficients::ChromaticityDerivedNonConstant
1514 )
1515 && matches!(
1516 self.primaries,
1517 CicpColorPrimaries::SRgb
1518 | CicpColorPrimaries::SmpteRp431
1519 | CicpColorPrimaries::SmpteRp432
1520 | CicpColorPrimaries::Bt601
1521 | CicpColorPrimaries::Rgb240m
1522 )
1523 && matches!(
1524 self.transfer,
1525 CicpTransferCharacteristics::SRgb
1526 | CicpTransferCharacteristics::Bt709
1527 | CicpTransferCharacteristics::Bt601
1528 | CicpTransferCharacteristics::Linear
1529 )
1530 }
1531
1532 pub(crate) const fn into_rgb(self) -> CicpRgb {
1534 CicpRgb {
1535 primaries: self.primaries,
1536 transfer: self.transfer,
1537 luminance: DerivedLuminance::NonConstant,
1544 }
1545 }
1546
1547 pub(crate) fn try_into_rgb(self) -> Result<CicpRgb, ImageError> {
1548 if Cicp::from(self.into_rgb()) != self {
1549 Err(ImageError::Parameter(ParameterError::from_kind(
1550 ParameterErrorKind::RgbCicpRequired(self),
1551 )))
1552 } else {
1553 Ok(self.into_rgb())
1554 }
1555 }
1556}
1557
1558impl CicpRgb {
1559 pub(crate) fn derived_luminance(&self) -> Option<[f32; 3]> {
1563 let primaries = match self.primaries {
1564 CicpColorPrimaries::SRgb => moxcms::ColorPrimaries::BT_709,
1565 CicpColorPrimaries::RgbM => moxcms::ColorPrimaries::BT_470M,
1566 CicpColorPrimaries::RgbB => moxcms::ColorPrimaries::BT_470BG,
1567 CicpColorPrimaries::Bt601 => moxcms::ColorPrimaries::BT_601,
1568 CicpColorPrimaries::Rgb240m => moxcms::ColorPrimaries::SMPTE_240,
1569 CicpColorPrimaries::GenericFilm => moxcms::ColorPrimaries::GENERIC_FILM,
1570 CicpColorPrimaries::Rgb2020 => moxcms::ColorPrimaries::BT_2020,
1571 CicpColorPrimaries::Xyz => moxcms::ColorPrimaries::XYZ,
1572 CicpColorPrimaries::SmpteRp431 => moxcms::ColorPrimaries::DISPLAY_P3,
1573 CicpColorPrimaries::SmpteRp432 => moxcms::ColorPrimaries::DISPLAY_P3,
1574 CicpColorPrimaries::Industry22 => moxcms::ColorPrimaries::EBU_3213,
1575 CicpColorPrimaries::Unspecified => return None,
1576 };
1577
1578 const ILLUMINANT_C: moxcms::Chromaticity = moxcms::Chromaticity::new(0.310, 0.316);
1579
1580 let whitepoint = match self.primaries {
1581 CicpColorPrimaries::SRgb => moxcms::Chromaticity::D65,
1582 CicpColorPrimaries::RgbM => ILLUMINANT_C,
1583 CicpColorPrimaries::RgbB => moxcms::Chromaticity::D65,
1584 CicpColorPrimaries::Bt601 => moxcms::Chromaticity::D65,
1585 CicpColorPrimaries::Rgb240m => moxcms::Chromaticity::D65,
1586 CicpColorPrimaries::GenericFilm => ILLUMINANT_C,
1587 CicpColorPrimaries::Rgb2020 => moxcms::Chromaticity::D65,
1588 CicpColorPrimaries::Xyz => moxcms::Chromaticity::new(1. / 3., 1. / 3.),
1589 CicpColorPrimaries::SmpteRp431 => moxcms::Chromaticity::new(0.314, 0.351),
1590 CicpColorPrimaries::SmpteRp432 => moxcms::Chromaticity::D65,
1591 CicpColorPrimaries::Industry22 => moxcms::Chromaticity::D65,
1592 CicpColorPrimaries::Unspecified => return None,
1593 };
1594
1595 let matrix = primaries.transform_to_xyz(whitepoint);
1596
1597 Some(matrix.v[1])
1599 }
1600}
1601
1602impl From<CicpRgb> for Cicp {
1603 fn from(cicp: CicpRgb) -> Self {
1604 Cicp {
1605 primaries: cicp.primaries,
1606 transfer: cicp.transfer,
1607 matrix: CicpMatrixCoefficients::Identity,
1608 full_range: CicpVideoFullRangeFlag::FullRange,
1609 }
1610 }
1611}
1612
1613struct ColorProfile {
1621 rgb: moxcms::ColorProfile,
1622}
1623
1624impl ColorProfile {
1625 fn map_layout(&self, layout: LayoutWithColor) -> (&moxcms::ColorProfile, moxcms::Layout) {
1626 match layout {
1627 LayoutWithColor::Rgb => (&self.rgb, moxcms::Layout::Rgb),
1628 LayoutWithColor::Rgba => (&self.rgb, moxcms::Layout::Rgba),
1629 LayoutWithColor::Luma | LayoutWithColor::LumaAlpha => unreachable!(),
1631 }
1632 }
1633}
1634
1635#[cfg(test)]
1636#[test]
1637fn moxcms() {
1638 let l = moxcms::TransferCharacteristics::Linear;
1639 assert_eq!(l.linearize(1.0), 1.0);
1640 assert_eq!(l.gamma(1.0), 1.0);
1641
1642 assert_eq!(l.gamma(0.5), 0.5);
1643}
1644
1645#[cfg(test)]
1646#[test]
1647fn derived_luminance() {
1648 let luminance = Cicp::SRGB.into_rgb().derived_luminance();
1649 let [kr, kg, kb] = luminance.unwrap();
1650 assert!((kr - 0.2126).abs() < 1e-4);
1651 assert!((kg - 0.7152).abs() < 1e-4);
1652 assert!((kb - 0.0722).abs() < 1e-4);
1653}
1654
1655#[cfg(test)]
1656mod tests {
1657 use super::{Cicp, CicpTransform};
1658 use crate::{Luma, LumaA, Rgb, Rgba};
1659
1660 #[test]
1661 fn can_create_transforms() {
1662 assert!(CicpTransform::new(Cicp::SRGB, Cicp::SRGB).is_some());
1663 assert!(CicpTransform::new(Cicp::SRGB, Cicp::DISPLAY_P3).is_some());
1664 assert!(CicpTransform::new(Cicp::DISPLAY_P3, Cicp::SRGB).is_some());
1665 assert!(CicpTransform::new(Cicp::DISPLAY_P3, Cicp::DISPLAY_P3).is_some());
1666 }
1667
1668 fn no_coefficient_fallback() -> [f32; 3] {
1669 panic!("Fallback coefficients required")
1670 }
1671
1672 #[test]
1673 fn transform_pixels_srgb() {
1674 let data = [255, 0, 0, 255];
1677 let color = Cicp::SRGB.into_rgb();
1678 let rgba = color.cast_pixels::<Rgba<u8>, Rgb<u8>>(&data, &no_coefficient_fallback);
1679 assert_eq!(rgba, [255, 0, 0]);
1680 let luma = color.cast_pixels::<Rgba<u8>, Luma<u8>>(&data, &no_coefficient_fallback);
1681 assert_eq!(luma, [54]); let luma_a = color.cast_pixels::<Rgba<u8>, LumaA<u8>>(&data, &no_coefficient_fallback);
1683 assert_eq!(luma_a, [54, 255]);
1684 }
1685
1686 #[test]
1687 fn transform_pixels_srgb_16() {
1688 let data = [u16::MAX / 2];
1691 let color = Cicp::SRGB.into_rgb();
1692 let rgba = color.cast_pixels::<Luma<u16>, Rgb<u8>>(&data, &no_coefficient_fallback);
1693 assert_eq!(rgba, [127; 3]);
1694 let luma = color.cast_pixels::<Luma<u16>, Luma<u8>>(&data, &no_coefficient_fallback);
1695 assert_eq!(luma, [127]);
1696 let luma_a = color.cast_pixels::<Luma<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1697 assert_eq!(luma_a, [127, 255]);
1698
1699 let data = [u16::MAX / 2 + 1];
1700 let color = Cicp::SRGB.into_rgb();
1701 let rgba = color.cast_pixels::<Luma<u16>, Rgb<u8>>(&data, &no_coefficient_fallback);
1702 assert_eq!(rgba, [128; 3]);
1703 let luma = color.cast_pixels::<Luma<u16>, Luma<u8>>(&data, &no_coefficient_fallback);
1704 assert_eq!(luma, [128]);
1705 let luma_a = color.cast_pixels::<Luma<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1706 assert_eq!(luma_a, [128, 255]);
1707 }
1708
1709 #[test]
1710 fn transform_pixels_srgb_luma_alpha() {
1711 let data = [u16::MAX / 2, u16::MAX];
1714 let color = Cicp::SRGB.into_rgb();
1715 let rgba = color.cast_pixels::<LumaA<u16>, Rgb<u8>>(&data, &no_coefficient_fallback);
1716 assert_eq!(rgba, [127; 3]);
1717 let luma = color.cast_pixels::<LumaA<u16>, Luma<u8>>(&data, &no_coefficient_fallback);
1718 assert_eq!(luma, [127]);
1719 let luma = color.cast_pixels::<LumaA<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1720 assert_eq!(luma, [127, u8::MAX]);
1721 let luma_a = color.cast_pixels::<LumaA<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1722 assert_eq!(luma_a, [127, 255]);
1723
1724 let data = [u16::MAX / 2 + 1, u16::MAX];
1725 let color = Cicp::SRGB.into_rgb();
1726 let rgba = color.cast_pixels::<LumaA<u16>, Rgb<u8>>(&data, &no_coefficient_fallback);
1727 assert_eq!(rgba, [128; 3]);
1728 let luma = color.cast_pixels::<LumaA<u16>, Luma<u8>>(&data, &no_coefficient_fallback);
1729 assert_eq!(luma, [128]);
1730 let luma = color.cast_pixels::<LumaA<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1731 assert_eq!(luma, [128, u8::MAX]);
1732 let luma_a = color.cast_pixels::<LumaA<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1733 assert_eq!(luma_a, [128, 255]);
1734 }
1735}