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