moxcms/conversions/katana/
md4x3.rs1use crate::conversions::katana::KatanaInitialStage;
30use crate::conversions::katana::md3x3::MultidimensionalDirection;
31use crate::mlaf::mlaf;
32use crate::safe_math::SafeMul;
33use crate::trc::lut_interp_linear_float;
34use crate::{
35 CmsError, DataColorSpace, Hypercube, InterpolationMethod, LutMultidimensionalType,
36 MalformedSize, Matrix3d, Matrix3f, PointeeSizeExpressible, TransformOptions, Vector3d,
37 Vector3f,
38};
39use num_traits::AsPrimitive;
40use std::marker::PhantomData;
41
42pub(crate) fn execute_simple_curves3(dst: &mut [f32], curves: &[Vec<f32>; 3]) {
43 let curve0 = &curves[0];
44 let curve1 = &curves[1];
45 let curve2 = &curves[2];
46
47 for dst in dst.chunks_exact_mut(3) {
48 let a0 = dst[0];
49 let a1 = dst[1];
50 let a2 = dst[2];
51 let b0 = lut_interp_linear_float(a0, curve0);
52 let b1 = lut_interp_linear_float(a1, curve1);
53 let b2 = lut_interp_linear_float(a2, curve2);
54 dst[0] = b0;
55 dst[1] = b1;
56 dst[2] = b2;
57 }
58}
59
60pub(crate) fn execute_matrix_stage3(matrix: Matrix3f, bias: Vector3f, dst: &mut [f32]) {
61 let m = matrix;
62 let b = bias;
63
64 if !m.test_equality(Matrix3f::IDENTITY) || !b.eq(&Vector3f::default()) {
65 for dst in dst.chunks_exact_mut(3) {
66 let x = dst[0];
67 let y = dst[1];
68 let z = dst[2];
69 dst[0] = mlaf(mlaf(mlaf(b.v[0], x, m.v[0][0]), y, m.v[0][1]), z, m.v[0][2]);
70 dst[1] = mlaf(mlaf(mlaf(b.v[1], x, m.v[1][0]), y, m.v[1][1]), z, m.v[1][2]);
71 dst[2] = mlaf(mlaf(mlaf(b.v[2], x, m.v[2][0]), y, m.v[2][1]), z, m.v[2][2]);
72 }
73 }
74}
75
76struct Multidimensional4x3<
77 T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
78> {
79 a_curves: Option<Box<[Vec<f32>; 4]>>,
80 m_curves: Option<Box<[Vec<f32>; 3]>>,
81 b_curves: Option<Box<[Vec<f32>; 3]>>,
82 clut: Option<Vec<f32>>,
83 matrix: Matrix3f,
84 bias: Vector3f,
85 direction: MultidimensionalDirection,
86 options: TransformOptions,
87 pcs: DataColorSpace,
88 grid_size: [u8; 4],
89 _phantom: PhantomData<T>,
90 bit_depth: usize,
91}
92
93impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
94 Multidimensional4x3<T>
95{
96 fn to_pcs_impl<Fetch: Fn(f32, f32, f32, f32) -> Vector3f>(
97 &self,
98 input: &[T],
99 dst: &mut [f32],
100 fetch: Fetch,
101 ) -> Result<(), CmsError> {
102 let norm_value = if T::FINITE {
103 1.0 / ((1u32 << self.bit_depth) - 1) as f32
104 } else {
105 1.0
106 };
107 assert_eq!(
108 self.direction,
109 MultidimensionalDirection::DeviceToPcs,
110 "PCS to device cannot be used on `to pcs` stage"
111 );
112
113 if let (Some(a_curves), Some(clut)) = (self.a_curves.as_ref(), self.clut.as_ref()) {
117 if !clut.is_empty() {
118 let curve0 = &a_curves[0];
119 let curve1 = &a_curves[1];
120 let curve2 = &a_curves[2];
121 let curve3 = &a_curves[3];
122 for (src, dst) in input.chunks_exact(4).zip(dst.chunks_exact_mut(3)) {
123 let b0 = lut_interp_linear_float(src[0].as_() * norm_value, curve0);
124 let b1 = lut_interp_linear_float(src[1].as_() * norm_value, curve1);
125 let b2 = lut_interp_linear_float(src[2].as_() * norm_value, curve2);
126 let b3 = lut_interp_linear_float(src[3].as_() * norm_value, curve3);
127 let interpolated = fetch(b0, b1, b2, b3);
128 dst[0] = interpolated.v[0];
129 dst[1] = interpolated.v[1];
130 dst[2] = interpolated.v[2];
131 }
132 }
133 } else {
134 return Err(CmsError::InvalidAtoBLut);
135 }
136
137 if let Some(m_curves) = self.m_curves.as_ref() {
140 execute_simple_curves3(dst, m_curves);
141 execute_matrix_stage3(self.matrix, self.bias, dst);
142 }
143
144 if let Some(b_curves) = &self.b_curves.as_ref() {
146 execute_simple_curves3(dst, b_curves);
147 }
148
149 Ok(())
150 }
151}
152
153impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
154 KatanaInitialStage<f32, T> for Multidimensional4x3<T>
155{
156 fn to_pcs(&self, input: &[T]) -> Result<Vec<f32>, CmsError> {
157 if input.len() % 4 != 0 {
158 return Err(CmsError::LaneMultipleOfChannels);
159 }
160 let fixed_new_clut = Vec::new();
161 let new_clut = self.clut.as_ref().unwrap_or(&fixed_new_clut);
162 let lut = Hypercube::new_hypercube(new_clut, self.grid_size);
163
164 let mut new_dst = vec![0f32; (input.len() / 4) * 3];
165
166 if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
168 self.to_pcs_impl(input, &mut new_dst, |x, y, z, w| {
169 lut.quadlinear_vec3(x, y, z, w)
170 })?;
171 return Ok(new_dst);
172 }
173
174 match self.options.interpolation_method {
175 #[cfg(feature = "options")]
176 InterpolationMethod::Tetrahedral => {
177 self.to_pcs_impl(input, &mut new_dst, |x, y, z, w| lut.tetra_vec3(x, y, z, w))?;
178 }
179 #[cfg(feature = "options")]
180 InterpolationMethod::Pyramid => {
181 self.to_pcs_impl(input, &mut new_dst, |x, y, z, w| {
182 lut.pyramid_vec3(x, y, z, w)
183 })?;
184 }
185 #[cfg(feature = "options")]
186 InterpolationMethod::Prism => {
187 self.to_pcs_impl(input, &mut new_dst, |x, y, z, w| lut.prism_vec3(x, y, z, w))?;
188 }
189 InterpolationMethod::Linear => {
190 self.to_pcs_impl(input, &mut new_dst, |x, y, z, w| {
191 lut.quadlinear_vec3(x, y, z, w)
192 })?;
193 }
194 }
195 Ok(new_dst)
196 }
197}
198
199fn make_multidimensional_4x3<
200 T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
201>(
202 mab: &LutMultidimensionalType,
203 options: TransformOptions,
204 pcs: DataColorSpace,
205 direction: MultidimensionalDirection,
206 bit_depth: usize,
207) -> Result<Multidimensional4x3<T>, CmsError> {
208 if mab.num_input_channels != 4 && mab.num_output_channels != 3 {
209 return Err(CmsError::UnsupportedProfileConnection);
210 }
211 if mab.b_curves.is_empty() || mab.b_curves.len() != 3 {
212 return Err(CmsError::InvalidAtoBLut);
213 }
214
215 let grid_size = [
216 mab.grid_points[0],
217 mab.grid_points[1],
218 mab.grid_points[2],
219 mab.grid_points[3],
220 ];
221
222 let clut: Option<Vec<f32>> = if mab.a_curves.len() == 4 && mab.clut.is_some() {
223 let clut = mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
224 let lut_grid = (mab.grid_points[0] as usize)
225 .safe_mul(mab.grid_points[1] as usize)?
226 .safe_mul(mab.grid_points[2] as usize)?
227 .safe_mul(mab.grid_points[3] as usize)?
228 .safe_mul(mab.num_output_channels as usize)?;
229 if clut.len() != lut_grid {
230 return Err(CmsError::MalformedCurveLutTable(MalformedSize {
231 size: clut.len(),
232 expected: lut_grid,
233 }));
234 }
235 Some(clut)
236 } else {
237 return Err(CmsError::InvalidAtoBLut);
238 };
239
240 let a_curves: Option<Box<[Vec<f32>; 4]>> = if mab.a_curves.len() == 4 && mab.clut.is_some() {
241 let mut arr = Box::<[Vec<f32>; 4]>::default();
242 for (a_curve, dst) in mab.a_curves.iter().zip(arr.iter_mut()) {
243 *dst = a_curve.to_clut()?;
244 }
245 Some(arr)
246 } else {
247 None
248 };
249
250 let b_curves: Option<Box<[Vec<f32>; 3]>> = if mab.b_curves.len() == 3 {
251 let mut arr = Box::<[Vec<f32>; 3]>::default();
252 let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
253 if all_curves_linear {
254 None
255 } else {
256 for (c_curve, dst) in mab.b_curves.iter().zip(arr.iter_mut()) {
257 *dst = c_curve.to_clut()?;
258 }
259 Some(arr)
260 }
261 } else {
262 return Err(CmsError::InvalidAtoBLut);
263 };
264
265 let matrix = mab.matrix.to_f32();
266
267 let m_curves: Option<Box<[Vec<f32>; 3]>> = if mab.m_curves.len() == 3 {
268 let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
269 if !all_curves_linear
270 || !mab.matrix.test_equality(Matrix3d::IDENTITY)
271 || mab.bias.ne(&Vector3d::default())
272 {
273 let mut arr = Box::<[Vec<f32>; 3]>::default();
274 for (curve, dst) in mab.m_curves.iter().zip(arr.iter_mut()) {
275 *dst = curve.to_clut()?;
276 }
277 Some(arr)
278 } else {
279 None
280 }
281 } else {
282 None
283 };
284
285 let bias = mab.bias.cast();
286
287 let transform = Multidimensional4x3::<T> {
288 a_curves,
289 b_curves,
290 m_curves,
291 matrix,
292 direction,
293 options,
294 clut,
295 pcs,
296 grid_size,
297 bias,
298 _phantom: PhantomData,
299 bit_depth,
300 };
301
302 Ok(transform)
303}
304
305pub(crate) fn multi_dimensional_4x3_to_pcs<
306 T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
307>(
308 mab: &LutMultidimensionalType,
309 options: TransformOptions,
310 pcs: DataColorSpace,
311 bit_depth: usize,
312) -> Result<Box<dyn KatanaInitialStage<f32, T> + Send + Sync>, CmsError> {
313 let transform = make_multidimensional_4x3::<T>(
314 mab,
315 options,
316 pcs,
317 MultidimensionalDirection::DeviceToPcs,
318 bit_depth,
319 )?;
320 Ok(Box::new(transform))
321}