use crate::conversions::katana::KatanaInitialStage;
use crate::conversions::katana::md3x3::MultidimensionalDirection;
use crate::mlaf::mlaf;
use crate::safe_math::SafeMul;
use crate::trc::lut_interp_linear_float;
use crate::{
CmsError, DataColorSpace, Hypercube, InterpolationMethod, LutMultidimensionalType,
MalformedSize, Matrix3d, Matrix3f, PointeeSizeExpressible, TransformOptions, Vector3d,
Vector3f,
};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
pub(crate) fn execute_simple_curves3(dst: &mut [f32], curves: &[Vec<f32>; 3]) {
let curve0 = &curves[0];
let curve1 = &curves[1];
let curve2 = &curves[2];
for dst in dst.chunks_exact_mut(3) {
let a0 = dst[0];
let a1 = dst[1];
let a2 = dst[2];
let b0 = lut_interp_linear_float(a0, curve0);
let b1 = lut_interp_linear_float(a1, curve1);
let b2 = lut_interp_linear_float(a2, curve2);
dst[0] = b0;
dst[1] = b1;
dst[2] = b2;
}
}
pub(crate) fn execute_matrix_stage3(matrix: Matrix3f, bias: Vector3f, dst: &mut [f32]) {
let m = matrix;
let b = bias;
if !m.test_equality(Matrix3f::IDENTITY) || !b.eq(&Vector3f::default()) {
for dst in dst.chunks_exact_mut(3) {
let x = dst[0];
let y = dst[1];
let z = dst[2];
dst[0] = mlaf(mlaf(mlaf(b.v[0], x, m.v[0][0]), y, m.v[0][1]), z, m.v[0][2]);
dst[1] = mlaf(mlaf(mlaf(b.v[1], x, m.v[1][0]), y, m.v[1][1]), z, m.v[1][2]);
dst[2] = mlaf(mlaf(mlaf(b.v[2], x, m.v[2][0]), y, m.v[2][1]), z, m.v[2][2]);
}
}
}
struct Multidimensional4x3<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
> {
a_curves: Option<Box<[Vec<f32>; 4]>>,
m_curves: Option<Box<[Vec<f32>; 3]>>,
b_curves: Option<Box<[Vec<f32>; 3]>>,
clut: Option<Vec<f32>>,
matrix: Matrix3f,
bias: Vector3f,
direction: MultidimensionalDirection,
options: TransformOptions,
pcs: DataColorSpace,
grid_size: [u8; 4],
_phantom: PhantomData<T>,
bit_depth: usize,
}
impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
Multidimensional4x3<T>
{
fn to_pcs_impl<Fetch: Fn(f32, f32, f32, f32) -> Vector3f>(
&self,
input: &[T],
dst: &mut [f32],
fetch: Fetch,
) -> Result<(), CmsError> {
let norm_value = if T::FINITE {
1.0 / ((1u32 << self.bit_depth) - 1) as f32
} else {
1.0
};
assert_eq!(
self.direction,
MultidimensionalDirection::DeviceToPcs,
"PCS to device cannot be used on `to pcs` stage"
);
if let (Some(a_curves), Some(clut)) = (self.a_curves.as_ref(), self.clut.as_ref()) {
if !clut.is_empty() {
let curve0 = &a_curves[0];
let curve1 = &a_curves[1];
let curve2 = &a_curves[2];
let curve3 = &a_curves[3];
for (src, dst) in input.chunks_exact(4).zip(dst.chunks_exact_mut(3)) {
let b0 = lut_interp_linear_float(src[0].as_() * norm_value, curve0);
let b1 = lut_interp_linear_float(src[1].as_() * norm_value, curve1);
let b2 = lut_interp_linear_float(src[2].as_() * norm_value, curve2);
let b3 = lut_interp_linear_float(src[3].as_() * norm_value, curve3);
let interpolated = fetch(b0, b1, b2, b3);
dst[0] = interpolated.v[0];
dst[1] = interpolated.v[1];
dst[2] = interpolated.v[2];
}
}
} else {
return Err(CmsError::InvalidAtoBLut);
}
if let Some(m_curves) = self.m_curves.as_ref() {
execute_simple_curves3(dst, m_curves);
execute_matrix_stage3(self.matrix, self.bias, dst);
}
if let Some(b_curves) = &self.b_curves.as_ref() {
execute_simple_curves3(dst, b_curves);
}
Ok(())
}
}
impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
KatanaInitialStage<f32, T> for Multidimensional4x3<T>
{
fn to_pcs(&self, input: &[T]) -> Result<Vec<f32>, CmsError> {
if input.len() % 4 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let fixed_new_clut = Vec::new();
let new_clut = self.clut.as_ref().unwrap_or(&fixed_new_clut);
let lut = Hypercube::new_hypercube(new_clut, self.grid_size, 3)?;
let mut new_dst = vec![0f32; (input.len() / 4) * 3];
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
self.to_pcs_impl(input, &mut new_dst, |x, y, z, w| {
lut.quadlinear_vec3(x, y, z, w)
})?;
return Ok(new_dst);
}
match self.options.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.to_pcs_impl(input, &mut new_dst, |x, y, z, w| lut.tetra_vec3(x, y, z, w))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.to_pcs_impl(input, &mut new_dst, |x, y, z, w| {
lut.pyramid_vec3(x, y, z, w)
})?;
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.to_pcs_impl(input, &mut new_dst, |x, y, z, w| lut.prism_vec3(x, y, z, w))?;
}
InterpolationMethod::Linear => {
self.to_pcs_impl(input, &mut new_dst, |x, y, z, w| {
lut.quadlinear_vec3(x, y, z, w)
})?;
}
}
Ok(new_dst)
}
}
fn make_multidimensional_4x3<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
>(
mab: &LutMultidimensionalType,
options: TransformOptions,
pcs: DataColorSpace,
direction: MultidimensionalDirection,
bit_depth: usize,
) -> Result<Multidimensional4x3<T>, CmsError> {
if mab.num_input_channels != 4 && mab.num_output_channels != 3 {
return Err(CmsError::UnsupportedProfileConnection);
}
if mab.b_curves.is_empty() || mab.b_curves.len() != 3 {
return Err(CmsError::InvalidAtoBLut);
}
let grid_size = [
mab.grid_points[0],
mab.grid_points[1],
mab.grid_points[2],
mab.grid_points[3],
];
let clut: Option<Vec<f32>> = if mab.a_curves.len() == 4 && mab.clut.is_some() {
let clut = mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
let lut_grid = (mab.grid_points[0] as usize)
.safe_mul(mab.grid_points[1] as usize)?
.safe_mul(mab.grid_points[2] as usize)?
.safe_mul(mab.grid_points[3] as usize)?
.safe_mul(mab.num_output_channels as usize)?;
if clut.len() != lut_grid {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: clut.len(),
expected: lut_grid,
}));
}
Some(clut)
} else {
return Err(CmsError::InvalidAtoBLut);
};
let a_curves: Option<Box<[Vec<f32>; 4]>> = if mab.a_curves.len() == 4 && mab.clut.is_some() {
let mut arr = Box::<[Vec<f32>; 4]>::default();
for (a_curve, dst) in mab.a_curves.iter().zip(arr.iter_mut()) {
*dst = a_curve.to_clut()?;
}
Some(arr)
} else {
None
};
let b_curves: Option<Box<[Vec<f32>; 3]>> = if mab.b_curves.len() == 3 {
let mut arr = Box::<[Vec<f32>; 3]>::default();
let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
if all_curves_linear {
None
} else {
for (c_curve, dst) in mab.b_curves.iter().zip(arr.iter_mut()) {
*dst = c_curve.to_clut()?;
}
Some(arr)
}
} else {
return Err(CmsError::InvalidAtoBLut);
};
let matrix = mab.matrix.to_f32();
let m_curves: Option<Box<[Vec<f32>; 3]>> = if mab.m_curves.len() == 3 {
let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
if !all_curves_linear
|| !mab.matrix.test_equality(Matrix3d::IDENTITY)
|| mab.bias.ne(&Vector3d::default())
{
let mut arr = Box::<[Vec<f32>; 3]>::default();
for (curve, dst) in mab.m_curves.iter().zip(arr.iter_mut()) {
*dst = curve.to_clut()?;
}
Some(arr)
} else {
None
}
} else {
None
};
let bias = mab.bias.cast();
let transform = Multidimensional4x3::<T> {
a_curves,
b_curves,
m_curves,
matrix,
direction,
options,
clut,
pcs,
grid_size,
bias,
_phantom: PhantomData,
bit_depth,
};
Ok(transform)
}
pub(crate) fn multi_dimensional_4x3_to_pcs<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
>(
mab: &LutMultidimensionalType,
options: TransformOptions,
pcs: DataColorSpace,
bit_depth: usize,
) -> Result<Box<dyn KatanaInitialStage<f32, T> + Send + Sync>, CmsError> {
let transform = make_multidimensional_4x3::<T>(
mab,
options,
pcs,
MultidimensionalDirection::DeviceToPcs,
bit_depth,
)?;
Ok(Box::new(transform))
}