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