Coverage Report

Created: 2025-12-05 07:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.7.10/src/conversions/mab.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::mlaf::mlaf;
30
use crate::safe_math::SafeMul;
31
use crate::{
32
    CmsError, Cube, DataColorSpace, InPlaceStage, InterpolationMethod, LutMultidimensionalType,
33
    MalformedSize, Matrix3d, Matrix3f, TransformOptions, Vector3d, Vector3f,
34
};
35
36
#[allow(unused)]
37
struct 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)]
49
struct ACurves3Optimized<'a> {
50
    clut: &'a [f32],
51
    grid_size: [u8; 3],
52
    interpolation_method: InterpolationMethod,
53
    pcs: DataColorSpace,
54
}
55
56
#[allow(unused)]
57
impl ACurves3<'_> {
58
0
    fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
59
0
        &self,
60
0
        dst: &mut [f32],
61
0
        fetch: Fetch,
62
0
    ) -> Result<(), CmsError> {
63
0
        let scale_value = (self.depth - 1) as f32;
64
65
0
        for dst in dst.chunks_exact_mut(3) {
66
0
            let a0 = (dst[0] * scale_value).round().min(scale_value) as u16;
67
0
            let a1 = (dst[1] * scale_value).round().min(scale_value) as u16;
68
0
            let a2 = (dst[2] * scale_value).round().min(scale_value) as u16;
69
0
            let b0 = self.curve0[a0 as usize];
70
0
            let b1 = self.curve1[a1 as usize];
71
0
            let b2 = self.curve2[a2 as usize];
72
0
            let interpolated = fetch(b0, b1, b2);
73
0
            dst[0] = interpolated.v[0];
74
0
            dst[1] = interpolated.v[1];
75
0
            dst[2] = interpolated.v[2];
76
0
        }
77
0
        Ok(())
78
0
    }
Unexecuted instantiation: <moxcms::conversions::mab::ACurves3>::transform_impl::<<moxcms::conversions::mab::ACurves3 as moxcms::transform::InPlaceStage>::transform::{closure#0}>
Unexecuted instantiation: <moxcms::conversions::mab::ACurves3>::transform_impl::<<moxcms::conversions::mab::ACurves3 as moxcms::transform::InPlaceStage>::transform::{closure#1}>
79
}
80
81
#[allow(unused)]
82
impl ACurves3Optimized<'_> {
83
0
    fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
84
0
        &self,
85
0
        dst: &mut [f32],
86
0
        fetch: Fetch,
87
0
    ) -> Result<(), CmsError> {
88
0
        for dst in dst.chunks_exact_mut(3) {
89
0
            let a0 = dst[0];
90
0
            let a1 = dst[1];
91
0
            let a2 = dst[2];
92
0
            let interpolated = fetch(a0, a1, a2);
93
0
            dst[0] = interpolated.v[0];
94
0
            dst[1] = interpolated.v[1];
95
0
            dst[2] = interpolated.v[2];
96
0
        }
97
0
        Ok(())
98
0
    }
Unexecuted instantiation: <moxcms::conversions::mab::ACurves3Optimized>::transform_impl::<<moxcms::conversions::mab::ACurves3Optimized as moxcms::transform::InPlaceStage>::transform::{closure#0}>
Unexecuted instantiation: <moxcms::conversions::mab::ACurves3Optimized>::transform_impl::<<moxcms::conversions::mab::ACurves3Optimized as moxcms::transform::InPlaceStage>::transform::{closure#1}>
99
}
100
101
impl InPlaceStage for ACurves3<'_> {
102
0
    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
103
0
        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
0
        if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
107
0
            return self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z));
108
0
        }
109
110
0
        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
0
                self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z))?;
125
            }
126
        }
127
0
        Ok(())
128
0
    }
129
}
130
131
impl InPlaceStage for ACurves3Optimized<'_> {
132
0
    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
133
0
        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
0
        if self.pcs == DataColorSpace::Lab {
137
0
            return self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z));
138
0
        }
139
140
0
        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
0
                self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z))?;
155
            }
156
        }
157
0
        Ok(())
158
0
    }
159
}
160
161
#[allow(unused)]
162
struct 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)]
174
impl ACurves3Inverse<'_> {
175
0
    fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
176
0
        &self,
177
0
        dst: &mut [f32],
178
0
        fetch: Fetch,
