mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-20 08:30:18 +00:00
360 lines
10 KiB
Rust
360 lines
10 KiB
Rust
|
|
/*
|
||
|
|
* // Copyright (c) Radzivon Bartoshyk 6/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::Xyz;
|
||
|
|
use crate::mlaf::mlaf;
|
||
|
|
use pxfm::{f_atan2f, f_powf, f_sincosf};
|
||
|
|
|
||
|
|
/// Darktable UCS JCH ( Darktable Uniform Color Space )
|
||
|
|
#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
|
||
|
|
pub struct DtUchJch {
|
||
|
|
pub j: f32,
|
||
|
|
pub c: f32,
|
||
|
|
pub h: f32,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Darktable UCS HSB ( Darktable Uniform Color Space )
|
||
|
|
#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
|
||
|
|
pub struct DtUchHsb {
|
||
|
|
pub h: f32,
|
||
|
|
pub s: f32,
|
||
|
|
pub b: f32,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Darktable HCB ( Darktable Uniform Color Space )
|
||
|
|
#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
|
||
|
|
pub struct DtUchHcb {
|
||
|
|
pub h: f32,
|
||
|
|
pub c: f32,
|
||
|
|
pub b: f32,
|
||
|
|
}
|
||
|
|
|
||
|
|
const DT_UCS_L_STAR_RANGE: f32 = 2.098883786377;
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
fn y_to_dt_ucs_l_star(y: f32) -> f32 {
|
||
|
|
let y_hat = f_powf(y, 0.631651345306265);
|
||
|
|
DT_UCS_L_STAR_RANGE * y_hat / (y_hat + 1.12426773749357)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
fn dt_ucs_l_star_to_y(x: f32) -> f32 {
|
||
|
|
f_powf(
|
||
|
|
1.12426773749357 * x / (DT_UCS_L_STAR_RANGE - x),
|
||
|
|
1.5831518565279648,
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
const L_WHITE: f32 = 0.98805060;
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
fn dt_ucs_luv_to_ucs_jch(
|
||
|
|
l_star: f32,
|
||
|
|
l_white: f32,
|
||
|
|
u_star_prime: f32,
|
||
|
|
v_star_prime: f32,
|
||
|
|
) -> DtUchJch {
|
||
|
|
let m2: f32 = mlaf(u_star_prime * u_star_prime, v_star_prime, v_star_prime); // square of colorfulness M
|
||
|
|
|
||
|
|
// should be JCH[0] = powf(L_star / L_white), cz) but we treat only the case where cz = 1
|
||
|
|
let j = l_star / l_white;
|
||
|
|
let c =
|
||
|
|
15.932993652962535 * f_powf(l_star, 0.6523997524738018) * f_powf(m2, 0.6007557017508491)
|
||
|
|
/ l_white;
|
||
|
|
let h = f_atan2f(v_star_prime, u_star_prime);
|
||
|
|
DtUchJch::new(j, c, h)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
fn dt_ucs_xy_to_uv(x: f32, y: f32) -> (f32, f32) {
|
||
|
|
const X_C: [f32; 3] = [-0.783941002840055, 0.745273540913283, 0.318707282433486];
|
||
|
|
const Y_C: [f32; 3] = [0.277512987809202, -0.205375866083878, 2.16743692732158];
|
||
|
|
const BIAS: [f32; 3] = [0.153836578598858, -0.165478376301988, 0.291320554395942];
|
||
|
|
|
||
|
|
let mut u_c = mlaf(mlaf(BIAS[0], Y_C[0], y), X_C[0], x);
|
||
|
|
let mut v_c = mlaf(mlaf(BIAS[1], Y_C[1], y), X_C[1], x);
|
||
|
|
let d_c = mlaf(mlaf(BIAS[2], Y_C[2], y), X_C[2], x);
|
||
|
|
|
||
|
|
let div = if d_c >= 0.0 {
|
||
|
|
d_c.max(f32::MIN)
|
||
|
|
} else {
|
||
|
|
d_c.min(-f32::MIN)
|
||
|
|
};
|
||
|
|
u_c /= div;
|
||
|
|
v_c /= div;
|
||
|
|
|
||
|
|
const STAR_C: [f32; 2] = [1.39656225667, 1.4513954287];
|
||
|
|
const STAR_HF_C: [f32; 2] = [1.49217352929, 1.52488637914];
|
||
|
|
|
||
|
|
let u_star = STAR_C[0] * u_c / (u_c.abs() + STAR_HF_C[0]);
|
||
|
|
let v_star = STAR_C[1] * v_c / (v_c.abs() + STAR_HF_C[1]);
|
||
|
|
|
||
|
|
// The following is equivalent to a 2D matrix product
|
||
|
|
let u_star_prime = mlaf(-1.124983854323892 * u_star, -0.980483721769325, v_star);
|
||
|
|
let v_star_prime = mlaf(1.86323315098672 * u_star, 1.971853092390862, v_star);
|
||
|
|
(u_star_prime, v_star_prime)
|
||
|
|
}
|
||
|
|
|
||
|
|
impl DtUchJch {
|
||
|
|
#[inline]
|
||
|
|
pub fn new(j: f32, c: f32, h: f32) -> DtUchJch {
|
||
|
|
DtUchJch { j, c, h }
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn from_xyz(xyz: Xyz) -> DtUchJch {
|
||
|
|
DtUchJch::from_xyy(xyz.to_xyy())
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn to_xyz(&self) -> Xyz {
|
||
|
|
let xyy = self.to_xyy();
|
||
|
|
Xyz::from_xyy(xyy)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn from_xyy(xyy: [f32; 3]) -> DtUchJch {
|
||
|
|
let l_star = y_to_dt_ucs_l_star(xyy[2]);
|
||
|
|
// let l_white = y_to_dt_ucs_l_star(1.);
|
||
|
|
|
||
|
|
let (u_star_prime, v_star_prime) = dt_ucs_xy_to_uv(xyy[0], xyy[1]);
|
||
|
|
dt_ucs_luv_to_ucs_jch(l_star, L_WHITE, u_star_prime, v_star_prime)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn to_xyy(&self) -> [f32; 3] {
|
||
|
|
// let l_white: f32 = y_to_dt_ucs_l_star(1.0);
|
||
|
|
let l_star = (self.j * L_WHITE).max(0.0).min(2.09885);
|
||
|
|
let m = if l_star != 0. {
|
||
|
|
f_powf(
|
||
|
|
self.c * L_WHITE / (15.932993652962535 * f_powf(l_star, 0.6523997524738018)),
|
||
|
|
0.8322850678616855,
|
||
|
|
)
|
||
|
|
} else {
|
||
|
|
0.
|
||
|
|
};
|
||
|
|
|
||
|
|
let sin_cos_h = f_sincosf(self.h);
|
||
|
|
let u_star_prime = m * sin_cos_h.1;
|
||
|
|
let v_star_prime = m * sin_cos_h.0;
|
||
|
|
|
||
|
|
// The following is equivalent to a 2D matrix product
|
||
|
|
let u_star = mlaf(
|
||
|
|
-5.037522385190711 * u_star_prime,
|
||
|
|
-2.504856328185843,
|
||
|
|
v_star_prime,
|
||
|
|
);
|
||
|
|
let v_star = mlaf(
|
||
|
|
4.760029407436461 * u_star_prime,
|
||
|
|
2.874012963239247,
|
||
|
|
v_star_prime,
|
||
|
|
);
|
||
|
|
|
||
|
|
const F: [f32; 2] = [1.39656225667, 1.4513954287];
|
||
|
|
const HF: [f32; 2] = [1.49217352929, 1.52488637914];
|
||
|
|
|
||
|
|
let u_c = -HF[0] * u_star / (u_star.abs() - F[0]);
|
||
|
|
let v_c = -HF[1] * v_star / (v_star.abs() - F[1]);
|
||
|
|
|
||
|
|
const U_C: [f32; 3] = [0.167171472114775, -0.150959086409163, 0.940254742367256];
|
||
|
|
const V_C: [f32; 3] = [0.141299802443708, -0.155185060382272, 1.000000000000000];
|
||
|
|
const BIAS: [f32; 3] = [
|
||
|
|
-0.00801531300850582,
|
||
|
|
-0.00843312433578007,
|
||
|
|
-0.0256325967652889,
|
||
|
|
];
|
||
|
|
|
||
|
|
let mut x = mlaf(mlaf(BIAS[0], V_C[0], v_c), U_C[0], u_c);
|
||
|
|
let mut y = mlaf(mlaf(BIAS[1], V_C[1], v_c), U_C[1], u_c);
|
||
|
|
let d = mlaf(mlaf(BIAS[2], V_C[2], v_c), U_C[2], u_c);
|
||
|
|
|
||
|
|
let div = if d >= 0.0 {
|
||
|
|
d.max(f32::MIN)
|
||
|
|
} else {
|
||
|
|
d.min(-f32::MIN)
|
||
|
|
};
|
||
|
|
x /= div;
|
||
|
|
y /= div;
|
||
|
|
let yb = dt_ucs_l_star_to_y(l_star);
|
||
|
|
[x, y, yb]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl DtUchHsb {
|
||
|
|
#[inline]
|
||
|
|
pub fn new(h: f32, s: f32, b: f32) -> DtUchHsb {
|
||
|
|
DtUchHsb { h, s, b }
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn from_jch(jch: DtUchJch) -> DtUchHsb {
|
||
|
|
let b = jch.j * (f_powf(jch.c, 1.33654221029386) + 1.);
|
||
|
|
let s = if b > 0. { jch.c / b } else { 0. };
|
||
|
|
let h = jch.h;
|
||
|
|
DtUchHsb::new(h, s, b)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn to_jch(&self) -> DtUchJch {
|
||
|
|
let h = self.h;
|
||
|
|
let c = self.s * self.b;
|
||
|
|
let j = self.b / (f_powf(c, 1.33654221029386) + 1.);
|
||
|
|
DtUchJch::new(j, c, h)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl DtUchHcb {
|
||
|
|
#[inline]
|
||
|
|
pub fn new(h: f32, c: f32, b: f32) -> DtUchHcb {
|
||
|
|
DtUchHcb { h, c, b }
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn from_jch(jch: DtUchJch) -> DtUchHcb {
|
||
|
|
let b = jch.j * (f_powf(jch.c, 1.33654221029386) + 1.);
|
||
|
|
let c = jch.c;
|
||
|
|
let h = jch.h;
|
||
|
|
DtUchHcb::new(h, c, b)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn to_jch(&self) -> DtUchJch {
|
||
|
|
let h = self.h;
|
||
|
|
let c = self.c;
|
||
|
|
let j = self.b / (f_powf(self.c, 1.33654221029386) + 1.);
|
||
|
|
DtUchJch::new(j, c, h)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_darktable_ucs_jch() {
|
||
|
|
let xyy = [0.4, 0.2, 0.5];
|
||
|
|
let ucs = DtUchJch::from_xyy(xyy);
|
||
|
|
let xyy_rev = ucs.to_xyy();
|
||
|
|
assert!(
|
||
|
|
(xyy[0] - xyy_rev[0]).abs() < 1e-5,
|
||
|
|
"Expected {}, got {}",
|
||
|
|
xyy[0],
|
||
|
|
xyy_rev[0]
|
||
|
|
);
|
||
|
|
assert!(
|
||
|
|
(xyy[1] - xyy_rev[1]).abs() < 1e-5,
|
||
|
|
"Expected {}, got {}",
|
||
|
|
xyy[1],
|
||
|
|
xyy_rev[1]
|
||
|
|
);
|
||
|
|
assert!(
|
||
|
|
(xyy[2] - xyy_rev[2]).abs() < 1e-5,
|
||
|
|
"Expected {}, got {}",
|
||
|
|
xyy[2],
|
||
|
|
xyy_rev[2]
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_darktable_hsb() {
|
||
|
|
let jch = DtUchJch::new(0.3, 0.6, 0.4);
|
||
|
|
let hsb = DtUchHsb::from_jch(jch);
|
||
|
|
let r_jch = hsb.to_jch();
|
||
|
|
|
||
|
|
assert!(
|
||
|
|
(r_jch.j - jch.j).abs() < 1e-5,
|
||
|
|
"Expected {}, got {}",
|
||
|
|
jch.j,
|
||
|
|
r_jch.j
|
||
|
|
);
|
||
|
|
assert!(
|
||
|
|
(r_jch.c - jch.c).abs() < 1e-5,
|
||
|
|
"Expected {}, got {}",
|
||
|
|
jch.c,
|
||
|
|
r_jch.c
|
||
|
|
);
|
||
|
|
assert!(
|
||
|
|
(r_jch.h - jch.h).abs() < 1e-5,
|
||
|
|
"Expected {}, got {}",
|
||
|
|
jch.h,
|
||
|
|
r_jch.h
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_darktable_hcb() {
|
||
|
|
let jch = DtUchJch::new(0.3, 0.6, 0.4);
|
||
|
|
let hcb = DtUchHcb::from_jch(jch);
|
||
|
|
let r_jch = hcb.to_jch();
|
||
|
|
|
||
|
|
assert!(
|
||
|
|
(r_jch.j - jch.j).abs() < 1e-5,
|
||
|
|
"Expected {}, got {}",
|
||
|
|
jch.j,
|
||
|
|
r_jch.j
|
||
|
|
);
|
||
|
|
assert!(
|
||
|
|
(r_jch.c - jch.c).abs() < 1e-5,
|
||
|
|
"Expected {}, got {}",
|
||
|
|
jch.c,
|
||
|
|
r_jch.c
|
||
|
|
);
|
||
|
|
assert!(
|
||
|
|
(r_jch.h - jch.h).abs() < 1e-5,
|
||
|
|
"Expected {}, got {}",
|
||
|
|
jch.h,
|
||
|
|
r_jch.h
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_darktable_ucs_jch_from_xyz() {
|
||
|
|
let xyz = Xyz::new(0.4, 0.2, 0.5);
|
||
|
|
let ucs = DtUchJch::from_xyz(xyz);
|
||
|
|
let xyy_rev = ucs.to_xyz();
|
||
|
|
assert!(
|
||
|
|
(xyz.x - xyz.x).abs() < 1e-5,
|
||
|
|
"Expected {}, got {}",
|
||
|
|
xyz.x,
|
||
|
|
xyy_rev.x
|
||
|
|
);
|
||
|
|
assert!(
|
||
|
|
(xyz.y - xyz.y).abs() < 1e-5,
|
||
|
|
"Expected {}, got {}",
|
||
|
|
xyz.y,
|
||
|
|
xyy_rev.y
|
||
|
|
);
|
||
|
|
assert!(
|
||
|
|
(xyz.z - xyz.z).abs() < 1e-5,
|
||
|
|
"Expected {}, got {}",
|
||
|
|
xyz.z,
|
||
|
|
xyy_rev.z
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|