moxcms/conversions/katana/
rgb_xyz.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::{KatanaInitialStage, KatanaIntermediateStage};
31use crate::{CmsError, ColorProfile, Layout, Matrix3f, PointeeSizeExpressible, TransformOptions};
32use num_traits::AsPrimitive;
33use std::marker::PhantomData;
34
35struct KatanaRgbLinearizationStage<T: Clone, const LAYOUT: u8, const LINEAR_CAP: usize> {
36    r_lin: Box<[f32; LINEAR_CAP]>,
37    g_lin: Box<[f32; LINEAR_CAP]>,
38    b_lin: Box<[f32; LINEAR_CAP]>,
39    linear_cap: usize,
40    bit_depth: usize,
41    _phantom: PhantomData<T>,
42}
43
44impl<
45    T: Clone + AsPrimitive<f32> + PointeeSizeExpressible,
46    const LAYOUT: u8,
47    const LINEAR_CAP: usize,
48> KatanaInitialStage<f32, T> for KatanaRgbLinearizationStage<T, LAYOUT, LINEAR_CAP>
49{
50    fn to_pcs(&self, input: &[T]) -> Result<Vec<f32>, CmsError> {
51        let src_layout = Layout::from(LAYOUT);
52        if input.len() % src_layout.channels() != 0 {
53            return Err(CmsError::LaneMultipleOfChannels);
54        }
55        let mut dst = vec![0.; input.len() / src_layout.channels() * 3];
56
57        let scale = if T::FINITE {
58            (self.linear_cap as f32 - 1.) / ((1 << self.bit_depth) - 1) as f32
59        } else {
60            (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32
61        };
62
63        let cap_value = if T::FINITE {
64            ((1 << self.bit_depth) - 1) as f32
65        } else {
66            (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32
67        };
68
69        for (src, dst) in input
70            .chunks_exact(src_layout.channels())
71            .zip(dst.chunks_exact_mut(3))
72        {
73            let j_r = src[0].as_() * scale;
74            let j_g = src[1].as_() * scale;
75            let j_b = src[2].as_() * scale;
76            dst[0] = self.r_lin[(j_r.round().min(cap_value).max(0.) as u16) as usize];
77            dst[1] = self.g_lin[(j_g.round().min(cap_value).max(0.) as u16) as usize];
78            dst[2] = self.b_lin[(j_b.round().min(cap_value).max(0.) as u16) as usize];
79        }
80        Ok(dst)
81    }
82}
83
84pub(crate) struct KatanaRgbLinearizationState<T> {
85    pub(crate) stages: Vec<Box<dyn KatanaIntermediateStage<f32> + Send + Sync>>,
86    pub(crate) initial_stage: Box<dyn KatanaInitialStage<f32, T> + Send + Sync>,
87}
88
89pub(crate) fn katana_create_rgb_lin_lut<
90    T: Copy + Default + AsPrimitive<f32> + Send + Sync + AsPrimitive<usize> + PointeeSizeExpressible,
91    const BIT_DEPTH: usize,
92    const LINEAR_CAP: usize,
93>(
94    layout: Layout,
95    source: &ColorProfile,
96    opts: TransformOptions,
97) -> Result<KatanaRgbLinearizationState<T>, CmsError>
98where
99    u32: AsPrimitive<T>,
100    f32: AsPrimitive<T>,
101{
102    let lin_r =
103        source.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(opts.allow_use_cicp_transfer)?;
104    let lin_g =
105        source.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(opts.allow_use_cicp_transfer)?;
106    let lin_b =
107        source.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(opts.allow_use_cicp_transfer)?;
108
109    let lin_stage: Box<dyn KatanaInitialStage<f32, T> + Send + Sync> = match layout {
110        Layout::Rgb => {
111            Box::new(
112                KatanaRgbLinearizationStage::<T, { Layout::Rgb as u8 }, LINEAR_CAP> {
113                    r_lin: lin_r,
114                    g_lin: lin_g,
115                    b_lin: lin_b,
116                    bit_depth: BIT_DEPTH,
117                    linear_cap: LINEAR_CAP,
118                    _phantom: PhantomData,
119                },
120            )
121        }
122        Layout::Rgba => {
123            Box::new(
124                KatanaRgbLinearizationStage::<T, { Layout::Rgba as u8 }, LINEAR_CAP> {
125                    r_lin: lin_r,
126                    g_lin: lin_g,
127                    b_lin: lin_b,
128                    bit_depth: BIT_DEPTH,
129                    linear_cap: LINEAR_CAP,
130                    _phantom: PhantomData,
131                },
132            )
133        }
134        Layout::Gray => unimplemented!("Gray should not be called on Rgb/Rgba execution path"),
135        Layout::GrayAlpha => {
136            unimplemented!("GrayAlpha should not be called on Rgb/Rgba execution path")
137        }
138        _ => unreachable!(),
139    };
140
141    let xyz_to_rgb = source.rgb_to_xyz_matrix();
142
143    let matrices: Vec<Box<dyn KatanaIntermediateStage<f32> + Send + Sync>> =
144        vec![Box::new(KatanaMatrixStage {
145            matrices: vec![
146                xyz_to_rgb.to_f32(),
147                Matrix3f {
148                    v: [
149                        [32768.0 / 65535.0, 0.0, 0.0],
150                        [0.0, 32768.0 / 65535.0, 0.0],
151                        [0.0, 0.0, 32768.0 / 65535.0],
152                    ],
153                },
154            ],
155        })];
156
157    Ok(KatanaRgbLinearizationState {
158        stages: matrices,
159        initial_stage: lin_stage,
160    })
161}