// qcms // Copyright (C) 2009 Mozilla Foundation // Copyright (C) 1998-2007 Marti Maria // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the Software // is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
use std::sync::atomic::Ordering; use std::sync::Arc; #[cfg(all(target_arch = "arm", feature = "neon"))] use std::arch::is_arm_feature_detected; #[cfg(all(target_arch = "aarch64", feature = "neon"))] use std::arch::is_aarch64_feature_detected;
#[repr(C)] #[derive(Debug)] pubstruct PrecacheOuput { /* We previously used a count of 65536 here but that seems like more *precisionthanweactuallyneed.Byreducingthesizewecan *improvestartupperformanceandreducememoryusage.ColorSyncon *10.5uses4097whichisperhapsbecausetheyuseafixedpoint
* representation where 1. is represented by 0x1000. */ pub lut_r: [u8; PRECACHE_OUTPUT_SIZE], pub lut_g: [u8; PRECACHE_OUTPUT_SIZE], pub lut_b: [u8; PRECACHE_OUTPUT_SIZE],
}
/* used as a lookup table for the output transformation. *werefcountthemsoweonlyneedtohaveonearoundperoutput
* profile, instead of duplicating them per transform */
#[inline] fn clamp_u8(v: f32) -> u8 { if v > 255. { 255
} elseif v < 0. { 0
} else {
(v + 0.5).floor() as u8
}
}
// Build a White point, primary chromas transfer matrix from RGB to CIE XYZ // This is just an approximation, I am not handling all the non-linear // aspects of the RGB to XYZ process, and assumming that the gamma correction // has transitive property in the tranformation chain. // // the alghoritm: // // - First I build the absolute conversion matrix using // primaries in XYZ. This matrix is next inverted // - Then I eval the source white point across this matrix // obtaining the coeficients of the transformation // - Then, I apply these coeficients to the original matrix fn build_RGB_to_XYZ_transfer_matrix(
white: qcms_CIE_xyY,
primrs: qcms_CIE_xyYTRIPLE,
) -> Option<Matrix> { letmut primaries: Matrix = Matrix { m: [[0.; 3]; 3] };
let chad_inv: Matrix = chad.invert()?;
cone_source_XYZ.v[0] = source_white_point.X as f32;
cone_source_XYZ.v[1] = source_white_point.Y as f32;
cone_source_XYZ.v[2] = source_white_point.Z as f32;
cone_dest_XYZ.v[0] = dest_white_point.X as f32;
cone_dest_XYZ.v[1] = dest_white_point.Y as f32;
cone_dest_XYZ.v[2] = dest_white_point.Z as f32;
let input_gamma_table_gray = transform
.input_gamma_table_gray
.as_ref()
.unwrap()
.as_ptr();
letmut i: u32 = 0; while (i as usize) < length { let device: u8 = *src;
src = src.offset(1); letmut alpha: u8 = 0xffu8; if I::has_alpha {
alpha = *src;
src = src.offset(1);
}
let linear: f32 = *input_gamma_table_gray.offset(device as isize); /* we could round here... */ let gray: u16 = (linear * PRECACHE_OUTPUT_MAX as f32) as u16;
*dest.add(F::kRIndex) = output_r[gray as usize];
*dest.add(F::kGIndex) = output_g[gray as usize];
*dest.add(F::kBIndex) = output_b[gray as usize]; if F::kAIndex != 0xff {
*dest.add(F::kAIndex) = alpha
}
dest = dest.offset(components as isize);
i += 1
}
} unsafefn qcms_transform_data_gray_out_precache(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_gray_template_precache::<Gray, RGB>(transform, src, dest, length);
} unsafefn qcms_transform_data_gray_rgba_out_precache(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_gray_template_precache::<Gray, RGBA>(transform, src, dest, length);
} unsafefn qcms_transform_data_gray_bgra_out_precache(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_gray_template_precache::<Gray, BGRA>(transform, src, dest, length);
} unsafefn qcms_transform_data_graya_rgba_out_precache(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_gray_template_precache::<GrayAlpha, RGBA>(transform, src, dest, length);
} unsafefn qcms_transform_data_graya_bgra_out_precache(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_gray_template_precache::<GrayAlpha, BGRA>(transform, src, dest, length);
} unsafefn qcms_transform_data_template_lut_precache<F: Format>(
transform: &qcms_transform, mut src: *const u8, mut dest: *mut u8,
length: usize,
) { let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32; let output_table_r = &transform.precache_output.as_deref().unwrap().lut_r; let output_table_g = &transform.precache_output.as_deref().unwrap().lut_g; let output_table_b = &transform.precache_output.as_deref().unwrap().lut_b; let input_gamma_table_r = transform.input_gamma_table_r.as_ref().unwrap().as_ptr(); let input_gamma_table_g = transform.input_gamma_table_g.as_ref().unwrap().as_ptr(); let input_gamma_table_b = transform.input_gamma_table_b.as_ref().unwrap().as_ptr();
let mat = &transform.matrix; letmut i: u32 = 0; while (i as usize) < length { let device_r: u8 = *src.add(F::kRIndex); let device_g: u8 = *src.add(F::kGIndex); let device_b: u8 = *src.add(F::kBIndex); letmut alpha: u8 = 0; if F::kAIndex != 0xff {
alpha = *src.add(F::kAIndex)
}
src = src.offset(components as isize);
let linear_r: f32 = *input_gamma_table_r.offset(device_r as isize); let linear_g: f32 = *input_gamma_table_g.offset(device_g as isize); let linear_b: f32 = *input_gamma_table_b.offset(device_b as isize); letmut out_linear_r = mat[0][0] * linear_r + mat[1][0] * linear_g + mat[2][0] * linear_b; letmut out_linear_g = mat[0][1] * linear_r + mat[1][1] * linear_g + mat[2][1] * linear_b; letmut out_linear_b = mat[0][2] * linear_r + mat[1][2] * linear_g + mat[2][2] * linear_b;
out_linear_r = clamp_float(out_linear_r);
out_linear_g = clamp_float(out_linear_g);
out_linear_b = clamp_float(out_linear_b); /* we could round here... */
let r: u16 = (out_linear_r * PRECACHE_OUTPUT_MAX as f32) as u16; let g: u16 = (out_linear_g * PRECACHE_OUTPUT_MAX as f32) as u16; let b: u16 = (out_linear_b * PRECACHE_OUTPUT_MAX as f32) as u16;
*dest.add(F::kRIndex) = output_table_r[r as usize];
*dest.add(F::kGIndex) = output_table_g[g as usize];
*dest.add(F::kBIndex) = output_table_b[b as usize]; if F::kAIndex != 0xff {
*dest.add(F::kAIndex) = alpha
}
dest = dest.offset(components as isize);
i += 1
}
} #[no_mangle] pubunsafefn qcms_transform_data_rgb_out_lut_precache(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_template_lut_precache::<RGB>(transform, src, dest, length);
} #[no_mangle] pubunsafefn qcms_transform_data_rgba_out_lut_precache(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_template_lut_precache::<RGBA>(transform, src, dest, length);
} #[no_mangle] pubunsafefn qcms_transform_data_bgra_out_lut_precache(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_template_lut_precache::<BGRA>(transform, src, dest, length);
} // Not used /* staticvoidqcms_transform_data_clut(constqcms_transform*transform,constunsignedchar*src,unsignedchar*dest,size_tlength){ unsignedinti; intxy_len=1; intx_len=transform->grid_size; intlen=x_len*x_len; constfloat*r_table=transform->r_clut; constfloat*g_table=transform->g_clut; constfloat*b_table=transform->b_clut;
let xy_len: i32 = 1; let x_len: i32 = transform.grid_size as i32; let len: i32 = x_len * x_len; let table = transform.clut.as_ref().unwrap().as_ptr(); let r_table: *const f32 = table; let g_table: *const f32 = table.offset(1); let b_table: *const f32 = table.offset(2);
letmut i: u32 = 0; while (i as usize) < length { let c0_r: f32; let c1_r: f32; let c2_r: f32; let c3_r: f32; let c0_g: f32; let c1_g: f32; let c2_g: f32; let c3_g: f32; let c0_b: f32; let c1_b: f32; let c2_b: f32; let c3_b: f32; let in_r: u8 = *src.add(F::kRIndex); let in_g: u8 = *src.add(F::kGIndex); let in_b: u8 = *src.add(F::kBIndex); letmut in_a: u8 = 0; if F::kAIndex != 0xff {
in_a = *src.add(F::kAIndex)
}
src = src.offset(components as isize); let linear_r: f32 = in_r as i32 as f32 / 255.0; let linear_g: f32 = in_g as i32 as f32 / 255.0; let linear_b: f32 = in_b as i32 as f32 / 255.0; let x: i32 = in_r as i32 * (transform.grid_size as i32 - 1) / 255; let y: i32 = in_g as i32 * (transform.grid_size as i32 - 1) / 255; let z: i32 = in_b as i32 * (transform.grid_size as i32 - 1) / 255; let x_n: i32 = int_div_ceil(in_r as i32 * (transform.grid_size as i32 - 1), 255); let y_n: i32 = int_div_ceil(in_g as i32 * (transform.grid_size as i32 - 1), 255); let z_n: i32 = int_div_ceil(in_b as i32 * (transform.grid_size as i32 - 1), 255); let rx: f32 = linear_r * (transform.grid_size as i32 - 1) as f32 - x as f32; let ry: f32 = linear_g * (transform.grid_size as i32 - 1) as f32 - y as f32; let rz: f32 = linear_b * (transform.grid_size as i32 - 1) as f32 - z as f32; let CLU = |table: *const f32, x, y, z| {
*table.offset(((x * len + y * x_len + z * xy_len) * 3) as isize)
};
unsafefn tetra(
transform: &qcms_transform,
table: *const f32,
in_r: u8,
in_g: u8,
in_b: u8,
) -> (f32, f32, f32) { let r_table: *const f32 = table; let g_table: *const f32 = table.offset(1); let b_table: *const f32 = table.offset(2); let linear_r: f32 = in_r as i32 as f32 / 255.0; let linear_g: f32 = in_g as i32 as f32 / 255.0; let linear_b: f32 = in_b as i32 as f32 / 255.0; let xy_len: i32 = 1; let x_len: i32 = transform.grid_size as i32; let len: i32 = x_len * x_len; let x: i32 = in_r as i32 * (transform.grid_size as i32 - 1) / 255; let y: i32 = in_g as i32 * (transform.grid_size as i32 - 1) / 255; let z: i32 = in_b as i32 * (transform.grid_size as i32 - 1) / 255; let x_n: i32 = int_div_ceil(in_r as i32 * (transform.grid_size as i32 - 1), 255); let y_n: i32 = int_div_ceil(in_g as i32 * (transform.grid_size as i32 - 1), 255); let z_n: i32 = int_div_ceil(in_b as i32 * (transform.grid_size as i32 - 1), 255); let rx: f32 = linear_r * (transform.grid_size as i32 - 1) as f32 - x as f32; let ry: f32 = linear_g * (transform.grid_size as i32 - 1) as f32 - y as f32; let rz: f32 = linear_b * (transform.grid_size as i32 - 1) as f32 - z as f32; let CLU = |table: *const f32, x, y, z| {
*table.offset(((x * len + y * x_len + z * xy_len) * 3) as isize)
}; let c0_r: f32; let c1_r: f32; let c2_r: f32; let c3_r: f32; let c0_g: f32; let c1_g: f32; let c2_g: f32; let c3_g: f32; let c0_b: f32; let c1_b: f32; let c2_b: f32; let c3_b: f32;
c0_r = CLU(r_table, x, y, z);
c0_g = CLU(g_table, x, y, z);
c0_b = CLU(b_table, x, y, z); if rx >= ry { if ry >= rz { //rx >= ry && ry >= rz
c1_r = CLU(r_table, x_n, y, z) - c0_r;
c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z);
c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
c1_g = CLU(g_table, x_n, y, z) - c0_g;
c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z);
c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
c1_b = CLU(b_table, x_n, y, z) - c0_b;
c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z);
c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
} elseif rx >= rz { //rx >= rz && rz >= ry
c1_r = CLU(r_table, x_n, y, z) - c0_r;
c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z);
c1_g = CLU(g_table, x_n, y, z) - c0_g;
c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z);
c1_b = CLU(b_table, x_n, y, z) - c0_b;
c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z);
} else { //rz > rx && rx >= ry
c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n);
c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
c3_r = CLU(r_table, x, y, z_n) - c0_r;
c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n);
c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
c3_g = CLU(g_table, x, y, z_n) - c0_g;
c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n);
c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
c3_b = CLU(b_table, x, y, z_n) - c0_b;
}
} elseif rx >= rz { //ry > rx && rx >= rz
c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z);
c2_r = CLU(r_table, x, y_n, z) - c0_r;
c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z);
c2_g = CLU(g_table, x, y_n, z) - c0_g;
c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z);
c2_b = CLU(b_table, x, y_n, z) - c0_b;
c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
} elseif ry >= rz { //ry >= rz && rz > rx
c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
c2_r = CLU(r_table, x, y_n, z) - c0_r;
c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z);
c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
c2_g = CLU(g_table, x, y_n, z) - c0_g;
c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z);
c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
c2_b = CLU(b_table, x, y_n, z) - c0_b;
c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z);
} else { //rz > ry && ry > rx
c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
c2_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y, z_n);
c3_r = CLU(r_table, x, y, z_n) - c0_r;
c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
c2_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y, z_n);
c3_g = CLU(g_table, x, y, z_n) - c0_g;
c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
c2_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y, z_n);
c3_b = CLU(b_table, x, y, z_n) - c0_b;
} let clut_r = c0_r + c1_r * rx + c2_r * ry + c3_r * rz; let clut_g = c0_g + c1_g * rx + c2_g * ry + c3_g * rz; let clut_b = c0_b + c1_b * rx + c2_b * ry + c3_b * rz;
(clut_r, clut_g, clut_b)
}
#[inline] fn lerp(a: f32, b: f32, t: f32) -> f32 {
a * (1.0 - t) + b * t
}
// lerp between two tetrahedral interpolations // See lcms:Eval4InputsFloat #[allow(clippy::many_single_char_names)] unsafefn qcms_transform_data_tetra_clut_cmyk(
transform: &qcms_transform, mut src: *const u8, mut dest: *mut u8,
length: usize,
) { let table = transform.clut.as_ref().unwrap().as_ptr();
assert!(
transform.clut.as_ref().unwrap().len()
>= ((transform.grid_size as i32).pow(4) * 3) as usize
); for _ in0..length { let c: u8 = *src.add(0); let m: u8 = *src.add(1); let y: u8 = *src.add(2); let k: u8 = *src.add(3);
src = src.offset(4); let linear_k: f32 = k as i32 as f32 / 255.0; let grid_size = transform.grid_size as i32; let w: i32 = k as i32 * (transform.grid_size as i32 - 1) / 255; let w_n: i32 = int_div_ceil(k as i32 * (transform.grid_size as i32 - 1), 255); let t: f32 = linear_k * (transform.grid_size as i32 - 1) as f32 - w as f32;
let table1 = table.offset((w * grid_size * grid_size * grid_size * 3) as isize); let table2 = table.offset((w_n * grid_size * grid_size * grid_size * 3) as isize);
let (r1, g1, b1) = tetra(transform, table1, c, m, y); let (r2, g2, b2) = tetra(transform, table2, c, m, y); let r = lerp(r1, r2, t); let g = lerp(g1, g2, t); let b = lerp(b1, b2, t);
*dest.add(0) = clamp_u8(r * 255.0);
*dest.add(1) = clamp_u8(g * 255.0);
*dest.add(2) = clamp_u8(b * 255.0);
dest = dest.offset(3);
}
}
let mat = &transform.matrix; letmut i: u32 = 0; let input_gamma_table_r = transform.input_gamma_table_r.as_ref().unwrap().as_ptr(); let input_gamma_table_g = transform.input_gamma_table_g.as_ref().unwrap().as_ptr(); let input_gamma_table_b = transform.input_gamma_table_b.as_ref().unwrap().as_ptr(); while (i as usize) < length { let device_r: u8 = *src.add(F::kRIndex); let device_g: u8 = *src.add(F::kGIndex); let device_b: u8 = *src.add(F::kBIndex); letmut alpha: u8 = 0; if F::kAIndex != 0xff {
alpha = *src.add(F::kAIndex)
}
src = src.offset(components as isize);
// See ICCv4 E.3 fn compute_whitepoint_adaption(X: f32, Y: f32, Z: f32) -> Matrix { let p: f32 = (0.96422 * bradford_matrix.m[0][0]
+ 1.000 * bradford_matrix.m[1][0]
+ 0.82521 * bradford_matrix.m[2][0])
/ (X * bradford_matrix.m[0][0] + Y * bradford_matrix.m[1][0] + Z * bradford_matrix.m[2][0]); let y: f32 = (0.96422 * bradford_matrix.m[0][1]
+ 1.000 * bradford_matrix.m[1][1]
+ 0.82521 * bradford_matrix.m[2][1])
/ (X * bradford_matrix.m[0][1] + Y * bradford_matrix.m[1][1] + Z * bradford_matrix.m[2][1]); let b: f32 = (0.96422 * bradford_matrix.m[0][2]
+ 1.000 * bradford_matrix.m[1][2]
+ 0.82521 * bradford_matrix.m[2][2])
/ (X * bradford_matrix.m[0][2] + Y * bradford_matrix.m[1][2] + Z * bradford_matrix.m[2][2]); let white_adaption = Matrix {
m: [[p, 0., 0.], [0., y, 0.], [0., 0., b]],
};
Matrix::multiply(
bradford_matrix_inv,
Matrix::multiply(white_adaption, bradford_matrix),
)
} #[no_mangle] pubextern"C"fn qcms_profile_precache_output_transform(profile: &mut Profile) { /* we only support precaching on rgb profiles */ if profile.color_space != RGB_SIGNATURE { return;
} if SUPPORTS_ICCV4.load(Ordering::Relaxed) { /* don't precache since we will use the B2A LUT */ if profile.B2A0.is_some() { return;
} /* don't precache since we will use the mBA LUT */ if profile.mBA.is_some() { return;
}
} /* don't precache if we do not have the TRC curves */ if profile.redTRC.is_none() || profile.greenTRC.is_none() || profile.blueTRC.is_none() { return;
} if profile.precache_output.is_none() { letmut precache = precache_create();
compute_precache(
profile.redTRC.as_deref().unwrap(),
&mut Arc::get_mut(&mut precache).unwrap().lut_r,
);
compute_precache(
profile.greenTRC.as_deref().unwrap(),
&mut Arc::get_mut(&mut precache).unwrap().lut_g,
);
compute_precache(
profile.blueTRC.as_deref().unwrap(),
&mut Arc::get_mut(&mut precache).unwrap().lut_b,
);
profile.precache_output = Some(precache);
}
} /* Replace the current transformation with a LUT transformation using a given number of sample points */ fn transform_precacheLUT_float( mut transform: Box<qcms_transform>,
input: &Profile,
output: &Profile,
samples: i32,
in_type: DataType,
) -> Option<Box<qcms_transform>> { /* The range between which 2 consecutive sample points can be used to interpolate */ let lutSize: u32 = (3 * samples * samples * samples) as u32;
letmut src = Vec::with_capacity(lutSize as usize); let dest = vec![0.; lutSize as usize]; /* Prepare a list of points we want to sample */ for x in0..samples { for y in0..samples { for z in0..samples {
src.push(x as f32 / (samples - 1) as f32);
src.push(y as f32 / (samples - 1) as f32);
src.push(z as f32 / (samples - 1) as f32);
}
}
} let lut = chain_transform(input, output, src, dest, lutSize as usize); iflet Some(lut) = lut {
transform.clut = Some(lut);
transform.grid_size = samples as u16; if in_type == RGBA8 {
transform.transform_fn = Some(qcms_transform_data_tetra_clut_rgba)
} elseif in_type == BGRA8 {
transform.transform_fn = Some(qcms_transform_data_tetra_clut_bgra)
} elseif in_type == RGB8 {
transform.transform_fn = Some(qcms_transform_data_tetra_clut_rgb)
}
debug_assert!(transform.transform_fn.is_some());
} else { return None;
}
Some(transform)
}
fn transform_precacheLUT_cmyk_float( mut transform: Box<qcms_transform>,
input: &Profile,
output: &Profile,
samples: i32,
in_type: DataType,
) -> Option<Box<qcms_transform>> { /* The range between which 2 consecutive sample points can be used to interpolate */ let lutSize: u32 = (4 * samples * samples * samples * samples) as u32;
letmut src = Vec::with_capacity(lutSize as usize); let dest = vec![0.; lutSize as usize]; /* Prepare a list of points we want to sample */ for k in0..samples { for c in0..samples { for m in0..samples { for y in0..samples {
src.push(c as f32 / (samples - 1) as f32);
src.push(m as f32 / (samples - 1) as f32);
src.push(y as f32 / (samples - 1) as f32);
src.push(k as f32 / (samples - 1) as f32);
}
}
}
} let lut = chain_transform(input, output, src, dest, lutSize as usize); iflet Some(lut) = lut {
transform.clut = Some(lut);
transform.grid_size = samples as u16;
assert!(in_type == DataType::CMYK);
transform.transform_fn = Some(qcms_transform_data_tetra_clut_cmyk)
} else { return None;
}
Some(transform)
}
pubfn transform_create(
input: &Profile,
in_type: DataType,
output: &Profile,
out_type: DataType,
_intent: Intent,
) -> Option<Box<qcms_transform>> { // Ensure the requested input and output types make sense. let matching_format = match (in_type, out_type) {
(RGB8, RGB8) => true,
(RGBA8, RGBA8) => true,
(BGRA8, BGRA8) => true,
(Gray8, out_type) => matches!(out_type, RGB8 | RGBA8 | BGRA8),
(GrayA8, out_type) => matches!(out_type, RGBA8 | BGRA8),
(CMYK, RGB8) => true,
_ => false,
}; if !matching_format {
debug_assert!(false, "input/output type"); return None;
} letmut transform: Box<qcms_transform> = Box::new(Default::default()); letmut precache: bool = false; if output.precache_output.is_some() {
precache = true
} // This precache assumes RGB_SIGNATURE (fails on GRAY_SIGNATURE, for instance) if SUPPORTS_ICCV4.load(Ordering::Relaxed)
&& (in_type == RGB8 || in_type == RGBA8 || in_type == BGRA8 || in_type == CMYK)
&& (input.A2B0.is_some()
|| output.B2A0.is_some()
|| input.mAB.is_some()
|| output.mAB.is_some())
{ if in_type == CMYK { return transform_precacheLUT_cmyk_float(transform, input, output, 17, in_type);
} // Precache the transformation to a CLUT 33x33x33 in size. // 33 is used by many profiles and works well in pratice. // This evenly divides 256 into blocks of 8x8x8. // TODO For transforming small data sets of about 200x200 or less // precaching should be avoided. let result = transform_precacheLUT_float(transform, input, output, 33, in_type);
debug_assert!(result.is_some(), "precacheLUT failed"); return result;
} if precache {
transform.precache_output = Some(Arc::clone(output.precache_output.as_ref().unwrap()));
} else { if output.redTRC.is_none() || output.greenTRC.is_none() || output.blueTRC.is_none() { return None;
}
transform.output_gamma_lut_r = build_output_lut(output.redTRC.as_deref().unwrap());
transform.output_gamma_lut_g = build_output_lut(output.greenTRC.as_deref().unwrap());
transform.output_gamma_lut_b = build_output_lut(output.blueTRC.as_deref().unwrap());
let result_0: Matrix = Matrix::multiply(out_matrix, in_matrix); /* check for NaN values in the matrix and bail if we find any */ letmut i: u32 = 0; while i < 3 { letmut j: u32 = 0; while j < 3 { #[allow(clippy::eq_op, clippy::float_cmp)] if result_0.m[i as usize][j as usize].is_nan() { return None;
}
j += 1
}
i += 1
} /* store the results in column major mode
* this makes doing the multiplication with sse easier */
transform.matrix[0][0] = result_0.m[0][0];
transform.matrix[1][0] = result_0.m[0][1];
transform.matrix[2][0] = result_0.m[0][2];
transform.matrix[0][1] = result_0.m[1][0];
transform.matrix[1][1] = result_0.m[1][1];
transform.matrix[2][1] = result_0.m[1][2];
transform.matrix[0][2] = result_0.m[2][0];
transform.matrix[1][2] = result_0.m[2][1];
transform.matrix[2][2] = result_0.m[2][2]
} elseif input.color_space == GRAY_SIGNATURE {
transform.input_gamma_table_gray = build_input_gamma_table(input.grayTRC.as_deref());
transform.input_gamma_table_gray.as_ref()?; if precache { if out_type == RGB8 {
transform.transform_fn = Some(qcms_transform_data_gray_out_precache)
} elseif out_type == RGBA8 { if in_type == Gray8 {
transform.transform_fn = Some(qcms_transform_data_gray_rgba_out_precache)
} else {
transform.transform_fn = Some(qcms_transform_data_graya_rgba_out_precache)
}
} elseif out_type == BGRA8 { if in_type == Gray8 {
transform.transform_fn = Some(qcms_transform_data_gray_bgra_out_precache)
} else {
transform.transform_fn = Some(qcms_transform_data_graya_bgra_out_precache)
}
}
} elseif out_type == RGB8 {
transform.transform_fn = Some(qcms_transform_data_gray_out_lut)
} elseif out_type == RGBA8 { if in_type == Gray8 {
transform.transform_fn = Some(qcms_transform_data_gray_rgba_out_lut)
} else {
transform.transform_fn = Some(qcms_transform_data_graya_rgba_out_lut)
}
} elseif out_type == BGRA8 { if in_type == Gray8 {
transform.transform_fn = Some(qcms_transform_data_gray_bgra_out_lut)
} else {
transform.transform_fn = Some(qcms_transform_data_graya_bgra_out_lut)
}
}
} else {
debug_assert!(false, "unexpected colorspace"); return None;
}
debug_assert!(transform.transform_fn.is_some());
Some(transform)
} /// A transform from an input profile to an output one. pubstruct Transform {
src_ty: DataType,
dst_ty: DataType,
xfm: Box<qcms_transform>,
}
impl Transform { /// Create a new transform from `input` to `output` for pixels of `DataType` `ty` with `intent` pubfn new(input: &Profile, output: &Profile, ty: DataType, intent: Intent) -> Option<Self> {
transform_create(input, ty, output, ty, intent).map(|xfm| Transform {
src_ty: ty,
dst_ty: ty,
xfm,
})
}
/// Create a new transform from `input` to `output` for pixels of `DataType` `ty` with `intent` pubfn new_to(
input: &Profile,
output: &Profile,
src_ty: DataType,
dst_ty: DataType,
intent: Intent,
) -> Option<Self> {
transform_create(input, src_ty, output, dst_ty, intent).map(|xfm| Transform {
src_ty,
dst_ty,
xfm,
})
}
/// Apply the color space transform to `data` pubfn apply(&self, data: &mut [u8]) { if data.len() % self.src_ty.bytes_per_pixel() != 0 {
panic!( "incomplete pixels: should be a multiple of {} got {}", self.src_ty.bytes_per_pixel(),
data.len()
)
} unsafe { self.xfm.transform_fn.expect("non-null function pointer")(
&*self.xfm,
data.as_ptr(),
data.as_mut_ptr(),
data.len() / self.src_ty.bytes_per_pixel(),
);
}
}
/// Apply the color space transform to `data` pubfn convert(&self, src: &[u8], dst: &mut[u8]) { if src.len() % self.src_ty.bytes_per_pixel() != 0 {
panic!( "incomplete pixels: should be a multiple of {} got {}", self.src_ty.bytes_per_pixel(),
src.len()
)
} if dst.len() % self.dst_ty.bytes_per_pixel() != 0 {
panic!( "incomplete pixels: should be a multiple of {} got {}", self.dst_ty.bytes_per_pixel(),
dst.len()
)
}
assert_eq!(
src.len() / self.src_ty.bytes_per_pixel(),
dst.len() / self.dst_ty.bytes_per_pixel()
); unsafe { self.xfm.transform_fn.expect("non-null function pointer")(
&*self.xfm,
src.as_ptr(),
dst.as_mut_ptr(),
src.len() / self.src_ty.bytes_per_pixel(),
);
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.