Skip to main content

moxcms/conversions/
transform_lut4_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")]
30use crate::conversions::LutBarycentricReduction;
31use crate::conversions::interpolator::*;
32use crate::conversions::lut_transforms::Lut4x3Factory;
33use crate::math::{FusedMultiplyAdd, FusedMultiplyNegAdd, m_clamp};
34use crate::{
35    BarycentricWeightScale, CmsError, DataColorSpace, InterpolationMethod, Layout,
36    PointeeSizeExpressible, TransformExecutor, TransformOptions, Vector3f,
37};
38use num_traits::AsPrimitive;
39use std::marker::PhantomData;
40use std::sync::Arc;
41
42pub(crate) trait Vector3fCmykLerp {
43    fn interpolate(a: Vector3f, b: Vector3f, t: f32, scale: f32) -> Vector3f;
44}
45
46#[allow(unused)]
47#[derive(Copy, Clone, Default)]
48struct DefaultVector3fLerp;
49
50impl Vector3fCmykLerp for DefaultVector3fLerp {
51    #[inline(always)]
52    fn interpolate(a: Vector3f, b: Vector3f, t: f32, scale: f32) -> Vector3f {
53        let t = Vector3f::from(t);
54        let inter = a.neg_mla(a, t).mla(b, t);
55        let mut new_vec = Vector3f::from(0.5).mla(inter, Vector3f::from(scale));
56        new_vec.v[0] = m_clamp(new_vec.v[0], 0.0, scale);
57        new_vec.v[1] = m_clamp(new_vec.v[1], 0.0, scale);
58        new_vec.v[2] = m_clamp(new_vec.v[2], 0.0, scale);
59        new_vec
60    }
61}
62
63#[allow(unused)]
64#[derive(Copy, Clone, Default)]
65pub(crate) struct NonFiniteVector3fLerp;
66
67impl Vector3fCmykLerp for NonFiniteVector3fLerp {
68    #[inline(always)]
69    fn interpolate(a: Vector3f, b: Vector3f, t: f32, _: f32) -> Vector3f {
70        let t = Vector3f::from(t);
71        a.neg_mla(a, t).mla(b, t)
72    }
73}
74
75#[allow(unused)]
76#[derive(Copy, Clone, Default)]
77pub(crate) struct NonFiniteVector3fLerpUnbound;
78
79impl Vector3fCmykLerp for NonFiniteVector3fLerpUnbound {
80    #[inline(always)]
81    fn interpolate(a: Vector3f, b: Vector3f, t: f32, _: f32) -> Vector3f {
82        let t = Vector3f::from(t);
83        a.neg_mla(a, t).mla(b, t)
84    }
85}
86
87#[allow(unused)]
88struct TransformLut4To3<
89    T,
90    U,
91    const LAYOUT: u8,
92    const GRID_SIZE: usize,
93    const BIT_DEPTH: usize,
94    const BINS: usize,
95    const BARYCENTRIC_BINS: usize,
96> {
97    lut: Vec<f32>,
98    _phantom: PhantomData<T>,
99    _phantom1: PhantomData<U>,
100    interpolation_method: InterpolationMethod,
101    weights: Box<[BarycentricWeight<f32>; BINS]>,
102    color_space: DataColorSpace,
103    is_linear: bool,
104}
105
106#[allow(unused)]
107impl<
108    T: Copy + AsPrimitive<f32> + Default,
109    U: AsPrimitive<usize>,
110    const LAYOUT: u8,
111    const GRID_SIZE: usize,
112    const BIT_DEPTH: usize,
113    const BINS: usize,
114    const BARYCENTRIC_BINS: usize,
115> TransformLut4To3<T, U, LAYOUT, GRID_SIZE, BIT_DEPTH, BINS, BARYCENTRIC_BINS>
116where
117    f32: AsPrimitive<T>,
118    u32: AsPrimitive<T>,
119    (): LutBarycentricReduction<T, U>,
120{
121    #[inline(never)]
122    fn transform_chunk<Interpolation: Vector3fCmykLerp>(
123        &self,
124        src: &[T],
125        dst: &mut [T],
126        interpolator: Box<dyn MultidimensionalInterpolation + Send + Sync>,
127    ) {
128        let cn = Layout::from(LAYOUT);
129        let channels = cn.channels();
130        let grid_size = GRID_SIZE as i32;
131        let grid_size3 = grid_size * grid_size * grid_size;
132
133        let value_scale = ((1 << BIT_DEPTH) - 1) as f32;
134        let max_value = ((1 << BIT_DEPTH) - 1u32).as_();
135
136        for (src, dst) in src.chunks_exact(4).zip(dst.chunks_exact_mut(channels)) {
137            let c = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
138                src[0],
139            );
140            let m = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
141                src[1],
142            );
143            let y = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
144                src[2],
145            );
146            let k = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
147                src[3],
148            );
149
150            let k_weights = self.weights[k.as_()];
151
152            let w: i32 = k_weights.x;
153            let w_n: i32 = k_weights.x_n;
154            let t: f32 = k_weights.w;
155
156            let table1 = &self.lut[(w * grid_size3 * 3) as usize..];
157            let table2 = &self.lut[(w_n * grid_size3 * 3) as usize..];
158
159            let r1 = interpolator.inter3(
160                table1,
161                &self.weights[c.as_()],
162                &self.weights[m.as_()],
163                &self.weights[y.as_()],
164            );
165            let r2 = interpolator.inter3(
166                table2,
167                &self.weights[c.as_()],
168                &self.weights[m.as_()],
169                &self.weights[y.as_()],
170            );
171            let r = Interpolation::interpolate(r1, r2, t, value_scale);
172            dst[cn.r_i()] = r.v[0].as_();
173            dst[cn.g_i()] = r.v[1].as_();
174            dst[cn.b_i()] = r.v[2].as_();
175            if channels == 4 {
176                dst[cn.a_i()] = max_value;
177            }
178        }
179    }
180}
181
182#[allow(unused)]
183impl<
184    T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible,
185    U: AsPrimitive<usize>,
186    const LAYOUT: u8,
187    const GRID_SIZE: usize,
188    const BIT_DEPTH: usize,
189    const BINS: usize,
190    const BARYCENTRIC_BINS: usize,
191> TransformExecutor<T>
192    for TransformLut4To3<T, U, LAYOUT, GRID_SIZE, BIT_DEPTH, BINS, BARYCENTRIC_BINS>
193where
194    f32: AsPrimitive<T>,
195    u32: AsPrimitive<T>,
196    (): LutBarycentricReduction<T, U>,
197{
198    fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
199        let cn = Layout::from(LAYOUT);
200        let channels = cn.channels();
201        if src.len() % 4 != 0 {
202            return Err(CmsError::LaneMultipleOfChannels);
203        }
204        if dst.len() % channels != 0 {
205            return Err(CmsError::LaneMultipleOfChannels);
206        }
207        let src_chunks = src.len() / 4;
208        let dst_chunks = dst.len() / channels;
209        if src_chunks != dst_chunks {
210            return Err(CmsError::LaneSizeMismatch);
211        }
212
213        if self.color_space == DataColorSpace::Lab
214            || (self.is_linear && self.color_space == DataColorSpace::Rgb)
215            || self.color_space == DataColorSpace::Xyz
216        {
217            if T::FINITE {
218                self.transform_chunk::<DefaultVector3fLerp>(
219                    src,
220                    dst,
221                    Box::new(Trilinear::<GRID_SIZE> {}),
222                );
223            } else {
224                self.transform_chunk::<NonFiniteVector3fLerp>(
225                    src,
226                    dst,
227                    Box::new(Trilinear::<GRID_SIZE> {}),
228                );
229            }
230        } else {
231            match self.interpolation_method {
232                #[cfg(feature = "options")]
233                InterpolationMethod::Tetrahedral => {
234                    if T::FINITE {
235                        self.transform_chunk::<DefaultVector3fLerp>(
236                            src,
237                            dst,
238                            Box::new(Tetrahedral::<GRID_SIZE> {}),
239                        );
240                    } else {
241                        self.transform_chunk::<NonFiniteVector3fLerp>(
242                            src,
243                            dst,
244                            Box::new(Tetrahedral::<GRID_SIZE> {}),
245                        );
246                    }
247                }
248                #[cfg(feature = "options")]
249                InterpolationMethod::Pyramid => {
250                    if T::FINITE {
251                        self.transform_chunk::<DefaultVector3fLerp>(
252                            src,
253                            dst,
254                            Box::new(Pyramidal::<GRID_SIZE> {}),
255                        );
256                    } else {
257                        self.transform_chunk::<NonFiniteVector3fLerp>(
258                            src,
259                            dst,
260                            Box::new(Pyramidal::<GRID_SIZE> {}),
261                        );
262                    }
263                }
264                #[cfg(feature = "options")]
265                InterpolationMethod::Prism => {
266                    if T::FINITE {
267                        self.transform_chunk::<DefaultVector3fLerp>(
268                            src,
269                            dst,
270                            Box::new(Prismatic::<GRID_SIZE> {}),
271                        );
272                    } else {
273                        self.transform_chunk::<NonFiniteVector3fLerp>(
274                            src,
275                            dst,
276                            Box::new(Prismatic::<GRID_SIZE> {}),
277                        );
278                    }
279                }
280                InterpolationMethod::Linear => {
281                    if T::FINITE {
282                        self.transform_chunk::<DefaultVector3fLerp>(
283                            src,
284                            dst,
285                            Box::new(Trilinear::<GRID_SIZE> {}),
286                        );
287                    } else {
288                        self.transform_chunk::<NonFiniteVector3fLerp>(
289                            src,
290                            dst,
291                            Box::new(Trilinear::<GRID_SIZE> {}),
292                        );
293                    }
294                }
295            }
296        }
297
298        Ok(())
299    }
300}
301
302#[allow(dead_code)]
303pub(crate) struct DefaultLut4x3Factory {}
304
305#[allow(dead_code)]
306impl Lut4x3Factory for DefaultLut4x3Factory {
307    fn make_transform_4x3<
308        T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible + 'static + Send + Sync,
309        const LAYOUT: u8,
310        const GRID_SIZE: usize,
311        const BIT_DEPTH: usize,
312    >(
313        lut: Vec<f32>,
314        options: TransformOptions,
315        color_space: DataColorSpace,
316        is_linear: bool,
317    ) -> Arc<dyn TransformExecutor<T> + Sync + Send>
318    where
319        f32: AsPrimitive<T>,
320        u32: AsPrimitive<T>,
321        (): LutBarycentricReduction<T, u8>,
322        (): LutBarycentricReduction<T, u16>,
323    {
324        match options.barycentric_weight_scale {
325            BarycentricWeightScale::Low => {
326                Arc::new(
327                    TransformLut4To3::<T, u8, LAYOUT, GRID_SIZE, BIT_DEPTH, 256, 256> {
328                        lut,
329                        _phantom: PhantomData,
330                        _phantom1: PhantomData,
331                        interpolation_method: options.interpolation_method,
332                        weights: BarycentricWeight::<f32>::create_ranged_256::<GRID_SIZE>(),
333                        color_space,
334                        is_linear,
335                    },
336                )
337            }
338            #[cfg(feature = "options")]
339            BarycentricWeightScale::High => {
340                Arc::new(
341                    TransformLut4To3::<T, u16, LAYOUT, GRID_SIZE, BIT_DEPTH, 65536, 65536> {
342                        lut,
343                        _phantom: PhantomData,
344                        _phantom1: PhantomData,
345                        interpolation_method: options.interpolation_method,
346                        weights: BarycentricWeight::<f32>::create_binned::<GRID_SIZE, 65536>(),
347                        color_space,
348                        is_linear,
349                    },
350                )
351            }
352        }
353    }
354}