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