Skip to main content

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 */
29#![cfg(feature = "lut")]
30use crate::conversions::mab::{BCurves3, MCurves3};
31use crate::err::try_vec;
32use crate::safe_math::SafeMul;
33use crate::{
34    CmsError, DataColorSpace, Hypercube, InPlaceStage, InterpolationMethod,
35    LutMultidimensionalType, MalformedSize, Matrix3d, Stage, TransformOptions, Vector3d, Vector3f,
36};
37
38#[allow(dead_code)]
39struct ACurves4x3<'a> {
40    curve0: Box<[f32; 65536]>,
41    curve1: Box<[f32; 65536]>,
42    curve2: Box<[f32; 65536]>,
43    curve3: Box<[f32; 65536]>,
44    clut: &'a [f32],
45    grid_size: [u8; 4],
46    interpolation_method: InterpolationMethod,
47    pcs: DataColorSpace,
48    depth: usize,
49}
50
51#[allow(dead_code)]
52struct ACurves4x3Optimized<'a> {
53    clut: &'a [f32],
54    grid_size: [u8; 4],
55    interpolation_method: InterpolationMethod,
56    pcs: DataColorSpace,
57}
58
59#[allow(dead_code)]
60impl ACurves4x3<'_> {
61    fn transform_impl<Fetch: Fn(f32, f32, f32, f32) -> Vector3f>(
62        &self,
63        src: &[f32],
64        dst: &mut [f32],
65        fetch: Fetch,
66    ) -> Result<(), CmsError> {
67        let scale_value = (self.depth - 1) as f32;
68
69        assert_eq!(src.len() / 4, dst.len() / 3);
70
71        for (src, dst) in src.chunks_exact(4).zip(dst.chunks_exact_mut(3)) {
72            let a0 = (src[0] * scale_value).round().min(scale_value) as u16;
73            let a1 = (src[1] * scale_value).round().min(scale_value) as u16;
74            let a2 = (src[2] * scale_value).round().min(scale_value) as u16;
75            let a3 = (src[3] * scale_value).round().min(scale_value) as u16;
76            let c = self.curve0[a0 as usize];
77            let m = self.curve1[a1 as usize];
78            let y = self.curve2[a2 as usize];
79            let k = self.curve3[a3 as usize];
80
81            let r = fetch(c, m, y, k);
82            dst[0] = r.v[0];
83            dst[1] = r.v[1];
84            dst[2] = r.v[2];
85        }
86        Ok(())
87    }
88}
89
90#[allow(dead_code)]
91impl ACurves4x3Optimized<'_> {
92    fn transform_impl<Fetch: Fn(f32, f32, f32, f32) -> Vector3f>(
93        &self,
94        src: &[f32],
95        dst: &mut [f32],
96        fetch: Fetch,
97    ) -> Result<(), CmsError> {
98        assert_eq!(src.len() / 4, dst.len() / 3);
99
100        for (src, dst) in src.chunks_exact(4).zip(dst.chunks_exact_mut(3)) {
101            let c = src[0];
102            let m = src[1];
103            let y = src[2];
104            let k = src[3];
105
106            let r = fetch(c, m, y, k);
107            dst[0] = r.v[0];
108            dst[1] = r.v[1];
109            dst[2] = r.v[2];
110        }
111        Ok(())
112    }
113}
114
115impl Stage for ACurves4x3<'_> {
116    fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
117        let lut = Hypercube::new_hypercube(self.clut, self.grid_size, 3)?;
118
119        // If PCS is LAB then linear interpolation should be used
120        if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
121            return self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w));
122        }
123
124        match self.interpolation_method {
125            #[cfg(feature = "options")]
126            InterpolationMethod::Tetrahedral => {
127                self.transform_impl(src, dst, |x, y, z, w| lut.tetra_vec3(x, y, z, w))?;
128            }
129            #[cfg(feature = "options")]
130            InterpolationMethod::Pyramid => {
131                self.transform_impl(src, dst, |x, y, z, w| lut.pyramid_vec3(x, y, z, w))?;
132            }
133            #[cfg(feature = "options")]
134            InterpolationMethod::Prism => {
135                self.transform_impl(src, dst, |x, y, z, w| lut.prism_vec3(x, y, z, w))?;
136            }
137            InterpolationMethod::Linear => {
138                self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w))?;
139            }
140        }
141        Ok(())
142    }
143}
144
145impl Stage for ACurves4x3Optimized<'_> {
146    fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
147        let lut = Hypercube::new_hypercube(self.clut, self.grid_size, 3)?;
148
149        // If PCS is LAB then linear interpolation should be used
150        if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
151            return self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w));
152        }
153
154        match self.interpolation_method {
155            #[cfg(feature = "options")]
156            InterpolationMethod::Tetrahedral => {
157                self.transform_impl(src, dst, |x, y, z, w| lut.tetra_vec3(x, y, z, w))?;
158            }
159            #[cfg(feature = "options")]
160            InterpolationMethod::Pyramid => {
161                self.transform_impl(src, dst, |x, y, z, w| lut.pyramid_vec3(x, y, z, w))?;
162            }
163            #[cfg(feature = "options")]
164            InterpolationMethod::Prism => {
165                self.transform_impl(src, dst, |x, y, z, w| lut.prism_vec3(x, y, z, w))?;
166            }
167            InterpolationMethod::Linear => {
168                self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w))?;
169            }
170        }
171        Ok(())
172    }
173}
174
175pub(crate) fn prepare_mab_4x3(
176    mab: &LutMultidimensionalType,
177    lut: &mut [f32],
178    options: TransformOptions,
179    pcs: DataColorSpace,
180) -> Result<Vec<f32>, CmsError> {
181    const LERP_DEPTH: usize = 65536;
182    const BP: usize = 13;
183    const DEPTH: usize = 8192;
184    if mab.num_input_channels != 4 || mab.num_output_channels != 3 {
185        return Err(CmsError::UnsupportedProfileConnection);
186    }
187    let mut new_lut = try_vec![0f32; (lut.len() / 4) * 3];
188    if mab.a_curves.len() == 4 && mab.clut.is_some() {
189        let clut = &mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
190
191        let lut_grid = (mab.grid_points[0] as usize)
192            .safe_mul(mab.grid_points[1] as usize)?
193            .safe_mul(mab.grid_points[2] as usize)?
194            .safe_mul(mab.grid_points[3] as usize)?
195            .safe_mul(mab.num_output_channels as usize)?;
196        if clut.len() != lut_grid {
197            return Err(CmsError::MalformedClut(MalformedSize {
198                size: clut.len(),
199                expected: lut_grid,
200            }));
201        }
202
203        let all_curves_linear = mab.a_curves.iter().all(|curve| curve.is_linear());
204        let grid_size = [
205            mab.grid_points[0],
206            mab.grid_points[1],
207            mab.grid_points[2],
208            mab.grid_points[3],
209        ];
210
211        if all_curves_linear {
212            let l = ACurves4x3Optimized {
213                clut,
214                grid_size,
215                interpolation_method: options.interpolation_method,
216                pcs,
217            };
218            l.transform(lut, &mut new_lut)?;
219        } else {
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 l = ACurves4x3 {
232                curve0,
233                curve1,
234                curve2,
235                curve3,
236                clut,
237                grid_size,
238                interpolation_method: options.interpolation_method,
239                pcs,
240                depth: DEPTH,
241            };
242            l.transform(lut, &mut new_lut)?;
243        }
244    } else {
245        // Not supported
246        return Err(CmsError::UnsupportedProfileConnection);
247    }
248
249    if mab.m_curves.len() == 3 {
250        let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
251        if !all_curves_linear
252            || !mab.matrix.test_equality(Matrix3d::IDENTITY)
253            || mab.bias.ne(&Vector3d::default())
254        {
255            let curves: Result<Vec<_>, _> = mab
256                .m_curves
257                .iter()
258                .map(|c| {
259                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
260                        .ok_or(CmsError::InvalidTrcCurve)
261                })
262                .collect();
263
264            let [curve0, curve1, curve2] =
265                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
266
267            let matrix = mab.matrix.to_f32();
268            let bias: Vector3f = mab.bias.cast();
269            let m_curves = MCurves3 {
270                curve0,
271                curve1,
272                curve2,
273                matrix,
274                bias,
275                inverse: false,
276                depth: DEPTH,
277            };
278            m_curves.transform(&mut new_lut)?;
279        }
280    }
281
282    if mab.b_curves.len() == 3 {
283        let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
284        if !all_curves_linear {
285            let curves: Result<Vec<_>, _> = mab
286                .b_curves
287                .iter()
288                .map(|c| {
289                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
290                        .ok_or(CmsError::InvalidTrcCurve)
291                })
292                .collect();
293
294            let [curve0, curve1, curve2] =
295                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
296            let b_curves = BCurves3::<DEPTH> {
297                curve0,
298                curve1,
299                curve2,
300            };
301            b_curves.transform(&mut new_lut)?;
302        }
303    } else {
304        return Err(CmsError::InvalidAtoBLut);
305    }
306
307    Ok(new_lut)
308}