moxcms/conversions/
mab4x3.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::conversions::mab::{BCurves3, MCurves3};
30use crate::safe_math::SafeMul;
31use crate::{
32    CmsError, DataColorSpace, Hypercube, InPlaceStage, InterpolationMethod,
33    LutMultidimensionalType, MalformedSize, Matrix3d, Stage, TransformOptions, Vector3d, Vector3f,
34};
35
36#[allow(dead_code)]
37struct ACurves4x3<'a, const DEPTH: usize> {
38    curve0: Box<[f32; 65536]>,
39    curve1: Box<[f32; 65536]>,
40    curve2: Box<[f32; 65536]>,
41    curve3: Box<[f32; 65536]>,
42    clut: &'a [f32],
43    grid_size: [u8; 4],
44    interpolation_method: InterpolationMethod,
45    pcs: DataColorSpace,
46}
47
48#[allow(dead_code)]
49struct ACurves4x3Optimized<'a> {
50    clut: &'a [f32],
51    grid_size: [u8; 4],
52    interpolation_method: InterpolationMethod,
53    pcs: DataColorSpace,
54}
55
56#[allow(dead_code)]
57impl<const DEPTH: usize> ACurves4x3<'_, DEPTH> {
58    fn transform_impl<Fetch: Fn(f32, f32, f32, f32) -> Vector3f>(
59        &self,
60        src: &[f32],
61        dst: &mut [f32],
62        fetch: Fetch,
63    ) -> Result<(), CmsError> {
64        let scale_value = (DEPTH - 1) as f32;
65
66        assert_eq!(src.len() / 4, dst.len() / 3);
67
68        for (src, dst) in src.chunks_exact(4).zip(dst.chunks_exact_mut(3)) {
69            let a0 = (src[0] * scale_value).round().min(scale_value) as u16;
70            let a1 = (src[1] * scale_value).round().min(scale_value) as u16;
71            let a2 = (src[2] * scale_value).round().min(scale_value) as u16;
72            let a3 = (src[3] * scale_value).round().min(scale_value) as u16;
73            let c = self.curve0[a0 as usize];
74            let m = self.curve1[a1 as usize];
75            let y = self.curve2[a2 as usize];
76            let k = self.curve3[a3 as usize];
77
78            let r = fetch(c, m, y, k);
79            dst[0] = r.v[0];
80            dst[1] = r.v[1];
81            dst[2] = r.v[2];
82        }
83        Ok(())
84    }
85}
86
87#[allow(dead_code)]
88impl ACurves4x3Optimized<'_> {
89    fn transform_impl<Fetch: Fn(f32, f32, f32, f32) -> Vector3f>(
90        &self,
91        src: &[f32],
92        dst: &mut [f32],
93        fetch: Fetch,
94    ) -> Result<(), CmsError> {
95        assert_eq!(src.len() / 4, dst.len() / 3);
96
97        for (src, dst) in src.chunks_exact(4).zip(dst.chunks_exact_mut(3)) {
98            let c = src[0];
99            let m = src[1];
100            let y = src[2];
101            let k = src[3];
102
103            let r = fetch(c, m, y, k);
104            dst[0] = r.v[0];
105            dst[1] = r.v[1];
106            dst[2] = r.v[2];
107        }
108        Ok(())
109    }
110}
111
112impl<const DEPTH: usize> Stage for ACurves4x3<'_, DEPTH> {
113    fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
114        let lut = Hypercube::new_hypercube(self.clut, self.grid_size);
115
116        // If PCS is LAB then linear interpolation should be used
117        if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
118            return self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w));
119        }
120
121        match self.interpolation_method {
122            #[cfg(feature = "options")]
123            InterpolationMethod::Tetrahedral => {
124                self.transform_impl(src, dst, |x, y, z, w| lut.tetra_vec3(x, y, z, w))?;
125            }
126            #[cfg(feature = "options")]
127            InterpolationMethod::Pyramid => {
128                self.transform_impl(src, dst, |x, y, z, w| lut.pyramid_vec3(x, y, z, w))?;
129            }
130            #[cfg(feature = "options")]
131            InterpolationMethod::Prism => {
132                self.transform_impl(src, dst, |x, y, z, w| lut.prism_vec3(x, y, z, w))?;
133            }
134            InterpolationMethod::Linear => {
135                self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w))?;
136            }
137        }
138        Ok(())
139    }
140}
141
142impl Stage for ACurves4x3Optimized<'_> {
143    fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
144        let lut = Hypercube::new_hypercube(self.clut, self.grid_size);
145
146        // If PCS is LAB then linear interpolation should be used
147        if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
148            return self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w));
149        }
150
151        match self.interpolation_method {
152            #[cfg(feature = "options")]
153            InterpolationMethod::Tetrahedral => {
154                self.transform_impl(src, dst, |x, y, z, w| lut.tetra_vec3(x, y, z, w))?;
155            }
156            #[cfg(feature = "options")]
157            InterpolationMethod::Pyramid => {
158                self.transform_impl(src, dst, |x, y, z, w| lut.pyramid_vec3(x, y, z, w))?;
159            }
160            #[cfg(feature = "options")]
161            InterpolationMethod::Prism => {
162                self.transform_impl(src, dst, |x, y, z, w| lut.prism_vec3(x, y, z, w))?;
163            }
164            InterpolationMethod::Linear => {
165                self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w))?;
166            }
167        }
168        Ok(())
169    }
170}
171
172pub(crate) fn prepare_mab_4x3(
173    mab: &LutMultidimensionalType,
174    lut: &mut [f32],
175    options: TransformOptions,
176    pcs: DataColorSpace,
177) -> Result<Vec<f32>, CmsError> {
178    const LERP_DEPTH: usize = 65536;
179    const BP: usize = 13;
180    const DEPTH: usize = 8192;
181    if mab.num_input_channels != 4 && mab.num_output_channels != 3 {
182        return Err(CmsError::UnsupportedProfileConnection);
183    }
184    let mut new_lut = vec![0f32; (lut.len() / 4) * 3];
185    if mab.a_curves.len() == 4 && mab.clut.is_some() {
186        let clut = &mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
187
188        let lut_grid = (mab.grid_points[0] as usize)
189            .safe_mul(mab.grid_points[1] as usize)?
190            .safe_mul(mab.grid_points[2] as usize)?
191            .safe_mul(mab.grid_points[3] as usize)?
192            .safe_mul(mab.num_output_channels as usize)?;
193        if clut.len() != lut_grid {
194            return Err(CmsError::MalformedClut(MalformedSize {
195                size: clut.len(),
196                expected: lut_grid,
197            }));
198        }
199
200        let all_curves_linear = mab.a_curves.iter().all(|curve| curve.is_linear());
201        let grid_size = [
202            mab.grid_points[0],
203            mab.grid_points[1],
204            mab.grid_points[2],
205            mab.grid_points[3],
206        ];
207
208        #[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
209        if all_curves_linear {
210            use crate::conversions::neon::ACurves4x3NeonOptimizedNeon;
211            let a_curves = ACurves4x3NeonOptimizedNeon {
212                clut,
213                grid_size,
214                interpolation_method: options.interpolation_method,
215                pcs,
216            };
217            a_curves.transform(lut, &mut new_lut)?;
218        } else {
219            use crate::conversions::neon::ACurves4x3Neon;
220            let curves: Result<Vec<_>, _> = mab
221                .a_curves
222                .iter()
223                .map(|c| {
224                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
225                        .ok_or(CmsError::InvalidTrcCurve)
226                })
227                .collect();
228
229            let [curve0, curve1, curve2, curve3] =
230                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
231            let a_curves = ACurves4x3Neon::<DEPTH> {
232                curve0,
233                curve1,
234                curve2,
235                curve3,
236                clut,
237                grid_size,
238                interpolation_method: options.interpolation_method,
239                pcs,
240            };
241            a_curves.transform(lut, &mut new_lut)?;
242        }
243
244        #[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
245        {
246            let mut execution_box: Option<Box<dyn Stage>> = None;
247
248            if all_curves_linear {
249                #[cfg(all(target_arch = "x86_64", feature = "avx"))]
250                {
251                    use crate::conversions::avx::ACurves4x3AvxFmaOptimized;
252                    if std::arch::is_x86_feature_detected!("avx2")
253                        && std::arch::is_x86_feature_detected!("fma")
254                    {
255                        execution_box = Some(Box::new(ACurves4x3AvxFmaOptimized {
256                            clut,
257                            grid_size,
258                            interpolation_method: options.interpolation_method,
259                            pcs,
260                        }));
261                    }
262                }
263                if execution_box.is_none() {
264                    execution_box = Some(Box::new(ACurves4x3Optimized {
265                        clut,
266                        grid_size,
267                        interpolation_method: options.interpolation_method,
268                        pcs,
269                    }));
270                }
271            } else {
272                #[cfg(all(target_arch = "x86_64", feature = "avx"))]
273                {
274                    use crate::conversions::avx::ACurves4x3AvxFma;
275                    if std::arch::is_x86_feature_detected!("avx2")
276                        && std::arch::is_x86_feature_detected!("fma")
277                    {
278                        let curves: Result<Vec<_>, _> = mab
279                            .a_curves
280                            .iter()
281                            .map(|c| {
282                                c.build_linearize_table::<u16, LERP_DEPTH, BP>()
283                                    .ok_or(CmsError::InvalidTrcCurve)
284                            })
285                            .collect();
286
287                        let [curve0, curve1, curve2, curve3] =
288                            curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
289                        execution_box = Some(Box::new(ACurves4x3AvxFma::<DEPTH> {
290                            curve0,
291                            curve1,
292                            curve2,
293                            curve3,
294                            clut,
295                            grid_size,
296                            interpolation_method: options.interpolation_method,
297                            pcs,
298                        }));
299                    }
300                }
301
302                if execution_box.is_none() {
303                    let curves: Result<Vec<_>, _> = mab
304                        .a_curves
305                        .iter()
306                        .map(|c| {
307                            c.build_linearize_table::<u16, LERP_DEPTH, BP>()
308                                .ok_or(CmsError::InvalidTrcCurve)
309                        })
310                        .collect();
311
312                    let [curve0, curve1, curve2, curve3] =
313                        curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
314                    execution_box = Some(Box::new(ACurves4x3::<DEPTH> {
315                        curve0,
316                        curve1,
317                        curve2,
318                        curve3,
319                        clut,
320                        grid_size,
321                        interpolation_method: options.interpolation_method,
322                        pcs,
323                    }));
324                }
325            }
326
327            execution_box
328                .expect("Sampler for Multidimensional 4x3 must be set")
329                .transform(lut, &mut new_lut)?;
330        }
331    } else {
332        // Not supported
333        return Err(CmsError::UnsupportedProfileConnection);
334    }
335
336    if mab.m_curves.len() == 3 {
337        let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
338        if !all_curves_linear
339            || !mab.matrix.test_equality(Matrix3d::IDENTITY)
340            || mab.bias.ne(&Vector3d::default())
341        {
342            let curves: Result<Vec<_>, _> = mab
343                .m_curves
344                .iter()
345                .map(|c| {
346                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
347                        .ok_or(CmsError::InvalidTrcCurve)
348                })
349                .collect();
350
351            let [curve0, curve1, curve2] =
352                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
353
354            let matrix = mab.matrix.to_f32();
355            let bias: Vector3f = mab.bias.cast();
356            let m_curves = MCurves3::<DEPTH> {
357                curve0,
358                curve1,
359                curve2,
360                matrix,
361                bias,
362                inverse: false,
363            };
364            m_curves.transform(&mut new_lut)?;
365        }
366    }
367
368    if mab.b_curves.len() == 3 {
369        let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
370        if !all_curves_linear {
371            let curves: Result<Vec<_>, _> = mab
372                .b_curves
373                .iter()
374                .map(|c| {
375                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
376                        .ok_or(CmsError::InvalidTrcCurve)
377                })
378                .collect();
379
380            let [curve0, curve1, curve2] =
381                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
382            let b_curves = BCurves3::<DEPTH> {
383                curve0,
384                curve1,
385                curve2,
386            };
387            b_curves.transform(&mut new_lut)?;
388        }
389    } else {
390        return Err(CmsError::InvalidAtoBLut);
391    }
392
393    Ok(new_lut)
394}