1use crate::conversions::mab::{BCurves3, MCurves3};
30use crate::safe_math::SafeMul;
31use crate::{
32 CmsError, DataColorSpace, Hypercube, InPlaceStage, InterpolationMethod,
33 LutMultidimensionalType, MalformedSize, Matrix3d, Stage, TransformOptions, Vector3d, Vector3f,
34};
35
36#[allow(dead_code)]
37struct ACurves4x3<'a, const DEPTH: usize> {
38 curve0: Box<[f32; 65536]>,
39 curve1: Box<[f32; 65536]>,
40 curve2: Box<[f32; 65536]>,
41 curve3: Box<[f32; 65536]>,
42 clut: &'a [f32],
43 grid_size: [u8; 4],
44 interpolation_method: InterpolationMethod,
45 pcs: DataColorSpace,
46}
47
48#[allow(dead_code)]
49struct ACurves4x3Optimized<'a> {
50 clut: &'a [f32],
51 grid_size: [u8; 4],
52 interpolation_method: InterpolationMethod,
53 pcs: DataColorSpace,
54}
55
56#[allow(dead_code)]
57impl<const DEPTH: usize> ACurves4x3<'_, DEPTH> {
58 fn transform_impl<Fetch: Fn(f32, f32, f32, f32) -> Vector3f>(
59 &self,
60 src: &[f32],
61 dst: &mut [f32],
62 fetch: Fetch,
63 ) -> Result<(), CmsError> {
64 let scale_value = (DEPTH - 1) as f32;
65
66 assert_eq!(src.len() / 4, dst.len() / 3);
67
68 for (src, dst) in src.chunks_exact(4).zip(dst.chunks_exact_mut(3)) {
69 let a0 = (src[0] * scale_value).round().min(scale_value) as u16;
70 let a1 = (src[1] * scale_value).round().min(scale_value) as u16;
71 let a2 = (src[2] * scale_value).round().min(scale_value) as u16;
72 let a3 = (src[3] * scale_value).round().min(scale_value) as u16;
73 let c = self.curve0[a0 as usize];
74 let m = self.curve1[a1 as usize];
75 let y = self.curve2[a2 as usize];
76 let k = self.curve3[a3 as usize];
77
78 let r = fetch(c, m, y, k);
79 dst[0] = r.v[0];
80 dst[1] = r.v[1];
81 dst[2] = r.v[2];
82 }
83 Ok(())
84 }
85}
86
87#[allow(dead_code)]
88impl ACurves4x3Optimized<'_> {
89 fn transform_impl<Fetch: Fn(f32, f32, f32, f32) -> Vector3f>(
90 &self,
91 src: &[f32],
92 dst: &mut [f32],
93 fetch: Fetch,
94 ) -> Result<(), CmsError> {
95 assert_eq!(src.len() / 4, dst.len() / 3);
96
97 for (src, dst) in src.chunks_exact(4).zip(dst.chunks_exact_mut(3)) {
98 let c = src[0];
99 let m = src[1];
100 let y = src[2];
101 let k = src[3];
102
103 let r = fetch(c, m, y, k);
104 dst[0] = r.v[0];
105 dst[1] = r.v[1];
106 dst[2] = r.v[2];
107 }
108 Ok(())
109 }
110}
111
112impl<const DEPTH: usize> Stage for ACurves4x3<'_, DEPTH> {
113 fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
114 let lut = Hypercube::new_hypercube(self.clut, self.grid_size);
115
116 if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
118 return self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w));
119 }
120
121 match self.interpolation_method {
122 #[cfg(feature = "options")]
123 InterpolationMethod::Tetrahedral => {
124 self.transform_impl(src, dst, |x, y, z, w| lut.tetra_vec3(x, y, z, w))?;
125 }
126 #[cfg(feature = "options")]
127 InterpolationMethod::Pyramid => {
128 self.transform_impl(src, dst, |x, y, z, w| lut.pyramid_vec3(x, y, z, w))?;
129 }
130 #[cfg(feature = "options")]
131 InterpolationMethod::Prism => {
132 self.transform_impl(src, dst, |x, y, z, w| lut.prism_vec3(x, y, z, w))?;
133 }
134 InterpolationMethod::Linear => {
135 self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w))?;
136 }
137 }
138 Ok(())
139 }
140}
141
142impl Stage for ACurves4x3Optimized<'_> {
143 fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
144 let lut = Hypercube::new_hypercube(self.clut, self.grid_size);
145
146 if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
148 return self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w));
149 }
150
151 match self.interpolation_method {
152 #[cfg(feature = "options")]
153 InterpolationMethod::Tetrahedral => {
154 self.transform_impl(src, dst, |x, y, z, w| lut.tetra_vec3(x, y, z, w))?;
155 }
156 #[cfg(feature = "options")]
157 InterpolationMethod::Pyramid => {
158 self.transform_impl(src, dst, |x, y, z, w| lut.pyramid_vec3(x, y, z, w))?;
159 }
160 #[cfg(feature = "options")]
161 InterpolationMethod::Prism => {
162 self.transform_impl(src, dst, |x, y, z, w| lut.prism_vec3(x, y, z, w))?;
163 }
164 InterpolationMethod::Linear => {
165 self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w))?;
166 }
167 }
168 Ok(())
169 }
170}
171
172pub(crate) fn prepare_mab_4x3(
173 mab: &LutMultidimensionalType,
174 lut: &mut [f32],
175 options: TransformOptions,
176 pcs: DataColorSpace,
177) -> Result<Vec<f32>, CmsError> {
178 const LERP_DEPTH: usize = 65536;
179 const BP: usize = 13;
180 const DEPTH: usize = 8192;
181 if mab.num_input_channels != 4 && mab.num_output_channels != 3 {
182 return Err(CmsError::UnsupportedProfileConnection);
183 }
184 let mut new_lut = vec![0f32; (lut.len() / 4) * 3];
185 if mab.a_curves.len() == 4 && mab.clut.is_some() {
186 let clut = &mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
187
188 let lut_grid = (mab.grid_points[0] as usize)
189 .safe_mul(mab.grid_points[1] as usize)?
190 .safe_mul(mab.grid_points[2] as usize)?
191 .safe_mul(mab.grid_points[3] as usize)?
192 .safe_mul(mab.num_output_channels as usize)?;
193 if clut.len() != lut_grid {
194 return Err(CmsError::MalformedClut(MalformedSize {
195 size: clut.len(),
196 expected: lut_grid,
197 }));
198 }
199
200 let all_curves_linear = mab.a_curves.iter().all(|curve| curve.is_linear());
201 let grid_size = [
202 mab.grid_points[0],
203 mab.grid_points[1],
204 mab.grid_points[2],
205 mab.grid_points[3],
206 ];
207
208 #[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
209 if all_curves_linear {
210 use crate::conversions::neon::ACurves4x3NeonOptimizedNeon;
211 let a_curves = ACurves4x3NeonOptimizedNeon {
212 clut,
213 grid_size,
214 interpolation_method: options.interpolation_method,
215 pcs,
216 };
217 a_curves.transform(lut, &mut new_lut)?;
218 } else {
219 use crate::conversions::neon::ACurves4x3Neon;
220 let curves: Result<Vec<_>, _> = mab
221 .a_curves
222 .iter()
223 .map(|c| {
224 c.build_linearize_table::<u16, LERP_DEPTH, BP>()
225 .ok_or(CmsError::InvalidTrcCurve)
226 })
227 .collect();
228
229 let [curve0, curve1, curve2, curve3] =
230 curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
231 let a_curves = ACurves4x3Neon::<DEPTH> {
232 curve0,
233 curve1,
234 curve2,
235 curve3,
236 clut,
237 grid_size,
238 interpolation_method: options.interpolation_method,
239 pcs,
240 };
241 a_curves.transform(lut, &mut new_lut)?;
242 }
243
244 #[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
245 {
246 let mut execution_box: Option<Box<dyn Stage>> = None;
247
248 if all_curves_linear {
249 #[cfg(all(target_arch = "x86_64", feature = "avx"))]
250 {
251 use crate::conversions::avx::ACurves4x3AvxFmaOptimized;
252 if std::arch::is_x86_feature_detected!("avx2")
253 && std::arch::is_x86_feature_detected!("fma")
254 {
255 execution_box = Some(Box::new(ACurves4x3AvxFmaOptimized {
256 clut,
257 grid_size,
258 interpolation_method: options.interpolation_method,
259 pcs,
260 }));
261 }
262 }
263 if execution_box.is_none() {
264 execution_box = Some(Box::new(ACurves4x3Optimized {
265 clut,
266 grid_size,
267 interpolation_method: options.interpolation_method,
268 pcs,
269 }));
270 }
271 } else {
272 #[cfg(all(target_arch = "x86_64", feature = "avx"))]
273 {
274 use crate::conversions::avx::ACurves4x3AvxFma;
275 if std::arch::is_x86_feature_detected!("avx2")
276 && std::arch::is_x86_feature_detected!("fma")
277 {
278 let curves: Result<Vec<_>, _> = mab
279 .a_curves
280 .iter()
281 .map(|c| {
282 c.build_linearize_table::<u16, LERP_DEPTH, BP>()
283 .ok_or(CmsError::InvalidTrcCurve)
284 })
285 .collect();
286
287 let [curve0, curve1, curve2, curve3] =
288 curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
289 execution_box = Some(Box::new(ACurves4x3AvxFma::<DEPTH> {
290 curve0,
291 curve1,
292 curve2,
293 curve3,
294 clut,
295 grid_size,
296 interpolation_method: options.interpolation_method,
297 pcs,
298 }));
299 }
300 }
301
302 if execution_box.is_none() {
303 let curves: Result<Vec<_>, _> = mab
304 .a_curves
305 .iter()
306 .map(|c| {
307 c.build_linearize_table::<u16, LERP_DEPTH, BP>()
308 .ok_or(CmsError::InvalidTrcCurve)
309 })
310 .collect();
311
312 let [curve0, curve1, curve2, curve3] =
313 curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
314 execution_box = Some(Box::new(ACurves4x3::<DEPTH> {
315 curve0,
316 curve1,
317 curve2,
318 curve3,
319 clut,
320 grid_size,
321 interpolation_method: options.interpolation_method,
322 pcs,
323 }));
324 }
325 }
326
327 execution_box
328 .expect("Sampler for Multidimensional 4x3 must be set")
329 .transform(lut, &mut new_lut)?;
330 }
331 } else {
332 return Err(CmsError::UnsupportedProfileConnection);
334 }
335
336 if mab.m_curves.len() == 3 {
337 let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
338 if !all_curves_linear
339 || !mab.matrix.test_equality(Matrix3d::IDENTITY)
340 || mab.bias.ne(&Vector3d::default())
341 {
342 let curves: Result<Vec<_>, _> = mab
343 .m_curves
344 .iter()
345 .map(|c| {
346 c.build_linearize_table::<u16, LERP_DEPTH, BP>()
347 .ok_or(CmsError::InvalidTrcCurve)
348 })
349 .collect();
350
351 let [curve0, curve1, curve2] =
352 curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
353
354 let matrix = mab.matrix.to_f32();
355 let bias: Vector3f = mab.bias.cast();
356 let m_curves = MCurves3::<DEPTH> {
357 curve0,
358 curve1,
359 curve2,
360 matrix,
361 bias,
362 inverse: false,
363 };
364 m_curves.transform(&mut new_lut)?;
365 }
366 }
367
368 if mab.b_curves.len() == 3 {
369 let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
370 if !all_curves_linear {
371 let curves: Result<Vec<_>, _> = mab
372 .b_curves
373 .iter()
374 .map(|c| {
375 c.build_linearize_table::<u16, LERP_DEPTH, BP>()
376 .ok_or(CmsError::InvalidTrcCurve)
377 })
378 .collect();
379
380 let [curve0, curve1, curve2] =
381 curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
382 let b_curves = BCurves3::<DEPTH> {
383 curve0,
384 curve1,
385 curve2,
386 };
387 b_curves.transform(&mut new_lut)?;
388 }
389 } else {
390 return Err(CmsError::InvalidAtoBLut);
391 }
392
393 Ok(new_lut)
394}