Coverage Report

Created: 2026-10-28 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mozilla-central/gfx/qcms/src/iccread.rs
Line
Count
Source
1
//  qcms
2
//  Copyright (C) 2009 Mozilla Foundation
3
//  Copyright (C) 1998-2007 Marti Maria
4
//
5
// Permission is hereby granted, free of charge, to any person obtaining
6
// a copy of this software and associated documentation files (the "Software"),
7
// to deal in the Software without restriction, including without limitation
8
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
9
// and/or sell copies of the Software, and to permit persons to whom the Software
10
// is furnished to do so, subject to the following conditions:
11
//
12
// The above copyright notice and this permission notice shall be included in
13
// all copies or substantial portions of the Software.
14
//
15
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
17
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
23
use std::{
24
    convert::{TryInto, TryFrom},
25
    sync::atomic::AtomicBool,
26
    sync::Arc,
27
};
28
29
use crate::{
30
    double_to_s15Fixed16Number,
31
    transform::{set_rgb_colorants, PrecacheOuput},
32
};
33
use crate::{
34
    matrix::{Matrix, Vector},
35
    s15Fixed16Number, s15Fixed16Number_to_float, Intent,
36
    Intent::*,
37
};
38
39
pub static SUPPORTS_ICCV4: AtomicBool = AtomicBool::new(cfg!(feature = "iccv4-enabled"));
40
41
pub const RGB_SIGNATURE: u32 = 0x52474220;
42
pub const GRAY_SIGNATURE: u32 = 0x47524159;
43
pub const XYZ_SIGNATURE: u32 = 0x58595A20;
44
pub const LAB_SIGNATURE: u32 = 0x4C616220;
45
pub const CMYK_SIGNATURE: u32 = 0x434D594B; // 'CMYK'
46
47
/// A color profile
48
#[derive(Default, Debug)]
49
pub struct Profile {
50
    pub(crate) class_type: u32,
51
    pub(crate) color_space: u32,
52
    pub(crate) pcs: u32,
53
    pub(crate) rendering_intent: Intent,
54
    pub(crate) redColorant: XYZNumber,
55
    pub(crate) blueColorant: XYZNumber,
56
    pub(crate) greenColorant: XYZNumber,
57
    // "TRC" is EOTF, e.g. gamma->linear transfer function.
58
    // Because ICC profiles are phrased as decodings to the xyzd50-linear PCS.
59
    pub(crate) redTRC: Option<Box<curveType>>,
60
    pub(crate) blueTRC: Option<Box<curveType>>,
61
    pub(crate) greenTRC: Option<Box<curveType>>,
62
    pub(crate) grayTRC: Option<Box<curveType>>,
63
    pub(crate) A2B0: Option<Box<lutType>>,
64
    pub(crate) B2A0: Option<Box<lutType>>,
65
    pub(crate) mAB: Option<Box<lutmABType>>,
66
    pub(crate) mBA: Option<Box<lutmABType>>,
67
    pub(crate) chromaticAdaption: Option<Matrix>,
68
    pub(crate) description: Option<String>,
69
    pub(crate) precache_output: Option<Arc<PrecacheOuput>>,
70
    is_srgb: bool,
71
}
72
73
#[derive(Debug, Default)]
74
#[allow(clippy::upper_case_acronyms)]
75
pub(crate) struct lutmABType {
76
    pub num_in_channels: u8,
77
    pub num_out_channels: u8,
78
    // 16 is the upperbound, actual is 0..num_in_channels.
79
    pub num_grid_points: [u8; 16],
80
    pub e00: s15Fixed16Number,
81
    pub e01: s15Fixed16Number,
82
    pub e02: s15Fixed16Number,
83
    pub e03: s15Fixed16Number,
84
    pub e10: s15Fixed16Number,
85
    pub e11: s15Fixed16Number,
86
    pub e12: s15Fixed16Number,
87
    pub e13: s15Fixed16Number,
88
    pub e20: s15Fixed16Number,
89
    pub e21: s15Fixed16Number,
90
    pub e22: s15Fixed16Number,
91
    pub e23: s15Fixed16Number,
92
    // reversed elements (for mBA)
93
    pub reversed: bool,
94
    pub clut_table: Option<Vec<f32>>,
95
    pub a_curves: [Option<Box<curveType>>; MAX_CHANNELS],
96
    pub b_curves: [Option<Box<curveType>>; MAX_CHANNELS],
97
    pub m_curves: [Option<Box<curveType>>; MAX_CHANNELS],
98
}
99
#[derive(Clone, Debug)]
100
pub(crate) enum curveType {
101
    Curve(Vec<uInt16Number>), // len=0 => Linear, len=1 => Gamma(v[0]), _ => lut
102
    /// The ICC parametricCurveType is specified in terms of s15Fixed16Number,
103
    /// so it's possible to use this variant to specify greater precision than
104
    /// any raw ICC profile could
105
    Parametric(Vec<f32>),
106
}
107
type uInt16Number = u16;
108
109
/* should lut8Type and lut16Type be different types? */
110
#[derive(Debug)]
111
pub(crate) struct lutType {
112
    // used by lut8Type/lut16Type (mft2) only
113
    pub num_input_channels: u8,
114
    pub num_output_channels: u8,
115
    pub num_clut_grid_points: u8,
116
    pub e00: s15Fixed16Number,
117
    pub e01: s15Fixed16Number,
118
    pub e02: s15Fixed16Number,
119
    pub e10: s15Fixed16Number,
120
    pub e11: s15Fixed16Number,
121
    pub e12: s15Fixed16Number,
122
    pub e20: s15Fixed16Number,
123
    pub e21: s15Fixed16Number,
124
    pub e22: s15Fixed16Number,
125
    pub num_input_table_entries: u16,
126
    pub num_output_table_entries: u16,
127
    pub input_table: Vec<f32>,
128
    pub clut_table: Vec<f32>,
129
    pub output_table: Vec<f32>,
130
}
131
132
#[repr(C)]
133
#[derive(Copy, Clone, Debug, Default)]
134
#[allow(clippy::upper_case_acronyms)]
135
pub struct XYZNumber {
136
    pub X: s15Fixed16Number,
137
    pub Y: s15Fixed16Number,
138
    pub Z: s15Fixed16Number,
139
}
140
141
/// A color in the CIE xyY color space
142
/* the names for the following two types are sort of ugly */
143
#[repr(C)]
144
#[derive(Copy, Clone)]
145
#[allow(clippy::upper_case_acronyms)]
146
pub struct qcms_CIE_xyY {
147
    pub x: f64,
148
    pub y: f64,
149
    pub Y: f64,
150
}
151
152
/// A more convenient type for specifying primaries and white points where
153
/// luminosity is irrelevant
154
struct qcms_chromaticity {
155
    x: f64,
156
    y: f64,
157
}
158
159
impl qcms_chromaticity {
160
    const D65: Self = Self {
161
        x: 0.3127,
162
        y: 0.3290,
163
    };
164
}
165
166
impl From<qcms_chromaticity> for qcms_CIE_xyY {
167
24
    fn from(qcms_chromaticity { x, y }: qcms_chromaticity) -> Self {
168
24
        Self { x, y, Y: 1.0 }
169
24
    }
170
}
171
172
/// a set of CIE_xyY values that can use to describe the primaries of a color space
173
#[repr(C)]
174
#[derive(Copy, Clone)]
175
#[allow(clippy::upper_case_acronyms)]
176
pub struct qcms_CIE_xyYTRIPLE {
177
    pub red: qcms_CIE_xyY,
178
    pub green: qcms_CIE_xyY,
179
    pub blue: qcms_CIE_xyY,
180
}
181
182
struct Tag {
183
    signature: u32,
184
    offset: u32,
185
    size: u32,
186
}
187
188
/* It might be worth having a unified limit on content controlled
189
 * allocation per profile. This would remove the need for many
190
 * of the arbitrary limits that we used */
191
192
type TagIndex = [Tag];
193
194
/* a wrapper around the memory that we are going to parse
195
 * into a qcms_profile */
196
struct MemSource<'a> {
197
    buf: &'a [u8],
198
    valid: bool,
199
    invalid_reason: Option<&'static str>,
