/src/image/src/metadata/moxcms.rs
Line | Count | Source |
1 | | /// Contains bindings to `moxcms` as a [`super::CmsProvider`]. |
2 | | use std::sync::Arc; |
3 | | |
4 | | use crate::{ |
5 | | metadata::{ |
6 | | cicp::ColorComponentForCicp, Cicp, CicpColorPrimaries, CicpMatrixCoefficients, |
7 | | CicpTransferCharacteristics, CicpTransform, CicpVideoFullRangeFlag, |
8 | | }, |
9 | | traits::private::LayoutWithColor, |
10 | | }; |
11 | | |
12 | | pub(crate) struct Moxcms; |
13 | | |
14 | | impl super::CmsProvider for Moxcms { |
15 | 0 | fn transform(&self, from: Cicp, into: Cicp) -> Option<CicpTransform> { |
16 | 0 | if !from.qualify_stability() || !into.qualify_stability() { |
17 | | // To avoid regressions, we do not support all kinds of transforms from the start. |
18 | | // Instead, a selected list will be gradually enlarged as more in-depth tests are done |
19 | | // and the selected implementation library is checked for suitability in use. |
20 | 0 | return None; |
21 | 0 | } |
22 | | |
23 | | // Unused, but introduces symmetry to the supported color space transforms. That said we |
24 | | // calculate the derived luminance coefficients for all color that have a matching moxcms |
25 | | // profile so this really should not block anything. |
26 | 0 | let _input_coefs = from.into_rgb().derived_luminance()?; |
27 | 0 | let output_coefs = into.into_rgb().derived_luminance()?; |
28 | | |
29 | 0 | let mox_from = from.to_moxcms_compute_profile()?; |
30 | 0 | let mox_into = into.to_moxcms_compute_profile()?; |
31 | | |
32 | 0 | let opt = moxcms::TransformOptions::default(); |
33 | | |
34 | 0 | let f32_fallback = { |
35 | 0 | let try_f32 = CicpTransform::LAYOUTS.map(|(from_layout, into_layout)| { |
36 | 0 | let (from, from_layout) = mox_from.map_layout(from_layout); |
37 | 0 | let (into, into_layout) = mox_into.map_layout(into_layout); |
38 | | |
39 | 0 | from.create_transform_f32(from_layout, into, into_layout, opt) |
40 | 0 | .ok() |
41 | 0 | }); |
42 | | |
43 | 0 | if try_f32.iter().any(Option::is_none) { |
44 | 0 | return None; |
45 | 0 | } |
46 | | |
47 | 0 | try_f32.map(Option::unwrap) |
48 | | }; |
49 | | |
50 | | // TODO: really these should be lazy, eh? |
51 | | Some(CicpTransform { |
52 | 0 | from, |
53 | 0 | into, |
54 | 0 | u8: Self::build_transforms( |
55 | 0 | CicpTransform::LAYOUTS.map(|(from_layout, into_layout)| { |
56 | 0 | let (from, from_layout) = mox_from.map_layout(from_layout); |
57 | 0 | let (into, into_layout) = mox_into.map_layout(into_layout); |
58 | | |
59 | 0 | from.create_transform_8bit(from_layout, into, into_layout, opt) |
60 | 0 | .ok() |
61 | 0 | }), |
62 | 0 | f32_fallback.clone(), |
63 | 0 | output_coefs, |
64 | 0 | )?, |
65 | 0 | u16: Self::build_transforms( |
66 | 0 | CicpTransform::LAYOUTS.map(|(from_layout, into_layout)| { |
67 | 0 | let (from, from_layout) = mox_from.map_layout(from_layout); |
68 | 0 | let (into, into_layout) = mox_into.map_layout(into_layout); |
69 | | |
70 | 0 | from.create_transform_16bit(from_layout, into, into_layout, opt) |
71 | 0 | .ok() |
72 | 0 | }), |
73 | 0 | f32_fallback.clone(), |
74 | 0 | output_coefs, |
75 | 0 | )?, |
76 | 0 | f32: Self::build_transforms( |
77 | 0 | f32_fallback.clone().map(Some), |
78 | 0 | f32_fallback, |
79 | 0 | output_coefs, |
80 | 0 | )?, |
81 | 0 | output_coefs, |
82 | | }) |
83 | 0 | } |
84 | | |
85 | 5.65k | fn parse_icc(&self, icc: &[u8]) -> Option<Cicp> { |
86 | 5.65k | let profile = moxcms::ColorProfile::new_from_slice(icc).ok()?; |
87 | 1.72k | let cicp = profile.cicp?; |
88 | | |
89 | | use moxcms::CicpColorPrimaries as Mcp; |
90 | | use moxcms::MatrixCoefficients as Mmc; |
91 | | use moxcms::TransferCharacteristics as Mtc; |
92 | | |
93 | | Some(Cicp { |
94 | 2 | primaries: match cicp.color_primaries { |
95 | 0 | Mcp::Bt709 => CicpColorPrimaries::SRgb, |
96 | 2 | Mcp::Unspecified | Mcp::Reserved => CicpColorPrimaries::Unspecified, |
97 | 0 | Mcp::Bt470M => CicpColorPrimaries::RgbM, |
98 | 0 | Mcp::Bt470Bg => CicpColorPrimaries::RgbB, |
99 | 0 | Mcp::Bt601 => CicpColorPrimaries::Bt601, |
100 | 0 | Mcp::Smpte240 => CicpColorPrimaries::Rgb240m, |
101 | 0 | Mcp::GenericFilm => CicpColorPrimaries::GenericFilm, |
102 | 0 | Mcp::Bt2020 => CicpColorPrimaries::Rgb2020, |
103 | 0 | Mcp::Xyz => CicpColorPrimaries::Xyz, |
104 | 0 | Mcp::Smpte431 => CicpColorPrimaries::SmpteRp431, |
105 | 0 | Mcp::Smpte432 => CicpColorPrimaries::SmpteRp432, |
106 | 0 | Mcp::Ebu3213 => CicpColorPrimaries::Industry22, |
107 | | }, |
108 | 2 | transfer: match cicp.transfer_characteristics { |
109 | 0 | Mtc::Bt709 => CicpTransferCharacteristics::Bt709, |
110 | 2 | Mtc::Unspecified | Mtc::Reserved => CicpTransferCharacteristics::Unspecified, |
111 | 0 | Mtc::Bt470M => CicpTransferCharacteristics::Bt470M, |
112 | 0 | Mtc::Bt470Bg => CicpTransferCharacteristics::Bt470BG, |
113 | 0 | Mtc::Bt601 => CicpTransferCharacteristics::Bt601, |
114 | 0 | Mtc::Smpte240 => CicpTransferCharacteristics::Smpte240m, |
115 | 0 | Mtc::Linear => CicpTransferCharacteristics::Linear, |
116 | 0 | Mtc::Log100 => CicpTransferCharacteristics::Log100, |
117 | 0 | Mtc::Log100sqrt10 => CicpTransferCharacteristics::LogSqrt, |
118 | 0 | Mtc::Iec61966 => CicpTransferCharacteristics::Iec61966_2_4, |
119 | 0 | Mtc::Bt1361 => CicpTransferCharacteristics::Bt1361, |
120 | 0 | Mtc::Srgb => CicpTransferCharacteristics::SRgb, |
121 | 0 | Mtc::Bt202010bit => CicpTransferCharacteristics::Bt2020_10bit, |
122 | 0 | Mtc::Bt202012bit => CicpTransferCharacteristics::Bt2020_12bit, |
123 | 0 | Mtc::Smpte2084 => CicpTransferCharacteristics::Smpte2084, |
124 | 0 | Mtc::Smpte428 => CicpTransferCharacteristics::Smpte428, |
125 | 0 | Mtc::Hlg => CicpTransferCharacteristics::Bt2100Hlg, |
126 | | }, |
127 | 2 | matrix: match cicp.matrix_coefficients { |
128 | 2 | Mmc::Identity => CicpMatrixCoefficients::Identity, |
129 | 0 | Mmc::Unspecified | Mmc::Reserved => CicpMatrixCoefficients::Unspecified, |
130 | 0 | Mmc::Bt709 => CicpMatrixCoefficients::Bt709, |
131 | 0 | Mmc::Fcc => CicpMatrixCoefficients::UsFCC, |
132 | 0 | Mmc::Bt470Bg => CicpMatrixCoefficients::Bt470BG, |
133 | 0 | Mmc::Smpte170m => CicpMatrixCoefficients::Smpte170m, |
134 | 0 | Mmc::Smpte240m => CicpMatrixCoefficients::Smpte240m, |
135 | 0 | Mmc::YCgCo => CicpMatrixCoefficients::YCgCo, |
136 | 0 | Mmc::Bt2020Ncl => CicpMatrixCoefficients::Bt2020NonConstant, |
137 | 0 | Mmc::Bt2020Cl => CicpMatrixCoefficients::Bt2020Constant, |
138 | 0 | Mmc::Smpte2085 => CicpMatrixCoefficients::Smpte2085, |
139 | | Mmc::ChromaticityDerivedNCL => { |
140 | 0 | CicpMatrixCoefficients::ChromaticityDerivedNonConstant |
141 | | } |
142 | 0 | Mmc::ChromaticityDerivedCL => CicpMatrixCoefficients::ChromaticityDerivedConstant, |
143 | 0 | Mmc::ICtCp => CicpMatrixCoefficients::Bt2100, |
144 | | }, |
145 | 2 | full_range: match cicp.full_range { |
146 | 2 | false => CicpVideoFullRangeFlag::NarrowRange, |
147 | 0 | true => CicpVideoFullRangeFlag::FullRange, |
148 | | }, |
149 | | }) |
150 | 5.65k | } |
151 | | } |
152 | | |
153 | | impl Moxcms { |
154 | 0 | fn build_transforms<P: ColorComponentForCicp + Default + 'static>( |
155 | 0 | trs: [Option<Arc<dyn moxcms::TransformExecutor<P> + Send + Sync>>; 4], |
156 | 0 | f32: [Arc<dyn moxcms::TransformExecutor<f32> + Send + Sync>; 4], |
157 | 0 | output_coef: [f32; 3], |
158 | 0 | ) -> Option<crate::metadata::cicp::RgbTransforms<P>> { |
159 | | // We would use `[array]::try_map` here, but it is not stable yet. |
160 | 0 | if trs.iter().any(Option::is_none) { |
161 | 0 | return None; |
162 | 0 | } |
163 | | |
164 | 0 | let trs = trs.map(Option::unwrap); |
165 | | |
166 | | // rgb-rgb transforms are done directly via moxcms. |
167 | 0 | let slices = trs.map(|tr| { |
168 | 0 | Arc::new(move |input: &[P], output: &mut [P]| { |
169 | 0 | tr.transform(input, output).expect("transform failed") |
170 | 0 | }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync> Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<f32>::{closure#0}::{closure#0}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u8>::{closure#0}::{closure#0}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u16>::{closure#0}::{closure#0} |
171 | 0 | }); Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<f32>::{closure#0}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u8>::{closure#0}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u16>::{closure#0} |
172 | | |
173 | | const N: usize = 256; |
174 | | |
175 | | // luma-rgb transforms expand the Luma to Rgb (and LumaAlpha to Rgba) |
176 | 0 | let luma_rgb = { |
177 | 0 | let [tr33, tr34, tr43, tr44] = f32.clone(); |
178 | | |
179 | | [ |
180 | 0 | Arc::new(move |input: &[P], output: &mut [P]| { |
181 | 0 | let mut ibuffer = [0.0f32; 3 * N]; |
182 | 0 | let mut obuffer = [0.0f32; 3 * N]; |
183 | | |
184 | 0 | for (luma, output) in input.chunks(N).zip(output.chunks_mut(3 * N)) { |
185 | 0 | let n = luma.len(); |
186 | 0 | let ibuffer = &mut ibuffer[..3 * n]; |
187 | 0 | let obuffer = &mut obuffer[..3 * n]; |
188 | 0 | CicpTransform::expand_luma_rgb(luma, ibuffer); |
189 | 0 | tr33.transform(ibuffer, obuffer).expect("transform failed"); |
190 | 0 | CicpTransform::clamp_rgb(obuffer, output); |
191 | 0 | } |
192 | 0 | }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>, Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<f32>::{closure#1}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u8>::{closure#1}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u16>::{closure#1} |
193 | 0 | Arc::new(move |input: &[P], output: &mut [P]| { |
194 | 0 | let mut ibuffer = [0.0f32; 3 * N]; |
195 | 0 | let mut obuffer = [0.0f32; 4 * N]; |
196 | | |
197 | 0 | for (luma, output) in input.chunks(N).zip(output.chunks_mut(4 * N)) { |
198 | 0 | let n = luma.len(); |
199 | 0 | let ibuffer = &mut ibuffer[..3 * n]; |
200 | 0 | let obuffer = &mut obuffer[..4 * n]; |
201 | 0 | CicpTransform::expand_luma_rgb(luma, ibuffer); |
202 | 0 | tr34.transform(ibuffer, obuffer).expect("transform failed"); |
203 | 0 | CicpTransform::clamp_rgba(obuffer, output); |
204 | 0 | } |
205 | 0 | }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>, Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<f32>::{closure#2}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u8>::{closure#2}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u16>::{closure#2} |
206 | 0 | Arc::new(move |input: &[P], output: &mut [P]| { |
207 | 0 | let mut ibuffer = [0.0f32; 4 * N]; |
208 | 0 | let mut obuffer = [0.0f32; 3 * N]; |
209 | | |
210 | 0 | for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(3 * N)) { |
211 | 0 | let n = luma.len() / 2; |
212 | 0 | let ibuffer = &mut ibuffer[..4 * n]; |
213 | 0 | let obuffer = &mut obuffer[..3 * n]; |
214 | 0 | CicpTransform::expand_luma_rgba(luma, ibuffer); |
215 | 0 | tr43.transform(ibuffer, obuffer).expect("transform failed"); |
216 | 0 | CicpTransform::clamp_rgb(obuffer, output); |
217 | 0 | } |
218 | 0 | }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>, Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<f32>::{closure#3}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u8>::{closure#3}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u16>::{closure#3} |
219 | 0 | Arc::new(move |input: &[P], output: &mut [P]| { |
220 | 0 | let mut ibuffer = [0.0f32; 4 * N]; |
221 | 0 | let mut obuffer = [0.0f32; 4 * N]; |
222 | | |
223 | 0 | for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(4 * N)) { |
224 | 0 | let n = luma.len() / 2; |
225 | 0 | let ibuffer = &mut ibuffer[..4 * n]; |
226 | 0 | let obuffer = &mut obuffer[..4 * n]; |
227 | 0 | CicpTransform::expand_luma_rgba(luma, ibuffer); |
228 | 0 | tr44.transform(ibuffer, obuffer).expect("transform failed"); |
229 | 0 | CicpTransform::clamp_rgba(obuffer, output); |
230 | 0 | } |
231 | 0 | }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>, Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<f32>::{closure#4}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u8>::{closure#4}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u16>::{closure#4} |
232 | | ] |
233 | | }; |
234 | | |
235 | | // rgb-luma transforms contract Rgb to Luma (and Rgba to LumaAlpha) |
236 | 0 | let rgb_luma = { |
237 | 0 | let [tr33, tr34, tr43, tr44] = f32.clone(); |
238 | | |
239 | | [ |
240 | 0 | Arc::new(move |input: &[P], output: &mut [P]| { |
241 | 0 | debug_assert_eq!(input.len() / 3, output.len()); |
242 | | |
243 | 0 | let mut ibuffer = [0.0f32; 3 * N]; |
244 | 0 | let mut obuffer = [0.0f32; 3 * N]; |
245 | | |
246 | 0 | for (rgb, output) in input.chunks(3 * N).zip(output.chunks_mut(N)) { |
247 | 0 | let n = output.len(); |
248 | 0 | let ibuffer = &mut ibuffer[..3 * n]; |
249 | 0 | let obuffer = &mut obuffer[..3 * n]; |
250 | 0 | CicpTransform::expand_rgb(rgb, ibuffer); |
251 | 0 | tr33.transform(ibuffer, obuffer).expect("transform failed"); |
252 | 0 | CicpTransform::clamp_rgb_luma(obuffer, output, output_coef); |
253 | 0 | } |
254 | 0 | }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>, Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<f32>::{closure#5}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u8>::{closure#5}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u16>::{closure#5} |
255 | 0 | Arc::new(move |input: &[P], output: &mut [P]| { |
256 | 0 | debug_assert_eq!(input.len() / 3, output.len() / 2); |
257 | | |
258 | 0 | let mut ibuffer = [0.0f32; 3 * N]; |
259 | 0 | let mut obuffer = [0.0f32; 4 * N]; |
260 | | |
261 | 0 | for (rgb, output) in input.chunks(4 * N).zip(output.chunks_mut(2 * N)) { |
262 | 0 | let n = output.len() / 2; |
263 | 0 | let ibuffer = &mut ibuffer[..3 * n]; |
264 | 0 | let obuffer = &mut obuffer[..4 * n]; |
265 | 0 | CicpTransform::expand_rgb(rgb, ibuffer); |
266 | 0 | tr34.transform(ibuffer, obuffer).expect("transform failed"); |
267 | 0 | CicpTransform::clamp_rgba_luma(obuffer, output, output_coef); |
268 | 0 | } |
269 | 0 | }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>, Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<f32>::{closure#6}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u8>::{closure#6}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u16>::{closure#6} |
270 | 0 | Arc::new(move |input: &[P], output: &mut [P]| { |
271 | 0 | debug_assert_eq!(input.len() / 4, output.len()); |
272 | | |
273 | 0 | let mut ibuffer = [0.0f32; 4 * N]; |
274 | 0 | let mut obuffer = [0.0f32; 3 * N]; |
275 | | |
276 | 0 | for (rgba, output) in input.chunks(4 * N).zip(output.chunks_mut(N)) { |
277 | 0 | let n = output.len(); |
278 | 0 | let ibuffer = &mut ibuffer[..4 * n]; |
279 | 0 | let obuffer = &mut obuffer[..3 * n]; |
280 | 0 | CicpTransform::expand_rgba(rgba, ibuffer); |
281 | 0 | tr43.transform(ibuffer, obuffer).expect("transform failed"); |
282 | 0 | CicpTransform::clamp_rgb_luma(obuffer, output, output_coef); |
283 | 0 | } |
284 | 0 | }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>, Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<f32>::{closure#7}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u8>::{closure#7}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u16>::{closure#7} |
285 | 0 | Arc::new(move |input: &[P], output: &mut [P]| { |
286 | 0 | debug_assert_eq!(input.len() / 4, output.len() / 2); |
287 | | |
288 | 0 | let mut ibuffer = [0.0f32; 4 * N]; |
289 | 0 | let mut obuffer = [0.0f32; 4 * N]; |
290 | | |
291 | 0 | for (rgba, output) in input.chunks(4 * N).zip(output.chunks_mut(2 * N)) { |
292 | 0 | let n = output.len() / 2; |
293 | 0 | let ibuffer = &mut ibuffer[..4 * n]; |
294 | 0 | let obuffer = &mut obuffer[..4 * n]; |
295 | 0 | CicpTransform::expand_rgba(rgba, ibuffer); |
296 | 0 | tr44.transform(ibuffer, obuffer).expect("transform failed"); |
297 | 0 | CicpTransform::clamp_rgba_luma(obuffer, output, output_coef); |
298 | 0 | } |
299 | 0 | }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>, Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<f32>::{closure#8}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u8>::{closure#8}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u16>::{closure#8} |
300 | | ] |
301 | | }; |
302 | | |
303 | | // luma-luma both expand and contract |
304 | 0 | let luma_luma = { |
305 | 0 | let [tr33, tr34, tr43, tr44] = f32; |
306 | | |
307 | | [ |
308 | 0 | Arc::new(move |input: &[P], output: &mut [P]| { |
309 | 0 | debug_assert_eq!(input.len(), output.len()); |
310 | 0 | let mut ibuffer = [0.0f32; 3 * N]; |
311 | 0 | let mut obuffer = [0.0f32; 3 * N]; |
312 | | |
313 | 0 | for (luma, output) in input.chunks(N).zip(output.chunks_mut(N)) { |
314 | 0 | let n = luma.len(); |
315 | 0 | let ibuffer = &mut ibuffer[..3 * n]; |
316 | 0 | let obuffer = &mut obuffer[..3 * n]; |
317 | 0 | CicpTransform::expand_luma_rgb(luma, ibuffer); |
318 | 0 | tr33.transform(ibuffer, obuffer).expect("transform failed"); |
319 | 0 | CicpTransform::clamp_rgb_luma(obuffer, output, output_coef); |
320 | 0 | } |
321 | 0 | }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>, Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<f32>::{closure#9}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u8>::{closure#9}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u16>::{closure#9} |
322 | 0 | Arc::new(move |input: &[P], output: &mut [P]| { |
323 | 0 | debug_assert_eq!(input.len(), output.len() / 2); |
324 | 0 | let mut ibuffer = [0.0f32; 3 * N]; |
325 | 0 | let mut obuffer = [0.0f32; 4 * N]; |
326 | | |
327 | 0 | for (luma, output) in input.chunks(N).zip(output.chunks_mut(2 * N)) { |
328 | 0 | let n = luma.len(); |
329 | 0 | let ibuffer = &mut ibuffer[..3 * n]; |
330 | 0 | let obuffer = &mut obuffer[..4 * n]; |
331 | 0 | CicpTransform::expand_luma_rgb(luma, ibuffer); |
332 | 0 | tr34.transform(ibuffer, obuffer).expect("transform failed"); |
333 | 0 | CicpTransform::clamp_rgba_luma(obuffer, output, output_coef); |
334 | 0 | } |
335 | 0 | }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>, Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<f32>::{closure#10}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u8>::{closure#10}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u16>::{closure#10} |
336 | 0 | Arc::new(move |input: &[P], output: &mut [P]| { |
337 | 0 | debug_assert_eq!(input.len() / 2, output.len()); |
338 | 0 | let mut ibuffer = [0.0f32; 4 * N]; |
339 | 0 | let mut obuffer = [0.0f32; 3 * N]; |
340 | | |
341 | 0 | for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(N)) { |
342 | 0 | let n = luma.len() / 2; |
343 | 0 | let ibuffer = &mut ibuffer[..4 * n]; |
344 | 0 | let obuffer = &mut obuffer[..3 * n]; |
345 | 0 | CicpTransform::expand_luma_rgba(luma, ibuffer); |
346 | 0 | tr43.transform(ibuffer, obuffer).expect("transform failed"); |
347 | 0 | CicpTransform::clamp_rgb_luma(obuffer, output, output_coef); |
348 | 0 | } |
349 | 0 | }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>, Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<f32>::{closure#11}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u8>::{closure#11}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u16>::{closure#11} |
350 | 0 | Arc::new(move |input: &[P], output: &mut [P]| { |
351 | 0 | debug_assert_eq!(input.len() / 2, output.len() / 2); |
352 | 0 | let mut ibuffer = [0.0f32; 4 * N]; |
353 | 0 | let mut obuffer = [0.0f32; 4 * N]; |
354 | | |
355 | 0 | for (luma, output) in input.chunks(2 * N).zip(output.chunks_mut(2 * N)) { |
356 | 0 | let n = luma.len() / 2; |
357 | 0 | let ibuffer = &mut ibuffer[..4 * n]; |
358 | 0 | let obuffer = &mut obuffer[..4 * n]; |
359 | 0 | CicpTransform::expand_luma_rgba(luma, ibuffer); |
360 | 0 | tr44.transform(ibuffer, obuffer).expect("transform failed"); |
361 | 0 | CicpTransform::clamp_rgba_luma(obuffer, output, output_coef); |
362 | 0 | } |
363 | 0 | }) as Arc<dyn Fn(&[P], &mut [P]) + Send + Sync>, Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<f32>::{closure#12}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u8>::{closure#12}Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u16>::{closure#12} |
364 | | ] |
365 | | }; |
366 | | |
367 | 0 | Some(crate::metadata::cicp::RgbTransforms { |
368 | 0 | slices, |
369 | 0 | luma_rgb, |
370 | 0 | rgb_luma, |
371 | 0 | luma_luma, |
372 | 0 | }) |
373 | 0 | } Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<f32> Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u8> Unexecuted instantiation: <image::metadata::moxcms::Moxcms>::build_transforms::<u16> |
374 | | } |
375 | | |
376 | | /// An RGB profile with its related (same tone-mapping) gray profile. |
377 | | /// |
378 | | /// This is the whole input information which we must be able to pass to the CMS in a support |
379 | | /// transform, to handle all possible combinations of `ColorType` pixels that can be thrown at us. |
380 | | /// For instance, in a previous iteration we had a separate gray profile here (but now handle that |
381 | | /// internally by expansion to RGB through an YCbCr). Future iterations may add additional structs |
382 | | /// to be computed for validating `CicpTransform::new`. |
383 | | struct ColorProfile { |
384 | | rgb: moxcms::ColorProfile, |
385 | | } |
386 | | |
387 | | impl ColorProfile { |
388 | 0 | fn map_layout(&self, layout: LayoutWithColor) -> (&moxcms::ColorProfile, moxcms::Layout) { |
389 | 0 | match layout { |
390 | 0 | LayoutWithColor::Rgb => (&self.rgb, moxcms::Layout::Rgb), |
391 | 0 | LayoutWithColor::Rgba => (&self.rgb, moxcms::Layout::Rgba), |
392 | | // See comment in `to_moxcms_profile`. |
393 | 0 | LayoutWithColor::Luma | LayoutWithColor::LumaAlpha => unreachable!(), |
394 | | } |
395 | 0 | } |
396 | | } |
397 | | |
398 | | impl Cicp { |
399 | | /// Get an compute representation of an ICC profile for RGB. |
400 | | /// |
401 | | /// Note you should *not* be using this profile for export in a file, as discussed below. |
402 | | /// |
403 | | /// This is straightforward for Rgb and RgbA representations. |
404 | | /// |
405 | | /// Our luma models a Y component of a YCbCr color space. It turns out that ICC V4 does |
406 | | /// not support pure Luma in any other whitepoint apart from D50 (the native profile |
407 | | /// connection space). The use of a grayTRC does *not* take the chromatic adaptation |
408 | | /// matrix into account. Of course we can encode the adaptation into the TRC as a |
409 | | /// coefficient, the Y component of the product of the whitepoint adaptation matrix |
410 | | /// inverse and the pcs's whitepoint XYZ, but that is only correct for gray -> gray |
411 | | /// conversion (and that coefficient should generally be `1`). |
412 | | /// |
413 | | /// Hence we use a YCbCr. The data->pcs path could be modelled by ("M" curves, matrix, "B" |
414 | | /// curves) where B curves or M curves are all the identity, depending on whether constant or |
415 | | /// non-constant luma is in use. This is a subset of the capabilities that a lutAToBType |
416 | | /// allows. Unfortunately, this is not implemented in moxcms yet and for efficiency we would |
417 | | /// like to have a masked `create_transform_*` in which the CbCr channels are discarded / |
418 | | /// assumed 0 instead of them being in memory. Due to this special case and for supporting |
419 | | /// conversions between sample types, we implement said promotion as part of conversion to |
420 | | /// Rgba32F in this crate. |
421 | | /// |
422 | | /// For export to file, it would arguably correct to use a carefully crafted gray profile which |
423 | | /// we may implement in another function. That is, we could setup a tone reproduction curve |
424 | | /// which maps each sample value (which ICC regards as D50) into XYZ D50 in such a way that it |
425 | | /// _appears_ with the correct D50 luminance that we would get if we had used the conversion |
426 | | /// unders its true input whitepoint. The resulting color has a slightly wrong chroma as it is |
427 | | /// linearly dependent on D50 instead, but it's brightness would be correctly presented. At |
428 | | /// least for perceptual intent this might be alright. |
429 | 0 | fn to_moxcms_compute_profile(self) -> Option<ColorProfile> { |
430 | 0 | let mut rgb = moxcms::ColorProfile::new_srgb(); |
431 | | |
432 | 0 | rgb.update_rgb_colorimetry_from_cicp(moxcms::CicpProfile { |
433 | 0 | color_primaries: self.primaries.to_moxcms(), |
434 | 0 | transfer_characteristics: self.transfer.to_moxcms(), |
435 | 0 | matrix_coefficients: self.matrix.to_moxcms()?, |
436 | 0 | full_range: match self.full_range { |
437 | 0 | CicpVideoFullRangeFlag::NarrowRange => false, |
438 | 0 | CicpVideoFullRangeFlag::FullRange => true, |
439 | | }, |
440 | | }); |
441 | | |
442 | 0 | Some(ColorProfile { rgb }) |
443 | 0 | } |
444 | | } |
445 | | |
446 | | impl CicpColorPrimaries { |
447 | 0 | fn to_moxcms(self) -> moxcms::CicpColorPrimaries { |
448 | | use moxcms::CicpColorPrimaries as M; |
449 | | |
450 | 0 | match self { |
451 | 0 | CicpColorPrimaries::SRgb => M::Bt709, |
452 | 0 | CicpColorPrimaries::Unspecified => M::Unspecified, |
453 | 0 | CicpColorPrimaries::RgbM => M::Bt470M, |
454 | 0 | CicpColorPrimaries::RgbB => M::Bt470Bg, |
455 | 0 | CicpColorPrimaries::Bt601 => M::Bt601, |
456 | 0 | CicpColorPrimaries::Rgb240m => M::Smpte240, |
457 | 0 | CicpColorPrimaries::GenericFilm => M::GenericFilm, |
458 | 0 | CicpColorPrimaries::Rgb2020 => M::Bt2020, |
459 | 0 | CicpColorPrimaries::Xyz => M::Xyz, |
460 | 0 | CicpColorPrimaries::SmpteRp431 => M::Smpte431, |
461 | 0 | CicpColorPrimaries::SmpteRp432 => M::Smpte432, |
462 | 0 | CicpColorPrimaries::Industry22 => M::Ebu3213, |
463 | | } |
464 | 0 | } |
465 | | } |
466 | | |
467 | | impl CicpTransferCharacteristics { |
468 | 0 | fn to_moxcms(self) -> moxcms::TransferCharacteristics { |
469 | | use moxcms::TransferCharacteristics as T; |
470 | | |
471 | 0 | match self { |
472 | 0 | CicpTransferCharacteristics::Bt709 => T::Bt709, |
473 | 0 | CicpTransferCharacteristics::Unspecified => T::Unspecified, |
474 | 0 | CicpTransferCharacteristics::Bt470M => T::Bt470M, |
475 | 0 | CicpTransferCharacteristics::Bt470BG => T::Bt470Bg, |
476 | 0 | CicpTransferCharacteristics::Bt601 => T::Bt601, |
477 | 0 | CicpTransferCharacteristics::Smpte240m => T::Smpte240, |
478 | 0 | CicpTransferCharacteristics::Linear => T::Linear, |
479 | 0 | CicpTransferCharacteristics::Log100 => T::Log100, |
480 | 0 | CicpTransferCharacteristics::LogSqrt => T::Log100sqrt10, |
481 | 0 | CicpTransferCharacteristics::Iec61966_2_4 => T::Iec61966, |
482 | 0 | CicpTransferCharacteristics::Bt1361 => T::Bt1361, |
483 | 0 | CicpTransferCharacteristics::SRgb => T::Srgb, |
484 | 0 | CicpTransferCharacteristics::Bt2020_10bit => T::Bt202010bit, |
485 | 0 | CicpTransferCharacteristics::Bt2020_12bit => T::Bt202012bit, |
486 | 0 | CicpTransferCharacteristics::Smpte2084 => T::Smpte2084, |
487 | 0 | CicpTransferCharacteristics::Smpte428 => T::Smpte428, |
488 | 0 | CicpTransferCharacteristics::Bt2100Hlg => T::Hlg, |
489 | | } |
490 | 0 | } |
491 | | } |
492 | | |
493 | | impl CicpMatrixCoefficients { |
494 | 0 | fn to_moxcms(self) -> Option<moxcms::MatrixCoefficients> { |
495 | | use moxcms::MatrixCoefficients as M; |
496 | | |
497 | 0 | Some(match self { |
498 | 0 | CicpMatrixCoefficients::Identity => M::Identity, |
499 | 0 | CicpMatrixCoefficients::Unspecified => M::Unspecified, |
500 | 0 | CicpMatrixCoefficients::Bt709 => M::Bt709, |
501 | 0 | CicpMatrixCoefficients::UsFCC => M::Fcc, |
502 | 0 | CicpMatrixCoefficients::Bt470BG => M::Bt470Bg, |
503 | 0 | CicpMatrixCoefficients::Smpte170m => M::Smpte170m, |
504 | 0 | CicpMatrixCoefficients::Smpte240m => M::Smpte240m, |
505 | 0 | CicpMatrixCoefficients::YCgCo => M::YCgCo, |
506 | 0 | CicpMatrixCoefficients::Bt2020NonConstant => M::Bt2020Ncl, |
507 | 0 | CicpMatrixCoefficients::Bt2020Constant => M::Bt2020Cl, |
508 | 0 | CicpMatrixCoefficients::Smpte2085 => M::Smpte2085, |
509 | 0 | CicpMatrixCoefficients::ChromaticityDerivedNonConstant => M::ChromaticityDerivedNCL, |
510 | 0 | CicpMatrixCoefficients::ChromaticityDerivedConstant => M::ChromaticityDerivedCL, |
511 | 0 | CicpMatrixCoefficients::Bt2100 => M::ICtCp, |
512 | | CicpMatrixCoefficients::IptPqC2 |
513 | | | CicpMatrixCoefficients::YCgCoRe |
514 | 0 | | CicpMatrixCoefficients::YCgCoRo => return None, |
515 | | }) |
516 | 0 | } |
517 | | } |