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