Coverage Report

Created: 2026-09-02 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}