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