Coverage Report

Created: 2025-10-12 08:06

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