179
0
    ) -> Result<(), CmsError> {
180
0
        let scale_value = (self.depth as u32 - 1u32) as f32;
181
182
0
        for dst in dst.chunks_exact_mut(3) {
183
0
            let interpolated = fetch(dst[0], dst[1], dst[2]);
184
0
            let a0 = (interpolated.v[0] * scale_value).round().min(scale_value) as u16;
185
0
            let a1 = (interpolated.v[1] * scale_value).round().min(scale_value) as u16;
186
0
            let a2 = (interpolated.v[2] * scale_value).round().min(scale_value) as u16;
187
0
            let b0 = self.curve0[a0 as usize];
188
0
            let b1 = self.curve1[a1 as usize];
189
0
            let b2 = self.curve2[a2 as usize];
190
0
            dst[0] = b0;
191
0
            dst[1] = b1;
192
0
            dst[2] = b2;
193
0
        }
194
0
        Ok(())
195
0
    }
Unexecuted instantiation: <moxcms::conversions::mab::ACurves3Inverse>::transform_impl::<<moxcms::conversions::mab::ACurves3Inverse as moxcms::transform::InPlaceStage>::transform::{closure#0}>
Unexecuted instantiation: <moxcms::conversions::mab::ACurves3Inverse>::transform_impl::<<moxcms::conversions::mab::ACurves3Inverse as moxcms::transform::InPlaceStage>::transform::{closure#1}>
196
}
197
198
impl InPlaceStage for ACurves3Inverse<'_> {
199
0
    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
200
0
        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
0
        if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
204
0
            return self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z));
205
0
        }
206
207
0
        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
0
                self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z))?;
222
            }
223
        }
224
0
        Ok(())
225
0
    }
