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