1#![cfg(feature = "lut")]
30use crate::conversions::lut3x4::create_lut3_samples;
31use crate::err::try_vec;
32use crate::mlaf::mlaf;
33use crate::{
34 CmsError, ColorProfile, GammaLutInterpolate, InPlaceStage, Matrix3f, PointeeSizeExpressible,
35 Rgb, TransformOptions,
36};
37use num_traits::AsPrimitive;
38use std::marker::PhantomData;
39
40pub(crate) struct XyzToRgbStage<T: Clone> {
41 pub(crate) r_gamma: Box<[T; 65536]>,
42 pub(crate) g_gamma: Box<[T; 65536]>,
43 pub(crate) b_gamma: Box<[T; 65536]>,
44 pub(crate) matrices: Vec<Matrix3f>,
45 pub(crate) bit_depth: usize,
46 pub(crate) gamma_lut: usize,
47}
48
49impl<T: Clone + AsPrimitive<f32>> InPlaceStage for XyzToRgbStage<T> {
50 fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
51 assert!(self.bit_depth > 0);
52 if !self.matrices.is_empty() {
53 let m = self.matrices[0];
54 for dst in dst.chunks_exact_mut(3) {
55 let x = dst[0];
56 let y = dst[1];
57 let z = dst[2];
58 dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
59 dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
60 dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
61 }
62 }
63
64 for m in self.matrices.iter().skip(1) {
65 for dst in dst.chunks_exact_mut(3) {
66 let x = dst[0];
67 let y = dst[1];
68 let z = dst[2];
69 dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
70 dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
71 dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
72 }
73 }
74
75 let max_colors = (1 << self.bit_depth) - 1;
76 let color_scale = 1f32 / max_colors as f32;
77 let lut_cap = (self.gamma_lut - 1) as f32;
78
79 for dst in dst.chunks_exact_mut(3) {
80 let rgb = Rgb::new(dst[0], dst[1], dst[2]);
81 let r = mlaf(0.5f32, rgb.r, lut_cap).min(lut_cap).max(0f32) as u16;
82 let g = mlaf(0.5f32, rgb.g, lut_cap).min(lut_cap).max(0f32) as u16;
83 let b = mlaf(0.5f32, rgb.b, lut_cap).min(lut_cap).max(0f32) as u16;
84
85 dst[0] = self.r_gamma[r as usize].as_() * color_scale;
86 dst[1] = self.g_gamma[g as usize].as_() * color_scale;
87 dst[2] = self.b_gamma[b as usize].as_() * color_scale;
88 }
89
90 Ok(())
91 }
92}
93
94#[cfg(feature = "extended_range")]
95use crate::trc::ToneCurveEvaluator;
96
97#[cfg(feature = "extended_range")]
98pub(crate) struct XyzToRgbStageExtended<T: Clone> {
99 pub(crate) gamma_evaluator: Box<dyn ToneCurveEvaluator>,
100 pub(crate) matrices: Vec<Matrix3f>,
101 pub(crate) phantom_data: PhantomData<T>,
102}
103
104#[cfg(feature = "extended_range")]
105impl<T: Clone + AsPrimitive<f32>> InPlaceStage for XyzToRgbStageExtended<T> {
106 fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
107 if !self.matrices.is_empty() {
108 let m = self.matrices[0];
109 for dst in dst.chunks_exact_mut(3) {
110 let x = dst[0];
111 let y = dst[1];
112 let z = dst[2];
113 dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
114 dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
115 dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
116 }
117 }
118
119 for m in self.matrices.iter().skip(1) {
120 for dst in dst.chunks_exact_mut(3) {
121 let x = dst[0];
122 let y = dst[1];
123 let z = dst[2];
124 dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
125 dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
126 dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
127 }
128 }
129
130 for dst in dst.chunks_exact_mut(3) {
131 let mut rgb = Rgb::new(dst[0], dst[1], dst[2]);
132 rgb = self.gamma_evaluator.evaluate_tristimulus(rgb);
133 dst[0] = rgb.r.as_();
134 dst[1] = rgb.g.as_();
135 dst[2] = rgb.b.as_();
136 }
137
138 Ok(())
139 }
140}
141
142struct RgbLinearizationStage<T: Clone, const LINEAR_CAP: usize, const SAMPLES: usize> {
143 r_lin: Box<[f32; LINEAR_CAP]>,
144 g_lin: Box<[f32; LINEAR_CAP]>,
145 b_lin: Box<[f32; LINEAR_CAP]>,
146 _phantom: PhantomData<T>,
147 bit_depth: usize,
148}
149
150impl<
151 T: Clone + AsPrimitive<usize> + PointeeSizeExpressible,
152 const LINEAR_CAP: usize,
153 const SAMPLES: usize,
154> RgbLinearizationStage<T, LINEAR_CAP, SAMPLES>
155{
156 fn transform(&self, src: &[T], dst: &mut [f32]) -> Result<(), CmsError> {
157 if src.len() % 3 != 0 {
158 return Err(CmsError::LaneMultipleOfChannels);
159 }
160 if dst.len() % 3 != 0 {
161 return Err(CmsError::LaneMultipleOfChannels);
162 }
163
164 let scale = if T::FINITE {
165 ((1 << self.bit_depth) - 1) as f32 / (SAMPLES as f32 - 1f32)
166 } else {
167 (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32 / (SAMPLES as f32 - 1f32)
168 };
169
170 let capped_value = if T::FINITE {
171 (1 << self.bit_depth) - 1
172 } else {
173 T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
174 };
175
176 for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(3)) {
177 let j_r = src[0].as_() as f32 * scale;
178 let j_g = src[1].as_() as f32 * scale;
179 let j_b = src[2].as_() as f32 * scale;
180 dst[0] = self.r_lin[(j_r.round().max(0.0).min(capped_value as f32) as u16) as usize];
181 dst[1] = self.g_lin[(j_g.round().max(0.0).min(capped_value as f32) as u16) as usize];
182 dst[2] = self.b_lin[(j_b.round().max(0.0).min(capped_value as f32) as u16) as usize];
183 }
184 Ok(())
185 }
186}
187
188pub(crate) fn create_rgb_lin_lut<
189 T: Copy + Default + AsPrimitive<f32> + Send + Sync + AsPrimitive<usize> + PointeeSizeExpressible,
190 const BIT_DEPTH: usize,
191 const LINEAR_CAP: usize,
192 const GRID_SIZE: usize,
193>(
194 source: &ColorProfile,
195 opts: TransformOptions,
196) -> Result<Vec<f32>, CmsError>
197where
198 u32: AsPrimitive<T>,
199 f32: AsPrimitive<T>,
200{
201 let lut_origins = create_lut3_samples::<T, GRID_SIZE>();
202
203 let lin_r =
204 source.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(opts.allow_use_cicp_transfer)?;
205 let lin_g =
206 source.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(opts.allow_use_cicp_transfer)?;
207 let lin_b =
208 source.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(opts.allow_use_cicp_transfer)?;
209
210 let lin_stage = RgbLinearizationStage::<T, LINEAR_CAP, GRID_SIZE> {
211 r_lin: lin_r,
212 g_lin: lin_g,
213 b_lin: lin_b,
214 _phantom: PhantomData,
215 bit_depth: BIT_DEPTH,
216 };
217
218 let mut lut = try_vec![0f32; lut_origins.len()];
219 lin_stage.transform(&lut_origins, &mut lut)?;
220
221 let xyz_to_rgb = source.rgb_to_xyz_matrix();
222
223 let matrices = vec![
224 xyz_to_rgb.to_f32(),
225 Matrix3f {
226 v: [
227 [32768.0 / 65535.0, 0.0, 0.0],
228 [0.0, 32768.0 / 65535.0, 0.0],
229 [0.0, 0.0, 32768.0 / 65535.0],
230 ],
231 },
232 ];
233
234 let matrix_stage = crate::conversions::lut_transforms::MatrixStage { matrices };
235 matrix_stage.transform(&mut lut)?;
236 Ok(lut)
237}
238
239pub(crate) fn prepare_inverse_lut_rgb_xyz<
240 T: Copy
241 + Default
242 + AsPrimitive<f32>
243 + Send
244 + Sync
245 + AsPrimitive<usize>
246 + PointeeSizeExpressible
247 + GammaLutInterpolate,
248 const BIT_DEPTH: usize,
249 const GAMMA_LUT: usize,
250>(
251 dest: &ColorProfile,
252 lut: &mut [f32],
253 options: TransformOptions,
254) -> Result<(), CmsError>
255where
256 f32: AsPrimitive<T>,
257 u32: AsPrimitive<T>,
258{
259 #[cfg(feature = "extended_range")]
260 if !T::FINITE && options.allow_extended_range_rgb_xyz {
261 if let Some(extended_gamma) = dest.try_extended_gamma_evaluator() {
262 let xyz_to_rgb = dest.rgb_to_xyz_matrix().inverse();
263
264 let mut matrices = vec![Matrix3f {
265 v: [
266 [65535.0 / 32768.0, 0.0, 0.0],
267 [0.0, 65535.0 / 32768.0, 0.0],
268 [0.0, 0.0, 65535.0 / 32768.0],
269 ],
270 }];
271
272 matrices.push(xyz_to_rgb.to_f32());
273 let xyz_to_rgb_stage = XyzToRgbStageExtended::<T> {
274 gamma_evaluator: extended_gamma,
275 matrices,
276 phantom_data: PhantomData,
277 };
278 xyz_to_rgb_stage.transform(lut)?;
279 return Ok(());
280 }
281 }
282 let gamma_map_r = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
283 &dest.red_trc,
284 options.allow_use_cicp_transfer,
285 )?;
286 let gamma_map_g = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
287 &dest.green_trc,
288 options.allow_use_cicp_transfer,
289 )?;
290 let gamma_map_b = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
291 &dest.blue_trc,
292 options.allow_use_cicp_transfer,
293 )?;
294
295 let xyz_to_rgb = dest.rgb_to_xyz_matrix().inverse();
296
297 let mut matrices = vec![Matrix3f {
298 v: [
299 [65535.0 / 32768.0, 0.0, 0.0],
300 [0.0, 65535.0 / 32768.0, 0.0],
301 [0.0, 0.0, 65535.0 / 32768.0],
302 ],
303 }];
304
305 matrices.push(xyz_to_rgb.to_f32());
306 let xyz_to_rgb_stage = XyzToRgbStage::<T> {
307 r_gamma: gamma_map_r,
308 g_gamma: gamma_map_g,
309 b_gamma: gamma_map_b,
310 matrices,
311 gamma_lut: GAMMA_LUT,
312 bit_depth: BIT_DEPTH,
313 };
314 xyz_to_rgb_stage.transform(lut)?;
315 Ok(())
316}