1use crate::{CmsError, Layout, Matrix3, Matrix3f, TransformExecutor};
30use num_traits::AsPrimitive;
31
32pub(crate) struct TransformMatrixShaper<T: Clone, const BUCKET: usize> {
33 pub(crate) r_linear: Box<[f32; BUCKET]>,
34 pub(crate) g_linear: Box<[f32; BUCKET]>,
35 pub(crate) b_linear: Box<[f32; BUCKET]>,
36 pub(crate) r_gamma: Box<[T; 65536]>,
37 pub(crate) g_gamma: Box<[T; 65536]>,
38 pub(crate) b_gamma: Box<[T; 65536]>,
39 pub(crate) adaptation_matrix: Matrix3f,
40}
41
42pub(crate) struct TransformMatrixShaperOptimized<T: Clone, const BUCKET: usize> {
45 pub(crate) linear: Box<[f32; BUCKET]>,
46 pub(crate) gamma: Box<[T; 65536]>,
47 pub(crate) adaptation_matrix: Matrix3f,
48}
49
50impl<T: Clone + PointeeSizeExpressible, const BUCKET: usize> TransformMatrixShaper<T, BUCKET> {
51 pub(crate) fn to_q2_13_n<
52 R: Copy + 'static + Default,
53 const PRECISION: i32,
54 const LINEAR_CAP: usize,
55 const GAMMA_LUT: usize,
56 const BIT_DEPTH: usize,
57 >(
58 &self,
59 ) -> TransformMatrixShaperFixedPoint<R, T, BUCKET>
60 where
61 f32: AsPrimitive<R>,
62 {
63 let linear_scale = if T::FINITE {
64 let lut_scale = (GAMMA_LUT - 1) as f32 / ((1 << BIT_DEPTH) - 1) as f32;
65 ((1 << BIT_DEPTH) - 1) as f32 * lut_scale
66 } else {
67 let lut_scale = (GAMMA_LUT - 1) as f32 / (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32;
68 (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32 * lut_scale
69 };
70 let mut new_box_r = Box::new([R::default(); BUCKET]);
71 let mut new_box_g = Box::new([R::default(); BUCKET]);
72 let mut new_box_b = Box::new([R::default(); BUCKET]);
73 for (dst, &src) in new_box_r.iter_mut().zip(self.r_linear.iter()) {
74 *dst = (src * linear_scale).round().as_();
75 }
76 for (dst, &src) in new_box_g.iter_mut().zip(self.g_linear.iter()) {
77 *dst = (src * linear_scale).round().as_();
78 }
79 for (dst, &src) in new_box_b.iter_mut().zip(self.b_linear.iter()) {
80 *dst = (src * linear_scale).round().as_();
81 }
82 let scale: f32 = (1i32 << PRECISION) as f32;
83 let source_matrix = self.adaptation_matrix;
84 let mut dst_matrix = Matrix3::<i16> { v: [[0i16; 3]; 3] };
85 for i in 0..3 {
86 for j in 0..3 {
87 dst_matrix.v[i][j] = (source_matrix.v[i][j] * scale) as i16;
88 }
89 }
90 TransformMatrixShaperFixedPoint {
91 r_linear: new_box_r,
92 g_linear: new_box_g,
93 b_linear: new_box_b,
94 r_gamma: self.r_gamma.clone(),
95 g_gamma: self.g_gamma.clone(),
96 b_gamma: self.b_gamma.clone(),
97 adaptation_matrix: dst_matrix,
98 }
99 }
100}
101
102impl<T: Clone + PointeeSizeExpressible, const BUCKET: usize>
103 TransformMatrixShaperOptimized<T, BUCKET>
104{
105 pub(crate) fn to_q2_13_n<
106 R: Copy + 'static + Default,
107 const PRECISION: i32,
108 const LINEAR_CAP: usize,
109 const GAMMA_LUT: usize,
110 const BIT_DEPTH: usize,
111 >(
112 &self,
113 ) -> TransformMatrixShaperFixedPointOpt<R, i16, T, BUCKET>
114 where
115 f32: AsPrimitive<R>,
116 {
117 let linear_scale = if T::FINITE {
118 let lut_scale = (GAMMA_LUT - 1) as f32 / ((1 << BIT_DEPTH) - 1) as f32;
119 ((1 << BIT_DEPTH) - 1) as f32 * lut_scale
120 } else {
121 let lut_scale = (GAMMA_LUT - 1) as f32 / (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32;
122 (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32 * lut_scale
123 };
124 let mut new_box_linear = Box::new([R::default(); BUCKET]);
125 for (dst, src) in new_box_linear.iter_mut().zip(self.linear.iter()) {
126 *dst = (*src * linear_scale).round().as_();
127 }
128 let scale: f32 = (1i32 << PRECISION) as f32;
129 let source_matrix = self.adaptation_matrix;
130 let mut dst_matrix = Matrix3::<i16> {
131 v: [[i16::default(); 3]; 3],
132 };
133 for i in 0..3 {
134 for j in 0..3 {
135 dst_matrix.v[i][j] = (source_matrix.v[i][j] * scale) as i16;
136 }
137 }
138 TransformMatrixShaperFixedPointOpt {
139 linear: new_box_linear,
140 gamma: self.gamma.clone(),
141 adaptation_matrix: dst_matrix,
142 }
143 }
144
145 #[allow(dead_code)]
146 pub(crate) fn to_q1_30_n<
147 R: Copy + 'static + Default,
148 const PRECISION: i32,
149 const LINEAR_CAP: usize,
150 const GAMMA_LUT: usize,
151 const BIT_DEPTH: usize,
152 >(
153 &self,
154 ) -> TransformMatrixShaperFixedPointOpt<R, i32, T, BUCKET>
155 where
156 f32: AsPrimitive<R>,
157 f64: AsPrimitive<R>,
158 {
159 let table_size = if T::FINITE {
161 (1 << BIT_DEPTH) - 1
162 } else {
163 T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
164 };
165 let ext_bp = if T::FINITE {
166 BIT_DEPTH as u32 + 1
167 } else {
168 let bp = (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1).count_ones();
169 bp + 1
170 };
171 let linear_scale = {
172 let lut_scale = (GAMMA_LUT - 1) as f64 / table_size as f64;
173 ((1u32 << ext_bp) - 1) as f64 * lut_scale
174 };
175 let mut new_box_linear = Box::new([R::default(); BUCKET]);
176 for (dst, &src) in new_box_linear.iter_mut().zip(self.linear.iter()) {
177 *dst = (src as f64 * linear_scale).round().as_();
178 }
179 let scale: f64 = (1i64 << PRECISION) as f64;
180 let source_matrix = self.adaptation_matrix;
181 let mut dst_matrix = Matrix3::<i32> {
182 v: [[i32::default(); 3]; 3],
183 };
184 for i in 0..3 {
185 for j in 0..3 {
186 dst_matrix.v[i][j] = (source_matrix.v[i][j] as f64 * scale) as i32;
187 }
188 }
189 TransformMatrixShaperFixedPointOpt {
190 linear: new_box_linear,
191 gamma: self.gamma.clone(),
192 adaptation_matrix: dst_matrix,
193 }
194 }
195}
196
197#[allow(unused)]
198struct TransformMatrixShaperScalar<
199 T: Clone,
200 const SRC_LAYOUT: u8,
201 const DST_LAYOUT: u8,
202 const LINEAR_CAP: usize,
203 const GAMMA_LUT: usize,
204 const BIT_DEPTH: usize,
205> {
206 pub(crate) profile: TransformMatrixShaper<T, LINEAR_CAP>,
207}
208
209#[allow(unused)]
210struct TransformMatrixShaperOptScalar<
211 T: Clone,
212 const SRC_LAYOUT: u8,
213 const DST_LAYOUT: u8,
214 const LINEAR_CAP: usize,
215 const GAMMA_LUT: usize,
216 const BIT_DEPTH: usize,
217> {
218 pub(crate) profile: TransformMatrixShaperOptimized<T, LINEAR_CAP>,
219}
220
221#[cfg(any(
222 any(target_arch = "x86", target_arch = "x86_64"),
223 all(target_arch = "aarch64", target_feature = "neon")
224))]
225#[allow(unused)]
226macro_rules! create_rgb_xyz_dependant_executor {
227 ($dep_name: ident, $dependant: ident, $shaper: ident) => {
228 pub(crate) fn $dep_name<
229 T: Clone + Send + Sync + Default + PointeeSizeExpressible + Copy + 'static,
230 const LINEAR_CAP: usize,
231 const GAMMA_LUT: usize,
232 const BIT_DEPTH: usize,
233 >(
234 src_layout: Layout,
235 dst_layout: Layout,
236 profile: $shaper<T, LINEAR_CAP>,
237 ) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
238 where
239 u32: AsPrimitive<T>,
240 {
241 if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
242 return Ok(Box::new($dependant::<
243 T,
244 { Layout::Rgba as u8 },
245 { Layout::Rgba as u8 },
246 LINEAR_CAP,
247 GAMMA_LUT,
248 > {
249 profile,
250 bit_depth: BIT_DEPTH,
251 }));
252 } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
253 return Ok(Box::new($dependant::<
254 T,
255 { Layout::Rgb as u8 },
256 { Layout::Rgba as u8 },
257 LINEAR_CAP,
258 GAMMA_LUT,
259 > {
260 profile,
261 bit_depth: BIT_DEPTH,
262 }));
263 } else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
264 return Ok(Box::new($dependant::<
265 T,
266 { Layout::Rgba as u8 },
267 { Layout::Rgb as u8 },
268 LINEAR_CAP,
269 GAMMA_LUT,
270 > {
271 profile,
272 bit_depth: BIT_DEPTH,
273 }));
274 } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
275 return Ok(Box::new($dependant::<
276 T,
277 { Layout::Rgb as u8 },
278 { Layout::Rgb as u8 },
279 LINEAR_CAP,
280 GAMMA_LUT,
281 > {
282 profile,
283 bit_depth: BIT_DEPTH,
284 }));
285 }
286 Err(CmsError::UnsupportedProfileConnection)
287 }
288 };
289}
290
291#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
292use crate::conversions::sse::{TransformShaperRgbOptSse, TransformShaperRgbSse};
293
294#[cfg(all(target_arch = "x86_64", feature = "avx"))]
295use crate::conversions::avx::{TransformShaperRgbAvx, TransformShaperRgbOptAvx};
296
297#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
298create_rgb_xyz_dependant_executor!(
299 make_rgb_xyz_rgb_transform_sse_41,
300 TransformShaperRgbSse,
301 TransformMatrixShaper
302);
303
304#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
305create_rgb_xyz_dependant_executor!(
306 make_rgb_xyz_rgb_transform_sse_41_opt,
307 TransformShaperRgbOptSse,
308 TransformMatrixShaperOptimized
309);
310
311#[cfg(all(target_arch = "x86_64", feature = "avx"))]
312create_rgb_xyz_dependant_executor!(
313 make_rgb_xyz_rgb_transform_avx2,
314 TransformShaperRgbAvx,
315 TransformMatrixShaper
316);
317
318#[cfg(all(target_arch = "x86_64", feature = "avx"))]
319create_rgb_xyz_dependant_executor!(
320 make_rgb_xyz_rgb_transform_avx2_opt,
321 TransformShaperRgbOptAvx,
322 TransformMatrixShaperOptimized
323);
324
325#[cfg(all(target_arch = "x86_64", feature = "avx512"))]
326use crate::conversions::avx512::TransformShaperRgbOptAvx512;
327
328#[cfg(all(target_arch = "x86_64", feature = "avx512"))]
329create_rgb_xyz_dependant_executor!(
330 make_rgb_xyz_rgb_transform_avx512_opt,
331 TransformShaperRgbOptAvx512,
332 TransformMatrixShaperOptimized
333);
334
335#[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
336pub(crate) fn make_rgb_xyz_rgb_transform<
337 T: Clone + Send + Sync + PointeeSizeExpressible + 'static + Copy + Default,
338 const LINEAR_CAP: usize,
339 const GAMMA_LUT: usize,
340 const BIT_DEPTH: usize,
341>(
342 src_layout: Layout,
343 dst_layout: Layout,
344 profile: TransformMatrixShaper<T, LINEAR_CAP>,
345) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
346where
347 u32: AsPrimitive<T>,
348{
349 #[cfg(all(feature = "avx", target_arch = "x86_64"))]
350 if std::arch::is_x86_feature_detected!("avx2") {
351 return make_rgb_xyz_rgb_transform_avx2::<T, LINEAR_CAP, GAMMA_LUT, BIT_DEPTH>(
352 src_layout, dst_layout, profile,
353 );
354 }
355 #[cfg(all(feature = "sse", any(target_arch = "x86", target_arch = "x86_64")))]
356 if std::arch::is_x86_feature_detected!("sse4.1") {
357 return make_rgb_xyz_rgb_transform_sse_41::<T, LINEAR_CAP, GAMMA_LUT, BIT_DEPTH>(
358 src_layout, dst_layout, profile,
359 );
360 }
361 if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
362 return Ok(Box::new(TransformMatrixShaperScalar::<
363 T,
364 { Layout::Rgba as u8 },
365 { Layout::Rgba as u8 },
366 LINEAR_CAP,
367 GAMMA_LUT,
368 BIT_DEPTH,
369 > {
370 profile,
371 }));
372 } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
373 return Ok(Box::new(TransformMatrixShaperScalar::<
374 T,
375 { Layout::Rgb as u8 },
376 { Layout::Rgba as u8 },
377 LINEAR_CAP,
378 GAMMA_LUT,
379 BIT_DEPTH,
380 > {
381 profile,
382 }));
383 } else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
384 return Ok(Box::new(TransformMatrixShaperScalar::<
385 T,
386 { Layout::Rgba as u8 },
387 { Layout::Rgb as u8 },
388 LINEAR_CAP,
389 GAMMA_LUT,
390 BIT_DEPTH,
391 > {
392 profile,
393 }));
394 } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
395 return Ok(Box::new(TransformMatrixShaperScalar::<
396 T,
397 { Layout::Rgb as u8 },
398 { Layout::Rgb as u8 },
399 LINEAR_CAP,
400 GAMMA_LUT,
401 BIT_DEPTH,
402 > {
403 profile,
404 }));
405 }
406 Err(CmsError::UnsupportedProfileConnection)
407}
408
409#[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
410pub(crate) fn make_rgb_xyz_rgb_transform_opt<
411 T: Clone + Send + Sync + PointeeSizeExpressible + 'static + Copy + Default,
412 const LINEAR_CAP: usize,
413 const GAMMA_LUT: usize,
414 const BIT_DEPTH: usize,
415>(
416 src_layout: Layout,
417 dst_layout: Layout,
418 profile: TransformMatrixShaperOptimized<T, LINEAR_CAP>,
419) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
420where
421 u32: AsPrimitive<T>,
422{
423 #[cfg(all(feature = "avx512", target_arch = "x86_64"))]
424 if std::arch::is_x86_feature_detected!("avx512bw")
425 && std::arch::is_x86_feature_detected!("avx512vl")
426 && std::arch::is_x86_feature_detected!("fma")
427 {
428 return make_rgb_xyz_rgb_transform_avx512_opt::<T, LINEAR_CAP, GAMMA_LUT, BIT_DEPTH>(
429 src_layout, dst_layout, profile,
430 );
431 }
432 #[cfg(all(feature = "avx", target_arch = "x86_64"))]
433 if std::arch::is_x86_feature_detected!("avx2") {
434 return make_rgb_xyz_rgb_transform_avx2_opt::<T, LINEAR_CAP, GAMMA_LUT, BIT_DEPTH>(
435 src_layout, dst_layout, profile,
436 );
437 }
438 #[cfg(all(feature = "sse", any(target_arch = "x86", target_arch = "x86_64")))]
439 if std::arch::is_x86_feature_detected!("sse4.1") {
440 return make_rgb_xyz_rgb_transform_sse_41_opt::<T, LINEAR_CAP, GAMMA_LUT, BIT_DEPTH>(
441 src_layout, dst_layout, profile,
442 );
443 }
444 if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
445 return Ok(Box::new(TransformMatrixShaperOptScalar::<
446 T,
447 { Layout::Rgba as u8 },
448 { Layout::Rgba as u8 },
449 LINEAR_CAP,
450 GAMMA_LUT,
451 BIT_DEPTH,
452 > {
453 profile,
454 }));
455 } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
456 return Ok(Box::new(TransformMatrixShaperOptScalar::<
457 T,
458 { Layout::Rgb as u8 },
459 { Layout::Rgba as u8 },
460 LINEAR_CAP,
461 GAMMA_LUT,
462 BIT_DEPTH,
463 > {
464 profile,
465 }));
466 } else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
467 return Ok(Box::new(TransformMatrixShaperOptScalar::<
468 T,
469 { Layout::Rgba as u8 },
470 { Layout::Rgb as u8 },
471 LINEAR_CAP,
472 GAMMA_LUT,
473 BIT_DEPTH,
474 > {
475 profile,
476 }));
477 } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
478 return Ok(Box::new(TransformMatrixShaperOptScalar::<
479 T,
480 { Layout::Rgb as u8 },
481 { Layout::Rgb as u8 },
482 LINEAR_CAP,
483 GAMMA_LUT,
484 BIT_DEPTH,
485 > {
486 profile,
487 }));
488 }
489 Err(CmsError::UnsupportedProfileConnection)
490}
491
492#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
493use crate::conversions::neon::{TransformShaperRgbNeon, TransformShaperRgbOptNeon};
494use crate::conversions::rgbxyz_fixed::{
495 TransformMatrixShaperFixedPoint, TransformMatrixShaperFixedPointOpt,
496};
497use crate::transform::PointeeSizeExpressible;
498
499#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
500create_rgb_xyz_dependant_executor!(
501 make_rgb_xyz_rgb_transform,
502 TransformShaperRgbNeon,
503 TransformMatrixShaper
504);
505
506#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
507create_rgb_xyz_dependant_executor!(
508 make_rgb_xyz_rgb_transform_opt,
509 TransformShaperRgbOptNeon,
510 TransformMatrixShaperOptimized
511);
512
513#[allow(unused)]
514impl<
515 T: Clone + PointeeSizeExpressible + Copy + Default + 'static,
516 const SRC_LAYOUT: u8,
517 const DST_LAYOUT: u8,
518 const LINEAR_CAP: usize,
519 const GAMMA_LUT: usize,
520 const BIT_DEPTH: usize,
521> TransformExecutor<T>
522 for TransformMatrixShaperScalar<T, SRC_LAYOUT, DST_LAYOUT, LINEAR_CAP, GAMMA_LUT, BIT_DEPTH>
523where
524 u32: AsPrimitive<T>,
525{
526 fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
527 use crate::mlaf::mlaf;
528 let src_cn = Layout::from(SRC_LAYOUT);
529 let dst_cn = Layout::from(DST_LAYOUT);
530 let src_channels = src_cn.channels();
531 let dst_channels = dst_cn.channels();
532
533 if src.len() / src_channels != dst.len() / dst_channels {
534 return Err(CmsError::LaneSizeMismatch);
535 }
536 if src.len() % src_channels != 0 {
537 return Err(CmsError::LaneMultipleOfChannels);
538 }
539 if dst.len() % dst_channels != 0 {
540 return Err(CmsError::LaneMultipleOfChannels);
541 }
542
543 let transform = self.profile.adaptation_matrix;
544 let scale = (GAMMA_LUT - 1) as f32;
545 let max_colors: T = ((1 << BIT_DEPTH) - 1).as_();
546
547 for (src, dst) in src
548 .chunks_exact(src_channels)
549 .zip(dst.chunks_exact_mut(dst_channels))
550 {
551 let r = self.profile.r_linear[src[src_cn.r_i()]._as_usize()];
552 let g = self.profile.g_linear[src[src_cn.g_i()]._as_usize()];
553 let b = self.profile.b_linear[src[src_cn.b_i()]._as_usize()];
554 let a = if src_channels == 4 {
555 src[src_cn.a_i()]
556 } else {
557 max_colors
558 };
559
560 let new_r = mlaf(
561 0.5f32,
562 mlaf(
563 mlaf(r * transform.v[0][0], g, transform.v[0][1]),
564 b,
565 transform.v[0][2],
566 )
567 .max(0f32)
568 .min(1f32),
569 scale,
570 );
571
572 let new_g = mlaf(
573 0.5f32,
574 mlaf(
575 mlaf(r * transform.v[1][0], g, transform.v[1][1]),
576 b,
577 transform.v[1][2],
578 )
579 .max(0f32)
580 .min(1f32),
581 scale,
582 );
583
584 let new_b = mlaf(
585 0.5f32,
586 mlaf(
587 mlaf(r * transform.v[2][0], g, transform.v[2][1]),
588 b,
589 transform.v[2][2],
590 )
591 .max(0f32)
592 .min(1f32),
593 scale,
594 );
595
596 dst[dst_cn.r_i()] = self.profile.r_gamma[(new_r as u16) as usize];
597 dst[dst_cn.g_i()] = self.profile.g_gamma[(new_g as u16) as usize];
598 dst[dst_cn.b_i()] = self.profile.b_gamma[(new_b as u16) as usize];
599 if dst_channels == 4 {
600 dst[dst_cn.a_i()] = a;
601 }
602 }
603
604 Ok(())
605 }
606}
607
608#[allow(unused)]
609impl<
610 T: Clone + PointeeSizeExpressible + Copy + Default + 'static,
611 const SRC_LAYOUT: u8,
612 const DST_LAYOUT: u8,
613 const LINEAR_CAP: usize,
614 const GAMMA_LUT: usize,
615 const BIT_DEPTH: usize,
616> TransformExecutor<T>
617 for TransformMatrixShaperOptScalar<T, SRC_LAYOUT, DST_LAYOUT, LINEAR_CAP, GAMMA_LUT, BIT_DEPTH>
618where
619 u32: AsPrimitive<T>,
620{
621 fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
622 use crate::mlaf::mlaf;
623 let src_cn = Layout::from(SRC_LAYOUT);
624 let dst_cn = Layout::from(DST_LAYOUT);
625 let src_channels = src_cn.channels();
626 let dst_channels = dst_cn.channels();
627
628 if src.len() / src_channels != dst.len() / dst_channels {
629 return Err(CmsError::LaneSizeMismatch);
630 }
631 if src.len() % src_channels != 0 {
632 return Err(CmsError::LaneMultipleOfChannels);
633 }
634 if dst.len() % dst_channels != 0 {
635 return Err(CmsError::LaneMultipleOfChannels);
636 }
637
638 let transform = self.profile.adaptation_matrix;
639 let scale = (GAMMA_LUT - 1) as f32;
640 let max_colors: T = ((1 << BIT_DEPTH) - 1).as_();
641
642 for (src, dst) in src
643 .chunks_exact(src_channels)
644 .zip(dst.chunks_exact_mut(dst_channels))
645 {
646 let r = self.profile.linear[src[src_cn.r_i()]._as_usize()];
647 let g = self.profile.linear[src[src_cn.g_i()]._as_usize()];
648 let b = self.profile.linear[src[src_cn.b_i()]._as_usize()];
649 let a = if src_channels == 4 {
650 src[src_cn.a_i()]
651 } else {
652 max_colors
653 };
654
655 let new_r = mlaf(
656 0.5f32,
657 mlaf(
658 mlaf(r * transform.v[0][0], g, transform.v[0][1]),
659 b,
660 transform.v[0][2],
661 )
662 .max(0f32)
663 .min(1f32),
664 scale,
665 );
666
667 let new_g = mlaf(
668 0.5f32,
669 mlaf(
670 mlaf(r * transform.v[1][0], g, transform.v[1][1]),
671 b,
672 transform.v[1][2],
673 )
674 .max(0f32)
675 .min(1f32),
676 scale,
677 );
678
679 let new_b = mlaf(
680 0.5f32,
681 mlaf(
682 mlaf(r * transform.v[2][0], g, transform.v[2][1]),
683 b,
684 transform.v[2][2],
685 )
686 .max(0f32)
687 .min(1f32),
688 scale,
689 );
690
691 dst[dst_cn.r_i()] = self.profile.gamma[(new_r as u16) as usize];
692 dst[dst_cn.g_i()] = self.profile.gamma[(new_g as u16) as usize];
693 dst[dst_cn.b_i()] = self.profile.gamma[(new_b as u16) as usize];
694 if dst_channels == 4 {
695 dst[dst_cn.a_i()] = a;
696 }
697 }
698
699 Ok(())
700 }
701}