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 .map(Arc::<dyn moxcms::TransformExecutor<f32> + Send + Sync>::from)
371 .ok()
372 });
373
374 if try_f32.iter().any(Option::is_none) {
375 return None;
376 }
377
378 try_f32.map(Option::unwrap)
379 };
380
381 Some(CicpTransform {
383 from,
384 into,
385 u8: Self::build_transforms(
386 Self::LAYOUTS.map(|(from_layout, into_layout)| {
387 let (from, from_layout) = mox_from.map_layout(from_layout);
388 let (into, into_layout) = mox_into.map_layout(into_layout);
389
390 from.create_transform_8bit(from_layout, into, into_layout, opt)
391 .map(Arc::<dyn moxcms::TransformExecutor<_> + Send + Sync>::from)
392 .ok()
393 }),
394 f32_fallback.clone(),
395 output_coefs,
396 )?,
397 u16: Self::build_transforms(
398 Self::LAYOUTS.map(|(from_layout, into_layout)| {
399 let (from, from_layout) = mox_from.map_layout(from_layout);
400 let (into, into_layout) = mox_into.map_layout(into_layout);
401
402 from.create_transform_16bit(from_layout, into, into_layout, opt)
403 .map(Arc::<dyn moxcms::TransformExecutor<_> + Send + Sync>::from)
404 .ok()
405 }),
406 f32_fallback.clone(),
407 output_coefs,
408 )?,
409 f32: Self::build_transforms(
410 f32_fallback.clone().map(Some),
411 f32_fallback.clone(),
412 output_coefs,
413 )?,
414 output_coefs,
415 })
416 }
417
418 pub(crate) fn supported_transform_fn<From: PixelWithColorType, Into: PixelWithColorType>(
427 &self,
428 ) -> &'_ CicpApplicable<'_, From::Subpixel> {
429 use crate::traits::private::double_dispatch_transform_from_sealed;
430 double_dispatch_transform_from_sealed::<From, Into>(self)
431 }
432
433 pub(crate) fn check_applicable(&self, from: Cicp, into: Cicp) -> Result<(), ImageError> {
435 let check_expectation = |expected, found| {
436 if expected == found {
437 Ok(())
438 } else {
439 Err(ParameterError::from_kind(
440 ParameterErrorKind::CicpMismatch { expected, found },
441 ))
442 }
443 };
444
445 check_expectation(self.from, from).map_err(ImageError::Parameter)?;
446 check_expectation(self.into, into).map_err(ImageError::Parameter)?;
447
448 Ok(())
449 }
450
451 fn build_transforms<P: ColorComponentForCicp + Default + 'static>(
452 trs: [Option<Arc<dyn moxcms::TransformExecutor<P> + Send + Sync>>; 4],
453 f32: [Arc<dyn moxcms::TransformExecutor<f32> + Send + Sync>; 4],
454 output_coef: [f32; 3],
455 ) -> Option<RgbTransforms<P>> {
456 if trs.iter().any(Option::is_none) {
458 return None;
459 }
460
461 let trs = trs.map(Option::unwrap);
462
463 let slices = trs.clone().map(|tr| {
465 Arc::new(move |input: &[P], output: &mut [P]| {
466 tr.transform(input, output).expect("transform failed")
467 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>
468 });
469
470 const N: usize = 256;
471
472 let luma_rgb = {
474 let [tr33, tr34, tr43, tr44] = f32.clone();
475
476 [
477 Arc::new(move |input: &[P], output: &mut [P]| {
478 let mut ibuffer = [0.0f32; 3 * N];
479 let mut obuffer = [0.0f32; 3 * N];
480
481 for (luma, output) in input.chunks(N).zip(output.chunks_mut(3 * N)) {
482 let n = luma.len();
483 let ibuffer = &mut ibuffer[..3 * n];
484 let obuffer = &mut obuffer[..3 * n];
485 Self::expand_luma_rgb(luma, ibuffer);
486 tr33.transform(ibuffer, obuffer).expect("transform failed");
487 Self::clamp_rgb(obuffer, output);
488 }
489 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
490 Arc::new(move |input: &[P], output: &mut [P]| {
491 let mut ibuffer = [0.0f32; 3 * N];
492 let mut obuffer = [0.0f32; 4 * N];
493
494 for (luma, output) in input.chunks(N).zip(output.chunks_mut(4 * N)) {
495 let n = luma.len();
496 let ibuffer = &mut ibuffer[..3 * n];
497 let obuffer = &mut obuffer[..4 * n];
498 Self::expand_luma_rgb(luma, ibuffer);
499 tr34.transform(ibuffer, obuffer).expect("transform failed");
500 Self::clamp_rgba(obuffer, output);
501 }
502 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
503 Arc::new(move |input: &[P], output: &mut [P]| {
504 let mut ibuffer = [0.0f32; 4 * N];
505 let mut obuffer = [0.0f32; 3 * N];
506
507 for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(3 * N)) {
508 let n = luma.len() / 2;
509 let ibuffer = &mut ibuffer[..4 * n];
510 let obuffer = &mut obuffer[..3 * n];
511 Self::expand_luma_rgba(luma, ibuffer);
512 tr43.transform(ibuffer, obuffer).expect("transform failed");
513 Self::clamp_rgb(obuffer, output);
514 }
515 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
516 Arc::new(move |input: &[P], output: &mut [P]| {
517 let mut ibuffer = [0.0f32; 4 * N];
518 let mut obuffer = [0.0f32; 4 * N];
519
520 for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(4 * N)) {
521 let n = luma.len() / 2;
522 let ibuffer = &mut ibuffer[..4 * n];
523 let obuffer = &mut obuffer[..4 * n];
524 Self::expand_luma_rgba(luma, ibuffer);
525 tr44.transform(ibuffer, obuffer).expect("transform failed");
526 Self::clamp_rgba(obuffer, output);
527 }
528 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
529 ]
530 };
531
532 let rgb_luma = {
534 let [tr33, tr34, tr43, tr44] = f32.clone();
535
536 [
537 Arc::new(move |input: &[P], output: &mut [P]| {
538 debug_assert_eq!(input.len() / 3, output.len());
539
540 let mut ibuffer = [0.0f32; 3 * N];
541 let mut obuffer = [0.0f32; 3 * N];
542
543 for (rgb, output) in input.chunks(3 * N).zip(output.chunks_mut(N)) {
544 let n = output.len();
545 let ibuffer = &mut ibuffer[..3 * n];
546 let obuffer = &mut obuffer[..3 * n];
547 Self::expand_rgb(rgb, ibuffer);
548 tr33.transform(ibuffer, obuffer).expect("transform failed");
549 Self::clamp_rgb_luma(obuffer, output, output_coef);
550 }
551 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
552 Arc::new(move |input: &[P], output: &mut [P]| {
553 debug_assert_eq!(input.len() / 3, output.len() / 2);
554
555 let mut ibuffer = [0.0f32; 3 * N];
556 let mut obuffer = [0.0f32; 4 * N];
557
558 for (rgb, output) in input.chunks(4 * N).zip(output.chunks_mut(2 * N)) {
559 let n = output.len() / 2;
560 let ibuffer = &mut ibuffer[..3 * n];
561 let obuffer = &mut obuffer[..4 * n];
562 Self::expand_rgb(rgb, ibuffer);
563 tr34.transform(ibuffer, obuffer).expect("transform failed");
564 Self::clamp_rgba_luma(obuffer, output, output_coef);
565 }
566 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
567 Arc::new(move |input: &[P], output: &mut [P]| {
568 debug_assert_eq!(input.len() / 4, output.len());
569
570 let mut ibuffer = [0.0f32; 4 * N];
571 let mut obuffer = [0.0f32; 3 * N];
572
573 for (rgba, output) in input.chunks(4 * N).zip(output.chunks_mut(N)) {
574 let n = output.len();
575 let ibuffer = &mut ibuffer[..4 * n];
576 let obuffer = &mut obuffer[..3 * n];
577 Self::expand_rgba(rgba, ibuffer);
578 tr43.transform(ibuffer, obuffer).expect("transform failed");
579 Self::clamp_rgb_luma(obuffer, output, output_coef);
580 }
581 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
582 Arc::new(move |input: &[P], output: &mut [P]| {
583 debug_assert_eq!(input.len() / 4, output.len() / 2);
584
585 let mut ibuffer = [0.0f32; 4 * N];
586 let mut obuffer = [0.0f32; 4 * N];
587
588 for (rgba, output) in input.chunks(4 * N).zip(output.chunks_mut(2 * N)) {
589 let n = output.len() / 2;
590 let ibuffer = &mut ibuffer[..4 * n];
591 let obuffer = &mut obuffer[..4 * n];
592 Self::expand_rgba(rgba, ibuffer);
593 tr44.transform(ibuffer, obuffer).expect("transform failed");
594 Self::clamp_rgba_luma(obuffer, output, output_coef);
595 }
596 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
597 ]
598 };
599
600 let luma_luma = {
602 let [tr33, tr34, tr43, tr44] = f32.clone();
603
604 [
605 Arc::new(move |input: &[P], output: &mut [P]| {
606 debug_assert_eq!(input.len(), output.len());
607 let mut ibuffer = [0.0f32; 3 * N];
608 let mut obuffer = [0.0f32; 3 * N];
609
610 for (luma, output) in input.chunks(N).zip(output.chunks_mut(N)) {
611 let n = luma.len();
612 let ibuffer = &mut ibuffer[..3 * n];
613 let obuffer = &mut obuffer[..3 * n];
614 Self::expand_luma_rgb(luma, ibuffer);
615 tr33.transform(ibuffer, obuffer).expect("transform failed");
616 Self::clamp_rgb_luma(obuffer, output, output_coef);
617 }
618 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
619 Arc::new(move |input: &[P], output: &mut [P]| {
620 debug_assert_eq!(input.len(), output.len() / 2);
621 let mut ibuffer = [0.0f32; 3 * N];
622 let mut obuffer = [0.0f32; 4 * N];
623
624 for (luma, output) in input.chunks(N).zip(output.chunks_mut(2 * N)) {
625 let n = luma.len();
626 let ibuffer = &mut ibuffer[..3 * n];
627 let obuffer = &mut obuffer[..4 * n];
628 Self::expand_luma_rgb(luma, ibuffer);
629 tr34.transform(ibuffer, obuffer).expect("transform failed");
630 Self::clamp_rgba_luma(obuffer, output, output_coef);
631 }
632 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
633 Arc::new(move |input: &[P], output: &mut [P]| {
634 debug_assert_eq!(input.len() / 2, output.len());
635 let mut ibuffer = [0.0f32; 4 * N];
636 let mut obuffer = [0.0f32; 3 * N];
637
638 for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(N)) {
639 let n = luma.len() / 2;
640 let ibuffer = &mut ibuffer[..4 * n];
641 let obuffer = &mut obuffer[..3 * n];
642 Self::expand_luma_rgba(luma, ibuffer);
643 tr43.transform(ibuffer, obuffer).expect("transform failed");
644 Self::clamp_rgb_luma(obuffer, output, output_coef);
645 }
646 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
647 Arc::new(move |input: &[P], output: &mut [P]| {
648 debug_assert_eq!(input.len() / 2, output.len() / 2);
649 let mut ibuffer = [0.0f32; 4 * N];
650 let mut obuffer = [0.0f32; 4 * N];
651
652 for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(2 * N)) {
653 let n = luma.len() / 2;
654 let ibuffer = &mut ibuffer[..4 * n];
655 let obuffer = &mut obuffer[..4 * n];
656 Self::expand_luma_rgba(luma, ibuffer);
657 tr44.transform(ibuffer, obuffer).expect("transform failed");
658 Self::clamp_rgba_luma(obuffer, output, output_coef);
659 }
660 }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>,
661 ]
662 };
663
664 Some(RgbTransforms {
665 slices,
666 luma_rgb,
667 rgb_luma,
668 luma_luma,
669 })
670 }
671
672 pub(crate) fn transform_dynamic(&self, lhs: &mut DynamicImage, rhs: &DynamicImage) {
673 const STEP: usize = 256;
674
675 let mut ibuffer = [0.0f32; 4 * STEP];
676 let mut obuffer = [0.0f32; 4 * STEP];
677
678 let pixels = (u64::from(lhs.width()) * u64::from(lhs.height())) as usize;
679
680 let input_samples;
681 let output_samples;
682
683 let inner_transform = match (
684 LayoutWithColor::from(lhs.color()),
685 LayoutWithColor::from(rhs.color()),
686 ) {
687 (
688 LayoutWithColor::Luma | LayoutWithColor::Rgb,
689 LayoutWithColor::Luma | LayoutWithColor::Rgb,
690 ) => {
691 output_samples = 3;
692 input_samples = 3;
693 &*self.f32.slices[0]
694 }
695 (
696 LayoutWithColor::LumaAlpha | LayoutWithColor::Rgba,
697 LayoutWithColor::Luma | LayoutWithColor::Rgb,
698 ) => {
699 output_samples = 4;
700 input_samples = 3;
701 &*self.f32.slices[1]
702 }
703 (
704 LayoutWithColor::Luma | LayoutWithColor::Rgb,
705 LayoutWithColor::LumaAlpha | LayoutWithColor::Rgba,
706 ) => {
707 output_samples = 3;
708 input_samples = 4;
709 &*self.f32.slices[2]
710 }
711 (
712 LayoutWithColor::LumaAlpha | LayoutWithColor::Rgba,
713 LayoutWithColor::LumaAlpha | LayoutWithColor::Rgba,
714 ) => {
715 output_samples = 4;
716 input_samples = 4;
717 &*self.f32.slices[3]
718 }
719 };
720
721 for start_idx in (0..pixels).step_by(STEP) {
722 let end_idx = (start_idx + STEP).min(pixels);
723 let count = end_idx - start_idx;
724
725 match rhs {
728 DynamicImage::ImageLuma8(buf) => {
729 CicpTransform::expand_luma_rgb(
730 &buf.inner_pixels()[start_idx..end_idx],
731 &mut ibuffer[..3 * count],
732 );
733 }
734 DynamicImage::ImageLumaA8(buf) => {
735 CicpTransform::expand_luma_rgba(
736 &buf.inner_pixels()[2 * start_idx..2 * end_idx],
737 &mut ibuffer[..4 * count],
738 );
739 }
740 DynamicImage::ImageRgb8(buf) => {
741 CicpTransform::expand_rgb(
742 &buf.inner_pixels()[3 * start_idx..3 * end_idx],
743 &mut ibuffer[..3 * count],
744 );
745 }
746 DynamicImage::ImageRgba8(buf) => {
747 CicpTransform::expand_rgba(
748 &buf.inner_pixels()[4 * start_idx..4 * end_idx],
749 &mut ibuffer[..4 * count],
750 );
751 }
752 DynamicImage::ImageLuma16(buf) => {
753 CicpTransform::expand_luma_rgb(
754 &buf.inner_pixels()[start_idx..end_idx],
755 &mut ibuffer[..3 * count],
756 );
757 }
758 DynamicImage::ImageLumaA16(buf) => {
759 CicpTransform::expand_luma_rgba(
760 &buf.inner_pixels()[2 * start_idx..2 * end_idx],
761 &mut ibuffer[..4 * count],
762 );
763 }
764 DynamicImage::ImageRgb16(buf) => {
765 CicpTransform::expand_rgb(
766 &buf.inner_pixels()[3 * start_idx..3 * end_idx],
767 &mut ibuffer[..3 * count],
768 );
769 }
770
771 DynamicImage::ImageRgba16(buf) => {
772 CicpTransform::expand_rgba(
773 &buf.inner_pixels()[4 * start_idx..4 * end_idx],
774 &mut ibuffer[..4 * count],
775 );
776 }
777 DynamicImage::ImageRgb32F(buf) => {
778 CicpTransform::expand_rgb(
779 &buf.inner_pixels()[3 * start_idx..3 * end_idx],
780 &mut ibuffer[..3 * count],
781 );
782 }
783 DynamicImage::ImageRgba32F(buf) => {
784 CicpTransform::expand_rgba(
785 &buf.inner_pixels()[4 * start_idx..4 * end_idx],
786 &mut ibuffer[..4 * count],
787 );
788 }
789 }
790
791 let islice = &ibuffer[..input_samples * count];
792 let oslice = &mut obuffer[..output_samples * count];
793
794 inner_transform(islice, oslice);
795
796 match lhs {
797 DynamicImage::ImageLuma8(buf) => {
798 CicpTransform::clamp_rgb_luma(
799 &obuffer[..3 * count],
800 &mut buf.inner_pixels_mut()[start_idx..end_idx],
801 self.output_coefs,
802 );
803 }
804 DynamicImage::ImageLumaA8(buf) => {
805 CicpTransform::clamp_rgba_luma(
806 &obuffer[..4 * count],
807 &mut buf.inner_pixels_mut()[2 * start_idx..2 * end_idx],
808 self.output_coefs,
809 );
810 }
811 DynamicImage::ImageRgb8(buf) => {
812 CicpTransform::clamp_rgb(
813 &obuffer[..3 * count],
814 &mut buf.inner_pixels_mut()[3 * start_idx..3 * end_idx],
815 );
816 }
817 DynamicImage::ImageRgba8(buf) => {
818 CicpTransform::clamp_rgba(
819 &obuffer[..4 * count],
820 &mut buf.inner_pixels_mut()[4 * start_idx..4 * end_idx],
821 );
822 }
823 DynamicImage::ImageLuma16(buf) => {
824 CicpTransform::clamp_rgb_luma(
825 &obuffer[..3 * count],
826 &mut buf.inner_pixels_mut()[start_idx..end_idx],
827 self.output_coefs,
828 );
829 }
830 DynamicImage::ImageLumaA16(buf) => {
831 CicpTransform::clamp_rgba_luma(
832 &obuffer[..4 * count],
833 &mut buf.inner_pixels_mut()[2 * start_idx..2 * end_idx],
834 self.output_coefs,
835 );
836 }
837 DynamicImage::ImageRgb16(buf) => {
838 CicpTransform::clamp_rgba(
839 &obuffer[..3 * count],
840 &mut buf.inner_pixels_mut()[3 * start_idx..3 * end_idx],
841 );
842 }
843
844 DynamicImage::ImageRgba16(buf) => {
845 CicpTransform::clamp_rgba(
846 &obuffer[..4 * count],
847 &mut buf.inner_pixels_mut()[4 * start_idx..4 * end_idx],
848 );
849 }
850 DynamicImage::ImageRgb32F(buf) => {
851 CicpTransform::clamp_rgb(
852 &obuffer[..3 * count],
853 &mut buf.inner_pixels_mut()[3 * start_idx..3 * end_idx],
854 );
855 }
856 DynamicImage::ImageRgba32F(buf) => {
857 CicpTransform::clamp_rgba(
858 &obuffer[..4 * count],
859 &mut buf.inner_pixels_mut()[4 * start_idx..4 * end_idx],
860 );
861 }
862 }
863 }
864 }
865
866 pub(crate) fn select_transform_u8<P: SealedPixelWithColorType<TransformableSubpixel = u8>>(
873 &self,
874 into: LayoutWithColor,
875 ) -> &Arc<CicpApplicable<'static, u8>> {
876 self.u8.select_transform::<P>(into)
877 }
878
879 pub(crate) fn select_transform_u16<O: SealedPixelWithColorType<TransformableSubpixel = u16>>(
880 &self,
881 into: LayoutWithColor,
882 ) -> &Arc<CicpApplicable<'static, u16>> {
883 self.u16.select_transform::<O>(into)
884 }
885
886 pub(crate) fn select_transform_f32<O: SealedPixelWithColorType<TransformableSubpixel = f32>>(
887 &self,
888 into: LayoutWithColor,
889 ) -> &Arc<CicpApplicable<'static, f32>> {
890 self.f32.select_transform::<O>(into)
891 }
892
893 const LAYOUTS: [(LayoutWithColor, LayoutWithColor); 4] = [
894 (LayoutWithColor::Rgb, LayoutWithColor::Rgb),
895 (LayoutWithColor::Rgb, LayoutWithColor::Rgba),
896 (LayoutWithColor::Rgba, LayoutWithColor::Rgb),
897 (LayoutWithColor::Rgba, LayoutWithColor::Rgba),
898 ];
899
900 pub(crate) fn expand_luma_rgb<P: ColorComponentForCicp>(luma: &[P], rgb: &mut [f32]) {
901 for (&pix, rgb) in luma.iter().zip(rgb.chunks_exact_mut(3)) {
902 let luma = pix.expand_to_f32();
903 rgb[0] = luma;
904 rgb[1] = luma;
905 rgb[2] = luma;
906 }
907 }
908
909 pub(crate) fn expand_luma_rgba<P: ColorComponentForCicp>(luma: &[P], rgb: &mut [f32]) {
910 for (pix, rgb) in luma.chunks_exact(2).zip(rgb.chunks_exact_mut(4)) {
911 let luma = pix[0].expand_to_f32();
912 rgb[0] = luma;
913 rgb[1] = luma;
914 rgb[2] = luma;
915 rgb[3] = pix[1].expand_to_f32();
916 }
917 }
918
919 pub(crate) fn expand_rgb<P: ColorComponentForCicp>(input: &[P], output: &mut [f32]) {
920 for (&component, val) in input.iter().zip(output) {
921 *val = component.expand_to_f32();
922 }
923 }
924
925 pub(crate) fn expand_rgba<P: ColorComponentForCicp>(input: &[P], output: &mut [f32]) {
926 for (&component, val) in input.iter().zip(output) {
927 *val = component.expand_to_f32();
928 }
929 }
930
931 pub(crate) fn clamp_rgb<P: ColorComponentForCicp>(input: &[f32], output: &mut [P]) {
932 for (&component, val) in input.iter().zip(output) {
934 *val = P::clamp_from_f32(component);
935 }
936 }
937
938 pub(crate) fn clamp_rgba<P: ColorComponentForCicp>(input: &[f32], output: &mut [P]) {
939 for (&component, val) in input.iter().zip(output) {
940 *val = P::clamp_from_f32(component);
941 }
942 }
943
944 pub(crate) fn clamp_rgb_luma<P: ColorComponentForCicp>(
945 input: &[f32],
946 output: &mut [P],
947 coef: [f32; 3],
948 ) {
949 for (rgb, pix) in input.chunks_exact(3).zip(output) {
950 let mut luma = 0.0;
951
952 for (&component, coef) in rgb.iter().zip(coef) {
953 luma = multiply_accumulate(luma, component, coef);
954 }
955
956 *pix = P::clamp_from_f32(luma);
957 }
958 }
959
960 pub(crate) fn clamp_rgba_luma<P: ColorComponentForCicp>(
961 input: &[f32],
962 output: &mut [P],
963 coef: [f32; 3],
964 ) {
965 for (rgba, pix) in input.chunks_exact(4).zip(output.chunks_exact_mut(2)) {
966 let mut luma = 0.0;
967
968 for (&component, coef) in rgba[..3].iter().zip(coef) {
969 luma = multiply_accumulate(luma, component, coef);
970 }
971
972 pix[0] = P::clamp_from_f32(luma);
973 pix[1] = P::clamp_from_f32(rgba[3]);
974 }
975 }
976}
977
978impl CicpRgb {
979 pub(crate) fn cast_pixels<FromColor, IntoColor>(
983 &self,
984 buffer: &[FromColor::Subpixel],
985 color_space_fallback: &dyn Fn() -> [f32; 3],
988 ) -> Vec<IntoColor::Subpixel>
989 where
990 FromColor: Pixel + SealedPixelWithColorType<TransformableSubpixel = FromColor::Subpixel>,
991 IntoColor: Pixel,
992 IntoColor: CicpPixelCast<FromColor>,
993 FromColor::Subpixel: ColorComponentForCicp,
994 IntoColor::Subpixel: ColorComponentForCicp + FromPrimitive<FromColor::Subpixel>,
995 {
996 use crate::traits::private::PrivateToken;
997 let from_layout = <FromColor as SealedPixelWithColorType>::layout(PrivateToken);
998 let into_layout = <IntoColor as SealedPixelWithColorType>::layout(PrivateToken);
999
1000 let mut output = match self.cast_pixels_from_subpixels(buffer, from_layout, into_layout) {
1001 Ok(ok) => return ok,
1002 Err(buffer) => buffer,
1003 };
1004
1005 let color_space_coefs = self
1007 .derived_luminance()
1008 .unwrap_or_else(color_space_fallback);
1012
1013 let pixels = buffer.len() / from_layout.channels();
1014
1015 output.resize(
1019 pixels * into_layout.channels(),
1020 <IntoColor::Subpixel as Primitive>::DEFAULT_MIN_VALUE,
1021 );
1022
1023 Self::cast_pixels_by_fallback(
1024 buffer,
1025 output.as_mut_slice(),
1026 from_layout,
1027 into_layout,
1028 color_space_coefs,
1029 );
1030 output
1031 }
1032
1033 fn cast_pixels_by_fallback<
1034 From: Primitive + ColorComponentForCicp,
1035 Into: ColorComponentForCicp,
1036 >(
1037 buffer: &[From],
1038 output: &mut [Into],
1039 from_layout: LayoutWithColor,
1040 into_layout: LayoutWithColor,
1041 color_space_coefs: [f32; 3],
1042 ) {
1043 use LayoutWithColor as Layout;
1044
1045 const STEP: usize = 256;
1046 let pixels = buffer.len() / from_layout.channels();
1047
1048 let mut ibuffer = [0.0f32; 4 * STEP];
1049 let mut obuffer = [0.0f32; 4 * STEP];
1050
1051 let ibuf_step = match from_layout {
1052 Layout::Rgb | Layout::Luma => 3,
1053 Layout::Rgba | Layout::LumaAlpha => 4,
1054 };
1055
1056 let obuf_step = match into_layout {
1057 Layout::Rgb | Layout::Luma => 3,
1058 Layout::Rgba | Layout::LumaAlpha => 4,
1059 };
1060
1061 for start_idx in (0..pixels).step_by(STEP) {
1062 let end_idx = (start_idx + STEP).min(pixels);
1063 let count = end_idx - start_idx;
1064
1065 let ibuffer = &mut ibuffer[..ibuf_step * count];
1066
1067 match from_layout {
1068 Layout::Rgb => {
1069 CicpTransform::expand_rgb(&buffer[3 * start_idx..3 * end_idx], ibuffer)
1070 }
1071 Layout::Rgba => {
1072 CicpTransform::expand_rgba(&buffer[4 * start_idx..4 * end_idx], ibuffer)
1073 }
1074 Layout::Luma => {
1075 CicpTransform::expand_luma_rgb(&buffer[start_idx..end_idx], ibuffer)
1076 }
1077 Layout::LumaAlpha => {
1078 CicpTransform::expand_luma_rgba(&buffer[2 * start_idx..2 * end_idx], ibuffer)
1079 }
1080 }
1081
1082 let obuffer = match (ibuf_step, obuf_step) {
1086 (3, 4) => {
1087 for (rgb, rgba) in ibuffer
1088 .chunks_exact(3)
1089 .zip(obuffer.chunks_exact_mut(4))
1090 .take(count)
1091 {
1092 rgba[0] = rgb[0];
1093 rgba[1] = rgb[1];
1094 rgba[2] = rgb[2];
1095 rgba[3] = 1.0;
1096 }
1097
1098 &obuffer[..4 * count]
1099 }
1100 (4, 3) => {
1101 for (rgba, rgb) in ibuffer
1102 .chunks_exact(4)
1103 .zip(obuffer.chunks_exact_mut(3))
1104 .take(count)
1105 {
1106 rgb[0] = rgba[0];
1107 rgb[1] = rgba[1];
1108 rgb[2] = rgba[2];
1109 }
1110
1111 &obuffer[..3 * count]
1112 }
1113 (n, m) => {
1114 debug_assert_eq!(n, m);
1115 &ibuffer[..m * count]
1116 }
1117 };
1118
1119 match into_layout {
1120 Layout::Rgb => {
1121 CicpTransform::clamp_rgb(obuffer, &mut output[3 * start_idx..3 * end_idx]);
1122 }
1123 Layout::Rgba => {
1124 CicpTransform::clamp_rgba(obuffer, &mut output[4 * start_idx..4 * end_idx]);
1125 }
1126 Layout::Luma => {
1127 CicpTransform::clamp_rgb_luma(
1128 obuffer,
1129 &mut output[start_idx..end_idx],
1130 color_space_coefs,
1131 );
1132 }
1133 Layout::LumaAlpha => {
1134 CicpTransform::clamp_rgba_luma(
1135 obuffer,
1136 &mut output[2 * start_idx..2 * end_idx],
1137 color_space_coefs,
1138 );
1139 }
1140 }
1141 }
1142 }
1143
1144 pub(crate) fn cast_pixels_from_subpixels<FromSubpixel, IntoSubpixel>(
1147 &self,
1148 buffer: &[FromSubpixel],
1149 from_layout: LayoutWithColor,
1150 into_layout: LayoutWithColor,
1151 ) -> Result<Vec<IntoSubpixel>, Vec<IntoSubpixel>>
1152 where
1153 FromSubpixel: ColorComponentForCicp,
1154 IntoSubpixel: ColorComponentForCicp + FromPrimitive<FromSubpixel> + Primitive,
1155 {
1156 use crate::traits::private::LayoutWithColor as Layout;
1157
1158 assert!(buffer.len() % from_layout.channels() == 0);
1159 let pixels = buffer.len() / from_layout.channels();
1160
1161 let mut output: Vec<IntoSubpixel> = vec_try_with_capacity(pixels * into_layout.channels())
1162 .expect("input layout already allocated with appropriate layout");
1165 let map_channel = <IntoSubpixel as FromPrimitive<FromSubpixel>>::from_primitive;
1166
1167 match (from_layout, into_layout) {
1168 (Layout::Rgb, Layout::Rgb)
1170 | (Layout::Rgba, Layout::Rgba)
1171 | (Layout::Luma, Layout::Luma)
1172 | (Layout::LumaAlpha, Layout::LumaAlpha) => {
1173 output.extend(buffer.iter().copied().map(map_channel));
1174 }
1175 (Layout::Rgb, Layout::Rgba) => {
1176 output.extend(buffer.chunks_exact(3).flat_map(|rgb| {
1178 let &rgb: &[_; 3] = rgb.try_into().unwrap();
1179 let [r, g, b] = rgb.map(map_channel);
1180 let a = <IntoSubpixel as Primitive>::DEFAULT_MAX_VALUE;
1181 [r, g, b, a]
1182 }));
1183 }
1184 (Layout::Rgba, Layout::Rgb) => {
1185 output.extend(buffer.chunks_exact(4).flat_map(|rgb| {
1186 let &[r, g, b, _]: &[_; 4] = rgb.try_into().unwrap();
1187 [r, g, b].map(map_channel)
1188 }));
1189 }
1190 (Layout::Luma, Layout::LumaAlpha) => {
1191 output.extend(buffer.iter().copied().flat_map(|luma| {
1192 let l = map_channel(luma);
1193 let a = <IntoSubpixel as Primitive>::DEFAULT_MAX_VALUE;
1194 [l, a]
1195 }));
1196 }
1197 (Layout::LumaAlpha, Layout::Luma) => {
1198 output.extend(buffer.chunks_exact(2).map(|rgb| {
1199 let &[luma, _]: &[_; 2] = rgb.try_into().unwrap();
1200 map_channel(luma)
1201 }));
1202 }
1203 _ => return Err(output),
1204 }
1205
1206 Ok(output)
1207 }
1208}
1209
1210pub(crate) trait CicpPixelCast<FromColor>
1218where
1219 Self: Pixel + SealedPixelWithColorType<TransformableSubpixel = <Self as Pixel>::Subpixel>,
1222 FromColor:
1223 Pixel + SealedPixelWithColorType<TransformableSubpixel = <FromColor as Pixel>::Subpixel>,
1224 Self::Subpixel: ColorComponentForCicp + FromPrimitive<FromColor::Subpixel>,
1225 FromColor::Subpixel: ColorComponentForCicp,
1226{
1227}
1228
1229impl<FromColor, IntoColor> CicpPixelCast<FromColor> for IntoColor
1230where
1231 IntoColor: Pixel + SealedPixelWithColorType<TransformableSubpixel = IntoColor::Subpixel>,
1232 FromColor: Pixel + SealedPixelWithColorType<TransformableSubpixel = FromColor::Subpixel>,
1233 IntoColor::Subpixel: ColorComponentForCicp + FromPrimitive<FromColor::Subpixel>,
1234 FromColor::Subpixel: ColorComponentForCicp,
1235{
1236}
1237
1238pub(crate) trait ColorComponentForCicp: Copy {
1239 fn expand_to_f32(self) -> f32;
1240
1241 fn clamp_from_f32(val: f32) -> Self;
1242}
1243
1244impl ColorComponentForCicp for u8 {
1245 fn expand_to_f32(self) -> f32 {
1246 const R: f32 = 1.0 / u8::MAX as f32;
1247 self as f32 * R
1248 }
1249
1250 #[inline]
1251 fn clamp_from_f32(val: f32) -> Self {
1252 (val * Self::MAX as f32).round() as u8
1254 }
1255}
1256
1257impl ColorComponentForCicp for u16 {
1258 fn expand_to_f32(self) -> f32 {
1259 const R: f32 = 1.0 / u16::MAX as f32;
1260 self as f32 * R
1261 }
1262
1263 #[inline]
1264 fn clamp_from_f32(val: f32) -> Self {
1265 (val * Self::MAX as f32).round() as u16
1267 }
1268}
1269
1270impl ColorComponentForCicp for f32 {
1271 fn expand_to_f32(self) -> f32 {
1272 self
1273 }
1274
1275 fn clamp_from_f32(val: f32) -> Self {
1276 val
1277 }
1278}
1279
1280impl<P> RgbTransforms<P> {
1281 fn select_transform<O: SealedPixelWithColorType>(
1282 &self,
1283 into: LayoutWithColor,
1284 ) -> &Arc<CicpApplicable<'static, P>> {
1285 use crate::traits::private::{LayoutWithColor as Layout, PrivateToken};
1286 let from = O::layout(PrivateToken);
1287
1288 match (from, into) {
1289 (Layout::Rgb, Layout::Rgb) => &self.slices[0],
1290 (Layout::Rgb, Layout::Rgba) => &self.slices[1],
1291 (Layout::Rgba, Layout::Rgb) => &self.slices[2],
1292 (Layout::Rgba, Layout::Rgba) => &self.slices[3],
1293 (Layout::Rgb, Layout::Luma) => &self.rgb_luma[0],
1294 (Layout::Rgb, Layout::LumaAlpha) => &self.rgb_luma[1],
1295 (Layout::Rgba, Layout::Luma) => &self.rgb_luma[2],
1296 (Layout::Rgba, Layout::LumaAlpha) => &self.rgb_luma[3],
1297 (Layout::Luma, Layout::Rgb) => &self.luma_rgb[0],
1298 (Layout::Luma, Layout::Rgba) => &self.luma_rgb[1],
1299 (Layout::LumaAlpha, Layout::Rgb) => &self.luma_rgb[2],
1300 (Layout::LumaAlpha, Layout::Rgba) => &self.luma_rgb[3],
1301 (Layout::Luma, Layout::Luma) => &self.luma_luma[0],
1302 (Layout::Luma, Layout::LumaAlpha) => &self.luma_luma[1],
1303 (Layout::LumaAlpha, Layout::Luma) => &self.luma_luma[2],
1304 (Layout::LumaAlpha, Layout::LumaAlpha) => &self.luma_luma[3],
1305 }
1306 }
1307}
1308
1309impl Cicp {
1310 pub const SRGB: Self = Cicp {
1312 primaries: CicpColorPrimaries::SRgb,
1313 transfer: CicpTransferCharacteristics::SRgb,
1314 matrix: CicpMatrixCoefficients::Identity,
1315 full_range: CicpVideoFullRangeFlag::FullRange,
1316 };
1317
1318 pub const SRGB_LINEAR: Self = Cicp {
1320 primaries: CicpColorPrimaries::SRgb,
1321 transfer: CicpTransferCharacteristics::Linear,
1322 matrix: CicpMatrixCoefficients::Identity,
1323 full_range: CicpVideoFullRangeFlag::FullRange,
1324 };
1325
1326 pub const DISPLAY_P3: Self = Cicp {
1332 primaries: CicpColorPrimaries::SmpteRp432,
1333 transfer: CicpTransferCharacteristics::SRgb,
1334 matrix: CicpMatrixCoefficients::Identity,
1335 full_range: CicpVideoFullRangeFlag::FullRange,
1336 };
1337
1338 fn to_moxcms_compute_profile(self) -> Option<ColorProfile> {
1369 let mut rgb = moxcms::ColorProfile::new_srgb();
1370
1371 rgb.update_rgb_colorimetry_from_cicp(moxcms::CicpProfile {
1372 color_primaries: self.primaries.to_moxcms(),
1373 transfer_characteristics: self.transfer.to_moxcms(),
1374 matrix_coefficients: self.matrix.to_moxcms()?,
1375 full_range: match self.full_range {
1376 CicpVideoFullRangeFlag::NarrowRange => false,
1377 CicpVideoFullRangeFlag::FullRange => true,
1378 },
1379 });
1380
1381 Some(ColorProfile { rgb })
1382 }
1383
1384 pub(crate) const fn qualify_stability(&self) -> bool {
1397 const _: () = {
1398 assert!(Cicp::SRGB.qualify_stability());
1400 assert!(Cicp::SRGB_LINEAR.qualify_stability());
1401 assert!(Cicp::DISPLAY_P3.qualify_stability());
1402 };
1403
1404 matches!(self.full_range, CicpVideoFullRangeFlag::FullRange)
1405 && matches!(
1406 self.matrix,
1407 CicpMatrixCoefficients::Identity
1409 | CicpMatrixCoefficients::ChromaticityDerivedNonConstant
1411 )
1412 && matches!(
1413 self.primaries,
1414 CicpColorPrimaries::SRgb
1415 | CicpColorPrimaries::SmpteRp431
1416 | CicpColorPrimaries::SmpteRp432
1417 | CicpColorPrimaries::Bt601
1418 | CicpColorPrimaries::Rgb240m
1419 )
1420 && matches!(
1421 self.transfer,
1422 CicpTransferCharacteristics::SRgb
1423 | CicpTransferCharacteristics::Bt709
1424 | CicpTransferCharacteristics::Bt601
1425 | CicpTransferCharacteristics::Linear
1426 )
1427 }
1428
1429 pub(crate) const fn into_rgb(self) -> CicpRgb {
1431 CicpRgb {
1432 primaries: self.primaries,
1433 transfer: self.transfer,
1434 luminance: DerivedLuminance::NonConstant,
1441 }
1442 }
1443
1444 pub(crate) fn try_into_rgb(self) -> Result<CicpRgb, ImageError> {
1445 if Cicp::from(self.into_rgb()) != self {
1446 Err(ImageError::Parameter(ParameterError::from_kind(
1447 ParameterErrorKind::RgbCicpRequired(self),
1448 )))
1449 } else {
1450 Ok(self.into_rgb())
1451 }
1452 }
1453}
1454
1455impl CicpRgb {
1456 pub(crate) fn derived_luminance(&self) -> Option<[f32; 3]> {
1460 let primaries = match self.primaries {
1461 CicpColorPrimaries::SRgb => moxcms::ColorPrimaries::BT_709,
1462 CicpColorPrimaries::RgbM => moxcms::ColorPrimaries::BT_470M,
1463 CicpColorPrimaries::RgbB => moxcms::ColorPrimaries::BT_470BG,
1464 CicpColorPrimaries::Bt601 => moxcms::ColorPrimaries::BT_601,
1465 CicpColorPrimaries::Rgb240m => moxcms::ColorPrimaries::SMPTE_240,
1466 CicpColorPrimaries::GenericFilm => moxcms::ColorPrimaries::GENERIC_FILM,
1467 CicpColorPrimaries::Rgb2020 => moxcms::ColorPrimaries::BT_2020,
1468 CicpColorPrimaries::Xyz => moxcms::ColorPrimaries::XYZ,
1469 CicpColorPrimaries::SmpteRp431 => moxcms::ColorPrimaries::DISPLAY_P3,
1470 CicpColorPrimaries::SmpteRp432 => moxcms::ColorPrimaries::DISPLAY_P3,
1471 CicpColorPrimaries::Industry22 => moxcms::ColorPrimaries::EBU_3213,
1472 CicpColorPrimaries::Unspecified => return None,
1473 };
1474
1475 const ILLUMINANT_C: moxcms::Chromaticity = moxcms::Chromaticity::new(0.310, 0.316);
1476
1477 let whitepoint = match self.primaries {
1478 CicpColorPrimaries::SRgb => moxcms::Chromaticity::D65,
1479 CicpColorPrimaries::RgbM => ILLUMINANT_C,
1480 CicpColorPrimaries::RgbB => moxcms::Chromaticity::D65,
1481 CicpColorPrimaries::Bt601 => moxcms::Chromaticity::D65,
1482 CicpColorPrimaries::Rgb240m => moxcms::Chromaticity::D65,
1483 CicpColorPrimaries::GenericFilm => ILLUMINANT_C,
1484 CicpColorPrimaries::Rgb2020 => moxcms::Chromaticity::D65,
1485 CicpColorPrimaries::Xyz => moxcms::Chromaticity::new(1. / 3., 1. / 3.),
1486 CicpColorPrimaries::SmpteRp431 => moxcms::Chromaticity::new(0.314, 0.351),
1487 CicpColorPrimaries::SmpteRp432 => moxcms::Chromaticity::D65,
1488 CicpColorPrimaries::Industry22 => moxcms::Chromaticity::D65,
1489 CicpColorPrimaries::Unspecified => return None,
1490 };
1491
1492 let matrix = primaries.transform_to_xyz(whitepoint);
1493
1494 Some(matrix.v[1])
1496 }
1497}
1498
1499impl From<CicpRgb> for Cicp {
1500 fn from(cicp: CicpRgb) -> Self {
1501 Cicp {
1502 primaries: cicp.primaries,
1503 transfer: cicp.transfer,
1504 matrix: CicpMatrixCoefficients::Identity,
1505 full_range: CicpVideoFullRangeFlag::FullRange,
1506 }
1507 }
1508}
1509
1510struct ColorProfile {
1518 rgb: moxcms::ColorProfile,
1519}
1520
1521impl ColorProfile {
1522 fn map_layout(&self, layout: LayoutWithColor) -> (&moxcms::ColorProfile, moxcms::Layout) {
1523 match layout {
1524 LayoutWithColor::Rgb => (&self.rgb, moxcms::Layout::Rgb),
1525 LayoutWithColor::Rgba => (&self.rgb, moxcms::Layout::Rgba),
1526 LayoutWithColor::Luma | LayoutWithColor::LumaAlpha => unreachable!(),
1528 }
1529 }
1530}
1531
1532#[cfg(test)]
1533#[test]
1534fn moxcms() {
1535 let l = moxcms::TransferCharacteristics::Linear;
1536 assert_eq!(l.linearize(1.0), 1.0);
1537 assert_eq!(l.gamma(1.0), 1.0);
1538
1539 assert_eq!(l.gamma(0.5), 0.5);
1540}
1541
1542#[cfg(test)]
1543#[test]
1544fn derived_luminance() {
1545 let luminance = Cicp::SRGB.into_rgb().derived_luminance();
1546 let [kr, kg, kb] = luminance.unwrap();
1547 assert!((kr - 0.2126).abs() < 1e-4);
1548 assert!((kg - 0.7152).abs() < 1e-4);
1549 assert!((kb - 0.0722).abs() < 1e-4);
1550}
1551
1552#[cfg(test)]
1553mod tests {
1554 use super::{Cicp, CicpTransform};
1555 use crate::{Luma, LumaA, Rgb, Rgba};
1556
1557 #[test]
1558 fn can_create_transforms() {
1559 assert!(CicpTransform::new(Cicp::SRGB, Cicp::SRGB).is_some());
1560 assert!(CicpTransform::new(Cicp::SRGB, Cicp::DISPLAY_P3).is_some());
1561 assert!(CicpTransform::new(Cicp::DISPLAY_P3, Cicp::SRGB).is_some());
1562 assert!(CicpTransform::new(Cicp::DISPLAY_P3, Cicp::DISPLAY_P3).is_some());
1563 }
1564
1565 fn no_coefficient_fallback() -> [f32; 3] {
1566 panic!("Fallback coefficients required")
1567 }
1568
1569 #[test]
1570 fn transform_pixels_srgb() {
1571 let data = [255, 0, 0, 255];
1574 let color = Cicp::SRGB.into_rgb();
1575 let rgba = color.cast_pixels::<Rgba<u8>, Rgb<u8>>(&data, &no_coefficient_fallback);
1576 assert_eq!(rgba, [255, 0, 0]);
1577 let luma = color.cast_pixels::<Rgba<u8>, Luma<u8>>(&data, &no_coefficient_fallback);
1578 assert_eq!(luma, [54]); let luma_a = color.cast_pixels::<Rgba<u8>, LumaA<u8>>(&data, &no_coefficient_fallback);
1580 assert_eq!(luma_a, [54, 255]);
1581 }
1582
1583 #[test]
1584 fn transform_pixels_srgb_16() {
1585 let data = [u16::MAX / 2];
1588 let color = Cicp::SRGB.into_rgb();
1589 let rgba = color.cast_pixels::<Luma<u16>, Rgb<u8>>(&data, &no_coefficient_fallback);
1590 assert_eq!(rgba, [127; 3]);
1591 let luma = color.cast_pixels::<Luma<u16>, Luma<u8>>(&data, &no_coefficient_fallback);
1592 assert_eq!(luma, [127]);
1593 let luma_a = color.cast_pixels::<Luma<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1594 assert_eq!(luma_a, [127, 255]);
1595
1596 let data = [u16::MAX / 2 + 1];
1597 let color = Cicp::SRGB.into_rgb();
1598 let rgba = color.cast_pixels::<Luma<u16>, Rgb<u8>>(&data, &no_coefficient_fallback);
1599 assert_eq!(rgba, [128; 3]);
1600 let luma = color.cast_pixels::<Luma<u16>, Luma<u8>>(&data, &no_coefficient_fallback);
1601 assert_eq!(luma, [128]);
1602 let luma_a = color.cast_pixels::<Luma<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1603 assert_eq!(luma_a, [128, 255]);
1604 }
1605
1606 #[test]
1607 fn transform_pixels_srgb_luma_alpha() {
1608 let data = [u16::MAX / 2, u16::MAX];
1611 let color = Cicp::SRGB.into_rgb();
1612 let rgba = color.cast_pixels::<LumaA<u16>, Rgb<u8>>(&data, &no_coefficient_fallback);
1613 assert_eq!(rgba, [127; 3]);
1614 let luma = color.cast_pixels::<LumaA<u16>, Luma<u8>>(&data, &no_coefficient_fallback);
1615 assert_eq!(luma, [127]);
1616 let luma = color.cast_pixels::<LumaA<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1617 assert_eq!(luma, [127, u8::MAX]);
1618 let luma_a = color.cast_pixels::<LumaA<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1619 assert_eq!(luma_a, [127, 255]);
1620
1621 let data = [u16::MAX / 2 + 1, u16::MAX];
1622 let color = Cicp::SRGB.into_rgb();
1623 let rgba = color.cast_pixels::<LumaA<u16>, Rgb<u8>>(&data, &no_coefficient_fallback);
1624 assert_eq!(rgba, [128; 3]);
1625 let luma = color.cast_pixels::<LumaA<u16>, Luma<u8>>(&data, &no_coefficient_fallback);
1626 assert_eq!(luma, [128]);
1627 let luma = color.cast_pixels::<LumaA<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1628 assert_eq!(luma, [128, u8::MAX]);
1629 let luma_a = color.cast_pixels::<LumaA<u16>, LumaA<u8>>(&data, &no_coefficient_fallback);
1630 assert_eq!(luma_a, [128, 255]);
1631 }
1632}