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(always)]
119 fn transform_chunk<
120 'k,
121 Tetrahedral: MultidimensionalInterpolation<'k, GRID_SIZE>,
122 Interpolation: Vector3fCmykLerp,
123 >(
124 &'k self,
125 src: &[T],
126 dst: &mut [T],
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 tetrahedral1 = Tetrahedral::new(table1);
160 let tetrahedral2 = Tetrahedral::new(table2);
161 let r1 = tetrahedral1.inter3(c, m, y, &self.weights);
162 let r2 = tetrahedral2.inter3(c, m, y, &self.weights);
163 let r = Interpolation::interpolate(r1, r2, t, value_scale);
164 dst[cn.r_i()] = r.v[0].as_();
165 dst[cn.g_i()] = r.v[1].as_();
166 dst[cn.b_i()] = r.v[2].as_();
167 if channels == 4 {
168 dst[cn.a_i()] = max_value;
169 }
170 }
171 }
172}
173
174#[allow(unused)]
175impl<
176 T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible,
177 U: AsPrimitive<usize>,
178 const LAYOUT: u8,
179 const GRID_SIZE: usize,
180 const BIT_DEPTH: usize,
181 const BINS: usize,
182 const BARYCENTRIC_BINS: usize,
183> TransformExecutor<T>
184 for TransformLut4To3<T, U, LAYOUT, GRID_SIZE, BIT_DEPTH, BINS, BARYCENTRIC_BINS>
185where
186 f32: AsPrimitive<T>,
187 u32: AsPrimitive<T>,
188 (): LutBarycentricReduction<T, U>,
189{
190 fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
191 let cn = Layout::from(LAYOUT);
192 let channels = cn.channels();
193 if src.len() % 4 != 0 {
194 return Err(CmsError::LaneMultipleOfChannels);
195 }
196 if dst.len() % channels != 0 {
197 return Err(CmsError::LaneMultipleOfChannels);
198 }
199 let src_chunks = src.len() / 4;
200 let dst_chunks = dst.len() / channels;
201 if src_chunks != dst_chunks {
202 return Err(CmsError::LaneSizeMismatch);
203 }
204
205 if self.color_space == DataColorSpace::Lab
206 || (self.is_linear && self.color_space == DataColorSpace::Rgb)
207 || self.color_space == DataColorSpace::Xyz
208 {
209 if T::FINITE {
210 self.transform_chunk::<Trilinear<GRID_SIZE>, DefaultVector3fLerp>(src, dst);
211 } else {
212 self.transform_chunk::<Trilinear<GRID_SIZE>, NonFiniteVector3fLerp>(src, dst);
213 }
214 } else {
215 match self.interpolation_method {
216 #[cfg(feature = "options")]
217 InterpolationMethod::Tetrahedral => {
218 if T::FINITE {
219 self.transform_chunk::<Tetrahedral<GRID_SIZE>, DefaultVector3fLerp>(
220 src, dst,
221 );
222 } else {
223 self.transform_chunk::<Tetrahedral<GRID_SIZE>, NonFiniteVector3fLerp>(
224 src, dst,
225 );
226 }
227 }
228 #[cfg(feature = "options")]
229 InterpolationMethod::Pyramid => {
230 if T::FINITE {
231 self.transform_chunk::<Pyramidal<GRID_SIZE>, DefaultVector3fLerp>(src, dst);
232 } else {
233 self.transform_chunk::<Pyramidal<GRID_SIZE>, NonFiniteVector3fLerp>(
234 src, dst,
235 );
236 }
237 }
238 #[cfg(feature = "options")]
239 InterpolationMethod::Prism => {
240 if T::FINITE {
241 self.transform_chunk::<Prismatic<GRID_SIZE>, DefaultVector3fLerp>(src, dst);
242 } else {
243 self.transform_chunk::<Prismatic<GRID_SIZE>, NonFiniteVector3fLerp>(
244 src, dst,
245 );
246 }
247 }
248 InterpolationMethod::Linear => {
249 if T::FINITE {
250 self.transform_chunk::<Trilinear<GRID_SIZE>, DefaultVector3fLerp>(src, dst);
251 } else {
252 self.transform_chunk::<Trilinear<GRID_SIZE>, NonFiniteVector3fLerp>(
253 src, dst,
254 );
255 }
256 }
257 }
258 }
259
260 Ok(())
261 }
262}
263
264#[allow(dead_code)]
265pub(crate) struct DefaultLut4x3Factory {}
266
267#[allow(dead_code)]
268impl Lut4x3Factory for DefaultLut4x3Factory {
269 fn make_transform_4x3<
270 T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible + 'static + Send + Sync,
271 const LAYOUT: u8,
272 const GRID_SIZE: usize,
273 const BIT_DEPTH: usize,
274 >(
275 lut: Vec<f32>,
276 options: TransformOptions,
277 color_space: DataColorSpace,
278 is_linear: bool,
279 ) -> Box<dyn TransformExecutor<T> + Sync + Send>
280 where
281 f32: AsPrimitive<T>,
282 u32: AsPrimitive<T>,
283 (): LutBarycentricReduction<T, u8>,
284 (): LutBarycentricReduction<T, u16>,
285 {
286 match options.barycentric_weight_scale {
287 BarycentricWeightScale::Low => {
288 Box::new(
289 TransformLut4To3::<T, u8, LAYOUT, GRID_SIZE, BIT_DEPTH, 256, 256> {
290 lut,
291 _phantom: PhantomData,
292 _phantom1: PhantomData,
293 interpolation_method: options.interpolation_method,
294 weights: BarycentricWeight::<f32>::create_ranged_256::<GRID_SIZE>(),
295 color_space,
296 is_linear,
297 },
298 )
299 }
300 #[cfg(feature = "options")]
301 BarycentricWeightScale::High => {
302 Box::new(
303 TransformLut4To3::<T, u16, LAYOUT, GRID_SIZE, BIT_DEPTH, 65536, 65536> {
304 lut,
305 _phantom: PhantomData,
306 _phantom1: PhantomData,
307 interpolation_method: options.interpolation_method,
308 weights: BarycentricWeight::<f32>::create_binned::<GRID_SIZE, 65536>(),
309 color_space,
310 is_linear,
311 },
312 )
313 }
314 }
315 }
316}