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