moxcms/conversions/katana/
md_3xn.rs1use crate::conversions::katana::KatanaFinalStage;
30use crate::conversions::katana::md3x3::MultidimensionalDirection;
31use crate::conversions::katana::md4x3::{execute_matrix_stage3, execute_simple_curves3};
32use crate::conversions::md_lut::{MultidimensionalLut, tetra_3i_to_any_vec};
33use crate::safe_math::SafeMul;
34use crate::trc::lut_interp_linear_float;
35use crate::{
36 CmsError, DataColorSpace, Layout, LutMultidimensionalType, MalformedSize, Matrix3d, Matrix3f,
37 PointeeSizeExpressible, TransformOptions, Vector3d, Vector3f,
38};
39use num_traits::AsPrimitive;
40use std::marker::PhantomData;
41
42struct Multidimensional3xN<
43 T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
44> {
45 a_curves: Option<Vec<Vec<f32>>>,
46 m_curves: Option<Box<[Vec<f32>; 3]>>,
47 b_curves: Option<Box<[Vec<f32>; 3]>>,
48 clut: Option<Vec<f32>>,
49 matrix: Matrix3f,
50 bias: Vector3f,
51 direction: MultidimensionalDirection,
52 grid_size: [u8; 16],
53 output_inks: usize,
54 _phantom: PhantomData<T>,
55 dst_layout: Layout,
56 bit_depth: usize,
57}
58
59impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
60 Multidimensional3xN<T>
61where
62 f32: AsPrimitive<T>,
63{
64 fn to_output_impl(&self, src: &mut [f32], dst: &mut [T]) -> Result<(), CmsError> {
65 let norm_value = if T::FINITE {
66 ((1u32 << self.bit_depth) - 1) as f32
67 } else {
68 1.0
69 };
70 assert_eq!(
71 self.direction,
72 MultidimensionalDirection::PcsToDevice,
73 "PCS to device cannot be used on `to pcs` stage"
74 );
75
76 if let Some(b_curves) = &self.b_curves.as_ref() {
78 execute_simple_curves3(src, b_curves);
79 }
80
81 if let Some(m_curves) = self.m_curves.as_ref() {
84 execute_matrix_stage3(self.matrix, self.bias, src);
85 execute_simple_curves3(src, m_curves);
86 }
87
88 if let (Some(a_curves), Some(clut)) = (self.a_curves.as_ref(), self.clut.as_ref()) {
89 let mut inks = vec![0.; self.output_inks];
90
91 if clut.is_empty() {
92 return Err(CmsError::InvalidAtoBLut);
93 }
94
95 let md_lut = MultidimensionalLut::new(self.grid_size, 3, self.output_inks);
96
97 for (src, dst) in src
98 .chunks_exact(3)
99 .zip(dst.chunks_exact_mut(self.dst_layout.channels()))
100 {
101 tetra_3i_to_any_vec(
102 &md_lut,
103 clut,
104 src[0],
105 src[1],
106 src[2],
107 &mut inks,
108 self.output_inks,
109 );
110
111 for (ink, curve) in inks.iter_mut().zip(a_curves.iter()) {
112 *ink = lut_interp_linear_float(*ink, curve);
113 }
114
115 if T::FINITE {
116 for (dst, ink) in dst.iter_mut().zip(inks.iter()) {
117 *dst = (*ink * norm_value).round().max(0.).min(norm_value).as_();
118 }
119 } else {
120 for (dst, ink) in dst.iter_mut().zip(inks.iter()) {
121 *dst = (*ink * norm_value).as_();
122 }
123 }
124 }
125 } else {
126 return Err(CmsError::InvalidAtoBLut);
127 }
128
129 Ok(())
130 }
131}
132
133impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
134 KatanaFinalStage<f32, T> for Multidimensional3xN<T>
135where
136 f32: AsPrimitive<T>,
137{
138 fn to_output(&self, src: &mut [f32], dst: &mut [T]) -> Result<(), CmsError> {
139 if src.len() % 3 != 0 {
140 return Err(CmsError::LaneMultipleOfChannels);
141 }
142 if dst.len() % self.output_inks != 0 {
143 return Err(CmsError::LaneMultipleOfChannels);
144 }
145
146 self.to_output_impl(src, dst)?;
147 Ok(())
148 }
149}
150
151fn make_multidimensional_nx3<
152 T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
153>(
154 dst_layout: Layout,
155 mab: &LutMultidimensionalType,
156 _: TransformOptions,
157 pcs: DataColorSpace,
158 direction: MultidimensionalDirection,
159 bit_depth: usize,
160) -> Result<Multidimensional3xN<T>, CmsError> {
161 let real_inks = if pcs == DataColorSpace::Rgb {
162 3
163 } else {
164 dst_layout.channels()
165 };
166
167 if mab.num_output_channels != real_inks as u8 {
168 return Err(CmsError::UnsupportedProfileConnection);
169 }
170
171 if mab.b_curves.is_empty() || mab.b_curves.len() != 3 {
172 return Err(CmsError::InvalidAtoBLut);
173 }
174
175 let clut: Option<Vec<f32>> =
176 if mab.a_curves.len() == mab.num_output_channels as usize && mab.clut.is_some() {
177 let clut = mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
178 let mut lut_grid = 1usize;
179 for grid in mab.grid_points.iter().take(mab.num_input_channels as usize) {
180 lut_grid = lut_grid.safe_mul(*grid as usize)?;
181 }
182 let lut_grid = lut_grid.safe_mul(mab.num_output_channels as usize)?;
183 if clut.len() != lut_grid {
184 return Err(CmsError::MalformedCurveLutTable(MalformedSize {
185 size: clut.len(),
186 expected: lut_grid,
187 }));
188 }
189 Some(clut)
190 } else {
191 return Err(CmsError::InvalidAtoBLut);
192 };
193
194 let a_curves: Option<Vec<Vec<f32>>> =
195 if mab.a_curves.len() == mab.num_output_channels as usize && mab.clut.is_some() {
196 let mut arr = Vec::new();
197 for a_curve in mab.a_curves.iter() {
198 arr.push(a_curve.to_clut()?);
199 }
200 Some(arr)
201 } else {
202 None
203 };
204
205 let b_curves: Option<Box<[Vec<f32>; 3]>> = if mab.b_curves.len() == 3 {
206 let mut arr = Box::<[Vec<f32>; 3]>::default();
207 let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
208 if all_curves_linear {
209 None
210 } else {
211 for (c_curve, dst) in mab.b_curves.iter().zip(arr.iter_mut()) {
212 *dst = c_curve.to_clut()?;
213 }
214 Some(arr)
215 }
216 } else {
217 return Err(CmsError::InvalidAtoBLut);
218 };
219
220 let matrix = mab.matrix.to_f32();
221
222 let m_curves: Option<Box<[Vec<f32>; 3]>> = if mab.m_curves.len() == 3 {
223 let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
224 if !all_curves_linear
225 || !mab.matrix.test_equality(Matrix3d::IDENTITY)
226 || mab.bias.ne(&Vector3d::default())
227 {
228 let mut arr = Box::<[Vec<f32>; 3]>::default();
229 for (curve, dst) in mab.m_curves.iter().zip(arr.iter_mut()) {
230 *dst = curve.to_clut()?;
231 }
232 Some(arr)
233 } else {
234 None
235 }
236 } else {
237 None
238 };
239
240 let bias = mab.bias.cast();
241
242 let transform = Multidimensional3xN::<T> {
243 a_curves,
244 b_curves,
245 m_curves,
246 matrix,
247 direction,
248 clut,
249 grid_size: mab.grid_points,
250 bias,
251 dst_layout,
252 output_inks: real_inks,
253 _phantom: PhantomData,
254 bit_depth,
255 };
256
257 Ok(transform)
258}
259
260pub(crate) fn katana_multi_dimensional_3xn_to_device<
261 T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
262>(
263 dst_layout: Layout,
264 mab: &LutMultidimensionalType,
265 options: TransformOptions,
266 pcs: DataColorSpace,
267 bit_depth: usize,
268) -> Result<Box<dyn KatanaFinalStage<f32, T> + Send + Sync>, CmsError>
269where
270 f32: AsPrimitive<T>,
271{
272 if mab.num_input_channels == 0 {
273 return Err(CmsError::UnsupportedProfileConnection);
274 }
275 let transform = make_multidimensional_nx3::<T>(
276 dst_layout,
277 mab,
278 options,
279 pcs,
280 MultidimensionalDirection::PcsToDevice,
281 bit_depth,
282 )?;
283 Ok(Box::new(transform))
284}