1use crate::trc::ToneCurveEvaluator;
30use crate::{CmsError, Layout, Matrix3f, PointeeSizeExpressible, Rgb, TransformExecutor};
31use num_traits::AsPrimitive;
32use std::marker::PhantomData;
33
34pub(crate) struct TransformShaperRgbFloat<T: Clone, const BUCKET: usize> {
35 pub(crate) r_linear: Box<[f32; BUCKET]>,
36 pub(crate) g_linear: Box<[f32; BUCKET]>,
37 pub(crate) b_linear: Box<[f32; BUCKET]>,
38 pub(crate) gamma_evaluator: Box<dyn ToneCurveEvaluator + Send + Sync>,
39 pub(crate) adaptation_matrix: Matrix3f,
40 pub(crate) phantom_data: PhantomData<T>,
41}
42
43pub(crate) struct TransformShaperFloatInOut<T: Clone> {
44 pub(crate) linear_evaluator: Box<dyn ToneCurveEvaluator + Send + Sync>,
45 pub(crate) gamma_evaluator: Box<dyn ToneCurveEvaluator + Send + Sync>,
46 pub(crate) adaptation_matrix: Matrix3f,
47 pub(crate) phantom_data: PhantomData<T>,
48}
49
50struct TransformShaperFloatScalar<
51 T: Clone,
52 const SRC_LAYOUT: u8,
53 const DST_LAYOUT: u8,
54 const LINEAR_CAP: usize,
55 const BIT_DEPTH: usize,
56> {
57 pub(crate) profile: TransformShaperRgbFloat<T, LINEAR_CAP>,
58}
59
60struct TransformShaperRgbFloatInOut<T: Clone, const SRC_LAYOUT: u8, const DST_LAYOUT: u8> {
61 pub(crate) profile: TransformShaperFloatInOut<T>,
62 pub(crate) bit_depth: usize,
63}
64
65pub(crate) fn make_rgb_xyz_rgb_transform_float<
66 T: Clone + Send + Sync + PointeeSizeExpressible + 'static + Copy + Default,
67 const LINEAR_CAP: usize,
68 const BIT_DEPTH: usize,
69>(
70 src_layout: Layout,
71 dst_layout: Layout,
72 profile: TransformShaperRgbFloat<T, LINEAR_CAP>,
73) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
74where
75 u32: AsPrimitive<T>,
76 f32: AsPrimitive<T>,
77{
78 if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
79 return Ok(Box::new(TransformShaperFloatScalar::<
80 T,
81 { Layout::Rgba as u8 },
82 { Layout::Rgba as u8 },
83 LINEAR_CAP,
84 BIT_DEPTH,
85 > {
86 profile,
87 }));
88 } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
89 return Ok(Box::new(TransformShaperFloatScalar::<
90 T,
91 { Layout::Rgb as u8 },
92 { Layout::Rgba as u8 },
93 LINEAR_CAP,
94 BIT_DEPTH,
95 > {
96 profile,
97 }));
98 } else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
99 return Ok(Box::new(TransformShaperFloatScalar::<
100 T,
101 { Layout::Rgba as u8 },
102 { Layout::Rgb as u8 },
103 LINEAR_CAP,
104 BIT_DEPTH,
105 > {
106 profile,
107 }));
108 } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
109 return Ok(Box::new(TransformShaperFloatScalar::<
110 T,
111 { Layout::Rgb as u8 },
112 { Layout::Rgb as u8 },
113 LINEAR_CAP,
114 BIT_DEPTH,
115 > {
116 profile,
117 }));
118 }
119 Err(CmsError::UnsupportedProfileConnection)
120}
121
122pub(crate) fn make_rgb_xyz_rgb_transform_float_in_out<
123 T: Clone + Send + Sync + PointeeSizeExpressible + 'static + Copy + Default + AsPrimitive<f32>,
124 const BIT_DEPTH: usize,
125>(
126 src_layout: Layout,
127 dst_layout: Layout,
128 profile: TransformShaperFloatInOut<T>,
129) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
130where
131 u32: AsPrimitive<T>,
132 f32: AsPrimitive<T>,
133{
134 if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
135 return Ok(Box::new(TransformShaperRgbFloatInOut::<
136 T,
137 { Layout::Rgba as u8 },
138 { Layout::Rgba as u8 },
139 > {
140 profile,
141 bit_depth: BIT_DEPTH,
142 }));
143 } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
144 return Ok(Box::new(TransformShaperRgbFloatInOut::<
145 T,
146 { Layout::Rgb as u8 },
147 { Layout::Rgba as u8 },
148 > {
149 profile,
150 bit_depth: BIT_DEPTH,
151 }));
152 } else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
153 return Ok(Box::new(TransformShaperRgbFloatInOut::<
154 T,
155 { Layout::Rgba as u8 },
156 { Layout::Rgb as u8 },
157 > {
158 profile,
159 bit_depth: BIT_DEPTH,
160 }));
161 } else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
162 return Ok(Box::new(TransformShaperRgbFloatInOut::<
163 T,
164 { Layout::Rgb as u8 },
165 { Layout::Rgb as u8 },
166 > {
167 profile,
168 bit_depth: BIT_DEPTH,
169 }));
170 }
171 Err(CmsError::UnsupportedProfileConnection)
172}
173
174impl<
175 T: Clone + PointeeSizeExpressible + Copy + Default + 'static,
176 const SRC_LAYOUT: u8,
177 const DST_LAYOUT: u8,
178 const LINEAR_CAP: usize,
179 const BIT_DEPTH: usize,
180> TransformExecutor<T>
181 for TransformShaperFloatScalar<T, SRC_LAYOUT, DST_LAYOUT, LINEAR_CAP, BIT_DEPTH>
182where
183 u32: AsPrimitive<T>,
184 f32: AsPrimitive<T>,
185{
186 fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
187 use crate::mlaf::mlaf;
188 let src_cn = Layout::from(SRC_LAYOUT);
189 let dst_cn = Layout::from(DST_LAYOUT);
190 let src_channels = src_cn.channels();
191 let dst_channels = dst_cn.channels();
192
193 if src.len() / src_channels != dst.len() / dst_channels {
194 return Err(CmsError::LaneSizeMismatch);
195 }
196 if src.len() % src_channels != 0 {
197 return Err(CmsError::LaneMultipleOfChannels);
198 }
199 if dst.len() % dst_channels != 0 {
200 return Err(CmsError::LaneMultipleOfChannels);
201 }
202
203 let transform = self.profile.adaptation_matrix;
204 let max_colors: T = ((1 << BIT_DEPTH) - 1).as_();
205
206 for (src, dst) in src
207 .chunks_exact(src_channels)
208 .zip(dst.chunks_exact_mut(dst_channels))
209 {
210 let r = self.profile.r_linear[src[src_cn.r_i()]._as_usize()];
211 let g = self.profile.g_linear[src[src_cn.g_i()]._as_usize()];
212 let b = self.profile.b_linear[src[src_cn.b_i()]._as_usize()];
213 let a = if src_channels == 4 {
214 src[src_cn.a_i()]
215 } else {
216 max_colors
217 };
218
219 let new_r = mlaf(
220 mlaf(r * transform.v[0][0], g, transform.v[0][1]),
221 b,
222 transform.v[0][2],
223 );
224
225 let new_g = mlaf(
226 mlaf(r * transform.v[1][0], g, transform.v[1][1]),
227 b,
228 transform.v[1][2],
229 );
230
231 let new_b = mlaf(
232 mlaf(r * transform.v[2][0], g, transform.v[2][1]),
233 b,
234 transform.v[2][2],
235 );
236
237 let mut rgb = Rgb::new(new_r, new_g, new_b);
238 rgb = self.profile.gamma_evaluator.evaluate_tristimulus(rgb);
239
240 dst[dst_cn.r_i()] = rgb.r.as_();
241 dst[dst_cn.g_i()] = rgb.g.as_();
242 dst[dst_cn.b_i()] = rgb.b.as_();
243 if dst_channels == 4 {
244 dst[dst_cn.a_i()] = a;
245 }
246 }
247
248 Ok(())
249 }
250}
251
252impl<
253 T: Clone + PointeeSizeExpressible + Copy + Default + 'static + AsPrimitive<f32>,
254 const SRC_LAYOUT: u8,
255 const DST_LAYOUT: u8,
256> TransformExecutor<T> for TransformShaperRgbFloatInOut<T, SRC_LAYOUT, DST_LAYOUT>
257where
258 u32: AsPrimitive<T>,
259 f32: AsPrimitive<T>,
260{
261 fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
262 use crate::mlaf::mlaf;
263 let src_cn = Layout::from(SRC_LAYOUT);
264 let dst_cn = Layout::from(DST_LAYOUT);
265 let src_channels = src_cn.channels();
266 let dst_channels = dst_cn.channels();
267
268 if src.len() / src_channels != dst.len() / dst_channels {
269 return Err(CmsError::LaneSizeMismatch);
270 }
271 if src.len() % src_channels != 0 {
272 return Err(CmsError::LaneMultipleOfChannels);
273 }
274 if dst.len() % dst_channels != 0 {
275 return Err(CmsError::LaneMultipleOfChannels);
276 }
277
278 let transform = self.profile.adaptation_matrix;
279 let max_colors: T = ((1 << self.bit_depth) - 1).as_();
280
281 for (src, dst) in src
282 .chunks_exact(src_channels)
283 .zip(dst.chunks_exact_mut(dst_channels))
284 {
285 let mut src_rgb = Rgb::new(
286 src[src_cn.r_i()].as_(),
287 src[src_cn.g_i()].as_(),
288 src[src_cn.b_i()].as_(),
289 );
290 src_rgb = self.profile.linear_evaluator.evaluate_tristimulus(src_rgb);
291 let r = src_rgb.r;
292 let g = src_rgb.g;
293 let b = src_rgb.b;
294 let a = if src_channels == 4 {
295 src[src_cn.a_i()]
296 } else {
297 max_colors
298 };
299
300 let new_r = mlaf(
301 mlaf(r * transform.v[0][0], g, transform.v[0][1]),
302 b,
303 transform.v[0][2],
304 );
305
306 let new_g = mlaf(
307 mlaf(r * transform.v[1][0], g, transform.v[1][1]),
308 b,
309 transform.v[1][2],
310 );
311
312 let new_b = mlaf(
313 mlaf(r * transform.v[2][0], g, transform.v[2][1]),
314 b,
315 transform.v[2][2],
316 );
317
318 let mut rgb = Rgb::new(new_r, new_g, new_b);
319 rgb = self.profile.gamma_evaluator.evaluate_tristimulus(rgb);
320
321 dst[dst_cn.r_i()] = rgb.r.as_();
322 dst[dst_cn.g_i()] = rgb.g.as_();
323 dst[dst_cn.b_i()] = rgb.b.as_();
324
325 if dst_channels == 4 {
326 dst[dst_cn.a_i()] = a;
327 }
328 }
329
330 Ok(())
331 }
332}