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