1use 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 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 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 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}