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    const GAMMA_LUT: usize,
49> {
50    trc_box: ToneReproductionRgbToGray<T, BUCKET>,
51    weights: Vector3f,
52    bit_depth: usize,
53}
54
55pub(crate) fn make_rgb_to_gray<
56    T: Copy + Default + PointeeSizeExpressible + Send + Sync + 'static,
57    const BUCKET: usize,
58    const BIT_DEPTH: usize,
59    const GAMMA_LUT: usize,
60>(
61    src_layout: Layout,
62    dst_layout: Layout,
63    trc: ToneReproductionRgbToGray<T, BUCKET>,
64    weights: Vector3f,
65) -> Box<dyn TransformExecutor<T> + Send + Sync>
66where
67    u32: AsPrimitive<T>,
68{
69    match src_layout {
70        Layout::Rgb => match dst_layout {
71            Layout::Rgb => unreachable!(),
72            Layout::Rgba => unreachable!(),
73            Layout::Gray => Box::new(TransformRgbToGrayExecutor::<
74                T,
75                { Layout::Rgb as u8 },
76                { Layout::Gray as u8 },
77                BUCKET,
78                GAMMA_LUT,
79            > {
80                trc_box: trc,
81                weights,
82                bit_depth: BIT_DEPTH,
83            }),
84            Layout::GrayAlpha => Box::new(TransformRgbToGrayExecutor::<
85                T,
86                { Layout::Rgb as u8 },
87                { Layout::GrayAlpha as u8 },
88                BUCKET,
89                GAMMA_LUT,
90            > {
91                trc_box: trc,
92                weights,
93                bit_depth: BIT_DEPTH,
94            }),
95            _ => unreachable!(),
96        },
97        Layout::Rgba => match dst_layout {
98            Layout::Rgb => unreachable!(),
99            Layout::Rgba => unreachable!(),
100            Layout::Gray => Box::new(TransformRgbToGrayExecutor::<
101                T,
102                { Layout::Rgba as u8 },
103                { Layout::Gray as u8 },
104                BUCKET,
105                GAMMA_LUT,
106            > {
107                trc_box: trc,
108                weights,
109                bit_depth: BIT_DEPTH,
110            }),
111            Layout::GrayAlpha => Box::new(TransformRgbToGrayExecutor::<
112                T,
113                { Layout::Rgba as u8 },
114                { Layout::GrayAlpha as u8 },
115                BUCKET,
116                GAMMA_LUT,
117            > {
118                trc_box: trc,
119                weights,
120                bit_depth: BIT_DEPTH,
121            }),
122            _ => unreachable!(),
123        },
124        Layout::Gray => unreachable!(),
125        Layout::GrayAlpha => unreachable!(),
126        _ => unreachable!(),
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    const GAMMA_LUT: usize,
136> TransformExecutor<T> for TransformRgbToGrayExecutor<T, SRC_LAYOUT, DST_LAYOUT, BUCKET, GAMMA_LUT>
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 = (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}