Source code
Revision control
Copy as Markdown
Other Tools
// 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.
#![allow(clippy::missing_safety_doc)]
#[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "neon"))]
use crate::transform_neon::{
qcms_transform_data_bgra_out_lut_neon, qcms_transform_data_rgb_out_lut_neon,
qcms_transform_data_rgba_out_lut_neon,
};
use crate::{
chain::chain_transform,
double_to_s15Fixed16Number,
iccread::SUPPORTS_ICCV4,
matrix::*,
transform_util::{
build_colorant_matrix, build_input_gamma_table, build_output_lut, compute_precache,
lut_interp_linear,
},
};
use crate::{
iccread::{qcms_CIE_xyY, qcms_CIE_xyYTRIPLE, Profile, GRAY_SIGNATURE, RGB_SIGNATURE},
transform_util::clamp_float,
Intent,
};
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use crate::{
transform_avx::{
qcms_transform_data_bgra_out_lut_avx, qcms_transform_data_rgb_out_lut_avx,
qcms_transform_data_rgba_out_lut_avx,
},
transform_sse2::{
qcms_transform_data_bgra_out_lut_sse2, qcms_transform_data_rgb_out_lut_sse2,
qcms_transform_data_rgba_out_lut_sse2,
},
};
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;
pub const PRECACHE_OUTPUT_SIZE: usize = 8192;
pub const PRECACHE_OUTPUT_MAX: usize = PRECACHE_OUTPUT_SIZE - 1;
pub const FLOATSCALE: f32 = PRECACHE_OUTPUT_SIZE as f32;
pub const CLAMPMAXVAL: f32 = ((PRECACHE_OUTPUT_SIZE - 1) as f32) / PRECACHE_OUTPUT_SIZE as f32;
#[repr(C)]
#[derive(Debug)]
pub struct PrecacheOuput {
/* We previously used a count of 65536 here but that seems like more
* precision than we actually need. By reducing the size we can
* improve startup performance and reduce memory usage. ColorSync on
* 10.5 uses 4097 which is perhaps because they use a fixed point
* 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],
}
impl Default for PrecacheOuput {
fn default() -> PrecacheOuput {
PrecacheOuput {
lut_r: [0; PRECACHE_OUTPUT_SIZE],
lut_g: [0; PRECACHE_OUTPUT_SIZE],
lut_b: [0; PRECACHE_OUTPUT_SIZE],
}
}
}
/* used as a lookup table for the output transformation.
* we refcount them so we only need to have one around per output
* profile, instead of duplicating them per transform */
#[repr(C)]
#[repr(align(16))]
#[derive(Clone, Default)]
pub struct qcms_transform {
pub matrix: [[f32; 4]; 3],
pub input_gamma_table_r: Option<Box<[f32; 256]>>,
pub input_gamma_table_g: Option<Box<[f32; 256]>>,
pub input_gamma_table_b: Option<Box<[f32; 256]>>,
pub input_clut_table_length: u16,
pub clut: Option<Vec<f32>>,
pub grid_size: u16,
pub output_clut_table_length: u16,
pub input_gamma_table_gray: Option<Box<[f32; 256]>>,
pub out_gamma_r: f32,
pub out_gamma_g: f32,
pub out_gamma_b: f32,
pub out_gamma_gray: f32,
pub output_gamma_lut_r: Option<Vec<u16>>,
pub output_gamma_lut_g: Option<Vec<u16>>,
pub output_gamma_lut_b: Option<Vec<u16>>,
pub output_gamma_lut_gray: Option<Vec<u16>>,
pub output_gamma_lut_r_length: usize,
pub output_gamma_lut_g_length: usize,
pub output_gamma_lut_b_length: usize,
pub output_gamma_lut_gray_length: usize,
pub precache_output: Option<Arc<PrecacheOuput>>,
pub transform_fn: transform_fn_t,
}
pub type transform_fn_t =
Option<unsafe fn(_: &qcms_transform, _: *const u8, _: *mut u8, _: usize) -> ()>;
/// The format of pixel data
#[repr(u32)]
#[derive(PartialEq, Eq, Clone, Copy)]
#[allow(clippy::upper_case_acronyms)]
pub enum DataType {
RGB8 = 0,
RGBA8 = 1,
BGRA8 = 2,
Gray8 = 3,
GrayA8 = 4,
CMYK = 5,
}
impl DataType {
pub fn bytes_per_pixel(&self) -> usize {
match self {
RGB8 => 3,
RGBA8 => 4,
BGRA8 => 4,
Gray8 => 1,
GrayA8 => 2,
CMYK => 4,
}
}
}
use DataType::*;
#[repr(C)]
#[derive(Copy, Clone)]
#[allow(clippy::upper_case_acronyms)]
pub struct CIE_XYZ {
pub X: f64,
pub Y: f64,
pub Z: f64,
}
pub trait Format {
const kRIndex: usize;
const kGIndex: usize;
const kBIndex: usize;
const kAIndex: usize;
}
#[allow(clippy::upper_case_acronyms)]
pub struct BGRA;
impl Format for BGRA {
const kBIndex: usize = 0;
const kGIndex: usize = 1;
const kRIndex: usize = 2;
const kAIndex: usize = 3;
}
#[allow(clippy::upper_case_acronyms)]
pub struct RGBA;
impl Format for RGBA {
const kRIndex: usize = 0;
const kGIndex: usize = 1;
const kBIndex: usize = 2;
const kAIndex: usize = 3;
}
#[allow(clippy::upper_case_acronyms)]
pub struct RGB;
impl Format for RGB {
const kRIndex: usize = 0;
const kGIndex: usize = 1;
const kBIndex: usize = 2;
const kAIndex: usize = 0xFF;
}
pub trait GrayFormat {
const has_alpha: bool;
}
pub struct Gray;
impl GrayFormat for Gray {
const has_alpha: bool = false;
}
pub struct GrayAlpha;
impl GrayFormat for GrayAlpha {
const has_alpha: bool = true;
}
#[inline]
fn clamp_u8(v: f32) -> u8 {
if v > 255. {
255
} else if 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> {
let mut primaries: Matrix = Matrix { m: [[0.; 3]; 3] };
let mut result: Matrix = Matrix { m: [[0.; 3]; 3] };
let mut white_point: Vector = Vector { v: [0.; 3] };
let xn: f64 = white.x;
let yn: f64 = white.y;
if yn == 0.0f64 {
return None;
}
let xr: f64 = primrs.red.x;
let yr: f64 = primrs.red.y;
let xg: f64 = primrs.green.x;
let yg: f64 = primrs.green.y;
let xb: f64 = primrs.blue.x;
let yb: f64 = primrs.blue.y;
primaries.m[0][0] = xr as f32;
primaries.m[0][1] = xg as f32;
primaries.m[0][2] = xb as f32;
primaries.m[1][0] = yr as f32;
primaries.m[1][1] = yg as f32;
primaries.m[1][2] = yb as f32;
primaries.m[2][0] = (1f64 - xr - yr) as f32;
primaries.m[2][1] = (1f64 - xg - yg) as f32;
primaries.m[2][2] = (1f64 - xb - yb) as f32;
white_point.v[0] = (xn / yn) as f32;
white_point.v[1] = 1.;
white_point.v[2] = ((1.0f64 - xn - yn) / yn) as f32;
let primaries_invert: Matrix = primaries.invert()?;
let coefs: Vector = primaries_invert.eval(white_point);
result.m[0][0] = (coefs.v[0] as f64 * xr) as f32;
result.m[0][1] = (coefs.v[1] as f64 * xg) as f32;
result.m[0][2] = (coefs.v[2] as f64 * xb) as f32;
result.m[1][0] = (coefs.v[0] as f64 * yr) as f32;
result.m[1][1] = (coefs.v[1] as f64 * yg) as f32;
result.m[1][2] = (coefs.v[2] as f64 * yb) as f32;
result.m[2][0] = (coefs.v[0] as f64 * (1.0f64 - xr - yr)) as f32;
result.m[2][1] = (coefs.v[1] as f64 * (1.0f64 - xg - yg)) as f32;
result.m[2][2] = (coefs.v[2] as f64 * (1.0f64 - xb - yb)) as f32;
Some(result)
}
/* CIE Illuminant D50 */
const D50_XYZ: CIE_XYZ = CIE_XYZ {
X: 0.9642f64,
Y: 1.0000f64,
Z: 0.8249f64,
};
/* from lcms: xyY2XYZ()
* corresponds to argyll: icmYxy2XYZ() */
fn xyY2XYZ(source: qcms_CIE_xyY) -> CIE_XYZ {
let mut dest: CIE_XYZ = CIE_XYZ {
X: 0.,
Y: 0.,
Z: 0.,
};
dest.X = source.x / source.y * source.Y;
dest.Y = source.Y;
dest.Z = (1f64 - source.x - source.y) / source.y * source.Y;
dest
}
/* from lcms: ComputeChromaticAdaption */
// Compute chromatic adaption matrix using chad as cone matrix
fn compute_chromatic_adaption(
source_white_point: CIE_XYZ,
dest_white_point: CIE_XYZ,
chad: Matrix,
) -> Option<Matrix> {
let mut cone_source_XYZ: Vector = Vector { v: [0.; 3] };
let mut cone_dest_XYZ: Vector = Vector { v: [0.; 3] };
let mut cone: 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 cone_source_rgb: Vector = chad.eval(cone_source_XYZ);
let cone_dest_rgb: Vector = chad.eval(cone_dest_XYZ);
cone.m[0][0] = cone_dest_rgb.v[0] / cone_source_rgb.v[0];
cone.m[0][1] = 0.;
cone.m[0][2] = 0.;
cone.m[1][0] = 0.;
cone.m[1][1] = cone_dest_rgb.v[1] / cone_source_rgb.v[1];
cone.m[1][2] = 0.;
cone.m[2][0] = 0.;
cone.m[2][1] = 0.;
cone.m[2][2] = cone_dest_rgb.v[2] / cone_source_rgb.v[2];
// Normalize
Some(Matrix::multiply(chad_inv, Matrix::multiply(cone, chad)))
}
/* from lcms: cmsAdaptionMatrix */
// Returns the final chrmatic adaptation from illuminant FromIll to Illuminant ToIll
// Bradford is assumed
fn adaption_matrix(source_illumination: CIE_XYZ, target_illumination: CIE_XYZ) -> Option<Matrix> {
let lam_rigg: Matrix = {
Matrix {
m: [
[0.8951, 0.2664, -0.1614],
[-0.7502, 1.7135, 0.0367],
[0.0389, -0.0685, 1.0296],
],
}
};
compute_chromatic_adaption(source_illumination, target_illumination, lam_rigg)
}
/* from lcms: cmsAdaptMatrixToD50 */
fn adapt_matrix_to_D50(r: Option<Matrix>, source_white_pt: qcms_CIE_xyY) -> Option<Matrix> {
if source_white_pt.y == 0.0f64 {
return None;
}
let Dn: CIE_XYZ = xyY2XYZ(source_white_pt);
let Bradford: Matrix = adaption_matrix(Dn, D50_XYZ)?;
Some(Matrix::multiply(Bradford, r?))
}
pub(crate) fn set_rgb_colorants(
profile: &mut Profile,
white_point: qcms_CIE_xyY,
primaries: qcms_CIE_xyYTRIPLE,
) -> bool {
let colorants = build_RGB_to_XYZ_transfer_matrix(white_point, primaries);
let colorants = match adapt_matrix_to_D50(colorants, white_point) {
Some(colorants) => colorants,
None => return false,
};
/* note: there's a transpose type of operation going on here */
profile.redColorant.X = double_to_s15Fixed16Number(colorants.m[0][0] as f64);
profile.redColorant.Y = double_to_s15Fixed16Number(colorants.m[1][0] as f64);
profile.redColorant.Z = double_to_s15Fixed16Number(colorants.m[2][0] as f64);
profile.greenColorant.X = double_to_s15Fixed16Number(colorants.m[0][1] as f64);
profile.greenColorant.Y = double_to_s15Fixed16Number(colorants.m[1][1] as f64);
profile.greenColorant.Z = double_to_s15Fixed16Number(colorants.m[2][1] as f64);
profile.blueColorant.X = double_to_s15Fixed16Number(colorants.m[0][2] as f64);
profile.blueColorant.Y = double_to_s15Fixed16Number(colorants.m[1][2] as f64);
profile.blueColorant.Z = double_to_s15Fixed16Number(colorants.m[2][2] as f64);
true
}
pub(crate) fn get_rgb_colorants(
white_point: qcms_CIE_xyY,
primaries: qcms_CIE_xyYTRIPLE,
) -> Option<Matrix> {
let colorants = build_RGB_to_XYZ_transfer_matrix(white_point, primaries);
adapt_matrix_to_D50(colorants, white_point)
}
/* Alpha is not corrected.
A rationale for this is found in Alvy Ray's "Should Alpha Be Nonlinear If
RGB Is?" Tech Memo 17 (December 14, 1998).
*/
unsafe extern "C" fn qcms_transform_data_gray_template_lut<I: GrayFormat, 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 input_gamma_table_gray = transform.input_gamma_table_gray.as_ref().unwrap();
let mut i: u32 = 0;
while (i as usize) < length {
let fresh0 = src;
src = src.offset(1);
let device: u8 = *fresh0;
let mut alpha: u8 = 0xffu8;
if I::has_alpha {
let fresh1 = src;
src = src.offset(1);
alpha = *fresh1
}
let linear: f32 = input_gamma_table_gray[device as usize];
let out_device_r: f32 = lut_interp_linear(
linear as f64,
&transform.output_gamma_lut_r.as_ref().unwrap(),
);
let out_device_g: f32 = lut_interp_linear(
linear as f64,
&transform.output_gamma_lut_g.as_ref().unwrap(),
);
let out_device_b: f32 = lut_interp_linear(
linear as f64,
&transform.output_gamma_lut_b.as_ref().unwrap(),
);
*dest.add(F::kRIndex) = clamp_u8(out_device_r * 255f32);
*dest.add(F::kGIndex) = clamp_u8(out_device_g * 255f32);
*dest.add(F::kBIndex) = clamp_u8(out_device_b * 255f32);
if F::kAIndex != 0xff {
*dest.add(F::kAIndex) = alpha
}
dest = dest.offset(components as isize);
i += 1
}
}
unsafe fn qcms_transform_data_gray_out_lut(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_gray_template_lut::<Gray, RGB>(transform, src, dest, length);
}
unsafe fn qcms_transform_data_gray_rgba_out_lut(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_gray_template_lut::<Gray, RGBA>(transform, src, dest, length);
}
unsafe fn qcms_transform_data_gray_bgra_out_lut(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_gray_template_lut::<Gray, BGRA>(transform, src, dest, length);
}
unsafe fn qcms_transform_data_graya_rgba_out_lut(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_gray_template_lut::<GrayAlpha, RGBA>(transform, src, dest, length);
}
unsafe fn qcms_transform_data_graya_bgra_out_lut(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_gray_template_lut::<GrayAlpha, BGRA>(transform, src, dest, length);
}
unsafe fn qcms_transform_data_gray_template_precache<I: GrayFormat, 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 precache_output = transform.precache_output.as_deref().unwrap();
let output_r = &precache_output.lut_r;
let output_g = &precache_output.lut_g;
let output_b = &precache_output.lut_b;
let input_gamma_table_gray = transform
.input_gamma_table_gray
.as_ref()
.unwrap()
.as_ptr();
let mut i: u32 = 0;
while (i as usize) < length {
let fresh2 = src;
src = src.offset(1);
let device: u8 = *fresh2;
let mut alpha: u8 = 0xffu8;
if I::has_alpha {
let fresh3 = src;
src = src.offset(1);
alpha = *fresh3
}
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
}
}
unsafe fn 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);
}
unsafe fn 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);
}
unsafe fn 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);
}
unsafe fn 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);
}
unsafe fn 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);
}
unsafe fn 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;
let mut 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);
let mut 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);
let mut out_linear_r = mat[0][0] * linear_r + mat[1][0] * linear_g + mat[2][0] * linear_b;
let mut out_linear_g = mat[0][1] * linear_r + mat[1][1] * linear_g + mat[2][1] * linear_b;
let mut 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]
pub unsafe fn 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]
pub unsafe fn 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]
pub unsafe fn 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
/*
static void qcms_transform_data_clut(const qcms_transform *transform, const unsigned char *src, unsigned char *dest, size_t length) {
unsigned int i;
int xy_len = 1;
int x_len = transform->grid_size;
int len = x_len * x_len;
const float* r_table = transform->r_clut;
const float* g_table = transform->g_clut;
const float* b_table = transform->b_clut;
for (i = 0; i < length; i++) {
unsigned char in_r = *src++;
unsigned char in_g = *src++;
unsigned char in_b = *src++;
float linear_r = in_r/255.0f, linear_g=in_g/255.0f, linear_b = in_b/255.0f;
int x = floorf(linear_r * (transform->grid_size-1));
int y = floorf(linear_g * (transform->grid_size-1));
int z = floorf(linear_b * (transform->grid_size-1));
int x_n = ceilf(linear_r * (transform->grid_size-1));
int y_n = ceilf(linear_g * (transform->grid_size-1));
int z_n = ceilf(linear_b * (transform->grid_size-1));
float x_d = linear_r * (transform->grid_size-1) - x;
float y_d = linear_g * (transform->grid_size-1) - y;
float z_d = linear_b * (transform->grid_size-1) - z;
float r_x1 = lerp(CLU(r_table,x,y,z), CLU(r_table,x_n,y,z), x_d);
float r_x2 = lerp(CLU(r_table,x,y_n,z), CLU(r_table,x_n,y_n,z), x_d);
float r_y1 = lerp(r_x1, r_x2, y_d);
float r_x3 = lerp(CLU(r_table,x,y,z_n), CLU(r_table,x_n,y,z_n), x_d);
float r_x4 = lerp(CLU(r_table,x,y_n,z_n), CLU(r_table,x_n,y_n,z_n), x_d);
float r_y2 = lerp(r_x3, r_x4, y_d);
float clut_r = lerp(r_y1, r_y2, z_d);
float g_x1 = lerp(CLU(g_table,x,y,z), CLU(g_table,x_n,y,z), x_d);
float g_x2 = lerp(CLU(g_table,x,y_n,z), CLU(g_table,x_n,y_n,z), x_d);
float g_y1 = lerp(g_x1, g_x2, y_d);
float g_x3 = lerp(CLU(g_table,x,y,z_n), CLU(g_table,x_n,y,z_n), x_d);
float g_x4 = lerp(CLU(g_table,x,y_n,z_n), CLU(g_table,x_n,y_n,z_n), x_d);
float g_y2 = lerp(g_x3, g_x4, y_d);
float clut_g = lerp(g_y1, g_y2, z_d);
float b_x1 = lerp(CLU(b_table,x,y,z), CLU(b_table,x_n,y,z), x_d);
float b_x2 = lerp(CLU(b_table,x,y_n,z), CLU(b_table,x_n,y_n,z), x_d);
float b_y1 = lerp(b_x1, b_x2, y_d);
float b_x3 = lerp(CLU(b_table,x,y,z_n), CLU(b_table,x_n,y,z_n), x_d);
float b_x4 = lerp(CLU(b_table,x,y_n,z_n), CLU(b_table,x_n,y_n,z_n), x_d);
float b_y2 = lerp(b_x3, b_x4, y_d);
float clut_b = lerp(b_y1, b_y2, z_d);
*dest++ = clamp_u8(clut_r*255.0f);
*dest++ = clamp_u8(clut_g*255.0f);
*dest++ = clamp_u8(clut_b*255.0f);
}
}
*/
fn int_div_ceil(value: i32, div: i32) -> i32 {
(value + div - 1) / div
}
// Using lcms' tetra interpolation algorithm.
unsafe extern "C" fn qcms_transform_data_tetra_clut_template<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 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);
let mut 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);
let mut 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)
};
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);
} else if 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;
}
} else if 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);
} else if 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;
*dest.add(F::kRIndex) = clamp_u8(clut_r * 255.0);
*dest.add(F::kGIndex) = clamp_u8(clut_g * 255.0);
*dest.add(F::kBIndex) = clamp_u8(clut_b * 255.0);
if F::kAIndex != 0xff {
*dest.add(F::kAIndex) = in_a
}
dest = dest.offset(components as isize);
i += 1
}
}
unsafe fn 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);
} else if 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;
}
} else if 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);
} else if 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)]
unsafe fn 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 _ in 0..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);
}
}
unsafe fn qcms_transform_data_tetra_clut_rgb(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_tetra_clut_template::<RGB>(transform, src, dest, length);
}
unsafe fn qcms_transform_data_tetra_clut_rgba(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_tetra_clut_template::<RGBA>(transform, src, dest, length);
}
unsafe fn qcms_transform_data_tetra_clut_bgra(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_tetra_clut_template::<BGRA>(transform, src, dest, length);
}
unsafe fn qcms_transform_data_template_lut<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 mat = &transform.matrix;
let mut 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);
let mut 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);
let mut out_linear_r = mat[0][0] * linear_r + mat[1][0] * linear_g + mat[2][0] * linear_b;
let mut out_linear_g = mat[0][1] * linear_r + mat[1][1] * linear_g + mat[2][1] * linear_b;
let mut 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);
let out_device_r: f32 = lut_interp_linear(
out_linear_r as f64,
&transform.output_gamma_lut_r.as_ref().unwrap(),
);
let out_device_g: f32 = lut_interp_linear(
out_linear_g as f64,
transform.output_gamma_lut_g.as_ref().unwrap(),
);
let out_device_b: f32 = lut_interp_linear(
out_linear_b as f64,
transform.output_gamma_lut_b.as_ref().unwrap(),
);
*dest.add(F::kRIndex) = clamp_u8(out_device_r * 255f32);
*dest.add(F::kGIndex) = clamp_u8(out_device_g * 255f32);
*dest.add(F::kBIndex) = clamp_u8(out_device_b * 255f32);
if F::kAIndex != 0xff {
*dest.add(F::kAIndex) = alpha
}
dest = dest.offset(components as isize);
i += 1
}
}
#[no_mangle]
pub unsafe fn qcms_transform_data_rgb_out_lut(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_template_lut::<RGB>(transform, src, dest, length);
}
#[no_mangle]
pub unsafe fn qcms_transform_data_rgba_out_lut(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_template_lut::<RGBA>(transform, src, dest, length);
}
#[no_mangle]
pub unsafe fn qcms_transform_data_bgra_out_lut(
transform: &qcms_transform,
src: *const u8,
dest: *mut u8,
length: usize,
) {
qcms_transform_data_template_lut::<BGRA>(transform, src, dest, length);
}
fn precache_create() -> Arc<PrecacheOuput> {
Arc::new(PrecacheOuput::default())
}
#[no_mangle]
pub unsafe extern "C" fn qcms_transform_release(t: *mut qcms_transform) {
drop(Box::from_raw(t));
}
const bradford_matrix: Matrix = Matrix {
m: [
[0.8951, 0.2664, -0.1614],
[-0.7502, 1.7135, 0.0367],
[0.0389, -0.0685, 1.0296],
],
};
const bradford_matrix_inv: Matrix = Matrix {
m: [
[0.9869929, -0.1470543, 0.1599627],
[0.4323053, 0.5183603, 0.0492912],
[-0.0085287, 0.0400428, 0.9684867],
],
};
// 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]
pub extern "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() {
let mut 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;
let mut 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 in 0..samples {
for y in 0..samples {
for z in 0..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);
if let 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)
} else if in_type == BGRA8 {
transform.transform_fn = Some(qcms_transform_data_tetra_clut_bgra)
} else if 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;
let mut 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 in 0..samples {
for c in 0..samples {
for m in 0..samples {
for y in 0..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);
if let 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)
}
pub fn 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;
}
let mut transform: Box<qcms_transform> = Box::new(Default::default());
let mut 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());
if transform.output_gamma_lut_r.is_none()
|| transform.output_gamma_lut_g.is_none()
|| transform.output_gamma_lut_b.is_none()
{
return None;
}
}
if input.color_space == RGB_SIGNATURE {
if precache {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if is_x86_feature_detected!("avx") {
if in_type == RGB8 {
transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_avx)
} else if in_type == RGBA8 {
transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_avx)
} else if in_type == BGRA8 {
transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_avx)
}
} else if cfg!(not(miri)) && is_x86_feature_detected!("sse2") {
if in_type == RGB8 {
transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_sse2)
} else if in_type == RGBA8 {
transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_sse2)
} else if in_type == BGRA8 {
transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_sse2)
}
}
#[cfg(all(target_arch = "arm", feature = "neon"))]
let neon_supported = is_arm_feature_detected!("neon");
#[cfg(all(target_arch = "aarch64", feature = "neon"))]
let neon_supported = is_aarch64_feature_detected!("neon");
#[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "neon"))]
if neon_supported {
if in_type == RGB8 {
transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_neon)
} else if in_type == RGBA8 {
transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_neon)
} else if in_type == BGRA8 {
transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_neon)
}
}
if transform.transform_fn.is_none() {
if in_type == RGB8 {
transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_precache)
} else if in_type == RGBA8 {
transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_precache)
} else if in_type == BGRA8 {
transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_precache)
}
}
} else if in_type == RGB8 {
transform.transform_fn = Some(qcms_transform_data_rgb_out_lut)
} else if in_type == RGBA8 {
transform.transform_fn = Some(qcms_transform_data_rgba_out_lut)
} else if in_type == BGRA8 {
transform.transform_fn = Some(qcms_transform_data_bgra_out_lut)
}
//XXX: avoid duplicating tables if we can
transform.input_gamma_table_r = build_input_gamma_table(input.redTRC.as_deref());
transform.input_gamma_table_g = build_input_gamma_table(input.greenTRC.as_deref());
transform.input_gamma_table_b = build_input_gamma_table(input.blueTRC.as_deref());
if transform.input_gamma_table_r.is_none()
|| transform.input_gamma_table_g.is_none()
|| transform.input_gamma_table_b.is_none()
{
return None;
}
/* build combined colorant matrix */
let in_matrix: Matrix = build_colorant_matrix(input);
let mut out_matrix: Matrix = build_colorant_matrix(output);
out_matrix = out_matrix.invert()?;
let result_0: Matrix = Matrix::multiply(out_matrix, in_matrix);
/* check for NaN values in the matrix and bail if we find any */
let mut i: u32 = 0;
while i < 3 {
let mut 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]
} else if 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)
} else if 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)
}
} else if 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)
}
}
} else if out_type == RGB8 {
transform.transform_fn = Some(qcms_transform_data_gray_out_lut)
} else if 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)
}
} else if 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.
pub struct 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`
pub fn 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`
pub fn 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`
pub fn 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`
pub fn 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(),
);
}
}
}
#[no_mangle]
pub extern "C" fn qcms_enable_iccv4() {
SUPPORTS_ICCV4.store(true, Ordering::Relaxed);
}