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