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, BIT_DEPTH>(
526 src_layout, dst_layout, p,
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, BIT_DEPTH>(
552 src_layout, dst_layout, p,
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 make_gray_to_unfused::<T, LINEAR_CAP>(
739 src_layout,
740 dst_layout,
741 gray_linear,
742 red_gamma,
743 green_gamma,
744 blue_gamma,
745 BIT_DEPTH,
746 GAMMA_CAP,
747 )
748 }
749 }
750 } else if self.color_space == DataColorSpace::Rgb
751 && (dst_pr.color_space == DataColorSpace::Gray && dst_pr.gray_trc.is_some())
752 && dst_pr.pcs == DataColorSpace::Xyz
753 && self.pcs == DataColorSpace::Xyz
754 {
755 if src_layout == Layout::Gray || src_layout == Layout::GrayAlpha {
756 return Err(CmsError::InvalidLayout);
757 }
758 if dst_layout != Layout::Gray && dst_layout != Layout::GrayAlpha {
759 return Err(CmsError::InvalidLayout);
760 }
761
762 if self.has_device_to_pcs_lut() || dst_pr.has_pcs_to_device_lut() {
763 return make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>(
764 src_layout, self, dst_layout, dst_pr, options,
765 );
766 }
767
768 let transform = self.transform_matrix(dst_pr).to_f32();
769
770 let vector = Vector3f {
771 v: [transform.v[1][0], transform.v[1][1], transform.v[1][2]],
772 };
773
774 if !T::FINITE && options.allow_extended_range_rgb_xyz {
775 if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() {
776 if let Some(linear_evaluator) = self.try_extended_linearizing_evaluator() {
777 use crate::conversions::make_rgb_to_gray_extended;
778 return Ok(make_rgb_to_gray_extended::<T>(
779 src_layout,
780 dst_layout,
781 linear_evaluator,
782 gamma_evaluator,
783 vector,
784 BIT_DEPTH,
785 ));
786 }
787 }
788 }
789
790 let lin_r = self.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
791 options.allow_use_cicp_transfer,
792 )?;
793 let lin_g = self.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
794 options.allow_use_cicp_transfer,
795 )?;
796 let lin_b = self.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
797 options.allow_use_cicp_transfer,
798 )?;
799 let gray_linear = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
800 &dst_pr.gray_trc,
801 options.allow_use_cicp_transfer,
802 )?;
803
804 let trc_box = ToneReproductionRgbToGray::<T, LINEAR_CAP> {
805 r_linear: lin_r,
806 g_linear: lin_g,
807 b_linear: lin_b,
808 gray_gamma: gray_linear,
809 };
810
811 Ok(make_rgb_to_gray::<T, LINEAR_CAP, BIT_DEPTH, GAMMA_CAP>(
812 src_layout, dst_layout, trc_box, vector,
813 ))
814 } else if (self.color_space.is_three_channels()
815 || self.color_space == DataColorSpace::Cmyk
816 || self.color_space == DataColorSpace::Color4)
817 && (dst_pr.color_space.is_three_channels()
818 || dst_pr.color_space == DataColorSpace::Cmyk
819 || dst_pr.color_space == DataColorSpace::Color4)
820 && (dst_pr.pcs == DataColorSpace::Xyz || dst_pr.pcs == DataColorSpace::Lab)
821 && (self.pcs == DataColorSpace::Xyz || self.pcs == DataColorSpace::Lab)
822 {
823 if src_layout == Layout::Gray || src_layout == Layout::GrayAlpha {
824 return Err(CmsError::InvalidLayout);
825 }
826 if dst_layout == Layout::Gray || dst_layout == Layout::GrayAlpha {
827 return Err(CmsError::InvalidLayout);
828 }
829 make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>(
830 src_layout, self, dst_layout, dst_pr, options,
831 )
832 } else {
833 make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>(
834 src_layout, self, dst_layout, dst_pr, options,
835 )
836 }
837 }
838
839 pub fn create_transform_8bit(
842 &self,
843 src_layout: Layout,
844 dst_pr: &ColorProfile,
845 dst_layout: Layout,
846 options: TransformOptions,
847 ) -> Result<Box<Transform8BitExecutor>, CmsError> {
848 self.create_transform_nbit::<u8, 8, 256, 4096>(src_layout, dst_pr, dst_layout, options)
849 }
850
851 pub(crate) fn get_device_to_pcs(&self, intent: RenderingIntent) -> Option<&LutWarehouse> {
852 match intent {
853 RenderingIntent::AbsoluteColorimetric => self.lut_a_to_b_colorimetric.as_ref(),
854 RenderingIntent::Saturation => self.lut_a_to_b_saturation.as_ref(),
855 RenderingIntent::RelativeColorimetric => self.lut_a_to_b_colorimetric.as_ref(),
856 RenderingIntent::Perceptual => self.lut_a_to_b_perceptual.as_ref(),
857 }
858 }
859
860 pub(crate) fn get_pcs_to_device(&self, intent: RenderingIntent) -> Option<&LutWarehouse> {
861 match intent {
862 RenderingIntent::AbsoluteColorimetric => self.lut_b_to_a_colorimetric.as_ref(),
863 RenderingIntent::Saturation => self.lut_b_to_a_saturation.as_ref(),
864 RenderingIntent::RelativeColorimetric => self.lut_b_to_a_colorimetric.as_ref(),
865 RenderingIntent::Perceptual => self.lut_b_to_a_perceptual.as_ref(),
866 }
867 }
868}
869
870#[cfg(test)]
871mod tests {
872 use crate::{ColorProfile, DataColorSpace, Layout, RenderingIntent, TransformOptions};
873 use rand::Rng;
874
875 #[test]
876 fn test_transform_rgb8() {
877 let mut srgb_profile = ColorProfile::new_srgb();
878 let bt2020_profile = ColorProfile::new_bt2020();
879 let random_point_x = rand::rng().random_range(0..255);
880 let transform = bt2020_profile
881 .create_transform_8bit(
882 Layout::Rgb,
883 &srgb_profile,
884 Layout::Rgb,
885 TransformOptions::default(),
886 )
887 .unwrap();
888 let src = vec![random_point_x; 256 * 256 * 3];
889 let mut dst = vec![random_point_x; 256 * 256 * 3];
890 transform.transform(&src, &mut dst).unwrap();
891
892 let transform = bt2020_profile
893 .create_transform_8bit(
894 Layout::Rgb,
895 &srgb_profile,
896 Layout::Rgb,
897 TransformOptions {
898 ..TransformOptions::default()
899 },
900 )
901 .unwrap();
902 transform.transform(&src, &mut dst).unwrap();
903 srgb_profile.rendering_intent = RenderingIntent::RelativeColorimetric;
904 let transform = bt2020_profile
905 .create_transform_8bit(
906 Layout::Rgb,
907 &srgb_profile,
908 Layout::Rgb,
909 TransformOptions {
910 ..TransformOptions::default()
911 },
912 )
913 .unwrap();
914 transform.transform(&src, &mut dst).unwrap();
915 srgb_profile.rendering_intent = RenderingIntent::Saturation;
916 let transform = bt2020_profile
917 .create_transform_8bit(
918 Layout::Rgb,
919 &srgb_profile,
920 Layout::Rgb,
921 TransformOptions {
922 ..TransformOptions::default()
923 },
924 )
925 .unwrap();
926 transform.transform(&src, &mut dst).unwrap();
927 }
928
929 #[test]
930 fn test_transform_rgba8() {
931 let srgb_profile = ColorProfile::new_srgb();
932 let bt2020_profile = ColorProfile::new_bt2020();
933 let random_point_x = rand::rng().random_range(0..255);
934 let transform = bt2020_profile
935 .create_transform_8bit(
936 Layout::Rgba,
937 &srgb_profile,
938 Layout::Rgba,
939 TransformOptions::default(),
940 )
941 .unwrap();
942 let src = vec![random_point_x; 256 * 256 * 4];
943 let mut dst = vec![random_point_x; 256 * 256 * 4];
944 transform.transform(&src, &mut dst).unwrap();
945 }
946
947 #[test]
948 fn test_transform_gray_to_rgb8() {
949 let gray_profile = ColorProfile::new_gray_with_gamma(2.2f32);
950 let bt2020_profile = ColorProfile::new_bt2020();
951 let random_point_x = rand::rng().random_range(0..255);
952 let transform = gray_profile
953 .create_transform_8bit(
954 Layout::Gray,
955 &bt2020_profile,
956 Layout::Rgb,
957 TransformOptions::default(),
958 )
959 .unwrap();
960 let src = vec![random_point_x; 256 * 256];
961 let mut dst = vec![random_point_x; 256 * 256 * 3];
962 transform.transform(&src, &mut dst).unwrap();
963 }
964
965 #[test]
966 fn test_transform_gray_to_rgba8() {
967 let srgb_profile = ColorProfile::new_gray_with_gamma(2.2f32);
968 let bt2020_profile = ColorProfile::new_bt2020();
969 let random_point_x = rand::rng().random_range(0..255);
970 let transform = srgb_profile
971 .create_transform_8bit(
972 Layout::Gray,
973 &bt2020_profile,
974 Layout::Rgba,
975 TransformOptions::default(),
976 )
977 .unwrap();
978 let src = vec![random_point_x; 256 * 256];
979 let mut dst = vec![random_point_x; 256 * 256 * 4];
980 transform.transform(&src, &mut dst).unwrap();
981 }
982
983 #[test]
984 fn test_transform_gray_to_gray_alpha8() {
985 let srgb_profile = ColorProfile::new_gray_with_gamma(2.2f32);
986 let bt2020_profile = ColorProfile::new_bt2020();
987 let random_point_x = rand::rng().random_range(0..255);
988 let transform = srgb_profile
989 .create_transform_8bit(
990 Layout::Gray,
991 &bt2020_profile,
992 Layout::GrayAlpha,
993 TransformOptions::default(),
994 )
995 .unwrap();
996 let src = vec![random_point_x; 256 * 256];
997 let mut dst = vec![random_point_x; 256 * 256 * 2];
998 transform.transform(&src, &mut dst).unwrap();
999 }
1000
1001 #[test]
1002 fn test_transform_rgb10() {
1003 let srgb_profile = ColorProfile::new_srgb();
1004 let bt2020_profile = ColorProfile::new_bt2020();
1005 let random_point_x = rand::rng().random_range(0..((1 << 10) - 1));
1006 let transform = bt2020_profile
1007 .create_transform_10bit(
1008 Layout::Rgb,
1009 &srgb_profile,
1010 Layout::Rgb,
1011 TransformOptions::default(),
1012 )
1013 .unwrap();
1014 let src = vec![random_point_x; 256 * 256 * 3];
1015 let mut dst = vec![random_point_x; 256 * 256 * 3];
1016 transform.transform(&src, &mut dst).unwrap();
1017 }
1018
1019 #[test]
1020 fn test_transform_rgb12() {
1021 let srgb_profile = ColorProfile::new_srgb();
1022 let bt2020_profile = ColorProfile::new_bt2020();
1023 let random_point_x = rand::rng().random_range(0..((1 << 12) - 1));
1024 let transform = bt2020_profile
1025 .create_transform_12bit(
1026 Layout::Rgb,
1027 &srgb_profile,
1028 Layout::Rgb,
1029 TransformOptions::default(),
1030 )
1031 .unwrap();
1032 let src = vec![random_point_x; 256 * 256 * 3];
1033 let mut dst = vec![random_point_x; 256 * 256 * 3];
1034 transform.transform(&src, &mut dst).unwrap();
1035 }
1036
1037 #[test]
1038 fn test_transform_rgb16() {
1039 let srgb_profile = ColorProfile::new_srgb();
1040 let bt2020_profile = ColorProfile::new_bt2020();
1041 let random_point_x = rand::rng().random_range(0..((1u32 << 16u32) - 1u32)) as u16;
1042 let transform = bt2020_profile
1043 .create_transform_16bit(
1044 Layout::Rgb,
1045 &srgb_profile,
1046 Layout::Rgb,
1047 TransformOptions::default(),
1048 )
1049 .unwrap();
1050 let src = vec![random_point_x; 256 * 256 * 3];
1051 let mut dst = vec![random_point_x; 256 * 256 * 3];
1052 transform.transform(&src, &mut dst).unwrap();
1053 }
1054
1055 #[test]
1056 fn test_transform_round_trip_rgb8() {
1057 let srgb_profile = ColorProfile::new_srgb();
1058 let bt2020_profile = ColorProfile::new_bt2020();
1059 let transform = srgb_profile
1060 .create_transform_8bit(
1061 Layout::Rgb,
1062 &bt2020_profile,
1063 Layout::Rgb,
1064 TransformOptions::default(),
1065 )
1066 .unwrap();
1067 let mut src = vec![0u8; 256 * 256 * 3];
1068 for dst in src.chunks_exact_mut(3) {
1069 dst[0] = 175;
1070 dst[1] = 75;
1071 dst[2] = 13;
1072 }
1073 let mut dst = vec![0u8; 256 * 256 * 3];
1074 transform.transform(&src, &mut dst).unwrap();
1075
1076 let transform_inverse = bt2020_profile
1077 .create_transform_8bit(
1078 Layout::Rgb,
1079 &srgb_profile,
1080 Layout::Rgb,
1081 TransformOptions::default(),
1082 )
1083 .unwrap();
1084
1085 transform_inverse.transform(&dst, &mut src).unwrap();
1086
1087 for src in src.chunks_exact_mut(3) {
1088 let diff0 = (src[0] as i32 - 175).abs();
1089 let diff1 = (src[1] as i32 - 75).abs();
1090 let diff2 = (src[2] as i32 - 13).abs();
1091 assert!(
1092 diff0 < 3,
1093 "On channel 0 difference should be less than 3, but it was {diff0}"
1094 );
1095 assert!(
1096 diff1 < 3,
1097 "On channel 1 difference should be less than 3, but it was {diff1}"
1098 );
1099 assert!(
1100 diff2 < 3,
1101 "On channel 2 difference should be less than 3, but it was {diff2}"
1102 );
1103 }
1104 }
1105
1106 #[test]
1107 fn test_transform_round_trip_rgb10() {
1108 let srgb_profile = ColorProfile::new_srgb();
1109 let bt2020_profile = ColorProfile::new_bt2020();
1110 let transform = srgb_profile
1111 .create_transform_10bit(
1112 Layout::Rgb,
1113 &bt2020_profile,
1114 Layout::Rgb,
1115 TransformOptions::default(),
1116 )
1117 .unwrap();
1118 let mut src = vec![0u16; 256 * 256 * 3];
1119 for dst in src.chunks_exact_mut(3) {
1120 dst[0] = 175;
1121 dst[1] = 256;
1122 dst[2] = 512;
1123 }
1124 let mut dst = vec![0u16; 256 * 256 * 3];
1125 transform.transform(&src, &mut dst).unwrap();
1126
1127 let transform_inverse = bt2020_profile
1128 .create_transform_10bit(
1129 Layout::Rgb,
1130 &srgb_profile,
1131 Layout::Rgb,
1132 TransformOptions::default(),
1133 )
1134 .unwrap();
1135
1136 transform_inverse.transform(&dst, &mut src).unwrap();
1137
1138 for src in src.chunks_exact_mut(3) {
1139 let diff0 = (src[0] as i32 - 175).abs();
1140 let diff1 = (src[1] as i32 - 256).abs();
1141 let diff2 = (src[2] as i32 - 512).abs();
1142 assert!(
1143 diff0 < 15,
1144 "On channel 0 difference should be less than 15, but it was {diff0}"
1145 );
1146 assert!(
1147 diff1 < 15,
1148 "On channel 1 difference should be less than 15, but it was {diff1}"
1149 );
1150 assert!(
1151 diff2 < 15,
1152 "On channel 2 difference should be less than 15, but it was {diff2}"
1153 );
1154 }
1155 }
1156
1157 #[test]
1158 fn test_transform_round_trip_rgb12() {
1159 let srgb_profile = ColorProfile::new_srgb();
1160 let bt2020_profile = ColorProfile::new_bt2020();
1161 let transform = srgb_profile
1162 .create_transform_12bit(
1163 Layout::Rgb,
1164 &bt2020_profile,
1165 Layout::Rgb,
1166 TransformOptions::default(),
1167 )
1168 .unwrap();
1169 let mut src = vec![0u16; 256 * 256 * 3];
1170 for dst in src.chunks_exact_mut(3) {
1171 dst[0] = 1750;
1172 dst[1] = 2560;
1173 dst[2] = 3143;
1174 }
1175 let mut dst = vec![0u16; 256 * 256 * 3];
1176 transform.transform(&src, &mut dst).unwrap();
1177
1178 let transform_inverse = bt2020_profile
1179 .create_transform_12bit(
1180 Layout::Rgb,
1181 &srgb_profile,
1182 Layout::Rgb,
1183 TransformOptions::default(),
1184 )
1185 .unwrap();
1186
1187 transform_inverse.transform(&dst, &mut src).unwrap();
1188
1189 for src in src.chunks_exact_mut(3) {
1190 let diff0 = (src[0] as i32 - 1750).abs();
1191 let diff1 = (src[1] as i32 - 2560).abs();
1192 let diff2 = (src[2] as i32 - 3143).abs();
1193 assert!(
1194 diff0 < 25,
1195 "On channel 0 difference should be less than 25, but it was {diff0}"
1196 );
1197 assert!(
1198 diff1 < 25,
1199 "On channel 1 difference should be less than 25, but it was {diff1}"
1200 );
1201 assert!(
1202 diff2 < 25,
1203 "On channel 2 difference should be less than 25, but it was {diff2}"
1204 );
1205 }
1206 }
1207
1208 #[test]
1209 fn test_transform_round_trip_rgb16() {
1210 let srgb_profile = ColorProfile::new_srgb();
1211 let bt2020_profile = ColorProfile::new_bt2020();
1212 let transform = srgb_profile
1213 .create_transform_16bit(
1214 Layout::Rgb,
1215 &bt2020_profile,
1216 Layout::Rgb,
1217 TransformOptions::default(),
1218 )
1219 .unwrap();
1220 let mut src = vec![0u16; 256 * 256 * 3];
1221 for dst in src.chunks_exact_mut(3) {
1222 dst[0] = 1760;
1223 dst[1] = 2560;
1224 dst[2] = 5120;
1225 }
1226 let mut dst = vec![0u16; 256 * 256 * 3];
1227 transform.transform(&src, &mut dst).unwrap();
1228
1229 let transform_inverse = bt2020_profile
1230 .create_transform_16bit(
1231 Layout::Rgb,
1232 &srgb_profile,
1233 Layout::Rgb,
1234 TransformOptions::default(),
1235 )
1236 .unwrap();
1237
1238 transform_inverse.transform(&dst, &mut src).unwrap();
1239
1240 for src in src.chunks_exact_mut(3) {
1241 let diff0 = (src[0] as i32 - 1760).abs();
1242 let diff1 = (src[1] as i32 - 2560).abs();
1243 let diff2 = (src[2] as i32 - 5120).abs();
1244 assert!(
1245 diff0 < 35,
1246 "On channel 0 difference should be less than 35, but it was {diff0}"
1247 );
1248 assert!(
1249 diff1 < 35,
1250 "On channel 1 difference should be less than 35, but it was {diff1}"
1251 );
1252 assert!(
1253 diff2 < 35,
1254 "On channel 2 difference should be less than 35, but it was {diff2}"
1255 );
1256 }
1257 }
1258
1259 #[test]
1260 fn test_transform_rgb_to_gray_extended() {
1261 let srgb = ColorProfile::new_srgb();
1262 let mut gray_profile = ColorProfile::new_gray_with_gamma(1.0);
1263 gray_profile.color_space = DataColorSpace::Gray;
1264 gray_profile.gray_trc = srgb.red_trc.clone();
1265 let mut test_profile = vec![0.; 4];
1266 test_profile[2] = 1.;
1267 let mut dst = vec![0.; 1];
1268
1269 let mut inverse = vec![0.; 4];
1270
1271 let cvt0 = srgb
1272 .create_transform_f32(
1273 Layout::Rgba,
1274 &gray_profile,
1275 Layout::Gray,
1276 TransformOptions {
1277 allow_extended_range_rgb_xyz: true,
1278 ..Default::default()
1279 },
1280 )
1281 .unwrap();
1282 cvt0.transform(&test_profile, &mut dst).unwrap();
1283 assert!((dst[0] - 0.273046) < 1e-4);
1284
1285 let cvt_inverse = gray_profile
1286 .create_transform_f32(
1287 Layout::Gray,
1288 &srgb,
1289 Layout::Rgba,
1290 TransformOptions {
1291 allow_extended_range_rgb_xyz: false,
1292 ..Default::default()
1293 },
1294 )
1295 .unwrap();
1296 cvt_inverse.transform(&dst, &mut inverse).unwrap();
1297 assert!((inverse[0] - 0.273002833) < 1e-4);
1298
1299 let cvt1 = srgb
1300 .create_transform_f32(
1301 Layout::Rgba,
1302 &gray_profile,
1303 Layout::Gray,
1304 TransformOptions {
1305 allow_extended_range_rgb_xyz: false,
1306 ..Default::default()
1307 },
1308 )
1309 .unwrap();
1310 cvt1.transform(&test_profile, &mut dst).unwrap();
1311 assert!((dst[0] - 0.27307168) < 1e-5);
1312
1313 inverse.fill(0.);
1314
1315 let cvt_inverse = gray_profile
1316 .create_transform_f32(
1317 Layout::Gray,
1318 &srgb,
1319 Layout::Rgba,
1320 TransformOptions {
1321 allow_extended_range_rgb_xyz: true,
1322 ..Default::default()
1323 },
1324 )
1325 .unwrap();
1326 cvt_inverse.transform(&dst, &mut inverse).unwrap();
1327 assert!((inverse[0] - 0.273002833) < 1e-4);
1328 }
1329}