moxcms/conversions/
mab.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 3/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 */
29use crate::mlaf::mlaf;
30use crate::safe_math::SafeMul;
31use crate::{
32    CmsError, Cube, DataColorSpace, InPlaceStage, InterpolationMethod, LutMultidimensionalType,
33    MalformedSize, Matrix3d, Matrix3f, TransformOptions, Vector3d, Vector3f,
34};
35
36#[allow(unused)]
37struct ACurves3<'a, const DEPTH: usize> {
38    curve0: Box<[f32; 65536]>,
39    curve1: Box<[f32; 65536]>,
40    curve2: Box<[f32; 65536]>,
41    clut: &'a [f32],
42    grid_size: [u8; 3],
43    interpolation_method: InterpolationMethod,
44    pcs: DataColorSpace,
45}
46
47#[allow(unused)]
48struct ACurves3Optimized<'a> {
49    clut: &'a [f32],
50    grid_size: [u8; 3],
51    interpolation_method: InterpolationMethod,
52    pcs: DataColorSpace,
53}
54
55#[allow(unused)]
56impl<const DEPTH: usize> ACurves3<'_, DEPTH> {
57    fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
58        &self,
59        dst: &mut [f32],
60        fetch: Fetch,
61    ) -> Result<(), CmsError> {
62        let scale_value = (DEPTH - 1) as f32;
63
64        for dst in dst.chunks_exact_mut(3) {
65            let a0 = (dst[0] * scale_value).round().min(scale_value) as u16;
66            let a1 = (dst[1] * scale_value).round().min(scale_value) as u16;
67            let a2 = (dst[2] * scale_value).round().min(scale_value) as u16;
68            let b0 = self.curve0[a0 as usize];
69            let b1 = self.curve1[a1 as usize];
70            let b2 = self.curve2[a2 as usize];
71            let interpolated = fetch(b0, b1, b2);
72            dst[0] = interpolated.v[0];
73            dst[1] = interpolated.v[1];
74            dst[2] = interpolated.v[2];
75        }
76        Ok(())
77    }
78}
79
80#[allow(unused)]
81impl ACurves3Optimized<'_> {
82    fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
83        &self,
84        dst: &mut [f32],
85        fetch: Fetch,
86    ) -> Result<(), CmsError> {
87        for dst in dst.chunks_exact_mut(3) {
88            let a0 = dst[0];
89            let a1 = dst[1];
90            let a2 = dst[2];
91            let interpolated = fetch(a0, a1, a2);
92            dst[0] = interpolated.v[0];
93            dst[1] = interpolated.v[1];
94            dst[2] = interpolated.v[2];
95        }
96        Ok(())
97    }
98}
99
100impl<const DEPTH: usize> InPlaceStage for ACurves3<'_, DEPTH> {
101    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
102        let lut = Cube::new_cube(self.clut, self.grid_size);
103
104        // If PCS is LAB then linear interpolation should be used
105        if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
106            return self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z));
107        }
108
109        match self.interpolation_method {
110            #[cfg(feature = "options")]
111            InterpolationMethod::Tetrahedral => {
112                self.transform_impl(dst, |x, y, z| lut.tetra_vec3(x, y, z))?;
113            }
114            #[cfg(feature = "options")]
115            InterpolationMethod::Pyramid => {
116                self.transform_impl(dst, |x, y, z| lut.pyramid_vec3(x, y, z))?;
117            }
118            #[cfg(feature = "options")]
119            InterpolationMethod::Prism => {
120                self.transform_impl(dst, |x, y, z| lut.prism_vec3(x, y, z))?;
121            }
122            InterpolationMethod::Linear => {
123                self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z))?;
124            }
125        }
126        Ok(())
127    }
128}
129
130impl InPlaceStage for ACurves3Optimized<'_> {
131    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
132        let lut = Cube::new_cube(self.clut, self.grid_size);
133
134        // If PCS is LAB then linear interpolation should be used
135        if self.pcs == DataColorSpace::Lab {
136            return self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z));
137        }
138
139        match self.interpolation_method {
140            #[cfg(feature = "options")]
141            InterpolationMethod::Tetrahedral => {
142                self.transform_impl(dst, |x, y, z| lut.tetra_vec3(x, y, z))?;
143            }
144            #[cfg(feature = "options")]
145            InterpolationMethod::Pyramid => {
146                self.transform_impl(dst, |x, y, z| lut.pyramid_vec3(x, y, z))?;
147            }
148            #[cfg(feature = "options")]
149            InterpolationMethod::Prism => {
150                self.transform_impl(dst, |x, y, z| lut.prism_vec3(x, y, z))?;
151            }
152            InterpolationMethod::Linear => {
153                self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z))?;
154            }
155        }
156        Ok(())
157    }
158}
159
160#[allow(unused)]
161struct ACurves3Inverse<'a, const DEPTH: usize> {
162    curve0: Box<[f32; 65536]>,
163    curve1: Box<[f32; 65536]>,
164    curve2: Box<[f32; 65536]>,
165    clut: &'a [f32],
166    grid_size: [u8; 3],
167    interpolation_method: InterpolationMethod,
168    pcs: DataColorSpace,
169}
170
171#[allow(unused)]
172impl<const DEPTH: usize> ACurves3Inverse<'_, DEPTH> {
173    fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
174        &self,
175        dst: &mut [f32],
176        fetch: Fetch,
177    ) -> Result<(), CmsError> {
178        let scale_value = (DEPTH as u32 - 1u32) as f32;
179
180        for dst in dst.chunks_exact_mut(3) {
181            let interpolated = fetch(dst[0], dst[1], dst[2]);
182            let a0 = (interpolated.v[0] * scale_value).round().min(scale_value) as u16;
183            let a1 = (interpolated.v[1] * scale_value).round().min(scale_value) as u16;
184            let a2 = (interpolated.v[2] * scale_value).round().min(scale_value) as u16;
185            let b0 = self.curve0[a0 as usize];
186            let b1 = self.curve1[a1 as usize];
187            let b2 = self.curve2[a2 as usize];
188            dst[0] = b0;
189            dst[1] = b1;
190            dst[2] = b2;
191        }
192        Ok(())
193    }
194}
195
196impl<const DEPTH: usize> InPlaceStage for ACurves3Inverse<'_, DEPTH> {
197    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
198        let lut = Cube::new_cube(self.clut, self.grid_size);
199
200        // If PCS is LAB then linear interpolation should be used
201        if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
202            return self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z));
203        }
204
205        match self.interpolation_method {
206            #[cfg(feature = "options")]
207            InterpolationMethod::Tetrahedral => {
208                self.transform_impl(dst, |x, y, z| lut.tetra_vec3(x, y, z))?;
209            }
210            #[cfg(feature = "options")]
211            InterpolationMethod::Pyramid => {
212                self.transform_impl(dst, |x, y, z| lut.pyramid_vec3(x, y, z))?;
213            }
214            #[cfg(feature = "options")]
215            InterpolationMethod::Prism => {
216                self.transform_impl(dst, |x, y, z| lut.prism_vec3(x, y, z))?;
217            }
218            InterpolationMethod::Linear => {
219                self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z))?;
220            }
221        }
222        Ok(())
223    }
224}
225
226pub(crate) struct MCurves3<const DEPTH: usize> {
227    pub(crate) curve0: Box<[f32; 65536]>,
228    pub(crate) curve1: Box<[f32; 65536]>,
229    pub(crate) curve2: Box<[f32; 65536]>,
230    pub(crate) matrix: Matrix3f,
231    pub(crate) bias: Vector3f,
232    pub(crate) inverse: bool,
233}
234
235impl<const DEPTH: usize> MCurves3<DEPTH> {
236    fn execute_matrix_stage(&self, dst: &mut [f32]) {
237        let m = self.matrix;
238        let b = self.bias;
239
240        if !m.test_equality(Matrix3f::IDENTITY) || !b.eq(&Vector3f::default()) {
241            for dst in dst.chunks_exact_mut(3) {
242                let x = dst[0];
243                let y = dst[1];
244                let z = dst[2];
245                dst[0] = mlaf(mlaf(mlaf(b.v[0], x, m.v[0][0]), y, m.v[0][1]), z, m.v[0][2]);
246                dst[1] = mlaf(mlaf(mlaf(b.v[1], x, m.v[1][0]), y, m.v[1][1]), z, m.v[1][2]);
247                dst[2] = mlaf(mlaf(mlaf(b.v[2], x, m.v[2][0]), y, m.v[2][1]), z, m.v[2][2]);
248            }
249        }
250    }
251}
252
253impl<const DEPTH: usize> InPlaceStage for MCurves3<DEPTH> {
254    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
255        let scale_value = (DEPTH - 1) as f32;
256
257        if self.inverse {
258            self.execute_matrix_stage(dst);
259        }
260
261        for dst in dst.chunks_exact_mut(3) {
262            let a0 = (dst[0] * scale_value).round().min(scale_value) as u16;
263            let a1 = (dst[1] * scale_value).round().min(scale_value) as u16;
264            let a2 = (dst[2] * scale_value).round().min(scale_value) as u16;
265            let b0 = self.curve0[a0 as usize];
266            let b1 = self.curve1[a1 as usize];
267            let b2 = self.curve2[a2 as usize];
268            dst[0] = b0;
269            dst[1] = b1;
270            dst[2] = b2;
271        }
272
273        if !self.inverse {
274            self.execute_matrix_stage(dst);
275        }
276
277        Ok(())
278    }
279}
280
281pub(crate) struct BCurves3<const DEPTH: usize> {
282    pub(crate) curve0: Box<[f32; 65536]>,
283    pub(crate) curve1: Box<[f32; 65536]>,
284    pub(crate) curve2: Box<[f32; 65536]>,
285}
286
287impl<const DEPTH: usize> InPlaceStage for BCurves3<DEPTH> {
288    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
289        let scale_value = (DEPTH - 1) as f32;
290
291        for dst in dst.chunks_exact_mut(3) {
292            let a0 = (dst[0] * scale_value).round().min(scale_value) as u16;
293            let a1 = (dst[1] * scale_value).round().min(scale_value) as u16;
294            let a2 = (dst[2] * scale_value).round().min(scale_value) as u16;
295            let b0 = self.curve0[a0 as usize];
296            let b1 = self.curve1[a1 as usize];
297            let b2 = self.curve2[a2 as usize];
298            dst[0] = b0;
299            dst[1] = b1;
300            dst[2] = b2;
301        }
302
303        Ok(())
304    }
305}
306
307pub(crate) fn prepare_mab_3x3(
308    mab: &LutMultidimensionalType,
309    lut: &mut [f32],
310    options: TransformOptions,
311    pcs: DataColorSpace,
312) -> Result<(), CmsError> {
313    const LERP_DEPTH: usize = 65536;
314    const BP: usize = 13;
315    const DEPTH: usize = 8192;
316
317    if mab.num_input_channels != 3 && mab.num_output_channels != 3 {
318        return Err(CmsError::UnsupportedProfileConnection);
319    }
320    if mab.a_curves.len() == 3 && mab.clut.is_some() {
321        let clut = &mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
322        let lut_grid = (mab.grid_points[0] as usize)
323            .safe_mul(mab.grid_points[1] as usize)?
324            .safe_mul(mab.grid_points[2] as usize)?
325            .safe_mul(mab.num_output_channels as usize)?;
326        if clut.len() != lut_grid {
327            return Err(CmsError::MalformedCurveLutTable(MalformedSize {
328                size: clut.len(),
329                expected: lut_grid,
330            }));
331        }
332
333        let all_curves_linear = mab.a_curves.iter().all(|curve| curve.is_linear());
334        let grid_size = [mab.grid_points[0], mab.grid_points[1], mab.grid_points[2]];
335
336        #[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
337        if all_curves_linear {
338            use crate::conversions::neon::ACurves3OptimizedNeon;
339            let a_curves = ACurves3OptimizedNeon {
340                clut,
341                grid_size,
342                interpolation_method: options.interpolation_method,
343                pcs,
344            };
345            a_curves.transform(lut)?;
346        } else {
347            use crate::conversions::neon::ACurves3Neon;
348            let curves: Result<Vec<_>, _> = mab
349                .a_curves
350                .iter()
351                .map(|c| {
352                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
353                        .ok_or(CmsError::InvalidTrcCurve)
354                })
355                .collect();
356
357            let [curve0, curve1, curve2] =
358                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
359            let a_curves = ACurves3Neon::<DEPTH> {
360                curve0,
361                curve1,
362                curve2,
363                clut,
364                grid_size,
365                interpolation_method: options.interpolation_method,
366                pcs,
367            };
368            a_curves.transform(lut)?;
369        }
370
371        #[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
372        {
373            let mut execution_box: Option<Box<dyn InPlaceStage>> = None;
374
375            if all_curves_linear {
376                #[cfg(all(target_arch = "x86_64", feature = "avx"))]
377                {
378                    use crate::conversions::avx::ACurves3OptimizedAvxFma;
379                    if std::arch::is_x86_feature_detected!("avx2")
380                        && std::arch::is_x86_feature_detected!("fma")
381                    {
382                        execution_box = Some(Box::new(ACurves3OptimizedAvxFma {
383                            clut,
384                            grid_size,
385                            interpolation_method: options.interpolation_method,
386                            pcs,
387                        }));
388                    }
389                }
390                if execution_box.is_none() {
391                    execution_box = Some(Box::new(ACurves3Optimized {
392                        clut,
393                        grid_size,
394                        interpolation_method: options.interpolation_method,
395                        pcs,
396                    }));
397                }
398            } else {
399                #[cfg(all(target_arch = "x86_64", feature = "avx"))]
400                {
401                    use crate::conversions::avx::ACurves3AvxFma;
402                    if std::arch::is_x86_feature_detected!("avx2")
403                        && std::arch::is_x86_feature_detected!("fma")
404                    {
405                        let curves: Result<Vec<_>, _> = mab
406                            .a_curves
407                            .iter()
408                            .map(|c| {
409                                c.build_linearize_table::<u16, LERP_DEPTH, BP>()
410                                    .ok_or(CmsError::InvalidTrcCurve)
411                            })
412                            .collect();
413
414                        let [curve0, curve1, curve2] =
415                            curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
416                        execution_box = Some(Box::new(ACurves3AvxFma::<DEPTH> {
417                            curve0,
418                            curve1,
419                            curve2,
420                            clut,
421                            grid_size,
422                            interpolation_method: options.interpolation_method,
423                            pcs,
424                        }));
425                    }
426                }
427
428                if execution_box.is_none() {
429                    let curves: Result<Vec<_>, _> = mab
430                        .a_curves
431                        .iter()
432                        .map(|c| {
433                            c.build_linearize_table::<u16, LERP_DEPTH, BP>()
434                                .ok_or(CmsError::InvalidTrcCurve)
435                        })
436                        .collect();
437
438                    let [curve0, curve1, curve2] =
439                        curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
440                    execution_box = Some(Box::new(ACurves3::<DEPTH> {
441                        curve0,
442                        curve1,
443                        curve2,
444                        clut,
445                        grid_size,
446                        interpolation_method: options.interpolation_method,
447                        pcs,
448                    }));
449                }
450            }
451
452            execution_box
453                .expect("LUT Sampler on Multidimensional 3x3 must be set")
454                .transform(lut)?;
455        }
456    }
457
458    if mab.m_curves.len() == 3 {
459        let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
460        if !all_curves_linear
461            || !mab.matrix.test_equality(Matrix3d::IDENTITY)
462            || mab.bias.ne(&Vector3d::default())
463        {
464            let curves: Result<Vec<_>, _> = mab
465                .m_curves
466                .iter()
467                .map(|c| {
468                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
469                        .ok_or(CmsError::InvalidTrcCurve)
470                })
471                .collect();
472
473            let [curve0, curve1, curve2] =
474                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
475            let matrix = mab.matrix.to_f32();
476            let bias: Vector3f = mab.bias.cast();
477            let m_curves = MCurves3::<DEPTH> {
478                curve0,
479                curve1,
480                curve2,
481                matrix,
482                bias,
483                inverse: false,
484            };
485            m_curves.transform(lut)?;
486        }
487    }
488
489    if mab.b_curves.len() == 3 {
490        const LERP_DEPTH: usize = 65536;
491        const BP: usize = 13;
492        const DEPTH: usize = 8192;
493        let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
494        if !all_curves_linear {
495            let curves: Result<Vec<_>, _> = mab
496                .b_curves
497                .iter()
498                .map(|c| {
499                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
500                        .ok_or(CmsError::InvalidTrcCurve)
501                })
502                .collect();
503
504            let [curve0, curve1, curve2] =
505                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
506
507            let b_curves = BCurves3::<DEPTH> {
508                curve0,
509                curve1,
510                curve2,
511            };
512            b_curves.transform(lut)?;
513        }
514    } else {
515        return Err(CmsError::InvalidAtoBLut);
516    }
517
518    Ok(())
519}
520
521pub(crate) fn prepare_mba_3x3(
522    mab: &LutMultidimensionalType,
523    lut: &mut [f32],
524    options: TransformOptions,
525    pcs: DataColorSpace,
526) -> Result<(), CmsError> {
527    if mab.num_input_channels != 3 && mab.num_output_channels != 3 {
528        return Err(CmsError::UnsupportedProfileConnection);
529    }
530    const LERP_DEPTH: usize = 65536;
531    const BP: usize = 13;
532    const DEPTH: usize = 8192;
533
534    if mab.b_curves.len() == 3 {
535        let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
536        if !all_curves_linear {
537            let curves: Result<Vec<_>, _> = mab
538                .b_curves
539                .iter()
540                .map(|c| {
541                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
542                        .ok_or(CmsError::InvalidTrcCurve)
543                })
544                .collect();
545
546            let [curve0, curve1, curve2] =
547                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
548            let b_curves = BCurves3::<DEPTH> {
549                curve0,
550                curve1,
551                curve2,
552            };
553            b_curves.transform(lut)?;
554        }
555    } else {
556        return Err(CmsError::InvalidAtoBLut);
557    }
558
559    if mab.m_curves.len() == 3 {
560        let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
561        if !all_curves_linear
562            || !mab.matrix.test_equality(Matrix3d::IDENTITY)
563            || mab.bias.ne(&Vector3d::default())
564        {
565            let curves: Result<Vec<_>, _> = mab
566                .m_curves
567                .iter()
568                .map(|c| {
569                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
570                        .ok_or(CmsError::InvalidTrcCurve)
571                })
572                .collect();
573
574            let [curve0, curve1, curve2] =
575                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
576
577            let matrix = mab.matrix.to_f32();
578            let bias: Vector3f = mab.bias.cast();
579            let m_curves = MCurves3::<DEPTH> {
580                curve0,
581                curve1,
582                curve2,
583                matrix,
584                bias,
585                inverse: true,
586            };
587            m_curves.transform(lut)?;
588        }
589    }
590
591    if mab.a_curves.len() == 3 && mab.clut.is_some() {
592        let clut = &mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
593        let lut_grid = (mab.grid_points[0] as usize)
594            .safe_mul(mab.grid_points[1] as usize)?
595            .safe_mul(mab.grid_points[2] as usize)?
596            .safe_mul(mab.num_output_channels as usize)?;
597        if clut.len() != lut_grid {
598            return Err(CmsError::MalformedCurveLutTable(MalformedSize {
599                size: clut.len(),
600                expected: lut_grid,
601            }));
602        }
603
604        let all_curves_linear = mab.a_curves.iter().all(|curve| curve.is_linear());
605        let grid_size = [mab.grid_points[0], mab.grid_points[1], mab.grid_points[2]];
606
607        #[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
608        if all_curves_linear {
609            use crate::conversions::neon::ACurves3OptimizedNeon;
610            let a_curves = ACurves3OptimizedNeon {
611                clut,
612                grid_size,
613                interpolation_method: options.interpolation_method,
614                pcs,
615            };
616            a_curves.transform(lut)?;
617        } else {
618            use crate::conversions::neon::ACurves3InverseNeon;
619            let curves: Result<Vec<_>, _> = mab
620                .a_curves
621                .iter()
622                .map(|c| {
623                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
624                        .ok_or(CmsError::InvalidTrcCurve)
625                })
626                .collect();
627
628            let [curve0, curve1, curve2] =
629                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
630            let a_curves = ACurves3InverseNeon::<DEPTH> {
631                curve0,
632                curve1,
633                curve2,
634                clut,
635                grid_size,
636                interpolation_method: options.interpolation_method,
637                pcs,
638            };
639            a_curves.transform(lut)?;
640        }
641        #[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
642        {
643            let mut execution_box: Option<Box<dyn InPlaceStage>> = None;
644
645            if all_curves_linear {
646                #[cfg(all(target_arch = "x86_64", feature = "avx"))]
647                {
648                    use crate::conversions::avx::ACurves3OptimizedAvxFma;
649                    if std::arch::is_x86_feature_detected!("avx2")
650                        && std::arch::is_x86_feature_detected!("fma")
651                    {
652                        execution_box = Some(Box::new(ACurves3OptimizedAvxFma {
653                            clut,
654                            grid_size,
655                            interpolation_method: options.interpolation_method,
656                            pcs,
657                        }));
658                    }
659                }
660
661                if execution_box.is_none() {
662                    execution_box = Some(Box::new(ACurves3Optimized {
663                        clut,
664                        grid_size,
665                        interpolation_method: options.interpolation_method,
666                        pcs,
667                    }));
668                }
669            } else {
670                #[cfg(all(target_arch = "x86_64", feature = "avx"))]
671                {
672                    use crate::conversions::avx::ACurves3InverseAvxFma;
673                    if std::arch::is_x86_feature_detected!("avx2")
674                        && std::arch::is_x86_feature_detected!("fma")
675                    {
676                        let curves: Result<Vec<_>, _> = mab
677                            .a_curves
678                            .iter()
679                            .map(|c| {
680                                c.build_linearize_table::<u16, LERP_DEPTH, BP>()
681                                    .ok_or(CmsError::InvalidTrcCurve)
682                            })
683                            .collect();
684
685                        let [curve0, curve1, curve2] =
686                            curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
687                        execution_box = Some(Box::new(ACurves3InverseAvxFma::<DEPTH> {
688                            curve0,
689                            curve1,
690                            curve2,
691                            clut,
692                            grid_size,
693                            interpolation_method: options.interpolation_method,
694                            pcs,
695                        }));
696                    }
697                }
698
699                if execution_box.is_none() {
700                    let curves: Result<Vec<_>, _> = mab
701                        .a_curves
702                        .iter()
703                        .map(|c| {
704                            c.build_linearize_table::<u16, LERP_DEPTH, BP>()
705                                .ok_or(CmsError::InvalidTrcCurve)
706                        })
707                        .collect();
708
709                    let [curve0, curve1, curve2] =
710                        curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
711                    execution_box = Some(Box::new(ACurves3Inverse::<DEPTH> {
712                        curve0,
713                        curve1,
714                        curve2,
715                        clut,
716                        grid_size,
717                        interpolation_method: options.interpolation_method,
718                        pcs,
719                    }));
720                }
721            }
722
723            execution_box
724                .expect("LUT Sampler on Multidimensional Inverse 3x3 must be set")
725                .transform(lut)?;
726        }
727    }
728
729    Ok(())
730}