moxcms/conversions/
rgbxyz.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 */
29use crate::{CmsError, Layout, Matrix3, Matrix3f, TransformExecutor};
30use num_traits::AsPrimitive;
31
32pub(crate) struct TransformMatrixShaper<T: Clone, const BUCKET: usize> {
33    pub(crate) r_linear: Box<[f32; BUCKET]>,
34    pub(crate) g_linear: Box<[f32; BUCKET]>,
35    pub(crate) b_linear: Box<[f32; BUCKET]>,
36    pub(crate) r_gamma: Box<[T; 65536]>,
37    pub(crate) g_gamma: Box<[T; 65536]>,
38    pub(crate) b_gamma: Box<[T; 65536]>,
39    pub(crate) adaptation_matrix: Matrix3f,
40}
41
42/// Low memory footprint optimized routine for matrix shaper profiles with the same
43/// Gamma and linear curves.
44pub(crate) struct TransformMatrixShaperOptimized<T: Clone, const BUCKET: usize> {
45    pub(crate) linear: Box<[f32; BUCKET]>,
46    pub(crate) gamma: Box<[T; 65536]>,
47    pub(crate) adaptation_matrix: Matrix3f,
48}
49
50impl<T: Clone + PointeeSizeExpressible, const BUCKET: usize> TransformMatrixShaper<T, BUCKET> {
51    pub(crate) fn to_q2_13_n<
52        R: Copy + 'static + Default,
53        const PRECISION: i32,
54        const LINEAR_CAP: usize,
55        const GAMMA_LUT: usize,
56        const BIT_DEPTH: usize,
57    >(
58        &self,
59    ) -> TransformMatrixShaperFixedPoint<R, T, BUCKET>
60    where
61        f32: AsPrimitive<R>,
62    {
63        let linear_scale = if T::FINITE {
64            let lut_scale = (GAMMA_LUT - 1) as f32 / ((1 << BIT_DEPTH) - 1) as f32;
65            ((1 << BIT_DEPTH) - 1) as f32 * lut_scale
66        } else {
67            let lut_scale = (GAMMA_LUT - 1) as f32 / (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32;
68            (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32 * lut_scale
69        };
70        let mut new_box_r = Box::new([R::default(); BUCKET]);
71        let mut new_box_g = Box::new([R::default(); BUCKET]);
72        let mut new_box_b = Box::new([R::default(); BUCKET]);
73        for (dst, &src) in new_box_r.iter_mut().zip(self.r_linear.iter()) {
74            *dst = (src * linear_scale).round().as_();
75        }
76        for (dst, &src) in new_box_g.iter_mut().zip(self.g_linear.iter()) {
77            *dst = (src * linear_scale).round().as_();
78        }
79        for (dst, &src) in new_box_b.iter_mut().zip(self.b_linear.iter()) {
80            *dst = (src * linear_scale).round().as_();
81        }
82        let scale: f32 = (1i32 << PRECISION) as f32;
83        let source_matrix = self.adaptation_matrix;
84        let mut dst_matrix = Matrix3::<i16> { v: [[0i16; 3]; 3] };
85        for i in 0..3 {
86            for j in 0..3 {
87                dst_matrix.v[i][j] = (source_matrix.v[i][j] * scale) as i16;
88            }
89        }
90        TransformMatrixShaperFixedPoint {
91            r_linear: new_box_r,
92            g_linear: new_box_g,
93            b_linear: new_box_b,
94            r_gamma: self.r_gamma.clone(),
95            g_gamma: self.g_gamma.clone(),
96            b_gamma: self.b_gamma.clone(),
97            adaptation_matrix: dst_matrix,
98        }
99    }
100}
101
102impl<T: Clone + PointeeSizeExpressible, const BUCKET: usize>
103    TransformMatrixShaperOptimized<T, BUCKET>
104{
105    pub(crate) fn to_q2_13_n<
106        R: Copy + 'static + Default,
107        const PRECISION: i32,
108        const LINEAR_CAP: usize,
109        const GAMMA_LUT: usize,
110        const BIT_DEPTH: usize,
111    >(
112        &self,
113    ) -> TransformMatrixShaperFixedPointOpt<R, i16, T, BUCKET>
114    where
115        f32: AsPrimitive<R>,
116    {
117        let linear_scale = if T::FINITE {
118            let lut_scale = (GAMMA_LUT - 1) as f32 / ((1 << BIT_DEPTH) - 1) as f32;
119            ((1 << BIT_DEPTH) - 1) as f32 * lut_scale
120        } else {
121            let lut_scale = (GAMMA_LUT - 1) as f32 / (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32;
122            (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32 * lut_scale
123        };
124        let mut new_box_linear = Box::new([R::default(); BUCKET]);
125        for (dst, src) in new_box_linear.iter_mut().zip(self.linear.iter()) {
126            *dst = (*src * linear_scale).round().as_();
127        }
128        let scale: f32 = (1i32 << PRECISION) as f32;
129        let source_matrix = self.adaptation_matrix;
130        let mut dst_matrix = Matrix3::<i16> {
131            v: [[i16::default(); 3]; 3],
132        };
133        for i in 0..3 {
134            for j in 0..3 {
135                dst_matrix.v[i][j] = (source_matrix.v[i][j] * scale) as i16;
136            }
137        }
138        TransformMatrixShaperFixedPointOpt {
139            linear: new_box_linear,
140            gamma: self.gamma.clone(),
141            adaptation_matrix: dst_matrix,
142        }
143    }
144
145    #[allow(dead_code)]
146    pub(crate) fn to_q1_30_n<
147        R: Copy + 'static + Default,
148        const PRECISION: i32,
149        const LINEAR_CAP: usize,
150        const GAMMA_LUT: usize,
151        const BIT_DEPTH: usize,
152    >(
153        &self,
154    ) -> TransformMatrixShaperFixedPointOpt<R, i32, T, BUCKET>
155    where
156        f32: AsPrimitive<R>,
157        f64: AsPrimitive<R>,
158    {
159        // It is important to scale 1 bit more to compensate vqrdmlah Q0.31, because we're going to use Q1.30
160        let table_size = if T::FINITE {
161            (1 << BIT_DEPTH) - 1
162        } else {
163            T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
164        };
165        let ext_bp = if T::FINITE {
166            BIT_DEPTH as u32 + 1
167        } else {
168            let bp = (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1).count_ones();
169            bp + 1
170        };
171        let linear_scale = {
172            let lut_scale = (GAMMA_LUT - 1) as f64 / table_size as f64;
173            ((1u32 << ext_bp) - 1) as f64 * lut_scale
174        };
175        let mut new_box_linear = Box::new([R::default(); BUCKET]);
176        for (dst, &src) in new_box_linear.iter_mut().zip(self.linear.iter()) {
177            *dst = (src as f64 * linear_scale).round().as_();
178        }
179        let scale: f64 = (1i64 << PRECISION) as f64;
180        let source_matrix = self.adaptation_matrix;
181        let mut dst_matrix = Matrix3::<i32> {
182            v: [[i32::default(); 3]; 3],
183        };
184        for i in 0..3 {
185            for j in 0..3 {
186                dst_matrix.v[i][j] = (source_matrix.v[i][j] as f64 * scale) as i32;
187            }
188        }
189        TransformMatrixShaperFixedPointOpt {
190            linear: new_box_linear,
191            gamma: self.gamma.clone(),
192            adaptation_matrix: dst_matrix,
193        }
194    }
195}
196
197#[allow(unused)]
198struct TransformMatrixShaperScalar<
199    T: Clone,
200    const SRC_LAYOUT: u8,
201    const DST_LAYOUT: u8,
202    const LINEAR_CAP: usize,
203    const GAMMA_LUT: usize,
204    const BIT_DEPTH: usize,
205> {
206    pub(crate) profile: TransformMatrixShaper<T, LINEAR_CAP>,
207}
208
209#[allow(unused)]
210struct TransformMatrixShaperOptScalar<
211    T: Clone,
212    const SRC_LAYOUT: u8,
213    const DST_LAYOUT: u8,
214    const LINEAR_CAP: usize,
215    const GAMMA_LUT: usize,
216    const BIT_DEPTH: usize,
217> {
218    pub(crate) profile: TransformMatrixShaperOptimized<T, LINEAR_CAP>,
219}
220
221#[cfg(any(
222    any(target_arch = "x86", target_arch = "x86_64"),
223    all(target_arch = "aarch64", target_feature = "neon")
224))]
225#[allow(unused)]
226macro_rules! create_rgb_xyz_dependant_executor {
227    ($dep_name: ident, $dependant: ident, $shaper: ident) => {
228        pub(crate) fn $dep_name<
229            T: Clone + Send + Sync + Default + PointeeSizeExpressible + Copy + 'static,
230            const LINEAR_CAP: usize,
231            const GAMMA_LUT: usize,
232            const BIT_DEPTH: usize,
233        >(
234            src_layout: Layout,
235            dst_layout: Layout,
236            profile: $shaper<T, LINEAR_CAP>,
237        ) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
238        where
239            u32: AsPrimitive<T>,
240        {
241            if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
242                return Ok(Box::new($dependant::<
243                    T,
244                    { Layout::Rgba as u8 },
245                    { Layout::Rgba as u8 },
246                    LINEAR_CAP,
247                    GAMMA_LUT,
248                > {
249                    profile,
250                    bit_depth: BIT_DEPTH,
251                }));
252            } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
253                return Ok(Box::new($dependant::<
254                    T,
255                    { Layout::Rgb as u8 },
256                    { Layout::Rgba as u8 },
257                    LINEAR_CAP,
258                    GAMMA_LUT,
259                > {
260                    profile,
261                    bit_depth: BIT_DEPTH,
262                }));
263            } else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
264                return Ok(Box::new($dependant::<
265                    T,
266                    { Layout::Rgba as u8 },
267                    { Layout::Rgb as u8 },
268                    LINEAR_CAP,
269                    GAMMA_LUT,
270                > {
271                    profile,
272                    bit_depth: BIT_DEPTH,
273                }));
274            } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
275                return Ok(Box::new($dependant::<
276                    T,
277                    { Layout::Rgb as u8 },
278                    { Layout::Rgb as u8 },
279                    LINEAR_CAP,
280                    GAMMA_LUT,
281                > {
282                    profile,
283                    bit_depth: BIT_DEPTH,
284                }));
285            }
286            Err(CmsError::UnsupportedProfileConnection)
287        }
288    };
289}
290
291#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
292use crate::conversions::sse::{TransformShaperRgbOptSse, TransformShaperRgbSse};
293
294#[cfg(all(target_arch = "x86_64", feature = "avx"))]
295use crate::conversions::avx::{TransformShaperRgbAvx, TransformShaperRgbOptAvx};
296
297#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
298create_rgb_xyz_dependant_executor!(
299    make_rgb_xyz_rgb_transform_sse_41,
300    TransformShaperRgbSse,
301    TransformMatrixShaper
302);
303
304#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
305create_rgb_xyz_dependant_executor!(
306    make_rgb_xyz_rgb_transform_sse_41_opt,
307    TransformShaperRgbOptSse,
308    TransformMatrixShaperOptimized
309);
310
311#[cfg(all(target_arch = "x86_64", feature = "avx"))]
312create_rgb_xyz_dependant_executor!(
313    make_rgb_xyz_rgb_transform_avx2,
314    TransformShaperRgbAvx,
315    TransformMatrixShaper
316);
317
318#[cfg(all(target_arch = "x86_64", feature = "avx"))]
319create_rgb_xyz_dependant_executor!(
320    make_rgb_xyz_rgb_transform_avx2_opt,
321    TransformShaperRgbOptAvx,
322    TransformMatrixShaperOptimized
323);
324
325#[cfg(all(target_arch = "x86_64", feature = "avx512"))]
326use crate::conversions::avx512::TransformShaperRgbOptAvx512;
327
328#[cfg(all(target_arch = "x86_64", feature = "avx512"))]
329create_rgb_xyz_dependant_executor!(
330    make_rgb_xyz_rgb_transform_avx512_opt,
331    TransformShaperRgbOptAvx512,
332    TransformMatrixShaperOptimized
333);
334
335#[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
336pub(crate) fn make_rgb_xyz_rgb_transform<
337    T: Clone + Send + Sync + PointeeSizeExpressible + 'static + Copy + Default,
338    const LINEAR_CAP: usize,
339    const GAMMA_LUT: usize,
340    const BIT_DEPTH: usize,
341>(
342    src_layout: Layout,
343    dst_layout: Layout,
344    profile: TransformMatrixShaper<T, LINEAR_CAP>,
345) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
346where
347    u32: AsPrimitive<T>,
348{
349    #[cfg(all(feature = "avx", target_arch = "x86_64"))]
350    if std::arch::is_x86_feature_detected!("avx2") {
351        return make_rgb_xyz_rgb_transform_avx2::<T, LINEAR_CAP, GAMMA_LUT, BIT_DEPTH>(
352            src_layout, dst_layout, profile,
353        );
354    }
355    #[cfg(all(feature = "sse", any(target_arch = "x86", target_arch = "x86_64")))]
356    if std::arch::is_x86_feature_detected!("sse4.1") {
357        return make_rgb_xyz_rgb_transform_sse_41::<T, LINEAR_CAP, GAMMA_LUT, BIT_DEPTH>(
358            src_layout, dst_layout, profile,
359        );
360    }
361    if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
362        return Ok(Box::new(TransformMatrixShaperScalar::<
363            T,
364            { Layout::Rgba as u8 },
365            { Layout::Rgba as u8 },
366            LINEAR_CAP,
367            GAMMA_LUT,
368            BIT_DEPTH,
369        > {
370            profile,
371        }));
372    } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
373        return Ok(Box::new(TransformMatrixShaperScalar::<
374            T,
375            { Layout::Rgb as u8 },
376            { Layout::Rgba as u8 },
377            LINEAR_CAP,
378            GAMMA_LUT,
379            BIT_DEPTH,
380        > {
381            profile,
382        }));
383    } else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
384        return Ok(Box::new(TransformMatrixShaperScalar::<
385            T,
386            { Layout::Rgba as u8 },
387            { Layout::Rgb as u8 },
388            LINEAR_CAP,
389            GAMMA_LUT,
390            BIT_DEPTH,
391        > {
392            profile,
393        }));
394    } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
395        return Ok(Box::new(TransformMatrixShaperScalar::<
396            T,
397            { Layout::Rgb as u8 },
398            { Layout::Rgb as u8 },
399            LINEAR_CAP,
400            GAMMA_LUT,
401            BIT_DEPTH,
402        > {
403            profile,
404        }));
405    }
406    Err(CmsError::UnsupportedProfileConnection)
407}
408
409#[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
410pub(crate) fn make_rgb_xyz_rgb_transform_opt<
411    T: Clone + Send + Sync + PointeeSizeExpressible + 'static + Copy + Default,
412    const LINEAR_CAP: usize,
413    const GAMMA_LUT: usize,
414    const BIT_DEPTH: usize,
415>(
416    src_layout: Layout,
417    dst_layout: Layout,
418    profile: TransformMatrixShaperOptimized<T, LINEAR_CAP>,
419) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
420where
421    u32: AsPrimitive<T>,
422{
423    #[cfg(all(feature = "avx512", target_arch = "x86_64"))]
424    if std::arch::is_x86_feature_detected!("avx512bw")
425        && std::arch::is_x86_feature_detected!("avx512vl")
426        && std::arch::is_x86_feature_detected!("fma")
427    {
428        return make_rgb_xyz_rgb_transform_avx512_opt::<T, LINEAR_CAP, GAMMA_LUT, BIT_DEPTH>(
429            src_layout, dst_layout, profile,
430        );
431    }
432    #[cfg(all(feature = "avx", target_arch = "x86_64"))]
433    if std::arch::is_x86_feature_detected!("avx2") {
434        return make_rgb_xyz_rgb_transform_avx2_opt::<T, LINEAR_CAP, GAMMA_LUT, BIT_DEPTH>(
435            src_layout, dst_layout, profile,
436        );
437    }
438    #[cfg(all(feature = "sse", any(target_arch = "x86", target_arch = "x86_64")))]
439    if std::arch::is_x86_feature_detected!("sse4.1") {
440        return make_rgb_xyz_rgb_transform_sse_41_opt::<T, LINEAR_CAP, GAMMA_LUT, BIT_DEPTH>(
441            src_layout, dst_layout, profile,
442        );
443    }
444    if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
445        return Ok(Box::new(TransformMatrixShaperOptScalar::<
446            T,
447            { Layout::Rgba as u8 },
448            { Layout::Rgba as u8 },
449            LINEAR_CAP,
450            GAMMA_LUT,
451            BIT_DEPTH,
452        > {
453            profile,
454        }));
455    } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
456        return Ok(Box::new(TransformMatrixShaperOptScalar::<
457            T,
458            { Layout::Rgb as u8 },
459            { Layout::Rgba as u8 },
460            LINEAR_CAP,
461            GAMMA_LUT,
462            BIT_DEPTH,
463        > {
464            profile,
465        }));
466    } else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
467        return Ok(Box::new(TransformMatrixShaperOptScalar::<
468            T,
469            { Layout::Rgba as u8 },
470            { Layout::Rgb as u8 },
471            LINEAR_CAP,
472            GAMMA_LUT,
473            BIT_DEPTH,
474        > {
475            profile,
476        }));
477    } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
478        return Ok(Box::new(TransformMatrixShaperOptScalar::<
479            T,
480            { Layout::Rgb as u8 },
481            { Layout::Rgb as u8 },
482            LINEAR_CAP,
483            GAMMA_LUT,
484            BIT_DEPTH,
485        > {
486            profile,
487        }));
488    }
489    Err(CmsError::UnsupportedProfileConnection)
490}
491
492#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
493use crate::conversions::neon::{TransformShaperRgbNeon, TransformShaperRgbOptNeon};
494use crate::conversions::rgbxyz_fixed::{
495    TransformMatrixShaperFixedPoint, TransformMatrixShaperFixedPointOpt,
496};
497use crate::transform::PointeeSizeExpressible;
498
499#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
500create_rgb_xyz_dependant_executor!(
501    make_rgb_xyz_rgb_transform,
502    TransformShaperRgbNeon,
503    TransformMatrixShaper
504);
505
506#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
507create_rgb_xyz_dependant_executor!(
508    make_rgb_xyz_rgb_transform_opt,
509    TransformShaperRgbOptNeon,
510    TransformMatrixShaperOptimized
511);
512
513#[allow(unused)]
514impl<
515    T: Clone + PointeeSizeExpressible + Copy + Default + 'static,
516    const SRC_LAYOUT: u8,
517    const DST_LAYOUT: u8,
518    const LINEAR_CAP: usize,
519    const GAMMA_LUT: usize,
520    const BIT_DEPTH: usize,
521> TransformExecutor<T>
522    for TransformMatrixShaperScalar<T, SRC_LAYOUT, DST_LAYOUT, LINEAR_CAP, GAMMA_LUT, BIT_DEPTH>
523where
524    u32: AsPrimitive<T>,
525{
526    fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
527        use crate::mlaf::mlaf;
528        let src_cn = Layout::from(SRC_LAYOUT);
529        let dst_cn = Layout::from(DST_LAYOUT);
530        let src_channels = src_cn.channels();
531        let dst_channels = dst_cn.channels();
532
533        if src.len() / src_channels != dst.len() / dst_channels {
534            return Err(CmsError::LaneSizeMismatch);
535        }
536        if src.len() % src_channels != 0 {
537            return Err(CmsError::LaneMultipleOfChannels);
538        }
539        if dst.len() % dst_channels != 0 {
540            return Err(CmsError::LaneMultipleOfChannels);
541        }
542
543        let transform = self.profile.adaptation_matrix;
544        let scale = (GAMMA_LUT - 1) as f32;
545        let max_colors: T = ((1 << BIT_DEPTH) - 1).as_();
546
547        for (src, dst) in src
548            .chunks_exact(src_channels)
549            .zip(dst.chunks_exact_mut(dst_channels))
550        {
551            let r = self.profile.r_linear[src[src_cn.r_i()]._as_usize()];
552            let g = self.profile.g_linear[src[src_cn.g_i()]._as_usize()];
553            let b = self.profile.b_linear[src[src_cn.b_i()]._as_usize()];
554            let a = if src_channels == 4 {
555                src[src_cn.a_i()]
556            } else {
557                max_colors
558            };
559
560            let new_r = mlaf(
561                0.5f32,
562                mlaf(
563                    mlaf(r * transform.v[0][0], g, transform.v[0][1]),
564                    b,
565                    transform.v[0][2],
566                )
567                .max(0f32)
568                .min(1f32),
569                scale,
570            );
571
572            let new_g = mlaf(
573                0.5f32,
574                mlaf(
575                    mlaf(r * transform.v[1][0], g, transform.v[1][1]),
576                    b,
577                    transform.v[1][2],
578                )
579                .max(0f32)
580                .min(1f32),
581                scale,
582            );
583
584            let new_b = mlaf(
585                0.5f32,
586                mlaf(
587                    mlaf(r * transform.v[2][0], g, transform.v[2][1]),
588                    b,
589                    transform.v[2][2],
590                )
591                .max(0f32)
592                .min(1f32),
593                scale,
594            );
595
596            dst[dst_cn.r_i()] = self.profile.r_gamma[(new_r as u16) as usize];
597            dst[dst_cn.g_i()] = self.profile.g_gamma[(new_g as u16) as usize];
598            dst[dst_cn.b_i()] = self.profile.b_gamma[(new_b as u16) as usize];
599            if dst_channels == 4 {
600                dst[dst_cn.a_i()] = a;
601            }
602        }
603
604        Ok(())
605    }
606}
607
608#[allow(unused)]
609impl<
610    T: Clone + PointeeSizeExpressible + Copy + Default + 'static,
611    const SRC_LAYOUT: u8,
612    const DST_LAYOUT: u8,
613    const LINEAR_CAP: usize,
614    const GAMMA_LUT: usize,
615    const BIT_DEPTH: usize,
616> TransformExecutor<T>
617    for TransformMatrixShaperOptScalar<T, SRC_LAYOUT, DST_LAYOUT, LINEAR_CAP, GAMMA_LUT, BIT_DEPTH>
618where
619    u32: AsPrimitive<T>,
620{
621    fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
622        use crate::mlaf::mlaf;
623        let src_cn = Layout::from(SRC_LAYOUT);
624        let dst_cn = Layout::from(DST_LAYOUT);
625        let src_channels = src_cn.channels();
626        let dst_channels = dst_cn.channels();
627
628        if src.len() / src_channels != dst.len() / dst_channels {
629            return Err(CmsError::LaneSizeMismatch);
630        }
631        if src.len() % src_channels != 0 {
632            return Err(CmsError::LaneMultipleOfChannels);
633        }
634        if dst.len() % dst_channels != 0 {
635            return Err(CmsError::LaneMultipleOfChannels);
636        }
637
638        let transform = self.profile.adaptation_matrix;
639        let scale = (GAMMA_LUT - 1) as f32;
640        let max_colors: T = ((1 << BIT_DEPTH) - 1).as_();
641
642        for (src, dst) in src
643            .chunks_exact(src_channels)
644            .zip(dst.chunks_exact_mut(dst_channels))
645        {
646            let r = self.profile.linear[src[src_cn.r_i()]._as_usize()];
647            let g = self.profile.linear[src[src_cn.g_i()]._as_usize()];
648            let b = self.profile.linear[src[src_cn.b_i()]._as_usize()];
649            let a = if src_channels == 4 {
650                src[src_cn.a_i()]
651            } else {
652                max_colors
653            };
654
655            let new_r = mlaf(
656                0.5f32,
657                mlaf(
658                    mlaf(r * transform.v[0][0], g, transform.v[0][1]),
659                    b,
660                    transform.v[0][2],
661                )
662                .max(0f32)
663                .min(1f32),
664                scale,
665            );
666
667            let new_g = mlaf(
668                0.5f32,
669                mlaf(
670                    mlaf(r * transform.v[1][0], g, transform.v[1][1]),
671                    b,
672                    transform.v[1][2],
673                )
674                .max(0f32)
675                .min(1f32),
676                scale,
677            );
678
679            let new_b = mlaf(
680                0.5f32,
681                mlaf(
682                    mlaf(r * transform.v[2][0], g, transform.v[2][1]),
683                    b,
684                    transform.v[2][2],
685                )
686                .max(0f32)
687                .min(1f32),
688                scale,
689            );
690
691            dst[dst_cn.r_i()] = self.profile.gamma[(new_r as u16) as usize];
692            dst[dst_cn.g_i()] = self.profile.gamma[(new_g as u16) as usize];
693            dst[dst_cn.b_i()] = self.profile.gamma[(new_b as u16) as usize];
694            if dst_channels == 4 {
695                dst[dst_cn.a_i()] = a;
696            }
697        }
698
699        Ok(())
700    }
701}