1use crate::conversions::mab::{BCurves3, MCurves3};
30use crate::err::try_vec;
31use crate::safe_math::SafeMul;
32use crate::{
33 CmsError, DataColorSpace, Hypercube, InPlaceStage, InterpolationMethod,
34 LutMultidimensionalType, MalformedSize, Matrix3d, Stage, TransformOptions, Vector3d, Vector3f,
35};
36
37#[allow(dead_code)]
38struct ACurves4x3<'a> {
39 curve0: Box<[f32; 65536]>,
40 curve1: Box<[f32; 65536]>,
41 curve2: Box<[f32; 65536]>,
42 curve3: Box<[f32; 65536]>,
43 clut: &'a [f32],
44 grid_size: [u8; 4],
45 interpolation_method: InterpolationMethod,
46 pcs: DataColorSpace,
47 depth: usize,
48}
49
50#[allow(dead_code)]
51struct ACurves4x3Optimized<'a> {
52 clut: &'a [f32],
53 grid_size: [u8; 4],
54 interpolation_method: InterpolationMethod,
55 pcs: DataColorSpace,
56}
57
58#[allow(dead_code)]
59impl ACurves4x3<'_> {
60 fn transform_impl<Fetch: Fn(f32, f32, f32, f32) -> Vector3f>(
61 &self,
62 src: &[f32],
63 dst: &mut [f32],
64 fetch: Fetch,
65 ) -> Result<(), CmsError> {
66 let scale_value = (self.depth - 1) as f32;
67
68 assert_eq!(src.len() / 4, dst.len() / 3);
69
70 for (src, dst) in src.chunks_exact(4).zip(dst.chunks_exact_mut(3)) {
71 let a0 = (src[0] * scale_value).round().min(scale_value) as u16;
72 let a1 = (src[1] * scale_value).round().min(scale_value) as u16;
73 let a2 = (src[2] * scale_value).round().min(scale_value) as u16;
74 let a3 = (src[3] * scale_value).round().min(scale_value) as u16;
75 let c = self.curve0[a0 as usize];
76 let m = self.curve1[a1 as usize];
77 let y = self.curve2[a2 as usize];
78 let k = self.curve3[a3 as usize];
79
80 let r = fetch(c, m, y, k);
81 dst[0] = r.v[0];
82 dst[1] = r.v[1];
83 dst[2] = r.v[2];
84 }
85 Ok(())
86 }
87}
88
89#[allow(dead_code)]
90impl ACurves4x3Optimized<'_> {
91 fn transform_impl<Fetch: Fn(f32, f32, f32, f32) -> Vector3f>(
92 &self,
93 src: &[f32],
94 dst: &mut [f32],
95 fetch: Fetch,
96 ) -> Result<(), CmsError> {
97 assert_eq!(src.len() / 4, dst.len() / 3);
98
99 for (src, dst) in src.chunks_exact(4).zip(dst.chunks_exact_mut(3)) {
100 let c = src[0];
101 let m = src[1];
102 let y = src[2];
103 let k = src[3];
104
105 let r = fetch(c, m, y, k);
106 dst[0] = r.v[0];
107 dst[1] = r.v[1];
108 dst[2] = r.v[2];
109 }
110 Ok(())
111 }
112}
113
114impl Stage for ACurves4x3<'_> {
115 fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
116 let lut = Hypercube::new_checked_hypercube(self.clut, self.grid_size, 3)?;
117
118 if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
120 return self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w));
121 }
122
123 match self.interpolation_method {
124 #[cfg(feature = "options")]
125 InterpolationMethod::Tetrahedral => {
126 self.transform_impl(src, dst, |x, y, z, w| lut.tetra_vec3(x, y, z, w))?;
127 }
128 #[cfg(feature = "options")]
129 InterpolationMethod::Pyramid => {
130 self.transform_impl(src, dst, |x, y, z, w| lut.pyramid_vec3(x, y, z, w))?;
131 }
132 #[cfg(feature = "options")]
133 InterpolationMethod::Prism => {
134 self.transform_impl(src, dst, |x, y, z, w| lut.prism_vec3(x, y, z, w))?;
135 }
136 InterpolationMethod::Linear => {
137 self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w))?;
138 }
139 }
140 Ok(())
141 }
142}
143
144impl Stage for ACurves4x3Optimized<'_> {
145 fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
146 let lut = Hypercube::new_checked_hypercube(self.clut, self.grid_size, 3)?;
147
148 if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
150 return self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w));
151 }
152
153 match self.interpolation_method {
154 #[cfg(feature = "options")]
155 InterpolationMethod::Tetrahedral => {
156 self.transform_impl(src, dst, |x, y, z, w| lut.tetra_vec3(x, y, z, w))?;
157 }
158 #[cfg(feature = "options")]
159 InterpolationMethod::Pyramid => {
160 self.transform_impl(src, dst, |x, y, z, w| lut.pyramid_vec3(x, y, z, w))?;
161 }
162 #[cfg(feature = "options")]
163 InterpolationMethod::Prism => {
164 self.transform_impl(src, dst, |x, y, z, w| lut.prism_vec3(x, y, z, w))?;
165 }
166 InterpolationMethod::Linear => {
167 self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w))?;
168 }
169 }
170 Ok(())
171 }
172}
173
174pub(crate) fn prepare_mab_4x3(
175 mab: &LutMultidimensionalType,
176 lut: &mut [f32],
177 options: TransformOptions,
178 pcs: DataColorSpace,
179) -> Result<Vec<f32>, CmsError> {
180 const LERP_DEPTH: usize = 65536;
181 const BP: usize = 13;
182 const DEPTH: usize = 8192;
183 if mab.num_input_channels != 4 || mab.num_output_channels != 3 {
184 return Err(CmsError::UnsupportedProfileConnection);
185 }
186 let mut new_lut = try_vec![0f32; (lut.len() / 4) * 3];
187 if mab.a_curves.len() == 4 && mab.clut.is_some() {
188 let clut = &mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
189
190 let lut_grid = (mab.grid_points[0] as usize)
191 .safe_mul(mab.grid_points[1] as usize)?
192 .safe_mul(mab.grid_points[2] as usize)?
193 .safe_mul(mab.grid_points[3] as usize)?
194 .safe_mul(mab.num_output_channels as usize)?;
195 if clut.len() != lut_grid {
196 return Err(CmsError::MalformedClut(MalformedSize {
197 size: clut.len(),
198 expected: lut_grid,
199 }));
200 }
201
202 let all_curves_linear = mab.a_curves.iter().all(|curve| curve.is_linear());
203 let grid_size = [
204 mab.grid_points[0],
205 mab.grid_points[1],
206 mab.grid_points[2],
207 mab.grid_points[3],
208 ];
209
210 if all_curves_linear {
211 let l = ACurves4x3Optimized {
212 clut,
213 grid_size,
214 interpolation_method: options.interpolation_method,
215 pcs,
216 };
217 l.transform(lut, &mut new_lut)?;
218 } else {
219 let curves: Result<Vec<_>, _> = mab
220 .a_curves
221 .iter()
222 .map(|c| {
223 c.build_linearize_table::<u16, LERP_DEPTH, BP>()
224 .ok_or(CmsError::InvalidTrcCurve)
225 })
226 .collect();
227
228 let [curve0, curve1, curve2, curve3] =
229 curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
230 let l = ACurves4x3 {
231 curve0,
232 curve1,
233 curve2,
234 curve3,
235 clut,
236 grid_size,
237 interpolation_method: options.interpolation_method,
238 pcs,
239 depth: DEPTH,
240 };
241 l.transform(lut, &mut new_lut)?;
242 }
243 } else {
244 return Err(CmsError::UnsupportedProfileConnection);
246 }
247
248 if mab.m_curves.len() == 3 {
249 let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
250 if !all_curves_linear
251 || !mab.matrix.test_equality(Matrix3d::IDENTITY)
252 || mab.bias.ne(&Vector3d::default())
253 {
254 let curves: Result<Vec<_>, _> = mab
255 .m_curves
256 .iter()
257 .map(|c| {
258 c.build_linearize_table::<u16, LERP_DEPTH, BP>()
259 .ok_or(CmsError::InvalidTrcCurve)
260 })
261 .collect();
262
263 let [curve0, curve1, curve2] =
264 curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
265
266 let matrix = mab.matrix.to_f32();
267 let bias: Vector3f = mab.bias.cast();
268 let m_curves = MCurves3 {
269 curve0,
270 curve1,
271 curve2,
272 matrix,
273 bias,
274 inverse: false,
275 depth: DEPTH,
276 };
277 m_curves.transform(&mut new_lut)?;
278 }
279 }
280
281 if mab.b_curves.len() == 3 {
282 let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
283 if !all_curves_linear {
284 let curves: Result<Vec<_>, _> = mab
285 .b_curves
286 .iter()
287 .map(|c| {
288 c.build_linearize_table::<u16, LERP_DEPTH, BP>()
289 .ok_or(CmsError::InvalidTrcCurve)
290 })
291 .collect();
292
293 let [curve0, curve1, curve2] =
294 curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
295 let b_curves = BCurves3::<DEPTH> {
296 curve0,
297 curve1,
298 curve2,
299 };
300 b_curves.transform(&mut new_lut)?;
301 }
302 } else {
303 return Err(CmsError::InvalidAtoBLut);
304 }
305
306 Ok(new_lut)
307}