1use crate::conversions::interpolator::*;
30use crate::conversions::lut_transforms::Lut4x3Factory;
31use crate::math::{FusedMultiplyAdd, FusedMultiplyNegAdd, m_clamp};
32use crate::{
33 BarycentricWeightScale, CmsError, DataColorSpace, InterpolationMethod, Layout,
34 PointeeSizeExpressible, TransformExecutor, TransformOptions, Vector3f,
35};
36use num_traits::AsPrimitive;
37use std::marker::PhantomData;
38
39pub(crate) trait Vector3fCmykLerp {
40 fn interpolate(a: Vector3f, b: Vector3f, t: f32, scale: f32) -> Vector3f;
41}
42
43#[allow(unused)]
44#[derive(Copy, Clone, Default)]
45struct DefaultVector3fLerp;
46
47impl Vector3fCmykLerp for DefaultVector3fLerp {
48 #[inline(always)]
49 fn interpolate(a: Vector3f, b: Vector3f, t: f32, scale: f32) -> Vector3f {
50 let t = Vector3f::from(t);
51 let inter = a.neg_mla(a, t).mla(b, t);
52 let mut new_vec = Vector3f::from(0.5).mla(inter, Vector3f::from(scale));
53 new_vec.v[0] = m_clamp(new_vec.v[0], 0.0, scale);
54 new_vec.v[1] = m_clamp(new_vec.v[1], 0.0, scale);
55 new_vec.v[2] = m_clamp(new_vec.v[2], 0.0, scale);
56 new_vec
57 }
58}
59
60#[allow(unused)]
61#[derive(Copy, Clone, Default)]
62pub(crate) struct NonFiniteVector3fLerp;
63
64impl Vector3fCmykLerp for NonFiniteVector3fLerp {
65 #[inline(always)]
66 fn interpolate(a: Vector3f, b: Vector3f, t: f32, _: f32) -> Vector3f {
67 let t = Vector3f::from(t);
68 a.neg_mla(a, t).mla(b, t)
69 }
70}
71
72#[allow(unused)]
73#[derive(Copy, Clone, Default)]
74pub(crate) struct NonFiniteVector3fLerpUnbound;
75
76impl Vector3fCmykLerp for NonFiniteVector3fLerpUnbound {
77 #[inline(always)]
78 fn interpolate(a: Vector3f, b: Vector3f, t: f32, _: f32) -> Vector3f {
79 let t = Vector3f::from(t);
80 a.neg_mla(a, t).mla(b, t)
81 }
82}
83
84#[allow(unused)]
85struct TransformLut4To3<
86 T,
87 U,
88 const LAYOUT: u8,
89 const GRID_SIZE: usize,
90 const BIT_DEPTH: usize,
91 const BINS: usize,
92 const BARYCENTRIC_BINS: usize,
93> {
94 lut: Vec<f32>,
95 _phantom: PhantomData<T>,
96 _phantom1: PhantomData<U>,
97 interpolation_method: InterpolationMethod,
98 weights: Box<[BarycentricWeight<f32>; BINS]>,
99 color_space: DataColorSpace,
100 is_linear: bool,
101}
102
103#[allow(unused)]
104impl<
105 T: Copy + AsPrimitive<f32> + Default,
106 U: AsPrimitive<usize>,
107 const LAYOUT: u8,
108 const GRID_SIZE: usize,
109 const BIT_DEPTH: usize,
110 const BINS: usize,
111 const BARYCENTRIC_BINS: usize,
112> TransformLut4To3<T, U, LAYOUT, GRID_SIZE, BIT_DEPTH, BINS, BARYCENTRIC_BINS>
113where
114 f32: AsPrimitive<T>,
115 u32: AsPrimitive<T>,
116 (): LutBarycentricReduction<T, U>,
117{
118 #[inline(never)]
119 fn transform_chunk<Interpolation: Vector3fCmykLerp>(
120 &self,
121 src: &[T],
122 dst: &mut [T],
123 interpolator: Box<dyn MultidimensionalInterpolation + Send + Sync>,
124 ) {
125 let cn = Layout::from(LAYOUT);
126 let channels = cn.channels();
127 let grid_size = GRID_SIZE as i32;
128 let grid_size3 = grid_size * grid_size * grid_size;
129
130 let value_scale = ((1 << BIT_DEPTH) - 1) as f32;
131 let max_value = ((1 << BIT_DEPTH) - 1u32).as_();
132
133 for (src, dst) in src.chunks_exact(4).zip(dst.chunks_exact_mut(channels)) {
134 let c = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
135 src[0],
136 );
137 let m = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
138 src[1],
139 );
140 let y = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
141 src[2],
142 );
143 let k = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
144 src[3],
145 );
146
147 let k_weights = self.weights[k.as_()];
148
149 let w: i32 = k_weights.x;
150 let w_n: i32 = k_weights.x_n;
151 let t: f32 = k_weights.w;
152
153 let table1 = &self.lut[(w * grid_size3 * 3) as usize..];
154 let table2 = &self.lut[(w_n * grid_size3 * 3) as usize..];
155
156 let r1 = interpolator.inter3(
157 table1,
158 &self.weights[c.as_()],
159 &self.weights[m.as_()],
160 &self.weights[y.as_()],
161 );
162 let r2 = interpolator.inter3(
163 table2,
164 &self.weights[c.as_()],
165 &self.weights[m.as_()],
166 &self.weights[y.as_()],
167 );
168 let r = Interpolation::interpolate(r1, r2, t, value_scale);
169 dst[cn.r_i()] = r.v[0].as_();
170 dst[cn.g_i()] = r.v[1].as_();
171 dst[cn.b_i()] = r.v[2].as_();
172 if channels == 4 {
173 dst[cn.a_i()] = max_value;
174 }
175 }
176 }
177}
178
179#[allow(unused)]
180impl<
181 T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible,
182 U: AsPrimitive<usize>,
183 const LAYOUT: u8,
184 const GRID_SIZE: usize,
185 const BIT_DEPTH: usize,
186 const BINS: usize,
187 const BARYCENTRIC_BINS: usize,
188> TransformExecutor<T>
189 for TransformLut4To3<T, U, LAYOUT, GRID_SIZE, BIT_DEPTH, BINS, BARYCENTRIC_BINS>
190where
191 f32: AsPrimitive<T>,
192 u32: AsPrimitive<T>,
193 (): LutBarycentricReduction<T, U>,
194{
195 fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
196 let cn = Layout::from(LAYOUT);
197 let channels = cn.channels();
198 if src.len() % 4 != 0 {
199 return Err(CmsError::LaneMultipleOfChannels);
200 }
201 if dst.len() % channels != 0 {
202 return Err(CmsError::LaneMultipleOfChannels);
203 }
204 let src_chunks = src.len() / 4;
205 let dst_chunks = dst.len() / channels;
206 if src_chunks != dst_chunks {
207 return Err(CmsError::LaneSizeMismatch);
208 }
209
210 if self.color_space == DataColorSpace::Lab
211 || (self.is_linear && self.color_space == DataColorSpace::Rgb)
212 || self.color_space == DataColorSpace::Xyz
213 {
214 if T::FINITE {
215 self.transform_chunk::<DefaultVector3fLerp>(
216 src,
217 dst,
218 Box::new(Trilinear::<GRID_SIZE> {}),
219 );
220 } else {
221 self.transform_chunk::<NonFiniteVector3fLerp>(
222 src,
223 dst,
224 Box::new(Trilinear::<GRID_SIZE> {}),
225 );
226 }
227 } else {
228 match self.interpolation_method {
229 #[cfg(feature = "options")]
230 InterpolationMethod::Tetrahedral => {
231 if T::FINITE {
232 self.transform_chunk::<DefaultVector3fLerp>(
233 src,
234 dst,
235 Box::new(Tetrahedral::<GRID_SIZE> {}),
236 );
237 } else {
238 self.transform_chunk::<NonFiniteVector3fLerp>(
239 src,
240 dst,
241 Box::new(Tetrahedral::<GRID_SIZE> {}),
242 );
243 }
244 }
245 #[cfg(feature = "options")]
246 InterpolationMethod::Pyramid => {
247 if T::FINITE {
248 self.transform_chunk::<DefaultVector3fLerp>(
249 src,
250 dst,
251 Box::new(Pyramidal::<GRID_SIZE> {}),
252 );
253 } else {
254 self.transform_chunk::<NonFiniteVector3fLerp>(
255 src,
256 dst,
257 Box::new(Pyramidal::<GRID_SIZE> {}),
258 );
259 }
260 }
261 #[cfg(feature = "options")]
262 InterpolationMethod::Prism => {
263 if T::FINITE {
264 self.transform_chunk::<DefaultVector3fLerp>(
265 src,
266 dst,
267 Box::new(Prismatic::<GRID_SIZE> {}),
268 );
269 } else {
270 self.transform_chunk::<NonFiniteVector3fLerp>(
271 src,
272 dst,
273 Box::new(Prismatic::<GRID_SIZE> {}),
274 );
275 }
276 }
277 InterpolationMethod::Linear => {
278 if T::FINITE {
279 self.transform_chunk::<DefaultVector3fLerp>(
280 src,
281 dst,
282 Box::new(Trilinear::<GRID_SIZE> {}),
283 );
284 } else {
285 self.transform_chunk::<NonFiniteVector3fLerp>(
286 src,
287 dst,
288 Box::new(Trilinear::<GRID_SIZE> {}),
289 );
290 }
291 }
292 }
293 }
294
295 Ok(())
296 }
297}
298
299#[allow(dead_code)]
300pub(crate) struct DefaultLut4x3Factory {}
301
302#[allow(dead_code)]
303impl Lut4x3Factory for DefaultLut4x3Factory {
304 fn make_transform_4x3<
305 T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible + 'static + Send + Sync,
306 const LAYOUT: u8,
307 const GRID_SIZE: usize,
308 const BIT_DEPTH: usize,
309 >(
310 lut: Vec<f32>,
311 options: TransformOptions,
312 color_space: DataColorSpace,
313 is_linear: bool,
314 ) -> Box<dyn TransformExecutor<T> + Sync + Send>
315 where
316 f32: AsPrimitive<T>,
317 u32: AsPrimitive<T>,
318 (): LutBarycentricReduction<T, u8>,
319 (): LutBarycentricReduction<T, u16>,
320 {
321 match options.barycentric_weight_scale {
322 BarycentricWeightScale::Low => {
323 Box::new(
324 TransformLut4To3::<T, u8, LAYOUT, GRID_SIZE, BIT_DEPTH, 256, 256> {
325 lut,
326 _phantom: PhantomData,
327 _phantom1: PhantomData,
328 interpolation_method: options.interpolation_method,
329 weights: BarycentricWeight::<f32>::create_ranged_256::<GRID_SIZE>(),
330 color_space,
331 is_linear,
332 },
333 )
334 }
335 #[cfg(feature = "options")]
336 BarycentricWeightScale::High => {
337 Box::new(
338 TransformLut4To3::<T, u16, LAYOUT, GRID_SIZE, BIT_DEPTH, 65536, 65536> {
339 lut,
340 _phantom: PhantomData,
341 _phantom1: PhantomData,
342 interpolation_method: options.interpolation_method,
343 weights: BarycentricWeight::<f32>::create_binned::<GRID_SIZE, 65536>(),
344 color_space,
345 is_linear,
346 },
347 )
348 }
349 }
350 }
351}