mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 08:30:16 +00:00
303 lines
11 KiB
Rust
303 lines
11 KiB
Rust
|
|
/*
|
||
|
|
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
|
||
|
|
* //
|
||
|
|
* // Redistribution and use in source and binary forms, with or without modification,
|
||
|
|
* // are permitted provided that the following conditions are met:
|
||
|
|
* //
|
||
|
|
* // 1. Redistributions of source code must retain the above copyright notice, this
|
||
|
|
* // list of conditions and the following disclaimer.
|
||
|
|
* //
|
||
|
|
* // 2. Redistributions in binary form must reproduce the above copyright notice,
|
||
|
|
* // this list of conditions and the following disclaimer in the documentation
|
||
|
|
* // and/or other materials provided with the distribution.
|
||
|
|
* //
|
||
|
|
* // 3. Neither the name of the copyright holder nor the names of its
|
||
|
|
* // contributors may be used to endorse or promote products derived from
|
||
|
|
* // this software without specific prior written permission.
|
||
|
|
* //
|
||
|
|
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||
|
|
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
|
|
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
|
|
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||
|
|
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||
|
|
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||
|
|
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||
|
|
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||
|
|
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||
|
|
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
|
*/
|
||
|
|
use crate::conversions::mab::{BCurves3, MCurves3};
|
||
|
|
use crate::err::try_vec;
|
||
|
|
use crate::safe_math::SafeMul;
|
||
|
|
use crate::{
|
||
|
|
CmsError, Cube, DataColorSpace, InPlaceStage, InterpolationMethod, LutMultidimensionalType,
|
||
|
|
MalformedSize, Matrix3d, Stage, TransformOptions, Vector3d, Vector4f,
|
||
|
|
};
|
||
|
|
|
||
|
|
struct ACurves3x4Inverse<'a> {
|
||
|
|
curve0: Box<[f32; 65536]>,
|
||
|
|
curve1: Box<[f32; 65536]>,
|
||
|
|
curve2: Box<[f32; 65536]>,
|
||
|
|
curve3: Box<[f32; 65536]>,
|
||
|
|
clut: &'a [f32],
|
||
|
|
grid_size: [u8; 3],
|
||
|
|
interpolation_method: InterpolationMethod,
|
||
|
|
pcs: DataColorSpace,
|
||
|
|
depth: usize,
|
||
|
|
}
|
||
|
|
|
||
|
|
struct ACurves3x4InverseOptimized<'a> {
|
||
|
|
clut: &'a [f32],
|
||
|
|
grid_size: [u8; 3],
|
||
|
|
interpolation_method: InterpolationMethod,
|
||
|
|
pcs: DataColorSpace,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl ACurves3x4Inverse<'_> {
|
||
|
|
fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector4f>(
|
||
|
|
&self,
|
||
|
|
src: &[f32],
|
||
|
|
dst: &mut [f32],
|
||
|
|
fetch: Fetch,
|
||
|
|
) -> Result<(), CmsError> {
|
||
|
|
let scale_value = (self.depth as u32 - 1u32) as f32;
|
||
|
|
|
||
|
|
assert_eq!(src.len() / 3, dst.len() / 4);
|
||
|
|
|
||
|
|
for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(4)) {
|
||
|
|
let interpolated = fetch(src[0], src[1], src[2]);
|
||
|
|
let a0 = (interpolated.v[0] * scale_value).round().min(scale_value) as u16;
|
||
|
|
let a1 = (interpolated.v[1] * scale_value).round().min(scale_value) as u16;
|
||
|
|
let a2 = (interpolated.v[2] * scale_value).round().min(scale_value) as u16;
|
||
|
|
let a3 = (interpolated.v[3] * scale_value).round().min(scale_value) as u16;
|
||
|
|
let b0 = self.curve0[a0 as usize];
|
||
|
|
let b1 = self.curve1[a1 as usize];
|
||
|
|
let b2 = self.curve2[a2 as usize];
|
||
|
|
let b3 = self.curve3[a3 as usize];
|
||
|
|
dst[0] = b0;
|
||
|
|
dst[1] = b1;
|
||
|
|
dst[2] = b2;
|
||
|
|
dst[3] = b3;
|
||
|
|
}
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl ACurves3x4InverseOptimized<'_> {
|
||
|
|
fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector4f>(
|
||
|
|
&self,
|
||
|
|
src: &[f32],
|
||
|
|
dst: &mut [f32],
|
||
|
|
fetch: Fetch,
|
||
|
|
) -> Result<(), CmsError> {
|
||
|
|
assert_eq!(src.len() / 3, dst.len() / 4);
|
||
|
|
|
||
|
|
for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(4)) {
|
||
|
|
let interpolated = fetch(src[0], src[1], src[2]);
|
||
|
|
let b0 = interpolated.v[0];
|
||
|
|
let b1 = interpolated.v[1];
|
||
|
|
let b2 = interpolated.v[2];
|
||
|
|
let b3 = interpolated.v[3];
|
||
|
|
dst[0] = b0;
|
||
|
|
dst[1] = b1;
|
||
|
|
dst[2] = b2;
|
||
|
|
dst[3] = b3;
|
||
|
|
}
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Stage for ACurves3x4Inverse<'_> {
|
||
|
|
fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
|
||
|
|
let lut = Cube::new_cube(self.clut, self.grid_size);
|
||
|
|
|
||
|
|
// If PCS is LAB then linear interpolation should be used
|
||
|
|
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
|
||
|
|
return self.transform_impl(src, dst, |x, y, z| lut.trilinear_vec4(x, y, z));
|
||
|
|
}
|
||
|
|
|
||
|
|
match self.interpolation_method {
|
||
|
|
#[cfg(feature = "options")]
|
||
|
|
InterpolationMethod::Tetrahedral => {
|
||
|
|
self.transform_impl(src, dst, |x, y, z| lut.tetra_vec4(x, y, z))?;
|
||
|
|
}
|
||
|
|
#[cfg(feature = "options")]
|
||
|
|
InterpolationMethod::Pyramid => {
|
||
|
|
self.transform_impl(src, dst, |x, y, z| lut.pyramid_vec4(x, y, z))?;
|
||
|
|
}
|
||
|
|
#[cfg(feature = "options")]
|
||
|
|
InterpolationMethod::Prism => {
|
||
|
|
self.transform_impl(src, dst, |x, y, z| lut.prism_vec4(x, y, z))?;
|
||
|
|
}
|
||
|
|
InterpolationMethod::Linear => {
|
||
|
|
self.transform_impl(src, dst, |x, y, z| lut.trilinear_vec4(x, y, z))?;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Stage for ACurves3x4InverseOptimized<'_> {
|
||
|
|
fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
|
||
|
|
let lut = Cube::new_cube(self.clut, self.grid_size);
|
||
|
|
|
||
|
|
// If PCS is LAB then linear interpolation should be used
|
||
|
|
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
|
||
|
|
return self.transform_impl(src, dst, |x, y, z| lut.trilinear_vec4(x, y, z));
|
||
|
|
}
|
||
|
|
|
||
|
|
match self.interpolation_method {
|
||
|
|
#[cfg(feature = "options")]
|
||
|
|
InterpolationMethod::Tetrahedral => {
|
||
|
|
self.transform_impl(src, dst, |x, y, z| lut.tetra_vec4(x, y, z))?;
|
||
|
|
}
|
||
|
|
#[cfg(feature = "options")]
|
||
|
|
InterpolationMethod::Pyramid => {
|
||
|
|
self.transform_impl(src, dst, |x, y, z| lut.pyramid_vec4(x, y, z))?;
|
||
|
|
}
|
||
|
|
#[cfg(feature = "options")]
|
||
|
|
InterpolationMethod::Prism => {
|
||
|
|
self.transform_impl(src, dst, |x, y, z| lut.prism_vec4(x, y, z))?;
|
||
|
|
}
|
||
|
|
InterpolationMethod::Linear => {
|
||
|
|
self.transform_impl(src, dst, |x, y, z| lut.trilinear_vec4(x, y, z))?;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub(crate) fn prepare_mba_3x4(
|
||
|
|
mab: &LutMultidimensionalType,
|
||
|
|
lut: &mut [f32],
|
||
|
|
options: TransformOptions,
|
||
|
|
pcs: DataColorSpace,
|
||
|
|
) -> Result<Vec<f32>, CmsError> {
|
||
|
|
if mab.num_input_channels != 3 && mab.num_output_channels != 4 {
|
||
|
|
return Err(CmsError::UnsupportedProfileConnection);
|
||
|
|
}
|
||
|
|
|
||
|
|
const LERP_DEPTH: usize = 65536;
|
||
|
|
const BP: usize = 13;
|
||
|
|
const DEPTH: usize = 8192;
|
||
|
|
|
||
|
|
if mab.b_curves.len() == 3 {
|
||
|
|
let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
|
||
|
|
|
||
|
|
if !all_curves_linear {
|
||
|
|
let curves: Result<Vec<_>, _> = mab
|
||
|
|
.b_curves
|
||
|
|
.iter()
|
||
|
|
.map(|c| {
|
||
|
|
c.build_linearize_table::<u16, LERP_DEPTH, BP>()
|
||
|
|
.ok_or(CmsError::InvalidTrcCurve)
|
||
|
|
})
|
||
|
|
.collect();
|
||
|
|
|
||
|
|
let [curve0, curve1, curve2] =
|
||
|
|
curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
|
||
|
|
let b_curves = BCurves3::<DEPTH> {
|
||
|
|
curve0,
|
||
|
|
curve1,
|
||
|
|
curve2,
|
||
|
|
};
|
||
|
|
b_curves.transform(lut)?;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
return Err(CmsError::InvalidAtoBLut);
|
||
|
|
}
|
||
|
|
|
||
|
|
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 curves: Result<Vec<_>, _> = mab
|
||
|
|
.m_curves
|
||
|
|
.iter()
|
||
|
|
.map(|c| {
|
||
|
|
c.build_linearize_table::<u16, LERP_DEPTH, BP>()
|
||
|
|
.ok_or(CmsError::InvalidTrcCurve)
|
||
|
|
})
|
||
|
|
.collect();
|
||
|
|
|
||
|
|
let [curve0, curve1, curve2] =
|
||
|
|
curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
|
||
|
|
|
||
|
|
let matrix = mab.matrix.to_f32();
|
||
|
|
let bias = mab.bias.cast();
|
||
|
|
let m_curves = MCurves3 {
|
||
|
|
curve0,
|
||
|
|
curve1,
|
||
|
|
curve2,
|
||
|
|
matrix,
|
||
|
|
bias,
|
||
|
|
inverse: true,
|
||
|
|
depth: DEPTH,
|
||
|
|
};
|
||
|
|
m_curves.transform(lut)?;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let mut new_lut = try_vec![0f32; (lut.len() / 3) * 4];
|
||
|
|
|
||
|
|
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.num_output_channels as usize)?;
|
||
|
|
if clut.len() != lut_grid {
|
||
|
|
return Err(CmsError::MalformedClut(MalformedSize {
|
||
|
|
size: clut.len(),
|
||
|
|
expected: lut_grid,
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
let grid_size = [mab.grid_points[0], mab.grid_points[1], mab.grid_points[2]];
|
||
|
|
|
||
|
|
let all_curves_linear = mab.a_curves.iter().all(|curve| curve.is_linear());
|
||
|
|
|
||
|
|
if all_curves_linear {
|
||
|
|
let a_curves = ACurves3x4InverseOptimized {
|
||
|
|
clut,
|
||
|
|
grid_size: [mab.grid_points[0], mab.grid_points[1], mab.grid_points[2]],
|
||
|
|
interpolation_method: options.interpolation_method,
|
||
|
|
pcs,
|
||
|
|
};
|
||
|
|
a_curves.transform(lut, &mut new_lut)?;
|
||
|
|
} else {
|
||
|
|
let curves: Result<Vec<_>, _> = mab
|
||
|
|
.a_curves
|
||
|
|
.iter()
|
||
|
|
.map(|c| {
|
||
|
|
c.build_linearize_table::<u16, LERP_DEPTH, BP>()
|
||
|
|
.ok_or(CmsError::InvalidTrcCurve)
|
||
|
|
})
|
||
|
|
.collect();
|
||
|
|
|
||
|
|
let [curve0, curve1, curve2, curve3] =
|
||
|
|
curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
|
||
|
|
|
||
|
|
let a_curves = ACurves3x4Inverse {
|
||
|
|
curve0,
|
||
|
|
curve1,
|
||
|
|
curve2,
|
||
|
|
curve3,
|
||
|
|
clut,
|
||
|
|
grid_size,
|
||
|
|
interpolation_method: options.interpolation_method,
|
||
|
|
depth: DEPTH,
|
||
|
|
pcs,
|
||
|
|
};
|
||
|
|
a_curves.transform(lut, &mut new_lut)?;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
return Err(CmsError::UnsupportedProfileConnection);
|
||
|
|
}
|
||
|
|
|
||
|
|
Ok(new_lut)
|
||
|
|
}
|