moxcms/conversions/
rgb2gray_extended.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 2/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::mlaf::mlaf;
30use crate::transform::PointeeSizeExpressible;
31use crate::trc::ToneCurveEvaluator;
32use crate::{CmsError, Layout, Rgb, TransformExecutor, Vector3f};
33use num_traits::AsPrimitive;
34use std::marker::PhantomData;
35
36struct TransformRgbToGrayExtendedExecutor<T, const SRC_LAYOUT: u8, const DST_LAYOUT: u8> {
37    linear_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
38    gamma_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
39    weights: Vector3f,
40    _phantom: PhantomData<T>,
41    bit_depth: usize,
42}
43
44pub(crate) fn make_rgb_to_gray_extended<
45    T: Copy + Default + PointeeSizeExpressible + Send + Sync + 'static + AsPrimitive<f32>,
46>(
47    src_layout: Layout,
48    dst_layout: Layout,
49    linear_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
50    gamma_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
51    weights: Vector3f,
52    bit_depth: usize,
53) -> Box<dyn TransformExecutor<T> + Send + Sync>
54where
55    u32: AsPrimitive<T>,
56    f32: AsPrimitive<T>,
57{
58    match src_layout {
59        Layout::Rgb => match dst_layout {
60            Layout::Rgb => unreachable!(),
61            Layout::Rgba => unreachable!(),
62            Layout::Gray => Box::new(TransformRgbToGrayExtendedExecutor::<
63                T,
64                { Layout::Rgb as u8 },
65                { Layout::Gray as u8 },
66            > {
67                linear_eval,
68                gamma_eval,
69                weights,
70                _phantom: PhantomData,
71                bit_depth,
72            }),
73            Layout::GrayAlpha => Box::new(TransformRgbToGrayExtendedExecutor::<
74                T,
75                { Layout::Rgb as u8 },
76                { Layout::GrayAlpha as u8 },
77            > {
78                linear_eval,
79                gamma_eval,
80                weights,
81                _phantom: PhantomData,
82                bit_depth,
83            }),
84            _ => unreachable!(),
85        },
86        Layout::Rgba => match dst_layout {
87            Layout::Rgb => unreachable!(),
88            Layout::Rgba => unreachable!(),
89            Layout::Gray => Box::new(TransformRgbToGrayExtendedExecutor::<
90                T,
91                { Layout::Rgba as u8 },
92                { Layout::Gray as u8 },
93            > {
94                linear_eval,
95                gamma_eval,
96                weights,
97                _phantom: PhantomData,
98                bit_depth,
99            }),
100            Layout::GrayAlpha => Box::new(TransformRgbToGrayExtendedExecutor::<
101                T,
102                { Layout::Rgba as u8 },
103                { Layout::GrayAlpha as u8 },
104            > {
105                linear_eval,
106                gamma_eval,
107                weights,
108                _phantom: PhantomData,
109                bit_depth,
110            }),
111            _ => unreachable!(),
112        },
113        Layout::Gray => unreachable!(),
114        Layout::GrayAlpha => unreachable!(),
115        _ => unreachable!(),
116    }
117}
118
119impl<
120    T: Copy + Default + PointeeSizeExpressible + 'static + AsPrimitive<f32>,
121    const SRC_LAYOUT: u8,
122    const DST_LAYOUT: u8,
123> TransformExecutor<T> for TransformRgbToGrayExtendedExecutor<T, SRC_LAYOUT, DST_LAYOUT>
124where
125    u32: AsPrimitive<T>,
126    f32: AsPrimitive<T>,
127{
128    fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
129        let src_cn = Layout::from(SRC_LAYOUT);
130        let dst_cn = Layout::from(DST_LAYOUT);
131        let src_channels = src_cn.channels();
132        let dst_channels = dst_cn.channels();
133
134        if src.len() / src_channels != dst.len() / dst_channels {
135            return Err(CmsError::LaneSizeMismatch);
136        }
137        if src.len() % src_channels != 0 {
138            return Err(CmsError::LaneMultipleOfChannels);
139        }
140        if dst.len() % dst_channels != 0 {
141            return Err(CmsError::LaneMultipleOfChannels);
142        }
143
144        let max_value = ((1u32 << self.bit_depth) - 1).as_();
145
146        for (src, dst) in src
147            .chunks_exact(src_channels)
148            .zip(dst.chunks_exact_mut(dst_channels))
149        {
150            let in_tristimulus = Rgb::<f32>::new(
151                src[src_cn.r_i()].as_(),
152                src[src_cn.g_i()].as_(),
153                src[src_cn.b_i()].as_(),
154            );
155            let lin_tristimulus = self.linear_eval.evaluate_tristimulus(in_tristimulus);
156            let a = if src_channels == 4 {
157                src[src_cn.a_i()]
158            } else {
159                max_value
160            };
161            let grey = mlaf(
162                mlaf(
163                    self.weights.v[0] * lin_tristimulus.r,
164                    self.weights.v[1],
165                    lin_tristimulus.g,
166                ),
167                self.weights.v[2],
168                lin_tristimulus.b,
169            )
170            .min(1.)
171            .max(0.);
172            let gamma_value = self.gamma_eval.evaluate_value(grey);
173            dst[0] = gamma_value.as_();
174            if dst_channels == 2 {
175                dst[1] = a;
176            }
177        }
178
179        Ok(())
180    }
181}