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