Skip to main content

moxcms/conversions/
transform_lut3_to_3.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 */
29#![cfg(feature = "lut")]
30#![allow(dead_code)]
31use crate::conversions::LutBarycentricReduction;
32use crate::conversions::interpolator::{BarycentricWeight, MultidimensionalInterpolation};
33use crate::conversions::lut_transforms::Lut3x3Factory;
34use crate::transform::PointeeSizeExpressible;
35use crate::{
36    BarycentricWeightScale, CmsError, DataColorSpace, InterpolationMethod, Layout,
37    TransformExecutor, TransformOptions,
38};
39use num_traits::AsPrimitive;
40use std::marker::PhantomData;
41use std::sync::Arc;
42
43pub(crate) struct TransformLut3x3<
44    T,
45    U,
46    const SRC_LAYOUT: u8,
47    const DST_LAYOUT: u8,
48    const GRID_SIZE: usize,
49    const BIT_DEPTH: usize,
50    const BINS: usize,
51    const BARYCENTRIC_BINS: usize,
52> {
53    pub(crate) lut: Vec<f32>,
54    pub(crate) _phantom: PhantomData<T>,
55    pub(crate) _phantom1: PhantomData<U>,
56    pub(crate) interpolation_method: InterpolationMethod,
57    pub(crate) weights: Box<[BarycentricWeight<f32>; BINS]>,
58    pub(crate) color_space: DataColorSpace,
59    pub(crate) is_linear: bool,
60}
61
62impl<
63    T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible,
64    U: AsPrimitive<usize>,
65    const SRC_LAYOUT: u8,
66    const DST_LAYOUT: u8,
67    const GRID_SIZE: usize,
68    const BIT_DEPTH: usize,
69    const BINS: usize,
70    const BARYCENTRIC_BINS: usize,
71> TransformLut3x3<T, U, SRC_LAYOUT, DST_LAYOUT, GRID_SIZE, BIT_DEPTH, BINS, BARYCENTRIC_BINS>
72where
73    f32: AsPrimitive<T>,
74    u32: AsPrimitive<T>,
75    (): LutBarycentricReduction<T, U>,
76{
77    #[inline(never)]
78    fn transform_chunk(
79        &self,
80        src: &[T],
81        dst: &mut [T],
82        interpolator: Box<dyn MultidimensionalInterpolation + Send + Sync>,
83    ) {
84        let src_cn = Layout::from(SRC_LAYOUT);
85        let src_channels = src_cn.channels();
86
87        let dst_cn = Layout::from(DST_LAYOUT);
88        let dst_channels = dst_cn.channels();
89
90        let value_scale = ((1 << BIT_DEPTH) - 1) as f32;
91        let max_value = ((1u32 << BIT_DEPTH) - 1).as_();
92
93        for (src, dst) in src
94            .chunks_exact(src_channels)
95            .zip(dst.chunks_exact_mut(dst_channels))
96        {
97            let x = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
98                src[src_cn.r_i()],
99            );
100            let y = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
101                src[src_cn.g_i()],
102            );
103            let z = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
104                src[src_cn.b_i()],
105            );
106
107            let a = if src_channels == 4 {
108                src[src_cn.a_i()]
109            } else {
110                max_value
111            };
112
113            let v = interpolator.inter3(
114                &self.lut,
115                &self.weights[x.as_()],
116                &self.weights[y.as_()],
117                &self.weights[z.as_()],
118            );
119            if T::FINITE {
120                let r = v * value_scale + 0.5;
121                dst[dst_cn.r_i()] = r.v[0].min(value_scale).max(0.).as_();
122                dst[dst_cn.g_i()] = r.v[1].min(value_scale).max(0.).as_();
123                dst[dst_cn.b_i()] = r.v[2].min(value_scale).max(0.).as_();
124                if dst_channels == 4 {
125                    dst[dst_cn.a_i()] = a;
126                }
127            } else {
128                dst[dst_cn.r_i()] = v.v[0].as_();
129                dst[dst_cn.g_i()] = v.v[1].as_();
130                dst[dst_cn.b_i()] = v.v[2].as_();
131                if dst_channels == 4 {
132                    dst[dst_cn.a_i()] = a;
133                }
134            }
135        }
136    }
137}
138
139impl<
140    T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible,
141    U: AsPrimitive<usize>,
142    const SRC_LAYOUT: u8,
143    const DST_LAYOUT: u8,
144    const GRID_SIZE: usize,
145    const BIT_DEPTH: usize,
146    const BINS: usize,
147    const BARYCENTRIC_BINS: usize,
148> TransformExecutor<T>
149    for TransformLut3x3<T, U, SRC_LAYOUT, DST_LAYOUT, GRID_SIZE, BIT_DEPTH, BINS, BARYCENTRIC_BINS>
150where
151    f32: AsPrimitive<T>,
152    u32: AsPrimitive<T>,
153    (): LutBarycentricReduction<T, U>,
154{
155    fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
156        let src_cn = Layout::from(SRC_LAYOUT);
157        let src_channels = src_cn.channels();
158
159        let dst_cn = Layout::from(DST_LAYOUT);
160        let dst_channels = dst_cn.channels();
161        if src.len() % src_channels != 0 {
162            return Err(CmsError::LaneMultipleOfChannels);
163        }
164        if dst.len() % dst_channels != 0 {
165            return Err(CmsError::LaneMultipleOfChannels);
166        }
167        let src_chunks = src.len() / src_channels;
168        let dst_chunks = dst.len() / dst_channels;
169        if src_chunks != dst_chunks {
170            return Err(CmsError::LaneSizeMismatch);
171        }
172
173        if self.color_space == DataColorSpace::Lab
174            || (self.is_linear && self.color_space == DataColorSpace::Rgb)
175            || self.color_space == DataColorSpace::Xyz
176        {
177            use crate::conversions::interpolator::Trilinear;
178            self.transform_chunk(src, dst, Box::new(Trilinear::<GRID_SIZE> {}));
179        } else {
180            match self.interpolation_method {
181                #[cfg(feature = "options")]
182                InterpolationMethod::Tetrahedral => {
183                    use crate::conversions::interpolator::Tetrahedral;
184                    self.transform_chunk(src, dst, Box::new(Tetrahedral::<GRID_SIZE> {}));
185                }
186                #[cfg(feature = "options")]
187                InterpolationMethod::Pyramid => {
188                    use crate::conversions::interpolator::Pyramidal;
189                    self.transform_chunk(src, dst, Box::new(Pyramidal::<GRID_SIZE> {}));
190                }
191                #[cfg(feature = "options")]
192                InterpolationMethod::Prism => {
193                    use crate::conversions::interpolator::Prismatic;
194                    self.transform_chunk(src, dst, Box::new(Prismatic::<GRID_SIZE> {}));
195                }
196                InterpolationMethod::Linear => {
197                    use crate::conversions::interpolator::Trilinear;
198                    self.transform_chunk(src, dst, Box::new(Trilinear::<GRID_SIZE> {}));
199                }
200            }
201        }
202
203        Ok(())
204    }
205}
206
207pub(crate) struct DefaultLut3x3Factory {}
208
209impl Lut3x3Factory for DefaultLut3x3Factory {
210    fn make_transform_3x3<
211        T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible + 'static + Send + Sync,
212        const SRC_LAYOUT: u8,
213        const DST_LAYOUT: u8,
214        const GRID_SIZE: usize,
215        const BIT_DEPTH: usize,
216    >(
217        lut: Vec<f32>,
218        options: TransformOptions,
219        color_space: DataColorSpace,
220        is_linear: bool,
221    ) -> Arc<dyn TransformExecutor<T> + Send + Sync>
222    where
223        f32: AsPrimitive<T>,
224        u32: AsPrimitive<T>,
225        (): LutBarycentricReduction<T, u8>,
226        (): LutBarycentricReduction<T, u16>,
227    {
228        match options.barycentric_weight_scale {
229            BarycentricWeightScale::Low => Arc::new(TransformLut3x3::<
230                T,
231                u8,
232                SRC_LAYOUT,
233                DST_LAYOUT,
234                GRID_SIZE,
235                BIT_DEPTH,
236                256,
237                256,
238            > {
239                lut,
240                _phantom: PhantomData,
241                _phantom1: PhantomData,
242                interpolation_method: options.interpolation_method,
243                weights: BarycentricWeight::<f32>::create_ranged_256::<GRID_SIZE>(),
244                color_space,
245                is_linear,
246            }),
247            #[cfg(feature = "options")]
248            BarycentricWeightScale::High => Arc::new(TransformLut3x3::<
249                T,
250                u16,
251                SRC_LAYOUT,
252                DST_LAYOUT,
253                GRID_SIZE,
254                BIT_DEPTH,
255                65536,
256                65536,
257            > {
258                lut,
259                _phantom: PhantomData,
260                _phantom1: PhantomData,
261                interpolation_method: options.interpolation_method,
262                weights: BarycentricWeight::<f32>::create_binned::<GRID_SIZE, 65536>(),
263                color_space,
264                is_linear,
265            }),
266        }
267    }
268}