moxcms/conversions/katana/
xyz_rgb.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 6/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::katana::pcs_stages::KatanaMatrixStage;
30use crate::conversions::katana::{
31    KatanaDefaultIntermediate, KatanaFinalStage, KatanaIntermediateStage,
32};
33use crate::mlaf::mlaf;
34use crate::{
35    CmsError, ColorProfile, GammaLutInterpolate, Layout, Matrix3f, PointeeSizeExpressible,
36    RenderingIntent, Rgb, TransformOptions, filmlike_clip,
37};
38use num_traits::AsPrimitive;
39
40pub(crate) struct KatanaXyzToRgbStage<T: Clone, const LAYOUT: u8> {
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) intent: RenderingIntent,
45    pub(crate) bit_depth: usize,
46    pub(crate) gamma_lut: usize,
47}
48
49impl<T: Clone + AsPrimitive<f32> + PointeeSizeExpressible, const LAYOUT: u8>
50    KatanaFinalStage<f32, T> for KatanaXyzToRgbStage<T, LAYOUT>
51where
52    u32: AsPrimitive<T>,
53    f32: AsPrimitive<T>,
54{
55    fn to_output(&self, src: &mut [f32], dst: &mut [T]) -> Result<(), CmsError> {
56        let dst_cn = Layout::from(LAYOUT);
57        let dst_channels = dst_cn.channels();
58        if src.len() % 3 != 0 {
59            return Err(CmsError::LaneMultipleOfChannels);
60        }
61        if dst.len() % dst_channels != 0 {
62            return Err(CmsError::LaneMultipleOfChannels);
63        }
64        let src_chunks = src.len() / 3;
65        let dst_chunks = dst.len() / dst_channels;
66        if src_chunks != dst_chunks {
67            return Err(CmsError::LaneSizeMismatch);
68        }
69
70        let max_colors: T = (if T::FINITE {
71            ((1u32 << self.bit_depth) - 1) as f32
72        } else {
73            1.0
74        })
75        .as_();
76        let lut_cap = (self.gamma_lut - 1) as f32;
77
78        if self.intent != RenderingIntent::AbsoluteColorimetric {
79            for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(dst_channels)) {
80                let mut rgb = Rgb::new(src[0], src[1], src[2]);
81                if rgb.is_out_of_gamut() {
82                    rgb = filmlike_clip(rgb);
83                }
84                let r = mlaf(0.5, rgb.r, lut_cap).min(lut_cap).max(0.) as u16;
85                let g = mlaf(0.5, rgb.g, lut_cap).min(lut_cap).max(0.) as u16;
86                let b = mlaf(0.5, rgb.b, lut_cap).min(lut_cap).max(0.) as u16;
87
88                dst[0] = self.r_gamma[r as usize];
89                dst[1] = self.g_gamma[g as usize];
90                dst[2] = self.b_gamma[b as usize];
91                if dst_cn == Layout::Rgba {
92                    dst[3] = max_colors;
93                }
94            }
95        } else {
96            for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(dst_channels)) {
97                let rgb = Rgb::new(src[0], src[1], src[2]);
98                let r = mlaf(0.5, rgb.r, lut_cap).min(lut_cap).max(0.) as u16;
99                let g = mlaf(0.5, rgb.g, lut_cap).min(lut_cap).max(0.) as u16;
100                let b = mlaf(0.5, rgb.b, lut_cap).min(lut_cap).max(0.) as u16;
101
102                dst[0] = self.r_gamma[r as usize];
103                dst[1] = self.g_gamma[g as usize];
104                dst[2] = self.b_gamma[b as usize];
105                if dst_cn == Layout::Rgba {
106                    dst[3] = max_colors;
107                }
108            }
109        }
110
111        Ok(())
112    }
113}
114
115pub(crate) struct KatanaXyzRgbState<T> {
116    pub(crate) stages: Vec<Box<dyn KatanaIntermediateStage<f32> + Send + Sync>>,
117    pub(crate) final_stage: Box<dyn KatanaFinalStage<f32, T> + Send + Sync>,
118}
119
120pub(crate) fn katana_prepare_inverse_lut_rgb_xyz<
121    T: Copy
122        + Default
123        + AsPrimitive<f32>
124        + Send
125        + Sync
126        + AsPrimitive<usize>
127        + PointeeSizeExpressible
128        + GammaLutInterpolate,
129    const BIT_DEPTH: usize,
130    const GAMMA_LUT: usize,
131>(
132    dest: &ColorProfile,
133    dest_layout: Layout,
134    options: TransformOptions,
135) -> Result<KatanaXyzRgbState<T>, CmsError>
136where
137    f32: AsPrimitive<T>,
138    u32: AsPrimitive<T>,
139{
140    // if !T::FINITE {
141    // if let Some(extended_gamma) = dest.try_extended_gamma_evaluator() {
142    //     let xyz_to_rgb = dest.rgb_to_xyz_matrix().inverse();
143    //
144    //     let mut matrices = vec![Matrix3f {
145    //         v: [
146    //             [65535.0 / 32768.0, 0.0, 0.0],
147    //             [0.0, 65535.0 / 32768.0, 0.0],
148    //             [0.0, 0.0, 65535.0 / 32768.0],
149    //         ],
150    //     }];
151    //
152    //     matrices.push(xyz_to_rgb.to_f32());
153    //     let xyz_to_rgb_stage = XyzToRgbStageExtended::<T> {
154    //         gamma_evaluator: extended_gamma,
155    //         matrices,
156    //         phantom_data: PhantomData,
157    //     };
158    //     xyz_to_rgb_stage.transform(lut)?;
159    //     return Ok(());
160    // }
161    // }
162    let gamma_map_r = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
163        &dest.red_trc,
164        options.allow_use_cicp_transfer,
165    )?;
166    let gamma_map_g = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
167        &dest.green_trc,
168        options.allow_use_cicp_transfer,
169    )?;
170    let gamma_map_b = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
171        &dest.blue_trc,
172        options.allow_use_cicp_transfer,
173    )?;
174
175    let xyz_to_rgb = dest.rgb_to_xyz_matrix().inverse();
176
177    let mut matrices: Vec<Box<KatanaDefaultIntermediate>> =
178        vec![Box::new(KatanaMatrixStage::new(Matrix3f {
179            v: [
180                [65535.0 / 32768.0, 0.0, 0.0],
181                [0.0, 65535.0 / 32768.0, 0.0],
182                [0.0, 0.0, 65535.0 / 32768.0],
183            ],
184        }))];
185
186    matrices.push(Box::new(KatanaMatrixStage::new(xyz_to_rgb.to_f32())));
187    match dest_layout {
188        Layout::Rgb => {
189            let xyz_to_rgb_stage = KatanaXyzToRgbStage::<T, { Layout::Rgb as u8 }> {
190                r_gamma: gamma_map_r,
191                g_gamma: gamma_map_g,
192                b_gamma: gamma_map_b,
193                intent: options.rendering_intent,
194                bit_depth: BIT_DEPTH,
195                gamma_lut: GAMMA_LUT,
196            };
197            Ok(KatanaXyzRgbState {
198                stages: matrices,
199                final_stage: Box::new(xyz_to_rgb_stage),
200            })
201        }
202        Layout::Rgba => {
203            let xyz_to_rgb_stage = KatanaXyzToRgbStage::<T, { Layout::Rgba as u8 }> {
204                r_gamma: gamma_map_r,
205                g_gamma: gamma_map_g,
206                b_gamma: gamma_map_b,
207                intent: options.rendering_intent,
208                bit_depth: BIT_DEPTH,
209                gamma_lut: GAMMA_LUT,
210            };
211            Ok(KatanaXyzRgbState {
212                stages: matrices,
213                final_stage: Box::new(xyz_to_rgb_stage),
214            })
215        }
216        Layout::Gray => unreachable!("Gray layout must not be called on Rgb/Rgba path"),
217        Layout::GrayAlpha => unreachable!("Gray layout must not be called on Rgb/Rgba path"),
218        _ => unreachable!(
219            "layout {:?} should not be called on xyz->rgb path",
220            dest_layout
221        ),
222    }
223}