1use 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}