moxcms/conversions/
rgbxyz_float.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 2/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::trc::ToneCurveEvaluator;
30use crate::{CmsError, Layout, Matrix3f, PointeeSizeExpressible, Rgb, TransformExecutor};
31use num_traits::AsPrimitive;
32use std::marker::PhantomData;
33
34pub(crate) struct TransformShaperRgbFloat<T: Clone, const BUCKET: usize> {
35    pub(crate) r_linear: Box<[f32; BUCKET]>,
36    pub(crate) g_linear: Box<[f32; BUCKET]>,
37    pub(crate) b_linear: Box<[f32; BUCKET]>,
38    pub(crate) gamma_evaluator: Box<dyn ToneCurveEvaluator + Send + Sync>,
39    pub(crate) adaptation_matrix: Matrix3f,
40    pub(crate) phantom_data: PhantomData<T>,
41}
42
43pub(crate) struct TransformShaperFloatInOut<T: Clone> {
44    pub(crate) linear_evaluator: Box<dyn ToneCurveEvaluator + Send + Sync>,
45    pub(crate) gamma_evaluator: Box<dyn ToneCurveEvaluator + Send + Sync>,
46    pub(crate) adaptation_matrix: Matrix3f,
47    pub(crate) phantom_data: PhantomData<T>,
48}
49
50struct TransformShaperFloatScalar<
51    T: Clone,
52    const SRC_LAYOUT: u8,
53    const DST_LAYOUT: u8,
54    const LINEAR_CAP: usize,
55> {
56    pub(crate) profile: TransformShaperRgbFloat<T, LINEAR_CAP>,
57    pub(crate) bit_depth: usize,
58}
59
60struct TransformShaperRgbFloatInOut<T: Clone, const SRC_LAYOUT: u8, const DST_LAYOUT: u8> {
61    pub(crate) profile: TransformShaperFloatInOut<T>,
62    pub(crate) bit_depth: usize,
63}
64
65pub(crate) fn make_rgb_xyz_rgb_transform_float<
66    T: Clone + Send + Sync + PointeeSizeExpressible + 'static + Copy + Default,
67    const LINEAR_CAP: usize,
68>(
69    src_layout: Layout,
70    dst_layout: Layout,
71    profile: TransformShaperRgbFloat<T, LINEAR_CAP>,
72    bit_depth: usize,
73) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
74where
75    u32: AsPrimitive<T>,
76    f32: AsPrimitive<T>,
77{
78    if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
79        return Ok(Box::new(TransformShaperFloatScalar::<
80            T,
81            { Layout::Rgba as u8 },
82            { Layout::Rgba as u8 },
83            LINEAR_CAP,
84        > {
85            profile,
86            bit_depth,
87        }));
88    } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
89        return Ok(Box::new(TransformShaperFloatScalar::<
90            T,
91            { Layout::Rgb as u8 },
92            { Layout::Rgba as u8 },
93            LINEAR_CAP,
94        > {
95            profile,
96            bit_depth,
97        }));
98    } else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
99        return Ok(Box::new(TransformShaperFloatScalar::<
100            T,
101            { Layout::Rgba as u8 },
102            { Layout::Rgb as u8 },
103            LINEAR_CAP,
104        > {
105            profile,
106            bit_depth,
107        }));
108    } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
109        return Ok(Box::new(TransformShaperFloatScalar::<
110            T,
111            { Layout::Rgb as u8 },
112            { Layout::Rgb as u8 },
113            LINEAR_CAP,
114        > {
115            profile,
116            bit_depth,
117        }));
118    }
119    Err(CmsError::UnsupportedProfileConnection)
120}
121
122pub(crate) fn make_rgb_xyz_rgb_transform_float_in_out<
123    T: Clone + Send + Sync + PointeeSizeExpressible + 'static + Copy + Default + AsPrimitive<f32>,
124>(
125    src_layout: Layout,
126    dst_layout: Layout,
127    profile: TransformShaperFloatInOut<T>,
128    bit_depth: usize,
129) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
130where
131    u32: AsPrimitive<T>,
132    f32: AsPrimitive<T>,
133{
134    if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
135        return Ok(Box::new(TransformShaperRgbFloatInOut::<
136            T,
137            { Layout::Rgba as u8 },
138            { Layout::Rgba as u8 },
139        > {
140            profile,
141            bit_depth,
142        }));
143    } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
144        return Ok(Box::new(TransformShaperRgbFloatInOut::<
145            T,
146            { Layout::Rgb as u8 },
147            { Layout::Rgba as u8 },
148        > {
149            profile,
150            bit_depth,
151        }));
152    } else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
153        return Ok(Box::new(TransformShaperRgbFloatInOut::<
154            T,
155            { Layout::Rgba as u8 },
156            { Layout::Rgb as u8 },
157        > {
158            profile,
159            bit_depth,
160        }));
161    } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
162        return Ok(Box::new(TransformShaperRgbFloatInOut::<
163            T,
164            { Layout::Rgb as u8 },
165            { Layout::Rgb as u8 },
166        > {
167            profile,
168            bit_depth,
169        }));
170    }
171    Err(CmsError::UnsupportedProfileConnection)
172}
173
174impl<
175    T: Clone + PointeeSizeExpressible + Copy + Default + 'static,
176    const SRC_LAYOUT: u8,
177    const DST_LAYOUT: u8,
178    const LINEAR_CAP: usize,
179> TransformExecutor<T> for TransformShaperFloatScalar<T, SRC_LAYOUT, DST_LAYOUT, LINEAR_CAP>
180where
181    u32: AsPrimitive<T>,
182    f32: AsPrimitive<T>,
183{
184    fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
185        use crate::mlaf::mlaf;
186        let src_cn = Layout::from(SRC_LAYOUT);
187        let dst_cn = Layout::from(DST_LAYOUT);
188        let src_channels = src_cn.channels();
189        let dst_channels = dst_cn.channels();
190
191        if src.len() / src_channels != dst.len() / dst_channels {
192            return Err(CmsError::LaneSizeMismatch);
193        }
194        if src.len() % src_channels != 0 {
195            return Err(CmsError::LaneMultipleOfChannels);
196        }
197        if dst.len() % dst_channels != 0 {
198            return Err(CmsError::LaneMultipleOfChannels);
199        }
200
201        let transform = self.profile.adaptation_matrix;
202        let max_colors: T = ((1 << self.bit_depth) - 1).as_();
203
204        for (src, dst) in src
205            .chunks_exact(src_channels)
206            .zip(dst.chunks_exact_mut(dst_channels))
207        {
208            let r = self.profile.r_linear[src[src_cn.r_i()]._as_usize()];
209            let g = self.profile.g_linear[src[src_cn.g_i()]._as_usize()];
210            let b = self.profile.b_linear[src[src_cn.b_i()]._as_usize()];
211            let a = if src_channels == 4 {
212                src[src_cn.a_i()]
213            } else {
214                max_colors
215            };
216
217            let new_r = mlaf(
218                mlaf(r * transform.v[0][0], g, transform.v[0][1]),
219                b,
220                transform.v[0][2],
221            );
222
223            let new_g = mlaf(
224                mlaf(r * transform.v[1][0], g, transform.v[1][1]),
225                b,
226                transform.v[1][2],
227            );
228
229            let new_b = mlaf(
230                mlaf(r * transform.v[2][0], g, transform.v[2][1]),
231                b,
232                transform.v[2][2],
233            );
234
235            let mut rgb = Rgb::new(new_r, new_g, new_b);
236            rgb = self.profile.gamma_evaluator.evaluate_tristimulus(rgb);
237
238            dst[dst_cn.r_i()] = rgb.r.as_();
239            dst[dst_cn.g_i()] = rgb.g.as_();
240            dst[dst_cn.b_i()] = rgb.b.as_();
241            if dst_channels == 4 {
242                dst[dst_cn.a_i()] = a;
243            }
244        }
245
246        Ok(())
247    }
248}
249
250impl<
251    T: Clone + PointeeSizeExpressible + Copy + Default + 'static + AsPrimitive<f32>,
252    const SRC_LAYOUT: u8,
253    const DST_LAYOUT: u8,
254> TransformExecutor<T> for TransformShaperRgbFloatInOut<T, SRC_LAYOUT, DST_LAYOUT>
255where
256    u32: AsPrimitive<T>,
257    f32: AsPrimitive<T>,
258{
259    fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
260        use crate::mlaf::mlaf;
261        let src_cn = Layout::from(SRC_LAYOUT);
262        let dst_cn = Layout::from(DST_LAYOUT);
263        let src_channels = src_cn.channels();
264        let dst_channels = dst_cn.channels();
265
266        if src.len() / src_channels != dst.len() / dst_channels {
267            return Err(CmsError::LaneSizeMismatch);
268        }
269        if src.len() % src_channels != 0 {
270            return Err(CmsError::LaneMultipleOfChannels);
271        }
272        if dst.len() % dst_channels != 0 {
273            return Err(CmsError::LaneMultipleOfChannels);
274        }
275
276        let transform = self.profile.adaptation_matrix;
277        let max_colors: T = ((1 << self.bit_depth) - 1).as_();
278
279        for (src, dst) in src
280            .chunks_exact(src_channels)
281            .zip(dst.chunks_exact_mut(dst_channels))
282        {
283            let mut src_rgb = Rgb::new(
284                src[src_cn.r_i()].as_(),
285                src[src_cn.g_i()].as_(),
286                src[src_cn.b_i()].as_(),
287            );
288            src_rgb = self.profile.linear_evaluator.evaluate_tristimulus(src_rgb);
289            let r = src_rgb.r;
290            let g = src_rgb.g;
291            let b = src_rgb.b;
292            let a = if src_channels == 4 {
293                src[src_cn.a_i()]
294            } else {
295                max_colors
296            };
297
298            let new_r = mlaf(
299                mlaf(r * transform.v[0][0], g, transform.v[0][1]),
300                b,
301                transform.v[0][2],
302            );
303
304            let new_g = mlaf(
305                mlaf(r * transform.v[1][0], g, transform.v[1][1]),
306                b,
307                transform.v[1][2],
308            );
309
310            let new_b = mlaf(
311                mlaf(r * transform.v[2][0], g, transform.v[2][1]),
312                b,
313                transform.v[2][2],
314            );
315
316            let mut rgb = Rgb::new(new_r, new_g, new_b);
317            rgb = self.profile.gamma_evaluator.evaluate_tristimulus(rgb);
318
319            dst[dst_cn.r_i()] = rgb.r.as_();
320            dst[dst_cn.g_i()] = rgb.g.as_();
321            dst[dst_cn.b_i()] = rgb.b.as_();
322
323            if dst_channels == 4 {
324                dst[dst_cn.a_i()] = a;
325            }
326        }
327
328        Ok(())
329    }
330}