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