tor-browser

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

transform_util.rs (21507B)


      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 crate::{
     24    iccread::{curveType, Profile},
     25    s15Fixed16Number_to_float,
     26 };
     27 use crate::{matrix::Matrix, transform::PRECACHE_OUTPUT_MAX, transform::PRECACHE_OUTPUT_SIZE};
     28 
     29 //XXX: could use a bettername
     30 pub type uint16_fract_t = u16;
     31 
     32 #[inline]
     33 fn u8Fixed8Number_to_float(x: u16) -> f32 {
     34    // 0x0000 = 0.
     35    // 0x0100 = 1.
     36    // 0xffff = 255  + 255/256
     37    (x as i32 as f64 / 256.0) as f32
     38 }
     39 #[inline]
     40 pub fn clamp_float(a: f32) -> f32 {
     41    /* One would naturally write this function as the following:
     42    if (a > 1.)
     43      return 1.;
     44    else if (a < 0)
     45      return 0;
     46    else
     47      return a;
     48 
     49    However, that version will let NaNs pass through which is undesirable
     50    for most consumers.
     51    */
     52    if a > 1. {
     53        1.
     54    } else if a >= 0. {
     55        a
     56    } else {
     57        // a < 0 or a is NaN
     58        0.
     59    }
     60 }
     61 /* value must be a value between 0 and 1 */
     62 //XXX: is the above a good restriction to have?
     63 // the output range of this functions is 0..1
     64 pub fn lut_interp_linear(mut input_value: f64, table: &[u16]) -> f32 {
     65    if table.is_empty() {
     66        return input_value as f32;
     67    }
     68 
     69    input_value *= (table.len() - 1) as f64;
     70 
     71    let upper: i32 = input_value.ceil() as i32;
     72    let lower: i32 = input_value.floor() as i32;
     73    let value: f32 = ((table[(upper as usize).min(table.len() - 1)] as f64)
     74        * (1. - (upper as f64 - input_value))
     75        + (table[(lower as usize).min(table.len() - 1)] as f64 * (upper as f64 - input_value)))
     76        as f32;
     77    /* scale the value */
     78    value * (1.0 / 65535.0)
     79 }
     80 /* same as above but takes and returns a uint16_t value representing a range from 0..1 */
     81 #[no_mangle]
     82 pub fn lut_interp_linear16(input_value: u16, table: &[u16]) -> u16 {
     83    /* Start scaling input_value to the length of the array: 65535*(length-1).
     84     * We'll divide out the 65535 next */
     85    let mut value: u32 = (input_value as i32 * (table.len() as i32 - 1)) as u32; /* equivalent to ceil(value/65535) */
     86    let upper: u32 = (value + 65534) / 65535; /* equivalent to floor(value/65535) */
     87    let lower: u32 = value / 65535;
     88    /* interp is the distance from upper to value scaled to 0..65535 */
     89    let interp: u32 = value % 65535; // 0..65535*65535
     90    value = (table[upper as usize] as u32 * interp
     91        + table[lower as usize] as u32 * (65535 - interp))
     92        / 65535;
     93    value as u16
     94 }
     95 /* same as above but takes an input_value from 0..PRECACHE_OUTPUT_MAX
     96 * and returns a uint8_t value representing a range from 0..1 */
     97 fn lut_interp_linear_precache_output(input_value: u32, table: &[u16]) -> u8 {
     98    /* Start scaling input_value to the length of the array: PRECACHE_OUTPUT_MAX*(length-1).
     99     * We'll divide out the PRECACHE_OUTPUT_MAX next */
    100    let mut value: u32 = input_value * (table.len() - 1) as u32;
    101    /* equivalent to ceil(value/PRECACHE_OUTPUT_MAX) */
    102    let upper: u32 = (value + PRECACHE_OUTPUT_MAX as u32 - 1) / PRECACHE_OUTPUT_MAX as u32;
    103    /* equivalent to floor(value/PRECACHE_OUTPUT_MAX) */
    104    let lower: u32 = value / PRECACHE_OUTPUT_MAX as u32;
    105    /* interp is the distance from upper to value scaled to 0..PRECACHE_OUTPUT_MAX */
    106    let interp: u32 = value % PRECACHE_OUTPUT_MAX as u32;
    107    /* the table values range from 0..65535 */
    108    value = table[upper as usize] as u32 * interp
    109        + table[lower as usize] as u32 * (PRECACHE_OUTPUT_MAX as u32 - interp); // 0..(65535*PRECACHE_OUTPUT_MAX)
    110                                                                                /* round and scale */
    111    value += (PRECACHE_OUTPUT_MAX * 65535 / 255 / 2) as u32; // scale to 0..255
    112    value /= (PRECACHE_OUTPUT_MAX * 65535 / 255) as u32;
    113    value as u8
    114 }
    115 /* value must be a value between 0 and 1 */
    116 //XXX: is the above a good restriction to have?
    117 pub fn lut_interp_linear_float(mut value: f32, table: &[f32]) -> f32 {
    118    value *= (table.len() - 1) as f32;
    119 
    120    let upper: i32 = value.ceil() as i32;
    121    let lower: i32 = value.floor() as i32;
    122    //XXX: can we be more performant here?
    123    value = (table[upper as usize] as f64 * (1.0f64 - (upper as f32 - value) as f64)
    124        + (table[lower as usize] * (upper as f32 - value)) as f64) as f32;
    125    /* scale the value */
    126    value
    127 }
    128 
    129 fn compute_curve_gamma_table_type1(gamma_table: &mut [f32; 256], gamma: u16) {
    130    let gamma_float: f32 = u8Fixed8Number_to_float(gamma);
    131    for (i, g) in gamma_table.iter_mut().enumerate() {
    132        // 0..1^(0..255 + 255/256) will always be between 0 and 1
    133        *g = (i as f64 / 255.0).powf(gamma_float as f64) as f32;
    134    }
    135 }
    136 
    137 fn compute_curve_gamma_table_type2(gamma_table: &mut [f32; 256], table: &[u16]) {
    138    for (i, g) in gamma_table.iter_mut().enumerate() {
    139        *g = lut_interp_linear(i as f64 / 255.0, table);
    140    }
    141 }
    142 
    143 fn compute_curve_gamma_table_type_parametric(gamma_table: &mut [f32; 256], params: &[f32]) {
    144    let params = Param::new(params);
    145    for (i, g) in gamma_table.iter_mut().enumerate() {
    146        let X = i as f32 / 255.;
    147        *g = clamp_float(params.eval(X));
    148    }
    149 }
    150 
    151 fn compute_curve_gamma_table_type0(gamma_table: &mut [f32; 256]) {
    152    for (i, g) in gamma_table.iter_mut().enumerate() {
    153        *g = (i as f64 / 255.0) as f32;
    154    }
    155 }
    156 
    157 #[inline(always)]
    158 pub(crate) fn build_input_gamma_table(TRC: &curveType) -> [f32; 256] {
    159    let mut gamma_table = [0.; 256];
    160    match TRC {
    161        curveType::Parametric(params) => {
    162            compute_curve_gamma_table_type_parametric(&mut gamma_table, params)
    163        }
    164        curveType::Curve(data) => match data.len() {
    165            0 => compute_curve_gamma_table_type0(&mut gamma_table),
    166            1 => compute_curve_gamma_table_type1(&mut gamma_table, data[0]),
    167            _ => compute_curve_gamma_table_type2(&mut gamma_table, data),
    168        },
    169    };
    170    gamma_table
    171 }
    172 
    173 pub fn build_colorant_matrix(p: &Profile) -> Matrix {
    174    let mut result: Matrix = Matrix { m: [[0.; 3]; 3] };
    175    result.m[0][0] = s15Fixed16Number_to_float(p.redColorant.X);
    176    result.m[0][1] = s15Fixed16Number_to_float(p.greenColorant.X);
    177    result.m[0][2] = s15Fixed16Number_to_float(p.blueColorant.X);
    178    result.m[1][0] = s15Fixed16Number_to_float(p.redColorant.Y);
    179    result.m[1][1] = s15Fixed16Number_to_float(p.greenColorant.Y);
    180    result.m[1][2] = s15Fixed16Number_to_float(p.blueColorant.Y);
    181    result.m[2][0] = s15Fixed16Number_to_float(p.redColorant.Z);
    182    result.m[2][1] = s15Fixed16Number_to_float(p.greenColorant.Z);
    183    result.m[2][2] = s15Fixed16Number_to_float(p.blueColorant.Z);
    184    result
    185 }
    186 
    187 /** Parametric representation of transfer function */
    188 #[derive(Debug)]
    189 struct Param {
    190    g: f32,
    191    a: f32,
    192    b: f32,
    193    c: f32,
    194    d: f32,
    195    e: f32,
    196    f: f32,
    197 }
    198 
    199 impl Param {
    200    #[allow(clippy::many_single_char_names)]
    201    fn new(params: &[f32]) -> Param {
    202        // convert from the variable number of parameters
    203        // contained in profiles to a unified representation.
    204        let g: f32 = params[0];
    205        match params[1..] {
    206            [] => Param {
    207                g,
    208                a: 1.,
    209                b: 0.,
    210                c: 1.,
    211                d: 0.,
    212                e: 0.,
    213                f: 0.,
    214            },
    215            [a, b] => Param {
    216                g,
    217                a,
    218                b,
    219                c: 0.,
    220                d: -b / a,
    221                e: 0.,
    222                f: 0.,
    223            },
    224            [a, b, c] => Param {
    225                g,
    226                a,
    227                b,
    228                c: 0.,
    229                d: -b / a,
    230                e: c,
    231                f: c,
    232            },
    233            [a, b, c, d] => Param {
    234                g,
    235                a,
    236                b,
    237                c,
    238                d,
    239                e: 0.,
    240                f: 0.,
    241            },
    242            [a, b, c, d, e, f] => Param {
    243                g,
    244                a,
    245                b,
    246                c,
    247                d,
    248                e,
    249                f,
    250            },
    251            _ => panic!(),
    252        }
    253    }
    254 
    255    fn eval(&self, x: f32) -> f32 {
    256        if x < self.d {
    257            self.c * x + self.f
    258        } else {
    259            (self.a * x + self.b).powf(self.g) + self.e
    260        }
    261    }
    262    #[allow(clippy::many_single_char_names)]
    263    fn invert(&self) -> Option<Param> {
    264        // First check if the function is continuous at the cross-over point d.
    265        let d1 = (self.a * self.d + self.b).powf(self.g) + self.e;
    266        let d2 = self.c * self.d + self.f;
    267 
    268        if (d1 - d2).abs() > 0.1 {
    269            return None;
    270        }
    271        let d = d1;
    272 
    273        // y = (a * x + b)^g + e
    274        // y - e = (a * x + b)^g
    275        // (y - e)^(1/g) = a*x + b
    276        // (y - e)^(1/g) - b = a*x
    277        // (y - e)^(1/g)/a - b/a = x
    278        // ((y - e)/a^g)^(1/g) - b/a = x
    279        // ((1/(a^g)) * y - e/(a^g))^(1/g) - b/a = x
    280        let a = 1. / self.a.powf(self.g);
    281        let b = -self.e / self.a.powf(self.g);
    282        let g = 1. / self.g;
    283        let e = -self.b / self.a;
    284 
    285        // y = c * x + f
    286        // y - f = c * x
    287        // y/c - f/c = x
    288        let (c, f);
    289        if d <= 0. {
    290            c = 1.;
    291            f = 0.;
    292        } else {
    293            c = 1. / self.c;
    294            f = -self.f / self.c;
    295        }
    296 
    297        // if self.d > 0. and self.c == 0 as is likely with type 1 and 2 parametric function
    298        // then c and f will not be finite.
    299        if !(g.is_finite()
    300            && a.is_finite()
    301            && b.is_finite()
    302            && c.is_finite()
    303            && d.is_finite()
    304            && e.is_finite()
    305            && f.is_finite())
    306        {
    307            return None;
    308        }
    309 
    310        Some(Param {
    311            g,
    312            a,
    313            b,
    314            c,
    315            d,
    316            e,
    317            f,
    318        })
    319    }
    320 }
    321 
    322 #[test]
    323 fn param_invert() {
    324    let p3 = Param::new(&[2.4, 0.948, 0.052, 0.077, 0.04]);
    325    p3.invert().unwrap();
    326    let g2_2 = Param::new(&[2.2]);
    327    g2_2.invert().unwrap();
    328    let g2_2 = Param::new(&[2.2, 0.9, 0.052]);
    329    g2_2.invert().unwrap();
    330    let g2_2 = dbg!(Param::new(&[2.2, 0.9, -0.52]));
    331    g2_2.invert().unwrap();
    332    let g2_2 = dbg!(Param::new(&[2.2, 0.9, -0.52, 0.1]));
    333    assert!(g2_2.invert().is_none());
    334 }
    335 
    336 /* The following code is copied nearly directly from lcms.
    337 * I think it could be much better. For example, Argyll seems to have better code in
    338 * icmTable_lookup_bwd and icmTable_setup_bwd. However, for now this is a quick way
    339 * to a working solution and allows for easy comparing with lcms. */
    340 #[no_mangle]
    341 #[allow(clippy::many_single_char_names)]
    342 pub fn lut_inverse_interp16(Value: u16, LutTable: &[u16]) -> uint16_fract_t {
    343    let mut l: i32 = 1; // 'int' Give spacing for negative values
    344    let mut r: i32 = 0x10000;
    345    let mut x: i32 = 0;
    346    let mut res: i32;
    347    let length = LutTable.len() as i32;
    348 
    349    let mut NumZeroes: i32 = 0;
    350    while LutTable[NumZeroes as usize] as i32 == 0 && NumZeroes < length - 1 {
    351        NumZeroes += 1
    352    }
    353    // There are no zeros at the beginning and we are trying to find a zero, so
    354    // return anything. It seems zero would be the less destructive choice
    355    /* I'm not sure that this makes sense, but oh well... */
    356    if NumZeroes == 0 && Value as i32 == 0 {
    357        return 0u16;
    358    }
    359    let mut NumPoles: i32 = 0;
    360    while LutTable[(length - 1 - NumPoles) as usize] as i32 == 0xffff && NumPoles < length - 1 {
    361        NumPoles += 1
    362    }
    363    // Does the curve belong to this case?
    364    if NumZeroes > 1 || NumPoles > 1 {
    365        let a_0: i32;
    366        let b_0: i32;
    367        // Identify if value fall downto 0 or FFFF zone
    368        if Value as i32 == 0 {
    369            return 0u16;
    370        }
    371        // if (Value == 0xFFFF) return 0xFFFF;
    372        // else restrict to valid zone
    373        if NumZeroes > 1 {
    374            a_0 = (NumZeroes - 1) * 0xffff / (length - 1);
    375            l = a_0 - 1
    376        }
    377        if NumPoles > 1 {
    378            b_0 = (length - 1 - NumPoles) * 0xffff / (length - 1);
    379            r = b_0 + 1
    380        }
    381    }
    382    if r <= l {
    383        // If this happens LutTable is not invertible
    384        return 0u16;
    385    }
    386    // Seems not a degenerated case... apply binary search
    387    while r > l {
    388        x = (l + r) / 2;
    389        res = lut_interp_linear16((x - 1) as uint16_fract_t, LutTable) as i32;
    390        if res == Value as i32 {
    391            // Found exact match.
    392            return (x - 1) as uint16_fract_t;
    393        }
    394        if res > Value as i32 {
    395            r = x - 1
    396        } else {
    397            l = x + 1
    398        }
    399    }
    400 
    401    // Not found, should we interpolate?
    402 
    403    // Get surrounding nodes
    404    debug_assert!(x >= 1);
    405 
    406    let val2: f64 = (length - 1) as f64 * ((x - 1) as f64 / 65535.0);
    407    let cell0: i32 = val2.floor() as i32;
    408    let cell1: i32 = val2.ceil() as i32;
    409    if cell0 == cell1 {
    410        return x as uint16_fract_t;
    411    }
    412 
    413    let y0: f64 = LutTable[cell0 as usize] as f64;
    414    let x0: f64 = 65535.0 * cell0 as f64 / (length - 1) as f64;
    415    let y1: f64 = LutTable[cell1 as usize] as f64;
    416    let x1: f64 = 65535.0 * cell1 as f64 / (length - 1) as f64;
    417    let a: f64 = (y1 - y0) / (x1 - x0);
    418    let b: f64 = y0 - a * x0;
    419    if a.abs() < 0.01f64 {
    420        return x as uint16_fract_t;
    421    }
    422    let f: f64 = (Value as i32 as f64 - b) / a;
    423    if f < 0.0 {
    424        return 0u16;
    425    }
    426    if f >= 65535.0 {
    427        return 0xffffu16;
    428    }
    429    (f + 0.5f64).floor() as uint16_fract_t
    430 }
    431 /*
    432 The number of entries needed to invert a lookup table should not
    433 necessarily be the same as the original number of entries.  This is
    434 especially true of lookup tables that have a small number of entries.
    435 
    436 For example:
    437 Using a table like:
    438   {0, 3104, 14263, 34802, 65535}
    439 invert_lut will produce an inverse of:
    440   {3, 34459, 47529, 56801, 65535}
    441 which has an maximum error of about 9855 (pixel difference of ~38.346)
    442 
    443 For now, we punt the decision of output size to the caller. */
    444 fn invert_lut(table: &[u16], out_length: usize) -> Vec<u16> {
    445    /* for now we invert the lut by creating a lut of size out_length
    446     * and attempting to lookup a value for each entry using lut_inverse_interp16 */
    447    let mut output = Vec::with_capacity(out_length);
    448    for i in 0..out_length {
    449        let x: f64 = i as f64 * 65535.0 / (out_length - 1) as f64;
    450        let input: uint16_fract_t = (x + 0.5f64).floor() as uint16_fract_t;
    451        output.push(lut_inverse_interp16(input, table));
    452    }
    453    output
    454 }
    455 #[allow(clippy::needless_range_loop)]
    456 fn compute_precache_pow(output: &mut [u8; PRECACHE_OUTPUT_SIZE], gamma: f32) {
    457    for v in 0..PRECACHE_OUTPUT_SIZE {
    458        //XXX: don't do integer/float conversion... and round?
    459        output[v] = (255. * (v as f32 / PRECACHE_OUTPUT_MAX as f32).powf(gamma)) as u8;
    460    }
    461 }
    462 #[allow(clippy::needless_range_loop)]
    463 pub fn compute_precache_lut(output: &mut [u8; PRECACHE_OUTPUT_SIZE], table: &[u16]) {
    464    for v in 0..PRECACHE_OUTPUT_SIZE {
    465        output[v] = lut_interp_linear_precache_output(v as u32, table);
    466    }
    467 }
    468 #[allow(clippy::needless_range_loop)]
    469 pub fn compute_precache_linear(output: &mut [u8; PRECACHE_OUTPUT_SIZE]) {
    470    for v in 0..PRECACHE_OUTPUT_SIZE {
    471        //XXX: round?
    472        output[v] = (v / (PRECACHE_OUTPUT_SIZE / 256)) as u8;
    473    }
    474 }
    475 pub(crate) fn compute_precache(trc: &curveType, output: &mut [u8; PRECACHE_OUTPUT_SIZE]) {
    476    match trc {
    477        curveType::Parametric(params) => {
    478            let mut gamma_table_uint: [u16; 256] = [0; 256];
    479 
    480            let mut inverted_size: usize = 256;
    481            let mut gamma_table = [0.; 256];
    482            compute_curve_gamma_table_type_parametric(&mut gamma_table, params);
    483            let mut i: u16 = 0u16;
    484            while (i as i32) < 256 {
    485                gamma_table_uint[i as usize] = (gamma_table[i as usize] * 65535f32) as u16;
    486                i += 1
    487            }
    488            //XXX: the choice of a minimum of 256 here is not backed by any theory,
    489            //     measurement or data, however it is what lcms uses.
    490            //     the maximum number we would need is 65535 because that's the
    491            //     accuracy used for computing the pre cache table
    492            if inverted_size < 256 {
    493                inverted_size = 256
    494            }
    495            let inverted = invert_lut(&gamma_table_uint, inverted_size);
    496            compute_precache_lut(output, &inverted);
    497        }
    498        curveType::Curve(data) => {
    499            match data.len() {
    500                0 => compute_precache_linear(output),
    501                1 => compute_precache_pow(output, 1. / u8Fixed8Number_to_float(data[0])),
    502                _ => {
    503                    let mut inverted_size = data.len();
    504                    //XXX: the choice of a minimum of 256 here is not backed by any theory,
    505                    //     measurement or data, however it is what lcms uses.
    506                    //     the maximum number we would need is 65535 because that's the
    507                    //     accuracy used for computing the pre cache table
    508                    if inverted_size < 256 {
    509                        inverted_size = 256
    510                    } //XXX turn this conversion into a function
    511                    let inverted = invert_lut(data, inverted_size);
    512                    compute_precache_lut(output, &inverted);
    513                }
    514            }
    515        }
    516    }
    517 }
    518 fn build_linear_table(length: usize) -> Vec<u16> {
    519    let mut output = Vec::with_capacity(length);
    520    for i in 0..length {
    521        let x: f64 = i as f64 * 65535.0 / (length - 1) as f64;
    522        let input: uint16_fract_t = (x + 0.5f64).floor() as uint16_fract_t;
    523        output.push(input);
    524    }
    525    output
    526 }
    527 fn build_pow_table(gamma: f32, length: usize) -> Vec<u16> {
    528    let mut output = Vec::with_capacity(length);
    529    for i in 0..length {
    530        let mut x: f64 = i as f64 / (length - 1) as f64;
    531        x = x.powf(gamma as f64);
    532        let result: uint16_fract_t = (x * 65535.0 + 0.5f64).floor() as uint16_fract_t;
    533        output.push(result);
    534    }
    535    output
    536 }
    537 
    538 fn to_lut(params: &Param, len: usize) -> Vec<u16> {
    539    let mut output = Vec::with_capacity(len);
    540    for i in 0..len {
    541        let X = i as f32 / (len-1) as f32;
    542        output.push((params.eval(X) * 65535.) as u16);
    543    }
    544    output
    545 }
    546 
    547 pub(crate) fn build_lut_for_linear_from_tf(trc: &curveType,
    548        lut_len: Option<usize>) -> Vec<u16> {
    549    match trc {
    550        curveType::Parametric(params) => {
    551            let lut_len = lut_len.unwrap_or(256);
    552            let params = Param::new(params);
    553            to_lut(&params, lut_len)
    554        },
    555        curveType::Curve(data) => {
    556            let autogen_lut_len = lut_len.unwrap_or(4096);
    557            match data.len() {
    558                0 => build_linear_table(autogen_lut_len),
    559                1 => {
    560                    let gamma = u8Fixed8Number_to_float(data[0]);
    561                    build_pow_table(gamma, autogen_lut_len)
    562                }
    563                _ => {
    564                    let lut_len = lut_len.unwrap_or(data.len());
    565                    assert_eq!(lut_len, data.len());
    566                    data.clone() // I feel bad about this.
    567                }
    568            }
    569        },
    570    }
    571 }
    572 
    573 pub(crate) fn build_lut_for_tf_from_linear(trc: &curveType) -> Option<Vec<u16>> {
    574    match trc {
    575        curveType::Parametric(params) => {
    576            let lut_len = 256;
    577            let params = Param::new(params);
    578            if let Some(inv_params) = params.invert() {
    579                return Some(to_lut(&inv_params, lut_len));
    580            }
    581            // else return None instead of fallthrough to generic lut inversion.
    582            return None;
    583        },
    584        curveType::Curve(data) => {
    585            let autogen_lut_len = 4096;
    586            match data.len() {
    587                0 => {
    588                    return Some(build_linear_table(autogen_lut_len));
    589                },
    590                1 => {
    591                    let gamma = 1. / u8Fixed8Number_to_float(data[0]);
    592                    return Some(build_pow_table(gamma, autogen_lut_len));
    593                },
    594                _ => {},
    595            }
    596        },
    597    }
    598 
    599    let linear_from_tf = build_lut_for_linear_from_tf(trc, None);
    600 
    601    //XXX: the choice of a minimum of 256 here is not backed by any theory,
    602    //     measurement or data, however it is what lcms uses.
    603    let inverted_lut_len = std::cmp::max(linear_from_tf.len(), 256);
    604    Some(invert_lut(&linear_from_tf, inverted_lut_len))
    605 }
    606 
    607 pub(crate) fn build_output_lut(trc: &curveType) -> Option<Vec<u16>> {
    608    build_lut_for_tf_from_linear(trc)
    609 }