moxcms/conversions/
rgbxyz_fixed.rs

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 */
29use crate::Layout;
30use crate::conversions::TransformMatrixShaper;
31use crate::matrix::Matrix3;
32use crate::{CmsError, TransformExecutor};
33use num_traits::AsPrimitive;
34
35/// Fixed point conversion Q2.13
36pub(crate) struct TransformMatrixShaperFixedPoint<R, T, const LINEAR_CAP: usize> {
37    pub(crate) r_linear: Box<[R; LINEAR_CAP]>,
38    pub(crate) g_linear: Box<[R; LINEAR_CAP]>,
39    pub(crate) b_linear: Box<[R; LINEAR_CAP]>,
40    pub(crate) r_gamma: Box<[T; 65536]>,
41    pub(crate) g_gamma: Box<[T; 65536]>,
42    pub(crate) b_gamma: Box<[T; 65536]>,
43    pub(crate) adaptation_matrix: Matrix3<i16>,
44}
45
46/// Fixed point conversion Q2.13
47#[allow(dead_code)]
48pub(crate) struct TransformMatrixShaperFp<R, T> {
49    pub(crate) r_linear: Vec<R>,
50    pub(crate) g_linear: Vec<R>,
51    pub(crate) b_linear: Vec<R>,
52    pub(crate) r_gamma: Box<[T; 65536]>,
53    pub(crate) g_gamma: Box<[T; 65536]>,
54    pub(crate) b_gamma: Box<[T; 65536]>,
55    pub(crate) adaptation_matrix: Matrix3<i16>,
56}
57
58/// Fixed point conversion Q2.13
59///
60/// Optimized routine for *all same curves* matrix shaper.
61pub(crate) struct TransformMatrixShaperFixedPointOpt<R, W, T, const LINEAR_CAP: usize> {
62    pub(crate) linear: Box<[R; LINEAR_CAP]>,
63    pub(crate) gamma: Box<[T; 65536]>,
64    pub(crate) adaptation_matrix: Matrix3<W>,
65}
66
67/// Fixed point conversion Q2.13
68///
69/// Optimized routine for *all same curves* matrix shaper.
70#[allow(dead_code)]
71pub(crate) struct TransformMatrixShaperFpOptVec<R, W, T> {
72    pub(crate) linear: Vec<R>,
73    pub(crate) gamma: Box<[T; 65536]>,
74    pub(crate) adaptation_matrix: Matrix3<W>,
75}
76
77#[allow(unused)]
78struct TransformMatrixShaperQ2_13<
79    T: Copy,
80    const SRC_LAYOUT: u8,
81    const DST_LAYOUT: u8,
82    const LINEAR_CAP: usize,
83    const PRECISION: i32,
84> {
85    pub(crate) profile: TransformMatrixShaperFixedPoint<i16, T, LINEAR_CAP>,
86    pub(crate) bit_depth: usize,
87    pub(crate) gamma_lut: usize,
88}
89
90#[allow(unused)]
91struct TransformMatrixShaperQ2_13Optimized<
92    T: Copy,
93    const SRC_LAYOUT: u8,
94    const DST_LAYOUT: u8,
95    const LINEAR_CAP: usize,
96    const PRECISION: i32,
97> {
98    pub(crate) profile: TransformMatrixShaperFixedPointOpt<i16, i16, T, LINEAR_CAP>,
99    pub(crate) bit_depth: usize,
100    pub(crate) gamma_lut: usize,
101}
102
103#[allow(unused)]
104impl<
105    T: Clone + PointeeSizeExpressible + Copy + Default + 'static,
106    const SRC_LAYOUT: u8,
107    const DST_LAYOUT: u8,
108    const LINEAR_CAP: usize,
109    const PRECISION: i32,
110> TransformExecutor<T>
111    for TransformMatrixShaperQ2_13<T, SRC_LAYOUT, DST_LAYOUT, LINEAR_CAP, PRECISION>
112where
113    u32: AsPrimitive<T>,
114{
115    fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
116        let src_cn = Layout::from(SRC_LAYOUT);
117        let dst_cn = Layout::from(DST_LAYOUT);
118        let src_channels = src_cn.channels();
119        let dst_channels = dst_cn.channels();
120
121        if src.len() / src_channels != dst.len() / dst_channels {
122            return Err(CmsError::LaneSizeMismatch);
123        }
124        if src.len() % src_channels != 0 {
125            return Err(CmsError::LaneMultipleOfChannels);
126        }
127        if dst.len() % dst_channels != 0 {
128            return Err(CmsError::LaneMultipleOfChannels);
129        }
130
131        let transform = self.profile.adaptation_matrix;
132        let max_colors: T = ((1 << self.bit_depth as u32) - 1u32).as_();
133        let rnd: i32 = (1i32 << (PRECISION - 1));
134
135        let v_gamma_max = self.gamma_lut as i32 - 1;
136
137        for (src, dst) in src
138            .chunks_exact(src_channels)
139            .zip(dst.chunks_exact_mut(dst_channels))
140        {
141            let r = self.profile.r_linear[src[src_cn.r_i()]._as_usize()];
142            let g = self.profile.g_linear[src[src_cn.g_i()]._as_usize()];
143            let b = self.profile.b_linear[src[src_cn.b_i()]._as_usize()];
144            let a = if src_channels == 4 {
145                src[src_cn.a_i()]
146            } else {
147                max_colors
148            };
149
150            let new_r = r as i32 * transform.v[0][0] as i32
151                + g as i32 * transform.v[0][1] as i32
152                + b as i32 * transform.v[0][2] as i32
153                + rnd;
154
155            let r_q2_13 = (new_r >> PRECISION).min(v_gamma_max).max(0) as u16;
156
157            let new_g = r as i32 * transform.v[1][0] as i32
158                + g as i32 * transform.v[1][1] as i32
159                + b as i32 * transform.v[1][2] as i32
160                + rnd;
161
162            let g_q2_13 = (new_g >> PRECISION).min(v_gamma_max).max(0) as u16;
163
164            let new_b = r as i32 * transform.v[2][0] as i32
165                + g as i32 * transform.v[2][1] as i32
166                + b as i32 * transform.v[2][2] as i32
167                + rnd;
168
169            let b_q2_13 = (new_b >> PRECISION).min(v_gamma_max).max(0) as u16;
170
171            dst[dst_cn.r_i()] = self.profile.r_gamma[r_q2_13 as usize];
172            dst[dst_cn.g_i()] = self.profile.g_gamma[g_q2_13 as usize];
173            dst[dst_cn.b_i()] = self.profile.b_gamma[b_q2_13 as usize];
174            if dst_channels == 4 {
175                dst[dst_cn.a_i()] = a;
176            }
177        }
178        Ok(())
179    }
180}
181
182#[allow(unused)]
183impl<
184    T: Clone + PointeeSizeExpressible + Copy + Default + 'static,
185    const SRC_LAYOUT: u8,
186    const DST_LAYOUT: u8,
187    const LINEAR_CAP: usize,
188    const PRECISION: i32,
189> TransformExecutor<T>
190    for TransformMatrixShaperQ2_13Optimized<T, SRC_LAYOUT, DST_LAYOUT, LINEAR_CAP, PRECISION>
191where
192    u32: AsPrimitive<T>,
193{
194    fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
195        let src_cn = Layout::from(SRC_LAYOUT);
196        let dst_cn = Layout::from(DST_LAYOUT);
197        let src_channels = src_cn.channels();
198        let dst_channels = dst_cn.channels();
199
200        if src.len() / src_channels != dst.len() / dst_channels {
201            return Err(CmsError::LaneSizeMismatch);
202        }
203        if src.len() % src_channels != 0 {
204            return Err(CmsError::LaneMultipleOfChannels);
205        }
206        if dst.len() % dst_channels != 0 {
207            return Err(CmsError::LaneMultipleOfChannels);
208        }
209
210        let transform = self.profile.adaptation_matrix;
211        let max_colors: T = ((1 << self.bit_depth as u32) - 1u32).as_();
212        let rnd: i32 = (1i32 << (PRECISION - 1));
213
214        let v_gamma_max = self.gamma_lut as i32 - 1;
215
216        for (src, dst) in src
217            .chunks_exact(src_channels)
218            .zip(dst.chunks_exact_mut(dst_channels))
219        {
220            let r = self.profile.linear[src[src_cn.r_i()]._as_usize()];
221            let g = self.profile.linear[src[src_cn.g_i()]._as_usize()];
222            let b = self.profile.linear[src[src_cn.b_i()]._as_usize()];
223            let a = if src_channels == 4 {
224                src[src_cn.a_i()]
225            } else {
226                max_colors
227            };
228
229            let new_r = r as i32 * transform.v[0][0] as i32
230                + g as i32 * transform.v[0][1] as i32
231                + b as i32 * transform.v[0][2] as i32
232                + rnd;
233
234            let r_q2_13 = (new_r >> PRECISION).min(v_gamma_max).max(0) as u16;
235
236            let new_g = r as i32 * transform.v[1][0] as i32
237                + g as i32 * transform.v[1][1] as i32
238                + b as i32 * transform.v[1][2] as i32
239                + rnd;
240
241            let g_q2_13 = (new_g >> PRECISION).min(v_gamma_max).max(0) as u16;
242
243            let new_b = r as i32 * transform.v[2][0] as i32
244                + g as i32 * transform.v[2][1] as i32
245                + b as i32 * transform.v[2][2] as i32
246                + rnd;
247
248            let b_q2_13 = (new_b >> PRECISION).min(v_gamma_max).max(0) as u16;
249
250            dst[dst_cn.r_i()] = self.profile.gamma[r_q2_13 as usize];
251            dst[dst_cn.g_i()] = self.profile.gamma[g_q2_13 as usize];
252            dst[dst_cn.b_i()] = self.profile.gamma[b_q2_13 as usize];
253            if dst_channels == 4 {
254                dst[dst_cn.a_i()] = a;
255            }
256        }
257        Ok(())
258    }
259}
260
261#[allow(unused_macros)]
262macro_rules! create_rgb_xyz_dependant_q2_13_executor {
263    ($dep_name: ident, $dependant: ident, $resolution: ident, $shaper: ident) => {
264        pub(crate) fn $dep_name<
265            T: Clone + Send + Sync + AsPrimitive<usize> + Default + PointeeSizeExpressible,
266            const LINEAR_CAP: usize,
267            const PRECISION: i32,
268        >(
269            src_layout: Layout,
270            dst_layout: Layout,
271            profile: $shaper<T, LINEAR_CAP>,
272            gamma_lut: usize,
273            bit_depth: usize,
274        ) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
275        where
276            u32: AsPrimitive<T>,
277        {
278            let q2_13_profile =
279                profile.to_q2_13_n::<$resolution, PRECISION, LINEAR_CAP>(gamma_lut, bit_depth);
280            if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
281                return Ok(Box::new($dependant::<
282                    T,
283                    { Layout::Rgba as u8 },
284                    { Layout::Rgba as u8 },
285                    LINEAR_CAP,
286                    PRECISION,
287                > {
288                    profile: q2_13_profile,
289                    bit_depth,
290                    gamma_lut,
291                }));
292            } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
293                return Ok(Box::new($dependant::<
294                    T,
295                    { Layout::Rgb as u8 },
296                    { Layout::Rgba as u8 },
297                    LINEAR_CAP,
298                    PRECISION,
299                > {
300                    profile: q2_13_profile,
301                    bit_depth,
302                    gamma_lut,
303                }));
304            } else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
305                return Ok(Box::new($dependant::<
306                    T,
307                    { Layout::Rgba as u8 },
308                    { Layout::Rgb as u8 },
309                    LINEAR_CAP,
310                    PRECISION,
311                > {
312                    profile: q2_13_profile,
313                    bit_depth,
314                    gamma_lut,
315                }));
316            } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
317                return Ok(Box::new($dependant::<
318                    T,
319                    { Layout::Rgb as u8 },
320                    { Layout::Rgb as u8 },
321                    LINEAR_CAP,
322                    PRECISION,
323                > {
324                    profile: q2_13_profile,
325                    bit_depth,
326                    gamma_lut,
327                }));
328            }
329            Err(CmsError::UnsupportedProfileConnection)
330        }
331    };
332}
333
334#[allow(unused_macros)]
335macro_rules! create_rgb_xyz_dependant_q2_13_executor_fp {
336    ($dep_name: ident, $dependant: ident, $resolution: ident, $shaper: ident) => {
337        pub(crate) fn $dep_name<
338            T: Clone + Send + Sync + AsPrimitive<usize> + Default + PointeeSizeExpressible,
339            const LINEAR_CAP: usize,
340            const PRECISION: i32,
341        >(
342            src_layout: Layout,
343            dst_layout: Layout,
344            profile: $shaper<T, LINEAR_CAP>,
345            gamma_lut: usize,
346            bit_depth: usize,
347        ) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
348        where
349            u32: AsPrimitive<T>,
350        {
351            let q2_13_profile = profile.to_q2_13_i::<$resolution, PRECISION>(gamma_lut, bit_depth);
352            if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
353                return Ok(Box::new($dependant::<
354                    T,
355                    { Layout::Rgba as u8 },
356                    { Layout::Rgba as u8 },
357                    PRECISION,
358                > {
359                    profile: q2_13_profile,
360                    bit_depth,
361                    gamma_lut,
362                }));
363            } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
364                return Ok(Box::new($dependant::<
365                    T,
366                    { Layout::Rgb as u8 },
367                    { Layout::Rgba as u8 },
368                    PRECISION,
369                > {
370                    profile: q2_13_profile,
371                    bit_depth,
372                    gamma_lut,
373                }));
374            } else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
375                return Ok(Box::new($dependant::<
376                    T,
377                    { Layout::Rgba as u8 },
378                    { Layout::Rgb as u8 },
379                    PRECISION,
380                > {
381                    profile: q2_13_profile,
382                    bit_depth,
383                    gamma_lut,
384                }));
385            } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
386                return Ok(Box::new($dependant::<
387                    T,
388                    { Layout::Rgb as u8 },
389                    { Layout::Rgb as u8 },
390                    PRECISION,
391                > {
392                    profile: q2_13_profile,
393                    bit_depth,
394                    gamma_lut,
395                }));
396            }
397            Err(CmsError::UnsupportedProfileConnection)
398        }
399    };
400}
401
402#[cfg(all(target_arch = "aarch64", feature = "neon"))]
403macro_rules! create_rgb_xyz_dependant_q1_30_executor {
404    ($dep_name: ident, $dependant: ident, $resolution: ident, $shaper: ident) => {
405        pub(crate) fn $dep_name<
406            T: Clone + Send + Sync + AsPrimitive<usize> + Default + PointeeSizeExpressible,
407            const LINEAR_CAP: usize,
408            const PRECISION: i32,
409        >(
410            src_layout: Layout,
411            dst_layout: Layout,
412            profile: $shaper<T, LINEAR_CAP>,
413            gamma_lut: usize,
414            bit_depth: usize,
415        ) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
416        where
417            u32: AsPrimitive<T>,
418        {
419            let q1_30_profile = profile.to_q1_30_n::<$resolution, PRECISION>(gamma_lut, bit_depth);
420            if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
421                return Ok(Box::new($dependant::<
422                    T,
423                    { Layout::Rgba as u8 },
424                    { Layout::Rgba as u8 },
425                > {
426                    profile: q1_30_profile,
427                    gamma_lut,
428                    bit_depth,
429                }));
430            } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
431                return Ok(Box::new($dependant::<
432                    T,
433                    { Layout::Rgb as u8 },
434                    { Layout::Rgba as u8 },
435                > {
436                    profile: q1_30_profile,
437                    gamma_lut,
438                    bit_depth,
439                }));
440            } else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
441                return Ok(Box::new($dependant::<
442                    T,
443                    { Layout::Rgba as u8 },
444                    { Layout::Rgb as u8 },
445                > {
446                    profile: q1_30_profile,
447                    gamma_lut,
448                    bit_depth,
449                }));
450            } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
451                return Ok(Box::new($dependant::<
452                    T,
453                    { Layout::Rgb as u8 },
454                    { Layout::Rgb as u8 },
455                > {
456                    profile: q1_30_profile,
457                    gamma_lut,
458                    bit_depth,
459                }));
460            }
461            Err(CmsError::UnsupportedProfileConnection)
462        }
463    };
464}
465
466#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
467use crate::conversions::neon::{
468    TransformShaperQ1_30NeonOpt, TransformShaperQ2_13Neon, TransformShaperQ2_13NeonOpt,
469};
470
471#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
472create_rgb_xyz_dependant_q2_13_executor_fp!(
473    make_rgb_xyz_q2_13,
474    TransformShaperQ2_13Neon,
475    i16,
476    TransformMatrixShaper
477);
478
479#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
480create_rgb_xyz_dependant_q2_13_executor_fp!(
481    make_rgb_xyz_q2_13_opt,
482    TransformShaperQ2_13NeonOpt,
483    i16,
484    TransformMatrixShaperOptimized
485);
486
487#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
488create_rgb_xyz_dependant_q1_30_executor!(
489    make_rgb_xyz_q1_30_opt,
490    TransformShaperQ1_30NeonOpt,
491    i32,
492    TransformMatrixShaperOptimized
493);
494
495#[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
496create_rgb_xyz_dependant_q2_13_executor!(
497    make_rgb_xyz_q2_13,
498    TransformMatrixShaperQ2_13,
499    i16,
500    TransformMatrixShaper
501);
502
503#[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
504create_rgb_xyz_dependant_q2_13_executor!(
505    make_rgb_xyz_q2_13_opt,
506    TransformMatrixShaperQ2_13Optimized,
507    i16,
508    TransformMatrixShaperOptimized
509);
510
511#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
512use crate::conversions::sse::{TransformShaperQ2_13OptSse, TransformShaperQ2_13Sse};
513
514#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
515create_rgb_xyz_dependant_q2_13_executor_fp!(
516    make_rgb_xyz_q2_13_transform_sse_41,
517    TransformShaperQ2_13Sse,
518    i32,
519    TransformMatrixShaper
520);
521
522#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
523create_rgb_xyz_dependant_q2_13_executor_fp!(
524    make_rgb_xyz_q2_13_transform_sse_41_opt,
525    TransformShaperQ2_13OptSse,
526    i32,
527    TransformMatrixShaperOptimized
528);
529
530#[cfg(all(target_arch = "x86_64", feature = "avx"))]
531use crate::conversions::avx::{TransformShaperRgbQ2_13Avx, TransformShaperRgbQ2_13OptAvx};
532use crate::conversions::rgbxyz::TransformMatrixShaperOptimized;
533use crate::transform::PointeeSizeExpressible;
534
535#[cfg(all(target_arch = "x86_64", feature = "avx"))]
536create_rgb_xyz_dependant_q2_13_executor_fp!(
537    make_rgb_xyz_q2_13_transform_avx2,
538    TransformShaperRgbQ2_13Avx,
539    i32,
540    TransformMatrixShaper
541);
542
543#[cfg(all(target_arch = "x86_64", feature = "avx"))]
544create_rgb_xyz_dependant_q2_13_executor_fp!(
545    make_rgb_xyz_q2_13_transform_avx2_opt,
546    TransformShaperRgbQ2_13OptAvx,
547    i32,
548    TransformMatrixShaperOptimized
549);
550
551#[cfg(all(target_arch = "x86_64", feature = "avx512"))]
552use crate::conversions::avx512::TransformShaperRgbQ2_13OptAvx512;
553
554#[cfg(all(target_arch = "x86_64", feature = "avx512"))]
555create_rgb_xyz_dependant_q2_13_executor!(
556    make_rgb_xyz_q2_13_transform_avx512_opt,
557    TransformShaperRgbQ2_13OptAvx512,
558    i32,
559    TransformMatrixShaperOptimized
560);