1#[cfg(feature = "in_place")]
30use crate::InPlaceTransformExecutor;
31use crate::transform::PointeeSizeExpressible;
32use crate::{CmsError, Layout, TransformExecutor};
33use num_traits::AsPrimitive;
34use std::sync::Arc;
35
36#[derive(Clone)]
37struct TransformGray2RgbFusedExecutor<T, const SRC_LAYOUT: u8, const DEST_LAYOUT: u8> {
38 fused_gamma: Box<[T; 65536]>,
39 bit_depth: usize,
40}
41
42pub(crate) fn make_gray_to_x<
43 T: Copy + Default + PointeeSizeExpressible + 'static + Send + Sync,
44 const BUCKET: usize,
45>(
46 src_layout: Layout,
47 dst_layout: Layout,
48 gray_linear: &[f32; BUCKET],
49 gray_gamma: &[T; 65536],
50 bit_depth: usize,
51 gamma_lut: usize,
52) -> Result<Arc<dyn TransformExecutor<T> + Sync + Send>, CmsError>
53where
54 u32: AsPrimitive<T>,
55{
56 if src_layout != Layout::Gray && src_layout != Layout::GrayAlpha {
57 return Err(CmsError::UnsupportedProfileConnection);
58 }
59
60 let mut fused_gamma = Box::new([T::default(); 65536]);
61 let max_lut_size = (gamma_lut - 1) as f32;
62 for (&src, dst) in gray_linear.iter().zip(fused_gamma.iter_mut()) {
63 let possible_value = ((src * max_lut_size).round() as u32).min(max_lut_size as u32) as u16;
64 *dst = gray_gamma[possible_value as usize];
65 }
66
67 match src_layout {
68 Layout::Gray => match dst_layout {
69 Layout::Rgb => Ok(Arc::new(TransformGray2RgbFusedExecutor::<
70 T,
71 { Layout::Gray as u8 },
72 { Layout::Rgb as u8 },
73 > {
74 fused_gamma,
75 bit_depth,
76 })),
77 Layout::Rgba => Ok(Arc::new(TransformGray2RgbFusedExecutor::<
78 T,
79 { Layout::Gray as u8 },
80 { Layout::Rgba as u8 },
81 > {
82 fused_gamma,
83 bit_depth,
84 })),
85 Layout::Gray => Ok(Arc::new(TransformGray2RgbFusedExecutor::<
86 T,
87 { Layout::Gray as u8 },
88 { Layout::Gray as u8 },
89 > {
90 fused_gamma,
91 bit_depth,
92 })),
93 Layout::GrayAlpha => Ok(Arc::new(TransformGray2RgbFusedExecutor::<
94 T,
95 { Layout::Gray as u8 },
96 { Layout::GrayAlpha as u8 },
97 > {
98 fused_gamma,
99 bit_depth,
100 })),
101 _ => Err(CmsError::UnsupportedProfileConnection),
102 },
103 Layout::GrayAlpha => match dst_layout {
104 Layout::Rgb => Ok(Arc::new(TransformGray2RgbFusedExecutor::<
105 T,
106 { Layout::GrayAlpha as u8 },
107 { Layout::Rgb as u8 },
108 > {
109 fused_gamma,
110 bit_depth,
111 })),
112 Layout::Rgba => Ok(Arc::new(TransformGray2RgbFusedExecutor::<
113 T,
114 { Layout::GrayAlpha as u8 },
115 { Layout::Rgba as u8 },
116 > {
117 fused_gamma,
118 bit_depth,
119 })),
120 Layout::Gray => Ok(Arc::new(TransformGray2RgbFusedExecutor::<
121 T,
122 { Layout::GrayAlpha as u8 },
123 { Layout::Gray as u8 },
124 > {
125 fused_gamma,
126 bit_depth,
127 })),
128 Layout::GrayAlpha => Ok(Arc::new(TransformGray2RgbFusedExecutor::<
129 T,
130 { Layout::GrayAlpha as u8 },
131 { Layout::GrayAlpha as u8 },
132 > {
133 fused_gamma,
134 bit_depth,
135 })),
136 _ => Err(CmsError::UnsupportedProfileConnection),
137 },
138 _ => Err(CmsError::UnsupportedProfileConnection),
139 }
140}
141
142#[cfg(feature = "in_place")]
143pub(crate) fn make_gray_to_gray_in_place<
144 T: Copy + Default + PointeeSizeExpressible + 'static + Send + Sync,
145 const BUCKET: usize,
146>(
147 layout: Layout,
148 gray_linear: &[f32; BUCKET],
149 gray_gamma: &[T; 65536],
150 bit_depth: usize,
151 gamma_lut: usize,
152) -> Result<Arc<dyn InPlaceTransformExecutor<T> + Sync + Send>, CmsError>
153where
154 u32: AsPrimitive<T>,
155{
156 if layout != Layout::Gray && layout != Layout::GrayAlpha {
157 return Err(CmsError::UnsupportedProfileConnection);
158 }
159
160 let mut fused_gamma = Box::new([T::default(); 65536]);
161 let max_lut_size = (gamma_lut - 1) as f32;
162 for (&src, dst) in gray_linear.iter().zip(fused_gamma.iter_mut()) {
163 let possible_value = ((src * max_lut_size).round() as u32).min(max_lut_size as u32) as u16;
164 *dst = gray_gamma[possible_value as usize];
165 }
166
167 match layout {
168 Layout::Gray => Ok(Arc::new(TransformGray2RgbFusedExecutor::<
169 T,
170 { Layout::Gray as u8 },
171 { Layout::Gray as u8 },
172 > {
173 fused_gamma,
174 bit_depth,
175 })),
176 Layout::GrayAlpha => Ok(Arc::new(TransformGray2RgbFusedExecutor::<
177 T,
178 { Layout::GrayAlpha as u8 },
179 { Layout::GrayAlpha as u8 },
180 > {
181 fused_gamma,
182 bit_depth,
183 })),
184 _ => Err(CmsError::UnsupportedProfileConnection),
185 }
186}
187
188impl<
189 T: Copy + Default + PointeeSizeExpressible + 'static,
190 const SRC_LAYOUT: u8,
191 const DST_LAYOUT: u8,
192> TransformExecutor<T> for TransformGray2RgbFusedExecutor<T, SRC_LAYOUT, DST_LAYOUT>
193where
194 u32: AsPrimitive<T>,
195{
196 fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
197 let src_cn = Layout::from(SRC_LAYOUT);
198 let dst_cn = Layout::from(DST_LAYOUT);
199 let src_channels = src_cn.channels();
200 let dst_channels = dst_cn.channels();
201
202 if src.len() / src_channels != dst.len() / dst_channels {
203 return Err(CmsError::LaneSizeMismatch);
204 }
205 if src.len() % src_channels != 0 {
206 return Err(CmsError::LaneMultipleOfChannels);
207 }
208 if dst.len() % dst_channels != 0 {
209 return Err(CmsError::LaneMultipleOfChannels);
210 }
211
212 let is_gray_alpha = src_cn == Layout::GrayAlpha;
213
214 let max_value: T = ((1u32 << self.bit_depth as u32) - 1u32).as_();
215
216 for (src, dst) in src
217 .chunks_exact(src_channels)
218 .zip(dst.chunks_exact_mut(dst_channels))
219 {
220 let g = self.fused_gamma[src[0]._as_usize()];
221 let a = if is_gray_alpha { src[1] } else { max_value };
222
223 dst[0] = g;
224 if dst_cn == Layout::GrayAlpha {
225 dst[1] = a;
226 } else if dst_cn == Layout::Rgb {
227 dst[1] = g;
228 dst[2] = g;
229 } else if dst_cn == Layout::Rgba {
230 dst[1] = g;
231 dst[2] = g;
232 dst[3] = a;
233 }
234 }
235
236 Ok(())
237 }
238}
239
240#[cfg(feature = "in_place")]
241impl<
242 T: Copy + Default + PointeeSizeExpressible + 'static,
243 const SRC_LAYOUT: u8,
244 const DST_LAYOUT: u8,
245> InPlaceTransformExecutor<T> for TransformGray2RgbFusedExecutor<T, SRC_LAYOUT, DST_LAYOUT>
246where
247 u32: AsPrimitive<T>,
248{
249 fn transform(&self, in_out: &mut [T]) -> Result<(), CmsError> {
250 assert_eq!(
251 SRC_LAYOUT, DST_LAYOUT,
252 "This is in-place transform, layout must not diverge"
253 );
254 let src_cn = Layout::from(SRC_LAYOUT);
255 let src_channels = src_cn.channels();
256
257 if in_out.len() % src_channels != 0 {
258 return Err(CmsError::LaneMultipleOfChannels);
259 }
260
261 let is_gray_alpha = src_cn == Layout::GrayAlpha;
262
263 let max_value: T = ((1u32 << self.bit_depth as u32) - 1u32).as_();
264
265 for dst in in_out.chunks_exact_mut(src_channels) {
266 let g = self.fused_gamma[dst[0]._as_usize()];
267 let a = if is_gray_alpha { dst[1] } else { max_value };
268
269 dst[0] = g;
270 if src_cn == Layout::GrayAlpha {
271 dst[1] = a;
272 }
273 }
274
275 Ok(())
276 }
277}
278
279#[derive(Clone)]
280struct TransformGrayToRgbExecutor<T, const SRC_LAYOUT: u8, const DEST_LAYOUT: u8> {
281 gray_linear: Box<[f32; 65536]>,
282 red_gamma: Box<[T; 65536]>,
283 green_gamma: Box<[T; 65536]>,
284 blue_gamma: Box<[T; 65536]>,
285 bit_depth: usize,
286 gamma_lut: usize,
287}
288
289#[allow(clippy::too_many_arguments)]
290pub(crate) fn make_gray_to_unfused<
291 T: Copy + Default + PointeeSizeExpressible + 'static + Send + Sync,
292 const BUCKET: usize,
293>(
294 src_layout: Layout,
295 dst_layout: Layout,
296 gray_linear: Box<[f32; 65536]>,
297 red_gamma: Box<[T; 65536]>,
298 green_gamma: Box<[T; 65536]>,
299 blue_gamma: Box<[T; 65536]>,
300 bit_depth: usize,
301 gamma_lut: usize,
302) -> Result<Arc<dyn TransformExecutor<T> + Sync + Send>, CmsError>
303where
304 u32: AsPrimitive<T>,
305{
306 if src_layout != Layout::Gray && src_layout != Layout::GrayAlpha {
307 return Err(CmsError::UnsupportedProfileConnection);
308 }
309 if dst_layout != Layout::Rgb && dst_layout != Layout::Rgba {
310 return Err(CmsError::UnsupportedProfileConnection);
311 }
312 match src_layout {
313 Layout::Gray => match dst_layout {
314 Layout::Rgb => Ok(Arc::new(TransformGrayToRgbExecutor::<
315 T,
316 { Layout::Gray as u8 },
317 { Layout::Rgb as u8 },
318 > {
319 gray_linear,
320 red_gamma,
321 green_gamma,
322 blue_gamma,
323 bit_depth,
324 gamma_lut,
325 })),
326 Layout::Rgba => Ok(Arc::new(TransformGrayToRgbExecutor::<
327 T,
328 { Layout::Gray as u8 },
329 { Layout::Rgba as u8 },
330 > {
331 gray_linear,
332 red_gamma,
333 green_gamma,
334 blue_gamma,
335 bit_depth,
336 gamma_lut,
337 })),
338 Layout::Gray => Ok(Arc::new(TransformGrayToRgbExecutor::<
339 T,
340 { Layout::Gray as u8 },
341 { Layout::Gray as u8 },
342 > {
343 gray_linear,
344 red_gamma,
345 green_gamma,
346 blue_gamma,
347 bit_depth,
348 gamma_lut,
349 })),
350 Layout::GrayAlpha => Ok(Arc::new(TransformGrayToRgbExecutor::<
351 T,
352 { Layout::Gray as u8 },
353 { Layout::GrayAlpha as u8 },
354 > {
355 gray_linear,
356 red_gamma,
357 green_gamma,
358 blue_gamma,
359 bit_depth,
360 gamma_lut,
361 })),
362 _ => Err(CmsError::UnsupportedProfileConnection),
363 },
364 Layout::GrayAlpha => match dst_layout {
365 Layout::Rgb => Ok(Arc::new(TransformGrayToRgbExecutor::<
366 T,
367 { Layout::GrayAlpha as u8 },
368 { Layout::Rgb as u8 },
369 > {
370 gray_linear,
371 red_gamma,
372 green_gamma,
373 blue_gamma,
374 bit_depth,
375 gamma_lut,
376 })),
377 Layout::Rgba => Ok(Arc::new(TransformGrayToRgbExecutor::<
378 T,
379 { Layout::GrayAlpha as u8 },
380 { Layout::Rgba as u8 },
381 > {
382 gray_linear,
383 red_gamma,
384 green_gamma,
385 blue_gamma,
386 bit_depth,
387 gamma_lut,
388 })),
389 Layout::Gray => Ok(Arc::new(TransformGrayToRgbExecutor::<
390 T,
391 { Layout::GrayAlpha as u8 },
392 { Layout::Gray as u8 },
393 > {
394 gray_linear,
395 red_gamma,
396 green_gamma,
397 blue_gamma,
398 bit_depth,
399 gamma_lut,
400 })),
401 Layout::GrayAlpha => Ok(Arc::new(TransformGrayToRgbExecutor::<
402 T,
403 { Layout::GrayAlpha as u8 },
404 { Layout::GrayAlpha as u8 },
405 > {
406 gray_linear,
407 red_gamma,
408 green_gamma,
409 blue_gamma,
410 bit_depth,
411 gamma_lut,
412 })),
413 _ => Err(CmsError::UnsupportedProfileConnection),
414 },
415 _ => Err(CmsError::UnsupportedProfileConnection),
416 }
417}
418
419impl<
420 T: Copy + Default + PointeeSizeExpressible + 'static,
421 const SRC_LAYOUT: u8,
422 const DST_LAYOUT: u8,
423> TransformExecutor<T> for TransformGrayToRgbExecutor<T, SRC_LAYOUT, DST_LAYOUT>
424where
425 u32: AsPrimitive<T>,
426{
427 fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
428 let src_cn = Layout::from(SRC_LAYOUT);
429 let dst_cn = Layout::from(DST_LAYOUT);
430 let src_channels = src_cn.channels();
431 let dst_channels = dst_cn.channels();
432
433 if src.len() / src_channels != dst.len() / dst_channels {
434 return Err(CmsError::LaneSizeMismatch);
435 }
436 if src.len() % src_channels != 0 {
437 return Err(CmsError::LaneMultipleOfChannels);
438 }
439 if dst.len() % dst_channels != 0 {
440 return Err(CmsError::LaneMultipleOfChannels);
441 }
442
443 let is_gray_alpha = src_cn == Layout::GrayAlpha;
444
445 let max_value: T = ((1u32 << self.bit_depth as u32) - 1u32).as_();
446 let max_lut_size = (self.gamma_lut - 1) as f32;
447
448 for (src, dst) in src
449 .chunks_exact(src_channels)
450 .zip(dst.chunks_exact_mut(dst_channels))
451 {
452 let g = self.gray_linear[src[0]._as_usize()];
453 let a = if is_gray_alpha { src[1] } else { max_value };
454
455 let possible_value = ((g * max_lut_size).round() as u16) as usize;
456 let red_value = self.red_gamma[possible_value];
457 let green_value = self.green_gamma[possible_value];
458 let blue_value = self.blue_gamma[possible_value];
459
460 if dst_cn == Layout::Rgb {
461 dst[0] = red_value;
462 dst[1] = green_value;
463 dst[2] = blue_value;
464 } else if dst_cn == Layout::Rgba {
465 dst[0] = red_value;
466 dst[1] = green_value;
467 dst[2] = blue_value;
468 dst[3] = a;
469 } else {
470 return Err(CmsError::UnsupportedProfileConnection);
471 }
472 }
473
474 Ok(())
475 }
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481 use crate::{ColorProfile, TransformOptions};
482
483 #[test]
484 fn gray_rgb_roundtrip() {
485 let gray = ColorProfile::new_gray_with_gamma(2.2);
486 let srgb = ColorProfile::new_srgb();
487
488 let src_layout = [Layout::Gray, Layout::GrayAlpha];
489 let dst_layout = [Layout::Rgb, Layout::GrayAlpha, Layout::Rgba, Layout::Gray];
490 for src in src_layout {
491 for dst in dst_layout {
492 let transform = gray
493 .create_transform_8bit(src, &srgb, dst, TransformOptions::default())
494 .unwrap();
495 let mut in_px = vec![0u8; src.channels()];
496 let mut out_px = vec![0u8; dst.channels()];
497 transform.transform(&mut in_px, &mut out_px).unwrap();
498 }
499 }
500 }
501}