moxcms/conversions/
gray2rgb.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::transform::PointeeSizeExpressible;
30use crate::{CmsError, Layout, TransformExecutor};
31use num_traits::AsPrimitive;
32
33#[derive(Clone)]
34struct TransformGray2RgbFusedExecutor<
35    T,
36    const SRC_LAYOUT: u8,
37    const DEST_LAYOUT: u8,
38    const BUCKET: usize,
39> {
40    fused_gamma: Box<[T; BUCKET]>,
41    bit_depth: usize,
42}
43
44pub(crate) fn make_gray_to_x<
45    T: Copy + Default + PointeeSizeExpressible + 'static + Send + Sync,
46    const BUCKET: usize,
47>(
48    src_layout: Layout,
49    dst_layout: Layout,
50    gray_linear: &[f32; BUCKET],
51    gray_gamma: &[T; 65536],
52    bit_depth: usize,
53    gamma_lut: usize,
54) -> Result<Box<dyn TransformExecutor<T> + Sync + Send>, CmsError>
55where
56    u32: AsPrimitive<T>,
57{
58    if src_layout != Layout::Gray && src_layout != Layout::GrayAlpha {
59        return Err(CmsError::UnsupportedProfileConnection);
60    }
61
62    let mut fused_gamma = Box::new([T::default(); BUCKET]);
63    let max_lut_size = (gamma_lut - 1) as f32;
64    for (&src, dst) in gray_linear.iter().zip(fused_gamma.iter_mut()) {
65        let possible_value = ((src * max_lut_size).round() as u32).min(max_lut_size as u32) as u16;
66        *dst = gray_gamma[possible_value as usize];
67    }
68
69    match src_layout {
70        Layout::Gray => match dst_layout {
71            Layout::Rgb => Ok(Box::new(TransformGray2RgbFusedExecutor::<
72                T,
73                { Layout::Gray as u8 },
74                { Layout::Rgb as u8 },
75                BUCKET,
76            > {
77                fused_gamma,
78                bit_depth,
79            })),
80            Layout::Rgba => Ok(Box::new(TransformGray2RgbFusedExecutor::<
81                T,
82                { Layout::Gray as u8 },
83                { Layout::Rgba as u8 },
84                BUCKET,
85            > {
86                fused_gamma,
87                bit_depth,
88            })),
89            Layout::Gray => Ok(Box::new(TransformGray2RgbFusedExecutor::<
90                T,
91                { Layout::Gray as u8 },
92                { Layout::Gray as u8 },
93                BUCKET,
94            > {
95                fused_gamma,
96                bit_depth,
97            })),
98            Layout::GrayAlpha => Ok(Box::new(TransformGray2RgbFusedExecutor::<
99                T,
100                { Layout::Gray as u8 },
101                { Layout::GrayAlpha as u8 },
102                BUCKET,
103            > {
104                fused_gamma,
105                bit_depth,
106            })),
107            _ => unreachable!(),
108        },
109        Layout::GrayAlpha => match dst_layout {
110            Layout::Rgb => Ok(Box::new(TransformGray2RgbFusedExecutor::<
111                T,
112                { Layout::Gray as u8 },
113                { Layout::GrayAlpha as u8 },
114                BUCKET,
115            > {
116                fused_gamma,
117                bit_depth,
118            })),
119            Layout::Rgba => Ok(Box::new(TransformGray2RgbFusedExecutor::<
120                T,
121                { Layout::Gray as u8 },
122                { Layout::Rgba as u8 },
123                BUCKET,
124            > {
125                fused_gamma,
126                bit_depth,
127            })),
128            Layout::Gray => Ok(Box::new(TransformGray2RgbFusedExecutor::<
129                T,
130                { Layout::Gray as u8 },
131                { Layout::Gray as u8 },
132                BUCKET,
133            > {
134                fused_gamma,
135                bit_depth,
136            })),
137            Layout::GrayAlpha => Ok(Box::new(TransformGray2RgbFusedExecutor::<
138                T,
139                { Layout::GrayAlpha as u8 },
140                { Layout::GrayAlpha as u8 },
141                BUCKET,
142            > {
143                fused_gamma,
144                bit_depth,
145            })),
146            _ => unreachable!(),
147        },
148        _ => Err(CmsError::UnsupportedProfileConnection),
149    }
150}
151
152impl<
153    T: Copy + Default + PointeeSizeExpressible + 'static,
154    const SRC_LAYOUT: u8,
155    const DST_LAYOUT: u8,
156    const BUCKET: usize,
157> TransformExecutor<T> for TransformGray2RgbFusedExecutor<T, SRC_LAYOUT, DST_LAYOUT, BUCKET>
158where
159    u32: AsPrimitive<T>,
160{
161    fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
162        let src_cn = Layout::from(SRC_LAYOUT);
163        let dst_cn = Layout::from(DST_LAYOUT);
164        let src_channels = src_cn.channels();
165        let dst_channels = dst_cn.channels();
166
167        if src.len() / src_channels != dst.len() / dst_channels {
168            return Err(CmsError::LaneSizeMismatch);
169        }
170        if src.len() % src_channels != 0 {
171            return Err(CmsError::LaneMultipleOfChannels);
172        }
173        if dst.len() % dst_channels != 0 {
174            return Err(CmsError::LaneMultipleOfChannels);
175        }
176
177        let is_gray_alpha = src_cn == Layout::GrayAlpha;
178
179        let max_value: T = ((1u32 << self.bit_depth as u32) - 1u32).as_();
180
181        for (src, dst) in src
182            .chunks_exact(src_channels)
183            .zip(dst.chunks_exact_mut(dst_channels))
184        {
185            let g = self.fused_gamma[src[0]._as_usize()];
186            let a = if is_gray_alpha { src[1] } else { max_value };
187
188            dst[0] = g;
189            if dst_cn == Layout::GrayAlpha {
190                dst[1] = a;
191            } else if dst_cn == Layout::Rgb {
192                dst[1] = g;
193                dst[2] = g;
194            } else if dst_cn == Layout::Rgba {
195                dst[1] = g;
196                dst[2] = g;
197                dst[3] = a;
198            }
199        }
200
201        Ok(())
202    }
203}
204
205#[derive(Clone)]
206struct TransformGrayToRgbExecutor<
207    T,
208    const SRC_LAYOUT: u8,
209    const DEST_LAYOUT: u8,
210    const BUCKET: usize,
211> {
212    gray_linear: Box<[f32; BUCKET]>,
213    red_gamma: Box<[T; 65536]>,
214    green_gamma: Box<[T; 65536]>,
215    blue_gamma: Box<[T; 65536]>,
216    bit_depth: usize,
217    gamma_lut: usize,
218}
219
220#[allow(clippy::too_many_arguments)]
221pub(crate) fn make_gray_to_unfused<
222    T: Copy + Default + PointeeSizeExpressible + 'static + Send + Sync,
223    const BUCKET: usize,
224>(
225    src_layout: Layout,
226    dst_layout: Layout,
227    gray_linear: Box<[f32; BUCKET]>,
228    red_gamma: Box<[T; 65536]>,
229    green_gamma: Box<[T; 65536]>,
230    blue_gamma: Box<[T; 65536]>,
231    bit_depth: usize,
232    gamma_lut: usize,
233) -> Result<Box<dyn TransformExecutor<T> + Sync + Send>, CmsError>
234where
235    u32: AsPrimitive<T>,
236{
237    if src_layout != Layout::Gray && src_layout != Layout::GrayAlpha {
238        return Err(CmsError::UnsupportedProfileConnection);
239    }
240    if dst_layout != Layout::Rgb && dst_layout != Layout::Rgba {
241        return Err(CmsError::UnsupportedProfileConnection);
242    }
243    match src_layout {
244        Layout::Gray => match dst_layout {
245            Layout::Rgb => Ok(Box::new(TransformGrayToRgbExecutor::<
246                T,
247                { Layout::Gray as u8 },
248                { Layout::Rgb as u8 },
249                BUCKET,
250            > {
251                gray_linear,
252                red_gamma,
253                green_gamma,
254                blue_gamma,
255                bit_depth,
256                gamma_lut,
257            })),
258            Layout::Rgba => Ok(Box::new(TransformGrayToRgbExecutor::<
259                T,
260                { Layout::Gray as u8 },
261                { Layout::Rgba as u8 },
262                BUCKET,
263            > {
264                gray_linear,
265                red_gamma,
266                green_gamma,
267                blue_gamma,
268                bit_depth,
269                gamma_lut,
270            })),
271            Layout::Gray => Ok(Box::new(TransformGrayToRgbExecutor::<
272                T,
273                { Layout::Gray as u8 },
274                { Layout::Gray as u8 },
275                BUCKET,
276            > {
277                gray_linear,
278                red_gamma,
279                green_gamma,
280                blue_gamma,
281                bit_depth,
282                gamma_lut,
283            })),
284            Layout::GrayAlpha => Ok(Box::new(TransformGrayToRgbExecutor::<
285                T,
286                { Layout::Gray as u8 },
287                { Layout::GrayAlpha as u8 },
288                BUCKET,
289            > {
290                gray_linear,
291                red_gamma,
292                green_gamma,
293                blue_gamma,
294                bit_depth,
295                gamma_lut,
296            })),
297            _ => Err(CmsError::UnsupportedProfileConnection),
298        },
299        Layout::GrayAlpha => match dst_layout {
300            Layout::Rgb => Ok(Box::new(TransformGrayToRgbExecutor::<
301                T,
302                { Layout::Gray as u8 },
303                { Layout::GrayAlpha as u8 },
304                BUCKET,
305            > {
306                gray_linear,
307                red_gamma,
308                green_gamma,
309                blue_gamma,
310                bit_depth,
311                gamma_lut,
312            })),
313            Layout::Rgba => Ok(Box::new(TransformGrayToRgbExecutor::<
314                T,
315                { Layout::Gray as u8 },
316                { Layout::Rgba as u8 },
317                BUCKET,
318            > {
319                gray_linear,
320                red_gamma,
321                green_gamma,
322                blue_gamma,
323                bit_depth,
324                gamma_lut,
325            })),
326            Layout::Gray => Ok(Box::new(TransformGrayToRgbExecutor::<
327                T,
328                { Layout::Gray as u8 },
329                { Layout::Gray as u8 },
330                BUCKET,
331            > {
332                gray_linear,
333                red_gamma,
334                green_gamma,
335                blue_gamma,
336                bit_depth,
337                gamma_lut,
338            })),
339            Layout::GrayAlpha => Ok(Box::new(TransformGrayToRgbExecutor::<
340                T,
341                { Layout::GrayAlpha as u8 },
342                { Layout::GrayAlpha as u8 },
343                BUCKET,
344            > {
345                gray_linear,
346                red_gamma,
347                green_gamma,
348                blue_gamma,
349                bit_depth,
350                gamma_lut,
351            })),
352            _ => Err(CmsError::UnsupportedProfileConnection),
353        },
354        _ => Err(CmsError::UnsupportedProfileConnection),
355    }
356}
357
358impl<
359    T: Copy + Default + PointeeSizeExpressible + 'static,
360    const SRC_LAYOUT: u8,
361    const DST_LAYOUT: u8,
362    const BUCKET: usize,
363> TransformExecutor<T> for TransformGrayToRgbExecutor<T, SRC_LAYOUT, DST_LAYOUT, BUCKET>
364where
365    u32: AsPrimitive<T>,
366{
367    fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
368        let src_cn = Layout::from(SRC_LAYOUT);
369        let dst_cn = Layout::from(DST_LAYOUT);
370        let src_channels = src_cn.channels();
371        let dst_channels = dst_cn.channels();
372
373        if src.len() / src_channels != dst.len() / dst_channels {
374            return Err(CmsError::LaneSizeMismatch);
375        }
376        if src.len() % src_channels != 0 {
377            return Err(CmsError::LaneMultipleOfChannels);
378        }
379        if dst.len() % dst_channels != 0 {
380            return Err(CmsError::LaneMultipleOfChannels);
381        }
382
383        let is_gray_alpha = src_cn == Layout::GrayAlpha;
384
385        let max_value: T = ((1u32 << self.bit_depth as u32) - 1u32).as_();
386        let max_lut_size = (self.gamma_lut - 1) as f32;
387
388        for (src, dst) in src
389            .chunks_exact(src_channels)
390            .zip(dst.chunks_exact_mut(dst_channels))
391        {
392            let g = self.gray_linear[src[0]._as_usize()];
393            let a = if is_gray_alpha { src[1] } else { max_value };
394
395            let possible_value = ((g * max_lut_size).round() as u16) as usize;
396            let red_value = self.red_gamma[possible_value];
397            let green_value = self.green_gamma[possible_value];
398            let blue_value = self.blue_gamma[possible_value];
399
400            if dst_cn == Layout::Rgb {
401                dst[0] = red_value;
402                dst[1] = green_value;
403                dst[2] = blue_value;
404            } else if dst_cn == Layout::Rgba {
405                dst[0] = red_value;
406                dst[1] = green_value;
407                dst[2] = blue_value;
408                dst[3] = a;
409            } else {
410                return Err(CmsError::UnsupportedProfileConnection);
411            }
412        }
413
414        Ok(())
415    }
416}