Skip to main content

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