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