moxcms/conversions/
prelude_lut_xyz_rgb.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 4/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::conversions::lut3x4::create_lut3_samples;
30use crate::err::try_vec;
31use crate::mlaf::mlaf;
32use crate::trc::ToneCurveEvaluator;
33use crate::{
34    CmsError, ColorProfile, GammaLutInterpolate, InPlaceStage, Matrix3f, PointeeSizeExpressible,
35    RenderingIntent, Rgb, TransformOptions, filmlike_clip,
36};
37use num_traits::AsPrimitive;
38use std::marker::PhantomData;
39
40pub(crate) struct XyzToRgbStage<T: Clone> {
41    pub(crate) r_gamma: Box<[T; 65536]>,
42    pub(crate) g_gamma: Box<[T; 65536]>,
43    pub(crate) b_gamma: Box<[T; 65536]>,
44    pub(crate) matrices: Vec<Matrix3f>,
45    pub(crate) intent: RenderingIntent,
46    pub(crate) bit_depth: usize,
47    pub(crate) gamma_lut: usize,
48}
49
50impl<T: Clone + AsPrimitive<f32>> InPlaceStage for XyzToRgbStage<T> {
51    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
52        assert!(self.bit_depth > 0);
53        if !self.matrices.is_empty() {
54            let m = self.matrices[0];
55            for dst in dst.chunks_exact_mut(3) {
56                let x = dst[0];
57                let y = dst[1];
58                let z = dst[2];
59                dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
60                dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
61                dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
62            }
63        }
64
65        for m in self.matrices.iter().skip(1) {
66            for dst in dst.chunks_exact_mut(3) {
67                let x = dst[0];
68                let y = dst[1];
69                let z = dst[2];
70                dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
71                dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
72                dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
73            }
74        }
75
76        let max_colors = (1 << self.bit_depth) - 1;
77        let color_scale = 1f32 / max_colors as f32;
78        let lut_cap = (self.gamma_lut - 1) as f32;
79
80        if self.intent != RenderingIntent::AbsoluteColorimetric {
81            for dst in dst.chunks_exact_mut(3) {
82                let mut rgb = Rgb::new(dst[0], dst[1], dst[2]);
83                if rgb.is_out_of_gamut() {
84                    rgb = filmlike_clip(rgb);
85                }
86                let r = mlaf(0.5f32, rgb.r, lut_cap).min(lut_cap).max(0f32) as u16;
87                let g = mlaf(0.5f32, rgb.g, lut_cap).min(lut_cap).max(0f32) as u16;
88                let b = mlaf(0.5f32, rgb.b, lut_cap).min(lut_cap).max(0f32) as u16;
89
90                dst[0] = self.r_gamma[r as usize].as_() * color_scale;
91                dst[1] = self.g_gamma[g as usize].as_() * color_scale;
92                dst[2] = self.b_gamma[b as usize].as_() * color_scale;
93            }
94        } else {
95            for dst in dst.chunks_exact_mut(3) {
96                let rgb = Rgb::new(dst[0], dst[1], dst[2]);
97                let r = mlaf(0.5f32, rgb.r, lut_cap).min(lut_cap).max(0f32) as u16;
98                let g = mlaf(0.5f32, rgb.g, lut_cap).min(lut_cap).max(0f32) as u16;
99                let b = mlaf(0.5f32, rgb.b, lut_cap).min(lut_cap).max(0f32) as u16;
100
101                dst[0] = self.r_gamma[r as usize].as_() * color_scale;
102                dst[1] = self.g_gamma[g as usize].as_() * color_scale;
103                dst[2] = self.b_gamma[b as usize].as_() * color_scale;
104            }
105        }
106
107        Ok(())
108    }
109}
110
111pub(crate) struct XyzToRgbStageExtended<T: Clone> {
112    pub(crate) gamma_evaluator: Box<dyn ToneCurveEvaluator>,
113    pub(crate) matrices: Vec<Matrix3f>,
114    pub(crate) phantom_data: PhantomData<T>,
115}
116
117impl<T: Clone + AsPrimitive<f32>> InPlaceStage for XyzToRgbStageExtended<T> {
118    fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
119        if !self.matrices.is_empty() {
120            let m = self.matrices[0];
121            for dst in dst.chunks_exact_mut(3) {
122                let x = dst[0];
123                let y = dst[1];
124                let z = dst[2];
125                dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
126                dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
127                dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
128            }
129        }
130
131        for m in self.matrices.iter().skip(1) {
132            for dst in dst.chunks_exact_mut(3) {
133                let x = dst[0];
134                let y = dst[1];
135                let z = dst[2];
136                dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
137                dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
138                dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
139            }
140        }
141
142        for dst in dst.chunks_exact_mut(3) {
143            let mut rgb = Rgb::new(dst[0], dst[1], dst[2]);
144            rgb = self.gamma_evaluator.evaluate_tristimulus(rgb);
145            dst[0] = rgb.r.as_();
146            dst[1] = rgb.g.as_();
147            dst[2] = rgb.b.as_();
148        }
149
150        Ok(())
151    }
152}
153
154struct RgbLinearizationStage<T: Clone, const LINEAR_CAP: usize, const SAMPLES: usize> {
155    r_lin: Box<[f32; LINEAR_CAP]>,
156    g_lin: Box<[f32; LINEAR_CAP]>,
157    b_lin: Box<[f32; LINEAR_CAP]>,
158    _phantom: PhantomData<T>,
159    bit_depth: usize,
160}
161
162impl<
163    T: Clone + AsPrimitive<usize> + PointeeSizeExpressible,
164    const LINEAR_CAP: usize,
165    const SAMPLES: usize,
166> RgbLinearizationStage<T, LINEAR_CAP, SAMPLES>
167{
168    fn transform(&self, src: &[T], dst: &mut [f32]) -> Result<(), CmsError> {
169        if src.len() % 3 != 0 {
170            return Err(CmsError::LaneMultipleOfChannels);
171        }
172        if dst.len() % 3 != 0 {
173            return Err(CmsError::LaneMultipleOfChannels);
174        }
175
176        let scale = if T::FINITE {
177            ((1 << self.bit_depth) - 1) as f32 / (SAMPLES as f32 - 1f32)
178        } else {
179            (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32 / (SAMPLES as f32 - 1f32)
180        };
181
182        let capped_value = if T::FINITE {
183            (1 << self.bit_depth) - 1
184        } else {
185            T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
186        };
187
188        for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(3)) {
189            let j_r = src[0].as_() as f32 * scale;
190            let j_g = src[1].as_() as f32 * scale;
191            let j_b = src[2].as_() as f32 * scale;
192            dst[0] = self.r_lin[(j_r.round().max(0.0).min(capped_value as f32) as u16) as usize];
193            dst[1] = self.g_lin[(j_g.round().max(0.0).min(capped_value as f32) as u16) as usize];
194            dst[2] = self.b_lin[(j_b.round().max(0.0).min(capped_value as f32) as u16) as usize];
195        }
196        Ok(())
197    }
198}
199
200pub(crate) fn create_rgb_lin_lut<
201    T: Copy + Default + AsPrimitive<f32> + Send + Sync + AsPrimitive<usize> + PointeeSizeExpressible,
202    const BIT_DEPTH: usize,
203    const LINEAR_CAP: usize,
204    const GRID_SIZE: usize,
205>(
206    source: &ColorProfile,
207    opts: TransformOptions,
208) -> Result<Vec<f32>, CmsError>
209where
210    u32: AsPrimitive<T>,
211    f32: AsPrimitive<T>,
212{
213    let lut_origins = create_lut3_samples::<T, GRID_SIZE>();
214
215    let lin_r =
216        source.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(opts.allow_use_cicp_transfer)?;
217    let lin_g =
218        source.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(opts.allow_use_cicp_transfer)?;
219    let lin_b =
220        source.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(opts.allow_use_cicp_transfer)?;
221
222    let lin_stage = RgbLinearizationStage::<T, LINEAR_CAP, GRID_SIZE> {
223        r_lin: lin_r,
224        g_lin: lin_g,
225        b_lin: lin_b,
226        _phantom: PhantomData,
227        bit_depth: BIT_DEPTH,
228    };
229
230    let mut lut = try_vec![0f32; lut_origins.len()];
231    lin_stage.transform(&lut_origins, &mut lut)?;
232
233    let xyz_to_rgb = source.rgb_to_xyz_matrix();
234
235    let matrices = vec![
236        xyz_to_rgb.to_f32(),
237        Matrix3f {
238            v: [
239                [32768.0 / 65535.0, 0.0, 0.0],
240                [0.0, 32768.0 / 65535.0, 0.0],
241                [0.0, 0.0, 32768.0 / 65535.0],
242            ],
243        },
244    ];
245
246    let matrix_stage = crate::conversions::lut_transforms::MatrixStage { matrices };
247    matrix_stage.transform(&mut lut)?;
248    Ok(lut)
249}
250
251pub(crate) fn prepare_inverse_lut_rgb_xyz<
252    T: Copy
253        + Default
254        + AsPrimitive<f32>
255        + Send
256        + Sync
257        + AsPrimitive<usize>
258        + PointeeSizeExpressible
259        + GammaLutInterpolate,
260    const BIT_DEPTH: usize,
261    const GAMMA_LUT: usize,
262>(
263    dest: &ColorProfile,
264    lut: &mut [f32],
265    options: TransformOptions,
266) -> Result<(), CmsError>
267where
268    f32: AsPrimitive<T>,
269    u32: AsPrimitive<T>,
270{
271    if !T::FINITE {
272        if let Some(extended_gamma) = dest.try_extended_gamma_evaluator() {
273            let xyz_to_rgb = dest.rgb_to_xyz_matrix().inverse();
274
275            let mut matrices = vec![Matrix3f {
276                v: [
277                    [65535.0 / 32768.0, 0.0, 0.0],
278                    [0.0, 65535.0 / 32768.0, 0.0],
279                    [0.0, 0.0, 65535.0 / 32768.0],
280                ],
281            }];
282
283            matrices.push(xyz_to_rgb.to_f32());
284            let xyz_to_rgb_stage = XyzToRgbStageExtended::<T> {
285                gamma_evaluator: extended_gamma,
286                matrices,
287                phantom_data: PhantomData,
288            };
289            xyz_to_rgb_stage.transform(lut)?;
290            return Ok(());
291        }
292    }
293    let gamma_map_r = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
294        &dest.red_trc,
295        options.allow_use_cicp_transfer,
296    )?;
297    let gamma_map_g = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
298        &dest.green_trc,
299        options.allow_use_cicp_transfer,
300    )?;
301    let gamma_map_b = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
302        &dest.blue_trc,
303        options.allow_use_cicp_transfer,
304    )?;
305
306    let xyz_to_rgb = dest.rgb_to_xyz_matrix().inverse();
307
308    let mut matrices = vec![Matrix3f {
309        v: [
310            [65535.0 / 32768.0, 0.0, 0.0],
311            [0.0, 65535.0 / 32768.0, 0.0],
312            [0.0, 0.0, 65535.0 / 32768.0],
313        ],
314    }];
315
316    matrices.push(xyz_to_rgb.to_f32());
317    let xyz_to_rgb_stage = XyzToRgbStage::<T> {
318        r_gamma: gamma_map_r,
319        g_gamma: gamma_map_g,
320        b_gamma: gamma_map_b,
321        matrices,
322        intent: options.rendering_intent,
323        gamma_lut: GAMMA_LUT,
324        bit_depth: BIT_DEPTH,
325    };
326    xyz_to_rgb_stage.transform(lut)?;
327    Ok(())
328}