moxcms/conversions/
gray2rgb_extended.rs

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