tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

iccread.rs (65252B)


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