200
}
201
pub type uInt8Number = u8;
202
#[inline]
203
1.69M
fn uInt8Number_to_float(a: uInt8Number) -> f32 {
204
1.69M
    a as f32 / 255.0
205
1.69M
}
206
207
#[inline]
208
244k
fn uInt16Number_to_float(a: uInt16Number) -> f32 {
209
244k
    a as f32 / 65535.0
210
244k
}
211
212
991k
fn invalid_source(mem: &mut MemSource, reason: &'static str) {
213
991k
    mem.valid = false;
214
991k
    mem.invalid_reason = Some(reason);
215
991k
}
216
4.88k
fn read_u32(mem: &mut MemSource, offset: usize) -> u32 {
217
4.88k
    let val = mem.buf.get(offset..offset + 4);
218
4.88k
    if let Some(val) = val {
219
4.86k
        let val = val.try_into().unwrap();
220
4.86k
        u32::from_be_bytes(val)
221
    } else {
222
19
        invalid_source(mem, "Invalid offset");
223
19
        0
224
    }
225
4.88k
}
226
622k
fn read_u16(mem: &mut MemSource, offset: usize) -> u16 {
227
622k
    let val = mem.buf.get(offset..offset + 2);
228
622k
    if let Some(val) = val {
229
619k
        let val = val.try_into().unwrap();
230
619k
        u16::from_be_bytes(val)
231
    } else {
232
3.77k
        invalid_source(mem, "Invalid offset");
233
3.77k
        0
234
    }
235
622k
}
236
1.69M
fn read_u8(mem: &mut MemSource, offset: usize) -> u8 {
237
1.69M
    let val = mem.buf.get(offset);
238
1.69M
    if let Some(val) = val {
239
707k
        *val
240
    } else {
241
987k
        invalid_source(mem, "Invalid offset");
242
987k
        0
243
    }
244
1.69M
}
245
696
fn read_s15Fixed16Number(mem: &mut MemSource, offset: usize) -> s15Fixed16Number {
246
696
    read_u32(mem, offset) as s15Fixed16Number
247
696
}
248
1.69M
fn read_uInt8Number(mem: &mut MemSource, offset: usize) -> uInt8Number {
249
1.69M
    read_u8(mem, offset)
250
1.69M
}
251
244k
fn read_uInt16Number(mem: &mut MemSource, offset: usize) -> uInt16Number {
252
244k
    read_u16(mem, offset)
253
244k
}
254
0
pub fn write_u32(mem: &mut [u8], offset: usize, value: u32) {
255
    // we use get() and expect() instead of [..] so there's only one call to panic
256
    // instead of two
257
0
    mem.get_mut(offset..offset + std::mem::size_of_val(&value))
258
0
        .expect("OOB")
259
0
        .copy_from_slice(&value.to_be_bytes());
260
0
}
261
0
pub fn write_u16(mem: &mut [u8], offset: usize, value: u16) {
262
    // we use get() and expect() instead of [..] so there's only one call to panic
263
    // intead of two
264
0
    mem.get_mut(offset..offset + std::mem::size_of_val(&value))
265
0
        .expect("OOB")
266
0
        .copy_from_slice(&value.to_be_bytes());
267
0
}
268
269
/* An arbitrary 4MB limit on profile size */
270
pub(crate) const MAX_PROFILE_SIZE: usize = 1024 * 1024 * 4;
271
const MAX_TAG_COUNT: u32 = 1024;
272
273
40
fn check_CMM_type_signature(_src: &mut MemSource) {
274
    //uint32_t CMM_type_signature = read_u32(src, 4);
275
    //TODO: do the check?
276
40
}
277
40
fn check_profile_version(src: &mut MemSource) {
278
    /*
279
    uint8_t major_revision = read_u8(src, 8 + 0);
280
    uint8_t minor_revision = read_u8(src, 8 + 1);
281
    */
282
40
    let reserved1: u8 = read_u8(src, (8 + 2) as usize);
283
40
    let reserved2: u8 = read_u8(src, (8 + 3) as usize);
284
    /* Checking the version doesn't buy us anything
285
    if (major_revision != 0x4) {
286
        if (major_revision > 0x2)
287
            invalid_source(src, "Unsupported major revision");
288
        if (minor_revision > 0x40)
289
            invalid_source(src, "Unsupported minor revision");
290
    }
291
    */
292
40
    if reserved1 != 0 || reserved2 != 0 {
293
3
        invalid_source(src, "Invalid reserved bytes");
294
37
    };
295
40
}
296
297
const INPUT_DEVICE_PROFILE: u32 = 0x73636e72; // 'scnr'
298
pub const DISPLAY_DEVICE_PROFILE: u32 = 0x6d6e7472; // 'mntr'
299
const OUTPUT_DEVICE_PROFILE: u32 = 0x70727472; // 'prtr'
300
const DEVICE_LINK_PROFILE: u32 = 0x6c696e6b; // 'link'
301
const COLOR_SPACE_PROFILE: u32 = 0x73706163; // 'spac'
302
const ABSTRACT_PROFILE: u32 = 0x61627374; // 'abst'
303
const NAMED_COLOR_PROFILE: u32 = 0x6e6d636c; // 'nmcl'
304
305
40
fn read_class_signature(profile: &mut Profile, mem: &mut MemSource) {
306
40
    profile.class_type = read_u32(mem, 12);
307
40
    match profile.class_type {
308
        DISPLAY_DEVICE_PROFILE
309
        | INPUT_DEVICE_PROFILE
310
        | OUTPUT_DEVICE_PROFILE
311
31
        | COLOR_SPACE_PROFILE => {}
312
9
        _ => {
313
9
            invalid_source(mem, "Invalid  Profile/Device Class signature");
314
9
        }
315
    };
316
40
}
317
40
fn read_color_space(profile: &mut Profile, mem: &mut MemSource) {
318
40
    profile.color_space = read_u32(mem, 16);
319
40
    match profile.color_space {
320
30
        RGB_SIGNATURE | GRAY_SIGNATURE => {}
321
        #[cfg(feature = "cmyk")]
322
1
        CMYK_SIGNATURE => {}
323
9
        _ => {
324
9
            invalid_source(mem, "Unsupported colorspace");
325
9
        }
326
    };
327
40
}
328
40
fn read_pcs(profile: &mut Profile, mem: &mut MemSource) {
329
40
    profile.pcs = read_u32(mem, 20);
330
40
    match profile.pcs {
331
31
        XYZ_SIGNATURE | LAB_SIGNATURE => {}
332
9
        _ => {
333
9
            invalid_source(mem, "Unsupported pcs");
334
9
        }
335
    };
336
40
}
337
30
fn read_tag_table(_profile: &mut Profile, mem: &mut MemSource) -> Vec<Tag> {
338
30
    let count = read_u32(mem, 128);
339
30
    if count > MAX_TAG_COUNT {
340
0
        invalid_source(mem, "max number of tags exceeded");
341
0
        return Vec::new();
342
30
    }
343
30
    let mut index = Vec::with_capacity(count as usize);
344
1.21k
    for i in 0..count {
345
1.21k
        let tag_start = (128 + 4 + 4 * i * 3) as usize;
346
1.21k
        let offset = read_u32(mem, tag_start + 4);
347
1.21k
        if offset as usize > mem.buf.len() {
348
803
            invalid_source(mem, "tag points beyond the end of the buffer");
349
803
        }
350
1.21k
        index.push(Tag {
351
1.21k
            signature: read_u32(mem, tag_start),
352
1.21k
            offset,
353
1.21k
            size: read_u32(mem, tag_start + 8),
354
1.21k
        });
355
    }
356
357
30
    index
358
30
}
359
360
/// Checks a profile for obvious inconsistencies and returns
361
/// true if the profile looks bogus and should probably be
362
/// ignored.
363
#[no_mangle]
364
8
pub extern "C" fn qcms_profile_is_bogus(profile: &mut Profile) -> bool {
365
8
    let mut sum: [f32; 3] = [0.; 3];
366
8
    let mut target: [f32; 3] = [0.; 3];
367
8
    let mut tolerance: [f32; 3] = [0.; 3];
368
    let rX: f32;
369
    let rY: f32;
370
    let rZ: f32;
371
    let gX: f32;
372
    let gY: f32;
373
    let gZ: f32;
374
    let bX: f32;
375
    let bY: f32;
376
    let bZ: f32;
377
    let negative: bool;
378
    let mut i: u32;
379
    // We currently only check the bogosity of RGB profiles
380
8
    if profile.color_space != RGB_SIGNATURE {
381
0
        return false;
382
8
    }
383
8
    if profile.A2B0.is_some()
384
3
        || profile.B2A0.is_some()
385
3
        || profile.mAB.is_some()
386
1
        || profile.mBA.is_some()
387
    {
388
7
        return false;
389
1
    }
390
1
    rX = s15Fixed16Number_to_float(profile.redColorant.X);
391
1
    rY = s15Fixed16Number_to_float(profile.redColorant.Y);
392
1
    rZ = s15Fixed16Number_to_float(profile.redColorant.Z);
393
1
    gX = s15Fixed16Number_to_float(profile.greenColorant.X);
394
1
    gY = s15Fixed16Number_to_float(profile.greenColorant.Y);
395
1
    gZ = s15Fixed16Number_to_float(profile.greenColorant.Z);
396
1
    bX = s15Fixed16Number_to_float(profile.blueColorant.X);
397
1
    bY = s15Fixed16Number_to_float(profile.blueColorant.Y);
398
1
    bZ = s15Fixed16Number_to_float(profile.blueColorant.Z);
399
    // Sum the values; they should add up to something close to white
400
1
    sum[0] = rX + gX + bX;
401
1
    sum[1] = rY + gY + bY;
402
1
    sum[2] = rZ + gZ + bZ;
403
    // Build our target vector (see mozilla bug 460629)
404
1
    target[0] = 0.96420;
405
1
    target[1] = 1.00000;
406
1
    target[2] = 0.82491;
407
    // Our tolerance vector - Recommended by Chris Murphy based on
408
    // conversion from the LAB space criterion of no more than 3 in any one
409
    // channel. This is similar to, but slightly more tolerant than Adobe's
410
    // criterion.
411
1
    tolerance[0] = 0.02;
412
1
    tolerance[1] = 0.02;
413
1
    tolerance[2] = 0.04;
414
    // Compare with our tolerance
415
1
    i = 0;
416
4
    while i < 3 {
417
3
        if !(sum[i as usize] - tolerance[i as usize] <= target[i as usize]
418
3
            && sum[i as usize] + tolerance[i as usize] >= target[i as usize])
419
        {
420
0
            return true;
421
3
        }
422
3
        i += 1
423
    }
424
1
    if false {
425
0
        negative = (rX < 0.)
426
0
            || (rY < 0.)
427
0
            || (rZ < 0.)
428
0
            || (gX < 0.)
429
0
            || (gY < 0.)
430
0
            || (gZ < 0.)
431
0
            || (bX < 0.)
432
0
            || (bY < 0.)
433
0
            || (bZ < 0.);
434
1
    } else {
435
1
        // Chromatic adaption to D50 can result in negative XYZ, but the white
436
1
        // point D50 tolerance test has passed. Accept negative values herein.
437
1
        // See https://bugzilla.mozilla.org/show_bug.cgi?id=498245#c18 onwards
438
1
        // for discussion about whether profile XYZ can or cannot be negative,
439
1
        // per the spec. Also the https://bugzil.la/450923 user report.
440
1
        // Also: https://bugzil.la/1799391 and https://bugzil.la/1792469
441
1
        negative = false; // bogus
442
1
    }
443
1
    if negative {
444
0
        return true;
445
1
    }
446
    // All Good
447
1
    false
448
8
}
449
450
pub const TAG_bXYZ: u32 = 0x6258595a;
451
pub const TAG_gXYZ: u32 = 0x6758595a;
452
pub const TAG_rXYZ: u32 = 0x7258595a;
453
pub const TAG_rTRC: u32 = 0x72545243;
454
pub const TAG_bTRC: u32 = 0x62545243;
455
pub const TAG_gTRC: u32 = 0x67545243;
456
pub const TAG_kTRC: u32 = 0x6b545243;
457
pub const TAG_A2B0: u32 = 0x41324230;
458
pub const TAG_B2A0: u32 = 0x42324130;
459
pub const TAG_CHAD: u32 = 0x63686164;
460
pub const TAG_desc: u32 = 0x64657363;
461
462
215
fn find_tag(index: &TagIndex, tag_id: u32) -> Option<&Tag> {
463
1.10k
    for t in index {
464
1.02k
        if t.signature == tag_id {
465
127
            return Some(t);
466
894
        }
467
    }
468
88
    None
469
215
}
470
471
pub const XYZ_TYPE: u32 = 0x58595a20; // 'XYZ '
472
pub const CURVE_TYPE: u32 = 0x63757276; // 'curv'
473
pub const PARAMETRIC_CURVE_TYPE: u32 = 0x70617261; // 'para'
474
pub const LUT16_TYPE: u32 = 0x6d667432; // 'mft2'
475
pub const LUT8_TYPE: u32 = 0x6d667431; // 'mft1'
476
pub const LUT_MAB_TYPE: u32 = 0x6d414220; // 'mAB '
477
pub const LUT_MBA_TYPE: u32 = 0x6d424120; // 'mBA '
478
pub const CHROMATIC_TYPE: u32 = 0x73663332; // 'sf32'
479
pub const TEXT_DESCRIPTION_TYPE: u32 = 0x64657363; // 'desc'
480
pub const MULTI_LOCALIZED_UNICODE_TYPE: u32 = 0x6d6c7563; // 'mluc'
481
482
/// Reads a textDescriptionType (v2) or multiLocalizedUnicodeType (v4) tag.
483
/// The description is informational only, so malformed data returns None
484
/// instead of invalidating the profile.
485
0
fn read_tag_description(buf: &[u8], tag: &Tag) -> Option<String> {
486
    const MAX_DESCRIPTION_CHARS: usize = 256;
487
0
    let start = tag.offset as usize;
488
0
    let data = buf.get(start..start.checked_add(tag.size as usize)?)?;
489
0
    let read_u32 = |at: usize| -> Option<u32> {
490
0
        Some(u32::from_be_bytes(data.get(at..at.checked_add(4)?)?.try_into().ok()?))
491
0
    };
492
0
    let text = match read_u32(0)? {
493
        TEXT_DESCRIPTION_TYPE => {
494
0
            let count = read_u32(8)? as usize;
495
0
            String::from_utf8_lossy(data.get(12..12usize.checked_add(count)?)?).into_owned()
496
        }
497
        MULTI_LOCALIZED_UNICODE_TYPE => {
498
            const RECORDS_START: usize = 16;
499
0
            let record_size = read_u32(12)? as usize;
500
0
            if record_size < 12 {
501
0
                return None;
502
0
            }
503
0
            let num_records = (read_u32(8)? as usize).min(data.len() / record_size);
504
            // Prefer an English record, otherwise use the first one.
505
0
            let record = (0..num_records)
506
0
                .map(|i| RECORDS_START + i * record_size)
507
0
                .find(|&r| data.get(r..r + 2) == Some(b"en"))
508
0
                .unwrap_or(RECORDS_START);
509
0
            let length = read_u32(record + 4)? as usize;
510
0
            let offset = read_u32(record + 8)? as usize;
511
0
            let units: Vec<u16> = data
512
0
                .get(offset..offset.checked_add(length)?)?
513
0
                .chunks_exact(2)
514
0
                .map(|c| u16::from_be_bytes([c[0], c[1]]))
515
0
                .collect();
516
0
            String::from_utf16_lossy(&units)
517
        }
518
0
        _ => return None,
519
    };
520
0
    let text: String = text
521
0
        .split('\0')
522
0
        .next()?
523
0
        .chars()
524
0
        .filter(|c| !c.is_control())
525
0
        .take(MAX_DESCRIPTION_CHARS)
526
0
        .collect();
527
0
    if text.is_empty() {
528
0
        None
529
    } else {
530
0
        Some(text)
531
    }
532
0
}
533
534
19
fn read_tag_s15Fixed16ArrayType(src: &mut MemSource, tag: &Tag) -> Matrix {
535
19
    let mut matrix: Matrix = Matrix { m: [[0.; 3]; 3] };
536
19
    let offset: u32 = tag.offset;
537
19
    let type_0: u32 = read_u32(src, offset as usize);
538
    // Check mandatory type signature for s16Fixed16ArrayType
539
19
    if type_0 != CHROMATIC_TYPE {
540
19
        invalid_source(src, "unexpected type, expected \'sf32\'");
541
19
    }
542
190
    for i in 0..=8 {
543
171
        matrix.m[(i / 3) as usize][(i % 3) as usize] = s15Fixed16Number_to_float(
544
171
            read_s15Fixed16Number(src, (offset + 8 + (i * 4) as u32) as usize),
545
171
        );
546
171
    }
547
19
    matrix
548
19
}
549
45
fn read_tag_XYZType(src: &mut MemSource, index: &TagIndex, tag_id: u32) -> XYZNumber {
550
45
    let mut num = XYZNumber { X: 0, Y: 0, Z: 0 };
551
45
    let tag = find_tag(&index, tag_id);
552
45
    if let Some(tag) = tag {
553
22
        let offset: u32 = tag.offset;
554
22
        let type_0: u32 = read_u32(src, offset as usize);
555
22
        if type_0 != XYZ_TYPE {
556
11
            invalid_source(src, "unexpected type, expected XYZ");
557
11
        }
558
22
        num.X = read_s15Fixed16Number(src, (offset + 8) as usize);
559
22
        num.Y = read_s15Fixed16Number(src, (offset + 12) as usize);
560
22
        num.Z = read_s15Fixed16Number(src, (offset + 16) as usize)
561
23
    } else {
562
23
        invalid_source(src, "missing xyztag");
563
23
    }
564
45
    num
565
45
}
566
// Read the tag at a given offset rather then the tag_index.
567
// This method is used when reading mAB tags where nested curveType are
568
// present that are not part of the tag_index.
569
99
fn read_curveType(src: &mut MemSource, offset: u32, len: &mut u32) -> Option<Box<curveType>> {
570
    const COUNT_TO_LENGTH: [u32; 5] = [1, 3, 4, 5, 7]; //PARAMETRIC_CURVE_TYPE
571
99
    let type_0: u32 = read_u32(src, offset as usize);
572
    let count: u32;
573
99
    if type_0 != CURVE_TYPE && type_0 != PARAMETRIC_CURVE_TYPE {
574
11
        invalid_source(src, "unexpected type, expected CURV or PARA");
575
11
        return None;
576
88
    }
577
88
    if type_0 == CURVE_TYPE {
578
30
        count = read_u32(src, (offset + 8) as usize);
579
        //arbitrary
580
30
        if count > 40000 {
581
0
            invalid_source(src, "curve size too large");
582
0
            return None;
583
30
        }
584
30
        let mut table = Vec::with_capacity(count as usize);
585
378k
        for i in 0..count {
586
378k
            table.push(read_u16(src, (offset + 12 + i * 2) as usize));
587
378k
        }
588
30
        *len = 12 + count * 2;
589
30
        Some(Box::new(curveType::Curve(table)))
590
    } else {
591
58
        count = read_u16(src, (offset + 8) as usize) as u32;
592
58
        if count > 4 {
593
1
            invalid_source(src, "parametric function type not supported.");
594
1
            return None;
595
57
        }
596
57
        let mut params = Vec::with_capacity(count as usize);
597
201
        for i in 0..COUNT_TO_LENGTH[count as usize] {
598
201
            params.push(s15Fixed16Number_to_float(read_s15Fixed16Number(
599
201
                src,
600
201
                (offset + 12 + i * 4) as usize,
601
201
            )));
602
201
        }
603
57
        *len = 12 + COUNT_TO_LENGTH[count as usize] * 4;
604
57
        if count == 1 || count == 2 {
605
            /* we have a type 1 or type 2 function that has a division by 'a' */
606
36
            let a: f32 = params[1];
607
36
            if a == 0.0 {
608
0
                invalid_source(src, "parametricCurve definition causes division by zero");
609
36
            }
610
21
        }
611
57
        Some(Box::new(curveType::Parametric(params)))
612
    }
613
99
}
614
18
fn read_tag_curveType(
615
18
    src: &mut MemSource,
616
18
    index: &TagIndex,
617
18
    tag_id: u32,
618
18
) -> Option<Box<curveType>> {
619
18
    let tag = find_tag(index, tag_id);
620
18
    if let Some(tag) = tag {
621
18
        let mut len: u32 = 0;
622
18
        return read_curveType(src, tag.offset, &mut len);
623
0
    } else {
624
0
        invalid_source(src, "missing curvetag");
625
0
    }
626
0
    None
627
18
}
628
629
const MAX_LUT_SIZE: u32 = 500000; // arbitrary
630
const MAX_CHANNELS: usize = 10; // arbitrary
631
33
fn read_nested_curveType(
632
33
    src: &mut MemSource,
633
33
    curveArray: &mut [Option<Box<curveType>>; MAX_CHANNELS],
634
33
    num_channels: u8,
635
33
    curve_offset: u32,
636
33
) {
637
33
    let mut channel_offset: u32 = 0;
638
    #[allow(clippy::needless_range_loop)]
639
81
    for i in 0..usize::from(num_channels) {
640
81
        let mut tag_len: u32 = 0;
641
81
        curveArray[i] = read_curveType(src, curve_offset + channel_offset, &mut tag_len);
642
81
        if curveArray[i].is_none() {
643
9
            invalid_source(src, "invalid nested curveType curve");
644
9
            break;
645
        } else {
646
72
            channel_offset += tag_len;
647
            // 4 byte aligned
648
72
            if tag_len % 4 != 0 {
649
12
                channel_offset += 4 - tag_len % 4
650
60
            }
651
        }
652
    }
653
33
}
654
655
/* See section 10.10 for specs */
656
11
fn read_tag_lutmABType(src: &mut MemSource, tag: &Tag) -> Option<Box<lutmABType>> {
657
11
    let offset: u32 = tag.offset;
658
11
    let mut clut_size: u32 = 1;
659
11
    let type_0: u32 = read_u32(src, offset as usize);
660
11
    if type_0 != LUT_MAB_TYPE && type_0 != LUT_MBA_TYPE {
661
0
        return None;
662
11
    }
663
11
    let num_in_channels = read_u8(src, (offset + 8) as usize);
664
11
    let num_out_channels = read_u8(src, (offset + 9) as usize);
665
11
    if num_in_channels > 10 || num_out_channels > 10 {
666
0
        return None;
667
11
    }
668
    // We require 3in/out channels since we only support RGB->XYZ (or RGB->LAB)
669
    // XXX: If we remove this restriction make sure that the number of channels
670
    //      is less or equal to the maximum number of mAB curves in qcmsint.h
671
    //      also check for clut_size overflow. Also make sure it's != 0
672
11
    if num_in_channels != 3 || num_out_channels != 3 {
673
0
        return None;
674
11
    }
675
    // some of this data is optional and is denoted by a zero offset
676
    // we also use this to track their existance
677
11
    let mut a_curve_offset = read_u32(src, (offset + 28) as usize);
678
11
    let mut clut_offset = read_u32(src, (offset + 24) as usize);
679
11
    let mut m_curve_offset = read_u32(src, (offset + 20) as usize);
680
11
    let mut matrix_offset = read_u32(src, (offset + 16) as usize);
681
11
    let mut b_curve_offset = read_u32(src, (offset + 12) as usize);
682
    // Convert offsets relative to the tag to relative to the profile
683
    // preserve zero for optional fields
684
11
    if a_curve_offset != 0 {
685
11
        a_curve_offset += offset
686
0
    }
687
11
    if clut_offset != 0 {
688
7
        clut_offset += offset
689
4
    }
690
11
    if m_curve_offset != 0 {
691
11
        m_curve_offset += offset
692
0
    }
693
11
    if matrix_offset != 0 {
694
11
        matrix_offset += offset
695
0
    }
696
11
    if b_curve_offset != 0 {
697
11
        b_curve_offset += offset
698
0
    }
699
11
    if clut_offset != 0 {
700
7
        debug_assert!(num_in_channels == 3);
701
        // clut_size can not overflow since lg(256^num_in_channels) = 24 bits.
702
21
        for i in 0..u32::from(num_in_channels) {
703
21
            clut_size *= read_u8(src, (clut_offset + i) as usize) as u32;
704
21
            if clut_size == 0 {
705
6
                invalid_source(src, "bad clut_size");
706
15
            }
707
        }
708
    } else {
709
4
        clut_size = 0
710
    }
711
    // 24bits * 3 won't overflow either
712
11
    clut_size *= num_out_channels as u32;
713
11
    if clut_size > MAX_LUT_SIZE {
714
0
        return None;
715
11
    }
716
717
11
    let mut lut = Box::new(lutmABType::default());
718
719
11
    if clut_offset != 0 {
720
21
        for i in 0..usize::from(num_in_channels) {
721
21
            lut.num_grid_points[i] = read_u8(src, clut_offset as usize + i);
722
21
            if lut.num_grid_points[i] == 0 {
723
3
                invalid_source(src, "bad grid_points");
724
18
            }
725
        }
726
4
    }
727
    // Reverse the processing of transformation elements for mBA type.
728
11
    lut.reversed = type_0 == LUT_MBA_TYPE;
729
11
    lut.num_in_channels = num_in_channels;
730
11
    lut.num_out_channels = num_out_channels;
731
    #[allow(clippy::identity_op, clippy::erasing_op)]
732
11
    if matrix_offset != 0 {
733
        // read the matrix if we have it
734
11
        lut.e00 = read_s15Fixed16Number(src, (matrix_offset + (4 * 0) as u32) as usize); // the caller checks that this doesn't happen
735
11
        lut.e01 = read_s15Fixed16Number(src, (matrix_offset + (4 * 1) as u32) as usize);
736
11
        lut.e02 = read_s15Fixed16Number(src, (matrix_offset + (4 * 2) as u32) as usize);
737
11
        lut.e10 = read_s15Fixed16Number(src, (matrix_offset + (4 * 3) as u32) as usize);
738
11
        lut.e11 = read_s15Fixed16Number(src, (matrix_offset + (4 * 4) as u32) as usize);
739
11
        lut.e12 = read_s15Fixed16Number(src, (matrix_offset + (4 * 5) as u32) as usize);
740
11
        lut.e20 = read_s15Fixed16Number(src, (matrix_offset + (4 * 6) as u32) as usize);
741
11
        lut.e21 = read_s15Fixed16Number(src, (matrix_offset + (4 * 7) as u32) as usize);
742
11
        lut.e22 = read_s15Fixed16Number(src, (matrix_offset + (4 * 8) as u32) as usize);
743
11
        lut.e03 = read_s15Fixed16Number(src, (matrix_offset + (4 * 9) as u32) as usize);
744
11
        lut.e13 = read_s15Fixed16Number(src, (matrix_offset + (4 * 10) as u32) as usize);
745
11
        lut.e23 = read_s15Fixed16Number(src, (matrix_offset + (4 * 11) as u32) as usize)
746
0
    }
747
11
    if a_curve_offset != 0 {
748
11
        read_nested_curveType(src, &mut lut.a_curves, num_in_channels, a_curve_offset);
749
11
    }
750
11
    if m_curve_offset != 0 {
751
11
        read_nested_curveType(src, &mut lut.m_curves, num_out_channels, m_curve_offset);
752
11
    }
753
11
    if b_curve_offset != 0 {
754
11
        read_nested_curveType(src, &mut lut.b_curves, num_out_channels, b_curve_offset);
755
11
    } else {
756
0
        invalid_source(src, "B curves required");
757
0
    }
758
11
    if clut_offset != 0 {
759
7
        let clut_precision = read_u8(src, (clut_offset + 16) as usize);
760
7
        let mut clut_table = Vec::with_capacity(clut_size as usize);
761
7
        if clut_precision == 1 {
762
297
            for i in 0..clut_size {
763
297
                clut_table.push(uInt8Number_to_float(read_uInt8Number(
764
297
                    src,
765
297
                    (clut_offset + 20 + i) as usize,
766
297
                )));
767
297
            }
768
2
            lut.clut_table = Some(clut_table);
769
5
        } else if clut_precision == 2 {
770
212k
            for i in 0..clut_size {
771
212k
                clut_table.push(uInt16Number_to_float(read_uInt16Number(
772
212k
                    src,
773
212k
                    (clut_offset + 20 + i * 2) as usize,
774
212k
                )));
775
212k
            }
776
3
            lut.clut_table = Some(clut_table);
777
2
        } else {
778
2
            invalid_source(src, "Invalid clut precision");
779
2
        }
780
4
    }
781
11
    if !src.valid {
782
8
        return None;
783
3
    }
784
3
    Some(lut)
785
11
}
786
15
fn read_tag_lutType(src: &mut MemSource, tag: &Tag) -> Option<Box<lutType>> {
787
15
    let offset: u32 = tag.offset;
788
15
    let type_0: u32 = read_u32(src, offset as usize);
789
    let num_input_table_entries: u16;
790
    let num_output_table_entries: u16;
791
    let input_offset: u32;
792
    let entry_size: usize;
793
15
    if type_0 == LUT8_TYPE {
794
7
        num_input_table_entries = 256u16;
795
7
        num_output_table_entries = 256u16;
796
7
        entry_size = 1;
797
7
        input_offset = 48
798
8
    } else if type_0 == LUT16_TYPE {
799
8
        num_input_table_entries = read_u16(src, (offset + 48) as usize);
800
8
        num_output_table_entries = read_u16(src, (offset + 50) as usize);
801
802
        // these limits come from the spec
803
8
        if !(2..=4096).contains(&num_input_table_entries)
804
8
            || !(2..=4096).contains(&num_output_table_entries)
805
        {
806
0
            invalid_source(src, "Bad channel count");
807
0
            return None;
808
8
        }
809
8
        entry_size = 2;
810
8
        input_offset = 52
811
    } else {
812
0
        debug_assert!(false);
813
0
        invalid_source(src, "Unexpected lut type");
814
0
        return None;
815
    }
816
15
    let in_chan = read_u8(src, (offset + 8) as usize);
817
15
    let out_chan = read_u8(src, (offset + 9) as usize);
818
15
    if !(in_chan == 3 || in_chan == 4) || out_chan != 3 {
819
1
        invalid_source(src, "CLUT only supports RGB and CMYK");
820
1
        return None;
821
14
    }
822
823
14
    let grid_points = read_u8(src, (offset + 10) as usize);
824
14
    let clut_size = match (grid_points as u32).checked_pow(in_chan as u32) {
825
14
        Some(clut_size) => clut_size,
826
        _ => {
827
0
            invalid_source(src, "CLUT size overflow");
828
0
            return None;
829
        }
830
    };
831
14
    match clut_size {
832
14
        1..=MAX_LUT_SIZE => {} // OK
833
        0 => {
834
0
            invalid_source(src, "CLUT must not be empty.");
835
0
            return None;
836
        }
837
        _ => {
838
0
            invalid_source(src, "CLUT too large");
839
0
            return None;
840
        }
841
    }
842
843
14
    let e00 = read_s15Fixed16Number(src, (offset + 12) as usize);
844
14
    let e01 = read_s15Fixed16Number(src, (offset + 16) as usize);
845
14
    let e02 = read_s15Fixed16Number(src, (offset + 20) as usize);
846
14
    let e10 = read_s15Fixed16Number(src, (offset + 24) as usize);
847
14
    let e11 = read_s15Fixed16Number(src, (offset + 28) as usize);
848
14
    let e12 = read_s15Fixed16Number(src, (offset + 32) as usize);
849
14
    let e20 = read_s15Fixed16Number(src, (offset + 36) as usize);
850
14
    let e21 = read_s15Fixed16Number(src, (offset + 40) as usize);
851
14
    let e22 = read_s15Fixed16Number(src, (offset + 44) as usize);
852
853
14
    let mut input_table = Vec::with_capacity((num_input_table_entries * in_chan as u16) as usize);
854
31.4k
    for i in 0..(num_input_table_entries * in_chan as u16) {
855
31.4k
        if type_0 == LUT8_TYPE {
856
4.86k
            input_table.push(uInt8Number_to_float(read_uInt8Number(
857
4.86k
                src,
858
4.86k
                (offset + input_offset) as usize + i as usize * entry_size,
859
            )))
860
        } else {
861
26.5k
            input_table.push(uInt16Number_to_float(read_uInt16Number(
862
26.5k
                src,
863
26.5k
                (offset + input_offset) as usize + i as usize * entry_size,
864
            )))
865
        }
866
    }
867
14
    let clut_offset = ((offset + input_offset) as usize
868
14
        + (num_input_table_entries as i32 * in_chan as i32) as usize * entry_size)
869
14
        as u32;
870
871
14
    let mut clut_table = Vec::with_capacity((clut_size * out_chan as u32) as usize);
872
1.68M
    for i in 0..clut_size * out_chan as u32 {
873
1.68M
        if type_0 == LUT8_TYPE {
874
1.68M
            clut_table.push(uInt8Number_to_float(read_uInt8Number(
875
1.68M
                src,
876
1.68M
                clut_offset as usize + i as usize * entry_size,
877
1.68M
            )));
878
1.68M
        } else if type_0 == LUT16_TYPE {
879
1.93k
            clut_table.push(uInt16Number_to_float(read_uInt16Number(
880
1.93k
                src,
881
1.93k
                clut_offset as usize + i as usize * entry_size,
882
1.93k
            )));
883
1.93k
        }
884
    }
885
886
14
    let output_offset =
887
14
        (clut_offset as usize + (clut_size * out_chan as u32) as usize * entry_size) as u32;
888
889
14
    let mut output_table =
890
14
        Vec::with_capacity((num_output_table_entries * out_chan as u16) as usize);
891
8.23k
    for i in 0..num_output_table_entries as i32 * out_chan as i32 {
892
8.23k
        if type_0 == LUT8_TYPE {
893
4.60k
            output_table.push(uInt8Number_to_float(read_uInt8Number(
894
4.60k
                src,
895
4.60k
                output_offset as usize + i as usize * entry_size,
896
            )))
897
        } else {
898
3.63k
            output_table.push(uInt16Number_to_float(read_uInt16Number(
899
3.63k
                src,
900
3.63k
                output_offset as usize + i as usize * entry_size,
901
            )))
902
        }
903
    }
904
14
    Some(Box::new(lutType {
905
14
        num_input_table_entries,
906
14
        num_output_table_entries,
907
14
        num_input_channels: in_chan,
908
14
        num_output_channels: out_chan,
909
14
        num_clut_grid_points: grid_points,
910
14
        e00,
911
14
        e01,
912
14
        e02,
913
14
        e10,
914
14
        e11,
915
14
        e12,
916
14
        e20,
917
14
        e21,
918
14
        e22,
919
14
        input_table,
920
14
        clut_table,
921
14
        output_table,
922
14
    }))
923
15
}
924
40
fn read_rendering_intent(profile: &mut Profile, src: &mut MemSource) {
925
40
    let intent = read_u32(src, 64);
926
40
    profile.rendering_intent = match intent {
927
40
        x if x == Perceptual as u32 => Perceptual,
928
10
        x if x == RelativeColorimetric as u32 => RelativeColorimetric,
929
5
        x if x == Saturation as u32 => Saturation,
930
5
        x if x == AbsoluteColorimetric as u32 => AbsoluteColorimetric,
931
        _ => {
932
5
            invalid_source(src, "unknown rendering intent");
933
5
            Intent::default()
934
        }
935
    };
936
40
}
937
56
fn profile_create() -> Box<Profile> {
938
56
    Box::new(Profile::default())
939
56
}
940
/* build sRGB gamma table */
941
/* based on cmsBuildParametricGamma() */
942
#[allow(clippy::many_single_char_names)]
943
8
fn build_sRGB_gamma_table(num_entries: i32) -> Vec<u16> {
944
    /* taken from lcms: Build_sRGBGamma() */
945
8
    let gamma: f64 = 2.4;
946
8
    let a: f64 = 1.0 / 1.055;
947
8
    let b: f64 = 0.055 / 1.055;
948
8
    let c: f64 = 1.0 / 12.92;
949
8
    let d: f64 = 0.04045;
950
951
8
    build_trc_table(
952
8
        num_entries,
953
        // IEC 61966-2.1 (sRGB)
954
        // Y = (aX + b)^Gamma | X >= d
955
        // Y = cX             | X < d
956
8.19k
        |x| {
957
8.19k
            if x >= d {
958
7.85k
                let e: f64 = a * x + b;
959
7.85k
                if e > 0. {
960
7.85k
                    e.powf(gamma)
961
                } else {
962
0
                    0.
963
                }
964
            } else {
965
336
                c * x
966
            }
967
8.19k
        },
968
    )
969
8
}
970
971
/// eotf: electro-optical transfer characteristic function, maps from [0, 1]
972
/// in non-linear (voltage) space to [0, 1] in linear (optical) space. Should
973
/// generally be a concave up function.
974
8
fn build_trc_table(num_entries: i32, eotf: impl Fn(f64) -> f64) -> Vec<u16> {
975
8
    let mut table = Vec::with_capacity(num_entries as usize);
976
977
8.19k
    for i in 0..num_entries {
978
8.19k
        let x: f64 = i as f64 / (num_entries - 1) as f64;
979
8.19k
        let y: f64 = eotf(x);
980
        let mut output: f64;
981
        // Saturate -- this could likely move to a separate function
982
8.19k
        output = y * 65535.0 + 0.5;
983
8.19k
        if output > 65535.0 {
984
8
            output = 65535.0
985
8.18k
        }
986
8.19k
        if output < 0.0 {
987
0
            output = 0.0
988
8.19k
        }
989
8.19k
        table.push(output.floor() as u16);
990
    }
991
8
    table
992
8
}
qcms::iccread::build_trc_table::<qcms::iccread::build_sRGB_gamma_table::{closure#0}>
Line
Count
Source
974
8
fn build_trc_table(num_entries: i32, eotf: impl Fn(f64) -> f64) -> Vec<u16> {
975
8
    let mut table = Vec::with_capacity(num_entries as usize);
976
977
8.19k
    for i in 0..num_entries {
978
8.19k
        let x: f64 = i as f64 / (num_entries - 1) as f64;
979
8.19k
        let y: f64 = eotf(x);
980
        let mut output: f64;
981
        // Saturate -- this could likely move to a separate function
982
8.19k
        output = y * 65535.0 + 0.5;
983
8.19k
        if output > 65535.0 {
984
8
            output = 65535.0
985
8.18k
        }
986
8.19k
        if output < 0.0 {
987
0
            output = 0.0
988
8.19k
        }
989
8.19k
        table.push(output.floor() as u16);
990
    }
991
8
    table
992
8
}
Unexecuted instantiation: qcms::iccread::build_trc_table::<<qcms::iccread::curveType as core::convert::TryFrom<qcms::iccread::TransferCharacteristics>>::try_from::{closure#0}>
Unexecuted instantiation: qcms::iccread::build_trc_table::<<qcms::iccread::curveType as core::convert::TryFrom<qcms::iccread::TransferCharacteristics>>::try_from::{closure#2}>
Unexecuted instantiation: qcms::iccread::build_trc_table::<<qcms::iccread::curveType as core::convert::TryFrom<qcms::iccread::TransferCharacteristics>>::try_from::{closure#3}>
Unexecuted instantiation: qcms::iccread::build_trc_table::<<qcms::iccread::curveType as core::convert::TryFrom<qcms::iccread::TransferCharacteristics>>::try_from::{closure#1}>
993
24
fn curve_from_table(table: &[u16]) -> Box<curveType> {
994
24
    Box::new(curveType::Curve(table.to_vec()))
995
24
}
996
0
pub fn float_to_u8Fixed8Number(a: f32) -> u16 {
997
0
    if a > 255.0 + 255.0 / 256f32 {
998
0
        0xffffu16
999
0
    } else if a < 0.0 {
1000
0
        0u16
1001
    } else {
1002
0
        (a * 256.0 + 0.5).floor() as u16
1003
    }
1004
0
}
1005
1006
0
fn curve_from_gamma(gamma: f32) -> Box<curveType> {
1007
0
    Box::new(curveType::Curve(vec![float_to_u8Fixed8Number(gamma)]))
1008
0
}
1009
1010
24
fn identity_curve() -> Box<curveType> {
1011
24
    Box::new(curveType::Curve(Vec::new()))
1012
24
}
1013
1014
/* from lcms: cmsWhitePointFromTemp */
1015
/* tempK must be >= 4000. and <= 25000.
1016
 * Invalid values of tempK will return
1017
 * (x,y,Y) = (-1.0, -1.0, -1.0)
1018
 * similar to argyll: icx_DTEMP2XYZ() */
1019
8
fn white_point_from_temp(temp_K: i32) -> qcms_CIE_xyY {
1020
8
    let mut white_point: qcms_CIE_xyY = qcms_CIE_xyY {
1021
8
        x: 0.,
1022
8
        y: 0.,
1023
8
        Y: 0.,
1024
8
    };
1025
    // No optimization provided.
1026
8
    let T = temp_K as f64; // Square
1027
8
    let T2 = T * T; // Cube
1028
8
    let T3 = T2 * T;
1029
    // For correlated color temperature (T) between 4000K and 7000K:
1030
8
    let x = if (4000.0..=7000.0).contains(&T) {
1031
8
        -4.6070 * (1E9 / T3) + 2.9678 * (1E6 / T2) + 0.09911 * (1E3 / T) + 0.244063
1032
0
    } else if T > 7000.0 && T <= 25000.0 {
1033
0
        -2.0064 * (1E9 / T3) + 1.9018 * (1E6 / T2) + 0.24748 * (1E3 / T) + 0.237040
1034
    } else {
1035
        // or for correlated color temperature (T) between 7000K and 25000K:
1036
        // Invalid tempK
1037
0
        white_point.x = -1.0;
1038
0
        white_point.y = -1.0;
1039
0
        white_point.Y = -1.0;
1040
0
        debug_assert!(false, "invalid temp");
1041
0
        return white_point;
1042
    };
1043
    // Obtain y(x)
1044
8
    let y = -3.000 * (x * x) + 2.870 * x - 0.275;
1045
    // wave factors (not used, but here for futures extensions)
1046
    // let M1 = (-1.3515 - 1.7703*x + 5.9114 *y)/(0.0241 + 0.2562*x - 0.7341*y);
1047
    // let M2 = (0.0300 - 31.4424*x + 30.0717*y)/(0.0241 + 0.2562*x - 0.7341*y);
1048
    // Fill white_point struct
1049
8
    white_point.x = x;
1050
8
    white_point.y = y;
1051
8
    white_point.Y = 1.0;
1052
8
    white_point
1053
8
}
1054
#[no_mangle]
1055
8
pub extern "C" fn qcms_white_point_sRGB() -> qcms_CIE_xyY {
1056
8
    white_point_from_temp(6504)
1057
8
}
1058
1059
/// See [Rec. ITU-T H.273 (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en) Table 2
1060
/// Values 0, 3, 13–21, 23–255 are all reserved so all map to the same variant
1061
#[derive(Clone, Copy, Debug, PartialEq)]
1062
pub enum ColourPrimaries {
1063
    /// For future use by ITU-T | ISO/IEC
1064
    Reserved,
1065
    /// Rec. ITU-R BT.709-6<br />
1066
    /// Rec. ITU-R BT.1361-0 conventional colour gamut system and extended colour gamut system (historical)<br />
1067
    /// IEC 61966-2-1 sRGB or sYCC IEC 61966-2-4<br />
1068
    /// Society of Motion Picture and Television Engineers (MPTE) RP 177 (1993) Annex B<br />
1069
    Bt709 = 1,
1070
    /// Unspecified<br />
1071
    /// Image characteristics are unknown or are determined by the application.
1072
    Unspecified = 2,
1073
    /// Rec. ITU-R BT.470-6 System M (historical)<br />
1074
    /// United States National Television System Committee 1953 Recommendation for transmission standards for color television<br />
1075
    /// United States Federal Communications Commission (2003) Title 47 Code of Federal Regulations 73.682 (a) (20)<br />
1076
    Bt470M = 4,
1077
    /// Rec. ITU-R BT.470-6 System B, G (historical) Rec. ITU-R BT.601-7 625<br />
1078
    /// Rec. ITU-R BT.1358-0 625 (historical)<br />
1079
    /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM<br />
1080
    Bt470Bg = 5,
1081
    /// Rec. ITU-R BT.601-7 525<br />
1082
    /// Rec. ITU-R BT.1358-1 525 or 625 (historical) Rec. ITU-R BT.1700-0 NTSC<br />
1083
    /// SMPTE 170M (2004)<br />
1084
    /// (functionally the same as the value 7)<br />
1085
    Bt601 = 6,
1086
    /// SMPTE 240M (1999) (historical) (functionally the same as the value 6)<br />
1087
    Smpte240 = 7,
1088
    /// Generic film (colour filters using Illuminant C)<br />
1089
    Generic_film = 8,
1090
    /// Rec. ITU-R BT.2020-2<br />
1091
    /// Rec. ITU-R BT.2100-0<br />
1092
    Bt2020 = 9,
1093
    /// SMPTE ST 428-1<br />
1094
    /// (CIE 1931 XYZ as in ISO 11664-1)<br />
1095
    Xyz = 10,
1096
    /// SMPTE RP 431-2 (2011)<br />
1097
    Smpte431 = 11,
1098
    /// SMPTE EG 432-1 (2010)<br />
1099
    Smpte432 = 12,
1100
    /// EBU Tech. 3213-E (1975)<br />
1101
    Ebu3213 = 22,
1102
}
1103
1104
impl From<u8> for ColourPrimaries {
1105
0
    fn from(value: u8) -> Self {
1106
0
        match value {
1107
0
            0 | 3 | 13..=21 | 23..=255 => Self::Reserved,
1108
0
            1 => Self::Bt709,
1109
0
            2 => Self::Unspecified,
1110
0
            4 => Self::Bt470M,
1111
0
            5 => Self::Bt470Bg,
1112
0
            6 => Self::Bt601,
1113
0
            7 => Self::Smpte240,
1114
0
            8 => Self::Generic_film,
1115
0
            9 => Self::Bt2020,
1116
0
            10 => Self::Xyz,
1117
0
            11 => Self::Smpte431,
1118
0
            12 => Self::Smpte432,
1119
0
            22 => Self::Ebu3213,
1120
        }
1121
0
    }
1122
}
1123
1124
#[test]
1125
fn colour_primaries() {
1126
    for value in 0..=u8::MAX {
1127
        match ColourPrimaries::from(value) {
1128
            ColourPrimaries::Reserved => {}
1129
            variant => assert_eq!(value, variant as u8),
1130
        }
1131
    }
1132
}
1133
1134
impl From<ColourPrimaries> for qcms_CIE_xyYTRIPLE {
1135
8
    fn from(value: ColourPrimaries) -> Self {
1136
        let red;
1137
        let green;
1138
        let blue;
1139
1140
8
        match value {
1141
0
            ColourPrimaries::Reserved => panic!("CP={} is reserved", value as u8),
1142
8
            ColourPrimaries::Bt709 => {
1143
8
                green = qcms_chromaticity { x: 0.300, y: 0.600 };
1144
8
                blue = qcms_chromaticity { x: 0.150, y: 0.060 };
1145
8
                red = qcms_chromaticity { x: 0.640, y: 0.330 };
1146
8
            }
1147
0
            ColourPrimaries::Unspecified => panic!("CP={} is unspecified", value as u8),
1148
0
            ColourPrimaries::Bt470M => {
1149
0
                green = qcms_chromaticity { x: 0.21, y: 0.71 };
1150
0
                blue = qcms_chromaticity { x: 0.14, y: 0.08 };
1151
0
                red = qcms_chromaticity { x: 0.67, y: 0.33 };
1152
0
            }
1153
0
            ColourPrimaries::Bt470Bg => {
1154
0
                green = qcms_chromaticity { x: 0.29, y: 0.60 };
1155
0
                blue = qcms_chromaticity { x: 0.15, y: 0.06 };
1156
0
                red = qcms_chromaticity { x: 0.64, y: 0.33 };
1157
0
            }
1158
0
            ColourPrimaries::Bt601 | ColourPrimaries::Smpte240 => {
1159
0
                green = qcms_chromaticity { x: 0.310, y: 0.595 };
1160
0
                blue = qcms_chromaticity { x: 0.155, y: 0.070 };
1161
0
                red = qcms_chromaticity { x: 0.630, y: 0.340 };
1162
0
            }
1163
0
            ColourPrimaries::Generic_film => {
1164
0
                green = qcms_chromaticity { x: 0.243, y: 0.692 };
1165
0
                blue = qcms_chromaticity { x: 0.145, y: 0.049 };
1166
0
                red = qcms_chromaticity { x: 0.681, y: 0.319 };
1167
0
            }
1168
0
            ColourPrimaries::Bt2020 => {
1169
0
                green = qcms_chromaticity { x: 0.170, y: 0.797 };
1170
0
                blue = qcms_chromaticity { x: 0.131, y: 0.046 };
1171
0
                red = qcms_chromaticity { x: 0.708, y: 0.292 };
1172
0
            }
1173
0
            ColourPrimaries::Xyz => {
1174
0
                green = qcms_chromaticity { x: 0.0, y: 1.0 };
1175
0
                blue = qcms_chromaticity { x: 0.0, y: 0.0 };
1176
0
                red = qcms_chromaticity { x: 1.0, y: 0.0 };
1177
0
            }
1178
            // These two share primaries, but have distinct white points
1179
0
            ColourPrimaries::Smpte431 | ColourPrimaries::Smpte432 => {
1180
0
                green = qcms_chromaticity { x: 0.265, y: 0.690 };
1181
0
                blue = qcms_chromaticity { x: 0.150, y: 0.060 };
1182
0
                red = qcms_chromaticity { x: 0.680, y: 0.320 };
1183
0
            }
1184
0
            ColourPrimaries::Ebu3213 => {
1185
0
                green = qcms_chromaticity { x: 0.295, y: 0.605 };
1186
0
                blue = qcms_chromaticity { x: 0.155, y: 0.077 };
1187
0
                red = qcms_chromaticity { x: 0.630, y: 0.340 };
1188
0
            }
1189
        }
1190
1191
8
        Self {
1192
8
            red: red.into(),
1193
8
            green: green.into(),
1194
8
            blue: blue.into(),
1195
8
        }
1196
8
    }
1197
}
1198
1199
impl ColourPrimaries {
1200
0
    fn white_point(self) -> qcms_CIE_xyY {
1201
0
        match self {
1202
0
            Self::Reserved => panic!("CP={} is reserved", self as u8),
1203
            Self::Bt709
1204
            | Self::Bt470Bg
1205
            | Self::Bt601
1206
            | Self::Smpte240
1207
            | Self::Bt2020
1208
            | Self::Smpte432
1209
0
            | Self::Ebu3213 => qcms_chromaticity::D65,
1210
0
            Self::Unspecified => panic!("CP={} is unspecified", self as u8),
1211
0
            Self::Bt470M => qcms_chromaticity { x: 0.310, y: 0.316 },
1212
0
            Self::Generic_film => qcms_chromaticity { x: 0.310, y: 0.316 },
1213
0
            Self::Xyz => qcms_chromaticity {
1214
0
                x: 1. / 3.,
1215
0
                y: 1. / 3.,
1216
0
            },
1217
0
            Self::Smpte431 => qcms_chromaticity { x: 0.314, y: 0.351 },
1218
        }
1219
0
        .into()
1220
0
    }
1221
1222
0
    fn is_usable(self) -> bool {
1223
0
        match self {
1224
0
            Self::Reserved | Self::Unspecified => false,
1225
0
            _ => true
1226
        }
1227
0
    }
1228
}
1229
1230
/// See [Rec. ITU-T H.273 (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en) Table 3
1231
/// Values 0, 3, 19–255 are all reserved so all map to the same variant
1232
#[derive(Clone, Copy, Debug, PartialEq)]
1233
pub enum TransferCharacteristics {
1234
    /// For future use by ITU-T | ISO/IEC
1235
    Reserved,
1236
    /// Rec. ITU-R BT.709-6<br />
1237
    /// Rec. ITU-R BT.1361-0 conventional colour gamut system (historical)<br />
1238
    /// (functionally the same as the values 6, 14 and 15)    <br />
1239
    Bt709 = 1,
1240
    /// Image characteristics are unknown or are determined by the application.<br />
1241
    Unspecified = 2,
1242
    /// Rec. ITU-R BT.470-6 System M (historical)<br />
1243
    /// United States National Television System Committee 1953 Recommendation for transmission standards for color television<br />
1244
    /// United States Federal Communications Commission (2003) Title 47 Code of Federal Regulations 73.682 (a) (20)<br />
1245
    /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM<br />
1246
    Bt470M = 4,
1247
    /// Rec. ITU-R BT.470-6 System B, G (historical)<br />
1248
    Bt470Bg = 5,
1249
    /// Rec. ITU-R BT.601-7 525 or 625<br />
1250
    /// Rec. ITU-R BT.1358-1 525 or 625 (historical)<br />
1251
    /// Rec. ITU-R BT.1700-0 NTSC SMPTE 170M (2004)<br />
1252
    /// (functionally the same as the values 1, 14 and 15)<br />
1253
    Bt601 = 6,
1254
    /// SMPTE 240M (1999) (historical)<br />
1255
    Smpte240 = 7,
1256
    /// Linear transfer characteristics<br />
1257
    Linear = 8,
1258
    /// Logarithmic transfer characteristic (100:1 range)<br />
1259
    Log_100 = 9,
1260
    /// Logarithmic transfer characteristic (100 * Sqrt( 10 ) : 1 range)<br />
1261
    Log_100_sqrt10 = 10,
1262
    /// IEC 61966-2-4<br />
1263
    Iec61966 = 11,
1264
    /// Rec. ITU-R BT.1361-0 extended colour gamut system (historical)<br />
1265
    Bt_1361 = 12,
1266
    /// IEC 61966-2-1 sRGB or sYCC<br />
1267
    Srgb = 13,
1268
    /// Rec. ITU-R BT.2020-2 (10-bit system)<br />
1269
    /// (functionally the same as the values 1, 6 and 15)<br />
1270
    Bt2020_10bit = 14,
1271
    /// Rec. ITU-R BT.2020-2 (12-bit system)<br />
1272
    /// (functionally the same as the values 1, 6 and 14)<br />
1273
    Bt2020_12bit = 15,
1274
    /// SMPTE ST 2084 for 10-, 12-, 14- and 16-bitsystems<br />
1275
    /// Rec. ITU-R BT.2100-0 perceptual quantization (PQ) system<br />
1276
    Smpte2084 = 16,
1277
    /// SMPTE ST 428-1<br />
1278
    Smpte428 = 17,
1279
    /// ARIB STD-B67<br />
1280
    /// Rec. ITU-R BT.2100-0 hybrid log- gamma (HLG) system<br />
1281
    Hlg = 18,
1282
}
1283
1284
#[test]
1285
fn transfer_characteristics() {
1286
    for value in 0..=u8::MAX {
1287
        match TransferCharacteristics::from(value) {
1288
            TransferCharacteristics::Reserved => {}
1289
            variant => assert_eq!(value, variant as u8),
1290
        }
1291
    }
1292
}
1293
1294
impl From<u8> for TransferCharacteristics {
1295
0
    fn from(value: u8) -> Self {
1296
0
        match value {
1297
0
            0 | 3 | 19..=255 => Self::Reserved,
1298
0
            1 => Self::Bt709,
1299
0
            2 => Self::Unspecified,
1300
0
            4 => Self::Bt470M,
1301
0
            5 => Self::Bt470Bg,
1302
0
            6 => Self::Bt601,
1303
0
            7 => Self::Smpte240, // unimplemented
1304
0
            8 => Self::Linear,
1305
0
            9 => Self::Log_100,
1306
0
            10 => Self::Log_100_sqrt10,
1307
0
            11 => Self::Iec61966, // unimplemented
1308
0
            12 => Self::Bt_1361,  // unimplemented
1309
0
            13 => Self::Srgb,
1310
0
            14 => Self::Bt2020_10bit,
1311
0
            15 => Self::Bt2020_12bit,
1312
0
            16 => Self::Smpte2084,
1313
0
            17 => Self::Smpte428, // unimplemented
1314
0
            18 => Self::Hlg,
1315
        }
1316
0
    }
1317
}
1318
1319
impl TryFrom<TransferCharacteristics> for curveType {
1320
    type Error = ();
1321
    /// See [ICC.1:2010](https://www.color.org/specification/ICC1v43_2010-12.pdf)
1322
    /// See [Rec. ITU-R BT.2100-2](https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-E.pdf)
1323
0
    fn try_from(value: TransferCharacteristics) -> Result<Self, Self::Error> {
1324
        const NUM_TRC_TABLE_ENTRIES: i32 = 1024;
1325
1326
0
        Ok(match value {
1327
0
            TransferCharacteristics::Reserved => panic!("TC={} is reserved", value as u8),
1328
            TransferCharacteristics::Bt709
1329
            | TransferCharacteristics::Bt601
1330
            | TransferCharacteristics::Bt2020_10bit
1331
            | TransferCharacteristics::Bt2020_12bit => {
1332
                // The opto-electronic transfer characteristic function (OETF)
1333
                // as defined in ITU-T H.273 table 3, row 1:
1334
                //
1335
                // V = (α * Lc^0.45) − (α − 1)  for 1 >= Lc >= β
1336
                // V = 4.500 * Lc               for β >  Lc >= 0
1337
                //
1338
                // Inverting gives the electro-optical transfer characteristic
1339
                // function (EOTF) which can be represented as ICC
1340
                // parametricCurveType with 4 parameters (ICC.1:2010 Table 65).
1341
                // Converting between the two (Lc ↔︎ Y, V ↔︎ X):
1342
                //
1343
                // Y = (a * X + b)^g  for (X >= d)
1344
                // Y = c * X          for (X < d)
1345
                //
1346
                // g, a, b, c, d can then be defined in terms of α and β:
1347
                //
1348
                // g = 1 / 0.45
1349
                // a = 1 / α
1350
                // b = 1 - a
1351
                // c = 1 / 4.500
1352
                // d = 4.500 * β
1353
                //
1354
                // α and β are determined by solving the piecewise equations to
1355
                // ensure continuity of both value and slope at the value β.
1356
                // We use the values specified for 10-bit systems in
1357
                // https://www.itu.int/rec/R-REC-BT.2020-2-201510-I Table 4
1358
                // since this results in the similar values as available ICC
1359
                // profiles after converting to s15Fixed16Number, providing us
1360
                // good test coverage.
1361
1362
                type Float = f32;
1363
1364
                const alpha: Float = 1.099;
1365
                const beta: Float = 0.018;
1366
1367
                const linear_coef: Float = 4.500;
1368
                const pow_exp: Float = 0.45;
1369
1370
                const g: Float = 1. / pow_exp;
1371
                const a: Float = 1. / alpha;
1372
                const b: Float = 1. - a;
1373
                const c: Float = 1. / linear_coef;
1374
                const d: Float = linear_coef * beta;
1375
1376
0
                curveType::Parametric(vec![g, a, b, c, d])
1377
            }
1378
0
            TransferCharacteristics::Unspecified => panic!("TC={} is unspecified", value as u8),
1379
0
            TransferCharacteristics::Bt470M => *curve_from_gamma(2.2),
1380
0
            TransferCharacteristics::Bt470Bg => *curve_from_gamma(2.8),
1381
0
            TransferCharacteristics::Smpte240 => return Err(()),
1382
0
            TransferCharacteristics::Linear => *curve_from_gamma(1.),
1383
            TransferCharacteristics::Log_100 => {
1384
                // See log_100_transfer_characteristics() for derivation
1385
                // The opto-electronic transfer characteristic function (OETF)
1386
                // as defined in ITU-T H.273 table 3, row 9:
1387
                //
1388
                // V = 1.0 + Log10(Lc) ÷ 2  for 1    >= Lc >= 0.01
1389
                // V = 0.0                  for 0.01 >  Lc >= 0
1390
                //
1391
                // Inverting this to give the EOTF required for the profile gives
1392
                //
1393
                // Lc = 10^(2*V - 2)  for 1 >= V >= 0
1394
0
                let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |v| 10f64.powf(2. * v - 2.));
1395
0
                curveType::Curve(table)
1396
            }
1397
            TransferCharacteristics::Log_100_sqrt10 => {
1398
                // The opto-electronic transfer characteristic function (OETF)
1399
                // as defined in ITU-T H.273 table 3, row 10:
1400
                //
1401
                // V = 1.0 + Log10(Lc) ÷ 2.5  for               1 >= Lc >= Sqrt(10) ÷ 1000
1402
                // V = 0.0                    for Sqrt(10) ÷ 1000 >  Lc >= 0
1403
                //
1404
                // Inverting this to give the EOTF required for the profile gives
1405
                //
1406
                // Lc = 10^(2.5*V - 2.5)  for 1 >= V >= 0
1407
0
                let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |v| 10f64.powf(2.5 * v - 2.5));
1408
0
                curveType::Curve(table)
1409
            }
1410
0
            TransferCharacteristics::Iec61966 => return Err(()),
1411
0
            TransferCharacteristics::Bt_1361 => return Err(()),
1412
            TransferCharacteristics::Srgb => {
1413
                // Should we prefer this or curveType::Parametric?
1414
0
                curveType::Curve(build_sRGB_gamma_table(NUM_TRC_TABLE_ENTRIES))
1415
            }
1416
1417
            TransferCharacteristics::Smpte2084 => {
1418
                // Despite using Lo rather than Lc, H.273 gives the OETF:
1419
                //
1420
                // V = ( ( c1 + c2 * (Lo)^n ) ÷ ( 1 + c3 * (Lo)^n ) )^m
1421
                const c1: f64 = 0.8359375;
1422
                const c2: f64 = 18.8515625;
1423
                const c3: f64 = 18.6875;
1424
                const m: f64 = 78.84375;
1425
                const n: f64 = 0.1593017578125;
1426
1427
                // Inverting this to give the EOTF required for the profile
1428
                // (and confirmed by Rec. ITU-R BT.2100-2, Table 4) gives
1429
                //
1430
                // Y = ( max[( X^(1/m) - c1 ), 0] ÷ ( c2 - c3 * X^(1/m) ) )^(1/n)
1431
0
                let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |x| {
1432
0
                    ((x.powf(1. / m) - c1).max(0.) / (c2 - c3 * x.powf(1. / m))).powf(1. / n)
1433
0
                });
1434
0
                curveType::Curve(table)
1435
            }
1436
0
            TransferCharacteristics::Smpte428 => return Err(()),
1437
            TransferCharacteristics::Hlg => {
1438
                // The opto-electronic transfer characteristic function (OETF)
1439
                // as defined in ITU-T H.273 table 3, row 18:
1440
                //
1441
                // V = a * Ln(12 * Lc - b) + c  for 1      >= Lc >  1 ÷ 12
1442
                // V = Sqrt(3) * Lc^0.5         for 1 ÷ 12 >= Lc >= 0
1443
                const a: f64 = 0.17883277;
1444
                const b: f64 = 0.28466892;
1445
                const c: f64 = 0.55991073;
1446
1447
                // Inverting this to give the EOTF required for the profile
1448
                // (and confirmed by Rec. ITU-R BT.2100-2, Table 4) gives
1449
                //
1450
                // Y = (X^2) / 3             for 0   <= X <= 0.5
1451
                // Y = ((e^((X-c)/a))+b)/12  for 0.5 <  X <= 1
1452
0
                let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |x| {
1453
0
                    if x <= 0.5 {
1454
0
                        let y1 = x.powf(2.) / 3.;
1455
0
                        assert!((0. ..=1. / 12.).contains(&y1));
1456
0
                        y1
1457
                    } else {
1458
0
                        (std::f64::consts::E.powf((x - c) / a) + b) / 12.
1459
                    }
1460
0
                });
1461
0
                curveType::Curve(table)
1462
            }
1463
        })
1464
0
    }
1465
}
1466
1467
impl TransferCharacteristics {
1468
0
    fn is_usable(self) -> bool {
1469
0
        match self {
1470
0
            Self::Reserved | Self::Unspecified => false,
1471
0
            _ => true
1472
        }
1473
0
    }
1474
}
1475
1476
#[cfg(test)]
1477
fn check_transfer_characteristics(cicp: TransferCharacteristics, icc_path: &str) {
1478
    let mut cicp_out = [0u8; crate::transform::PRECACHE_OUTPUT_SIZE];
1479
    let mut icc_out = [0u8; crate::transform::PRECACHE_OUTPUT_SIZE];
1480
    let cicp_tc = curveType::try_from(cicp).unwrap();
1481
    let icc = Profile::new_from_path(icc_path).unwrap();
1482
    let icc_tc = icc.redTRC.as_ref().unwrap();
1483
1484
    eprintln!("cicp_tc: {:?}", cicp_tc);
1485
    eprintln!("icc_tc: {:?}", icc_tc);
1486
1487
    crate::transform_util::compute_precache(icc_tc, &mut icc_out);
1488
    crate::transform_util::compute_precache(&cicp_tc, &mut cicp_out);
1489
1490
    let mut off_by_one = 0;
1491
    for i in 0..cicp_out.len() {
1492
        match (cicp_out[i] as i16) - (icc_out[i] as i16) {
1493
            0 => {}
1494
            1 | -1 => {
1495
                off_by_one += 1;
1496
            }
1497
            _ => assert_eq!(cicp_out[i], icc_out[i], "difference at index {}", i),
1498
        }
1499
    }
1500
    eprintln!("{} / {} off by one", off_by_one, cicp_out.len());
1501
}
1502
1503
#[test]
1504
fn srgb_transfer_characteristics() {
1505
    check_transfer_characteristics(TransferCharacteristics::Srgb, "sRGB_lcms.icc");
1506
}
1507
1508
#[test]
1509
fn bt709_transfer_characteristics() {
1510
    check_transfer_characteristics(TransferCharacteristics::Bt709, "ITU-709.icc");
1511
}
1512
1513
#[test]
1514
fn bt2020_10bit_transfer_characteristics() {
1515
    check_transfer_characteristics(TransferCharacteristics::Bt2020_10bit, "ITU-2020.icc");
1516
}
1517
1518
#[test]
1519
fn bt2020_12bit_transfer_characteristics() {
1520
    check_transfer_characteristics(TransferCharacteristics::Bt2020_12bit, "ITU-2020.icc");
1521
}
1522
1523
impl Profile {
1524
    //XXX: it would be nice if we had a way of ensuring
1525
    // everything in a profile was initialized regardless of how it was created
1526
    //XXX: should this also be taking a black_point?
1527
    /* similar to CGColorSpaceCreateCalibratedRGB */
1528
8
    pub fn new_rgb_with_table(
1529
8
        white_point: qcms_CIE_xyY,
1530
8
        primaries: qcms_CIE_xyYTRIPLE,
1531
8
        table: &[u16],
1532
8
    ) -> Option<Box<Profile>> {
1533
8
        let mut profile = profile_create();
1534
        //XXX: should store the whitepoint
1535
8
        if !set_rgb_colorants(&mut profile, white_point, primaries) {
1536
0
            return None;
1537
8
        }
1538
8
        profile.redTRC = Some(curve_from_table(table));
1539
8
        profile.blueTRC = Some(curve_from_table(table));
1540
8
        profile.greenTRC = Some(curve_from_table(table));
1541
8
        profile.class_type = DISPLAY_DEVICE_PROFILE;
1542
8
        profile.rendering_intent = Perceptual;
1543
8
        profile.color_space = RGB_SIGNATURE;
1544
8
        profile.pcs = XYZ_TYPE;
1545
8
        Some(profile)
1546
8
    }
1547
8
    pub fn new_sRGB() -> Box<Profile> {
1548
8
        let D65 = qcms_white_point_sRGB();
1549
8
        let table = build_sRGB_gamma_table(1024);
1550
1551
8
        let mut srgb = Profile::new_rgb_with_table(
1552
8
            D65,
1553
8
            qcms_CIE_xyYTRIPLE::from(ColourPrimaries::Bt709),
1554
8
            &table,
1555
        )
1556
8
        .unwrap();
1557
8
        srgb.is_srgb = true;
1558
8
        srgb
1559
8
    }
1560
1561
    /// Returns true if this profile is sRGB
1562
0
    pub fn is_sRGB(&self) -> bool {
1563
0
        self.is_srgb
1564
0
    }
1565
1566
    /// Returns the profile's description tag, if it has a readable one.
1567
0
    pub fn description(&self) -> Option<&str> {
1568
0
        self.description.as_deref()
1569
0
    }
1570
1571
    /// Returns the chromaticities of the RGB colorants and of the white they
1572
    /// sum to. If the profile has a chromatic adaptation tag it is undone, so
1573
    /// the values describe the device rather than the D50 PCS.
1574
0
    pub fn colorant_chromaticities(&self) -> Option<(qcms_CIE_xyYTRIPLE, qcms_CIE_xyY)> {
1575
0
        if self.color_space != RGB_SIGNATURE {
1576
0
            return None;
1577
0
        }
1578
0
        let to_vector = |c: &XYZNumber| Vector {
1579
0
            v: [
1580
0
                s15Fixed16Number_to_float(c.X),
1581
0
                s15Fixed16Number_to_float(c.Y),
1582
0
                s15Fixed16Number_to_float(c.Z),
1583
0
            ],
1584
0
        };
1585
0
        let mut colorants = [
1586
0
            to_vector(&self.redColorant),
1587
0
            to_vector(&self.greenColorant),
1588
0
            to_vector(&self.blueColorant),
1589
0
        ];
1590
0
        if let Some(chad) = &self.chromaticAdaption {
1591
0
            let inverse = chad.invert()?;
1592
0
            for c in &mut colorants {
1593
0
                *c = inverse.eval(*c);
1594
0
            }
1595
0
        }
1596
0
        let [r, g, b] = colorants;
1597
0
        let white = Vector {
1598
0
            v: [
1599
0
                r.v[0] + g.v[0] + b.v[0],
1600
0
                r.v[1] + g.v[1] + b.v[1],
1601
0
                r.v[2] + g.v[2] + b.v[2],
1602
0
            ],
1603
0
        };
1604
0
        let to_xyY = |c: Vector| {
1605
0
            let sum = c.v[0] + c.v[1] + c.v[2];
1606
0
            if !(sum > 0.) {
1607
0
                return None;
1608
0
            }
1609
0
            Some(qcms_CIE_xyY {
1610
0
                x: (c.v[0] / sum) as f64,
1611
0
                y: (c.v[1] / sum) as f64,
1612
0
                Y: c.v[1] as f64,
1613
0
            })
1614
0
        };
1615
        Some((
1616
            qcms_CIE_xyYTRIPLE {
1617
0
                red: to_xyY(r)?,
1618
0
                green: to_xyY(g)?,
1619
0
                blue: to_xyY(b)?,
1620
            },
1621
0
            to_xyY(white)?,
1622
        ))
1623
0
    }
1624
1625
0
    pub fn rendering_intent(&self) -> Intent {
1626
0
        self.rendering_intent
1627
0
    }
1628
1629
    pub(crate) fn new_sRGB_parametric() -> Box<Profile> {
1630
        let primaries = qcms_CIE_xyYTRIPLE::from(ColourPrimaries::Bt709);
1631
        let white_point = qcms_white_point_sRGB();
1632
        let mut profile = profile_create();
1633
        set_rgb_colorants(&mut profile, white_point, primaries);
1634
1635
        let curve = Box::new(curveType::Parametric(vec![
1636
            2.4,
1637
            1. / 1.055,
1638
            0.055 / 1.055,
1639
            1. / 12.92,
1640
            0.04045,
1641
        ]));
1642
        profile.redTRC = Some(curve.clone());
1643
        profile.blueTRC = Some(curve.clone());
1644
        profile.greenTRC = Some(curve);
1645
        profile.class_type = DISPLAY_DEVICE_PROFILE;
1646
        profile.rendering_intent = Perceptual;
1647
        profile.color_space = RGB_SIGNATURE;
1648
        profile.pcs = XYZ_TYPE;
1649
        profile.is_srgb = true;
1650
        profile
1651
    }
1652
1653
0
    pub(crate) fn new_displayP3() -> Box<Profile> {
1654
0
        let primaries = qcms_CIE_xyYTRIPLE::from(ColourPrimaries::Smpte432);
1655
0
        let white_point = qcms_white_point_sRGB();
1656
0
        let mut profile = profile_create();
1657
0
        set_rgb_colorants(&mut profile, white_point, primaries);
1658
1659
0
        let curve = Box::new(curveType::Parametric(vec![
1660
0
            2.4,
1661
0
            1. / 1.055,
1662
0
            0.055 / 1.055,
1663
0
            1. / 12.92,
1664
0
            0.04045,
1665
0
        ]));
1666
0
        profile.redTRC = Some(curve.clone());
1667
0
        profile.blueTRC = Some(curve.clone());
1668
0
        profile.greenTRC = Some(curve);
1669
0
        profile.class_type = DISPLAY_DEVICE_PROFILE;
1670
0
        profile.rendering_intent = Perceptual;
1671
0
        profile.color_space = RGB_SIGNATURE;
1672
0
        profile.pcs = XYZ_TYPE;
1673
0
        profile.is_srgb = false;
1674
0
        profile
1675
0
    }
1676
1677
    /// Create a new profile with D50 adopted white and identity transform functions
1678
8
    pub fn new_XYZD50() -> Box<Profile> {
1679
8
        let mut profile = profile_create();
1680
8
        profile.redColorant.X = double_to_s15Fixed16Number(1.);
1681
8
        profile.redColorant.Y = double_to_s15Fixed16Number(0.);
1682
8
        profile.redColorant.Z = double_to_s15Fixed16Number(0.);
1683
8
        profile.greenColorant.X = double_to_s15Fixed16Number(0.);
1684
8
        profile.greenColorant.Y = double_to_s15Fixed16Number(1.);
1685
8
        profile.greenColorant.Z = double_to_s15Fixed16Number(0.);
1686
8
        profile.blueColorant.X = double_to_s15Fixed16Number(0.);
1687
8
        profile.blueColorant.Y = double_to_s15Fixed16Number(0.);
1688
8
        profile.blueColorant.Z = double_to_s15Fixed16Number(1.);
1689
8
        profile.redTRC = Some(identity_curve());
1690
8
        profile.blueTRC = Some(identity_curve());
1691
8
        profile.greenTRC = Some(identity_curve());
1692
1693
8
        profile.class_type = DISPLAY_DEVICE_PROFILE;
1694
8
        profile.rendering_intent = Perceptual;
1695
8
        profile.color_space = RGB_SIGNATURE;
1696
8
        profile.pcs = XYZ_TYPE;
1697
8
        profile
1698
8
    }
1699
1700
0
    pub fn new_cicp(cp: ColourPrimaries, tc: TransferCharacteristics) -> Option<Box<Profile>> {
1701
0
        Profile::new_cicp_with_intent(cp, tc, Perceptual)
1702
0
    }
1703
1704
0
    pub fn new_cicp_with_intent(
1705
0
        cp: ColourPrimaries,
1706
0
        tc: TransferCharacteristics,
1707
0
        rendering_intent: Intent,
1708
0
    ) -> Option<Box<Profile>> {
1709
0
        let mut profile = profile_create();
1710
0
        if !cp.is_usable() || !tc.is_usable() {
1711
0
            return None;
1712
0
        }
1713
        //XXX: should store the whitepoint
1714
0
        if !set_rgb_colorants(&mut profile, cp.white_point(), qcms_CIE_xyYTRIPLE::from(cp)) {
1715
0
            return None;
1716
0
        }
1717
0
        let curve = curveType::try_from(tc).ok()?;
1718
0
        profile.redTRC = Some(Box::new(curve.clone()));
1719
0
        profile.blueTRC = Some(Box::new(curve.clone()));
1720
0
        profile.greenTRC = Some(Box::new(curve));
1721
0
        profile.class_type = DISPLAY_DEVICE_PROFILE;
1722
0
        profile.rendering_intent = rendering_intent;
1723
0
        profile.color_space = RGB_SIGNATURE;
1724
0
        profile.pcs = XYZ_TYPE;
1725
1726
0
        profile.is_srgb = (cp, tc) == (ColourPrimaries::Bt709, TransferCharacteristics::Srgb);
1727
0
        Some(profile)
1728
0
    }
1729
1730
0
    pub fn new_gray_with_gamma(gamma: f32) -> Box<Profile> {
1731
0
        let mut profile = profile_create();
1732
1733
0
        profile.grayTRC = Some(curve_from_gamma(gamma));
1734
0
        profile.class_type = DISPLAY_DEVICE_PROFILE;
1735
0
        profile.rendering_intent = Perceptual;
1736
0
        profile.color_space = GRAY_SIGNATURE;
1737
0
        profile.pcs = XYZ_TYPE;
1738
0
        profile
1739
0
    }
1740
1741
0
    pub fn new_rgb_with_gamma_set(
1742
0
        white_point: qcms_CIE_xyY,
1743
0
        primaries: qcms_CIE_xyYTRIPLE,
1744
0
        redGamma: f32,
1745
0
        greenGamma: f32,
1746
0
        blueGamma: f32,
1747
0
    ) -> Option<Box<Profile>> {
1748
0
        let mut profile = profile_create();
1749
1750
        //XXX: should store the whitepoint
1751
0
        if !set_rgb_colorants(&mut profile, white_point, primaries) {
1752
0
            return None;
1753
0
        }
1754
0
        profile.redTRC = Some(curve_from_gamma(redGamma));
1755
0
        profile.blueTRC = Some(curve_from_gamma(blueGamma));
1756
0
        profile.greenTRC = Some(curve_from_gamma(greenGamma));
1757
0
        profile.class_type = DISPLAY_DEVICE_PROFILE;
1758
0
        profile.rendering_intent = Perceptual;
1759
0
        profile.color_space = RGB_SIGNATURE;
1760
0
        profile.pcs = XYZ_TYPE;
1761
0
        Some(profile)
1762
0
    }
1763
1764
0
    pub fn new_from_path(file: &str) -> Option<Box<Profile>> {
1765
0
        Profile::new_from_slice(&std::fs::read(file).ok()?, false)
1766
0
    }
1767
1768
48
    pub fn new_from_slice(mem: &[u8], curves_only: bool) -> Option<Box<Profile>> {
1769
        let length: u32;
1770
48
        let mut source: MemSource = MemSource {
1771
48
            buf: mem,
1772
48
            valid: false,
1773
48
            invalid_reason: None,
1774
48
        };
1775
        let index;
1776
48
        source.valid = true;
1777
48
        let src: &mut MemSource = &mut source;
1778
48
        if mem.len() < 4 {
1779
0
            return None;
1780
48
        }
1781
48
        length = read_u32(src, 0);
1782
48
        if length as usize <= mem.len() {
1783
46
            // shrink the area that we can read if appropriate
1784
46
            src.buf = &src.buf[0..length as usize];
1785
46
        } else {
1786
2
            return None;
1787
        }
1788
        /* ensure that the profile size is sane so it's easier to reason about */
1789
46
        if src.buf.len() <= 64 || src.buf.len() >= MAX_PROFILE_SIZE {
1790
6
            return None;
1791
40
        }
1792
40
        let mut profile = profile_create();
1793
1794
40
        check_CMM_type_signature(src);
1795
40
        check_profile_version(src);
1796
40
        read_class_signature(&mut profile, src);
1797
40
        read_rendering_intent(&mut profile, src);
1798
40
        read_color_space(&mut profile, src);
1799
40
        read_pcs(&mut profile, src);
1800
        //TODO read rest of profile stuff
1801
40
        if !src.valid {
1802
10
            return None;
1803
30
        }
1804
1805
30
        index = read_tag_table(&mut profile, src);
1806
30
        if !src.valid || index.is_empty() {
1807
1
            return None;
1808
29
        }
1809
1810
29
        if let Some(chad) = find_tag(&index, TAG_CHAD) {
1811
19
            profile.chromaticAdaption = Some(read_tag_s15Fixed16ArrayType(src, chad))
1812
10
        } else {
1813
10
            profile.chromaticAdaption = None; //Signal the data is not present
1814
10
        }
1815
1816
29
        profile.description =
1817
29
            find_tag(&index, TAG_desc).and_then(|tag| read_tag_description(src.buf, tag));
1818
1819
29
        if profile.class_type == DISPLAY_DEVICE_PROFILE
1820
27
            || profile.class_type == INPUT_DEVICE_PROFILE
1821
27
            || profile.class_type == OUTPUT_DEVICE_PROFILE
1822
0
            || profile.class_type == COLOR_SPACE_PROFILE
1823
        {
1824
29
            if profile.color_space == RGB_SIGNATURE {
1825
28
                if !curves_only {
1826
28
                    if let Some(A2B0) = find_tag(&index, TAG_A2B0) {
1827
26
                        let lut_type = read_u32(src, A2B0.offset as usize);
1828
26
                        if lut_type == LUT8_TYPE || lut_type == LUT16_TYPE {
1829
11
                            profile.A2B0 = read_tag_lutType(src, A2B0)
1830
15
                        } else if lut_type == LUT_MAB_TYPE {
1831
3
                            profile.mAB = read_tag_lutmABType(src, A2B0)
1832
12
                        }
1833
2
                    }
1834
28
                    if let Some(B2A0) = find_tag(&index, TAG_B2A0) {
1835
20
                        let lut_type = read_u32(src, B2A0.offset as usize);
1836
20
                        if lut_type == LUT8_TYPE || lut_type == LUT16_TYPE {
1837
4
                            profile.B2A0 = read_tag_lutType(src, B2A0)
1838
16
                        } else if lut_type == LUT_MBA_TYPE {
1839
7
                            profile.mBA = read_tag_lutmABType(src, B2A0)
1840
9
                        }
1841
8
                    }
1842
0
                }
1843
28
                if find_tag(&index, TAG_rXYZ).is_some() || curves_only {
1844
15
                    profile.redColorant = read_tag_XYZType(src, &index, TAG_rXYZ);
1845
15
                    profile.greenColorant = read_tag_XYZType(src, &index, TAG_gXYZ);
1846
15
                    profile.blueColorant = read_tag_XYZType(src, &index, TAG_bXYZ)
1847
13
                }
1848
28
                if !src.valid {
1849
19
                    return None;
1850
9
                }
1851
1852
9
                if find_tag(&index, TAG_rTRC).is_some() || curves_only {
1853
6
                    profile.redTRC = read_tag_curveType(src, &index, TAG_rTRC);
1854
6
                    profile.greenTRC = read_tag_curveType(src, &index, TAG_gTRC);
1855
6
                    profile.blueTRC = read_tag_curveType(src, &index, TAG_bTRC);
1856
6
                    if profile.redTRC.is_none()
1857
5
                        || profile.blueTRC.is_none()
1858
5
                        || profile.greenTRC.is_none()
1859
                    {
1860
1
                        return None;
1861
5
                    }
1862
3
                }
1863
1
            } else if profile.color_space == GRAY_SIGNATURE {
1864
0
                profile.grayTRC = read_tag_curveType(src, &index, TAG_kTRC);
1865
0
                profile.grayTRC.as_ref()?;
1866
1
            } else if profile.color_space == CMYK_SIGNATURE {
1867
1
                if let Some(A2B0) = find_tag(&index, TAG_A2B0) {
1868
1
                    let lut_type = read_u32(src, A2B0.offset as usize);
1869
1
                    if lut_type == LUT8_TYPE || lut_type == LUT16_TYPE {
1870
0
                        profile.A2B0 = read_tag_lutType(src, A2B0)
1871
1
                    } else if lut_type == LUT_MBA_TYPE {
1872
1
                        profile.mAB = read_tag_lutmABType(src, A2B0)
1873
0
                    }
1874
0
                }
1875
            } else {
1876
0
                debug_assert!(false, "read_color_space protects against entering here");
1877
0
                return None;
1878
            }
1879
        } else {
1880
0
            return None;
1881
        }
1882
1883
9
        if !src.valid {
1884
1
            return None;
1885
8
        }
1886
8
        Some(profile)
1887
48
    }
1888
    /// Precomputes the information needed for this profile to be
1889
    /// used as the output profile when constructing a `Transform`.
1890
0
    pub fn precache_output_transform(&mut self) {
1891
0
        crate::transform::qcms_profile_precache_output_transform(self);
1892
0
    }
1893
}