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    const BIT_DEPTH: usize,
56> {
57    pub(crate) profile: TransformShaperRgbFloat<T, LINEAR_CAP>,
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    const BIT_DEPTH: usize,
69>(
70    src_layout: Layout,
71    dst_layout: Layout,
72    profile: TransformShaperRgbFloat<T, LINEAR_CAP>,
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            BIT_DEPTH,
85        > {
86            profile,
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            BIT_DEPTH,
95        > {
96            profile,
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            BIT_DEPTH,
105        > {
106            profile,
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            BIT_DEPTH,
115        > {
116            profile,
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    const BIT_DEPTH: usize,
125>(
126    src_layout: Layout,
127    dst_layout: Layout,
128    profile: TransformShaperFloatInOut<T>,
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: 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: 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: 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: 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    const BIT_DEPTH: usize,
180> TransformExecutor<T>
181    for TransformShaperFloatScalar<T, SRC_LAYOUT, DST_LAYOUT, LINEAR_CAP, BIT_DEPTH>
182where
183    u32: AsPrimitive<T>,
184    f32: AsPrimitive<T>,
185{
186    fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
187        use crate::mlaf::mlaf;
188        let src_cn = Layout::from(SRC_LAYOUT);
189        let dst_cn = Layout::from(DST_LAYOUT);
190        let src_channels = src_cn.channels();
191        let dst_channels = dst_cn.channels();
192
193        if src.len() / src_channels != dst.len() / dst_channels {
194            return Err(CmsError::LaneSizeMismatch);
195        }
196        if src.len() % src_channels != 0 {
197            return Err(CmsError::LaneMultipleOfChannels);
198        }
199        if dst.len() % dst_channels != 0 {
200            return Err(CmsError::LaneMultipleOfChannels);
201        }
202
203        let transform = self.profile.adaptation_matrix;
204        let max_colors: T = ((1 << BIT_DEPTH) - 1).as_();
205
206        for (src, dst) in src
207            .chunks_exact(src_channels)
208            .zip(dst.chunks_exact_mut(dst_channels))
209        {
210            let r = self.profile.r_linear[src[src_cn.r_i()]._as_usize()];
211            let g = self.profile.g_linear[src[src_cn.g_i()]._as_usize()];
212            let b = self.profile.b_linear[src[src_cn.b_i()]._as_usize()];
213            let a = if src_channels == 4 {
214                src[src_cn.a_i()]
215            } else {
216                max_colors
217            };
218
219            let new_r = mlaf(
220                mlaf(r * transform.v[0][0], g, transform.v[0][1]),
221                b,
222                transform.v[0][2],
223            );
224
225            let new_g = mlaf(
226                mlaf(r * transform.v[1][0], g, transform.v[1][1]),
227                b,
228                transform.v[1][2],
229            );
230
231            let new_b = mlaf(
232                mlaf(r * transform.v[2][0], g, transform.v[2][1]),
233                b,
234                transform.v[2][2],
235            );
236
237            let mut rgb = Rgb::new(new_r, new_g, new_b);
238            rgb = self.profile.gamma_evaluator.evaluate_tristimulus(rgb);
239
240            dst[dst_cn.r_i()] = rgb.r.as_();
241            dst[dst_cn.g_i()] = rgb.g.as_();
242            dst[dst_cn.b_i()] = rgb.b.as_();
243            if dst_channels == 4 {
244                dst[dst_cn.a_i()] = a;
245            }
246        }
247
248        Ok(())
249    }
250}
251
252impl<
253    T: Clone + PointeeSizeExpressible + Copy + Default + 'static + AsPrimitive<f32>,
254    const SRC_LAYOUT: u8,
255    const DST_LAYOUT: u8,
256> TransformExecutor<T> for TransformShaperRgbFloatInOut<T, SRC_LAYOUT, DST_LAYOUT>
257where
258    u32: AsPrimitive<T>,
259    f32: AsPrimitive<T>,
260{
261    fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
262        use crate::mlaf::mlaf;
263        let src_cn = Layout::from(SRC_LAYOUT);
264        let dst_cn = Layout::from(DST_LAYOUT);
265        let src_channels = src_cn.channels();
266        let dst_channels = dst_cn.channels();
267
268        if src.len() / src_channels != dst.len() / dst_channels {
269            return Err(CmsError::LaneSizeMismatch);
270        }
271        if src.len() % src_channels != 0 {
272            return Err(CmsError::LaneMultipleOfChannels);
273        }
274        if dst.len() % dst_channels != 0 {
275            return Err(CmsError::LaneMultipleOfChannels);
276        }
277
278        let transform = self.profile.adaptation_matrix;
279        let max_colors: T = ((1 << self.bit_depth) - 1).as_();
280
281        for (src, dst) in src
282            .chunks_exact(src_channels)
283            .zip(dst.chunks_exact_mut(dst_channels))
284        {
285            let mut src_rgb = Rgb::new(
286                src[src_cn.r_i()].as_(),
287                src[src_cn.g_i()].as_(),
288                src[src_cn.b_i()].as_(),
289            );
290            src_rgb = self.profile.linear_evaluator.evaluate_tristimulus(src_rgb);
291            let r = src_rgb.r;
292            let g = src_rgb.g;
293            let b = src_rgb.b;
294            let a = if src_channels == 4 {
295                src[src_cn.a_i()]
296            } else {
297                max_colors
298            };
299
300            let new_r = mlaf(
301                mlaf(r * transform.v[0][0], g, transform.v[0][1]),
302                b,
303                transform.v[0][2],
304            );
305
306            let new_g = mlaf(
307                mlaf(r * transform.v[1][0], g, transform.v[1][1]),
308                b,
309                transform.v[1][2],
310            );
311
312            let new_b = mlaf(
313                mlaf(r * transform.v[2][0], g, transform.v[2][1]),
314                b,
315                transform.v[2][2],
316            );
317
318            let mut rgb = Rgb::new(new_r, new_g, new_b);
319            rgb = self.profile.gamma_evaluator.evaluate_tristimulus(rgb);
320
321            dst[dst_cn.r_i()] = rgb.r.as_();
322            dst[dst_cn.g_i()] = rgb.g.as_();
323            dst[dst_cn.b_i()] = rgb.b.as_();
324
325            if dst_channels == 4 {
326                dst[dst_cn.a_i()] = a;
327            }
328        }
329
330        Ok(())
331    }
332}