mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 01:10:17 +00:00
1584 lines
50 KiB
Rust
1584 lines
50 KiB
Rust
/*
|
|
* // Copyright (c) Radzivon Bartoshyk 2/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::cicp::create_rec709_parametric;
|
|
use crate::matan::is_curve_linear16;
|
|
use crate::math::m_clamp;
|
|
use crate::mlaf::{mlaf, neg_mlaf};
|
|
use crate::transform::PointeeSizeExpressible;
|
|
use crate::writer::FloatToFixedU8Fixed8;
|
|
use crate::{CmsError, ColorProfile, DataColorSpace, Rgb, TransferCharacteristics};
|
|
use num_traits::AsPrimitive;
|
|
use pxfm::{dirty_powf, f_pow, f_powf};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum ToneReprCurve {
|
|
Lut(Vec<u16>),
|
|
Parametric(Vec<f32>),
|
|
}
|
|
|
|
impl ToneReprCurve {
|
|
pub fn inverse(&self) -> Result<ToneReprCurve, CmsError> {
|
|
match self {
|
|
ToneReprCurve::Lut(lut) => {
|
|
let inverse_length = lut.len().max(256);
|
|
Ok(ToneReprCurve::Lut(invert_lut(lut, inverse_length)))
|
|
}
|
|
ToneReprCurve::Parametric(parametric) => ParametricCurve::new(parametric)
|
|
.and_then(|x| x.invert())
|
|
.map(|x| ToneReprCurve::Parametric([x.g, x.a, x.b, x.c, x.d, x.e, x.f].to_vec()))
|
|
.ok_or(CmsError::BuildTransferFunction),
|
|
}
|
|
}
|
|
|
|
/// Creates tone curve evaluator
|
|
pub fn make_linear_evaluator(
|
|
&self,
|
|
) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> {
|
|
match self {
|
|
ToneReprCurve::Lut(lut) => {
|
|
if lut.is_empty() {
|
|
return Ok(Box::new(ToneCurveEvaluatorLinear {}));
|
|
}
|
|
if lut.len() == 1 {
|
|
let gamma = u8_fixed_8number_to_float(lut[0]);
|
|
return Ok(Box::new(ToneCurveEvaluatorPureGamma { gamma }));
|
|
}
|
|
let converted_curve = lut.iter().map(|&x| x as f32 / 65535.0).collect::<Vec<_>>();
|
|
Ok(Box::new(ToneCurveLutEvaluator {
|
|
lut: converted_curve,
|
|
}))
|
|
}
|
|
ToneReprCurve::Parametric(parametric) => {
|
|
let parametric_curve =
|
|
ParametricCurve::new(parametric).ok_or(CmsError::BuildTransferFunction)?;
|
|
Ok(Box::new(ToneCurveParametricEvaluator {
|
|
parametric: parametric_curve,
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Creates tone curve evaluator from transfer characteristics
|
|
pub fn make_cicp_linear_evaluator(
|
|
transfer_characteristics: TransferCharacteristics,
|
|
) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> {
|
|
if !transfer_characteristics.has_transfer_curve() {
|
|
return Err(CmsError::BuildTransferFunction);
|
|
}
|
|
Ok(Box::new(ToneCurveCicpLinearEvaluator {
|
|
trc: transfer_characteristics,
|
|
}))
|
|
}
|
|
|
|
/// Creates tone curve inverse evaluator
|
|
pub fn make_gamma_evaluator(
|
|
&self,
|
|
) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> {
|
|
match self {
|
|
ToneReprCurve::Lut(lut) => {
|
|
if lut.is_empty() {
|
|
return Ok(Box::new(ToneCurveEvaluatorLinear {}));
|
|
}
|
|
if lut.len() == 1 {
|
|
let gamma = 1. / u8_fixed_8number_to_float(lut[0]);
|
|
return Ok(Box::new(ToneCurveEvaluatorPureGamma { gamma }));
|
|
}
|
|
let inverted_lut = invert_lut(lut, 16384);
|
|
let converted_curve = inverted_lut
|
|
.iter()
|
|
.map(|&x| x as f32 / 65535.0)
|
|
.collect::<Vec<_>>();
|
|
Ok(Box::new(ToneCurveLutEvaluator {
|
|
lut: converted_curve,
|
|
}))
|
|
}
|
|
ToneReprCurve::Parametric(parametric) => {
|
|
let parametric_curve = ParametricCurve::new(parametric)
|
|
.and_then(|x| x.invert())
|
|
.ok_or(CmsError::BuildTransferFunction)?;
|
|
Ok(Box::new(ToneCurveParametricEvaluator {
|
|
parametric: parametric_curve,
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Creates tone curve inverse evaluator from transfer characteristics
|
|
pub fn make_cicp_gamma_evaluator(
|
|
transfer_characteristics: TransferCharacteristics,
|
|
) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> {
|
|
if !transfer_characteristics.has_transfer_curve() {
|
|
return Err(CmsError::BuildTransferFunction);
|
|
}
|
|
Ok(Box::new(ToneCurveCicpGammaEvaluator {
|
|
trc: transfer_characteristics,
|
|
}))
|
|
}
|
|
}
|
|
|
|
struct ToneCurveCicpLinearEvaluator {
|
|
trc: TransferCharacteristics,
|
|
}
|
|
|
|
struct ToneCurveCicpGammaEvaluator {
|
|
trc: TransferCharacteristics,
|
|
}
|
|
|
|
impl ToneCurveEvaluator for ToneCurveCicpLinearEvaluator {
|
|
fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
|
|
Rgb::new(
|
|
self.trc.linearize(rgb.r as f64) as f32,
|
|
self.trc.linearize(rgb.g as f64) as f32,
|
|
self.trc.linearize(rgb.b as f64) as f32,
|
|
)
|
|
}
|
|
|
|
fn evaluate_value(&self, value: f32) -> f32 {
|
|
self.trc.linearize(value as f64) as f32
|
|
}
|
|
}
|
|
|
|
impl ToneCurveEvaluator for ToneCurveCicpGammaEvaluator {
|
|
fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
|
|
Rgb::new(
|
|
self.trc.gamma(rgb.r as f64) as f32,
|
|
self.trc.gamma(rgb.g as f64) as f32,
|
|
self.trc.gamma(rgb.b as f64) as f32,
|
|
)
|
|
}
|
|
|
|
fn evaluate_value(&self, value: f32) -> f32 {
|
|
self.trc.gamma(value as f64) as f32
|
|
}
|
|
}
|
|
|
|
struct ToneCurveLutEvaluator {
|
|
lut: Vec<f32>,
|
|
}
|
|
|
|
impl ToneCurveEvaluator for ToneCurveLutEvaluator {
|
|
fn evaluate_value(&self, value: f32) -> f32 {
|
|
lut_interp_linear_float(value, &self.lut)
|
|
}
|
|
|
|
fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
|
|
Rgb::new(
|
|
lut_interp_linear_float(rgb.r, &self.lut),
|
|
lut_interp_linear_float(rgb.g, &self.lut),
|
|
lut_interp_linear_float(rgb.b, &self.lut),
|
|
)
|
|
}
|
|
}
|
|
|
|
pub(crate) fn build_trc_table(num_entries: i32, eotf: impl Fn(f64) -> f64) -> Vec<u16> {
|
|
let mut table = vec![0u16; num_entries as usize];
|
|
|
|
for (i, table_value) in table.iter_mut().enumerate() {
|
|
let x: f64 = i as f64 / (num_entries - 1) as f64;
|
|
let y: f64 = eotf(x);
|
|
let mut output: f64;
|
|
output = y * 65535.0 + 0.5;
|
|
if output > 65535.0 {
|
|
output = 65535.0
|
|
}
|
|
if output < 0.0 {
|
|
output = 0.0
|
|
}
|
|
*table_value = output.floor() as u16;
|
|
}
|
|
table
|
|
}
|
|
|
|
/// Creates Tone Reproduction curve from gamma
|
|
pub fn curve_from_gamma(gamma: f32) -> ToneReprCurve {
|
|
ToneReprCurve::Lut(vec![gamma.to_u8_fixed8()])
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct ParametricCurve {
|
|
g: f32,
|
|
a: f32,
|
|
b: f32,
|
|
c: f32,
|
|
d: f32,
|
|
e: f32,
|
|
f: f32,
|
|
}
|
|
|
|
impl ParametricCurve {
|
|
#[allow(clippy::many_single_char_names)]
|
|
fn new(params: &[f32]) -> Option<ParametricCurve> {
|
|
// convert from the variable number of parameters
|
|
// contained in profiles to a unified representation.
|
|
let g: f32 = params[0];
|
|
match params[1..] {
|
|
[] => Some(ParametricCurve {
|
|
g,
|
|
a: 1.,
|
|
b: 0.,
|
|
c: 1.,
|
|
d: 0.,
|
|
e: 0.,
|
|
f: 0.,
|
|
}),
|
|
[a, b] => Some(ParametricCurve {
|
|
g,
|
|
a,
|
|
b,
|
|
c: 0.,
|
|
d: -b / a,
|
|
e: 0.,
|
|
f: 0.,
|
|
}),
|
|
[a, b, c] => Some(ParametricCurve {
|
|
g,
|
|
a,
|
|
b,
|
|
c: 0.,
|
|
d: -b / a,
|
|
e: c,
|
|
f: c,
|
|
}),
|
|
[a, b, c, d] => Some(ParametricCurve {
|
|
g,
|
|
a,
|
|
b,
|
|
c,
|
|
d,
|
|
e: 0.,
|
|
f: 0.,
|
|
}),
|
|
[a, b, c, d, e, f] => Some(ParametricCurve {
|
|
g,
|
|
a,
|
|
b,
|
|
c,
|
|
d,
|
|
e,
|
|
f,
|
|
}),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn is_linear(&self) -> bool {
|
|
(self.g - 1.0).abs() < 1e-5
|
|
&& (self.a - 1.0).abs() < 1e-5
|
|
&& self.b.abs() < 1e-5
|
|
&& self.c.abs() < 1e-5
|
|
}
|
|
|
|
fn eval(&self, x: f32) -> f32 {
|
|
if x < self.d {
|
|
self.c * x + self.f
|
|
} else {
|
|
f_powf(self.a * x + self.b, self.g) + self.e
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[allow(clippy::many_single_char_names)]
|
|
fn invert(&self) -> Option<ParametricCurve> {
|
|
// First check if the function is continuous at the cross-over point d.
|
|
let d1 = f_powf(self.a * self.d + self.b, self.g) + self.e;
|
|
let d2 = self.c * self.d + self.f;
|
|
|
|
if (d1 - d2).abs() > 0.1 {
|
|
return None;
|
|
}
|
|
let d = d1;
|
|
|
|
// y = (a * x + b)^g + e
|
|
// y - e = (a * x + b)^g
|
|
// (y - e)^(1/g) = a*x + b
|
|
// (y - e)^(1/g) - b = a*x
|
|
// (y - e)^(1/g)/a - b/a = x
|
|
// ((y - e)/a^g)^(1/g) - b/a = x
|
|
// ((1/(a^g)) * y - e/(a^g))^(1/g) - b/a = x
|
|
let a = 1. / f_powf(self.a, self.g);
|
|
let b = -self.e / f_powf(self.a, self.g);
|
|
let g = 1. / self.g;
|
|
let e = -self.b / self.a;
|
|
|
|
// y = c * x + f
|
|
// y - f = c * x
|
|
// y/c - f/c = x
|
|
let (c, f);
|
|
if d <= 0. {
|
|
c = 1.;
|
|
f = 0.;
|
|
} else {
|
|
c = 1. / self.c;
|
|
f = -self.f / self.c;
|
|
}
|
|
|
|
// if self.d > 0. and self.c == 0 as is likely with type 1 and 2 parametric function
|
|
// then c and f will not be finite.
|
|
if !(g.is_finite()
|
|
&& a.is_finite()
|
|
&& b.is_finite()
|
|
&& c.is_finite()
|
|
&& d.is_finite()
|
|
&& e.is_finite()
|
|
&& f.is_finite())
|
|
{
|
|
return None;
|
|
}
|
|
|
|
Some(ParametricCurve {
|
|
g,
|
|
a,
|
|
b,
|
|
c,
|
|
d,
|
|
e,
|
|
f,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn u8_fixed_8number_to_float(x: u16) -> f32 {
|
|
// 0x0000 = 0.
|
|
// 0x0100 = 1.
|
|
// 0xffff = 255 + 255/256
|
|
(x as i32 as f64 / 256.0) as f32
|
|
}
|
|
|
|
fn passthrough_table<T: PointeeSizeExpressible, const N: usize, const BIT_DEPTH: usize>()
|
|
-> Box<[f32; N]> {
|
|
let mut gamma_table = Box::new([0f32; N]);
|
|
let max_value = if T::FINITE {
|
|
(1 << BIT_DEPTH) - 1
|
|
} else {
|
|
T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
|
|
};
|
|
let cap_values = if T::FINITE {
|
|
(1u32 << BIT_DEPTH) as usize
|
|
} else {
|
|
T::NOT_FINITE_LINEAR_TABLE_SIZE
|
|
};
|
|
assert!(cap_values <= N, "Invalid lut table construction");
|
|
let scale_value = 1f64 / max_value as f64;
|
|
for (i, g) in gamma_table.iter_mut().enumerate().take(cap_values) {
|
|
*g = (i as f64 * scale_value) as f32;
|
|
}
|
|
|
|
gamma_table
|
|
}
|
|
|
|
fn linear_forward_table<T: PointeeSizeExpressible, const N: usize, const BIT_DEPTH: usize>(
|
|
gamma: u16,
|
|
) -> Box<[f32; N]> {
|
|
let mut gamma_table = Box::new([0f32; N]);
|
|
let gamma_float: f32 = u8_fixed_8number_to_float(gamma);
|
|
let max_value = if T::FINITE {
|
|
(1 << BIT_DEPTH) - 1
|
|
} else {
|
|
T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
|
|
};
|
|
let cap_values = if T::FINITE {
|
|
(1u32 << BIT_DEPTH) as usize
|
|
} else {
|
|
T::NOT_FINITE_LINEAR_TABLE_SIZE
|
|
};
|
|
assert!(cap_values <= N, "Invalid lut table construction");
|
|
let scale_value = 1f64 / max_value as f64;
|
|
for (i, g) in gamma_table.iter_mut().enumerate().take(cap_values) {
|
|
*g = f_pow(i as f64 * scale_value, gamma_float as f64) as f32;
|
|
}
|
|
|
|
gamma_table
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub(crate) fn lut_interp_linear_float(x: f32, table: &[f32]) -> f32 {
|
|
let value = x.min(1.).max(0.) * (table.len() - 1) as f32;
|
|
|
|
let upper: i32 = value.ceil() as i32;
|
|
let lower: i32 = value.floor() as i32;
|
|
|
|
let diff = upper as f32 - value;
|
|
let tu = table[upper as usize];
|
|
mlaf(neg_mlaf(tu, tu, diff), table[lower as usize], diff)
|
|
}
|
|
|
|
/// Lut interpolation float where values is already clamped
|
|
#[inline(always)]
|
|
#[allow(dead_code)]
|
|
pub(crate) fn lut_interp_linear_float_clamped(x: f32, table: &[f32]) -> f32 {
|
|
let value = x * (table.len() - 1) as f32;
|
|
|
|
let upper: i32 = value.ceil() as i32;
|
|
let lower: i32 = value.floor() as i32;
|
|
|
|
let diff = upper as f32 - value;
|
|
let tu = table[upper as usize];
|
|
mlaf(neg_mlaf(tu, tu, diff), table[lower as usize], diff)
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn lut_interp_linear(input_value: f64, table: &[u16]) -> f32 {
|
|
let mut input_value = input_value;
|
|
if table.is_empty() {
|
|
return input_value as f32;
|
|
}
|
|
|
|
input_value *= (table.len() - 1) as f64;
|
|
|
|
let upper: i32 = input_value.ceil() as i32;
|
|
let lower: i32 = input_value.floor() as i32;
|
|
let w0 = table[(upper as usize).min(table.len() - 1)] as f64;
|
|
let w1 = 1. - (upper as f64 - input_value);
|
|
let w2 = table[(lower as usize).min(table.len() - 1)] as f64;
|
|
let w3 = upper as f64 - input_value;
|
|
let value: f32 = mlaf(w2 * w3, w0, w1) as f32;
|
|
value * (1.0 / 65535.0)
|
|
}
|
|
|
|
fn linear_lut_interpolate<T: PointeeSizeExpressible, const N: usize, const BIT_DEPTH: usize>(
|
|
table: &[u16],
|
|
) -> Box<[f32; N]> {
|
|
let mut gamma_table = Box::new([0f32; N]);
|
|
let max_value = if T::FINITE {
|
|
(1 << BIT_DEPTH) - 1
|
|
} else {
|
|
T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
|
|
};
|
|
let cap_values = if T::FINITE {
|
|
(1u32 << BIT_DEPTH) as usize
|
|
} else {
|
|
T::NOT_FINITE_LINEAR_TABLE_SIZE
|
|
};
|
|
assert!(cap_values <= N, "Invalid lut table construction");
|
|
let scale_value = 1f64 / max_value as f64;
|
|
for (i, g) in gamma_table.iter_mut().enumerate().take(cap_values) {
|
|
*g = lut_interp_linear(i as f64 * scale_value, table);
|
|
}
|
|
gamma_table
|
|
}
|
|
|
|
fn linear_curve_parametric<T: PointeeSizeExpressible, const N: usize, const BIT_DEPTH: usize>(
|
|
params: &[f32],
|
|
) -> Option<Box<[f32; N]>> {
|
|
let params = ParametricCurve::new(params)?;
|
|
let mut gamma_table = Box::new([0f32; N]);
|
|
let max_value = if T::FINITE {
|
|
(1 << BIT_DEPTH) - 1
|
|
} else {
|
|
T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
|
|
};
|
|
let cap_value = if T::FINITE {
|
|
1 << BIT_DEPTH
|
|
} else {
|
|
T::NOT_FINITE_LINEAR_TABLE_SIZE
|
|
};
|
|
let scale_value = 1f32 / max_value as f32;
|
|
for (i, g) in gamma_table.iter_mut().enumerate().take(cap_value) {
|
|
let x = i as f32 * scale_value;
|
|
*g = m_clamp(params.eval(x), 0.0, 1.0);
|
|
}
|
|
Some(gamma_table)
|
|
}
|
|
|
|
fn linear_curve_parametric_s<const N: usize>(params: &[f32]) -> Option<Box<[f32; N]>> {
|
|
let params = ParametricCurve::new(params)?;
|
|
let mut gamma_table = Box::new([0f32; N]);
|
|
let scale_value = 1f32 / (N - 1) as f32;
|
|
for (i, g) in gamma_table.iter_mut().enumerate().take(N) {
|
|
let x = i as f32 * scale_value;
|
|
*g = m_clamp(params.eval(x), 0.0, 1.0);
|
|
}
|
|
Some(gamma_table)
|
|
}
|
|
|
|
pub(crate) fn make_gamma_linear_table<
|
|
T: Default + Copy + 'static + PointeeSizeExpressible,
|
|
const BUCKET: usize,
|
|
const N: usize,
|
|
>(
|
|
bit_depth: usize,
|
|
) -> Box<[T; BUCKET]>
|
|
where
|
|
f32: AsPrimitive<T>,
|
|
{
|
|
let mut table = Box::new([T::default(); BUCKET]);
|
|
let max_range = if T::FINITE {
|
|
(1f64 / ((N - 1) as f64 / (1 << bit_depth) as f64)) as f32
|
|
} else {
|
|
(1f64 / ((N - 1) as f64)) as f32
|
|
};
|
|
for (v, output) in table.iter_mut().take(N).enumerate() {
|
|
if T::FINITE {
|
|
*output = (v as f32 * max_range).round().as_();
|
|
} else {
|
|
*output = (v as f32 * max_range).as_();
|
|
}
|
|
}
|
|
table
|
|
}
|
|
|
|
#[inline]
|
|
fn lut_interp_linear_gamma_impl<
|
|
T: Default + Copy + 'static + PointeeSizeExpressible,
|
|
const N: usize,
|
|
const BIT_DEPTH: usize,
|
|
>(
|
|
input_value: u32,
|
|
table: &[u16],
|
|
) -> T
|
|
where
|
|
u32: AsPrimitive<T>,
|
|
{
|
|
// Start scaling input_value to the length of the array: GAMMA_CAP*(length-1).
|
|
// We'll divide out the GAMMA_CAP next
|
|
let mut value: u32 = input_value * (table.len() - 1) as u32;
|
|
let cap_value = N - 1;
|
|
// equivalent to ceil(value/GAMMA_CAP)
|
|
let upper: u32 = value.div_ceil(cap_value as u32);
|
|
// equivalent to floor(value/GAMMA_CAP)
|
|
let lower: u32 = value / cap_value as u32;
|
|
// interp is the distance from upper to value scaled to 0..GAMMA_CAP
|
|
let interp: u32 = value % cap_value as u32;
|
|
let lw_value = table[lower as usize];
|
|
let hw_value = table[upper as usize];
|
|
// the table values range from 0..65535
|
|
value = mlaf(
|
|
hw_value as u32 * interp,
|
|
lw_value as u32,
|
|
(N - 1) as u32 - interp,
|
|
); // 0..(65535*GAMMA_CAP)
|
|
|
|
// round and scale
|
|
let max_colors = if T::FINITE { (1 << BIT_DEPTH) - 1 } else { 1 };
|
|
value += (cap_value * 65535 / max_colors / 2) as u32; // scale to 0...max_colors
|
|
value /= (cap_value * 65535 / max_colors) as u32;
|
|
value.as_()
|
|
}
|
|
|
|
#[inline]
|
|
fn lut_interp_linear_gamma_impl_f32<
|
|
T: Default + Copy + 'static + PointeeSizeExpressible,
|
|
const N: usize,
|
|
const BIT_DEPTH: usize,
|
|
>(
|
|
input_value: u32,
|
|
table: &[u16],
|
|
) -> T
|
|
where
|
|
f32: AsPrimitive<T>,
|
|
{
|
|
// Start scaling input_value to the length of the array: GAMMA_CAP*(length-1).
|
|
// We'll divide out the GAMMA_CAP next
|
|
let guess: u32 = input_value * (table.len() - 1) as u32;
|
|
let cap_value = N - 1;
|
|
// equivalent to ceil(value/GAMMA_CAP)
|
|
let upper: u32 = guess.div_ceil(cap_value as u32);
|
|
// equivalent to floor(value/GAMMA_CAP)
|
|
let lower: u32 = guess / cap_value as u32;
|
|
// interp is the distance from upper to value scaled to 0..GAMMA_CAP
|
|
let interp: u32 = guess % cap_value as u32;
|
|
let lw_value = table[lower as usize];
|
|
let hw_value = table[upper as usize];
|
|
// the table values range from 0..65535
|
|
let mut value = mlaf(
|
|
hw_value as f32 * interp as f32,
|
|
lw_value as f32,
|
|
(N - 1) as f32 - interp as f32,
|
|
); // 0..(65535*GAMMA_CAP)
|
|
|
|
// round and scale
|
|
let max_colors = if T::FINITE { (1 << BIT_DEPTH) - 1 } else { 1 };
|
|
value /= (cap_value * 65535 / max_colors) as f32;
|
|
value.as_()
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
pub trait GammaLutInterpolate {
|
|
fn gamma_lut_interp<
|
|
T: Default + Copy + 'static + PointeeSizeExpressible,
|
|
const N: usize,
|
|
const BIT_DEPTH: usize,
|
|
>(
|
|
input_value: u32,
|
|
table: &[u16],
|
|
) -> T
|
|
where
|
|
u32: AsPrimitive<T>,
|
|
f32: AsPrimitive<T>;
|
|
}
|
|
|
|
macro_rules! gamma_lut_interp_fixed {
|
|
($i_type: ident) => {
|
|
impl GammaLutInterpolate for $i_type {
|
|
#[inline]
|
|
fn gamma_lut_interp<
|
|
T: Default + Copy + 'static + PointeeSizeExpressible,
|
|
const N: usize,
|
|
const BIT_DEPTH: usize,
|
|
>(
|
|
input_value: u32,
|
|
table: &[u16],
|
|
) -> T
|
|
where
|
|
u32: AsPrimitive<T>,
|
|
{
|
|
lut_interp_linear_gamma_impl::<T, N, BIT_DEPTH>(input_value, table)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
gamma_lut_interp_fixed!(u8);
|
|
gamma_lut_interp_fixed!(u16);
|
|
|
|
macro_rules! gammu_lut_interp_float {
|
|
($f_type: ident) => {
|
|
impl GammaLutInterpolate for $f_type {
|
|
#[inline]
|
|
fn gamma_lut_interp<
|
|
T: Default + Copy + 'static + PointeeSizeExpressible,
|
|
const N: usize,
|
|
const BIT_DEPTH: usize,
|
|
>(
|
|
input_value: u32,
|
|
table: &[u16],
|
|
) -> T
|
|
where
|
|
f32: AsPrimitive<T>,
|
|
u32: AsPrimitive<T>,
|
|
{
|
|
lut_interp_linear_gamma_impl_f32::<T, N, BIT_DEPTH>(input_value, table)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
gammu_lut_interp_float!(f32);
|
|
gammu_lut_interp_float!(f64);
|
|
|
|
pub(crate) fn make_gamma_lut<
|
|
T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate,
|
|
const BUCKET: usize,
|
|
const N: usize,
|
|
const BIT_DEPTH: usize,
|
|
>(
|
|
table: &[u16],
|
|
) -> Box<[T; BUCKET]>
|
|
where
|
|
u32: AsPrimitive<T>,
|
|
f32: AsPrimitive<T>,
|
|
{
|
|
let mut new_table = Box::new([T::default(); BUCKET]);
|
|
for (v, output) in new_table.iter_mut().take(N).enumerate() {
|
|
*output = T::gamma_lut_interp::<T, N, BIT_DEPTH>(v as u32, table);
|
|
}
|
|
new_table
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn lut_interp_linear16(input_value: u16, table: &[u16]) -> u16 {
|
|
// Start scaling input_value to the length of the array: 65535*(length-1).
|
|
// We'll divide out the 65535 next
|
|
let mut value: u32 = input_value as u32 * (table.len() as u32 - 1);
|
|
let upper: u16 = value.div_ceil(65535) as u16; // equivalent to ceil(value/65535)
|
|
let lower: u16 = (value / 65535) as u16; // equivalent to floor(value/65535)
|
|
// interp is the distance from upper to value scaled to 0..65535
|
|
let interp: u32 = value % 65535; // 0..65535*65535
|
|
value = (table[upper as usize] as u32 * interp
|
|
+ table[lower as usize] as u32 * (65535 - interp))
|
|
/ 65535;
|
|
value as u16
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn lut_interp_linear16_boxed<const N: usize>(input_value: u16, table: &[u16; N]) -> u16 {
|
|
// Start scaling input_value to the length of the array: 65535*(length-1).
|
|
// We'll divide out the 65535 next
|
|
let mut value: u32 = input_value as u32 * (table.len() as u32 - 1);
|
|
let upper: u16 = value.div_ceil(65535) as u16; // equivalent to ceil(value/65535)
|
|
let lower: u16 = (value / 65535) as u16; // equivalent to floor(value/65535)
|
|
// interp is the distance from upper to value scaled to 0..65535
|
|
let interp: u32 = value % 65535; // 0..65535*65535
|
|
value = (table[upper as usize] as u32 * interp
|
|
+ table[lower as usize] as u32 * (65535 - interp))
|
|
/ 65535;
|
|
value as u16
|
|
}
|
|
|
|
fn make_gamma_pow_table<
|
|
T: Default + Copy + 'static + PointeeSizeExpressible,
|
|
const BUCKET: usize,
|
|
const N: usize,
|
|
>(
|
|
gamma: f32,
|
|
bit_depth: usize,
|
|
) -> Box<[T; BUCKET]>
|
|
where
|
|
f32: AsPrimitive<T>,
|
|
{
|
|
let mut table = Box::new([T::default(); BUCKET]);
|
|
let scale = 1f32 / (N - 1) as f32;
|
|
let cap = ((1 << bit_depth) - 1) as f32;
|
|
if T::FINITE {
|
|
for (v, output) in table.iter_mut().take(N).enumerate() {
|
|
*output = (cap * f_powf(v as f32 * scale, gamma)).round().as_();
|
|
}
|
|
} else {
|
|
for (v, output) in table.iter_mut().take(N).enumerate() {
|
|
*output = (cap * f_powf(v as f32 * scale, gamma)).as_();
|
|
}
|
|
}
|
|
table
|
|
}
|
|
|
|
fn make_gamma_parametric_table<
|
|
T: Default + Copy + 'static + PointeeSizeExpressible,
|
|
const BUCKET: usize,
|
|
const N: usize,
|
|
const BIT_DEPTH: usize,
|
|
>(
|
|
parametric_curve: ParametricCurve,
|
|
) -> Box<[T; BUCKET]>
|
|
where
|
|
f32: AsPrimitive<T>,
|
|
{
|
|
let mut table = Box::new([T::default(); BUCKET]);
|
|
let scale = 1f32 / (N - 1) as f32;
|
|
let cap = ((1 << BIT_DEPTH) - 1) as f32;
|
|
if T::FINITE {
|
|
for (v, output) in table.iter_mut().take(N).enumerate() {
|
|
*output = (cap * parametric_curve.eval(v as f32 * scale))
|
|
.round()
|
|
.as_();
|
|
}
|
|
} else {
|
|
for (v, output) in table.iter_mut().take(N).enumerate() {
|
|
*output = (cap * parametric_curve.eval(v as f32 * scale)).as_();
|
|
}
|
|
}
|
|
table
|
|
}
|
|
|
|
#[inline]
|
|
fn compare_parametric(src: &[f32], dst: &[f32]) -> bool {
|
|
for (src, dst) in src.iter().zip(dst.iter()) {
|
|
if (src - dst).abs() > 1e-4 {
|
|
return false;
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
fn lut_inverse_interp16(value: u16, lut_table: &[u16]) -> u16 {
|
|
let mut l: i32 = 1; // 'int' Give spacing for negative values
|
|
let mut r: i32 = 0x10000;
|
|
let mut x: i32 = 0;
|
|
let mut res: i32;
|
|
let length = lut_table.len() as i32;
|
|
|
|
let mut num_zeroes: i32 = 0;
|
|
for &item in lut_table.iter() {
|
|
if item == 0 {
|
|
num_zeroes += 1
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if num_zeroes == 0 && value as i32 == 0 {
|
|
return 0u16;
|
|
}
|
|
let mut num_of_polys: i32 = 0;
|
|
for &item in lut_table.iter().rev() {
|
|
if item == 0xffff {
|
|
num_of_polys += 1
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
// Does the curve belong to this case?
|
|
if num_zeroes > 1 || num_of_polys > 1 {
|
|
let a_0: i32;
|
|
let b_0: i32;
|
|
// Identify if value fall downto 0 or FFFF zone
|
|
if value as i32 == 0 {
|
|
return 0u16;
|
|
}
|
|
// if (Value == 0xFFFF) return 0xFFFF;
|
|
// else restrict to valid zone
|
|
if num_zeroes > 1 {
|
|
a_0 = (num_zeroes - 1) * 0xffff / (length - 1);
|
|
l = a_0 - 1
|
|
}
|
|
if num_of_polys > 1 {
|
|
b_0 = (length - 1 - num_of_polys) * 0xffff / (length - 1);
|
|
r = b_0 + 1
|
|
}
|
|
}
|
|
if r <= l {
|
|
// If this happens LutTable is not invertible
|
|
return 0u16;
|
|
}
|
|
|
|
while r > l {
|
|
x = (l + r) / 2;
|
|
res = lut_interp_linear16((x - 1) as u16, lut_table) as i32;
|
|
if res == value as i32 {
|
|
// Found exact match.
|
|
return (x - 1) as u16;
|
|
}
|
|
if res > value as i32 {
|
|
r = x - 1
|
|
} else {
|
|
l = x + 1
|
|
}
|
|
}
|
|
|
|
// Not found, should we interpolate?
|
|
|
|
// Get surrounding nodes
|
|
debug_assert!(x >= 1);
|
|
|
|
let val2: f64 = (length - 1) as f64 * ((x - 1) as f64 / 65535.0);
|
|
let cell0: i32 = val2.floor() as i32;
|
|
let cell1: i32 = val2.ceil() as i32;
|
|
if cell0 == cell1 {
|
|
return x as u16;
|
|
}
|
|
|
|
let y0: f64 = lut_table[cell0 as usize] as f64;
|
|
let x0: f64 = 65535.0 * cell0 as f64 / (length - 1) as f64;
|
|
let y1: f64 = lut_table[cell1 as usize] as f64;
|
|
let x1: f64 = 65535.0 * cell1 as f64 / (length - 1) as f64;
|
|
let a: f64 = (y1 - y0) / (x1 - x0);
|
|
let b: f64 = mlaf(y0, -a, x0);
|
|
if a.abs() < 0.01f64 {
|
|
return x as u16;
|
|
}
|
|
let f: f64 = (value as i32 as f64 - b) / a;
|
|
if f < 0.0 {
|
|
return 0u16;
|
|
}
|
|
if f >= 65535.0 {
|
|
return 0xffffu16;
|
|
}
|
|
(f + 0.5f64).floor() as u16
|
|
}
|
|
|
|
fn lut_inverse_interp16_boxed<const N: usize>(value: u16, lut_table: &[u16; N]) -> u16 {
|
|
let mut l: i32 = 1; // 'int' Give spacing for negative values
|
|
let mut r: i32 = 0x10000;
|
|
let mut x: i32 = 0;
|
|
let mut res: i32;
|
|
let length = lut_table.len() as i32;
|
|
|
|
let mut num_zeroes: i32 = 0;
|
|
for &item in lut_table.iter() {
|
|
if item == 0 {
|
|
num_zeroes += 1
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if num_zeroes == 0 && value as i32 == 0 {
|
|
return 0u16;
|
|
}
|
|
let mut num_of_polys: i32 = 0;
|
|
for &item in lut_table.iter().rev() {
|
|
if item == 0xffff {
|
|
num_of_polys += 1
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
// Does the curve belong to this case?
|
|
if num_zeroes > 1 || num_of_polys > 1 {
|
|
let a_0: i32;
|
|
let b_0: i32;
|
|
// Identify if value fall downto 0 or FFFF zone
|
|
if value as i32 == 0 {
|
|
return 0u16;
|
|
}
|
|
// if (Value == 0xFFFF) return 0xFFFF;
|
|
// else restrict to valid zone
|
|
if num_zeroes > 1 {
|
|
a_0 = (num_zeroes - 1) * 0xffff / (length - 1);
|
|
l = a_0 - 1
|
|
}
|
|
if num_of_polys > 1 {
|
|
b_0 = (length - 1 - num_of_polys) * 0xffff / (length - 1);
|
|
r = b_0 + 1
|
|
}
|
|
}
|
|
if r <= l {
|
|
// If this happens LutTable is not invertible
|
|
return 0u16;
|
|
}
|
|
|
|
while r > l {
|
|
x = (l + r) / 2;
|
|
res = lut_interp_linear16_boxed((x - 1) as u16, lut_table) as i32;
|
|
if res == value as i32 {
|
|
// Found exact match.
|
|
return (x - 1) as u16;
|
|
}
|
|
if res > value as i32 {
|
|
r = x - 1
|
|
} else {
|
|
l = x + 1
|
|
}
|
|
}
|
|
|
|
// Not found, should we interpolate?
|
|
|
|
// Get surrounding nodes
|
|
debug_assert!(x >= 1);
|
|
|
|
let val2: f64 = (length - 1) as f64 * ((x - 1) as f64 / 65535.0);
|
|
let cell0: i32 = val2.floor() as i32;
|
|
let cell1: i32 = val2.ceil() as i32;
|
|
if cell0 == cell1 {
|
|
return x as u16;
|
|
}
|
|
|
|
let y0: f64 = lut_table[cell0 as usize] as f64;
|
|
let x0: f64 = 65535.0 * cell0 as f64 / (length - 1) as f64;
|
|
let y1: f64 = lut_table[cell1 as usize] as f64;
|
|
let x1: f64 = 65535.0 * cell1 as f64 / (length - 1) as f64;
|
|
let a: f64 = (y1 - y0) / (x1 - x0);
|
|
let b: f64 = mlaf(y0, -a, x0);
|
|
if a.abs() < 0.01f64 {
|
|
return x as u16;
|
|
}
|
|
let f: f64 = (value as i32 as f64 - b) / a;
|
|
if f < 0.0 {
|
|
return 0u16;
|
|
}
|
|
if f >= 65535.0 {
|
|
return 0xffffu16;
|
|
}
|
|
(f + 0.5f64).floor() as u16
|
|
}
|
|
|
|
fn invert_lut(table: &[u16], out_length: usize) -> Vec<u16> {
|
|
// For now, we invert the lut by creating a lut of size out_length
|
|
// and attempting to look up a value for each entry using lut_inverse_interp16
|
|
let mut output = vec![0u16; out_length];
|
|
let scale_value = 65535f64 / (out_length - 1) as f64;
|
|
for (i, out) in output.iter_mut().enumerate() {
|
|
let x: f64 = i as f64 * scale_value;
|
|
let input: u16 = (x + 0.5f64).floor() as u16;
|
|
*out = lut_inverse_interp16(input, table);
|
|
}
|
|
output
|
|
}
|
|
|
|
fn invert_lut_boxed<const N: usize>(table: &[u16; N], out_length: usize) -> Vec<u16> {
|
|
// For now, we invert the lut by creating a lut of size out_length
|
|
// and attempting to look up a value for each entry using lut_inverse_interp16
|
|
let mut output = vec![0u16; out_length];
|
|
let scale_value = 65535f64 / (out_length - 1) as f64;
|
|
for (i, out) in output.iter_mut().enumerate() {
|
|
let x: f64 = i as f64 * scale_value;
|
|
let input: u16 = (x + 0.5f64).floor() as u16;
|
|
*out = lut_inverse_interp16_boxed(input, table);
|
|
}
|
|
output
|
|
}
|
|
|
|
impl ToneReprCurve {
|
|
pub(crate) fn to_clut(&self) -> Result<Vec<f32>, CmsError> {
|
|
match self {
|
|
ToneReprCurve::Lut(lut) => {
|
|
if lut.is_empty() {
|
|
let passthrough_table = passthrough_table::<f32, 16384, 1>();
|
|
Ok(passthrough_table.to_vec())
|
|
} else {
|
|
Ok(lut
|
|
.iter()
|
|
.map(|&x| x as f32 * (1. / 65535.))
|
|
.collect::<Vec<_>>())
|
|
}
|
|
}
|
|
ToneReprCurve::Parametric(_) => {
|
|
let curve = self
|
|
.build_linearize_table::<f32, 65535, 1>()
|
|
.ok_or(CmsError::InvalidTrcCurve)?;
|
|
let max_value = f32::NOT_FINITE_LINEAR_TABLE_SIZE - 1;
|
|
let sliced = &curve[..max_value];
|
|
Ok(sliced.to_vec())
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn build_linearize_table<
|
|
T: PointeeSizeExpressible,
|
|
const N: usize,
|
|
const BIT_DEPTH: usize,
|
|
>(
|
|
&self,
|
|
) -> Option<Box<[f32; N]>> {
|
|
match self {
|
|
ToneReprCurve::Parametric(params) => linear_curve_parametric::<T, N, BIT_DEPTH>(params),
|
|
ToneReprCurve::Lut(data) => match data.len() {
|
|
0 => Some(passthrough_table::<T, N, BIT_DEPTH>()),
|
|
1 => Some(linear_forward_table::<T, N, BIT_DEPTH>(data[0])),
|
|
_ => Some(linear_lut_interpolate::<T, N, BIT_DEPTH>(data)),
|
|
},
|
|
}
|
|
}
|
|
|
|
pub(crate) fn build_gamma_table<
|
|
T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate,
|
|
const BUCKET: usize,
|
|
const N: usize,
|
|
const BIT_DEPTH: usize,
|
|
>(
|
|
&self,
|
|
) -> Option<Box<[T; BUCKET]>>
|
|
where
|
|
f32: AsPrimitive<T>,
|
|
u32: AsPrimitive<T>,
|
|
{
|
|
match self {
|
|
ToneReprCurve::Parametric(params) => {
|
|
if params.len() == 5 {
|
|
let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045];
|
|
let rec709_params = create_rec709_parametric();
|
|
|
|
let mut lc_params: [f32; 5] = [0.; 5];
|
|
for (dst, src) in lc_params.iter_mut().zip(params.iter()) {
|
|
*dst = *src;
|
|
}
|
|
|
|
if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) {
|
|
return Some(
|
|
TransferCharacteristics::Srgb
|
|
.make_gamma_table::<T, BUCKET, N>(BIT_DEPTH),
|
|
);
|
|
}
|
|
|
|
if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) {
|
|
return Some(
|
|
TransferCharacteristics::Bt709
|
|
.make_gamma_table::<T, BUCKET, N>(BIT_DEPTH),
|
|
);
|
|
}
|
|
}
|
|
|
|
let parametric_curve = ParametricCurve::new(params);
|
|
if let Some(v) = parametric_curve?
|
|
.invert()
|
|
.map(|x| make_gamma_parametric_table::<T, BUCKET, N, BIT_DEPTH>(x))
|
|
{
|
|
return Some(v);
|
|
}
|
|
|
|
let mut gamma_table_uint = Box::new([0; N]);
|
|
|
|
let inverted_size: usize = N;
|
|
let gamma_table = linear_curve_parametric_s::<N>(params)?;
|
|
for (&src, dst) in gamma_table.iter().zip(gamma_table_uint.iter_mut()) {
|
|
*dst = (src * 65535f32) as u16;
|
|
}
|
|
let inverted = invert_lut_boxed(&gamma_table_uint, inverted_size);
|
|
Some(make_gamma_lut::<T, BUCKET, N, BIT_DEPTH>(&inverted))
|
|
}
|
|
ToneReprCurve::Lut(data) => match data.len() {
|
|
0 => Some(make_gamma_linear_table::<T, BUCKET, N>(BIT_DEPTH)),
|
|
1 => Some(make_gamma_pow_table::<T, BUCKET, N>(
|
|
1. / u8_fixed_8number_to_float(data[0]),
|
|
BIT_DEPTH,
|
|
)),
|
|
_ => {
|
|
let mut inverted_size = data.len();
|
|
if inverted_size < 256 {
|
|
inverted_size = 256
|
|
}
|
|
let inverted = invert_lut(data, inverted_size);
|
|
Some(make_gamma_lut::<T, BUCKET, N, BIT_DEPTH>(&inverted))
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ColorProfile {
|
|
/// Produces LUT for 8 bit tone linearization
|
|
pub fn build_8bit_lin_table(
|
|
&self,
|
|
trc: &Option<ToneReprCurve>,
|
|
) -> Result<Box<[f32; 256]>, CmsError> {
|
|
trc.as_ref()
|
|
.and_then(|trc| trc.build_linearize_table::<u8, 256, 8>())
|
|
.ok_or(CmsError::BuildTransferFunction)
|
|
}
|
|
|
|
/// Produces LUT for Gray transfer curve with N depth
|
|
pub fn build_gray_linearize_table<
|
|
T: PointeeSizeExpressible,
|
|
const N: usize,
|
|
const BIT_DEPTH: usize,
|
|
>(
|
|
&self,
|
|
) -> Result<Box<[f32; N]>, CmsError> {
|
|
self.gray_trc
|
|
.as_ref()
|
|
.and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
|
|
.ok_or(CmsError::BuildTransferFunction)
|
|
}
|
|
|
|
/// Produces LUT for Red transfer curve with N depth
|
|
pub fn build_r_linearize_table<
|
|
T: PointeeSizeExpressible,
|
|
const N: usize,
|
|
const BIT_DEPTH: usize,
|
|
>(
|
|
&self,
|
|
use_cicp: bool,
|
|
) -> Result<Box<[f32; N]>, CmsError> {
|
|
if use_cicp {
|
|
if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
|
|
if tc.has_transfer_curve() {
|
|
return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>());
|
|
}
|
|
}
|
|
}
|
|
self.red_trc
|
|
.as_ref()
|
|
.and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
|
|
.ok_or(CmsError::BuildTransferFunction)
|
|
}
|
|
|
|
/// Produces LUT for Green transfer curve with N depth
|
|
pub fn build_g_linearize_table<
|
|
T: PointeeSizeExpressible,
|
|
const N: usize,
|
|
const BIT_DEPTH: usize,
|
|
>(
|
|
&self,
|
|
use_cicp: bool,
|
|
) -> Result<Box<[f32; N]>, CmsError> {
|
|
if use_cicp {
|
|
if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
|
|
if tc.has_transfer_curve() {
|
|
return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>());
|
|
}
|
|
}
|
|
}
|
|
self.green_trc
|
|
.as_ref()
|
|
.and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
|
|
.ok_or(CmsError::BuildTransferFunction)
|
|
}
|
|
|
|
/// Produces LUT for Blue transfer curve with N depth
|
|
pub fn build_b_linearize_table<
|
|
T: PointeeSizeExpressible,
|
|
const N: usize,
|
|
const BIT_DEPTH: usize,
|
|
>(
|
|
&self,
|
|
use_cicp: bool,
|
|
) -> Result<Box<[f32; N]>, CmsError> {
|
|
if use_cicp {
|
|
if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
|
|
if tc.has_transfer_curve() {
|
|
return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>());
|
|
}
|
|
}
|
|
}
|
|
self.blue_trc
|
|
.as_ref()
|
|
.and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>())
|
|
.ok_or(CmsError::BuildTransferFunction)
|
|
}
|
|
|
|
/// Build gamma table for 8 bit depth
|
|
/// Only 4092 first bins are used and values scaled in 0..255
|
|
pub fn build_8bit_gamma_table(
|
|
&self,
|
|
trc: &Option<ToneReprCurve>,
|
|
use_cicp: bool,
|
|
) -> Result<Box<[u16; 65536]>, CmsError> {
|
|
self.build_gamma_table::<u16, 65536, 4092, 8>(trc, use_cicp)
|
|
}
|
|
|
|
/// Build gamma table for 10 bit depth
|
|
/// Only 8192 first bins are used and values scaled in 0..1023
|
|
pub fn build_10bit_gamma_table(
|
|
&self,
|
|
trc: &Option<ToneReprCurve>,
|
|
use_cicp: bool,
|
|
) -> Result<Box<[u16; 65536]>, CmsError> {
|
|
self.build_gamma_table::<u16, 65536, 8192, 10>(trc, use_cicp)
|
|
}
|
|
|
|
/// Build gamma table for 12 bit depth
|
|
/// Only 16384 first bins are used and values scaled in 0..4095
|
|
pub fn build_12bit_gamma_table(
|
|
&self,
|
|
trc: &Option<ToneReprCurve>,
|
|
use_cicp: bool,
|
|
) -> Result<Box<[u16; 65536]>, CmsError> {
|
|
self.build_gamma_table::<u16, 65536, 16384, 12>(trc, use_cicp)
|
|
}
|
|
|
|
/// Build gamma table for 16 bit depth
|
|
/// Only 16384 first bins are used and values scaled in 0..65535
|
|
pub fn build_16bit_gamma_table(
|
|
&self,
|
|
trc: &Option<ToneReprCurve>,
|
|
use_cicp: bool,
|
|
) -> Result<Box<[u16; 65536]>, CmsError> {
|
|
self.build_gamma_table::<u16, 65536, 65536, 16>(trc, use_cicp)
|
|
}
|
|
|
|
/// Builds gamma table checking CICP for Transfer characteristics first.
|
|
pub fn build_gamma_table<
|
|
T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate,
|
|
const BUCKET: usize,
|
|
const N: usize,
|
|
const BIT_DEPTH: usize,
|
|
>(
|
|
&self,
|
|
trc: &Option<ToneReprCurve>,
|
|
use_cicp: bool,
|
|
) -> Result<Box<[T; BUCKET]>, CmsError>
|
|
where
|
|
f32: AsPrimitive<T>,
|
|
u32: AsPrimitive<T>,
|
|
{
|
|
if use_cicp {
|
|
if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
|
|
if tc.has_transfer_curve() {
|
|
return Ok(tc.make_gamma_table::<T, BUCKET, N>(BIT_DEPTH));
|
|
}
|
|
}
|
|
}
|
|
trc.as_ref()
|
|
.and_then(|trc| trc.build_gamma_table::<T, BUCKET, N, BIT_DEPTH>())
|
|
.ok_or(CmsError::BuildTransferFunction)
|
|
}
|
|
|
|
/// Checks if profile gamma can work in extended precision and we have implementation for this
|
|
pub(crate) fn try_extended_gamma_evaluator(
|
|
&self,
|
|
) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> {
|
|
if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
|
|
if tc.has_transfer_curve() {
|
|
return Some(Box::new(ToneCurveCicpEvaluator {
|
|
rgb_trc: tc.extended_gamma_tristimulus(),
|
|
trc: tc.extended_gamma_single(),
|
|
}));
|
|
}
|
|
}
|
|
if !self.are_all_trc_the_same() {
|
|
return None;
|
|
}
|
|
let reference_trc = if self.color_space == DataColorSpace::Gray {
|
|
self.gray_trc.as_ref()
|
|
} else {
|
|
self.red_trc.as_ref()
|
|
};
|
|
if let Some(red_trc) = reference_trc {
|
|
return Self::make_gamma_evaluator_all_the_same(red_trc);
|
|
}
|
|
None
|
|
}
|
|
|
|
fn make_gamma_evaluator_all_the_same(
|
|
red_trc: &ToneReprCurve,
|
|
) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> {
|
|
match red_trc {
|
|
ToneReprCurve::Lut(lut) => {
|
|
if lut.is_empty() {
|
|
return Some(Box::new(ToneCurveEvaluatorLinear {}));
|
|
}
|
|
if lut.len() == 1 {
|
|
let gamma = 1. / u8_fixed_8number_to_float(lut[0]);
|
|
return Some(Box::new(ToneCurveEvaluatorPureGamma { gamma }));
|
|
}
|
|
None
|
|
}
|
|
ToneReprCurve::Parametric(params) => {
|
|
if params.len() == 5 {
|
|
let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045];
|
|
let rec709_params = create_rec709_parametric();
|
|
|
|
let mut lc_params: [f32; 5] = [0.; 5];
|
|
for (dst, src) in lc_params.iter_mut().zip(params.iter()) {
|
|
*dst = *src;
|
|
}
|
|
|
|
if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) {
|
|
return Some(Box::new(ToneCurveCicpEvaluator {
|
|
rgb_trc: TransferCharacteristics::Srgb.extended_gamma_tristimulus(),
|
|
trc: TransferCharacteristics::Srgb.extended_gamma_single(),
|
|
}));
|
|
}
|
|
|
|
if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) {
|
|
return Some(Box::new(ToneCurveCicpEvaluator {
|
|
rgb_trc: TransferCharacteristics::Bt709.extended_gamma_tristimulus(),
|
|
trc: TransferCharacteristics::Bt709.extended_gamma_single(),
|
|
}));
|
|
}
|
|
}
|
|
|
|
let parametric_curve = ParametricCurve::new(params);
|
|
if let Some(v) = parametric_curve?.invert() {
|
|
return Some(Box::new(ToneCurveParametricEvaluator { parametric: v }));
|
|
}
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Check if all TRC are the same
|
|
pub(crate) fn are_all_trc_the_same(&self) -> bool {
|
|
if self.color_space == DataColorSpace::Gray {
|
|
return true;
|
|
}
|
|
if let (Some(red_trc), Some(green_trc), Some(blue_trc)) =
|
|
(&self.red_trc, &self.green_trc, &self.blue_trc)
|
|
{
|
|
if !matches!(
|
|
(red_trc, green_trc, blue_trc),
|
|
(
|
|
ToneReprCurve::Lut(_),
|
|
ToneReprCurve::Lut(_),
|
|
ToneReprCurve::Lut(_),
|
|
) | (
|
|
ToneReprCurve::Parametric(_),
|
|
ToneReprCurve::Parametric(_),
|
|
ToneReprCurve::Parametric(_)
|
|
)
|
|
) {
|
|
return false;
|
|
}
|
|
if let (ToneReprCurve::Lut(lut0), ToneReprCurve::Lut(lut1), ToneReprCurve::Lut(lut2)) =
|
|
(red_trc, green_trc, blue_trc)
|
|
{
|
|
if lut0 == lut1 || lut1 == lut2 {
|
|
return true;
|
|
}
|
|
}
|
|
if let (
|
|
ToneReprCurve::Parametric(lut0),
|
|
ToneReprCurve::Parametric(lut1),
|
|
ToneReprCurve::Parametric(lut2),
|
|
) = (red_trc, green_trc, blue_trc)
|
|
{
|
|
if lut0 == lut1 || lut1 == lut2 {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// Checks if profile is matrix shaper, have same TRC and TRC is linear.
|
|
pub(crate) fn is_linear_matrix_shaper(&self) -> bool {
|
|
if !self.is_matrix_shaper() {
|
|
return false;
|
|
}
|
|
if !self.are_all_trc_the_same() {
|
|
return false;
|
|
}
|
|
if let Some(red_trc) = &self.red_trc {
|
|
return match red_trc {
|
|
ToneReprCurve::Lut(lut) => {
|
|
if lut.is_empty() {
|
|
return true;
|
|
}
|
|
if is_curve_linear16(lut) {
|
|
return true;
|
|
}
|
|
false
|
|
}
|
|
ToneReprCurve::Parametric(params) => {
|
|
if let Some(curve) = ParametricCurve::new(params) {
|
|
return curve.is_linear();
|
|
}
|
|
false
|
|
}
|
|
};
|
|
}
|
|
false
|
|
}
|
|
|
|
/// Checks if profile linearization can work in extended precision and we have implementation for this
|
|
pub(crate) fn try_extended_linearizing_evaluator(
|
|
&self,
|
|
) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> {
|
|
if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) {
|
|
if tc.has_transfer_curve() {
|
|
return Some(Box::new(ToneCurveCicpEvaluator {
|
|
rgb_trc: tc.extended_linear_tristimulus(),
|
|
trc: tc.extended_linear_single(),
|
|
}));
|
|
}
|
|
}
|
|
if !self.are_all_trc_the_same() {
|
|
return None;
|
|
}
|
|
let reference_trc = if self.color_space == DataColorSpace::Gray {
|
|
self.gray_trc.as_ref()
|
|
} else {
|
|
self.red_trc.as_ref()
|
|
};
|
|
if let Some(red_trc) = reference_trc {
|
|
if let Some(value) = Self::make_linear_curve_evaluator_all_the_same(red_trc) {
|
|
return value;
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn make_linear_curve_evaluator_all_the_same(
|
|
evaluator_curve: &ToneReprCurve,
|
|
) -> Option<Option<Box<dyn ToneCurveEvaluator + Send + Sync>>> {
|
|
match evaluator_curve {
|
|
ToneReprCurve::Lut(lut) => {
|
|
if lut.is_empty() {
|
|
return Some(Some(Box::new(ToneCurveEvaluatorLinear {})));
|
|
}
|
|
if lut.len() == 1 {
|
|
let gamma = u8_fixed_8number_to_float(lut[0]);
|
|
return Some(Some(Box::new(ToneCurveEvaluatorPureGamma { gamma })));
|
|
}
|
|
}
|
|
ToneReprCurve::Parametric(params) => {
|
|
if params.len() == 5 {
|
|
let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045];
|
|
let rec709_params = create_rec709_parametric();
|
|
|
|
let mut lc_params: [f32; 5] = [0.; 5];
|
|
for (dst, src) in lc_params.iter_mut().zip(params.iter()) {
|
|
*dst = *src;
|
|
}
|
|
|
|
if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) {
|
|
return Some(Some(Box::new(ToneCurveCicpEvaluator {
|
|
rgb_trc: TransferCharacteristics::Srgb.extended_linear_tristimulus(),
|
|
trc: TransferCharacteristics::Srgb.extended_linear_single(),
|
|
})));
|
|
}
|
|
|
|
if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) {
|
|
return Some(Some(Box::new(ToneCurveCicpEvaluator {
|
|
rgb_trc: TransferCharacteristics::Bt709.extended_linear_tristimulus(),
|
|
trc: TransferCharacteristics::Bt709.extended_linear_single(),
|
|
})));
|
|
}
|
|
}
|
|
|
|
let parametric_curve = ParametricCurve::new(params);
|
|
if let Some(v) = parametric_curve {
|
|
return Some(Some(Box::new(ToneCurveParametricEvaluator {
|
|
parametric: v,
|
|
})));
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
pub(crate) struct ToneCurveCicpEvaluator {
|
|
rgb_trc: fn(Rgb<f32>) -> Rgb<f32>,
|
|
trc: fn(f32) -> f32,
|
|
}
|
|
|
|
pub(crate) struct ToneCurveParametricEvaluator {
|
|
parametric: ParametricCurve,
|
|
}
|
|
|
|
pub(crate) struct ToneCurveEvaluatorPureGamma {
|
|
gamma: f32,
|
|
}
|
|
|
|
pub(crate) struct ToneCurveEvaluatorLinear {}
|
|
|
|
impl ToneCurveEvaluator for ToneCurveCicpEvaluator {
|
|
fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
|
|
(self.rgb_trc)(rgb)
|
|
}
|
|
|
|
fn evaluate_value(&self, value: f32) -> f32 {
|
|
(self.trc)(value)
|
|
}
|
|
}
|
|
|
|
impl ToneCurveEvaluator for ToneCurveParametricEvaluator {
|
|
fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
|
|
Rgb::new(
|
|
self.parametric.eval(rgb.r),
|
|
self.parametric.eval(rgb.g),
|
|
self.parametric.eval(rgb.b),
|
|
)
|
|
}
|
|
|
|
fn evaluate_value(&self, value: f32) -> f32 {
|
|
self.parametric.eval(value)
|
|
}
|
|
}
|
|
|
|
impl ToneCurveEvaluator for ToneCurveEvaluatorPureGamma {
|
|
fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
|
|
Rgb::new(
|
|
dirty_powf(rgb.r, self.gamma),
|
|
dirty_powf(rgb.g, self.gamma),
|
|
dirty_powf(rgb.b, self.gamma),
|
|
)
|
|
}
|
|
|
|
fn evaluate_value(&self, value: f32) -> f32 {
|
|
dirty_powf(value, self.gamma)
|
|
}
|
|
}
|
|
|
|
impl ToneCurveEvaluator for ToneCurveEvaluatorLinear {
|
|
fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> {
|
|
rgb
|
|
}
|
|
|
|
fn evaluate_value(&self, value: f32) -> f32 {
|
|
value
|
|
}
|
|
}
|
|
|
|
pub trait ToneCurveEvaluator {
|
|
fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32>;
|
|
fn evaluate_value(&self, value: f32) -> f32;
|
|
}
|