226
}
227
228
pub(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
238
impl MCurves3 {
239
0
    fn execute_matrix_stage(&self, dst: &mut [f32]) {
240
0
        let m = self.matrix;
241
0
        let b = self.bias;
242
243
0
        if !m.test_equality(Matrix3f::IDENTITY) || !b.eq(&Vector3f::default()) {
244
0
            for dst in dst.chunks_exact_mut(3) {
245
0
                let x = dst[0];
246
0
                let y = dst[1];
247
0
                let z = dst[2];
248
0
                dst[0] = mlaf(mlaf(mlaf(b.v[0], x, m.v[0][0]), y, m.v[0][1]), z, m.v[0][2]);
249
0
                dst[1] = mlaf(mlaf(mlaf(b.v[1], x, m.v[1][0]), y, m.v[1][1]), z, m.v[1][2]);
250
0
                dst[2] = mlaf(mlaf(mlaf(b.v[2], x, m.v[2][0]), y, m.v[2][1]), z, m.v[2][2]);
251
0
            }
252
0
        }
253
0
    }
254
}
255
256
impl InPlaceStage for MCurves3 {
257
0
    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
258
0
        let scale_value = (self.depth - 1) as f32;
259
260
0
        if self.inverse {
261
0
            self.execute_matrix_stage(dst);
262
0
        }
263
264
0
        for dst in dst.chunks_exact_mut(3) {
265
0
            let a0 = (dst[0] * scale_value).round().min(scale_value) as u16;
266
0
            let a1 = (dst[1] * scale_value).round().min(scale_value) as u16;
267
0
            let a2 = (dst[2] * scale_value).round().min(scale_value) as u16;
268
0
            let b0 = self.curve0[a0 as usize];
269
0
            let b1 = self.curve1[a1 as usize];
270
0
            let b2 = self.curve2[a2 as usize];
271
0
            dst[0] = b0;
272
0
            dst[1] = b1;
273
0
            dst[2] = b2;
274
0
        }
275
276
0
        if !self.inverse {
277
0
            self.execute_matrix_stage(dst);
278
0
        }
279
280
0
        Ok(())
281
0
    }
282
}
283
284
pub(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
290
impl<const DEPTH: usize> InPlaceStage for BCurves3<DEPTH> {
291
0
    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
292
0
        let scale_value = (DEPTH - 1) as f32;
293
294
0
        for dst in dst.chunks_exact_mut(3) {
295
0
            let a0 = (dst[0] * scale_value).round().min(scale_value) as u16;
296
0
            let a1 = (dst[1] * scale_value).round().min(scale_value) as u16;
297
0
            let a2 = (dst[2] * scale_value).round().min(scale_value) as u16;
298
0
            let b0 = self.curve0[a0 as usize];
299
0
            let b1 = self.curve1[a1 as usize];
300
0
            let b2 = self.curve2[a2 as usize];
301
0
            dst[0] = b0;
302
0
            dst[1] = b1;
303
0
            dst[2] = b2;
304
0
        }
305
306
0
        Ok(())
307
0
    }
308
}
309
310
0
pub(crate) fn prepare_mab_3x3(
311
0
    mab: &LutMultidimensionalType,
312
0
    lut: &mut [f32],
313
0
    options: TransformOptions,
314
0
    pcs: DataColorSpace,
315
0
) -> Result<(), CmsError> {
316
    const LERP_DEPTH: usize = 65536;
317
    const BP: usize = 13;
318
    const DEPTH: usize = 8192;
319
320
0
    if mab.num_input_channels != 3 || mab.num_output_channels != 3 {
321
0
        return Err(CmsError::UnsupportedProfileConnection);
322
0
    }
323
0
    if mab.a_curves.len() == 3 && mab.clut.is_some() {
324
0
        let clut = &mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
325
0
        let lut_grid = (mab.grid_points[0] as usize)
326
0
            .safe_mul(mab.grid_points[1] as usize)?
327
0
            .safe_mul(mab.grid_points[2] as usize)?
328
0
            .safe_mul(mab.num_output_channels as usize)?;
329
0
        if clut.len() != lut_grid {
330
0
            return Err(CmsError::MalformedCurveLutTable(MalformedSize {
331
0
                size: clut.len(),
332
0
                expected: lut_grid,
333
0
            }));
334
0
        }
335
336
0
        let all_curves_linear = mab.a_curves.iter().all(|curve| curve.is_linear());
337
0
        let grid_size = [mab.grid_points[0], mab.grid_points[1], mab.grid_points[2]];
338
339
0
        if all_curves_linear {
340
0
            let l = ACurves3Optimized {
341
0
                clut,
342
0
                grid_size,
343
0
                interpolation_method: options.interpolation_method,
344
0
                pcs,
345
0
            };
346
0
            l.transform(lut)?;
347
        } else {
348
0
            let curves: Result<Vec<_>, _> = mab
349
0
                .a_curves
350
0
                .iter()
351
0
                .map(|c| {
352
0
                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
353
0
                        .ok_or(CmsError::InvalidTrcCurve)
354
0
                })
355
0
                .collect();
356
357
0
            let [curve0, curve1, curve2] =
358
0
                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
359
0
            let l = ACurves3 {
360
0
                curve0,
361
0
                curve1,
362
0
                curve2,
363
0
                clut,
364
0
                grid_size,
365
0
                interpolation_method: options.interpolation_method,
366
0
                pcs,
367
0
                depth: DEPTH,
368
0
            };
369
0
            l.transform(lut)?;
370
        }
371
0
    }
372
373
0
    if mab.m_curves.len() == 3 {
374
0
        let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
375
0
        if !all_curves_linear
376
0
            || !mab.matrix.test_equality(Matrix3d::IDENTITY)
377
0
            || mab.bias.ne(&Vector3d::default())
378
        {
379
0
            let curves: Result<Vec<_>, _> = mab
380
0
                .m_curves
381
0
                .iter()
382
0
                .map(|c| {
383
0
                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
384
0
                        .ok_or(CmsError::InvalidTrcCurve)
385
0
                })
386
0
                .collect();
387
388
0
            let [curve0, curve1, curve2] =
389
0
                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
390
0
            let matrix = mab.matrix.to_f32();
391
0
            let bias: Vector3f = mab.bias.cast();
392
0
            let m_curves = MCurves3 {
393
0
                curve0,
394
0
                curve1,
395
0
                curve2,
396
0
                matrix,
397
0
                bias,
398
0
                inverse: false,
399
0
                depth: DEPTH,
400
0
            };
401
0
            m_curves.transform(lut)?;
402
0
        }
403
0
    }
404
405
0
    if mab.b_curves.len() == 3 {
406
        const LERP_DEPTH: usize = 65536;
407
        const BP: usize = 13;
408
        const DEPTH: usize = 8192;
409
0
        let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
410
0
        if !all_curves_linear {
411
0
            let curves: Result<Vec<_>, _> = mab
412
0
                .b_curves
413
0
                .iter()
414
0
                .map(|c| {
415
0
                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
416
0
                        .ok_or(CmsError::InvalidTrcCurve)
417
0
                })
418
0
                .collect();
419
420
0
            let [curve0, curve1, curve2] =
421
0
                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
422
423
0
            let b_curves = BCurves3::<DEPTH> {
424
0
                curve0,
425
0
                curve1,
426
0
                curve2,
427
0
            };
428
0
            b_curves.transform(lut)?;
429
0
        }
430
    } else {
431
0
        return Err(CmsError::InvalidAtoBLut);
432
    }
433
434
0
    Ok(())
435
0
}
436
437
0
pub(crate) fn prepare_mba_3x3(
438
0
    mab: &LutMultidimensionalType,
439
0
    lut: &mut [f32],
440
0
    options: TransformOptions,
441
0
    pcs: DataColorSpace,
442
0
) -> Result<(), CmsError> {
443
0
    if mab.num_input_channels != 3 || mab.num_output_channels != 3 {
444
0
        return Err(CmsError::UnsupportedProfileConnection);
445
0
    }
446
    const LERP_DEPTH: usize = 65536;
447
    const BP: usize = 13;
448
    const DEPTH: usize = 8192;
449
450
0
    if mab.b_curves.len() == 3 {
451
0
        let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
452
0
        if !all_curves_linear {
453
0
            let curves: Result<Vec<_>, _> = mab
454
0
                .b_curves
455
0
                .iter()
456
0
                .map(|c| {
457
0
                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
458
0
                        .ok_or(CmsError::InvalidTrcCurve)
459
0
                })
460
0
                .collect();
461
462
0
            let [curve0, curve1, curve2] =
463
0
                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
464
0
            let b_curves = BCurves3::<DEPTH> {
465
0
                curve0,
466
0
                curve1,
467
0
                curve2,
468
0
            };
469
0
            b_curves.transform(lut)?;
470
0
        }
471
    } else {
472
0
        return Err(CmsError::InvalidAtoBLut);
473
    }
474
475
0
    if mab.m_curves.len() == 3 {
476
0
        let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
477
0
        if !all_curves_linear
478
0
            || !mab.matrix.test_equality(Matrix3d::IDENTITY)
479
0
            || mab.bias.ne(&Vector3d::default())
480
        {
481
0
            let curves: Result<Vec<_>, _> = mab
482
0
                .m_curves
483
0
                .iter()
484
0
                .map(|c| {
485
0
                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
486
0
                        .ok_or(CmsError::InvalidTrcCurve)
487
0
                })
488
0
                .collect();
489
490
0
            let [curve0, curve1, curve2] =
491
0
                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
492
493
0
            let matrix = mab.matrix.to_f32();
494
0
            let bias: Vector3f = mab.bias.cast();
495
0
            let m_curves = MCurves3 {
496
0
                curve0,
497
0
                curve1,
498
0
                curve2,
499
0
                matrix,
500
0
                bias,
501
0
                inverse: true,
502
0
                depth: DEPTH,
503
0
            };
504
0
            m_curves.transform(lut)?;
505
0
        }
506
0
    }
507
508
0
    if mab.a_curves.len() == 3 && mab.clut.is_some() {
509
0
        let clut = &mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
510
0
        let lut_grid = (mab.grid_points[0] as usize)
511
0
            .safe_mul(mab.grid_points[1] as usize)?
512
0
            .safe_mul(mab.grid_points[2] as usize)?
513
0
            .safe_mul(mab.num_output_channels as usize)?;
514
0
        if clut.len() != lut_grid {
515
0
            return Err(CmsError::MalformedCurveLutTable(MalformedSize {
516
0
                size: clut.len(),
517
0
                expected: lut_grid,
518
0
            }));
519
0
        }
520
521
0
        let all_curves_linear = mab.a_curves.iter().all(|curve| curve.is_linear());
522
0
        let grid_size = [mab.grid_points[0], mab.grid_points[1], mab.grid_points[2]];
523
524
0
        if all_curves_linear {
525
0
            let l = ACurves3Optimized {
526
0
                clut,
527
0
                grid_size,
528
0
                interpolation_method: options.interpolation_method,
529
0
                pcs,
530
0
            };
531
0
            l.transform(lut)?;
532
        } else {
533
0
            let curves: Result<Vec<_>, _> = mab
534
0
                .a_curves
535
0
                .iter()
536
0
                .map(|c| {
537
0
                    c.build_linearize_table::<u16, LERP_DEPTH, BP>()
538
0
                        .ok_or(CmsError::InvalidTrcCurve)
539
0
                })
540
0
                .collect();
541
542
0
            let [curve0, curve1, curve2] =
543
0
                curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
544
0
            let l = ACurves3Inverse {
545
0
                curve0,
546
0
                curve1,
547
0
                curve2,
548
0
                clut,
549
0
                grid_size,
550
0
                interpolation_method: options.interpolation_method,
551
0
                pcs,
552
0
                depth: DEPTH,
553
0
            };
554
0
            l.transform(lut)?;
555
        }
556
0
    }
557
558
0
    Ok(())
559
0
}