1#![cfg(feature = "lut")]
30use crate::conversions::lut3x3::create_lut3x3;
31#[cfg(feature = "any_to_any")]
32use crate::conversions::lut3x3::{katana_input_stage_lut_3x3, katana_output_stage_lut_3x3};
33use crate::conversions::lut3x4::{create_lut3_samples_norm, create_lut3x4};
34use crate::conversions::lut4::*;
35use crate::conversions::mab::{prepare_mab_3x3, prepare_mba_3x3};
36use crate::conversions::transform_lut3_to_4::make_transform_3x4;
37use crate::mlaf::mlaf;
38use crate::{
39 CmsError, ColorProfile, DataColorSpace, InPlaceStage, Layout, LutWarehouse, Matrix3f,
40 ProfileVersion, TransformExecutor, TransformOptions,
41};
42use num_traits::AsPrimitive;
43use std::sync::Arc;
44
45pub(crate) struct MatrixStage {
46 pub(crate) matrices: Vec<Matrix3f>,
47}
48
49impl InPlaceStage for MatrixStage {
50 fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
51 if !self.matrices.is_empty() {
52 let m = self.matrices[0];
53 for dst in dst.chunks_exact_mut(3) {
54 let x = dst[0];
55 let y = dst[1];
56 let z = dst[2];
57 dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
58 dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
59 dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
60 }
61 }
62
63 for m in self.matrices.iter().skip(1) {
64 for dst in dst.chunks_exact_mut(3) {
65 let x = dst[0];
66 let y = dst[1];
67 let z = dst[2];
68 dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
69 dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
70 dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
71 }
72 }
73
74 Ok(())
75 }
76}
77
78pub(crate) const LUT_SAMPLING: u16 = 255;
79
80pub(crate) trait Lut3x3Factory {
81 fn make_transform_3x3<
82 T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible + 'static + Send + Sync,
83 const SRC_LAYOUT: u8,
84 const DST_LAYOUT: u8,
85 const GRID_SIZE: usize,
86 const BIT_DEPTH: usize,
87 >(
88 lut: Vec<f32>,
89 options: TransformOptions,
90 color_space: DataColorSpace,
91 is_linear: bool,
92 ) -> Arc<dyn TransformExecutor<T> + Send + Sync>
93 where
94 f32: AsPrimitive<T>,
95 u32: AsPrimitive<T>,
96 (): LutBarycentricReduction<T, u8>,
97 (): LutBarycentricReduction<T, u16>;
98}
99
100pub(crate) trait Lut4x3Factory {
101 fn make_transform_4x3<
102 T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible + 'static + Send + Sync,
103 const LAYOUT: u8,
104 const GRID_SIZE: usize,
105 const BIT_DEPTH: usize,
106 >(
107 lut: Vec<f32>,
108 options: TransformOptions,
109 color_space: DataColorSpace,
110 is_linear: bool,
111 ) -> Arc<dyn TransformExecutor<T> + Sync + Send>
112 where
113 f32: AsPrimitive<T>,
114 u32: AsPrimitive<T>,
115 (): LutBarycentricReduction<T, u8>,
116 (): LutBarycentricReduction<T, u16>;
117}
118
119fn pcs_lab_v4_to_v2(profile: &ColorProfile, lut: &mut [f32]) {
120 if profile.pcs == DataColorSpace::Lab
121 && profile.version_internal < ProfileVersion::V4_0
122 && lut.len() % 3 == 0
123 {
124 assert_eq!(
125 lut.len() % 3,
126 0,
127 "Lut {:?} is not a multiple of 3, this should not happen for lab",
128 lut.len()
129 );
130 let v_mat = vec![Matrix3f {
131 v: [
132 [65280.0 / 65535.0, 0f32, 0f32],
133 [0f32, 65280.0 / 65535.0, 0f32],
134 [0f32, 0f32, 65280.0 / 65535.0f32],
135 ],
136 }];
137 let stage = MatrixStage { matrices: v_mat };
138 stage.transform(lut).unwrap();
139 }
140}
141
142fn pcs_lab_v2_to_v4(profile: &ColorProfile, lut: &mut [f32]) {
143 if profile.pcs == DataColorSpace::Lab
144 && profile.version_internal < ProfileVersion::V4_0
145 && lut.len() % 3 == 0
146 {
147 assert_eq!(
148 lut.len() % 3,
149 0,
150 "Lut {:?} is not a multiple of 3, this should not happen for lab",
151 lut.len()
152 );
153 let v_mat = vec![Matrix3f {
154 v: [
155 [65535.0 / 65280.0f32, 0f32, 0f32],
156 [0f32, 65535.0f32 / 65280.0f32, 0f32],
157 [0f32, 0f32, 65535.0f32 / 65280.0f32],
158 ],
159 }];
160 let stage = MatrixStage { matrices: v_mat };
161 stage.transform(lut).unwrap();
162 }
163}
164
165macro_rules! make_transform_3x3_fn {
166 ($method_name: ident, $exec_impl: ident) => {
167 fn $method_name<
168 T: Copy
169 + Default
170 + AsPrimitive<f32>
171 + Send
172 + Sync
173 + AsPrimitive<usize>
174 + PointeeSizeExpressible,
175 const GRID_SIZE: usize,
176 const BIT_DEPTH: usize,
177 >(
178 src_layout: Layout,
179 dst_layout: Layout,
180 lut: Vec<f32>,
181 options: TransformOptions,
182 color_space: DataColorSpace,
183 is_linear: bool,
184 ) -> Arc<dyn TransformExecutor<T> + Send + Sync>
185 where
186 f32: AsPrimitive<T>,
187 u32: AsPrimitive<T>,
188 (): LutBarycentricReduction<T, u8>,
189 (): LutBarycentricReduction<T, u16>,
190 {
191 match src_layout {
192 Layout::Rgb => match dst_layout {
193 Layout::Rgb => $exec_impl::make_transform_3x3::<
194 T,
195 { Layout::Rgb as u8 },
196 { Layout::Rgb as u8 },
197 GRID_SIZE,
198 BIT_DEPTH,
199 >(lut, options, color_space, is_linear),
200 Layout::Rgba => $exec_impl::make_transform_3x3::<
201 T,
202 { Layout::Rgb as u8 },
203 { Layout::Rgba as u8 },
204 GRID_SIZE,
205 BIT_DEPTH,
206 >(lut, options, color_space, is_linear),
207 _ => unimplemented!(),
208 },
209 Layout::Rgba => match dst_layout {
210 Layout::Rgb => $exec_impl::make_transform_3x3::<
211 T,
212 { Layout::Rgba as u8 },
213 { Layout::Rgb as u8 },
214 GRID_SIZE,
215 BIT_DEPTH,
216 >(lut, options, color_space, is_linear),
217 Layout::Rgba => $exec_impl::make_transform_3x3::<
218 T,
219 { Layout::Rgba as u8 },
220 { Layout::Rgba as u8 },
221 GRID_SIZE,
222 BIT_DEPTH,
223 >(lut, options, color_space, is_linear),
224 _ => unimplemented!(),
225 },
226 _ => unimplemented!(),
227 }
228 }
229 };
230}
231
232macro_rules! make_transform_4x3_fn {
233 ($method_name: ident, $exec_name: ident) => {
234 fn $method_name<
235 T: Copy
236 + Default
237 + AsPrimitive<f32>
238 + Send
239 + Sync
240 + AsPrimitive<usize>
241 + PointeeSizeExpressible,
242 const GRID_SIZE: usize,
243 const BIT_DEPTH: usize,
244 >(
245 dst_layout: Layout,
246 lut: Vec<f32>,
247 options: TransformOptions,
248 data_color_space: DataColorSpace,
249 is_linear: bool,
250 ) -> Arc<dyn TransformExecutor<T> + Send + Sync>
251 where
252 f32: AsPrimitive<T>,
253 u32: AsPrimitive<T>,
254 (): LutBarycentricReduction<T, u8>,
255 (): LutBarycentricReduction<T, u16>,
256 {
257 match dst_layout {
258 Layout::Rgb => $exec_name::make_transform_4x3::<
259 T,
260 { Layout::Rgb as u8 },
261 GRID_SIZE,
262 BIT_DEPTH,
263 >(lut, options, data_color_space, is_linear),
264 Layout::Rgba => $exec_name::make_transform_4x3::<
265 T,
266 { Layout::Rgba as u8 },
267 GRID_SIZE,
268 BIT_DEPTH,
269 >(lut, options, data_color_space, is_linear),
270 _ => unimplemented!(),
271 }
272 }
273 };
274}
275
276#[cfg(all(target_arch = "aarch64", feature = "neon_luts"))]
277use crate::conversions::neon::NeonLut3x3Factory;
278#[cfg(all(target_arch = "aarch64", feature = "neon_luts"))]
279make_transform_3x3_fn!(make_transformer_3x3, NeonLut3x3Factory);
280
281#[cfg(not(all(target_arch = "aarch64", feature = "neon_luts")))]
282use crate::conversions::transform_lut3_to_3::DefaultLut3x3Factory;
283#[cfg(not(all(target_arch = "aarch64", feature = "neon_luts")))]
284make_transform_3x3_fn!(make_transformer_3x3, DefaultLut3x3Factory);
285
286#[cfg(all(target_arch = "x86_64", feature = "avx_luts"))]
287use crate::conversions::avx::AvxLut3x3Factory;
288#[cfg(all(target_arch = "x86_64", feature = "avx_luts"))]
289make_transform_3x3_fn!(make_transformer_3x3_avx_fma, AvxLut3x3Factory);
290
291#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse_luts"))]
292use crate::conversions::sse::SseLut3x3Factory;
293#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse_luts"))]
294make_transform_3x3_fn!(make_transformer_3x3_sse41, SseLut3x3Factory);
295
296use crate::conversions::LutBarycentricReduction;
297#[cfg(all(target_arch = "x86_64", feature = "avx_luts"))]
298use crate::conversions::avx::AvxLut4x3Factory;
299#[cfg(feature = "any_to_any")]
300use crate::conversions::katana::{
301 Katana, KatanaDefaultIntermediate, KatanaInitialStage, KatanaPostFinalizationStage,
302 KatanaStageLabToXyz, KatanaStageXyzToLab, katana_create_rgb_lin_lut, katana_pcs_lab_v2_to_v4,
303 katana_pcs_lab_v4_to_v2, katana_prepare_inverse_lut_rgb_xyz, multi_dimensional_3x3_to_device,
304 multi_dimensional_3x3_to_pcs, multi_dimensional_4x3_to_pcs,
305};
306use crate::conversions::mab4x3::prepare_mab_4x3;
307use crate::conversions::mba3x4::prepare_mba_3x4;
308#[cfg(feature = "any_to_any")]
309use crate::conversions::md_luts_factory::{do_any_to_any, prepare_alpha_finalizer};
310#[cfg(all(target_arch = "x86_64", feature = "avx_luts"))]
313make_transform_4x3_fn!(make_transformer_4x3_avx_fma, AvxLut4x3Factory);
314
315#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse_luts"))]
316use crate::conversions::sse::SseLut4x3Factory;
317#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse_luts"))]
318make_transform_4x3_fn!(make_transformer_4x3_sse41, SseLut4x3Factory);
319
320#[cfg(not(all(target_arch = "aarch64", feature = "neon_luts")))]
321use crate::conversions::transform_lut4_to_3::DefaultLut4x3Factory;
322
323#[cfg(not(all(target_arch = "aarch64", feature = "neon_luts")))]
324make_transform_4x3_fn!(make_transformer_4x3, DefaultLut4x3Factory);
325
326use crate::conversions::prelude_lut_xyz_rgb::{create_rgb_lin_lut, prepare_inverse_lut_rgb_xyz};
327use crate::conversions::xyz_lab::{StageLabToXyz, StageXyzToLab};
328use crate::transform::PointeeSizeExpressible;
329use crate::trc::GammaLutInterpolate;
330
331#[cfg(all(target_arch = "aarch64", feature = "neon_luts"))]
332use crate::conversions::neon::NeonLut4x3Factory;
333#[cfg(all(target_arch = "aarch64", feature = "neon_luts"))]
334make_transform_4x3_fn!(make_transformer_4x3, NeonLut4x3Factory);
335
336#[inline(never)]
337#[cold]
338pub(crate) fn make_lut_transform<
339 T: Copy
340 + Default
341 + AsPrimitive<f32>
342 + Send
343 + Sync
344 + AsPrimitive<usize>
345 + PointeeSizeExpressible
346 + GammaLutInterpolate,
347 const BIT_DEPTH: usize,
348 const LINEAR_CAP: usize,
349 const GAMMA_LUT: usize,
350>(
351 src_layout: Layout,
352 source: &ColorProfile,
353 dst_layout: Layout,
354 dest: &ColorProfile,
355 options: TransformOptions,
356) -> Result<Arc<dyn TransformExecutor<T> + Send + Sync>, CmsError>
357where
358 f32: AsPrimitive<T>,
359 u32: AsPrimitive<T>,
360 (): LutBarycentricReduction<T, u8>,
361 (): LutBarycentricReduction<T, u16>,
362{
363 if (source.color_space == DataColorSpace::Cmyk || source.color_space == DataColorSpace::Color4)
364 && (dest.color_space == DataColorSpace::Rgb || dest.color_space == DataColorSpace::Lab)
365 {
366 source.color_space.check_layout(src_layout)?;
367 dest.color_space.check_layout(dst_layout)?;
368 if source.pcs != DataColorSpace::Xyz && source.pcs != DataColorSpace::Lab {
369 return Err(CmsError::UnsupportedProfileConnection);
370 }
371 if dest.pcs != DataColorSpace::Lab && dest.pcs != DataColorSpace::Xyz {
372 return Err(CmsError::UnsupportedProfileConnection);
373 }
374
375 const GRID_SIZE: usize = 17;
376
377 #[cfg(feature = "any_to_any")]
378 let is_katana_required_for_source = source
379 .get_device_to_pcs(options.rendering_intent)
380 .ok_or(CmsError::UnsupportedLutRenderingIntent(
381 source.rendering_intent,
382 ))
383 .map(|x| x.is_katana_required())?;
384
385 #[cfg(feature = "any_to_any")]
386 let is_katana_required_for_destination =
387 if dest.is_matrix_shaper() || dest.pcs == DataColorSpace::Xyz {
388 false
389 } else if dest.pcs == DataColorSpace::Lab {
390 dest.get_pcs_to_device(options.rendering_intent)
391 .ok_or(CmsError::UnsupportedProfileConnection)
392 .map(|x| x.is_katana_required())?
393 } else {
394 return Err(CmsError::UnsupportedProfileConnection);
395 };
396
397 #[cfg(feature = "any_to_any")]
398 if is_katana_required_for_source || is_katana_required_for_destination {
399 let initial_stage: Box<dyn KatanaInitialStage<f32, T> + Send + Sync> =
400 match source.get_device_to_pcs(options.rendering_intent).ok_or(
401 CmsError::UnsupportedLutRenderingIntent(source.rendering_intent),
402 )? {
403 LutWarehouse::Lut(lut) => {
404 katana_input_stage_lut_4x3::<T>(lut, options, source.pcs, BIT_DEPTH)?
405 }
406 LutWarehouse::Multidimensional(mab) => {
407 multi_dimensional_4x3_to_pcs::<T>(mab, options, source.pcs, BIT_DEPTH)?
408 }
409 };
410
411 let mut stages = Vec::new();
412
413 stages.push(katana_pcs_lab_v2_to_v4(source));
414 if source.pcs == DataColorSpace::Lab {
415 stages.push(Box::new(KatanaStageLabToXyz::default()));
416 }
417 if dest.pcs == DataColorSpace::Lab {
418 stages.push(Box::new(KatanaStageXyzToLab::default()));
419 }
420 stages.push(katana_pcs_lab_v4_to_v2(dest));
421
422 let final_stage = if dest.has_pcs_to_device_lut() {
423 let pcs_to_device = dest
424 .get_pcs_to_device(options.rendering_intent)
425 .ok_or(CmsError::UnsupportedProfileConnection)?;
426 match pcs_to_device {
427 LutWarehouse::Lut(lut) => {
428 katana_output_stage_lut_3x3::<T>(lut, options, dest.pcs, BIT_DEPTH)?
429 }
430 LutWarehouse::Multidimensional(mab) => {
431 multi_dimensional_3x3_to_device::<T>(mab, options, dest.pcs, BIT_DEPTH)?
432 }
433 }
434 } else if dest.is_matrix_shaper() {
435 let state = katana_prepare_inverse_lut_rgb_xyz::<T, BIT_DEPTH, GAMMA_LUT>(
436 dest, dst_layout, options,
437 )?;
438 stages.extend(state.stages);
439 state.final_stage
440 } else {
441 return Err(CmsError::UnsupportedProfileConnection);
442 };
443
444 let mut post_finalization: Vec<Box<dyn KatanaPostFinalizationStage<T> + Send + Sync>> =
445 Vec::new();
446 if let Some(stage) =
447 prepare_alpha_finalizer::<T>(src_layout, source, dst_layout, dest, BIT_DEPTH)
448 {
449 post_finalization.push(stage);
450 }
451
452 return Ok(Arc::new(Katana::<f32, T> {
453 initial_stage,
454 final_stage,
455 stages,
456 post_finalization,
457 }));
458 }
459
460 let mut lut = match source.get_device_to_pcs(options.rendering_intent).ok_or(
461 CmsError::UnsupportedLutRenderingIntent(source.rendering_intent),
462 )? {
463 LutWarehouse::Lut(lut) => create_lut4::<GRID_SIZE>(lut, options, source.pcs)?,
464 LutWarehouse::Multidimensional(m_curves) => {
465 let mut samples = create_lut4_norm_samples::<GRID_SIZE>();
466 prepare_mab_4x3(m_curves, &mut samples, options, source.pcs)?
467 }
468 };
469
470 pcs_lab_v2_to_v4(source, &mut lut);
471
472 if source.pcs == DataColorSpace::Lab {
473 let lab_to_xyz_stage = StageLabToXyz::default();
474 lab_to_xyz_stage.transform(&mut lut)?;
475 }
476
477 if dest.pcs == DataColorSpace::Lab {
491 let lab_to_xyz_stage = StageXyzToLab::default();
492 lab_to_xyz_stage.transform(&mut lut)?;
493 }
494
495 pcs_lab_v4_to_v2(dest, &mut lut);
496
497 if dest.pcs == DataColorSpace::Xyz {
498 if dest.is_matrix_shaper() {
499 prepare_inverse_lut_rgb_xyz::<T, BIT_DEPTH, GAMMA_LUT>(dest, &mut lut, options)?;
500 } else {
501 return Err(CmsError::UnsupportedProfileConnection);
502 }
503 } else if dest.pcs == DataColorSpace::Lab {
504 let pcs_to_device = dest
505 .get_pcs_to_device(options.rendering_intent)
506 .ok_or(CmsError::UnsupportedProfileConnection)?;
507 match pcs_to_device {
508 LutWarehouse::Lut(lut_data_type) => {
509 lut = create_lut3x3(lut_data_type, &lut, options, dest.pcs)?
510 }
511 LutWarehouse::Multidimensional(mab) => {
512 prepare_mba_3x3(mab, &mut lut, options, dest.pcs)?
513 }
514 }
515 }
516
517 let is_dest_linear_profile = dest.color_space == DataColorSpace::Rgb
518 && dest.is_matrix_shaper()
519 && dest.is_linear_matrix_shaper();
520
521 #[cfg(all(target_arch = "x86_64", feature = "avx_luts"))]
522 if std::arch::is_x86_feature_detected!("avx2") && std::arch::is_x86_feature_detected!("fma")
523 {
524 return Ok(make_transformer_4x3_avx_fma::<T, GRID_SIZE, BIT_DEPTH>(
525 dst_layout,
526 lut,
527 options,
528 dest.color_space,
529 is_dest_linear_profile,
530 ));
531 }
532 #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse_luts"))]
533 if std::arch::is_x86_feature_detected!("sse4.1") {
534 return Ok(make_transformer_4x3_sse41::<T, GRID_SIZE, BIT_DEPTH>(
535 dst_layout,
536 lut,
537 options,
538 dest.color_space,
539 is_dest_linear_profile,
540 ));
541 }
542
543 Ok(make_transformer_4x3::<T, GRID_SIZE, BIT_DEPTH>(
544 dst_layout,
545 lut,
546 options,
547 dest.color_space,
548 is_dest_linear_profile,
549 ))
550 } else if (source.color_space == DataColorSpace::Rgb
551 || source.color_space == DataColorSpace::Lab)
552 && (dest.color_space == DataColorSpace::Cmyk || dest.color_space == DataColorSpace::Color4)
553 {
554 source.color_space.check_layout(src_layout)?;
555 dest.color_space.check_layout(dst_layout)?;
556
557 if source.pcs != DataColorSpace::Xyz && source.pcs != DataColorSpace::Lab {
558 return Err(CmsError::UnsupportedProfileConnection);
559 }
560
561 const GRID_SIZE: usize = 33;
562
563 let mut lut: Vec<f32>;
564
565 if source.has_device_to_pcs_lut() {
566 let device_to_pcs = source
567 .get_device_to_pcs(options.rendering_intent)
568 .ok_or(CmsError::UnsupportedProfileConnection)?;
569 lut = create_lut3_samples_norm::<GRID_SIZE>();
570
571 match device_to_pcs {
572 LutWarehouse::Lut(lut_data_type) => {
573 lut = create_lut3x3(lut_data_type, &lut, options, source.pcs)?;
574 }
575 LutWarehouse::Multidimensional(mab) => {
576 prepare_mab_3x3(mab, &mut lut, options, source.pcs)?
577 }
578 }
579 } else if source.is_matrix_shaper() {
580 lut = create_rgb_lin_lut::<T, BIT_DEPTH, LINEAR_CAP, GRID_SIZE>(source, options)?;
581 } else {
582 return Err(CmsError::UnsupportedProfileConnection);
583 }
584
585 pcs_lab_v2_to_v4(source, &mut lut);
586
587 if source.pcs == DataColorSpace::Xyz && dest.pcs == DataColorSpace::Lab {
588 let xyz_to_lab = StageXyzToLab::default();
589 xyz_to_lab.transform(&mut lut)?;
590 } else if source.pcs == DataColorSpace::Lab && dest.pcs == DataColorSpace::Xyz {
591 let lab_to_xyz_stage = StageLabToXyz::default();
592 lab_to_xyz_stage.transform(&mut lut)?;
593 }
594
595 pcs_lab_v4_to_v2(dest, &mut lut);
596
597 let lut = match dest
598 .get_pcs_to_device(options.rendering_intent)
599 .ok_or(CmsError::UnsupportedProfileConnection)?
600 {
601 LutWarehouse::Lut(lut_type) => create_lut3x4(lut_type, &lut, options, dest.pcs)?,
602 LutWarehouse::Multidimensional(m_curves) => {
603 prepare_mba_3x4(m_curves, &mut lut, options, dest.pcs)?
604 }
605 };
606
607 let is_dest_linear_profile = dest.color_space == DataColorSpace::Rgb
608 && dest.is_matrix_shaper()
609 && dest.is_linear_matrix_shaper();
610
611 Ok(make_transform_3x4::<T, GRID_SIZE, BIT_DEPTH>(
612 src_layout,
613 lut,
614 options,
615 dest.color_space,
616 is_dest_linear_profile,
617 ))
618 } else if (source.color_space.is_three_channels()) && (dest.color_space.is_three_channels()) {
619 source.color_space.check_layout(src_layout)?;
620 dest.color_space.check_layout(dst_layout)?;
621
622 const GRID_SIZE: usize = 33;
623
624 #[cfg(feature = "any_to_any")]
625 let is_katana_required_for_source = if source.is_matrix_shaper() {
626 false
627 } else {
628 source
629 .get_device_to_pcs(options.rendering_intent)
630 .ok_or(CmsError::UnsupportedLutRenderingIntent(
631 source.rendering_intent,
632 ))
633 .map(|x| x.is_katana_required())?
634 };
635
636 #[cfg(feature = "any_to_any")]
637 let is_katana_required_for_destination =
638 if source.is_matrix_shaper() || dest.pcs == DataColorSpace::Xyz {
639 false
640 } else if dest.pcs == DataColorSpace::Lab {
641 dest.get_pcs_to_device(options.rendering_intent)
642 .ok_or(CmsError::UnsupportedProfileConnection)
643 .map(|x| x.is_katana_required())?
644 } else {
645 return Err(CmsError::UnsupportedProfileConnection);
646 };
647
648 #[cfg(feature = "any_to_any")]
649 let mut stages: Vec<Box<KatanaDefaultIntermediate>> = Vec::new();
650
651 #[cfg(feature = "any_to_any")]
653 if is_katana_required_for_source || is_katana_required_for_destination {
654 let source_stage: Box<dyn KatanaInitialStage<f32, T> + Send + Sync> =
655 if source.is_matrix_shaper() {
656 let state = katana_create_rgb_lin_lut::<T, BIT_DEPTH, LINEAR_CAP>(
657 src_layout, source, options,
658 )?;
659 stages.extend(state.stages);
660 state.initial_stage
661 } else {
662 match source.get_device_to_pcs(options.rendering_intent).ok_or(
663 CmsError::UnsupportedLutRenderingIntent(source.rendering_intent),
664 )? {
665 LutWarehouse::Lut(lut) => {
666 katana_input_stage_lut_3x3::<T>(lut, options, source.pcs, BIT_DEPTH)?
667 }
668 LutWarehouse::Multidimensional(mab) => {
669 multi_dimensional_3x3_to_pcs::<T>(mab, options, source.pcs, BIT_DEPTH)?
670 }
671 }
672 };
673
674 stages.push(katana_pcs_lab_v2_to_v4(source));
675 if source.pcs == DataColorSpace::Lab {
676 stages.push(Box::new(KatanaStageLabToXyz::default()));
677 }
678 if dest.pcs == DataColorSpace::Lab {
679 stages.push(Box::new(KatanaStageXyzToLab::default()));
680 }
681 stages.push(katana_pcs_lab_v4_to_v2(dest));
682
683 let final_stage = if dest.has_pcs_to_device_lut() {
684 let pcs_to_device = dest
685 .get_pcs_to_device(options.rendering_intent)
686 .ok_or(CmsError::UnsupportedProfileConnection)?;
687 match pcs_to_device {
688 LutWarehouse::Lut(lut) => {
689 katana_output_stage_lut_3x3::<T>(lut, options, dest.pcs, BIT_DEPTH)?
690 }
691 LutWarehouse::Multidimensional(mab) => {
692 multi_dimensional_3x3_to_device::<T>(mab, options, dest.pcs, BIT_DEPTH)?
693 }
694 }
695 } else if dest.is_matrix_shaper() {
696 let state = katana_prepare_inverse_lut_rgb_xyz::<T, BIT_DEPTH, GAMMA_LUT>(
697 dest, dst_layout, options,
698 )?;
699 stages.extend(state.stages);
700 state.final_stage
701 } else {
702 return Err(CmsError::UnsupportedProfileConnection);
703 };
704
705 let mut post_finalization: Vec<Box<dyn KatanaPostFinalizationStage<T> + Send + Sync>> =
706 Vec::new();
707 if let Some(stage) =
708 prepare_alpha_finalizer::<T>(src_layout, source, dst_layout, dest, BIT_DEPTH)
709 {
710 post_finalization.push(stage);
711 }
712
713 return Ok(Arc::new(Katana::<f32, T> {
714 initial_stage: source_stage,
715 final_stage,
716 stages,
717 post_finalization,
718 }));
719 }
720
721 let mut lut: Vec<f32>;
722
723 if source.has_device_to_pcs_lut() {
724 let device_to_pcs = source
725 .get_device_to_pcs(options.rendering_intent)
726 .ok_or(CmsError::UnsupportedProfileConnection)?;
727 lut = create_lut3_samples_norm::<GRID_SIZE>();
728
729 match device_to_pcs {
730 LutWarehouse::Lut(lut_data_type) => {
731 lut = create_lut3x3(lut_data_type, &lut, options, source.pcs)?;
732 }
733 LutWarehouse::Multidimensional(mab) => {
734 prepare_mab_3x3(mab, &mut lut, options, source.pcs)?
735 }
736 }
737 } else if source.is_matrix_shaper() {
738 lut = create_rgb_lin_lut::<T, BIT_DEPTH, LINEAR_CAP, GRID_SIZE>(source, options)?;
739 } else {
740 return Err(CmsError::UnsupportedProfileConnection);
741 }
742
743 pcs_lab_v2_to_v4(source, &mut lut);
744
745 if source.pcs == DataColorSpace::Xyz && dest.pcs == DataColorSpace::Lab {
746 let xyz_to_lab = StageXyzToLab::default();
747 xyz_to_lab.transform(&mut lut)?;
748 } else if source.pcs == DataColorSpace::Lab && dest.pcs == DataColorSpace::Xyz {
749 let lab_to_xyz_stage = StageLabToXyz::default();
750 lab_to_xyz_stage.transform(&mut lut)?;
751 }
752
753 pcs_lab_v4_to_v2(dest, &mut lut);
754
755 if dest.has_pcs_to_device_lut() {
756 let pcs_to_device = dest
757 .get_pcs_to_device(options.rendering_intent)
758 .ok_or(CmsError::UnsupportedProfileConnection)?;
759 match pcs_to_device {
760 LutWarehouse::Lut(lut_data_type) => {
761 lut = create_lut3x3(lut_data_type, &lut, options, dest.pcs)?;
762 }
763 LutWarehouse::Multidimensional(mab) => {
764 prepare_mba_3x3(mab, &mut lut, options, dest.pcs)?
765 }
766 }
767 } else if dest.is_matrix_shaper() {
768 prepare_inverse_lut_rgb_xyz::<T, BIT_DEPTH, GAMMA_LUT>(dest, &mut lut, options)?;
769 } else {
770 return Err(CmsError::UnsupportedProfileConnection);
771 }
772
773 let is_dest_linear_profile = dest.color_space == DataColorSpace::Rgb
774 && dest.is_matrix_shaper()
775 && dest.is_linear_matrix_shaper();
776
777 #[cfg(all(feature = "avx_luts", target_arch = "x86_64"))]
778 if std::arch::is_x86_feature_detected!("avx2") && std::is_x86_feature_detected!("fma") {
779 return Ok(make_transformer_3x3_avx_fma::<T, GRID_SIZE, BIT_DEPTH>(
780 src_layout,
781 dst_layout,
782 lut,
783 options,
784 dest.color_space,
785 is_dest_linear_profile,
786 ));
787 }
788 #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse_luts"))]
789 if std::arch::is_x86_feature_detected!("sse4.1") {
790 return Ok(make_transformer_3x3_sse41::<T, GRID_SIZE, BIT_DEPTH>(
791 src_layout,
792 dst_layout,
793 lut,
794 options,
795 dest.color_space,
796 is_dest_linear_profile,
797 ));
798 }
799
800 #[cfg(all(target_arch = "aarch64", feature = "neon_luts"))]
801 {
802 Ok(make_transformer_3x3::<T, GRID_SIZE, BIT_DEPTH>(
803 src_layout,
804 dst_layout,
805 lut,
806 options,
807 dest.color_space,
808 is_dest_linear_profile,
809 ))
810 }
811 #[cfg(not(all(target_arch = "aarch64", feature = "neon_luts")))]
812 {
813 Ok(make_transformer_3x3::<T, GRID_SIZE, BIT_DEPTH>(
814 src_layout,
815 dst_layout,
816 lut,
817 options,
818 dest.color_space,
819 is_dest_linear_profile,
820 ))
821 }
822 } else {
823 #[cfg(feature = "any_to_any")]
824 {
825 do_any_to_any::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_LUT>(
826 src_layout, source, dst_layout, dest, options,
827 )
828 }
829 #[cfg(not(feature = "any_to_any"))]
830 {
831 Err(CmsError::UnsupportedProfileConnection)
832 }
833 }
834}