1use crate::mlaf::mlaf;
30use crate::transform::PointeeSizeExpressible;
31use crate::{CmsError, Layout, TransformExecutor, Vector3f};
32use num_traits::AsPrimitive;
33use std::sync::Arc;
34
35#[derive(Clone)]
36pub(crate) struct ToneReproductionRgbToGray<T, const BUCKET: usize> {
37 pub(crate) r_linear: Box<[f32; BUCKET]>,
38 pub(crate) g_linear: Box<[f32; BUCKET]>,
39 pub(crate) b_linear: Box<[f32; BUCKET]>,
40 pub(crate) gray_gamma: Box<[T; 65536]>,
41}
42
43#[derive(Clone)]
44struct TransformRgbToGrayExecutor<
45 T,
46 const SRC_LAYOUT: u8,
47 const DST_LAYOUT: u8,
48 const BUCKET: usize,
49> {
50 trc_box: ToneReproductionRgbToGray<T, BUCKET>,
51 weights: Vector3f,
52 bit_depth: usize,
53 gamma_lut: usize,
54}
55
56pub(crate) fn make_rgb_to_gray<
57 T: Copy + Default + PointeeSizeExpressible + Send + Sync + 'static,
58 const BUCKET: usize,
59>(
60 src_layout: Layout,
61 dst_layout: Layout,
62 trc: ToneReproductionRgbToGray<T, BUCKET>,
63 weights: Vector3f,
64 gamma_lut: usize,
65 bit_depth: usize,
66) -> Result<Arc<dyn TransformExecutor<T> + Send + Sync>, CmsError>
67where
68 u32: AsPrimitive<T>,
69{
70 match src_layout {
71 Layout::Rgb => match dst_layout {
72 Layout::Rgb => Err(CmsError::UnsupportedProfileConnection),
73 Layout::Rgba => Err(CmsError::UnsupportedProfileConnection),
74 Layout::Gray => Ok(Arc::new(TransformRgbToGrayExecutor::<
75 T,
76 { Layout::Rgb as u8 },
77 { Layout::Gray as u8 },
78 BUCKET,
79 > {
80 trc_box: trc,
81 weights,
82 bit_depth,
83 gamma_lut,
84 })),
85 Layout::GrayAlpha => Ok(Arc::new(TransformRgbToGrayExecutor::<
86 T,
87 { Layout::Rgb as u8 },
88 { Layout::GrayAlpha as u8 },
89 BUCKET,
90 > {
91 trc_box: trc,
92 weights,
93 bit_depth,
94 gamma_lut,
95 })),
96 _ => Err(CmsError::UnsupportedProfileConnection),
97 },
98 Layout::Rgba => match dst_layout {
99 Layout::Rgb => Err(CmsError::UnsupportedProfileConnection),
100 Layout::Rgba => Err(CmsError::UnsupportedProfileConnection),
101 Layout::Gray => Ok(Arc::new(TransformRgbToGrayExecutor::<
102 T,
103 { Layout::Rgba as u8 },
104 { Layout::Gray as u8 },
105 BUCKET,
106 > {
107 trc_box: trc,
108 weights,
109 bit_depth,
110 gamma_lut,
111 })),
112 Layout::GrayAlpha => Ok(Arc::new(TransformRgbToGrayExecutor::<
113 T,
114 { Layout::Rgba as u8 },
115 { Layout::GrayAlpha as u8 },
116 BUCKET,
117 > {
118 trc_box: trc,
119 weights,
120 bit_depth,
121 gamma_lut,
122 })),
123 _ => Err(CmsError::UnsupportedProfileConnection),
124 },
125 Layout::Gray => Err(CmsError::UnsupportedProfileConnection),
126 Layout::GrayAlpha => Err(CmsError::UnsupportedProfileConnection),
127 _ => Err(CmsError::UnsupportedProfileConnection),
128 }
129}
130
131impl<
132 T: Copy + Default + PointeeSizeExpressible + 'static,
133 const SRC_LAYOUT: u8,
134 const DST_LAYOUT: u8,
135 const BUCKET: usize,
136> TransformExecutor<T> for TransformRgbToGrayExecutor<T, SRC_LAYOUT, DST_LAYOUT, BUCKET>
137where
138 u32: AsPrimitive<T>,
139{
140 fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
141 let src_cn = Layout::from(SRC_LAYOUT);
142 let dst_cn = Layout::from(DST_LAYOUT);
143 let src_channels = src_cn.channels();
144 let dst_channels = dst_cn.channels();
145
146 if src.len() / src_channels != dst.len() / dst_channels {
147 return Err(CmsError::LaneSizeMismatch);
148 }
149 if src.len() % src_channels != 0 {
150 return Err(CmsError::LaneMultipleOfChannels);
151 }
152 if dst.len() % dst_channels != 0 {
153 return Err(CmsError::LaneMultipleOfChannels);
154 }
155
156 let scale_value = (self.gamma_lut - 1) as f32;
157 let max_value = ((1u32 << self.bit_depth) - 1).as_();
158
159 for (src, dst) in src
160 .chunks_exact(src_channels)
161 .zip(dst.chunks_exact_mut(dst_channels))
162 {
163 let r = self.trc_box.r_linear[src[src_cn.r_i()]._as_usize()];
164 let g = self.trc_box.g_linear[src[src_cn.g_i()]._as_usize()];
165 let b = self.trc_box.b_linear[src[src_cn.b_i()]._as_usize()];
166 let a = if src_channels == 4 {
167 src[src_cn.a_i()]
168 } else {
169 max_value
170 };
171 let grey = mlaf(
172 0.5,
173 mlaf(
174 mlaf(self.weights.v[0] * r, self.weights.v[1], g),
175 self.weights.v[2],
176 b,
177 )
178 .min(1.)
179 .max(0.),
180 scale_value,
181 );
182 dst[0] = self.trc_box.gray_gamma[(grey as u16) as usize];
183 if dst_channels == 2 {
184 dst[1] = a;
185 }
186 }
187
188 Ok(())
189 }
190}