1use crate::conversions::{
30 LutBarycentricReduction, RgbXyzFactory, RgbXyzFactoryOpt, ToneReproductionRgbToGray,
31 TransformMatrixShaper, make_gray_to_unfused, make_gray_to_x, make_lut_transform,
32 make_rgb_to_gray,
33};
34use crate::err::CmsError;
35use crate::trc::GammaLutInterpolate;
36use crate::{ColorProfile, DataColorSpace, LutWarehouse, RenderingIntent, Vector3f, Xyzd};
37use num_traits::AsPrimitive;
38use std::marker::PhantomData;
39
40pub trait TransformExecutor<V: Copy + Default> {
42 fn transform(&self, src: &[V], dst: &mut [V]) -> Result<(), CmsError>;
45}
46
47pub trait Stage {
49 fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError>;
50}
51
52pub trait InPlaceStage {
54 fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError>;
55}
56
57#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default)]
61pub enum BarycentricWeightScale {
62 #[default]
63 Low,
68 #[cfg(feature = "options")]
69 High,
70}
71
72#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
74pub struct TransformOptions {
75 pub rendering_intent: RenderingIntent,
76 pub allow_use_cicp_transfer: bool,
79 pub prefer_fixed_point: bool,
89 pub interpolation_method: InterpolationMethod,
101 pub barycentric_weight_scale: BarycentricWeightScale,
105 pub allow_extended_range_rgb_xyz: bool,
110 }
112
113#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default)]
114pub enum InterpolationMethod {
121 #[cfg(feature = "options")]
124 Tetrahedral,
125 #[cfg(feature = "options")]
127 Pyramid,
128 #[cfg(feature = "options")]
130 Prism,
131 #[default]
133 Linear,
134}
135
136impl Default for TransformOptions {
137 fn default() -> Self {
138 Self {
139 rendering_intent: RenderingIntent::default(),
140 allow_use_cicp_transfer: true,
141 prefer_fixed_point: true,
142 interpolation_method: InterpolationMethod::default(),
143 barycentric_weight_scale: BarycentricWeightScale::default(),
144 allow_extended_range_rgb_xyz: false,
145 }
147 }
148}
149
150pub type Transform8BitExecutor = dyn TransformExecutor<u8> + Send + Sync;
151pub type Transform16BitExecutor = dyn TransformExecutor<u16> + Send + Sync;
152pub type TransformF32BitExecutor = dyn TransformExecutor<f32> + Send + Sync;
153pub type TransformF64BitExecutor = dyn TransformExecutor<f64> + Send + Sync;
154
155#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
160pub enum Layout {
161 Rgb = 0,
162 Rgba = 1,
163 Gray = 2,
164 GrayAlpha = 3,
165 Inks5 = 4,
166 Inks6 = 5,
167 Inks7 = 6,
168 Inks8 = 7,
169 Inks9 = 8,
170 Inks10 = 9,
171 Inks11 = 10,
172 Inks12 = 11,
173 Inks13 = 12,
174 Inks14 = 13,
175 Inks15 = 14,
176}
177
178impl Layout {
179 #[inline(always)]
181 pub const fn r_i(self) -> usize {
182 match self {
183 Layout::Rgb => 0,
184 Layout::Rgba => 0,
185 Layout::Gray => unimplemented!(),
186 Layout::GrayAlpha => unimplemented!(),
187 _ => unimplemented!(),
188 }
189 }
190
191 #[inline(always)]
193 pub const fn g_i(self) -> usize {
194 match self {
195 Layout::Rgb => 1,
196 Layout::Rgba => 1,
197 Layout::Gray => unimplemented!(),
198 Layout::GrayAlpha => unimplemented!(),
199 _ => unimplemented!(),
200 }
201 }
202
203 #[inline(always)]
205 pub const fn b_i(self) -> usize {
206 match self {
207 Layout::Rgb => 2,
208 Layout::Rgba => 2,
209 Layout::Gray => unimplemented!(),
210 Layout::GrayAlpha => unimplemented!(),
211 _ => unimplemented!(),
212 }
213 }
214
215 #[inline(always)]
216 pub const fn a_i(self) -> usize {
217 match self {
218 Layout::Rgb => unimplemented!(),
219 Layout::Rgba => 3,
220 Layout::Gray => unimplemented!(),
221 Layout::GrayAlpha => 1,
222 _ => unimplemented!(),
223 }
224 }
225
226 #[inline(always)]
227 pub const fn has_alpha(self) -> bool {
228 match self {
229 Layout::Rgb => false,
230 Layout::Rgba => true,
231 Layout::Gray => false,
232 Layout::GrayAlpha => true,
233 _ => false,
234 }
235 }
236
237 #[inline]
238 pub const fn channels(self) -> usize {
239 match self {
240 Layout::Rgb => 3,
241 Layout::Rgba => 4,
242 Layout::Gray => 1,
243 Layout::GrayAlpha => 2,
244 Layout::Inks5 => 5,
245 Layout::Inks6 => 6,
246 Layout::Inks7 => 7,
247 Layout::Inks8 => 8,
248 Layout::Inks9 => 9,
249 Layout::Inks10 => 10,
250 Layout::Inks11 => 11,
251 Layout::Inks12 => 12,
252 Layout::Inks13 => 13,
253 Layout::Inks14 => 14,
254 Layout::Inks15 => 15,
255 }
256 }
257
258 pub(crate) fn from_inks(inks: usize) -> Self {
259 match inks {
260 1 => Layout::Gray,
261 2 => Layout::GrayAlpha,
262 3 => Layout::Rgb,
263 4 => Layout::Rgba,
264 5 => Layout::Inks5,
265 6 => Layout::Inks6,
266 7 => Layout::Inks7,
267 8 => Layout::Inks8,
268 9 => Layout::Inks9,
269 10 => Layout::Inks10,
270 11 => Layout::Inks11,
271 12 => Layout::Inks12,
272 13 => Layout::Inks13,
273 14 => Layout::Inks14,
274 15 => Layout::Inks15,
275 _ => unreachable!("Impossible amount of inks"),
276 }
277 }
278}
279
280impl From<u8> for Layout {
281 fn from(value: u8) -> Self {
282 match value {
283 0 => Layout::Rgb,
284 1 => Layout::Rgba,
285 2 => Layout::Gray,
286 3 => Layout::GrayAlpha,
287 _ => unimplemented!(),
288 }
289 }
290}
291
292impl Layout {
293 #[inline(always)]
294 pub const fn resolve(value: u8) -> Self {
295 match value {
296 0 => Layout::Rgb,
297 1 => Layout::Rgba,
298 2 => Layout::Gray,
299 3 => Layout::GrayAlpha,
300 4 => Layout::Inks5,
301 5 => Layout::Inks6,
302 6 => Layout::Inks7,
303 7 => Layout::Inks8,
304 8 => Layout::Inks9,
305 9 => Layout::Inks10,
306 10 => Layout::Inks11,
307 11 => Layout::Inks12,
308 12 => Layout::Inks13,
309 13 => Layout::Inks14,
310 14 => Layout::Inks15,
311 _ => unimplemented!(),
312 }
313 }
314}
315
316#[doc(hidden)]
317pub trait PointeeSizeExpressible {
318 fn _as_usize(self) -> usize;
319 const FINITE: bool;
320 const NOT_FINITE_GAMMA_TABLE_SIZE: usize;
321 const NOT_FINITE_LINEAR_TABLE_SIZE: usize;
322 const IS_U8: bool;
323 const IS_U16: bool;
324}
325
326impl PointeeSizeExpressible for u8 {
327 #[inline(always)]
328 fn _as_usize(self) -> usize {
329 self as usize
330 }
331
332 const FINITE: bool = true;
333 const NOT_FINITE_GAMMA_TABLE_SIZE: usize = 1;
334 const NOT_FINITE_LINEAR_TABLE_SIZE: usize = 1;
335 const IS_U8: bool = true;
336 const IS_U16: bool = false;
337}
338
339impl PointeeSizeExpressible for u16 {
340 #[inline(always)]
341 fn _as_usize(self) -> usize {
342 self as usize
343 }
344
345 const FINITE: bool = true;
346
347 const NOT_FINITE_GAMMA_TABLE_SIZE: usize = 1;
348 const NOT_FINITE_LINEAR_TABLE_SIZE: usize = 1;
349
350 const IS_U8: bool = false;
351 const IS_U16: bool = true;
352}
353
354impl PointeeSizeExpressible for f32 {
355 #[inline(always)]
356 fn _as_usize(self) -> usize {
357 const MAX_14_BIT: f32 = ((1 << 14u32) - 1) as f32;
358 ((self * MAX_14_BIT).max(0f32).min(MAX_14_BIT) as u16) as usize
359 }
360
361 const FINITE: bool = false;
362
363 const NOT_FINITE_GAMMA_TABLE_SIZE: usize = 32768;
364 const NOT_FINITE_LINEAR_TABLE_SIZE: usize = 1 << 14u32;
365 const IS_U8: bool = false;
366 const IS_U16: bool = false;
367}
368
369impl PointeeSizeExpressible for f64 {
370 #[inline(always)]
371 fn _as_usize(self) -> usize {
372 const MAX_16_BIT: f64 = ((1 << 16u32) - 1) as f64;
373 ((self * MAX_16_BIT).max(0.).min(MAX_16_BIT) as u16) as usize
374 }
375
376 const FINITE: bool = false;
377
378 const NOT_FINITE_GAMMA_TABLE_SIZE: usize = 65536;
379 const NOT_FINITE_LINEAR_TABLE_SIZE: usize = 1 << 16;
380 const IS_U8: bool = false;
381 const IS_U16: bool = false;
382}
383
384impl ColorProfile {
385 pub fn is_matrix_shaper(&self) -> bool {
387 self.color_space == DataColorSpace::Rgb
388 && self.red_colorant != Xyzd::default()
389 && self.green_colorant != Xyzd::default()
390 && self.blue_colorant != Xyzd::default()
391 && self.red_trc.is_some()
392 && self.green_trc.is_some()
393 && self.blue_trc.is_some()
394 }
395
396 pub fn create_transform_16bit(
399 &self,
400 src_layout: Layout,
401 dst_pr: &ColorProfile,
402 dst_layout: Layout,
403 options: TransformOptions,
404 ) -> Result<Box<Transform16BitExecutor>, CmsError> {
405 self.create_transform_nbit::<u16, 16, 65536, 65536>(src_layout, dst_pr, dst_layout, options)
406 }
407
408 pub fn create_transform_12bit(
411 &self,
412 src_layout: Layout,
413 dst_pr: &ColorProfile,
414 dst_layout: Layout,
415 options: TransformOptions,
416 ) -> Result<Box<Transform16BitExecutor>, CmsError> {
417 self.create_transform_nbit::<u16, 12, 65536, 16384>(src_layout, dst_pr, dst_layout, options)
418 }
419
420 pub fn create_transform_10bit(
423 &self,
424 src_layout: Layout,
425 dst_pr: &ColorProfile,
426 dst_layout: Layout,
427 options: TransformOptions,
428 ) -> Result<Box<Transform16BitExecutor>, CmsError> {
429 self.create_transform_nbit::<u16, 10, 65536, 8192>(src_layout, dst_pr, dst_layout, options)
430 }
431
432 pub fn create_transform_f32(
439 &self,
440 src_layout: Layout,
441 dst_pr: &ColorProfile,
442 dst_layout: Layout,
443 options: TransformOptions,
444 ) -> Result<Box<TransformF32BitExecutor>, CmsError> {
445 self.create_transform_nbit::<f32, 1, 65536, 32768>(src_layout, dst_pr, dst_layout, options)
446 }
447
448 pub fn create_transform_f64(
455 &self,
456 src_layout: Layout,
457 dst_pr: &ColorProfile,
458 dst_layout: Layout,
459 options: TransformOptions,
460 ) -> Result<Box<TransformF64BitExecutor>, CmsError> {
461 self.create_transform_nbit::<f64, 1, 65536, 65536>(src_layout, dst_pr, dst_layout, options)
462 }
463
464 fn create_transform_nbit<
465 T: Copy
466 + Default
467 + AsPrimitive<usize>
468 + PointeeSizeExpressible
469 + Send
470 + Sync
471 + AsPrimitive<f32>
472 + RgbXyzFactory<T>
473 + RgbXyzFactoryOpt<T>
474 + GammaLutInterpolate,
475 const BIT_DEPTH: usize,
476 const LINEAR_CAP: usize,
477 const GAMMA_CAP: usize,
478 >(
479 &self,
480 src_layout: Layout,
481 dst_pr: &ColorProfile,
482 dst_layout: Layout,
483 options: TransformOptions,
484 ) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
485 where
486 f32: AsPrimitive<T>,
487 u32: AsPrimitive<T>,
488 (): LutBarycentricReduction<T, u8>,
489 (): LutBarycentricReduction<T, u16>,
490 {
491 if self.color_space == DataColorSpace::Rgb
492 && dst_pr.pcs == DataColorSpace::Xyz
493 && dst_pr.color_space == DataColorSpace::Rgb
494 && self.pcs == DataColorSpace::Xyz
495 && self.is_matrix_shaper()
496 && dst_pr.is_matrix_shaper()
497 {
498 if src_layout == Layout::Gray || src_layout == Layout::GrayAlpha {
499 return Err(CmsError::InvalidLayout);
500 }
501 if dst_layout == Layout::Gray || dst_layout == Layout::GrayAlpha {
502 return Err(CmsError::InvalidLayout);
503 }
504
505 if self.has_device_to_pcs_lut() || dst_pr.has_pcs_to_device_lut() {
506 return make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>(
507 src_layout, self, dst_layout, dst_pr, options,
508 );
509 }
510
511 let transform = self.transform_matrix(dst_pr);
512
513 if !T::FINITE && options.allow_extended_range_rgb_xyz {
514 if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() {
515 if let Some(linear_evaluator) = self.try_extended_linearizing_evaluator() {
516 use crate::conversions::{
517 TransformShaperFloatInOut, make_rgb_xyz_rgb_transform_float_in_out,
518 };
519 let p = TransformShaperFloatInOut {
520 linear_evaluator,
521 gamma_evaluator,
522 adaptation_matrix: transform.to_f32(),
523 phantom_data: PhantomData,
524 };
525 return make_rgb_xyz_rgb_transform_float_in_out::<T>(
526 src_layout, dst_layout, p, BIT_DEPTH,
527 );
528 }
529
530 let lin_r = self.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
531 options.allow_use_cicp_transfer,
532 )?;
533 let lin_g = self.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
534 options.allow_use_cicp_transfer,
535 )?;
536 let lin_b = self.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
537 options.allow_use_cicp_transfer,
538 )?;
539
540 use crate::conversions::{
541 TransformShaperRgbFloat, make_rgb_xyz_rgb_transform_float,
542 };
543 let p = TransformShaperRgbFloat {
544 r_linear: lin_r,
545 g_linear: lin_g,
546 b_linear: lin_b,
547 gamma_evaluator,
548 adaptation_matrix: transform.to_f32(),
549 phantom_data: PhantomData,
550 };
551 return make_rgb_xyz_rgb_transform_float::<T, LINEAR_CAP>(
552 src_layout, dst_layout, p, BIT_DEPTH,
553 );
554 }
555 }
556
557 if self.are_all_trc_the_same() && dst_pr.are_all_trc_the_same() {
558 let linear = self.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
559 options.allow_use_cicp_transfer,
560 )?;
561
562 let gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
563 &dst_pr.red_trc,
564 options.allow_use_cicp_transfer,
565 )?;
566
567 let profile_transform = crate::conversions::TransformMatrixShaperOptimized {
568 linear,
569 gamma,
570 adaptation_matrix: transform.to_f32(),
571 };
572
573 return T::make_optimized_transform::<LINEAR_CAP, GAMMA_CAP, BIT_DEPTH>(
574 src_layout,
575 dst_layout,
576 profile_transform,
577 options,
578 );
579 }
580
581 let lin_r = self.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
582 options.allow_use_cicp_transfer,
583 )?;
584 let lin_g = self.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
585 options.allow_use_cicp_transfer,
586 )?;
587 let lin_b = self.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
588 options.allow_use_cicp_transfer,
589 )?;
590
591 let gamma_r = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
592 &dst_pr.red_trc,
593 options.allow_use_cicp_transfer,
594 )?;
595 let gamma_g = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
596 &dst_pr.green_trc,
597 options.allow_use_cicp_transfer,
598 )?;
599 let gamma_b = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
600 &dst_pr.blue_trc,
601 options.allow_use_cicp_transfer,
602 )?;
603
604 let profile_transform = TransformMatrixShaper {
605 r_linear: lin_r,
606 g_linear: lin_g,
607 b_linear: lin_b,
608 r_gamma: gamma_r,
609 g_gamma: gamma_g,
610 b_gamma: gamma_b,
611 adaptation_matrix: transform.to_f32(),
612 };
613
614 T::make_transform::<LINEAR_CAP, GAMMA_CAP, BIT_DEPTH>(
615 src_layout,
616 dst_layout,
617 profile_transform,
618 options,
619 )
620 } else if (self.color_space == DataColorSpace::Gray && self.gray_trc.is_some())
621 && (dst_pr.color_space == DataColorSpace::Rgb
622 || (dst_pr.color_space == DataColorSpace::Gray && dst_pr.gray_trc.is_some()))
623 && self.pcs == DataColorSpace::Xyz
624 && dst_pr.pcs == DataColorSpace::Xyz
625 {
626 if src_layout != Layout::GrayAlpha && src_layout != Layout::Gray {
627 return Err(CmsError::InvalidLayout);
628 }
629
630 if self.has_device_to_pcs_lut() || dst_pr.has_pcs_to_device_lut() {
631 return make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>(
632 src_layout, self, dst_layout, dst_pr, options,
633 );
634 }
635
636 let gray_linear = self.build_gray_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>()?;
637
638 if dst_pr.color_space == DataColorSpace::Gray {
639 if !T::FINITE && options.allow_extended_range_rgb_xyz {
640 if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() {
641 if let Some(linear_evaluator) = self.try_extended_linearizing_evaluator() {
642 use crate::conversions::make_gray_to_one_trc_extended;
644 return make_gray_to_one_trc_extended::<T>(
645 src_layout,
646 dst_layout,
647 linear_evaluator,
648 gamma_evaluator,
649 BIT_DEPTH,
650 );
651 }
652 }
653 }
654
655 let gray_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
657 &dst_pr.gray_trc,
658 options.allow_use_cicp_transfer,
659 )?;
660
661 make_gray_to_x::<T, LINEAR_CAP>(
662 src_layout,
663 dst_layout,
664 &gray_linear,
665 &gray_gamma,
666 BIT_DEPTH,
667 GAMMA_CAP,
668 )
669 } else {
670 #[allow(clippy::collapsible_if)]
671 if dst_pr.are_all_trc_the_same() {
672 if !T::FINITE && options.allow_extended_range_rgb_xyz {
673 if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() {
674 if let Some(linear_evaluator) =
675 self.try_extended_linearizing_evaluator()
676 {
677 use crate::conversions::make_gray_to_one_trc_extended;
679 return make_gray_to_one_trc_extended::<T>(
680 src_layout,
681 dst_layout,
682 linear_evaluator,
683 gamma_evaluator,
684 BIT_DEPTH,
685 );
686 }
687 }
688 }
689
690 let rgb_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
692 &dst_pr.red_trc,
693 options.allow_use_cicp_transfer,
694 )?;
695
696 make_gray_to_x::<T, LINEAR_CAP>(
697 src_layout,
698 dst_layout,
699 &gray_linear,
700 &rgb_gamma,
701 BIT_DEPTH,
702 GAMMA_CAP,
703 )
704 } else {
705 if !T::FINITE && options.allow_extended_range_rgb_xyz {
707 if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() {
708 if let Some(linear_evaluator) =
709 self.try_extended_linearizing_evaluator()
710 {
711 use crate::conversions::make_gray_to_rgb_extended;
714 return make_gray_to_rgb_extended::<T>(
715 src_layout,
716 dst_layout,
717 linear_evaluator,
718 gamma_evaluator,
719 BIT_DEPTH,
720 );
721 }
722 }
723 }
724
725 let red_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
726 &dst_pr.red_trc,
727 options.allow_use_cicp_transfer,
728 )?;
729 let green_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
730 &dst_pr.green_trc,
731 options.allow_use_cicp_transfer,
732 )?;
733 let blue_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
734 &dst_pr.blue_trc,
735 options.allow_use_cicp_transfer,
736 )?;
737
738 let mut gray_linear2 = Box::new([0f32; 65536]);
739 for (dst, src) in gray_linear2.iter_mut().zip(gray_linear.iter()) {
740 *dst = *src;
741 }
742
743 make_gray_to_unfused::<T, LINEAR_CAP>(
744 src_layout,
745 dst_layout,
746 gray_linear2,
747 red_gamma,
748 green_gamma,
749 blue_gamma,
750 BIT_DEPTH,
751 GAMMA_CAP,
752 )
753 }
754 }
755 } else if self.color_space == DataColorSpace::Rgb
756 && (dst_pr.color_space == DataColorSpace::Gray && dst_pr.gray_trc.is_some())
757 && dst_pr.pcs == DataColorSpace::Xyz
758 && self.pcs == DataColorSpace::Xyz
759 {
760 if src_layout == Layout::Gray || src_layout == Layout::GrayAlpha {
761 return Err(CmsError::InvalidLayout);
762 }
763 if dst_layout != Layout::Gray && dst_layout != Layout::GrayAlpha {
764 return Err(CmsError::InvalidLayout);
765 }
766
767 if self.has_device_to_pcs_lut() || dst_pr.has_pcs_to_device_lut() {
768 return make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>(
769 src_layout, self, dst_layout, dst_pr, options,
770 );
771 }
772
773 let transform = self.transform_matrix(dst_pr).to_f32();
774
775 let vector = Vector3f {
776 v: [transform.v[1][0], transform.v[1][1], transform.v[1][2]],
777 };
778
779 if !T::FINITE && options.allow_extended_range_rgb_xyz {
780 if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() {
781 if let Some(linear_evaluator) = self.try_extended_linearizing_evaluator() {
782 use crate::conversions::make_rgb_to_gray_extended;
783 return Ok(make_rgb_to_gray_extended::<T>(
784 src_layout,
785 dst_layout,
786 linear_evaluator,
787 gamma_evaluator,
788 vector,
789 BIT_DEPTH,
790 ));
791 }
792 }
793 }
794
795 let lin_r = self.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
796 options.allow_use_cicp_transfer,
797 )?;
798 let lin_g = self.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
799 options.allow_use_cicp_transfer,
800 )?;
801 let lin_b = self.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
802 options.allow_use_cicp_transfer,
803 )?;
804 let gray_linear = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
805 &dst_pr.gray_trc,
806 options.allow_use_cicp_transfer,
807 )?;
808
809 let trc_box = ToneReproductionRgbToGray::<T, LINEAR_CAP> {
810 r_linear: lin_r,
811 g_linear: lin_g,
812 b_linear: lin_b,
813 gray_gamma: gray_linear,
814 };
815
816 Ok(make_rgb_to_gray::<T, LINEAR_CAP>(
817 src_layout, dst_layout, trc_box, vector, GAMMA_CAP, BIT_DEPTH,
818 ))
819 } else if (self.color_space.is_three_channels()
820 || self.color_space == DataColorSpace::Cmyk
821 || self.color_space == DataColorSpace::Color4)
822 && (dst_pr.color_space.is_three_channels()
823 || dst_pr.color_space == DataColorSpace::Cmyk
824 || dst_pr.color_space == DataColorSpace::Color4)
825 && (dst_pr.pcs == DataColorSpace::Xyz || dst_pr.pcs == DataColorSpace::Lab)
826 && (self.pcs == DataColorSpace::Xyz || self.pcs == DataColorSpace::Lab)
827 {
828 if src_layout == Layout::Gray || src_layout == Layout::GrayAlpha {
829 return Err(CmsError::InvalidLayout);
830 }
831 if dst_layout == Layout::Gray || dst_layout == Layout::GrayAlpha {
832 return Err(CmsError::InvalidLayout);
833 }
834 make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>(
835 src_layout, self, dst_layout, dst_pr, options,
836 )
837 } else {
838 make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>(
839 src_layout, self, dst_layout, dst_pr, options,
840 )
841 }
842 }
843
844 pub fn create_transform_8bit(
847 &self,
848 src_layout: Layout,
849 dst_pr: &ColorProfile,
850 dst_layout: Layout,
851 options: TransformOptions,
852 ) -> Result<Box<Transform8BitExecutor>, CmsError> {
853 self.create_transform_nbit::<u8, 8, 256, 4096>(src_layout, dst_pr, dst_layout, options)
854 }
855
856 pub(crate) fn get_device_to_pcs(&self, intent: RenderingIntent) -> Option<&LutWarehouse> {
857 match intent {
858 RenderingIntent::AbsoluteColorimetric => self.lut_a_to_b_colorimetric.as_ref(),
859 RenderingIntent::Saturation => self.lut_a_to_b_saturation.as_ref(),
860 RenderingIntent::RelativeColorimetric => self.lut_a_to_b_colorimetric.as_ref(),
861 RenderingIntent::Perceptual => self.lut_a_to_b_perceptual.as_ref(),
862 }
863 }
864
865 pub(crate) fn get_pcs_to_device(&self, intent: RenderingIntent) -> Option<&LutWarehouse> {
866 match intent {
867 RenderingIntent::AbsoluteColorimetric => self.lut_b_to_a_colorimetric.as_ref(),
868 RenderingIntent::Saturation => self.lut_b_to_a_saturation.as_ref(),
869 RenderingIntent::RelativeColorimetric => self.lut_b_to_a_colorimetric.as_ref(),
870 RenderingIntent::Perceptual => self.lut_b_to_a_perceptual.as_ref(),
871 }
872 }
873}
874
875#[cfg(test)]
876mod tests {
877 use crate::{ColorProfile, DataColorSpace, Layout, RenderingIntent, TransformOptions};
878 use rand::Rng;
879
880 #[test]
881 fn test_transform_rgb8() {
882 let mut srgb_profile = ColorProfile::new_srgb();
883 let bt2020_profile = ColorProfile::new_bt2020();
884 let random_point_x = rand::rng().random_range(0..255);
885 let transform = bt2020_profile
886 .create_transform_8bit(
887 Layout::Rgb,
888 &srgb_profile,
889 Layout::Rgb,
890 TransformOptions::default(),
891 )
892 .unwrap();
893 let src = vec![random_point_x; 256 * 256 * 3];
894 let mut dst = vec![random_point_x; 256 * 256 * 3];
895 transform.transform(&src, &mut dst).unwrap();
896
897 let transform = bt2020_profile
898 .create_transform_8bit(
899 Layout::Rgb,
900 &srgb_profile,
901 Layout::Rgb,
902 TransformOptions {
903 ..TransformOptions::default()
904 },
905 )
906 .unwrap();
907 transform.transform(&src, &mut dst).unwrap();
908 srgb_profile.rendering_intent = RenderingIntent::RelativeColorimetric;
909 let transform = bt2020_profile
910 .create_transform_8bit(
911 Layout::Rgb,
912 &srgb_profile,
913 Layout::Rgb,
914 TransformOptions {
915 ..TransformOptions::default()
916 },
917 )
918 .unwrap();
919 transform.transform(&src, &mut dst).unwrap();
920 srgb_profile.rendering_intent = RenderingIntent::Saturation;
921 let transform = bt2020_profile
922 .create_transform_8bit(
923 Layout::Rgb,
924 &srgb_profile,
925 Layout::Rgb,
926 TransformOptions {
927 ..TransformOptions::default()
928 },
929 )
930 .unwrap();
931 transform.transform(&src, &mut dst).unwrap();
932 }
933
934 #[test]
935 fn test_transform_rgba8() {
936 let srgb_profile = ColorProfile::new_srgb();
937 let bt2020_profile = ColorProfile::new_bt2020();
938 let random_point_x = rand::rng().random_range(0..255);
939 let transform = bt2020_profile
940 .create_transform_8bit(
941 Layout::Rgba,
942 &srgb_profile,
943 Layout::Rgba,
944 TransformOptions::default(),
945 )
946 .unwrap();
947 let src = vec![random_point_x; 256 * 256 * 4];
948 let mut dst = vec![random_point_x; 256 * 256 * 4];
949 transform.transform(&src, &mut dst).unwrap();
950 }
951
952 #[test]
953 fn test_transform_gray_to_rgb8() {
954 let gray_profile = ColorProfile::new_gray_with_gamma(2.2f32);
955 let bt2020_profile = ColorProfile::new_bt2020();
956 let random_point_x = rand::rng().random_range(0..255);
957 let transform = gray_profile
958 .create_transform_8bit(
959 Layout::Gray,
960 &bt2020_profile,
961 Layout::Rgb,
962 TransformOptions::default(),
963 )
964 .unwrap();
965 let src = vec![random_point_x; 256 * 256];
966 let mut dst = vec![random_point_x; 256 * 256 * 3];
967 transform.transform(&src, &mut dst).unwrap();
968 }
969
970 #[test]
971 fn test_transform_gray_to_rgba8() {
972 let srgb_profile = ColorProfile::new_gray_with_gamma(2.2f32);
973 let bt2020_profile = ColorProfile::new_bt2020();
974 let random_point_x = rand::rng().random_range(0..255);
975 let transform = srgb_profile
976 .create_transform_8bit(
977 Layout::Gray,
978 &bt2020_profile,
979 Layout::Rgba,
980 TransformOptions::default(),
981 )
982 .unwrap();
983 let src = vec![random_point_x; 256 * 256];
984 let mut dst = vec![random_point_x; 256 * 256 * 4];
985 transform.transform(&src, &mut dst).unwrap();
986 }
987
988 #[test]
989 fn test_transform_gray_to_gray_alpha8() {
990 let srgb_profile = ColorProfile::new_gray_with_gamma(2.2f32);
991 let bt2020_profile = ColorProfile::new_bt2020();
992 let random_point_x = rand::rng().random_range(0..255);
993 let transform = srgb_profile
994 .create_transform_8bit(
995 Layout::Gray,
996 &bt2020_profile,
997 Layout::GrayAlpha,
998 TransformOptions::default(),
999 )
1000 .unwrap();
1001 let src = vec![random_point_x; 256 * 256];
1002 let mut dst = vec![random_point_x; 256 * 256 * 2];
1003 transform.transform(&src, &mut dst).unwrap();
1004 }
1005
1006 #[test]
1007 fn test_transform_rgb10() {
1008 let srgb_profile = ColorProfile::new_srgb();
1009 let bt2020_profile = ColorProfile::new_bt2020();
1010 let random_point_x = rand::rng().random_range(0..((1 << 10) - 1));
1011 let transform = bt2020_profile
1012 .create_transform_10bit(
1013 Layout::Rgb,
1014 &srgb_profile,
1015 Layout::Rgb,
1016 TransformOptions::default(),
1017 )
1018 .unwrap();
1019 let src = vec![random_point_x; 256 * 256 * 3];
1020 let mut dst = vec![random_point_x; 256 * 256 * 3];
1021 transform.transform(&src, &mut dst).unwrap();
1022 }
1023
1024 #[test]
1025 fn test_transform_rgb12() {
1026 let srgb_profile = ColorProfile::new_srgb();
1027 let bt2020_profile = ColorProfile::new_bt2020();
1028 let random_point_x = rand::rng().random_range(0..((1 << 12) - 1));
1029 let transform = bt2020_profile
1030 .create_transform_12bit(
1031 Layout::Rgb,
1032 &srgb_profile,
1033 Layout::Rgb,
1034 TransformOptions::default(),
1035 )
1036 .unwrap();
1037 let src = vec![random_point_x; 256 * 256 * 3];
1038 let mut dst = vec![random_point_x; 256 * 256 * 3];
1039 transform.transform(&src, &mut dst).unwrap();
1040 }
1041
1042 #[test]
1043 fn test_transform_rgb16() {
1044 let srgb_profile = ColorProfile::new_srgb();
1045 let bt2020_profile = ColorProfile::new_bt2020();
1046 let random_point_x = rand::rng().random_range(0..((1u32 << 16u32) - 1u32)) as u16;
1047 let transform = bt2020_profile
1048 .create_transform_16bit(
1049 Layout::Rgb,
1050 &srgb_profile,
1051 Layout::Rgb,
1052 TransformOptions::default(),
1053 )
1054 .unwrap();
1055 let src = vec![random_point_x; 256 * 256 * 3];
1056 let mut dst = vec![random_point_x; 256 * 256 * 3];
1057 transform.transform(&src, &mut dst).unwrap();
1058 }
1059
1060 #[test]
1061 fn test_transform_round_trip_rgb8() {
1062 let srgb_profile = ColorProfile::new_srgb();
1063 let bt2020_profile = ColorProfile::new_bt2020();
1064 let transform = srgb_profile
1065 .create_transform_8bit(
1066 Layout::Rgb,
1067 &bt2020_profile,
1068 Layout::Rgb,
1069 TransformOptions::default(),
1070 )
1071 .unwrap();
1072 let mut src = vec![0u8; 256 * 256 * 3];
1073 for dst in src.chunks_exact_mut(3) {
1074 dst[0] = 175;
1075 dst[1] = 75;
1076 dst[2] = 13;
1077 }
1078 let mut dst = vec![0u8; 256 * 256 * 3];
1079 transform.transform(&src, &mut dst).unwrap();
1080
1081 let transform_inverse = bt2020_profile
1082 .create_transform_8bit(
1083 Layout::Rgb,
1084 &srgb_profile,
1085 Layout::Rgb,
1086 TransformOptions::default(),
1087 )
1088 .unwrap();
1089
1090 transform_inverse.transform(&dst, &mut src).unwrap();
1091
1092 for src in src.chunks_exact_mut(3) {
1093 let diff0 = (src[0] as i32 - 175).abs();
1094 let diff1 = (src[1] as i32 - 75).abs();
1095 let diff2 = (src[2] as i32 - 13).abs();
1096 assert!(
1097 diff0 < 3,
1098 "On channel 0 difference should be less than 3, but it was {diff0}"
1099 );
1100 assert!(
1101 diff1 < 3,
1102 "On channel 1 difference should be less than 3, but it was {diff1}"
1103 );
1104 assert!(
1105 diff2 < 3,
1106 "On channel 2 difference should be less than 3, but it was {diff2}"
1107 );
1108 }
1109 }
1110
1111 #[test]
1112 fn test_transform_round_trip_rgb10() {
1113 let srgb_profile = ColorProfile::new_srgb();
1114 let bt2020_profile = ColorProfile::new_bt2020();
1115 let transform = srgb_profile
1116 .create_transform_10bit(
1117 Layout::Rgb,
1118 &bt2020_profile,
1119 Layout::Rgb,
1120 TransformOptions::default(),
1121 )
1122 .unwrap();
1123 let mut src = vec![0u16; 256 * 256 * 3];
1124 for dst in src.chunks_exact_mut(3) {
1125 dst[0] = 175;
1126 dst[1] = 256;
1127 dst[2] = 512;
1128 }
1129 let mut dst = vec![0u16; 256 * 256 * 3];
1130 transform.transform(&src, &mut dst).unwrap();
1131
1132 let transform_inverse = bt2020_profile
1133 .create_transform_10bit(
1134 Layout::Rgb,
1135 &srgb_profile,
1136 Layout::Rgb,
1137 TransformOptions::default(),
1138 )
1139 .unwrap();
1140
1141 transform_inverse.transform(&dst, &mut src).unwrap();
1142
1143 for src in src.chunks_exact_mut(3) {
1144 let diff0 = (src[0] as i32 - 175).abs();
1145 let diff1 = (src[1] as i32 - 256).abs();
1146 let diff2 = (src[2] as i32 - 512).abs();
1147 assert!(
1148 diff0 < 15,
1149 "On channel 0 difference should be less than 15, but it was {diff0}"
1150 );
1151 assert!(
1152 diff1 < 15,
1153 "On channel 1 difference should be less than 15, but it was {diff1}"
1154 );
1155 assert!(
1156 diff2 < 15,
1157 "On channel 2 difference should be less than 15, but it was {diff2}"
1158 );
1159 }
1160 }
1161
1162 #[test]
1163 fn test_transform_round_trip_rgb12() {
1164 let srgb_profile = ColorProfile::new_srgb();
1165 let bt2020_profile = ColorProfile::new_bt2020();
1166 let transform = srgb_profile
1167 .create_transform_12bit(
1168 Layout::Rgb,
1169 &bt2020_profile,
1170 Layout::Rgb,
1171 TransformOptions::default(),
1172 )
1173 .unwrap();
1174 let mut src = vec![0u16; 256 * 256 * 3];
1175 for dst in src.chunks_exact_mut(3) {
1176 dst[0] = 1750;
1177 dst[1] = 2560;
1178 dst[2] = 3143;
1179 }
1180 let mut dst = vec![0u16; 256 * 256 * 3];
1181 transform.transform(&src, &mut dst).unwrap();
1182
1183 let transform_inverse = bt2020_profile
1184 .create_transform_12bit(
1185 Layout::Rgb,
1186 &srgb_profile,
1187 Layout::Rgb,
1188 TransformOptions::default(),
1189 )
1190 .unwrap();
1191
1192 transform_inverse.transform(&dst, &mut src).unwrap();
1193
1194 for src in src.chunks_exact_mut(3) {
1195 let diff0 = (src[0] as i32 - 1750).abs();
1196 let diff1 = (src[1] as i32 - 2560).abs();
1197 let diff2 = (src[2] as i32 - 3143).abs();
1198 assert!(
1199 diff0 < 25,
1200 "On channel 0 difference should be less than 25, but it was {diff0}"
1201 );
1202 assert!(
1203 diff1 < 25,
1204 "On channel 1 difference should be less than 25, but it was {diff1}"
1205 );
1206 assert!(
1207 diff2 < 25,
1208 "On channel 2 difference should be less than 25, but it was {diff2}"
1209 );
1210 }
1211 }
1212
1213 #[test]
1214 fn test_transform_round_trip_rgb16() {
1215 let srgb_profile = ColorProfile::new_srgb();
1216 let bt2020_profile = ColorProfile::new_bt2020();
1217 let transform = srgb_profile
1218 .create_transform_16bit(
1219 Layout::Rgb,
1220 &bt2020_profile,
1221 Layout::Rgb,
1222 TransformOptions::default(),
1223 )
1224 .unwrap();
1225 let mut src = vec![0u16; 256 * 256 * 3];
1226 for dst in src.chunks_exact_mut(3) {
1227 dst[0] = 1760;
1228 dst[1] = 2560;
1229 dst[2] = 5120;
1230 }
1231 let mut dst = vec![0u16; 256 * 256 * 3];
1232 transform.transform(&src, &mut dst).unwrap();
1233
1234 let transform_inverse = bt2020_profile
1235 .create_transform_16bit(
1236 Layout::Rgb,
1237 &srgb_profile,
1238 Layout::Rgb,
1239 TransformOptions::default(),
1240 )
1241 .unwrap();
1242
1243 transform_inverse.transform(&dst, &mut src).unwrap();
1244
1245 for src in src.chunks_exact_mut(3) {
1246 let diff0 = (src[0] as i32 - 1760).abs();
1247 let diff1 = (src[1] as i32 - 2560).abs();
1248 let diff2 = (src[2] as i32 - 5120).abs();
1249 assert!(
1250 diff0 < 35,
1251 "On channel 0 difference should be less than 35, but it was {diff0}"
1252 );
1253 assert!(
1254 diff1 < 35,
1255 "On channel 1 difference should be less than 35, but it was {diff1}"
1256 );
1257 assert!(
1258 diff2 < 35,
1259 "On channel 2 difference should be less than 35, but it was {diff2}"
1260 );
1261 }
1262 }
1263
1264 #[test]
1265 fn test_transform_rgb_to_gray_extended() {
1266 let srgb = ColorProfile::new_srgb();
1267 let mut gray_profile = ColorProfile::new_gray_with_gamma(1.0);
1268 gray_profile.color_space = DataColorSpace::Gray;
1269 gray_profile.gray_trc = srgb.red_trc.clone();
1270 let mut test_profile = vec![0.; 4];
1271 test_profile[2] = 1.;
1272 let mut dst = vec![0.; 1];
1273
1274 let mut inverse = vec![0.; 4];
1275
1276 let cvt0 = srgb
1277 .create_transform_f32(
1278 Layout::Rgba,
1279 &gray_profile,
1280 Layout::Gray,
1281 TransformOptions {
1282 allow_extended_range_rgb_xyz: true,
1283 ..Default::default()
1284 },
1285 )
1286 .unwrap();
1287 cvt0.transform(&test_profile, &mut dst).unwrap();
1288 assert!((dst[0] - 0.273046) < 1e-4);
1289
1290 let cvt_inverse = gray_profile
1291 .create_transform_f32(
1292 Layout::Gray,
1293 &srgb,
1294 Layout::Rgba,
1295 TransformOptions {
1296 allow_extended_range_rgb_xyz: false,
1297 ..Default::default()
1298 },
1299 )
1300 .unwrap();
1301 cvt_inverse.transform(&dst, &mut inverse).unwrap();
1302 assert!((inverse[0] - 0.273002833) < 1e-4);
1303
1304 let cvt1 = srgb
1305 .create_transform_f32(
1306 Layout::Rgba,
1307 &gray_profile,
1308 Layout::Gray,
1309 TransformOptions {
1310 allow_extended_range_rgb_xyz: false,
1311 ..Default::default()
1312 },
1313 )
1314 .unwrap();
1315 cvt1.transform(&test_profile, &mut dst).unwrap();
1316 assert!((dst[0] - 0.27307168) < 1e-5);
1317
1318 inverse.fill(0.);
1319
1320 let cvt_inverse = gray_profile
1321 .create_transform_f32(
1322 Layout::Gray,
1323 &srgb,
1324 Layout::Rgba,
1325 TransformOptions {
1326 allow_extended_range_rgb_xyz: true,
1327 ..Default::default()
1328 },
1329 )
1330 .unwrap();
1331 cvt_inverse.transform(&dst, &mut inverse).unwrap();
1332 assert!((inverse[0] - 0.273002833) < 1e-4);
1333 }
1334}