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