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> {
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    depth: usize,
46}
47
48#[allow(unused)]
49struct ACurves3Optimized<'a> {
50    clut: &'a [f32],
51    grid_size: [u8; 3],
52    interpolation_method: InterpolationMethod,
53    pcs: DataColorSpace,
54}
55
56#[allow(unused)]
57impl ACurves3<'_> {
58    fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
59        &self,
60        dst: &mut [f32],
61        fetch: Fetch,
62    ) -> Result<(), CmsError> {
63        let scale_value = (self.depth - 1) as f32;
64
65        for dst in dst.chunks_exact_mut(3) {
66            let a0 = (dst[0] * scale_value).round().min(scale_value) as u16;
67            let a1 = (dst[1] * scale_value).round().min(scale_value) as u16;
68            let a2 = (dst[2] * scale_value).round().min(scale_value) as u16;
69            let b0 = self.curve0[a0 as usize];
70            let b1 = self.curve1[a1 as usize];
71            let b2 = self.curve2[a2 as usize];
72            let interpolated = fetch(b0, b1, b2);
73            dst[0] = interpolated.v[0];
74            dst[1] = interpolated.v[1];
75            dst[2] = interpolated.v[2];
76        }
77        Ok(())
78    }
79}
80
81#[allow(unused)]
82impl ACurves3Optimized<'_> {
83    fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
84        &self,
85        dst: &mut [f32],
86        fetch: Fetch,
87    ) -> Result<(), CmsError> {
88        for dst in dst.chunks_exact_mut(3) {
89            let a0 = dst[0];
90            let a1 = dst[1];
91            let a2 = dst[2];
92            let interpolated = fetch(a0, a1, a2);
93            dst[0] = interpolated.v[0];
94            dst[1] = interpolated.v[1];
95            dst[2] = interpolated.v[2];
96        }
97        Ok(())
98    }
99}
100
101impl InPlaceStage for ACurves3<'_> {
102    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
103        let lut = Cube::new_checked_cube(self.clut, self.grid_size, 3)?;
104
105        // If PCS is LAB then linear interpolation should be used
106        if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
107            return self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z));
108        }
109
110        match self.interpolation_method {
111            #[cfg(feature = "options")]
112            InterpolationMethod::Tetrahedral => {
113                self.transform_impl(dst, |x, y, z| lut.tetra_vec3(x, y, z))?;
114            }
115            #[cfg(feature = "options")]
116            InterpolationMethod::Pyramid => {
117                self.transform_impl(dst, |x, y, z| lut.pyramid_vec3(x, y, z))?;
118            }
119            #[cfg(feature = "options")]
120            InterpolationMethod::Prism => {
121                self.transform_impl(dst, |x, y, z| lut.prism_vec3(x, y, z))?;
122            }
123            InterpolationMethod::Linear => {
124                self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z))?;
125            }
126        }
127        Ok(())
128    }
129}
130
131impl InPlaceStage for ACurves3Optimized<'_> {
132    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
133        let lut = Cube::new_checked_cube(self.clut, self.grid_size, 3)?;
134
135        // If PCS is LAB then linear interpolation should be used
136        if self.pcs == DataColorSpace::Lab {
137            return self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z));
138        }
139
140        match self.interpolation_method {
141            #[cfg(feature = "options")]
142            InterpolationMethod::Tetrahedral => {
143                self.transform_impl(dst, |x, y, z| lut.tetra_vec3(x, y, z))?;
144            }
145            #[cfg(feature = "options")]
146            InterpolationMethod::Pyramid => {
147                self.transform_impl(dst, |x, y, z| lut.pyramid_vec3(x, y, z))?;
148            }
149            #[cfg(feature = "options")]
150            InterpolationMethod::Prism => {
151                self.transform_impl(dst, |x, y, z| lut.prism_vec3(x, y, z))?;
152            }
153            InterpolationMethod::Linear => {
154                self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z))?;
155            }
156        }
157        Ok(())
158    }
159}
160
161#[allow(unused)]
162struct ACurves3Inverse<'a> {
163    curve0: Box<[f32; 65536]>,
164    curve1: Box<[f32; 65536]>,
165    curve2: Box<[f32; 65536]>,
166    clut: &'a [f32],
167    grid_size: [u8; 3],
168    interpolation_method: InterpolationMethod,
169    pcs: DataColorSpace,
170    depth: usize,
171}
172
173#[allow(unused)]
174impl ACurves3Inverse<'_> {
175    fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
176        &self,
177        dst: &mut [f32],
178        fetch: Fetch,
179    ) -> Result<(), CmsError> {
180        let scale_value = (self.depth as u32 - 1u32) as f32;
181
182        for dst in dst.chunks_exact_mut(3) {
183            let interpolated = fetch(dst[0], dst[1], dst[2]);
184            let a0 = (interpolated.v[0] * scale_value).round().min(scale_value) as u16;
185            let a1 = (interpolated.v[1] * scale_value).round().min(scale_value) as u16;
186            let a2 = (interpolated.v[2] * scale_value).round().min(scale_value) as u16;
187            let b0 = self.curve0[a0 as usize];
188            let b1 = self.curve1[a1 as usize];
189            let b2 = self.curve2[a2 as usize];
190            dst[0] = b0;
191            dst[1] = b1;
192            dst[2] = b2;
193        }
194        Ok(())
195    }
196}
197
198impl InPlaceStage for ACurves3Inverse<'_> {
199    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
200        let lut = Cube::new_checked_cube(self.clut, self.grid_size, 3)?;
201
202        // If PCS is LAB then linear interpolation should be used
203        if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
204            return self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z));
205        }
206
207        match self.interpolation_method {
208            #[cfg(feature = "options")]
209            InterpolationMethod::Tetrahedral => {
210                self.transform_impl(dst, |x, y, z| lut.tetra_vec3(x, y, z))?;
211            }
212            #[cfg(feature = "options")]
213            InterpolationMethod::Pyramid => {
214                self.transform_impl(dst, |x, y, z| lut.pyramid_vec3(x, y, z))?;
215            }
216            #[cfg(feature = "options")]
217            InterpolationMethod::Prism => {
218                self.transform_impl(dst, |x, y, z| lut.prism_vec3(x, y, z))?;
219            }
220            InterpolationMethod::Linear => {
221                self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z))?;
222            }
223        }
224        Ok(())
225    }
226}
227
228pub(crate) struct MCurves3 {
229    pub(crate) curve0: Box<[f32; 65536]>,
230    pub(crate) curve1: Box<[f32; 65536]>,
231    pub(crate) curve2: Box<[f32; 65536]>,
232    pub(crate) matrix: Matrix3f,
233    pub(crate) bias: Vector3f,
234    pub(crate) inverse: bool,
235    pub(crate) depth: usize,
236}
237
238impl MCurves3 {
239    fn execute_matrix_stage(&self, dst: &mut [f32]) {
240        let m = self.matrix;
241        let b = self.bias;
242
243        if !m.test_equality(Matrix3f::IDENTITY) || !b.eq(&Vector3f::default()) {
244            for dst in dst.chunks_exact_mut(3) {
245                let x = dst[0];
246                let y = dst[1];
247                let z = dst[2];
248                dst[0] = mlaf(mlaf(mlaf(b.v[0], x, m.v[0][0]), y, m.v[0][1]), z, m.v[0][2]);
249                dst[1] = mlaf(mlaf(mlaf(b.v[1], x, m.v[1][0]), y, m.v[1][1]), z, m.v[1][2]);
250                dst[2] = mlaf(mlaf(mlaf(b.v[2], x, m.v[2][0]), y, m.v[2][1]), z, m.v[2][2]);
251            }
252        }
253    }
254}
255
256impl InPlaceStage for MCurves3 {
257    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
258        let scale_value = (self.depth - 1) as f32;
259
260        if self.inverse {
261            self.execute_matrix_stage(dst);
262        }
263
264        for dst in dst.chunks_exact_mut(3) {
265            let a0 = (dst[0] * scale_value).round().min(scale_value) as u16;
266            let a1 = (dst[1] * scale_value).round().min(scale_value) as u16;
267            let a2 = (dst[2] * scale_value).round().min(scale_value) as u16;
268            let b0 = self.curve0[a0 as usize];
269            let b1 = self.curve1[a1 as usize];
270            let b2 = self.curve2[a2 as usize];
271            dst[0] = b0;
272            dst[1] = b1;
273            dst[2] = b2;
274        }
275
276        if !self.inverse {
277            self.execute_matrix_stage(dst);
278        }
279
280        Ok(())
281    }
282}
283
284pub(crate) struct BCurves3<const DEPTH: usize> {
285    pub(crate) curve0: Box<[f32; 65536]>,
286    pub(crate) curve1: Box<[f32; 65536]>,
287    pub(crate) curve2: Box<[f32; 65536]>,
288}
289
290impl<const DEPTH: usize> InPlaceStage for BCurves3<DEPTH> {
291    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
292        let scale_value = (DEPTH - 1) as f32;
293
294        for dst in dst.chunks_exact_mut(3) {
295            let a0 = (dst[0] * scale_value).round().min(scale_value) as u16;
296            let a1 = (dst[1] * scale_value).round().min(scale_value) as u16;
297            let a2 = (dst[2] * scale_value).round().min(scale_value) as u16;
298            let b0 = self.curve0[a0 as usize];
299            let b1 = self.curve1[a1 as usize];
300            let b2 = self.curve2[a2 as usize];
301            dst[0] = b0;
302            dst[1] = b1;
303            dst[2] = b2;
304        }
305
306        Ok(())
307    }
308}
309
310pub(crate) fn prepare_mab_3x3(
311    mab: &LutMultidimensionalType,
312    lut: &mut [f32],
313    options: TransformOptions,
314    pcs: DataColorSpace,
315) -> Result<(), CmsError> {
316    const LERP_DEPTH: usize = 65536;
317    const BP: usize = 13;
318    const DEPTH: usize = 8192;
319
320    if mab.num_input_channels != 3 || mab.num_output_channels != 3 {
321        return Err(CmsError::UnsupportedProfileConnection);
322    }
323    if mab.a_curves.len() == 3 && mab.clut.is_some() {
324        let clut = &mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
325        let lut_grid = (mab.grid_points[0] as usize)
326            .safe_mul(mab.grid_points[1] as usize)?
327            .safe_mul(mab.grid_points[2] as usize)?
328            .safe_mul(mab.num_output_channels as usize)?;
329        if clut.len() != lut_grid {
330            return Err(CmsError::MalformedCurveLutTable(MalformedSize {
331                size: clut.len(),
332                expected: lut_grid,
333            }));
334        }
335
336        let all_curves_linear = mab.a_curves.iter().all(|curve| curve.is_linear());
337        let grid_size = [mab.grid_points[0], mab.grid_points[1], mab.grid_points[2]];
338
339        if all_curves_linear {
340            let l = ACurves3Optimized {
341                clut,
342                grid_size,
343                interpolation_method: options.interpolation_method,
344                pcs,
345            };
346            l.transform(lut)?;
347        } else {
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 l = ACurves3 {
360                curve0,
361                curve1,
362                curve2,
363                clut,
364                grid_size,
365                interpolation_method: options.interpolation_method,
366                pcs,
367                depth: DEPTH,
368            };
369            l.transform(lut)?;
370        }
371    }
372
373    if mab.m_curves.len() == 3 {
374        let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
375        if !all_curves_linear
376            || !mab.matrix.test_equality(Matrix3d::IDENTITY)
377            || mab.bias.ne(&Vector3d::default())
378        {
379            let curves: Result<Vec<_>, _> = mab
380                .m_curves
381                .iter()
382                .map(|c| {
383                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
384                        .ok_or(CmsError::InvalidTrcCurve)
385                })
386                .collect();
387
388            let [curve0, curve1, curve2] =
389                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
390            let matrix = mab.matrix.to_f32();
391            let bias: Vector3f = mab.bias.cast();
392            let m_curves = MCurves3 {
393                curve0,
394                curve1,
395                curve2,
396                matrix,
397                bias,
398                inverse: false,
399                depth: DEPTH,
400            };
401            m_curves.transform(lut)?;
402        }
403    }
404
405    if mab.b_curves.len() == 3 {
406        const LERP_DEPTH: usize = 65536;
407        const BP: usize = 13;
408        const DEPTH: usize = 8192;
409        let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
410        if !all_curves_linear {
411            let curves: Result<Vec<_>, _> = mab
412                .b_curves
413                .iter()
414                .map(|c| {
415                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
416                        .ok_or(CmsError::InvalidTrcCurve)
417                })
418                .collect();
419
420            let [curve0, curve1, curve2] =
421                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
422
423            let b_curves = BCurves3::<DEPTH> {
424                curve0,
425                curve1,
426                curve2,
427            };
428            b_curves.transform(lut)?;
429        }
430    } else {
431        return Err(CmsError::InvalidAtoBLut);
432    }
433
434    Ok(())
435}
436
437pub(crate) fn prepare_mba_3x3(
438    mab: &LutMultidimensionalType,
439    lut: &mut [f32],
440    options: TransformOptions,
441    pcs: DataColorSpace,
442) -> Result<(), CmsError> {
443    if mab.num_input_channels != 3 || mab.num_output_channels != 3 {
444        return Err(CmsError::UnsupportedProfileConnection);
445    }
446    const LERP_DEPTH: usize = 65536;
447    const BP: usize = 13;
448    const DEPTH: usize = 8192;
449
450    if mab.b_curves.len() == 3 {
451        let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
452        if !all_curves_linear {
453            let curves: Result<Vec<_>, _> = mab
454                .b_curves
455                .iter()
456                .map(|c| {
457                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
458                        .ok_or(CmsError::InvalidTrcCurve)
459                })
460                .collect();
461
462            let [curve0, curve1, curve2] =
463                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
464            let b_curves = BCurves3::<DEPTH> {
465                curve0,
466                curve1,
467                curve2,
468            };
469            b_curves.transform(lut)?;
470        }
471    } else {
472        return Err(CmsError::InvalidAtoBLut);
473    }
474
475    if mab.m_curves.len() == 3 {
476        let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
477        if !all_curves_linear
478            || !mab.matrix.test_equality(Matrix3d::IDENTITY)
479            || mab.bias.ne(&Vector3d::default())
480        {
481            let curves: Result<Vec<_>, _> = mab
482                .m_curves
483                .iter()
484                .map(|c| {
485                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
486                        .ok_or(CmsError::InvalidTrcCurve)
487                })
488                .collect();
489
490            let [curve0, curve1, curve2] =
491                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
492
493            let matrix = mab.matrix.to_f32();
494            let bias: Vector3f = mab.bias.cast();
495            let m_curves = MCurves3 {
496                curve0,
497                curve1,
498                curve2,
499                matrix,
500                bias,
501                inverse: true,
502                depth: DEPTH,
503            };
504            m_curves.transform(lut)?;
505        }
506    }
507
508    if mab.a_curves.len() == 3 && mab.clut.is_some() {
509        let clut = &mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
510        let lut_grid = (mab.grid_points[0] as usize)
511            .safe_mul(mab.grid_points[1] as usize)?
512            .safe_mul(mab.grid_points[2] as usize)?
513            .safe_mul(mab.num_output_channels as usize)?;
514        if clut.len() != lut_grid {
515            return Err(CmsError::MalformedCurveLutTable(MalformedSize {
516                size: clut.len(),
517                expected: lut_grid,
518            }));
519        }
520
521        let all_curves_linear = mab.a_curves.iter().all(|curve| curve.is_linear());
522        let grid_size = [mab.grid_points[0], mab.grid_points[1], mab.grid_points[2]];
523
524        if all_curves_linear {
525            let l = ACurves3Optimized {
526                clut,
527                grid_size,
528                interpolation_method: options.interpolation_method,
529                pcs,
530            };
531            l.transform(lut)?;
532        } else {
533            let curves: Result<Vec<_>, _> = mab
534                .a_curves
535                .iter()
536                .map(|c| {
537                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
538                        .ok_or(CmsError::InvalidTrcCurve)
539                })
540                .collect();
541
542            let [curve0, curve1, curve2] =
543                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
544            let l = ACurves3Inverse {
545                curve0,
546                curve1,
547                curve2,
548                clut,
549                grid_size,
550                interpolation_method: options.interpolation_method,
551                pcs,
552                depth: DEPTH,
553            };
554            l.transform(lut)?;
555        }
556    }
557
558    Ok(())
559}