1use crate::conversions::*;
30use crate::err::CmsError;
31use crate::interceptors::{FromCmykaInterceptor, ToCmykaInterceptor};
32use crate::trc::GammaLutInterpolate;
33use crate::{ColorProfile, DataColorSpace, LutWarehouse, RenderingIntent, Vector3f, Xyzd};
34use num_traits::AsPrimitive;
35use std::sync::Arc;
36
37pub trait TransformExecutor<V: Copy + Default> {
39 fn transform(&self, src: &[V], dst: &mut [V]) -> Result<(), CmsError>;
42}
43
44pub trait Stage {
46 fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError>;
47}
48
49pub trait InPlaceStage {
51 fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError>;
52}
53
54pub trait InPlaceTransformExecutor<V: Copy + Default> {
55 fn transform(&self, in_out: &mut [V]) -> Result<(), CmsError>;
56}
57
58#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default)]
62pub enum BarycentricWeightScale {
63 #[default]
64 Low,
69 #[cfg(feature = "options")]
70 High,
71}
72
73#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
75pub struct TransformOptions {
76 pub rendering_intent: RenderingIntent,
77 pub allow_use_cicp_transfer: bool,
80 pub prefer_fixed_point: bool,
90 pub interpolation_method: InterpolationMethod,
102 pub barycentric_weight_scale: BarycentricWeightScale,
106 #[cfg(feature = "extended_range")]
111 pub allow_extended_range_rgb_xyz: bool,
112 }
114
115#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default)]
116pub enum InterpolationMethod {
123 #[cfg(feature = "options")]
126 Tetrahedral,
127 #[cfg(feature = "options")]
129 Pyramid,
130 #[cfg(feature = "options")]
132 Prism,
133 #[default]
135 Linear,
136}
137
138impl Default for TransformOptions {
139 fn default() -> Self {
140 Self {
141 rendering_intent: RenderingIntent::default(),
142 allow_use_cicp_transfer: true,
143 prefer_fixed_point: true,
144 interpolation_method: InterpolationMethod::default(),
145 barycentric_weight_scale: BarycentricWeightScale::default(),
146 #[cfg(feature = "extended_range")]
147 allow_extended_range_rgb_xyz: false,
148 }
150 }
151}
152
153pub type Transform8BitExecutor = dyn TransformExecutor<u8> + Send + Sync;
154pub type Transform16BitExecutor = dyn TransformExecutor<u16> + Send + Sync;
155pub type TransformF32Executor = dyn TransformExecutor<f32> + Send + Sync;
156pub type TransformF64Executor = dyn TransformExecutor<f64> + Send + Sync;
157
158#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
163pub enum Layout {
164 Rgb = 0,
165 Rgba = 1,
166 Cmyka = 16,
167 Gray = 2,
168 GrayAlpha = 3,
169 Inks5 = 4,
170 Inks6 = 5,
171 Inks7 = 6,
172 Inks8 = 7,
173 Inks9 = 8,
174 Inks10 = 9,
175 Inks11 = 10,
176 Inks12 = 11,
177 Inks13 = 12,
178 Inks14 = 13,
179 Inks15 = 14,
180}
181
182impl Layout {
183 #[inline(always)]
185 pub const fn r_i(self) -> usize {
186 match self {
187 Layout::Rgb => 0,
188 Layout::Rgba => 0,
189 Layout::Gray => unimplemented!(),
190 Layout::GrayAlpha => unimplemented!(),
191 _ => unimplemented!(),
192 }
193 }
194
195 #[inline(always)]
197 pub const fn g_i(self) -> usize {
198 match self {
199 Layout::Rgb => 1,
200 Layout::Rgba => 1,
201 Layout::Gray => unimplemented!(),
202 Layout::GrayAlpha => unimplemented!(),
203 _ => unimplemented!(),
204 }
205 }
206
207 #[inline(always)]
209 pub const fn b_i(self) -> usize {
210 match self {
211 Layout::Rgb => 2,
212 Layout::Rgba => 2,
213 Layout::Gray => unimplemented!(),
214 Layout::GrayAlpha => unimplemented!(),
215 _ => unimplemented!(),
216 }
217 }
218
219 #[inline(always)]
220 pub const fn a_i(self) -> usize {
221 match self {
222 Layout::Rgb => unimplemented!(),
223 Layout::Rgba => 3,
224 Layout::Gray => unimplemented!(),
225 Layout::GrayAlpha => 1,
226 _ => unimplemented!(),
227 }
228 }
229
230 #[inline(always)]
231 pub const fn has_alpha(self) -> bool {
232 match self {
233 Layout::Rgb => false,
234 Layout::Rgba => true,
235 Layout::Gray => false,
236 Layout::GrayAlpha => true,
237 _ => false,
238 }
239 }
240
241 #[inline]
242 pub const fn channels(self) -> usize {
243 match self {
244 Layout::Rgb => 3,
245 Layout::Rgba => 4,
246 Layout::Gray => 1,
247 Layout::GrayAlpha => 2,
248 Layout::Cmyka => 5,
249 Layout::Inks5 => 5,
250 Layout::Inks6 => 6,
251 Layout::Inks7 => 7,
252 Layout::Inks8 => 8,
253 Layout::Inks9 => 9,
254 Layout::Inks10 => 10,
255 Layout::Inks11 => 11,
256 Layout::Inks12 => 12,
257 Layout::Inks13 => 13,
258 Layout::Inks14 => 14,
259 Layout::Inks15 => 15,
260 }
261 }
262
263 #[cfg(feature = "any_to_any")]
264 pub(crate) fn from_inks(inks: usize) -> Self {
265 match inks {
266 1 => Layout::Gray,
267 2 => Layout::GrayAlpha,
268 3 => Layout::Rgb,
269 4 => Layout::Rgba,
270 5 => Layout::Inks5,
271 6 => Layout::Inks6,
272 7 => Layout::Inks7,
273 8 => Layout::Inks8,
274 9 => Layout::Inks9,
275 10 => Layout::Inks10,
276 11 => Layout::Inks11,
277 12 => Layout::Inks12,
278 13 => Layout::Inks13,
279 14 => Layout::Inks14,
280 15 => Layout::Inks15,
281 _ => unreachable!("Impossible amount of inks"),
282 }
283 }
284}
285
286impl From<u8> for Layout {
287 fn from(value: u8) -> Self {
288 match value {
289 0 => Layout::Rgb,
290 1 => Layout::Rgba,
291 2 => Layout::Gray,
292 3 => Layout::GrayAlpha,
293 _ => unimplemented!(),
294 }
295 }
296}
297
298impl Layout {
299 #[inline(always)]
300 pub const fn resolve(value: u8) -> Self {
301 match value {
302 0 => Layout::Rgb,
303 1 => Layout::Rgba,
304 2 => Layout::Gray,
305 3 => Layout::GrayAlpha,
306 4 => Layout::Inks5,
307 5 => Layout::Inks6,
308 6 => Layout::Inks7,
309 7 => Layout::Inks8,
310 8 => Layout::Inks9,
311 9 => Layout::Inks10,
312 10 => Layout::Inks11,
313 11 => Layout::Inks12,
314 12 => Layout::Inks13,
315 13 => Layout::Inks14,
316 14 => Layout::Inks15,
317 _ => unimplemented!(),
318 }
319 }
320}
321
322#[doc(hidden)]
323pub trait PointeeSizeExpressible {
324 fn _as_usize(self) -> usize;
325 const FINITE: bool;
326 const NOT_FINITE_GAMMA_TABLE_SIZE: usize;
327 const NOT_FINITE_LINEAR_TABLE_SIZE: usize;
328 const IS_U8: bool;
329 const IS_U16: bool;
330}
331
332impl PointeeSizeExpressible for u8 {
333 #[inline(always)]
334 fn _as_usize(self) -> usize {
335 self as usize
336 }
337
338 const FINITE: bool = true;
339 const NOT_FINITE_GAMMA_TABLE_SIZE: usize = 1;
340 const NOT_FINITE_LINEAR_TABLE_SIZE: usize = 1;
341 const IS_U8: bool = true;
342 const IS_U16: bool = false;
343}
344
345impl PointeeSizeExpressible for u16 {
346 #[inline(always)]
347 fn _as_usize(self) -> usize {
348 self as usize
349 }
350
351 const FINITE: bool = true;
352
353 const NOT_FINITE_GAMMA_TABLE_SIZE: usize = 1;
354 const NOT_FINITE_LINEAR_TABLE_SIZE: usize = 1;
355
356 const IS_U8: bool = false;
357 const IS_U16: bool = true;
358}
359
360impl PointeeSizeExpressible for f32 {
361 #[inline(always)]
362 fn _as_usize(self) -> usize {
363 const MAX_14_BIT: f32 = ((1 << 14u32) - 1) as f32;
364 ((self * MAX_14_BIT).max(0f32).min(MAX_14_BIT) as u16) as usize
365 }
366
367 const FINITE: bool = false;
368
369 const NOT_FINITE_GAMMA_TABLE_SIZE: usize = 32768;
370 const NOT_FINITE_LINEAR_TABLE_SIZE: usize = 1 << 14u32;
371 const IS_U8: bool = false;
372 const IS_U16: bool = false;
373}
374
375impl PointeeSizeExpressible for f64 {
376 #[inline(always)]
377 fn _as_usize(self) -> usize {
378 const MAX_16_BIT: f64 = ((1 << 16u32) - 1) as f64;
379 ((self * MAX_16_BIT).max(0.).min(MAX_16_BIT) as u16) as usize
380 }
381
382 const FINITE: bool = false;
383
384 const NOT_FINITE_GAMMA_TABLE_SIZE: usize = 65536;
385 const NOT_FINITE_LINEAR_TABLE_SIZE: usize = 1 << 16;
386 const IS_U8: bool = false;
387 const IS_U16: bool = false;
388}
389
390impl ColorProfile {
391 pub fn is_matrix_shaper(&self) -> bool {
393 self.color_space == DataColorSpace::Rgb
394 && self.red_colorant != Xyzd::default()
395 && self.green_colorant != Xyzd::default()
396 && self.blue_colorant != Xyzd::default()
397 && self.red_trc.is_some()
398 && self.green_trc.is_some()
399 && self.blue_trc.is_some()
400 }
401
402 pub fn create_transform_16bit(
405 &self,
406 src_layout: Layout,
407 dst_pr: &ColorProfile,
408 dst_layout: Layout,
409 options: TransformOptions,
410 ) -> Result<Arc<Transform16BitExecutor>, CmsError> {
411 let mut core_src_layout = src_layout;
412 if src_layout == Layout::Cmyka {
413 core_src_layout = Layout::Rgba;
414 }
415 let mut core_dst_layout = dst_layout;
416 if dst_layout == Layout::Cmyka {
417 core_dst_layout = Layout::Rgba;
418 }
419 let handle = self.create_transform_nbit::<u16, 16, 65536, 65536>(
420 core_src_layout,
421 dst_pr,
422 core_dst_layout,
423 options,
424 )?;
425 if core_src_layout == Layout::Cmyka {
426 return Ok(Arc::new(FromCmykaInterceptor::install(handle, dst_layout)));
427 } else if core_dst_layout == Layout::Cmyka {
428 return Ok(Arc::new(ToCmykaInterceptor::install(handle, dst_layout)));
429 }
430 Ok(handle)
431 }
432
433 #[cfg(feature = "in_place")]
438 pub fn create_in_place_transform_16bit(
439 &self,
440 layout: Layout,
441 dst_pr: &ColorProfile,
442 options: TransformOptions,
443 ) -> Result<Arc<dyn InPlaceTransformExecutor<u16> + Send + Sync>, CmsError> {
444 self.create_transform_in_place_nbit::<u16, 16, 65536, 65536>(layout, dst_pr, options)
445 }
446
447 pub fn create_transform_12bit(
450 &self,
451 src_layout: Layout,
452 dst_pr: &ColorProfile,
453 dst_layout: Layout,
454 options: TransformOptions,
455 ) -> Result<Arc<Transform16BitExecutor>, CmsError> {
456 let mut core_src_layout = src_layout;
457 if src_layout == Layout::Cmyka {
458 core_src_layout = Layout::Rgba;
459 }
460 let mut core_dst_layout = dst_layout;
461 if dst_layout == Layout::Cmyka {
462 core_dst_layout = Layout::Rgba;
463 }
464 let handle = self.create_transform_nbit::<u16, 12, 65536, 16384>(
465 core_src_layout,
466 dst_pr,
467 core_dst_layout,
468 options,
469 )?;
470 if core_src_layout == Layout::Cmyka {
471 return Ok(Arc::new(FromCmykaInterceptor::install(handle, dst_layout)));
472 } else if core_dst_layout == Layout::Cmyka {
473 return Ok(Arc::new(ToCmykaInterceptor::install(handle, dst_layout)));
474 }
475 Ok(handle)
476 }
477
478 #[cfg(feature = "in_place")]
483 pub fn create_in_place_transform_12bit(
484 &self,
485 layout: Layout,
486 dst_pr: &ColorProfile,
487 options: TransformOptions,
488 ) -> Result<Arc<dyn InPlaceTransformExecutor<u16> + Send + Sync>, CmsError> {
489 self.create_transform_in_place_nbit::<u16, 12, 65536, 16384>(layout, dst_pr, options)
490 }
491
492 pub fn create_transform_10bit(
495 &self,
496 src_layout: Layout,
497 dst_pr: &ColorProfile,
498 dst_layout: Layout,
499 options: TransformOptions,
500 ) -> Result<Arc<Transform16BitExecutor>, CmsError> {
501 let mut core_src_layout = src_layout;
502 if src_layout == Layout::Cmyka {
503 core_src_layout = Layout::Rgba;
504 }
505 let mut core_dst_layout = dst_layout;
506 if dst_layout == Layout::Cmyka {
507 core_dst_layout = Layout::Rgba;
508 }
509 let handle = self.create_transform_nbit::<u16, 10, 65536, 8192>(
510 core_src_layout,
511 dst_pr,
512 core_dst_layout,
513 options,
514 )?;
515 if core_src_layout == Layout::Cmyka {
516 return Ok(Arc::new(FromCmykaInterceptor::install(handle, dst_layout)));
517 } else if core_dst_layout == Layout::Cmyka {
518 return Ok(Arc::new(ToCmykaInterceptor::install(handle, dst_layout)));
519 }
520 Ok(handle)
521 }
522
523 #[cfg(feature = "in_place")]
528 pub fn create_in_place_transform_10bit(
529 &self,
530 layout: Layout,
531 dst_pr: &ColorProfile,
532 options: TransformOptions,
533 ) -> Result<Arc<dyn InPlaceTransformExecutor<u16> + Send + Sync>, CmsError> {
534 self.create_transform_in_place_nbit::<u16, 10, 65536, 8192>(layout, dst_pr, options)
535 }
536
537 pub fn create_transform_f32(
544 &self,
545 src_layout: Layout,
546 dst_pr: &ColorProfile,
547 dst_layout: Layout,
548 options: TransformOptions,
549 ) -> Result<Arc<TransformF32Executor>, CmsError> {
550 let mut core_src_layout = src_layout;
551 if src_layout == Layout::Cmyka {
552 core_src_layout = Layout::Rgba;
553 }
554 let mut core_dst_layout = dst_layout;
555 if dst_layout == Layout::Cmyka {
556 core_dst_layout = Layout::Rgba;
557 }
558 let handle = self.create_transform_nbit::<f32, 1, 65536, 32768>(
559 core_src_layout,
560 dst_pr,
561 core_dst_layout,
562 options,
563 )?;
564 if core_src_layout == Layout::Cmyka {
565 return Ok(Arc::new(FromCmykaInterceptor::install(handle, dst_layout)));
566 } else if core_dst_layout == Layout::Cmyka {
567 return Ok(Arc::new(ToCmykaInterceptor::install(handle, dst_layout)));
568 }
569 Ok(handle)
570 }
571
572 #[cfg(feature = "in_place")]
581 pub fn create_in_place_transform_f32(
582 &self,
583 layout: Layout,
584 dst_pr: &ColorProfile,
585 options: TransformOptions,
586 ) -> Result<Arc<dyn InPlaceTransformExecutor<f32> + Send + Sync>, CmsError> {
587 self.create_transform_in_place_nbit::<f32, 1, 65536, 32768>(layout, dst_pr, options)
588 }
589
590 pub fn create_transform_f64(
597 &self,
598 src_layout: Layout,
599 dst_pr: &ColorProfile,
600 dst_layout: Layout,
601 options: TransformOptions,
602 ) -> Result<Arc<TransformF64Executor>, CmsError> {
603 let mut core_src_layout = src_layout;
604 if src_layout == Layout::Cmyka {
605 core_src_layout = Layout::Rgba;
606 }
607 let mut core_dst_layout = dst_layout;
608 if dst_layout == Layout::Cmyka {
609 core_dst_layout = Layout::Rgba;
610 }
611 let handle = self.create_transform_nbit::<f64, 1, 65536, 65536>(
612 core_src_layout,
613 dst_pr,
614 core_dst_layout,
615 options,
616 )?;
617 if core_src_layout == Layout::Cmyka {
618 return Ok(Arc::new(FromCmykaInterceptor::install(handle, dst_layout)));
619 } else if core_dst_layout == Layout::Cmyka {
620 return Ok(Arc::new(ToCmykaInterceptor::install(handle, dst_layout)));
621 }
622 Ok(handle)
623 }
624
625 #[cfg(feature = "in_place")]
634 pub fn create_in_place_transform_f64(
635 &self,
636 layout: Layout,
637 dst_pr: &ColorProfile,
638 options: TransformOptions,
639 ) -> Result<Arc<dyn InPlaceTransformExecutor<f64> + Send + Sync>, CmsError> {
640 self.create_transform_in_place_nbit::<f64, 1, 65536, 65536>(layout, dst_pr, options)
641 }
642
643 #[cfg(feature = "in_place")]
644 fn create_transform_in_place_nbit<
645 T: Copy
646 + Default
647 + AsPrimitive<usize>
648 + PointeeSizeExpressible
649 + Send
650 + Sync
651 + AsPrimitive<f32>
652 + RgbXyzFactory<T>
653 + RgbXyzFactoryOpt<T>
654 + GammaLutInterpolate,
655 const BIT_DEPTH: usize,
656 const LINEAR_CAP: usize,
657 const GAMMA_CAP: usize,
658 >(
659 &self,
660 layout: Layout,
661 dst_pr: &ColorProfile,
662 options: TransformOptions,
663 ) -> Result<Arc<dyn InPlaceTransformExecutor<T> + Send + Sync>, CmsError>
664 where
665 f32: AsPrimitive<T>,
666 u32: AsPrimitive<T>,
667 {
668 let is_rgb_transform = self.color_space == DataColorSpace::Rgb
670 && dst_pr.pcs == DataColorSpace::Xyz
671 && dst_pr.color_space == DataColorSpace::Rgb
672 && self.pcs == DataColorSpace::Xyz
673 && self.is_matrix_shaper()
674 && dst_pr.is_matrix_shaper();
675 let is_gray_transform = (self.color_space == DataColorSpace::Gray
676 && self.gray_trc.is_some())
677 && (dst_pr.color_space == DataColorSpace::Gray && dst_pr.gray_trc.is_some())
678 && self.pcs == DataColorSpace::Xyz
679 && dst_pr.pcs == DataColorSpace::Xyz;
680
681 if is_rgb_transform {
682 let transform = self.transform_matrix(dst_pr);
683
684 if self.are_all_trc_the_same() && dst_pr.are_all_trc_the_same() {
685 let linear = self.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
686 options.allow_use_cicp_transfer,
687 )?;
688
689 let gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
690 &dst_pr.red_trc,
691 options.allow_use_cicp_transfer,
692 )?;
693
694 let profile_transform = TransformMatrixShaperOptimized {
695 linear,
696 gamma,
697 adaptation_matrix: transform.to_f32(),
698 };
699
700 return T::make_in_place_optimized_transform::<LINEAR_CAP, GAMMA_CAP, BIT_DEPTH>(
701 layout,
702 profile_transform,
703 options,
704 );
705 }
706
707 let lin_r = self.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
708 options.allow_use_cicp_transfer,
709 )?;
710 let lin_g = self.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
711 options.allow_use_cicp_transfer,
712 )?;
713 let lin_b = self.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
714 options.allow_use_cicp_transfer,
715 )?;
716
717 let gamma_r = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
718 &dst_pr.red_trc,
719 options.allow_use_cicp_transfer,
720 )?;
721 let gamma_g = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
722 &dst_pr.green_trc,
723 options.allow_use_cicp_transfer,
724 )?;
725 let gamma_b = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
726 &dst_pr.blue_trc,
727 options.allow_use_cicp_transfer,
728 )?;
729
730 let profile_transform = TransformMatrixShaper {
731 r_linear: lin_r,
732 g_linear: lin_g,
733 b_linear: lin_b,
734 r_gamma: gamma_r,
735 g_gamma: gamma_g,
736 b_gamma: gamma_b,
737 adaptation_matrix: transform.to_f32(),
738 };
739
740 return T::make_in_place_transform::<LINEAR_CAP, GAMMA_CAP, BIT_DEPTH>(
741 layout,
742 profile_transform,
743 options,
744 );
745 }
746
747 if is_gray_transform {
748 let gray_linear = self.build_gray_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>()?;
750
751 let gray_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
752 &dst_pr.gray_trc,
753 options.allow_use_cicp_transfer,
754 )?;
755
756 use crate::conversions::make_gray_to_gray_in_place;
757 return make_gray_to_gray_in_place::<T, LINEAR_CAP>(
758 layout,
759 &gray_linear,
760 &gray_gamma,
761 BIT_DEPTH,
762 GAMMA_CAP,
763 );
764 }
765
766 Err(CmsError::UnsupportedProfileConnection)
767 }
768
769 fn create_transform_nbit<
770 T: Copy
771 + Default
772 + AsPrimitive<usize>
773 + PointeeSizeExpressible
774 + Send
775 + Sync
776 + AsPrimitive<f32>
777 + RgbXyzFactory<T>
778 + RgbXyzFactoryOpt<T>
779 + GammaLutInterpolate,
780 const BIT_DEPTH: usize,
781 const LINEAR_CAP: usize,
782 const GAMMA_CAP: usize,
783 >(
784 &self,
785 src_layout: Layout,
786 dst_pr: &ColorProfile,
787 dst_layout: Layout,
788 options: TransformOptions,
789 ) -> Result<Arc<dyn TransformExecutor<T> + Send + Sync>, CmsError>
790 where
791 f32: AsPrimitive<T>,
792 u32: AsPrimitive<T>,
793 (): LutBarycentricReduction<T, u8>,
794 (): LutBarycentricReduction<T, u16>,
795 {
796 if self.color_space == DataColorSpace::Rgb
797 && dst_pr.pcs == DataColorSpace::Xyz
798 && dst_pr.color_space == DataColorSpace::Rgb
799 && self.pcs == DataColorSpace::Xyz
800 && self.is_matrix_shaper()
801 && dst_pr.is_matrix_shaper()
802 {
803 if src_layout == Layout::Gray || src_layout == Layout::GrayAlpha {
804 return Err(CmsError::InvalidLayout);
805 }
806 if dst_layout == Layout::Gray || dst_layout == Layout::GrayAlpha {
807 return Err(CmsError::InvalidLayout);
808 }
809
810 #[cfg(feature = "lut")]
811 if self.has_device_to_pcs_lut() || dst_pr.has_pcs_to_device_lut() {
812 return make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>(
813 src_layout, self, dst_layout, dst_pr, options,
814 );
815 }
816
817 let transform = self.transform_matrix(dst_pr);
818
819 #[cfg(feature = "extended_range")]
820 if !T::FINITE && options.allow_extended_range_rgb_xyz {
821 if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() {
822 if let Some(linear_evaluator) = self.try_extended_linearizing_evaluator() {
823 use crate::conversions::{
824 TransformShaperFloatInOut, make_rgb_xyz_rgb_transform_float_in_out,
825 };
826 use std::marker::PhantomData;
827 let p = TransformShaperFloatInOut {
828 linear_evaluator,
829 gamma_evaluator,
830 adaptation_matrix: transform.to_f32(),
831 phantom_data: PhantomData,
832 };
833 return make_rgb_xyz_rgb_transform_float_in_out::<T>(
834 src_layout, dst_layout, p, BIT_DEPTH,
835 );
836 }
837
838 let lin_r = self.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
839 options.allow_use_cicp_transfer,
840 )?;
841 let lin_g = self.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
842 options.allow_use_cicp_transfer,
843 )?;
844 let lin_b = self.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
845 options.allow_use_cicp_transfer,
846 )?;
847
848 use crate::conversions::{
849 TransformShaperRgbFloat, make_rgb_xyz_rgb_transform_float,
850 };
851 use std::marker::PhantomData;
852 let p = TransformShaperRgbFloat {
853 r_linear: lin_r,
854 g_linear: lin_g,
855 b_linear: lin_b,
856 gamma_evaluator,
857 adaptation_matrix: transform.to_f32(),
858 phantom_data: PhantomData,
859 };
860 return make_rgb_xyz_rgb_transform_float::<T, LINEAR_CAP>(
861 src_layout, dst_layout, p, BIT_DEPTH,
862 );
863 }
864 }
865
866 if self.are_all_trc_the_same() && dst_pr.are_all_trc_the_same() {
867 let linear = self.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
868 options.allow_use_cicp_transfer,
869 )?;
870
871 let gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
872 &dst_pr.red_trc,
873 options.allow_use_cicp_transfer,
874 )?;
875
876 let profile_transform = TransformMatrixShaperOptimized {
877 linear,
878 gamma,
879 adaptation_matrix: transform.to_f32(),
880 };
881
882 return T::make_optimized_transform::<LINEAR_CAP, GAMMA_CAP, BIT_DEPTH>(
883 src_layout,
884 dst_layout,
885 profile_transform,
886 options,
887 );
888 }
889
890 let lin_r = self.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
891 options.allow_use_cicp_transfer,
892 )?;
893 let lin_g = self.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
894 options.allow_use_cicp_transfer,
895 )?;
896 let lin_b = self.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
897 options.allow_use_cicp_transfer,
898 )?;
899
900 let gamma_r = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
901 &dst_pr.red_trc,
902 options.allow_use_cicp_transfer,
903 )?;
904 let gamma_g = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
905 &dst_pr.green_trc,
906 options.allow_use_cicp_transfer,
907 )?;
908 let gamma_b = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
909 &dst_pr.blue_trc,
910 options.allow_use_cicp_transfer,
911 )?;
912
913 let profile_transform = TransformMatrixShaper {
914 r_linear: lin_r,
915 g_linear: lin_g,
916 b_linear: lin_b,
917 r_gamma: gamma_r,
918 g_gamma: gamma_g,
919 b_gamma: gamma_b,
920 adaptation_matrix: transform.to_f32(),
921 };
922
923 T::make_transform::<LINEAR_CAP, GAMMA_CAP, BIT_DEPTH>(
924 src_layout,
925 dst_layout,
926 profile_transform,
927 options,
928 )
929 } else if (self.color_space == DataColorSpace::Gray && self.gray_trc.is_some())
930 && (dst_pr.color_space == DataColorSpace::Rgb
931 || (dst_pr.color_space == DataColorSpace::Gray && dst_pr.gray_trc.is_some()))
932 && self.pcs == DataColorSpace::Xyz
933 && dst_pr.pcs == DataColorSpace::Xyz
934 {
935 if src_layout != Layout::GrayAlpha && src_layout != Layout::Gray {
936 return Err(CmsError::InvalidLayout);
937 }
938
939 #[cfg(feature = "lut")]
940 if self.has_device_to_pcs_lut() || dst_pr.has_pcs_to_device_lut() {
941 return make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>(
942 src_layout, self, dst_layout, dst_pr, options,
943 );
944 }
945
946 let gray_linear = self.build_gray_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>()?;
947
948 if dst_pr.color_space == DataColorSpace::Gray {
949 #[cfg(feature = "extended_range")]
950 if !T::FINITE && options.allow_extended_range_rgb_xyz {
951 if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() {
952 if let Some(linear_evaluator) = self.try_extended_linearizing_evaluator() {
953 use crate::conversions::make_gray_to_one_trc_extended;
955 return make_gray_to_one_trc_extended::<T>(
956 src_layout,
957 dst_layout,
958 linear_evaluator,
959 gamma_evaluator,
960 BIT_DEPTH,
961 );
962 }
963 }
964 }
965
966 let gray_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
968 &dst_pr.gray_trc,
969 options.allow_use_cicp_transfer,
970 )?;
971
972 make_gray_to_x::<T, LINEAR_CAP>(
973 src_layout,
974 dst_layout,
975 &gray_linear,
976 &gray_gamma,
977 BIT_DEPTH,
978 GAMMA_CAP,
979 )
980 } else {
981 #[allow(clippy::collapsible_if)]
982 if dst_pr.are_all_trc_the_same() {
983 #[cfg(feature = "extended_range")]
984 if !T::FINITE && options.allow_extended_range_rgb_xyz {
985 if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() {
986 if let Some(linear_evaluator) =
987 self.try_extended_linearizing_evaluator()
988 {
989 use crate::conversions::make_gray_to_one_trc_extended;
991 return make_gray_to_one_trc_extended::<T>(
992 src_layout,
993 dst_layout,
994 linear_evaluator,
995 gamma_evaluator,
996 BIT_DEPTH,
997 );
998 }
999 }
1000 }
1001
1002 let rgb_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
1004 &dst_pr.red_trc,
1005 options.allow_use_cicp_transfer,
1006 )?;
1007
1008 make_gray_to_x::<T, LINEAR_CAP>(
1009 src_layout,
1010 dst_layout,
1011 &gray_linear,
1012 &rgb_gamma,
1013 BIT_DEPTH,
1014 GAMMA_CAP,
1015 )
1016 } else {
1017 #[cfg(feature = "extended_range")]
1019 if !T::FINITE && options.allow_extended_range_rgb_xyz {
1020 if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() {
1021 if let Some(linear_evaluator) =
1022 self.try_extended_linearizing_evaluator()
1023 {
1024 use crate::conversions::make_gray_to_rgb_extended;
1027 return make_gray_to_rgb_extended::<T>(
1028 src_layout,
1029 dst_layout,
1030 linear_evaluator,
1031 gamma_evaluator,
1032 BIT_DEPTH,
1033 );
1034 }
1035 }
1036 }
1037
1038 let red_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
1039 &dst_pr.red_trc,
1040 options.allow_use_cicp_transfer,
1041 )?;
1042 let green_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
1043 &dst_pr.green_trc,
1044 options.allow_use_cicp_transfer,
1045 )?;
1046 let blue_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
1047 &dst_pr.blue_trc,
1048 options.allow_use_cicp_transfer,
1049 )?;
1050
1051 let mut gray_linear2 = Box::new([0f32; 65536]);
1052 for (dst, src) in gray_linear2.iter_mut().zip(gray_linear.iter()) {
1053 *dst = *src;
1054 }
1055
1056 make_gray_to_unfused::<T, LINEAR_CAP>(
1057 src_layout,
1058 dst_layout,
1059 gray_linear2,
1060 red_gamma,
1061 green_gamma,
1062 blue_gamma,
1063 BIT_DEPTH,
1064 GAMMA_CAP,
1065 )
1066 }
1067 }
1068 } else if self.color_space == DataColorSpace::Rgb
1069 && (dst_pr.color_space == DataColorSpace::Gray && dst_pr.gray_trc.is_some())
1070 && dst_pr.pcs == DataColorSpace::Xyz
1071 && self.pcs == DataColorSpace::Xyz
1072 {
1073 if src_layout == Layout::Gray || src_layout == Layout::GrayAlpha {
1074 return Err(CmsError::InvalidLayout);
1075 }
1076 if dst_layout != Layout::Gray && dst_layout != Layout::GrayAlpha {
1077 return Err(CmsError::InvalidLayout);
1078 }
1079
1080 #[cfg(feature = "lut")]
1081 if self.has_device_to_pcs_lut() || dst_pr.has_pcs_to_device_lut() {
1082 return make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>(
1083 src_layout, self, dst_layout, dst_pr, options,
1084 );
1085 }
1086
1087 let transform = self.transform_matrix(dst_pr).to_f32();
1088
1089 let vector = Vector3f {
1090 v: [transform.v[1][0], transform.v[1][1], transform.v[1][2]],
1091 };
1092
1093 #[cfg(feature = "extended_range")]
1094 if !T::FINITE && options.allow_extended_range_rgb_xyz {
1095 if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() {
1096 if let Some(linear_evaluator) = self.try_extended_linearizing_evaluator() {
1097 use crate::conversions::make_rgb_to_gray_extended;
1098 return make_rgb_to_gray_extended::<T>(
1099 src_layout,
1100 dst_layout,
1101 linear_evaluator,
1102 gamma_evaluator,
1103 vector,
1104 BIT_DEPTH,
1105 );
1106 }
1107 }
1108 }
1109
1110 let lin_r = self.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
1111 options.allow_use_cicp_transfer,
1112 )?;
1113 let lin_g = self.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
1114 options.allow_use_cicp_transfer,
1115 )?;
1116 let lin_b = self.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(
1117 options.allow_use_cicp_transfer,
1118 )?;
1119 let gray_linear = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>(
1120 &dst_pr.gray_trc,
1121 options.allow_use_cicp_transfer,
1122 )?;
1123
1124 let trc_box = ToneReproductionRgbToGray::<T, LINEAR_CAP> {
1125 r_linear: lin_r,
1126 g_linear: lin_g,
1127 b_linear: lin_b,
1128 gray_gamma: gray_linear,
1129 };
1130
1131 make_rgb_to_gray::<T, LINEAR_CAP>(
1132 src_layout, dst_layout, trc_box, vector, GAMMA_CAP, BIT_DEPTH,
1133 )
1134 } else if (self.color_space.is_three_channels()
1135 || self.color_space == DataColorSpace::Cmyk
1136 || self.color_space == DataColorSpace::Color4)
1137 && (dst_pr.color_space.is_three_channels()
1138 || dst_pr.color_space == DataColorSpace::Cmyk
1139 || dst_pr.color_space == DataColorSpace::Color4)
1140 && (dst_pr.pcs == DataColorSpace::Xyz || dst_pr.pcs == DataColorSpace::Lab)
1141 && (self.pcs == DataColorSpace::Xyz || self.pcs == DataColorSpace::Lab)
1142 {
1143 #[cfg(feature = "lut")]
1144 {
1145 if src_layout == Layout::Gray || src_layout == Layout::GrayAlpha {
1146 return Err(CmsError::InvalidLayout);
1147 }
1148 if dst_layout == Layout::Gray || dst_layout == Layout::GrayAlpha {
1149 return Err(CmsError::InvalidLayout);
1150 }
1151 make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>(
1152 src_layout, self, dst_layout, dst_pr, options,
1153 )
1154 }
1155 #[cfg(not(feature = "lut"))]
1156 {
1157 Err(CmsError::UnsupportedProfileConnection)
1158 }
1159 } else {
1160 #[cfg(feature = "lut")]
1161 {
1162 make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>(
1163 src_layout, self, dst_layout, dst_pr, options,
1164 )
1165 }
1166 #[cfg(not(feature = "lut"))]
1167 {
1168 Err(CmsError::UnsupportedProfileConnection)
1169 }
1170 }
1171 }
1172
1173 pub fn create_transform_8bit(
1176 &self,
1177 src_layout: Layout,
1178 dst_pr: &ColorProfile,
1179 dst_layout: Layout,
1180 options: TransformOptions,
1181 ) -> Result<Arc<Transform8BitExecutor>, CmsError> {
1182 self.create_transform_nbit::<u8, 8, 256, 4096>(src_layout, dst_pr, dst_layout, options)
1183 }
1184
1185 #[cfg(feature = "in_place")]
1189 pub fn create_in_place_transform_8bit(
1190 &self,
1191 layout: Layout,
1192 dst_pr: &ColorProfile,
1193 options: TransformOptions,
1194 ) -> Result<Arc<dyn InPlaceTransformExecutor<u8> + Send + Sync>, CmsError> {
1195 self.create_transform_in_place_nbit::<u8, 8, 256, 4096>(layout, dst_pr, options)
1196 }
1197
1198 #[allow(unused)]
1199 pub(crate) fn get_device_to_pcs(&self, intent: RenderingIntent) -> Option<&LutWarehouse> {
1200 match intent {
1201 RenderingIntent::AbsoluteColorimetric => self.lut_a_to_b_colorimetric.as_ref(),
1202 RenderingIntent::Saturation => self.lut_a_to_b_saturation.as_ref(),
1203 RenderingIntent::RelativeColorimetric => self.lut_a_to_b_colorimetric.as_ref(),
1204 RenderingIntent::Perceptual => self.lut_a_to_b_perceptual.as_ref(),
1205 }
1206 }
1207
1208 #[allow(unused)]
1209 pub(crate) fn get_pcs_to_device(&self, intent: RenderingIntent) -> Option<&LutWarehouse> {
1210 match intent {
1211 RenderingIntent::AbsoluteColorimetric => self.lut_b_to_a_colorimetric.as_ref(),
1212 RenderingIntent::Saturation => self.lut_b_to_a_saturation.as_ref(),
1213 RenderingIntent::RelativeColorimetric => self.lut_b_to_a_colorimetric.as_ref(),
1214 RenderingIntent::Perceptual => self.lut_b_to_a_perceptual.as_ref(),
1215 }
1216 }
1217}
1218
1219#[cfg(test)]
1220mod tests {
1221 use crate::*;
1222 use rand::RngExt;
1223
1224 #[test]
1225 fn test_transform_rgb8() {
1226 let mut srgb_profile = ColorProfile::new_srgb();
1227 let bt2020_profile = ColorProfile::new_bt2020();
1228 let random_point_x = rand::rng().random_range(0..255);
1229 let transform = bt2020_profile
1230 .create_transform_8bit(
1231 Layout::Rgb,
1232 &srgb_profile,
1233 Layout::Rgb,
1234 TransformOptions::default(),
1235 )
1236 .unwrap();
1237 let src = vec![random_point_x; 256 * 256 * 3];
1238 let mut dst = vec![random_point_x; 256 * 256 * 3];
1239 transform.transform(&src, &mut dst).unwrap();
1240
1241 let transform = bt2020_profile
1242 .create_transform_8bit(
1243 Layout::Rgb,
1244 &srgb_profile,
1245 Layout::Rgb,
1246 TransformOptions {
1247 ..TransformOptions::default()
1248 },
1249 )
1250 .unwrap();
1251 transform.transform(&src, &mut dst).unwrap();
1252 srgb_profile.rendering_intent = RenderingIntent::RelativeColorimetric;
1253 let transform = bt2020_profile
1254 .create_transform_8bit(
1255 Layout::Rgb,
1256 &srgb_profile,
1257 Layout::Rgb,
1258 TransformOptions {
1259 ..TransformOptions::default()
1260 },
1261 )
1262 .unwrap();
1263 transform.transform(&src, &mut dst).unwrap();
1264 srgb_profile.rendering_intent = RenderingIntent::Saturation;
1265 let transform = bt2020_profile
1266 .create_transform_8bit(
1267 Layout::Rgb,
1268 &srgb_profile,
1269 Layout::Rgb,
1270 TransformOptions {
1271 ..TransformOptions::default()
1272 },
1273 )
1274 .unwrap();
1275 transform.transform(&src, &mut dst).unwrap();
1276 }
1277
1278 #[test]
1279 fn test_transform_rgba8() {
1280 let srgb_profile = ColorProfile::new_srgb();
1281 let bt2020_profile = ColorProfile::new_bt2020();
1282 let random_point_x = rand::rng().random_range(0..255);
1283 let transform = bt2020_profile
1284 .create_transform_8bit(
1285 Layout::Rgba,
1286 &srgb_profile,
1287 Layout::Rgba,
1288 TransformOptions::default(),
1289 )
1290 .unwrap();
1291 let src = vec![random_point_x; 256 * 256 * 4];
1292 let mut dst = vec![random_point_x; 256 * 256 * 4];
1293 transform.transform(&src, &mut dst).unwrap();
1294 }
1295
1296 #[test]
1297 fn test_transform_gray_to_rgb8() {
1298 let gray_profile = ColorProfile::new_gray_with_gamma(2.2f32);
1299 let bt2020_profile = ColorProfile::new_bt2020();
1300 let random_point_x = rand::rng().random_range(0..255);
1301 let transform = gray_profile
1302 .create_transform_8bit(
1303 Layout::Gray,
1304 &bt2020_profile,
1305 Layout::Rgb,
1306 TransformOptions::default(),
1307 )
1308 .unwrap();
1309 let src = vec![random_point_x; 256 * 256];
1310 let mut dst = vec![random_point_x; 256 * 256 * 3];
1311 transform.transform(&src, &mut dst).unwrap();
1312 }
1313
1314 #[test]
1315 fn test_transform_gray_to_rgba8() {
1316 let srgb_profile = ColorProfile::new_gray_with_gamma(2.2f32);
1317 let bt2020_profile = ColorProfile::new_bt2020();
1318 let random_point_x = rand::rng().random_range(0..255);
1319 let transform = srgb_profile
1320 .create_transform_8bit(
1321 Layout::Gray,
1322 &bt2020_profile,
1323 Layout::Rgba,
1324 TransformOptions::default(),
1325 )
1326 .unwrap();
1327 let src = vec![random_point_x; 256 * 256];
1328 let mut dst = vec![random_point_x; 256 * 256 * 4];
1329 transform.transform(&src, &mut dst).unwrap();
1330 }
1331
1332 #[test]
1333 fn test_transform_gray_to_gray_alpha8() {
1334 let srgb_profile = ColorProfile::new_gray_with_gamma(2.2f32);
1335 let bt2020_profile = ColorProfile::new_bt2020();
1336 let random_point_x = rand::rng().random_range(0..255);
1337 let transform = srgb_profile
1338 .create_transform_8bit(
1339 Layout::Gray,
1340 &bt2020_profile,
1341 Layout::GrayAlpha,
1342 TransformOptions::default(),
1343 )
1344 .unwrap();
1345 let src = vec![random_point_x; 256 * 256];
1346 let mut dst = vec![random_point_x; 256 * 256 * 2];
1347 transform.transform(&src, &mut dst).unwrap();
1348 }
1349
1350 #[test]
1351 fn test_transform_rgb10() {
1352 let srgb_profile = ColorProfile::new_srgb();
1353 let bt2020_profile = ColorProfile::new_bt2020();
1354 let random_point_x = rand::rng().random_range(0..((1 << 10) - 1));
1355 let transform = bt2020_profile
1356 .create_transform_10bit(
1357 Layout::Rgb,
1358 &srgb_profile,
1359 Layout::Rgb,
1360 TransformOptions::default(),
1361 )
1362 .unwrap();
1363 let src = vec![random_point_x; 256 * 256 * 3];
1364 let mut dst = vec![random_point_x; 256 * 256 * 3];
1365 transform.transform(&src, &mut dst).unwrap();
1366 }
1367
1368 #[test]
1369 fn test_transform_rgb12() {
1370 let srgb_profile = ColorProfile::new_srgb();
1371 let bt2020_profile = ColorProfile::new_bt2020();
1372 let random_point_x = rand::rng().random_range(0..((1 << 12) - 1));
1373 let transform = bt2020_profile
1374 .create_transform_12bit(
1375 Layout::Rgb,
1376 &srgb_profile,
1377 Layout::Rgb,
1378 TransformOptions::default(),
1379 )
1380 .unwrap();
1381 let src = vec![random_point_x; 256 * 256 * 3];
1382 let mut dst = vec![random_point_x; 256 * 256 * 3];
1383 transform.transform(&src, &mut dst).unwrap();
1384 }
1385
1386 #[test]
1387 fn test_transform_rgb16() {
1388 let srgb_profile = ColorProfile::new_srgb();
1389 let bt2020_profile = ColorProfile::new_bt2020();
1390 let random_point_x = rand::rng().random_range(0..((1u32 << 16u32) - 1u32)) as u16;
1391 let transform = bt2020_profile
1392 .create_transform_16bit(
1393 Layout::Rgb,
1394 &srgb_profile,
1395 Layout::Rgb,
1396 TransformOptions::default(),
1397 )
1398 .unwrap();
1399 let src = vec![random_point_x; 256 * 256 * 3];
1400 let mut dst = vec![random_point_x; 256 * 256 * 3];
1401 transform.transform(&src, &mut dst).unwrap();
1402 }
1403
1404 #[test]
1405 fn test_transform_round_trip_rgb8() {
1406 let srgb_profile = ColorProfile::new_srgb();
1407 let bt2020_profile = ColorProfile::new_bt2020();
1408 let transform = srgb_profile
1409 .create_transform_8bit(
1410 Layout::Rgb,
1411 &bt2020_profile,
1412 Layout::Rgb,
1413 TransformOptions::default(),
1414 )
1415 .unwrap();
1416 let mut src = vec![0u8; 256 * 256 * 3];
1417 for dst in src.chunks_exact_mut(3) {
1418 dst[0] = 175;
1419 dst[1] = 75;
1420 dst[2] = 13;
1421 }
1422 let mut dst = vec![0u8; 256 * 256 * 3];
1423 transform.transform(&src, &mut dst).unwrap();
1424
1425 let transform_inverse = bt2020_profile
1426 .create_transform_8bit(
1427 Layout::Rgb,
1428 &srgb_profile,
1429 Layout::Rgb,
1430 TransformOptions::default(),
1431 )
1432 .unwrap();
1433
1434 transform_inverse.transform(&dst, &mut src).unwrap();
1435
1436 for src in src.chunks_exact_mut(3) {
1437 let diff0 = (src[0] as i32 - 175).abs();
1438 let diff1 = (src[1] as i32 - 75).abs();
1439 let diff2 = (src[2] as i32 - 13).abs();
1440 assert!(
1441 diff0 < 3,
1442 "On channel 0 difference should be less than 3, but it was {diff0}"
1443 );
1444 assert!(
1445 diff1 < 3,
1446 "On channel 1 difference should be less than 3, but it was {diff1}"
1447 );
1448 assert!(
1449 diff2 < 3,
1450 "On channel 2 difference should be less than 3, but it was {diff2}"
1451 );
1452 }
1453 }
1454
1455 #[test]
1456 fn test_transform_round_trip_rgb10() {
1457 let srgb_profile = ColorProfile::new_srgb();
1458 let bt2020_profile = ColorProfile::new_bt2020();
1459 let transform = srgb_profile
1460 .create_transform_10bit(
1461 Layout::Rgb,
1462 &bt2020_profile,
1463 Layout::Rgb,
1464 TransformOptions::default(),
1465 )
1466 .unwrap();
1467 let mut src = vec![0u16; 256 * 256 * 3];
1468 for dst in src.chunks_exact_mut(3) {
1469 dst[0] = 175;
1470 dst[1] = 256;
1471 dst[2] = 512;
1472 }
1473 let mut dst = vec![0u16; 256 * 256 * 3];
1474 transform.transform(&src, &mut dst).unwrap();
1475
1476 let transform_inverse = bt2020_profile
1477 .create_transform_10bit(
1478 Layout::Rgb,
1479 &srgb_profile,
1480 Layout::Rgb,
1481 TransformOptions::default(),
1482 )
1483 .unwrap();
1484
1485 transform_inverse.transform(&dst, &mut src).unwrap();
1486
1487 for src in src.chunks_exact_mut(3) {
1488 let diff0 = (src[0] as i32 - 175).abs();
1489 let diff1 = (src[1] as i32 - 256).abs();
1490 let diff2 = (src[2] as i32 - 512).abs();
1491 assert!(
1492 diff0 < 15,
1493 "On channel 0 difference should be less than 15, but it was {diff0}"
1494 );
1495 assert!(
1496 diff1 < 15,
1497 "On channel 1 difference should be less than 15, but it was {diff1}"
1498 );
1499 assert!(
1500 diff2 < 15,
1501 "On channel 2 difference should be less than 15, but it was {diff2}"
1502 );
1503 }
1504 }
1505
1506 #[test]
1507 fn test_transform_round_trip_rgb12() {
1508 let srgb_profile = ColorProfile::new_srgb();
1509 let bt2020_profile = ColorProfile::new_bt2020();
1510 let transform = srgb_profile
1511 .create_transform_12bit(
1512 Layout::Rgb,
1513 &bt2020_profile,
1514 Layout::Rgb,
1515 TransformOptions::default(),
1516 )
1517 .unwrap();
1518 let mut src = vec![0u16; 256 * 256 * 3];
1519 for dst in src.chunks_exact_mut(3) {
1520 dst[0] = 1750;
1521 dst[1] = 2560;
1522 dst[2] = 3143;
1523 }
1524 let mut dst = vec![0u16; 256 * 256 * 3];
1525 transform.transform(&src, &mut dst).unwrap();
1526
1527 let transform_inverse = bt2020_profile
1528 .create_transform_12bit(
1529 Layout::Rgb,
1530 &srgb_profile,
1531 Layout::Rgb,
1532 TransformOptions::default(),
1533 )
1534 .unwrap();
1535
1536 transform_inverse.transform(&dst, &mut src).unwrap();
1537
1538 for src in src.chunks_exact_mut(3) {
1539 let diff0 = (src[0] as i32 - 1750).abs();
1540 let diff1 = (src[1] as i32 - 2560).abs();
1541 let diff2 = (src[2] as i32 - 3143).abs();
1542 assert!(
1543 diff0 < 25,
1544 "On channel 0 difference should be less than 25, but it was {diff0}"
1545 );
1546 assert!(
1547 diff1 < 25,
1548 "On channel 1 difference should be less than 25, but it was {diff1}"
1549 );
1550 assert!(
1551 diff2 < 25,
1552 "On channel 2 difference should be less than 25, but it was {diff2}"
1553 );
1554 }
1555 }
1556
1557 #[test]
1558 fn test_transform_round_trip_rgb16() {
1559 let srgb_profile = ColorProfile::new_srgb();
1560 let bt2020_profile = ColorProfile::new_bt2020();
1561 let transform = srgb_profile
1562 .create_transform_16bit(
1563 Layout::Rgb,
1564 &bt2020_profile,
1565 Layout::Rgb,
1566 TransformOptions::default(),
1567 )
1568 .unwrap();
1569 let mut src = vec![0u16; 256 * 256 * 3];
1570 for dst in src.chunks_exact_mut(3) {
1571 dst[0] = 1760;
1572 dst[1] = 2560;
1573 dst[2] = 5120;
1574 }
1575 let mut dst = vec![0u16; 256 * 256 * 3];
1576 transform.transform(&src, &mut dst).unwrap();
1577
1578 let transform_inverse = bt2020_profile
1579 .create_transform_16bit(
1580 Layout::Rgb,
1581 &srgb_profile,
1582 Layout::Rgb,
1583 TransformOptions::default(),
1584 )
1585 .unwrap();
1586
1587 transform_inverse.transform(&dst, &mut src).unwrap();
1588
1589 for src in src.chunks_exact_mut(3) {
1590 let diff0 = (src[0] as i32 - 1760).abs();
1591 let diff1 = (src[1] as i32 - 2560).abs();
1592 let diff2 = (src[2] as i32 - 5120).abs();
1593 assert!(
1594 diff0 < 35,
1595 "On channel 0 difference should be less than 35, but it was {diff0}"
1596 );
1597 assert!(
1598 diff1 < 35,
1599 "On channel 1 difference should be less than 35, but it was {diff1}"
1600 );
1601 assert!(
1602 diff2 < 35,
1603 "On channel 2 difference should be less than 35, but it was {diff2}"
1604 );
1605 }
1606 }
1607
1608 #[test]
1609 #[cfg(feature = "extended_range")]
1610 fn test_transform_rgb_to_gray_extended() {
1611 let srgb = ColorProfile::new_srgb();
1612 let mut gray_profile = ColorProfile::new_gray_with_gamma(1.0);
1613 gray_profile.color_space = DataColorSpace::Gray;
1614 gray_profile.gray_trc = srgb.red_trc.clone();
1615 let mut test_profile = vec![0.; 4];
1616 test_profile[2] = 1.;
1617 let mut dst = vec![0.; 1];
1618
1619 let mut inverse = vec![0.; 4];
1620
1621 let cvt0 = srgb
1622 .create_transform_f32(
1623 Layout::Rgba,
1624 &gray_profile,
1625 Layout::Gray,
1626 TransformOptions {
1627 allow_extended_range_rgb_xyz: true,
1628 ..Default::default()
1629 },
1630 )
1631 .unwrap();
1632 cvt0.transform(&test_profile, &mut dst).unwrap();
1633 assert!((dst[0] - 0.273046) < 1e-4);
1634
1635 let cvt_inverse = gray_profile
1636 .create_transform_f32(
1637 Layout::Gray,
1638 &srgb,
1639 Layout::Rgba,
1640 TransformOptions {
1641 allow_extended_range_rgb_xyz: false,
1642 ..Default::default()
1643 },
1644 )
1645 .unwrap();
1646 cvt_inverse.transform(&dst, &mut inverse).unwrap();
1647 assert!((inverse[0] - 0.273002833) < 1e-4);
1648
1649 let cvt1 = srgb
1650 .create_transform_f32(
1651 Layout::Rgba,
1652 &gray_profile,
1653 Layout::Gray,
1654 TransformOptions {
1655 allow_extended_range_rgb_xyz: false,
1656 ..Default::default()
1657 },
1658 )
1659 .unwrap();
1660 cvt1.transform(&test_profile, &mut dst).unwrap();
1661 assert!((dst[0] - 0.27307168) < 1e-5);
1662
1663 inverse.fill(0.);
1664
1665 let cvt_inverse = gray_profile
1666 .create_transform_f32(
1667 Layout::Gray,
1668 &srgb,
1669 Layout::Rgba,
1670 TransformOptions {
1671 allow_extended_range_rgb_xyz: true,
1672 ..Default::default()
1673 },
1674 )
1675 .unwrap();
1676 cvt_inverse.transform(&dst, &mut inverse).unwrap();
1677 assert!((inverse[0] - 0.273002833) < 1e-4);
1678 }
1679
1680 #[test]
1684 fn test_transform_rgb8_pixel_independence() {
1685 let srgb_profile = ColorProfile::new_srgb();
1686 let bt2020_profile = ColorProfile::new_bt2020();
1687
1688 let transform = bt2020_profile
1689 .create_transform_8bit(
1690 Layout::Rgb,
1691 &srgb_profile,
1692 Layout::Rgb,
1693 TransformOptions {
1694 prefer_fixed_point: true,
1695 ..Default::default()
1696 },
1697 )
1698 .unwrap();
1699
1700 let src: Vec<u8> = vec![
1707 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 0, ];
1712 let mut dst = vec![0u8; 12];
1713 transform.transform(&src, &mut dst).unwrap();
1714
1715 let mut single_pixel_results = Vec::new();
1717 for pixel in src.chunks(3) {
1718 let mut single_dst = vec![0u8; 3];
1719 transform.transform(pixel, &mut single_dst).unwrap();
1720 single_pixel_results.extend(single_dst);
1721 }
1722
1723 for (i, (batch, single)) in dst.iter().zip(single_pixel_results.iter()).enumerate() {
1726 assert_eq!(
1727 batch,
1728 single,
1729 "Mismatch at byte {} (pixel {}, channel {}): batch={}, single={}",
1730 i,
1731 i / 3,
1732 i % 3,
1733 batch,
1734 single
1735 );
1736 }
1737 }
1738}