tor-browser

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

transform.rs (59184B)


      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 #![allow(clippy::missing_safety_doc)]
     24 #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "neon"))]
     25 use crate::transform_neon::{
     26    qcms_transform_data_bgra_out_lut_neon, qcms_transform_data_rgb_out_lut_neon,
     27    qcms_transform_data_rgba_out_lut_neon,
     28 };
     29 use crate::{
     30    chain::chain_transform,
     31    double_to_s15Fixed16Number,
     32    iccread::SUPPORTS_ICCV4,
     33    matrix::*,
     34    transform_util::{
     35        build_colorant_matrix, build_input_gamma_table, build_output_lut, compute_precache,
     36        lut_interp_linear,
     37    },
     38 };
     39 use crate::{
     40    iccread::{qcms_CIE_xyY, qcms_CIE_xyYTRIPLE, Profile, GRAY_SIGNATURE, RGB_SIGNATURE},
     41    transform_util::clamp_float,
     42    Intent,
     43 };
     44 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
     45 use crate::{
     46    transform_avx::{
     47        qcms_transform_data_bgra_out_lut_avx, qcms_transform_data_rgb_out_lut_avx,
     48        qcms_transform_data_rgba_out_lut_avx,
     49    },
     50    transform_sse2::{
     51        qcms_transform_data_bgra_out_lut_sse2, qcms_transform_data_rgb_out_lut_sse2,
     52        qcms_transform_data_rgba_out_lut_sse2,
     53    },
     54 };
     55 
     56 use std::sync::atomic::Ordering;
     57 use std::sync::Arc;
     58 #[cfg(all(target_arch = "arm", feature = "neon"))]
     59 use std::arch::is_arm_feature_detected;
     60 #[cfg(all(target_arch = "aarch64", feature = "neon"))]
     61 use std::arch::is_aarch64_feature_detected;
     62 
     63 pub const PRECACHE_OUTPUT_SIZE: usize = 8192;
     64 pub const PRECACHE_OUTPUT_MAX: usize = PRECACHE_OUTPUT_SIZE - 1;
     65 pub const FLOATSCALE: f32 = PRECACHE_OUTPUT_SIZE as f32;
     66 pub const CLAMPMAXVAL: f32 = ((PRECACHE_OUTPUT_SIZE - 1) as f32) / PRECACHE_OUTPUT_SIZE as f32;
     67 
     68 #[repr(C)]
     69 #[derive(Debug)]
     70 pub struct PrecacheOuput {
     71    /* We previously used a count of 65536 here but that seems like more
     72     * precision than we actually need.  By reducing the size we can
     73     * improve startup performance and reduce memory usage. ColorSync on
     74     * 10.5 uses 4097 which is perhaps because they use a fixed point
     75     * representation where 1. is represented by 0x1000. */
     76    pub lut_r: [u8; PRECACHE_OUTPUT_SIZE],
     77    pub lut_g: [u8; PRECACHE_OUTPUT_SIZE],
     78    pub lut_b: [u8; PRECACHE_OUTPUT_SIZE],
     79 }
     80 
     81 impl Default for PrecacheOuput {
     82    fn default() -> PrecacheOuput {
     83        PrecacheOuput {
     84            lut_r: [0; PRECACHE_OUTPUT_SIZE],
     85            lut_g: [0; PRECACHE_OUTPUT_SIZE],
     86            lut_b: [0; PRECACHE_OUTPUT_SIZE],
     87        }
     88    }
     89 }
     90 
     91 /* used as a lookup table for the output transformation.
     92 * we refcount them so we only need to have one around per output
     93 * profile, instead of duplicating them per transform */
     94 
     95 #[repr(C)]
     96 #[repr(align(16))]
     97 #[derive(Clone, Default)]
     98 pub struct qcms_transform {
     99    pub matrix: [[f32; 4]; 3],
    100    pub input_gamma_table_r: Option<Box<[f32; 256]>>,
    101    pub input_gamma_table_g: Option<Box<[f32; 256]>>,
    102    pub input_gamma_table_b: Option<Box<[f32; 256]>>,
    103    pub input_clut_table_length: u16,
    104    pub clut: Option<Vec<f32>>,
    105    pub grid_size: u16,
    106    pub output_clut_table_length: u16,
    107    pub input_gamma_table_gray: Option<Box<[f32; 256]>>,
    108    pub out_gamma_r: f32,
    109    pub out_gamma_g: f32,
    110    pub out_gamma_b: f32,
    111    pub out_gamma_gray: f32,
    112    pub output_gamma_lut_r: Option<Vec<u16>>,
    113    pub output_gamma_lut_g: Option<Vec<u16>>,
    114    pub output_gamma_lut_b: Option<Vec<u16>>,
    115    pub output_gamma_lut_gray: Option<Vec<u16>>,
    116    pub output_gamma_lut_r_length: usize,
    117    pub output_gamma_lut_g_length: usize,
    118    pub output_gamma_lut_b_length: usize,
    119    pub output_gamma_lut_gray_length: usize,
    120    pub precache_output: Option<Arc<PrecacheOuput>>,
    121    pub transform_fn: transform_fn_t,
    122 }
    123 
    124 pub type transform_fn_t =
    125    Option<unsafe fn(_: &qcms_transform, _: *const u8, _: *mut u8, _: usize) -> ()>;
    126 /// The format of pixel data
    127 #[repr(u32)]
    128 #[derive(PartialEq, Eq, Clone, Copy)]
    129 #[allow(clippy::upper_case_acronyms)]
    130 pub enum DataType {
    131    RGB8 = 0,
    132    RGBA8 = 1,
    133    BGRA8 = 2,
    134    Gray8 = 3,
    135    GrayA8 = 4,
    136    CMYK = 5,
    137 }
    138 
    139 impl DataType {
    140    pub fn bytes_per_pixel(&self) -> usize {
    141        match self {
    142            RGB8 => 3,
    143            RGBA8 => 4,
    144            BGRA8 => 4,
    145            Gray8 => 1,
    146            GrayA8 => 2,
    147            CMYK => 4,
    148        }
    149    }
    150 }
    151 
    152 use DataType::*;
    153 
    154 #[repr(C)]
    155 #[derive(Copy, Clone)]
    156 #[allow(clippy::upper_case_acronyms)]
    157 pub struct CIE_XYZ {
    158    pub X: f64,
    159    pub Y: f64,
    160    pub Z: f64,
    161 }
    162 
    163 pub trait Format {
    164    const kRIndex: usize;
    165    const kGIndex: usize;
    166    const kBIndex: usize;
    167    const kAIndex: usize;
    168 }
    169 
    170 #[allow(clippy::upper_case_acronyms)]
    171 pub struct BGRA;
    172 impl Format for BGRA {
    173    const kBIndex: usize = 0;
    174    const kGIndex: usize = 1;
    175    const kRIndex: usize = 2;
    176    const kAIndex: usize = 3;
    177 }
    178 
    179 #[allow(clippy::upper_case_acronyms)]
    180 pub struct RGBA;
    181 impl Format for RGBA {
    182    const kRIndex: usize = 0;
    183    const kGIndex: usize = 1;
    184    const kBIndex: usize = 2;
    185    const kAIndex: usize = 3;
    186 }
    187 
    188 #[allow(clippy::upper_case_acronyms)]
    189 pub struct RGB;
    190 impl Format for RGB {
    191    const kRIndex: usize = 0;
    192    const kGIndex: usize = 1;
    193    const kBIndex: usize = 2;
    194    const kAIndex: usize = 0xFF;
    195 }
    196 
    197 pub trait GrayFormat {
    198    const has_alpha: bool;
    199 }
    200 
    201 pub struct Gray;
    202 impl GrayFormat for Gray {
    203    const has_alpha: bool = false;
    204 }
    205 
    206 pub struct GrayAlpha;
    207 impl GrayFormat for GrayAlpha {
    208    const has_alpha: bool = true;
    209 }
    210 
    211 #[inline]
    212 fn clamp_u8(v: f32) -> u8 {
    213    if v > 255. {
    214        255
    215    } else if v < 0. {
    216        0
    217    } else {
    218        (v + 0.5).floor() as u8
    219    }
    220 }
    221 
    222 // Build a White point, primary chromas transfer matrix from RGB to CIE XYZ
    223 // This is just an approximation, I am not handling all the non-linear
    224 // aspects of the RGB to XYZ process, and assumming that the gamma correction
    225 // has transitive property in the tranformation chain.
    226 //
    227 // the alghoritm:
    228 //
    229 //            - First I build the absolute conversion matrix using
    230 //              primaries in XYZ. This matrix is next inverted
    231 //            - Then I eval the source white point across this matrix
    232 //              obtaining the coeficients of the transformation
    233 //            - Then, I apply these coeficients to the original matrix
    234 fn build_RGB_to_XYZ_transfer_matrix(
    235    white: qcms_CIE_xyY,
    236    primrs: qcms_CIE_xyYTRIPLE,
    237 ) -> Option<Matrix> {
    238    let xn = white.x;
    239    let yn = white.y;
    240    if yn == 0.0 {
    241        return None;
    242    }
    243 
    244    let xr = primrs.red.x;
    245    let yr = primrs.red.y;
    246    let xg = primrs.green.x;
    247    let yg = primrs.green.y;
    248    let xb = primrs.blue.x;
    249    let yb = primrs.blue.y;
    250    let primaries = Matrix {
    251        m: [
    252            [xr as f32, xg as f32, xb as f32],
    253            [yr as f32, yg as f32, yb as f32],
    254            [
    255                (1. - xr - yr) as f32,
    256                (1. - xg - yg) as f32,
    257                (1. - xb - yb) as f32,
    258            ],
    259        ],
    260    };
    261    let white_point = Vector {
    262        v: [(xn / yn) as f32, 1., ((1. - xn - yn) / yn) as f32],
    263    };
    264    let primaries_invert = primaries.invert()?;
    265 
    266    let coefs = primaries_invert.eval(white_point);
    267    Some(Matrix {
    268        m: [
    269            [
    270                (coefs.v[0] as f64 * xr) as f32,
    271                (coefs.v[1] as f64 * xg) as f32,
    272                (coefs.v[2] as f64 * xb) as f32,
    273            ],
    274            [
    275                (coefs.v[0] as f64 * yr) as f32,
    276                (coefs.v[1] as f64 * yg) as f32,
    277                (coefs.v[2] as f64 * yb) as f32,
    278            ],
    279            [
    280                (coefs.v[0] as f64 * (1. - xr - yr)) as f32,
    281                (coefs.v[1] as f64 * (1. - xg - yg)) as f32,
    282                (coefs.v[2] as f64 * (1. - xb - yb)) as f32,
    283            ],
    284        ],
    285    })
    286 }
    287 
    288 /* CIE Illuminant D50 */
    289 const D50_XYZ: CIE_XYZ = CIE_XYZ {
    290    X: 0.9642,
    291    Y: 1.0000,
    292    Z: 0.8249,
    293 };
    294 
    295 /* from lcms: xyY2XYZ()
    296 * corresponds to argyll: icmYxy2XYZ() */
    297 fn xyY2XYZ(source: qcms_CIE_xyY) -> CIE_XYZ {
    298    CIE_XYZ {
    299        X: source.x / source.y * source.Y,
    300        Y: source.Y,
    301        Z: (1. - source.x - source.y) / source.y * source.Y,
    302    }
    303 }
    304 
    305 /* from lcms: ComputeChromaticAdaption */
    306 // Compute chromatic adaption matrix using chad as cone matrix
    307 fn compute_chromatic_adaption(
    308    source_white_point: CIE_XYZ,
    309    dest_white_point: CIE_XYZ,
    310    chad: Matrix,
    311 ) -> Option<Matrix> {
    312    let cone_source_XYZ = Vector {
    313        v: [
    314            source_white_point.X as f32,
    315            source_white_point.Y as f32,
    316            source_white_point.Z as f32,
    317        ],
    318    };
    319    let cone_source_rgb = chad.eval(cone_source_XYZ);
    320 
    321    let cone_dest_XYZ = Vector {
    322        v: [
    323            dest_white_point.X as f32,
    324            dest_white_point.Y as f32,
    325            dest_white_point.Z as f32,
    326        ],
    327    };
    328    let cone_dest_rgb = chad.eval(cone_dest_XYZ);
    329 
    330    let cone = Matrix {
    331        m: [
    332            [cone_dest_rgb.v[0] / cone_source_rgb.v[0], 0., 0.],
    333            [0., cone_dest_rgb.v[1] / cone_source_rgb.v[1], 0.],
    334            [0., 0., cone_dest_rgb.v[2] / cone_source_rgb.v[2]],
    335        ],
    336    };
    337 
    338    let chad_inv = chad.invert()?;
    339 
    340    // Normalize
    341    Some(Matrix::multiply(chad_inv, Matrix::multiply(cone, chad)))
    342 }
    343 
    344 /* from lcms: cmsAdaptionMatrix */
    345 // Returns the final chrmatic adaptation from illuminant FromIll to Illuminant ToIll
    346 // Bradford is assumed
    347 fn adaption_matrix(source_illumination: CIE_XYZ, target_illumination: CIE_XYZ) -> Option<Matrix> {
    348    let lam_rigg = {
    349        Matrix {
    350            m: [
    351                [0.8951, 0.2664, -0.1614],
    352                [-0.7502, 1.7135, 0.0367],
    353                [0.0389, -0.0685, 1.0296],
    354            ],
    355        }
    356    };
    357    compute_chromatic_adaption(source_illumination, target_illumination, lam_rigg)
    358 }
    359 /* from lcms: cmsAdaptMatrixToD50 */
    360 fn adapt_matrix_to_D50(r: Option<Matrix>, source_white_pt: qcms_CIE_xyY) -> Option<Matrix> {
    361    if source_white_pt.y == 0.0 {
    362        return None;
    363    }
    364 
    365    let Dn: CIE_XYZ = xyY2XYZ(source_white_pt);
    366    let Bradford = adaption_matrix(Dn, D50_XYZ)?;
    367    Some(Matrix::multiply(Bradford, r?))
    368 }
    369 pub(crate) fn set_rgb_colorants(
    370    profile: &mut Profile,
    371    white_point: qcms_CIE_xyY,
    372    primaries: qcms_CIE_xyYTRIPLE,
    373 ) -> bool {
    374    let colorants = build_RGB_to_XYZ_transfer_matrix(white_point, primaries);
    375    let colorants = match adapt_matrix_to_D50(colorants, white_point) {
    376        Some(colorants) => colorants,
    377        None => return false,
    378    };
    379 
    380    /* note: there's a transpose type of operation going on here */
    381    profile.redColorant.X = double_to_s15Fixed16Number(colorants.m[0][0] as f64);
    382    profile.redColorant.Y = double_to_s15Fixed16Number(colorants.m[1][0] as f64);
    383    profile.redColorant.Z = double_to_s15Fixed16Number(colorants.m[2][0] as f64);
    384    profile.greenColorant.X = double_to_s15Fixed16Number(colorants.m[0][1] as f64);
    385    profile.greenColorant.Y = double_to_s15Fixed16Number(colorants.m[1][1] as f64);
    386    profile.greenColorant.Z = double_to_s15Fixed16Number(colorants.m[2][1] as f64);
    387    profile.blueColorant.X = double_to_s15Fixed16Number(colorants.m[0][2] as f64);
    388    profile.blueColorant.Y = double_to_s15Fixed16Number(colorants.m[1][2] as f64);
    389    profile.blueColorant.Z = double_to_s15Fixed16Number(colorants.m[2][2] as f64);
    390    true
    391 }
    392 pub(crate) fn get_rgb_colorants(
    393    white_point: qcms_CIE_xyY,
    394    primaries: qcms_CIE_xyYTRIPLE,
    395 ) -> Option<Matrix> {
    396    let colorants = build_RGB_to_XYZ_transfer_matrix(white_point, primaries);
    397    adapt_matrix_to_D50(colorants, white_point)
    398 }
    399 /* Alpha is not corrected.
    400   A rationale for this is found in Alvy Ray's "Should Alpha Be Nonlinear If
    401   RGB Is?" Tech Memo 17 (December 14, 1998).
    402    See: ftp://ftp.alvyray.com/Acrobat/17_Nonln.pdf
    403 */
    404 unsafe extern "C" fn qcms_transform_data_gray_template_lut<I: GrayFormat, F: Format>(
    405    transform: &qcms_transform,
    406    mut src: *const u8,
    407    mut dest: *mut u8,
    408    length: usize,
    409 ) {
    410    let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
    411    let input_gamma_table_gray = transform.input_gamma_table_gray.as_ref().unwrap();
    412 
    413    let mut i: u32 = 0;
    414    while (i as usize) < length {
    415        let device: u8 = *src;
    416        src = src.offset(1);
    417        let mut alpha: u8 = 0xffu8;
    418        if I::has_alpha {
    419            alpha = *src;
    420            src = src.offset(1);
    421        }
    422        let linear: f32 = input_gamma_table_gray[device as usize];
    423 
    424        let out_device_r: f32 = lut_interp_linear(
    425            linear as f64,
    426            &transform.output_gamma_lut_r.as_ref().unwrap(),
    427        );
    428        let out_device_g: f32 = lut_interp_linear(
    429            linear as f64,
    430            &transform.output_gamma_lut_g.as_ref().unwrap(),
    431        );
    432        let out_device_b: f32 = lut_interp_linear(
    433            linear as f64,
    434            &transform.output_gamma_lut_b.as_ref().unwrap(),
    435        );
    436        *dest.add(F::kRIndex) = clamp_u8(out_device_r * 255f32);
    437        *dest.add(F::kGIndex) = clamp_u8(out_device_g * 255f32);
    438        *dest.add(F::kBIndex) = clamp_u8(out_device_b * 255f32);
    439        if F::kAIndex != 0xff {
    440            *dest.add(F::kAIndex) = alpha
    441        }
    442        dest = dest.offset(components as isize);
    443        i += 1
    444    }
    445 }
    446 unsafe fn qcms_transform_data_gray_out_lut(
    447    transform: &qcms_transform,
    448    src: *const u8,
    449    dest: *mut u8,
    450    length: usize,
    451 ) {
    452    qcms_transform_data_gray_template_lut::<Gray, RGB>(transform, src, dest, length);
    453 }
    454 unsafe fn qcms_transform_data_gray_rgba_out_lut(
    455    transform: &qcms_transform,
    456    src: *const u8,
    457    dest: *mut u8,
    458    length: usize,
    459 ) {
    460    qcms_transform_data_gray_template_lut::<Gray, RGBA>(transform, src, dest, length);
    461 }
    462 unsafe fn qcms_transform_data_gray_bgra_out_lut(
    463    transform: &qcms_transform,
    464    src: *const u8,
    465    dest: *mut u8,
    466    length: usize,
    467 ) {
    468    qcms_transform_data_gray_template_lut::<Gray, BGRA>(transform, src, dest, length);
    469 }
    470 unsafe fn qcms_transform_data_graya_rgba_out_lut(
    471    transform: &qcms_transform,
    472    src: *const u8,
    473    dest: *mut u8,
    474    length: usize,
    475 ) {
    476    qcms_transform_data_gray_template_lut::<GrayAlpha, RGBA>(transform, src, dest, length);
    477 }
    478 unsafe fn qcms_transform_data_graya_bgra_out_lut(
    479    transform: &qcms_transform,
    480    src: *const u8,
    481    dest: *mut u8,
    482    length: usize,
    483 ) {
    484    qcms_transform_data_gray_template_lut::<GrayAlpha, BGRA>(transform, src, dest, length);
    485 }
    486 unsafe fn qcms_transform_data_gray_template_precache<I: GrayFormat, F: Format>(
    487    transform: &qcms_transform,
    488    mut src: *const u8,
    489    mut dest: *mut u8,
    490    length: usize,
    491 ) {
    492    let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
    493    let precache_output = transform.precache_output.as_deref().unwrap();
    494    let output_r = &precache_output.lut_r;
    495    let output_g = &precache_output.lut_g;
    496    let output_b = &precache_output.lut_b;
    497 
    498    let input_gamma_table_gray = transform
    499        .input_gamma_table_gray
    500        .as_ref()
    501        .unwrap()
    502        .as_ptr();
    503 
    504    let mut i: u32 = 0;
    505    while (i as usize) < length {
    506        let device: u8 = *src;
    507        src = src.offset(1);
    508        let mut alpha: u8 = 0xffu8;
    509        if I::has_alpha {
    510            alpha  = *src;
    511            src = src.offset(1);
    512        }
    513 
    514        let linear: f32 = *input_gamma_table_gray.offset(device as isize);
    515        /* we could round here... */
    516        let gray: u16 = (linear * PRECACHE_OUTPUT_MAX as f32) as u16;
    517        *dest.add(F::kRIndex) = output_r[gray as usize];
    518        *dest.add(F::kGIndex) = output_g[gray as usize];
    519        *dest.add(F::kBIndex) = output_b[gray as usize];
    520        if F::kAIndex != 0xff {
    521            *dest.add(F::kAIndex) = alpha
    522        }
    523        dest = dest.offset(components as isize);
    524        i += 1
    525    }
    526 }
    527 unsafe fn qcms_transform_data_gray_out_precache(
    528    transform: &qcms_transform,
    529    src: *const u8,
    530    dest: *mut u8,
    531    length: usize,
    532 ) {
    533    qcms_transform_data_gray_template_precache::<Gray, RGB>(transform, src, dest, length);
    534 }
    535 unsafe fn qcms_transform_data_gray_rgba_out_precache(
    536    transform: &qcms_transform,
    537    src: *const u8,
    538    dest: *mut u8,
    539    length: usize,
    540 ) {
    541    qcms_transform_data_gray_template_precache::<Gray, RGBA>(transform, src, dest, length);
    542 }
    543 unsafe fn qcms_transform_data_gray_bgra_out_precache(
    544    transform: &qcms_transform,
    545    src: *const u8,
    546    dest: *mut u8,
    547    length: usize,
    548 ) {
    549    qcms_transform_data_gray_template_precache::<Gray, BGRA>(transform, src, dest, length);
    550 }
    551 unsafe fn qcms_transform_data_graya_rgba_out_precache(
    552    transform: &qcms_transform,
    553    src: *const u8,
    554    dest: *mut u8,
    555    length: usize,
    556 ) {
    557    qcms_transform_data_gray_template_precache::<GrayAlpha, RGBA>(transform, src, dest, length);
    558 }
    559 unsafe fn qcms_transform_data_graya_bgra_out_precache(
    560    transform: &qcms_transform,
    561    src: *const u8,
    562    dest: *mut u8,
    563    length: usize,
    564 ) {
    565    qcms_transform_data_gray_template_precache::<GrayAlpha, BGRA>(transform, src, dest, length);
    566 }
    567 unsafe fn qcms_transform_data_template_lut_precache<F: Format>(
    568    transform: &qcms_transform,
    569    mut src: *const u8,
    570    mut dest: *mut u8,
    571    length: usize,
    572 ) {
    573    let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
    574    let output_table_r = &transform.precache_output.as_deref().unwrap().lut_r;
    575    let output_table_g = &transform.precache_output.as_deref().unwrap().lut_g;
    576    let output_table_b = &transform.precache_output.as_deref().unwrap().lut_b;
    577    let input_gamma_table_r = transform.input_gamma_table_r.as_ref().unwrap().as_ptr();
    578    let input_gamma_table_g = transform.input_gamma_table_g.as_ref().unwrap().as_ptr();
    579    let input_gamma_table_b = transform.input_gamma_table_b.as_ref().unwrap().as_ptr();
    580 
    581    let mat = &transform.matrix;
    582    let mut i: u32 = 0;
    583    while (i as usize) < length {
    584        let device_r: u8 = *src.add(F::kRIndex);
    585        let device_g: u8 = *src.add(F::kGIndex);
    586        let device_b: u8 = *src.add(F::kBIndex);
    587        let mut alpha: u8 = 0;
    588        if F::kAIndex != 0xff {
    589            alpha = *src.add(F::kAIndex)
    590        }
    591        src = src.offset(components as isize);
    592 
    593        let linear_r: f32 = *input_gamma_table_r.offset(device_r as isize);
    594        let linear_g: f32 = *input_gamma_table_g.offset(device_g as isize);
    595        let linear_b: f32 = *input_gamma_table_b.offset(device_b as isize);
    596        let mut out_linear_r = mat[0][0] * linear_r + mat[1][0] * linear_g + mat[2][0] * linear_b;
    597        let mut out_linear_g = mat[0][1] * linear_r + mat[1][1] * linear_g + mat[2][1] * linear_b;
    598        let mut out_linear_b = mat[0][2] * linear_r + mat[1][2] * linear_g + mat[2][2] * linear_b;
    599        out_linear_r = clamp_float(out_linear_r);
    600        out_linear_g = clamp_float(out_linear_g);
    601        out_linear_b = clamp_float(out_linear_b);
    602        /* we could round here... */
    603 
    604        let r: u16 = (out_linear_r * PRECACHE_OUTPUT_MAX as f32) as u16;
    605        let g: u16 = (out_linear_g * PRECACHE_OUTPUT_MAX as f32) as u16;
    606        let b: u16 = (out_linear_b * PRECACHE_OUTPUT_MAX as f32) as u16;
    607        *dest.add(F::kRIndex) = output_table_r[r as usize];
    608        *dest.add(F::kGIndex) = output_table_g[g as usize];
    609        *dest.add(F::kBIndex) = output_table_b[b as usize];
    610        if F::kAIndex != 0xff {
    611            *dest.add(F::kAIndex) = alpha
    612        }
    613        dest = dest.offset(components as isize);
    614        i += 1
    615    }
    616 }
    617 #[no_mangle]
    618 pub unsafe fn qcms_transform_data_rgb_out_lut_precache(
    619    transform: &qcms_transform,
    620    src: *const u8,
    621    dest: *mut u8,
    622    length: usize,
    623 ) {
    624    qcms_transform_data_template_lut_precache::<RGB>(transform, src, dest, length);
    625 }
    626 #[no_mangle]
    627 pub unsafe fn qcms_transform_data_rgba_out_lut_precache(
    628    transform: &qcms_transform,
    629    src: *const u8,
    630    dest: *mut u8,
    631    length: usize,
    632 ) {
    633    qcms_transform_data_template_lut_precache::<RGBA>(transform, src, dest, length);
    634 }
    635 #[no_mangle]
    636 pub unsafe fn qcms_transform_data_bgra_out_lut_precache(
    637    transform: &qcms_transform,
    638    src: *const u8,
    639    dest: *mut u8,
    640    length: usize,
    641 ) {
    642    qcms_transform_data_template_lut_precache::<BGRA>(transform, src, dest, length);
    643 }
    644 // Not used
    645 /*
    646 static void qcms_transform_data_clut(const qcms_transform *transform, const unsigned char *src, unsigned char *dest, size_t length) {
    647    unsigned int i;
    648    int xy_len = 1;
    649    int x_len = transform->grid_size;
    650    int len = x_len * x_len;
    651    const float* r_table = transform->r_clut;
    652    const float* g_table = transform->g_clut;
    653    const float* b_table = transform->b_clut;
    654 
    655    for (i = 0; i < length; i++) {
    656        unsigned char in_r = *src++;
    657        unsigned char in_g = *src++;
    658        unsigned char in_b = *src++;
    659        float linear_r = in_r/255.0f, linear_g=in_g/255.0f, linear_b = in_b/255.0f;
    660 
    661        int x = floorf(linear_r * (transform->grid_size-1));
    662        int y = floorf(linear_g * (transform->grid_size-1));
    663        int z = floorf(linear_b * (transform->grid_size-1));
    664        int x_n = ceilf(linear_r * (transform->grid_size-1));
    665        int y_n = ceilf(linear_g * (transform->grid_size-1));
    666        int z_n = ceilf(linear_b * (transform->grid_size-1));
    667        float x_d = linear_r * (transform->grid_size-1) - x;
    668        float y_d = linear_g * (transform->grid_size-1) - y;
    669        float z_d = linear_b * (transform->grid_size-1) - z;
    670 
    671        float r_x1 = lerp(CLU(r_table,x,y,z), CLU(r_table,x_n,y,z), x_d);
    672        float r_x2 = lerp(CLU(r_table,x,y_n,z), CLU(r_table,x_n,y_n,z), x_d);
    673        float r_y1 = lerp(r_x1, r_x2, y_d);
    674        float r_x3 = lerp(CLU(r_table,x,y,z_n), CLU(r_table,x_n,y,z_n), x_d);
    675        float r_x4 = lerp(CLU(r_table,x,y_n,z_n), CLU(r_table,x_n,y_n,z_n), x_d);
    676        float r_y2 = lerp(r_x3, r_x4, y_d);
    677        float clut_r = lerp(r_y1, r_y2, z_d);
    678 
    679        float g_x1 = lerp(CLU(g_table,x,y,z), CLU(g_table,x_n,y,z), x_d);
    680        float g_x2 = lerp(CLU(g_table,x,y_n,z), CLU(g_table,x_n,y_n,z), x_d);
    681        float g_y1 = lerp(g_x1, g_x2, y_d);
    682        float g_x3 = lerp(CLU(g_table,x,y,z_n), CLU(g_table,x_n,y,z_n), x_d);
    683        float g_x4 = lerp(CLU(g_table,x,y_n,z_n), CLU(g_table,x_n,y_n,z_n), x_d);
    684        float g_y2 = lerp(g_x3, g_x4, y_d);
    685        float clut_g = lerp(g_y1, g_y2, z_d);
    686 
    687        float b_x1 = lerp(CLU(b_table,x,y,z), CLU(b_table,x_n,y,z), x_d);
    688        float b_x2 = lerp(CLU(b_table,x,y_n,z), CLU(b_table,x_n,y_n,z), x_d);
    689        float b_y1 = lerp(b_x1, b_x2, y_d);
    690        float b_x3 = lerp(CLU(b_table,x,y,z_n), CLU(b_table,x_n,y,z_n), x_d);
    691        float b_x4 = lerp(CLU(b_table,x,y_n,z_n), CLU(b_table,x_n,y_n,z_n), x_d);
    692        float b_y2 = lerp(b_x3, b_x4, y_d);
    693        float clut_b = lerp(b_y1, b_y2, z_d);
    694 
    695        *dest++ = clamp_u8(clut_r*255.0f);
    696        *dest++ = clamp_u8(clut_g*255.0f);
    697        *dest++ = clamp_u8(clut_b*255.0f);
    698    }
    699 }
    700 */
    701 fn int_div_ceil(value: i32, div: i32) -> i32 {
    702    (value + div - 1) / div
    703 }
    704 // Using lcms' tetra interpolation algorithm.
    705 unsafe extern "C" fn qcms_transform_data_tetra_clut_template<F: Format>(
    706    transform: &qcms_transform,
    707    mut src: *const u8,
    708    mut dest: *mut u8,
    709    length: usize,
    710 ) {
    711    let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
    712 
    713    let xy_len: i32 = 1;
    714    let x_len: i32 = transform.grid_size as i32;
    715    let len: i32 = x_len * x_len;
    716    let table = transform.clut.as_ref().unwrap().as_ptr();
    717    let r_table: *const f32 = table;
    718    let g_table: *const f32 = table.offset(1);
    719    let b_table: *const f32 = table.offset(2);
    720 
    721    let mut i: u32 = 0;
    722    while (i as usize) < length {
    723        let c0_r: f32;
    724        let c1_r: f32;
    725        let c2_r: f32;
    726        let c3_r: f32;
    727        let c0_g: f32;
    728        let c1_g: f32;
    729        let c2_g: f32;
    730        let c3_g: f32;
    731        let c0_b: f32;
    732        let c1_b: f32;
    733        let c2_b: f32;
    734        let c3_b: f32;
    735        let in_r: u8 = *src.add(F::kRIndex);
    736        let in_g: u8 = *src.add(F::kGIndex);
    737        let in_b: u8 = *src.add(F::kBIndex);
    738        let mut in_a: u8 = 0;
    739        if F::kAIndex != 0xff {
    740            in_a = *src.add(F::kAIndex)
    741        }
    742        src = src.offset(components as isize);
    743        let linear_r: f32 = in_r as i32 as f32 / 255.0;
    744        let linear_g: f32 = in_g as i32 as f32 / 255.0;
    745        let linear_b: f32 = in_b as i32 as f32 / 255.0;
    746        let x: i32 = in_r as i32 * (transform.grid_size as i32 - 1) / 255;
    747        let y: i32 = in_g as i32 * (transform.grid_size as i32 - 1) / 255;
    748        let z: i32 = in_b as i32 * (transform.grid_size as i32 - 1) / 255;
    749        let x_n: i32 = int_div_ceil(in_r as i32 * (transform.grid_size as i32 - 1), 255);
    750        let y_n: i32 = int_div_ceil(in_g as i32 * (transform.grid_size as i32 - 1), 255);
    751        let z_n: i32 = int_div_ceil(in_b as i32 * (transform.grid_size as i32 - 1), 255);
    752        let rx: f32 = linear_r * (transform.grid_size as i32 - 1) as f32 - x as f32;
    753        let ry: f32 = linear_g * (transform.grid_size as i32 - 1) as f32 - y as f32;
    754        let rz: f32 = linear_b * (transform.grid_size as i32 - 1) as f32 - z as f32;
    755        let CLU = |table: *const f32, x, y, z| {
    756            *table.offset(((x * len + y * x_len + z * xy_len) * 3) as isize)
    757        };
    758 
    759        c0_r = CLU(r_table, x, y, z);
    760        c0_g = CLU(g_table, x, y, z);
    761        c0_b = CLU(b_table, x, y, z);
    762        if rx >= ry {
    763            if ry >= rz {
    764                //rx >= ry && ry >= rz
    765                c1_r = CLU(r_table, x_n, y, z) - c0_r;
    766                c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z);
    767                c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
    768                c1_g = CLU(g_table, x_n, y, z) - c0_g;
    769                c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z);
    770                c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
    771                c1_b = CLU(b_table, x_n, y, z) - c0_b;
    772                c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z);
    773                c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
    774            } else if rx >= rz {
    775                //rx >= rz && rz >= ry
    776                c1_r = CLU(r_table, x_n, y, z) - c0_r;
    777                c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
    778                c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z);
    779                c1_g = CLU(g_table, x_n, y, z) - c0_g;
    780                c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
    781                c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z);
    782                c1_b = CLU(b_table, x_n, y, z) - c0_b;
    783                c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
    784                c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z);
    785            } else {
    786                //rz > rx && rx >= ry
    787                c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n);
    788                c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
    789                c3_r = CLU(r_table, x, y, z_n) - c0_r;
    790                c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n);
    791                c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
    792                c3_g = CLU(g_table, x, y, z_n) - c0_g;
    793                c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n);
    794                c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
    795                c3_b = CLU(b_table, x, y, z_n) - c0_b;
    796            }
    797        } else if rx >= rz {
    798            //ry > rx && rx >= rz
    799            c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z);
    800            c2_r = CLU(r_table, x, y_n, z) - c0_r;
    801            c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
    802            c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z);
    803            c2_g = CLU(g_table, x, y_n, z) - c0_g;
    804            c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
    805            c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z);
    806            c2_b = CLU(b_table, x, y_n, z) - c0_b;
    807            c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
    808        } else if ry >= rz {
    809            //ry >= rz && rz > rx
    810            c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
    811            c2_r = CLU(r_table, x, y_n, z) - c0_r;
    812            c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z);
    813            c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
    814            c2_g = CLU(g_table, x, y_n, z) - c0_g;
    815            c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z);
    816            c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
    817            c2_b = CLU(b_table, x, y_n, z) - c0_b;
    818            c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z);
    819        } else {
    820            //rz > ry && ry > rx
    821            c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
    822            c2_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y, z_n);
    823            c3_r = CLU(r_table, x, y, z_n) - c0_r;
    824            c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
    825            c2_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y, z_n);
    826            c3_g = CLU(g_table, x, y, z_n) - c0_g;
    827            c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
    828            c2_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y, z_n);
    829            c3_b = CLU(b_table, x, y, z_n) - c0_b;
    830        }
    831        let clut_r = c0_r + c1_r * rx + c2_r * ry + c3_r * rz;
    832        let clut_g = c0_g + c1_g * rx + c2_g * ry + c3_g * rz;
    833        let clut_b = c0_b + c1_b * rx + c2_b * ry + c3_b * rz;
    834        *dest.add(F::kRIndex) = clamp_u8(clut_r * 255.0);
    835        *dest.add(F::kGIndex) = clamp_u8(clut_g * 255.0);
    836        *dest.add(F::kBIndex) = clamp_u8(clut_b * 255.0);
    837        if F::kAIndex != 0xff {
    838            *dest.add(F::kAIndex) = in_a
    839        }
    840        dest = dest.offset(components as isize);
    841        i += 1
    842    }
    843 }
    844 
    845 unsafe fn tetra(
    846    transform: &qcms_transform,
    847    table: *const f32,
    848    in_r: u8,
    849    in_g: u8,
    850    in_b: u8,
    851 ) -> (f32, f32, f32) {
    852    let r_table: *const f32 = table;
    853    let g_table: *const f32 = table.offset(1);
    854    let b_table: *const f32 = table.offset(2);
    855    let linear_r: f32 = in_r as i32 as f32 / 255.0;
    856    let linear_g: f32 = in_g as i32 as f32 / 255.0;
    857    let linear_b: f32 = in_b as i32 as f32 / 255.0;
    858    let xy_len: i32 = 1;
    859    let x_len: i32 = transform.grid_size as i32;
    860    let len: i32 = x_len * x_len;
    861    let x: i32 = in_r as i32 * (transform.grid_size as i32 - 1) / 255;
    862    let y: i32 = in_g as i32 * (transform.grid_size as i32 - 1) / 255;
    863    let z: i32 = in_b as i32 * (transform.grid_size as i32 - 1) / 255;
    864    let x_n: i32 = int_div_ceil(in_r as i32 * (transform.grid_size as i32 - 1), 255);
    865    let y_n: i32 = int_div_ceil(in_g as i32 * (transform.grid_size as i32 - 1), 255);
    866    let z_n: i32 = int_div_ceil(in_b as i32 * (transform.grid_size as i32 - 1), 255);
    867    let rx: f32 = linear_r * (transform.grid_size as i32 - 1) as f32 - x as f32;
    868    let ry: f32 = linear_g * (transform.grid_size as i32 - 1) as f32 - y as f32;
    869    let rz: f32 = linear_b * (transform.grid_size as i32 - 1) as f32 - z as f32;
    870    let CLU = |table: *const f32, x, y, z| {
    871        *table.offset(((x * len + y * x_len + z * xy_len) * 3) as isize)
    872    };
    873    let c0_r: f32;
    874    let c1_r: f32;
    875    let c2_r: f32;
    876    let c3_r: f32;
    877    let c0_g: f32;
    878    let c1_g: f32;
    879    let c2_g: f32;
    880    let c3_g: f32;
    881    let c0_b: f32;
    882    let c1_b: f32;
    883    let c2_b: f32;
    884    let c3_b: f32;
    885    c0_r = CLU(r_table, x, y, z);
    886    c0_g = CLU(g_table, x, y, z);
    887    c0_b = CLU(b_table, x, y, z);
    888    if rx >= ry {
    889        if ry >= rz {
    890            //rx >= ry && ry >= rz
    891            c1_r = CLU(r_table, x_n, y, z) - c0_r;
    892            c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z);
    893            c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
    894            c1_g = CLU(g_table, x_n, y, z) - c0_g;
    895            c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z);
    896            c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
    897            c1_b = CLU(b_table, x_n, y, z) - c0_b;
    898            c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z);
    899            c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
    900        } else if rx >= rz {
    901            //rx >= rz && rz >= ry
    902            c1_r = CLU(r_table, x_n, y, z) - c0_r;
    903            c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
    904            c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z);
    905            c1_g = CLU(g_table, x_n, y, z) - c0_g;
    906            c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
    907            c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z);
    908            c1_b = CLU(b_table, x_n, y, z) - c0_b;
    909            c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
    910            c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z);
    911        } else {
    912            //rz > rx && rx >= ry
    913            c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n);
    914            c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
    915            c3_r = CLU(r_table, x, y, z_n) - c0_r;
    916            c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n);
    917            c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
    918            c3_g = CLU(g_table, x, y, z_n) - c0_g;
    919            c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n);
    920            c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
    921            c3_b = CLU(b_table, x, y, z_n) - c0_b;
    922        }
    923    } else if rx >= rz {
    924        //ry > rx && rx >= rz
    925        c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z);
    926        c2_r = CLU(r_table, x, y_n, z) - c0_r;
    927        c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
    928        c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z);
    929        c2_g = CLU(g_table, x, y_n, z) - c0_g;
    930        c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
    931        c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z);
    932        c2_b = CLU(b_table, x, y_n, z) - c0_b;
    933        c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
    934    } else if ry >= rz {
    935        //ry >= rz && rz > rx
    936        c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
    937        c2_r = CLU(r_table, x, y_n, z) - c0_r;
    938        c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z);
    939        c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
    940        c2_g = CLU(g_table, x, y_n, z) - c0_g;
    941        c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z);
    942        c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
    943        c2_b = CLU(b_table, x, y_n, z) - c0_b;
    944        c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z);
    945    } else {
    946        //rz > ry && ry > rx
    947        c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
    948        c2_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y, z_n);
    949        c3_r = CLU(r_table, x, y, z_n) - c0_r;
    950        c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
    951        c2_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y, z_n);
    952        c3_g = CLU(g_table, x, y, z_n) - c0_g;
    953        c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
    954        c2_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y, z_n);
    955        c3_b = CLU(b_table, x, y, z_n) - c0_b;
    956    }
    957    let clut_r = c0_r + c1_r * rx + c2_r * ry + c3_r * rz;
    958    let clut_g = c0_g + c1_g * rx + c2_g * ry + c3_g * rz;
    959    let clut_b = c0_b + c1_b * rx + c2_b * ry + c3_b * rz;
    960    (clut_r, clut_g, clut_b)
    961 }
    962 
    963 #[inline]
    964 fn lerp(a: f32, b: f32, t: f32) -> f32 {
    965    a * (1. - t) + b * t
    966 }
    967 
    968 // lerp between two tetrahedral interpolations
    969 // See lcms:Eval4InputsFloat
    970 #[allow(clippy::many_single_char_names)]
    971 unsafe fn qcms_transform_data_tetra_clut_cmyk(
    972    transform: &qcms_transform,
    973    mut src: *const u8,
    974    mut dest: *mut u8,
    975    length: usize,
    976 ) {
    977    let table = transform.clut.as_ref().unwrap().as_ptr();
    978    assert!(
    979        transform.clut.as_ref().unwrap().len()
    980            >= ((transform.grid_size as i32).pow(4) * 3) as usize
    981    );
    982    for _ in 0..length {
    983        let c: u8 = *src.add(0);
    984        let m: u8 = *src.add(1);
    985        let y: u8 = *src.add(2);
    986        let k: u8 = *src.add(3);
    987        src = src.offset(4);
    988        let linear_k: f32 = k as i32 as f32 / 255.0;
    989        let grid_size = transform.grid_size as i32;
    990        let w: i32 = k as i32 * (transform.grid_size as i32 - 1) / 255;
    991        let w_n: i32 = int_div_ceil(k as i32 * (transform.grid_size as i32 - 1), 255);
    992        let t: f32 = linear_k * (transform.grid_size as i32 - 1) as f32 - w as f32;
    993 
    994        let table1 = table.offset((w * grid_size * grid_size * grid_size * 3) as isize);
    995        let table2 = table.offset((w_n * grid_size * grid_size * grid_size * 3) as isize);
    996 
    997        let (r1, g1, b1) = tetra(transform, table1, c, m, y);
    998        let (r2, g2, b2) = tetra(transform, table2, c, m, y);
    999        let r = lerp(r1, r2, t);
   1000        let g = lerp(g1, g2, t);
   1001        let b = lerp(b1, b2, t);
   1002        *dest.add(0) = clamp_u8(r * 255.0);
   1003        *dest.add(1) = clamp_u8(g * 255.0);
   1004        *dest.add(2) = clamp_u8(b * 255.0);
   1005        dest = dest.offset(3);
   1006    }
   1007 }
   1008 
   1009 unsafe fn qcms_transform_data_tetra_clut_rgb(
   1010    transform: &qcms_transform,
   1011    src: *const u8,
   1012    dest: *mut u8,
   1013    length: usize,
   1014 ) {
   1015    qcms_transform_data_tetra_clut_template::<RGB>(transform, src, dest, length);
   1016 }
   1017 unsafe fn qcms_transform_data_tetra_clut_rgba(
   1018    transform: &qcms_transform,
   1019    src: *const u8,
   1020    dest: *mut u8,
   1021    length: usize,
   1022 ) {
   1023    qcms_transform_data_tetra_clut_template::<RGBA>(transform, src, dest, length);
   1024 }
   1025 unsafe fn qcms_transform_data_tetra_clut_bgra(
   1026    transform: &qcms_transform,
   1027    src: *const u8,
   1028    dest: *mut u8,
   1029    length: usize,
   1030 ) {
   1031    qcms_transform_data_tetra_clut_template::<BGRA>(transform, src, dest, length);
   1032 }
   1033 unsafe fn qcms_transform_data_template_lut<F: Format>(
   1034    transform: &qcms_transform,
   1035    mut src: *const u8,
   1036    mut dest: *mut u8,
   1037    length: usize,
   1038 ) {
   1039    let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
   1040 
   1041    let mat = &transform.matrix;
   1042    let mut i: u32 = 0;
   1043    let input_gamma_table_r = transform.input_gamma_table_r.as_ref().unwrap().as_ptr();
   1044    let input_gamma_table_g = transform.input_gamma_table_g.as_ref().unwrap().as_ptr();
   1045    let input_gamma_table_b = transform.input_gamma_table_b.as_ref().unwrap().as_ptr();
   1046    while (i as usize) < length {
   1047        let device_r: u8 = *src.add(F::kRIndex);
   1048        let device_g: u8 = *src.add(F::kGIndex);
   1049        let device_b: u8 = *src.add(F::kBIndex);
   1050        let mut alpha: u8 = 0;
   1051        if F::kAIndex != 0xff {
   1052            alpha = *src.add(F::kAIndex)
   1053        }
   1054        src = src.offset(components as isize);
   1055 
   1056        let linear_r: f32 = *input_gamma_table_r.offset(device_r as isize);
   1057        let linear_g: f32 = *input_gamma_table_g.offset(device_g as isize);
   1058        let linear_b: f32 = *input_gamma_table_b.offset(device_b as isize);
   1059        let mut out_linear_r = mat[0][0] * linear_r + mat[1][0] * linear_g + mat[2][0] * linear_b;
   1060        let mut out_linear_g = mat[0][1] * linear_r + mat[1][1] * linear_g + mat[2][1] * linear_b;
   1061        let mut out_linear_b = mat[0][2] * linear_r + mat[1][2] * linear_g + mat[2][2] * linear_b;
   1062        out_linear_r = clamp_float(out_linear_r);
   1063        out_linear_g = clamp_float(out_linear_g);
   1064        out_linear_b = clamp_float(out_linear_b);
   1065 
   1066        let out_device_r: f32 = lut_interp_linear(
   1067            out_linear_r as f64,
   1068            &transform.output_gamma_lut_r.as_ref().unwrap(),
   1069        );
   1070        let out_device_g: f32 = lut_interp_linear(
   1071            out_linear_g as f64,
   1072            transform.output_gamma_lut_g.as_ref().unwrap(),
   1073        );
   1074        let out_device_b: f32 = lut_interp_linear(
   1075            out_linear_b as f64,
   1076            transform.output_gamma_lut_b.as_ref().unwrap(),
   1077        );
   1078        *dest.add(F::kRIndex) = clamp_u8(out_device_r * 255f32);
   1079        *dest.add(F::kGIndex) = clamp_u8(out_device_g * 255f32);
   1080        *dest.add(F::kBIndex) = clamp_u8(out_device_b * 255f32);
   1081        if F::kAIndex != 0xff {
   1082            *dest.add(F::kAIndex) = alpha
   1083        }
   1084        dest = dest.offset(components as isize);
   1085        i += 1
   1086    }
   1087 }
   1088 #[no_mangle]
   1089 pub unsafe fn qcms_transform_data_rgb_out_lut(
   1090    transform: &qcms_transform,
   1091    src: *const u8,
   1092    dest: *mut u8,
   1093    length: usize,
   1094 ) {
   1095    qcms_transform_data_template_lut::<RGB>(transform, src, dest, length);
   1096 }
   1097 #[no_mangle]
   1098 pub unsafe fn qcms_transform_data_rgba_out_lut(
   1099    transform: &qcms_transform,
   1100    src: *const u8,
   1101    dest: *mut u8,
   1102    length: usize,
   1103 ) {
   1104    qcms_transform_data_template_lut::<RGBA>(transform, src, dest, length);
   1105 }
   1106 #[no_mangle]
   1107 pub unsafe fn qcms_transform_data_bgra_out_lut(
   1108    transform: &qcms_transform,
   1109    src: *const u8,
   1110    dest: *mut u8,
   1111    length: usize,
   1112 ) {
   1113    qcms_transform_data_template_lut::<BGRA>(transform, src, dest, length);
   1114 }
   1115 
   1116 fn precache_create() -> Arc<PrecacheOuput> {
   1117    Arc::new(PrecacheOuput::default())
   1118 }
   1119 
   1120 #[no_mangle]
   1121 pub unsafe extern "C" fn qcms_transform_release(t: *mut qcms_transform) {
   1122    drop(Box::from_raw(t));
   1123 }
   1124 
   1125 const bradford_matrix: Matrix = Matrix {
   1126    m: [
   1127        [0.8951, 0.2664, -0.1614],
   1128        [-0.7502, 1.7135, 0.0367],
   1129        [0.0389, -0.0685, 1.0296],
   1130    ],
   1131 };
   1132 
   1133 const bradford_matrix_inv: Matrix = Matrix {
   1134    m: [
   1135        [0.9869929, -0.1470543, 0.1599627],
   1136        [0.4323053, 0.5183603, 0.0492912],
   1137        [-0.0085287, 0.0400428, 0.9684867],
   1138    ],
   1139 };
   1140 
   1141 // See ICCv4 E.3
   1142 fn compute_whitepoint_adaption(X: f32, Y: f32, Z: f32) -> Matrix {
   1143    let p: f32 = (0.96422 * bradford_matrix.m[0][0]
   1144        + 1.000 * bradford_matrix.m[1][0]
   1145        + 0.82521 * bradford_matrix.m[2][0])
   1146        / (X * bradford_matrix.m[0][0] + Y * bradford_matrix.m[1][0] + Z * bradford_matrix.m[2][0]);
   1147    let y: f32 = (0.96422 * bradford_matrix.m[0][1]
   1148        + 1.000 * bradford_matrix.m[1][1]
   1149        + 0.82521 * bradford_matrix.m[2][1])
   1150        / (X * bradford_matrix.m[0][1] + Y * bradford_matrix.m[1][1] + Z * bradford_matrix.m[2][1]);
   1151    let b: f32 = (0.96422 * bradford_matrix.m[0][2]
   1152        + 1.000 * bradford_matrix.m[1][2]
   1153        + 0.82521 * bradford_matrix.m[2][2])
   1154        / (X * bradford_matrix.m[0][2] + Y * bradford_matrix.m[1][2] + Z * bradford_matrix.m[2][2]);
   1155    let white_adaption = Matrix {
   1156        m: [[p, 0., 0.], [0., y, 0.], [0., 0., b]],
   1157    };
   1158    Matrix::multiply(
   1159        bradford_matrix_inv,
   1160        Matrix::multiply(white_adaption, bradford_matrix),
   1161    )
   1162 }
   1163 #[no_mangle]
   1164 pub extern "C" fn qcms_profile_precache_output_transform(profile: &mut Profile) {
   1165    /* we only support precaching on rgb profiles */
   1166    if profile.color_space != RGB_SIGNATURE {
   1167        return;
   1168    }
   1169    if SUPPORTS_ICCV4.load(Ordering::Relaxed) {
   1170        /* don't precache since we will use the B2A LUT */
   1171        if profile.B2A0.is_some() {
   1172            return;
   1173        }
   1174        /* don't precache since we will use the mBA LUT */
   1175        if profile.mBA.is_some() {
   1176            return;
   1177        }
   1178    }
   1179    /* don't precache if we do not have the TRC curves */
   1180    if profile.redTRC.is_none() || profile.greenTRC.is_none() || profile.blueTRC.is_none() {
   1181        return;
   1182    }
   1183    if profile.precache_output.is_none() {
   1184        let mut precache = precache_create();
   1185        compute_precache(
   1186            profile.redTRC.as_deref().unwrap(),
   1187            &mut Arc::get_mut(&mut precache).unwrap().lut_r,
   1188        );
   1189        compute_precache(
   1190            profile.greenTRC.as_deref().unwrap(),
   1191            &mut Arc::get_mut(&mut precache).unwrap().lut_g,
   1192        );
   1193        compute_precache(
   1194            profile.blueTRC.as_deref().unwrap(),
   1195            &mut Arc::get_mut(&mut precache).unwrap().lut_b,
   1196        );
   1197        profile.precache_output = Some(precache);
   1198    }
   1199 }
   1200 /* Replace the current transformation with a LUT transformation using a given number of sample points */
   1201 fn transform_precacheLUT_float(
   1202    mut transform: Box<qcms_transform>,
   1203    input: &Profile,
   1204    output: &Profile,
   1205    samples: i32,
   1206    in_type: DataType,
   1207 ) -> Option<Box<qcms_transform>> {
   1208    /* The range between which 2 consecutive sample points can be used to interpolate */
   1209    let lutSize: u32 = (3 * samples * samples * samples) as u32;
   1210 
   1211    let mut src = Vec::with_capacity(lutSize as usize);
   1212    let dest = vec![0.; lutSize as usize];
   1213    /* Prepare a list of points we want to sample */
   1214    for x in 0..samples {
   1215        for y in 0..samples {
   1216            for z in 0..samples {
   1217                src.push(x as f32 / (samples - 1) as f32);
   1218                src.push(y as f32 / (samples - 1) as f32);
   1219                src.push(z as f32 / (samples - 1) as f32);
   1220            }
   1221        }
   1222    }
   1223    let lut = chain_transform(input, output, src, dest, lutSize as usize);
   1224    if let Some(lut) = lut {
   1225        transform.clut = Some(lut);
   1226        transform.grid_size = samples as u16;
   1227        if in_type == RGBA8 {
   1228            transform.transform_fn = Some(qcms_transform_data_tetra_clut_rgba)
   1229        } else if in_type == BGRA8 {
   1230            transform.transform_fn = Some(qcms_transform_data_tetra_clut_bgra)
   1231        } else if in_type == RGB8 {
   1232            transform.transform_fn = Some(qcms_transform_data_tetra_clut_rgb)
   1233        }
   1234        debug_assert!(transform.transform_fn.is_some());
   1235    } else {
   1236        return None;
   1237    }
   1238 
   1239    Some(transform)
   1240 }
   1241 
   1242 fn transform_precacheLUT_cmyk_float(
   1243    mut transform: Box<qcms_transform>,
   1244    input: &Profile,
   1245    output: &Profile,
   1246    samples: i32,
   1247    in_type: DataType,
   1248 ) -> Option<Box<qcms_transform>> {
   1249    /* The range between which 2 consecutive sample points can be used to interpolate */
   1250    let lutSize: u32 = (4 * samples * samples * samples * samples) as u32;
   1251 
   1252    let mut src = Vec::with_capacity(lutSize as usize);
   1253    let dest = vec![0.; lutSize as usize];
   1254    /* Prepare a list of points we want to sample */
   1255    for k in 0..samples {
   1256        for c in 0..samples {
   1257            for m in 0..samples {
   1258                for y in 0..samples {
   1259                    src.push(c as f32 / (samples - 1) as f32);
   1260                    src.push(m as f32 / (samples - 1) as f32);
   1261                    src.push(y as f32 / (samples - 1) as f32);
   1262                    src.push(k as f32 / (samples - 1) as f32);
   1263                }
   1264            }
   1265        }
   1266    }
   1267    let lut = chain_transform(input, output, src, dest, lutSize as usize);
   1268    if let Some(lut) = lut {
   1269        transform.clut = Some(lut);
   1270        transform.grid_size = samples as u16;
   1271        assert!(in_type == DataType::CMYK);
   1272        transform.transform_fn = Some(qcms_transform_data_tetra_clut_cmyk)
   1273    } else {
   1274        return None;
   1275    }
   1276 
   1277    Some(transform)
   1278 }
   1279 
   1280 pub fn transform_create(
   1281    input: &Profile,
   1282    in_type: DataType,
   1283    output: &Profile,
   1284    out_type: DataType,
   1285    _intent: Intent,
   1286 ) -> Option<Box<qcms_transform>> {
   1287    // Ensure the requested input and output types make sense.
   1288    let matching_format = match (in_type, out_type) {
   1289        (RGB8, RGB8) => true,
   1290        (RGBA8, RGBA8) => true,
   1291        (BGRA8, BGRA8) => true,
   1292        (Gray8, out_type) => matches!(out_type, RGB8 | RGBA8 | BGRA8),
   1293        (GrayA8, out_type) => matches!(out_type, RGBA8 | BGRA8),
   1294        (CMYK, RGB8) => true,
   1295        _ => false,
   1296    };
   1297    if !matching_format {
   1298        debug_assert!(false, "input/output type");
   1299        return None;
   1300    }
   1301    let mut transform: Box<qcms_transform> = Box::new(Default::default());
   1302    let mut precache: bool = false;
   1303    if output.precache_output.is_some() {
   1304        precache = true
   1305    }
   1306    // This precache assumes RGB_SIGNATURE (fails on GRAY_SIGNATURE, for instance)
   1307    if SUPPORTS_ICCV4.load(Ordering::Relaxed)
   1308        && (in_type == RGB8 || in_type == RGBA8 || in_type == BGRA8 || in_type == CMYK)
   1309        && (input.A2B0.is_some()
   1310            || output.B2A0.is_some()
   1311            || input.mAB.is_some()
   1312            || output.mAB.is_some())
   1313    {
   1314        if in_type == CMYK {
   1315            return transform_precacheLUT_cmyk_float(transform, input, output, 17, in_type);
   1316        }
   1317        // Precache the transformation to a CLUT 33x33x33 in size.
   1318        // 33 is used by many profiles and works well in pratice.
   1319        // This evenly divides 256 into blocks of 8x8x8.
   1320        // TODO For transforming small data sets of about 200x200 or less
   1321        // precaching should be avoided.
   1322        let result = transform_precacheLUT_float(transform, input, output, 33, in_type);
   1323        debug_assert!(result.is_some(), "precacheLUT failed");
   1324        return result;
   1325    }
   1326    if precache {
   1327        transform.precache_output = Some(Arc::clone(output.precache_output.as_ref().unwrap()));
   1328    } else {
   1329        if output.redTRC.is_none() || output.greenTRC.is_none() || output.blueTRC.is_none() {
   1330            return None;
   1331        }
   1332        transform.output_gamma_lut_r = build_output_lut(output.redTRC.as_deref().unwrap());
   1333        transform.output_gamma_lut_g = build_output_lut(output.greenTRC.as_deref().unwrap());
   1334        transform.output_gamma_lut_b = build_output_lut(output.blueTRC.as_deref().unwrap());
   1335 
   1336        if transform.output_gamma_lut_r.is_none()
   1337            || transform.output_gamma_lut_g.is_none()
   1338            || transform.output_gamma_lut_b.is_none()
   1339        {
   1340            return None;
   1341        }
   1342    }
   1343    if input.color_space == RGB_SIGNATURE {
   1344        if precache {
   1345            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
   1346            if is_x86_feature_detected!("avx") {
   1347                if in_type == RGB8 {
   1348                    transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_avx)
   1349                } else if in_type == RGBA8 {
   1350                    transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_avx)
   1351                } else if in_type == BGRA8 {
   1352                    transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_avx)
   1353                }
   1354            } else if cfg!(not(miri)) && is_x86_feature_detected!("sse2") {
   1355                if in_type == RGB8 {
   1356                    transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_sse2)
   1357                } else if in_type == RGBA8 {
   1358                    transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_sse2)
   1359                } else if in_type == BGRA8 {
   1360                    transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_sse2)
   1361                }
   1362            }
   1363 
   1364            #[cfg(all(target_arch = "arm", feature = "neon"))]
   1365            let neon_supported = is_arm_feature_detected!("neon");
   1366            #[cfg(all(target_arch = "aarch64", feature = "neon"))]
   1367            let neon_supported = is_aarch64_feature_detected!("neon");
   1368 
   1369            #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "neon"))]
   1370            if neon_supported {
   1371                if in_type == RGB8 {
   1372                    transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_neon)
   1373                } else if in_type == RGBA8 {
   1374                    transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_neon)
   1375                } else if in_type == BGRA8 {
   1376                    transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_neon)
   1377                }
   1378            }
   1379 
   1380            if transform.transform_fn.is_none() {
   1381                if in_type == RGB8 {
   1382                    transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_precache)
   1383                } else if in_type == RGBA8 {
   1384                    transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_precache)
   1385                } else if in_type == BGRA8 {
   1386                    transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_precache)
   1387                }
   1388            }
   1389        } else if in_type == RGB8 {
   1390            transform.transform_fn = Some(qcms_transform_data_rgb_out_lut)
   1391        } else if in_type == RGBA8 {
   1392            transform.transform_fn = Some(qcms_transform_data_rgba_out_lut)
   1393        } else if in_type == BGRA8 {
   1394            transform.transform_fn = Some(qcms_transform_data_bgra_out_lut)
   1395        }
   1396        //XXX: avoid duplicating tables if we can
   1397        transform.input_gamma_table_r = Some(Box::new(build_input_gamma_table(input.redTRC.as_deref()?)));
   1398        transform.input_gamma_table_g = Some(Box::new(build_input_gamma_table(input.greenTRC.as_deref()?)));
   1399        transform.input_gamma_table_b = Some(Box::new(build_input_gamma_table(input.blueTRC.as_deref()?)));
   1400 
   1401        /* build combined colorant matrix */
   1402 
   1403        let in_matrix = build_colorant_matrix(input);
   1404        let mut out_matrix = build_colorant_matrix(output);
   1405        out_matrix = out_matrix.invert()?;
   1406 
   1407        let result_0 = Matrix::multiply(out_matrix, in_matrix);
   1408        /* check for NaN values in the matrix and bail if we find any */
   1409        let mut i: u32 = 0;
   1410        while i < 3 {
   1411            let mut j: u32 = 0;
   1412            while j < 3 {
   1413                #[allow(clippy::eq_op, clippy::float_cmp)]
   1414                if result_0.m[i as usize][j as usize].is_nan() {
   1415                    return None;
   1416                }
   1417                j += 1
   1418            }
   1419            i += 1
   1420        }
   1421        /* store the results in column major mode
   1422         * this makes doing the multiplication with sse easier */
   1423        transform.matrix[0][0] = result_0.m[0][0];
   1424        transform.matrix[1][0] = result_0.m[0][1];
   1425        transform.matrix[2][0] = result_0.m[0][2];
   1426        transform.matrix[0][1] = result_0.m[1][0];
   1427        transform.matrix[1][1] = result_0.m[1][1];
   1428        transform.matrix[2][1] = result_0.m[1][2];
   1429        transform.matrix[0][2] = result_0.m[2][0];
   1430        transform.matrix[1][2] = result_0.m[2][1];
   1431        transform.matrix[2][2] = result_0.m[2][2]
   1432    } else if input.color_space == GRAY_SIGNATURE {
   1433        transform.input_gamma_table_gray = Some(Box::new(build_input_gamma_table(input.grayTRC.as_deref()?)));
   1434        if precache {
   1435            if out_type == RGB8 {
   1436                transform.transform_fn = Some(qcms_transform_data_gray_out_precache)
   1437            } else if out_type == RGBA8 {
   1438                if in_type == Gray8 {
   1439                    transform.transform_fn = Some(qcms_transform_data_gray_rgba_out_precache)
   1440                } else {
   1441                    transform.transform_fn = Some(qcms_transform_data_graya_rgba_out_precache)
   1442                }
   1443            } else if out_type == BGRA8 {
   1444                if in_type == Gray8 {
   1445                    transform.transform_fn = Some(qcms_transform_data_gray_bgra_out_precache)
   1446                } else {
   1447                    transform.transform_fn = Some(qcms_transform_data_graya_bgra_out_precache)
   1448                }
   1449            }
   1450        } else if out_type == RGB8 {
   1451            transform.transform_fn = Some(qcms_transform_data_gray_out_lut)
   1452        } else if out_type == RGBA8 {
   1453            if in_type == Gray8 {
   1454                transform.transform_fn = Some(qcms_transform_data_gray_rgba_out_lut)
   1455            } else {
   1456                transform.transform_fn = Some(qcms_transform_data_graya_rgba_out_lut)
   1457            }
   1458        } else if out_type == BGRA8 {
   1459            if in_type == Gray8 {
   1460                transform.transform_fn = Some(qcms_transform_data_gray_bgra_out_lut)
   1461            } else {
   1462                transform.transform_fn = Some(qcms_transform_data_graya_bgra_out_lut)
   1463            }
   1464        }
   1465    } else {
   1466        debug_assert!(false, "unexpected colorspace");
   1467        return None;
   1468    }
   1469    debug_assert!(transform.transform_fn.is_some());
   1470    Some(transform)
   1471 }
   1472 /// A transform from an input profile to an output one.
   1473 pub struct Transform {
   1474    src_ty: DataType,
   1475    dst_ty: DataType,
   1476    xfm: Box<qcms_transform>,
   1477 }
   1478 
   1479 impl Transform {
   1480    /// Create a new transform from `input` to `output` for pixels of `DataType` `ty` with `intent`
   1481    pub fn new(input: &Profile, output: &Profile, ty: DataType, intent: Intent) -> Option<Self> {
   1482        transform_create(input, ty, output, ty, intent).map(|xfm| Transform {
   1483            src_ty: ty,
   1484            dst_ty: ty,
   1485            xfm,
   1486        })
   1487    }
   1488 
   1489    /// Create a new transform from `input` to `output` for pixels of `DataType` `ty` with `intent`
   1490    pub fn new_to(
   1491        input: &Profile,
   1492        output: &Profile,
   1493        src_ty: DataType,
   1494        dst_ty: DataType,
   1495        intent: Intent,
   1496    ) -> Option<Self> {
   1497        transform_create(input, src_ty, output, dst_ty, intent).map(|xfm| Transform {
   1498            src_ty,
   1499            dst_ty,
   1500            xfm,
   1501        })
   1502    }
   1503 
   1504    /// Apply the color space transform to `data`
   1505    pub fn apply(&self, data: &mut [u8]) {
   1506        if data.len() % self.src_ty.bytes_per_pixel() != 0 {
   1507            panic!(
   1508                "incomplete pixels: should be a multiple of {} got {}",
   1509                self.src_ty.bytes_per_pixel(),
   1510                data.len()
   1511            )
   1512        }
   1513        unsafe {
   1514            self.xfm.transform_fn.expect("non-null function pointer")(
   1515                &*self.xfm,
   1516                data.as_ptr(),
   1517                data.as_mut_ptr(),
   1518                data.len() / self.src_ty.bytes_per_pixel(),
   1519            );
   1520        }
   1521    }
   1522 
   1523    /// Apply the color space transform to `data`
   1524    pub fn convert(&self, src: &[u8], dst: &mut [u8]) {
   1525        if src.len() % self.src_ty.bytes_per_pixel() != 0 {
   1526            panic!(
   1527                "incomplete pixels: should be a multiple of {} got {}",
   1528                self.src_ty.bytes_per_pixel(),
   1529                src.len()
   1530            )
   1531        }
   1532        if dst.len() % self.dst_ty.bytes_per_pixel() != 0 {
   1533            panic!(
   1534                "incomplete pixels: should be a multiple of {} got {}",
   1535                self.dst_ty.bytes_per_pixel(),
   1536                dst.len()
   1537            )
   1538        }
   1539        assert_eq!(
   1540            src.len() / self.src_ty.bytes_per_pixel(),
   1541            dst.len() / self.dst_ty.bytes_per_pixel()
   1542        );
   1543        unsafe {
   1544            self.xfm.transform_fn.expect("non-null function pointer")(
   1545                &*self.xfm,
   1546                src.as_ptr(),
   1547                dst.as_mut_ptr(),
   1548                src.len() / self.src_ty.bytes_per_pixel(),
   1549            );
   1550        }
   1551    }
   1552 }
   1553 
   1554 #[no_mangle]
   1555 pub extern "C" fn qcms_enable_iccv4() {
   1556    SUPPORTS_ICCV4.store(true, Ordering::Relaxed);
   1557 }