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