Skip to main content

moxcms/conversions/
lut_transforms.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
3 * //
4 * // Redistribution and use in source and binary forms, with or without modification,
5 * // are permitted provided that the following conditions are met:
6 * //
7 * // 1.  Redistributions of source code must retain the above copyright notice, this
8 * // list of conditions and the following disclaimer.
9 * //
10 * // 2.  Redistributions in binary form must reproduce the above copyright notice,
11 * // this list of conditions and the following disclaimer in the documentation
12 * // and/or other materials provided with the distribution.
13 * //
14 * // 3.  Neither the name of the copyright holder nor the names of its
15 * // contributors may be used to endorse or promote products derived from
16 * // this software without specific prior written permission.
17 * //
18 * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29#![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// use crate::conversions::bpc::compensate_bpc_in_lut;
311
312#[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 source.color_space == DataColorSpace::Cmyk
478        //     && (options.rendering_intent == RenderingIntent::Perceptual
479        //         || options.rendering_intent == RenderingIntent::RelativeColorimetric)
480        //     && options.black_point_compensation
481        // {
482        //     if let (Some(src_bp), Some(dst_bp)) = (
483        //         source.detect_black_point::<GRID_SIZE>(&lut),
484        //         dest.detect_black_point::<GRID_SIZE>(&lut),
485        //     ) {
486        //         compensate_bpc_in_lut(&mut lut, src_bp, dst_bp);
487        //     }
488        // }
489
490        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        // Slow and accurate fallback if anything not acceptable is detected by curve analysis
652        #[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}