moxcms/conversions/katana/
md_3xn.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 6/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::katana::KatanaFinalStage;
30use crate::conversions::katana::md3x3::MultidimensionalDirection;
31use crate::conversions::katana::md4x3::{execute_matrix_stage3, execute_simple_curves3};
32use crate::conversions::md_lut::{MultidimensionalLut, tetra_3i_to_any_vec};
33use crate::safe_math::SafeMul;
34use crate::trc::lut_interp_linear_float;
35use crate::{
36    CmsError, DataColorSpace, Layout, LutMultidimensionalType, MalformedSize, Matrix3d, Matrix3f,
37    PointeeSizeExpressible, TransformOptions, Vector3d, Vector3f,
38};
39use num_traits::AsPrimitive;
40use std::marker::PhantomData;
41
42struct Multidimensional3xN<
43    T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
44> {
45    a_curves: Option<Vec<Vec<f32>>>,
46    m_curves: Option<Box<[Vec<f32>; 3]>>,
47    b_curves: Option<Box<[Vec<f32>; 3]>>,
48    clut: Option<Vec<f32>>,
49    matrix: Matrix3f,
50    bias: Vector3f,
51    direction: MultidimensionalDirection,
52    grid_size: [u8; 16],
53    output_inks: usize,
54    _phantom: PhantomData<T>,
55    dst_layout: Layout,
56    bit_depth: usize,
57}
58
59impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
60    Multidimensional3xN<T>
61where
62    f32: AsPrimitive<T>,
63{
64    fn to_output_impl(&self, src: &mut [f32], dst: &mut [T]) -> Result<(), CmsError> {
65        let norm_value = if T::FINITE {
66            ((1u32 << self.bit_depth) - 1) as f32
67        } else {
68            1.0
69        };
70        assert_eq!(
71            self.direction,
72            MultidimensionalDirection::PcsToDevice,
73            "PCS to device cannot be used on `to pcs` stage"
74        );
75
76        // B-curves is mandatory
77        if let Some(b_curves) = &self.b_curves.as_ref() {
78            execute_simple_curves3(src, b_curves);
79        }
80
81        // Matrix stage
82
83        if let Some(m_curves) = self.m_curves.as_ref() {
84            execute_matrix_stage3(self.matrix, self.bias, src);
85            execute_simple_curves3(src, m_curves);
86        }
87
88        if let (Some(a_curves), Some(clut)) = (self.a_curves.as_ref(), self.clut.as_ref()) {
89            let mut inks = vec![0.; self.output_inks];
90
91            if clut.is_empty() {
92                return Err(CmsError::InvalidAtoBLut);
93            }
94
95            let md_lut = MultidimensionalLut::new(self.grid_size, 3, self.output_inks);
96
97            for (src, dst) in src
98                .chunks_exact(3)
99                .zip(dst.chunks_exact_mut(self.dst_layout.channels()))
100            {
101                tetra_3i_to_any_vec(
102                    &md_lut,
103                    clut,
104                    src[0],
105                    src[1],
106                    src[2],
107                    &mut inks,
108                    self.output_inks,
109                );
110
111                for (ink, curve) in inks.iter_mut().zip(a_curves.iter()) {
112                    *ink = lut_interp_linear_float(*ink, curve);
113                }
114
115                if T::FINITE {
116                    for (dst, ink) in dst.iter_mut().zip(inks.iter()) {
117                        *dst = (*ink * norm_value).round().max(0.).min(norm_value).as_();
118                    }
119                } else {
120                    for (dst, ink) in dst.iter_mut().zip(inks.iter()) {
121                        *dst = (*ink * norm_value).as_();
122                    }
123                }
124            }
125        } else {
126            return Err(CmsError::InvalidAtoBLut);
127        }
128
129        Ok(())
130    }
131}
132
133impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
134    KatanaFinalStage<f32, T> for Multidimensional3xN<T>
135where
136    f32: AsPrimitive<T>,
137{
138    fn to_output(&self, src: &mut [f32], dst: &mut [T]) -> Result<(), CmsError> {
139        if src.len() % 3 != 0 {
140            return Err(CmsError::LaneMultipleOfChannels);
141        }
142        if dst.len() % self.output_inks != 0 {
143            return Err(CmsError::LaneMultipleOfChannels);
144        }
145
146        self.to_output_impl(src, dst)?;
147        Ok(())
148    }
149}
150
151fn make_multidimensional_nx3<
152    T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
153>(
154    dst_layout: Layout,
155    mab: &LutMultidimensionalType,
156    _: TransformOptions,
157    pcs: DataColorSpace,
158    direction: MultidimensionalDirection,
159    bit_depth: usize,
160) -> Result<Multidimensional3xN<T>, CmsError> {
161    let real_inks = if pcs == DataColorSpace::Rgb {
162        3
163    } else {
164        dst_layout.channels()
165    };
166
167    if mab.num_output_channels != real_inks as u8 {
168        return Err(CmsError::UnsupportedProfileConnection);
169    }
170
171    if mab.b_curves.is_empty() || mab.b_curves.len() != 3 {
172        return Err(CmsError::InvalidAtoBLut);
173    }
174
175    let clut: Option<Vec<f32>> =
176        if mab.a_curves.len() == mab.num_output_channels as usize && mab.clut.is_some() {
177            let clut = mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
178            let mut lut_grid = 1usize;
179            for grid in mab.grid_points.iter().take(mab.num_input_channels as usize) {
180                lut_grid = lut_grid.safe_mul(*grid as usize)?;
181            }
182            let lut_grid = lut_grid.safe_mul(mab.num_output_channels as usize)?;
183            if clut.len() != lut_grid {
184                return Err(CmsError::MalformedCurveLutTable(MalformedSize {
185                    size: clut.len(),
186                    expected: lut_grid,
187                }));
188            }
189            Some(clut)
190        } else {
191            return Err(CmsError::InvalidAtoBLut);
192        };
193
194    let a_curves: Option<Vec<Vec<f32>>> =
195        if mab.a_curves.len() == mab.num_output_channels as usize && mab.clut.is_some() {
196            let mut arr = Vec::new();
197            for a_curve in mab.a_curves.iter() {
198                arr.push(a_curve.to_clut()?);
199            }
200            Some(arr)
201        } else {
202            None
203        };
204
205    let b_curves: Option<Box<[Vec<f32>; 3]>> = if mab.b_curves.len() == 3 {
206        let mut arr = Box::<[Vec<f32>; 3]>::default();
207        let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
208        if all_curves_linear {
209            None
210        } else {
211            for (c_curve, dst) in mab.b_curves.iter().zip(arr.iter_mut()) {
212                *dst = c_curve.to_clut()?;
213            }
214            Some(arr)
215        }
216    } else {
217        return Err(CmsError::InvalidAtoBLut);
218    };
219
220    let matrix = mab.matrix.to_f32();
221
222    let m_curves: Option<Box<[Vec<f32>; 3]>> = if mab.m_curves.len() == 3 {
223        let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
224        if !all_curves_linear
225            || !mab.matrix.test_equality(Matrix3d::IDENTITY)
226            || mab.bias.ne(&Vector3d::default())
227        {
228            let mut arr = Box::<[Vec<f32>; 3]>::default();
229            for (curve, dst) in mab.m_curves.iter().zip(arr.iter_mut()) {
230                *dst = curve.to_clut()?;
231            }
232            Some(arr)
233        } else {
234            None
235        }
236    } else {
237        None
238    };
239
240    let bias = mab.bias.cast();
241
242    let transform = Multidimensional3xN::<T> {
243        a_curves,
244        b_curves,
245        m_curves,
246        matrix,
247        direction,
248        clut,
249        grid_size: mab.grid_points,
250        bias,
251        dst_layout,
252        output_inks: real_inks,
253        _phantom: PhantomData,
254        bit_depth,
255    };
256
257    Ok(transform)
258}
259
260pub(crate) fn katana_multi_dimensional_3xn_to_device<
261    T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
262>(
263    dst_layout: Layout,
264    mab: &LutMultidimensionalType,
265    options: TransformOptions,
266    pcs: DataColorSpace,
267    bit_depth: usize,
268) -> Result<Box<dyn KatanaFinalStage<f32, T> + Send + Sync>, CmsError>
269where
270    f32: AsPrimitive<T>,
271{
272    if mab.num_input_channels == 0 {
273        return Err(CmsError::UnsupportedProfileConnection);
274    }
275    let transform = make_multidimensional_nx3::<T>(
276        dst_layout,
277        mab,
278        options,
279        pcs,
280        MultidimensionalDirection::PcsToDevice,
281        bit_depth,
282    )?;
283    Ok(Box::new(transform))
284}