1use crate::conversions::LutBarycentricReduction;
30use crate::conversions::interpolator::{BarycentricWeight, MultidimensionalInterpolation};
31use crate::transform::PointeeSizeExpressible;
32use crate::{
33 BarycentricWeightScale, CmsError, DataColorSpace, InterpolationMethod, Layout,
34 TransformExecutor, TransformOptions,
35};
36use num_traits::AsPrimitive;
37use std::marker::PhantomData;
38
39pub(crate) struct TransformLut3x4<
40 T,
41 U: AsPrimitive<usize>,
42 const LAYOUT: u8,
43 const GRID_SIZE: usize,
44 const BIT_DEPTH: usize,
45 const BINS: usize,
46 const BARYCENTRIC_BINS: usize,
47> {
48 pub(crate) lut: Vec<f32>,
49 pub(crate) _phantom: PhantomData<T>,
50 pub(crate) _phantom1: PhantomData<U>,
51 pub(crate) interpolation_method: InterpolationMethod,
52 pub(crate) weights: Box<[BarycentricWeight<f32>; BINS]>,
53 pub(crate) color_space: DataColorSpace,
54 pub(crate) is_linear: bool,
55}
56
57impl<
58 T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible,
59 U: AsPrimitive<usize>,
60 const LAYOUT: u8,
61 const GRID_SIZE: usize,
62 const BIT_DEPTH: usize,
63 const BINS: usize,
64 const BARYCENTRIC_BINS: usize,
65> TransformLut3x4<T, U, LAYOUT, GRID_SIZE, BIT_DEPTH, BINS, BARYCENTRIC_BINS>
66where
67 f32: AsPrimitive<T>,
68 u32: AsPrimitive<T>,
69 (): LutBarycentricReduction<T, U>,
70{
71 #[inline(never)]
72 fn transform_chunk(
73 &self,
74 src: &[T],
75 dst: &mut [T],
76 interpolator: Box<dyn MultidimensionalInterpolation + Send + Sync>,
77 ) {
78 let cn = Layout::from(LAYOUT);
79 let channels = cn.channels();
80
81 let value_scale = ((1 << BIT_DEPTH) - 1) as f32;
82
83 for (src, dst) in src.chunks_exact(channels).zip(dst.chunks_exact_mut(4)) {
84 let x = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
85 src[cn.r_i()],
86 );
87 let y = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
88 src[cn.g_i()],
89 );
90 let z = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
91 src[cn.b_i()],
92 );
93
94 let v = interpolator.inter4(
95 &self.lut,
96 &self.weights[x.as_()],
97 &self.weights[y.as_()],
98 &self.weights[z.as_()],
99 );
100 if T::FINITE {
101 let r = v * value_scale + 0.5;
102 dst[0] = r.v[0].min(value_scale).max(0.).as_();
103 dst[1] = r.v[1].min(value_scale).max(0.).as_();
104 dst[2] = r.v[2].min(value_scale).max(0.).as_();
105 dst[3] = r.v[3].min(value_scale).max(0.).as_();
106 } else {
107 dst[0] = v.v[0].as_();
108 dst[1] = v.v[1].as_();
109 dst[2] = v.v[2].as_();
110 dst[3] = v.v[3].as_();
111 }
112 }
113 }
114}
115
116impl<
117 T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible,
118 U: AsPrimitive<usize>,
119 const LAYOUT: u8,
120 const GRID_SIZE: usize,
121 const BIT_DEPTH: usize,
122 const BINS: usize,
123 const BARYCENTRIC_BINS: usize,
124> TransformExecutor<T>
125 for TransformLut3x4<T, U, LAYOUT, GRID_SIZE, BIT_DEPTH, BINS, BARYCENTRIC_BINS>
126where
127 f32: AsPrimitive<T>,
128 u32: AsPrimitive<T>,
129 (): LutBarycentricReduction<T, U>,
130{
131 fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
132 let cn = Layout::from(LAYOUT);
133 let channels = cn.channels();
134 if src.len() % channels != 0 {
135 return Err(CmsError::LaneMultipleOfChannels);
136 }
137 if dst.len() % 4 != 0 {
138 return Err(CmsError::LaneMultipleOfChannels);
139 }
140 let src_chunks = src.len() / channels;
141 let dst_chunks = dst.len() / 4;
142 if src_chunks != dst_chunks {
143 return Err(CmsError::LaneSizeMismatch);
144 }
145
146 if self.color_space == DataColorSpace::Lab
147 || (self.is_linear && self.color_space == DataColorSpace::Rgb)
148 || self.color_space == DataColorSpace::Xyz
149 {
150 use crate::conversions::interpolator::Trilinear;
151 self.transform_chunk(src, dst, Box::new(Trilinear::<GRID_SIZE> {}));
152 } else {
153 match self.interpolation_method {
154 #[cfg(feature = "options")]
155 InterpolationMethod::Tetrahedral => {
156 use crate::conversions::interpolator::Tetrahedral;
157 self.transform_chunk(src, dst, Box::new(Tetrahedral::<GRID_SIZE> {}));
158 }
159 #[cfg(feature = "options")]
160 InterpolationMethod::Pyramid => {
161 use crate::conversions::interpolator::Pyramidal;
162 self.transform_chunk(src, dst, Box::new(Pyramidal::<GRID_SIZE> {}));
163 }
164 #[cfg(feature = "options")]
165 InterpolationMethod::Prism => {
166 use crate::conversions::interpolator::Prismatic;
167 self.transform_chunk(src, dst, Box::new(Prismatic::<GRID_SIZE> {}));
168 }
169 InterpolationMethod::Linear => {
170 use crate::conversions::interpolator::Trilinear;
171 self.transform_chunk(src, dst, Box::new(Trilinear::<GRID_SIZE> {}));
172 }
173 }
174 }
175
176 Ok(())
177 }
178}
179
180pub(crate) fn make_transform_3x4<
181 T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible + 'static + Send + Sync,
182 const GRID_SIZE: usize,
183 const BIT_DEPTH: usize,
184>(
185 layout: Layout,
186 lut: Vec<f32>,
187 options: TransformOptions,
188 color_space: DataColorSpace,
189 is_linear: bool,
190) -> Box<dyn TransformExecutor<T> + Sync + Send>
191where
192 f32: AsPrimitive<T>,
193 u32: AsPrimitive<T>,
194 (): LutBarycentricReduction<T, u8>,
195 (): LutBarycentricReduction<T, u16>,
196{
197 match layout {
198 Layout::Rgb => match options.barycentric_weight_scale {
199 BarycentricWeightScale::Low => Box::new(TransformLut3x4::<
200 T,
201 u8,
202 { Layout::Rgb as u8 },
203 GRID_SIZE,
204 BIT_DEPTH,
205 256,
206 256,
207 > {
208 lut,
209 _phantom: PhantomData,
210 _phantom1: PhantomData,
211 interpolation_method: options.interpolation_method,
212 weights: BarycentricWeight::<f32>::create_ranged_256::<GRID_SIZE>(),
213 color_space,
214 is_linear,
215 }),
216 #[cfg(feature = "options")]
217 BarycentricWeightScale::High => Box::new(TransformLut3x4::<
218 T,
219 u16,
220 { Layout::Rgb as u8 },
221 GRID_SIZE,
222 BIT_DEPTH,
223 65536,
224 65536,
225 > {
226 lut,
227 _phantom: PhantomData,
228 _phantom1: PhantomData,
229 interpolation_method: options.interpolation_method,
230 weights: BarycentricWeight::<f32>::create_binned::<GRID_SIZE, 65536>(),
231 color_space,
232 is_linear,
233 }),
234 },
235 Layout::Rgba => match options.barycentric_weight_scale {
236 BarycentricWeightScale::Low => Box::new(TransformLut3x4::<
237 T,
238 u8,
239 { Layout::Rgba as u8 },
240 GRID_SIZE,
241 BIT_DEPTH,
242 256,
243 256,
244 > {
245 lut,
246 _phantom: PhantomData,
247 _phantom1: PhantomData,
248 interpolation_method: options.interpolation_method,
249 weights: BarycentricWeight::<f32>::create_ranged_256::<GRID_SIZE>(),
250 color_space,
251 is_linear,
252 }),
253 #[cfg(feature = "options")]
254 BarycentricWeightScale::High => Box::new(TransformLut3x4::<
255 T,
256 u16,
257 { Layout::Rgba as u8 },
258 GRID_SIZE,
259 BIT_DEPTH,
260 65536,
261 65536,
262 > {
263 lut,
264 _phantom: PhantomData,
265 _phantom1: PhantomData,
266 interpolation_method: options.interpolation_method,
267 weights: BarycentricWeight::<f32>::create_binned::<GRID_SIZE, 65536>(),
268 color_space,
269 is_linear,
270 }),
271 },
272 _ => unimplemented!(),
273 }
274}