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