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