use crate::error::{Error, Result};
pub type F2 = [f32; 2];
pub type F3 = [f32; 3];
pub type M33f = [f32; 9];
pub const IDENTITY_M33: M33f = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
#[inline]
pub fn f3_from_f(v: f32) -> F3 {
[v, v, v]
}
#[inline]
pub fn mult_f_f3(v: f32, f3: &F3) -> F3 {
[v * f3[0], v * f3[1], v * f3[2]]
}
#[inline]
pub fn mult_f3_f33(f3: &F3, mat33: &M33f) -> F3 {
[
f3[0] * mat33[0] + f3[1] * mat33[1] + f3[2] * mat33[2],
f3[0] * mat33[3] + f3[1] * mat33[4] + f3[2] * mat33[5],
f3[0] * mat33[6] + f3[1] * mat33[7] + f3[2] * mat33[8],
]
}
pub fn mult_f33_f33(a: &M33f, b: &M33f) -> M33f {
[
a[0] * b[0] + a[1] * b[3] + a[2] * b[6],
a[0] * b[1] + a[1] * b[4] + a[2] * b[7],
a[0] * b[2] + a[1] * b[5] + a[2] * b[8],
a[3] * b[0] + a[4] * b[3] + a[5] * b[6],
a[3] * b[1] + a[4] * b[4] + a[5] * b[7],
a[3] * b[2] + a[4] * b[5] + a[5] * b[8],
a[6] * b[0] + a[7] * b[3] + a[8] * b[6],
a[6] * b[1] + a[7] * b[4] + a[8] * b[7],
a[6] * b[2] + a[7] * b[5] + a[8] * b[8],
]
}
pub fn scale_f33(mat33: &M33f, scale: &F3) -> M33f {
[
mat33[0] * scale[0],
mat33[3],
mat33[6],
mat33[1],
mat33[4] * scale[1],
mat33[7],
mat33[2],
mat33[5],
mat33[8] * scale[2],
]
}
pub type M44d = [f64; 16];
const M44_IDENTITY: M44d = [
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
];
fn m44_inner(a: &M44d, b: &M44d) -> M44d {
let mut out = [0.0; 16];
for row in 0..4 {
for col in 0..4 {
let mut accum = 0.0;
for i in 0..4 {
accum += a[row * 4 + i] * b[i * 4 + col];
}
out[row * 4 + col] = accum;
}
}
out
}
fn m44_inverse(m: &M44d) -> Result<M44d> {
let mut t = *m;
let mut s = M44_IDENTITY;
let singular = || Error::msg("Singular Matrix can't be inverted.");
for i in 0..3 {
let mut pivot = i;
let mut pivotsize = t[i * 4 + i].abs();
for j in (i + 1)..4 {
let tmp = t[j * 4 + i].abs();
if tmp > pivotsize {
pivot = j;
pivotsize = tmp;
}
}
if pivotsize == 0.0 {
return Err(singular());
}
if pivot != i {
for j in 0..4 {
t.swap(i * 4 + j, pivot * 4 + j);
s.swap(i * 4 + j, pivot * 4 + j);
}
}
for j in (i + 1)..4 {
let f = t[j * 4 + i] / t[i * 4 + i];
for k in 0..4 {
t[j * 4 + k] -= f * t[i * 4 + k];
s[j * 4 + k] -= f * s[i * 4 + k];
}
}
}
for i in (0..4).rev() {
let f = t[i * 4 + i];
if f == 0.0 {
return Err(singular());
}
for j in 0..4 {
t[i * 4 + j] /= f;
s[i * 4 + j] /= f;
}
for j in 0..i {
let f = t[j * 4 + i];
for k in 0..4 {
t[j * 4 + k] -= f * t[i * 4 + k];
s[j * 4 + k] -= f * s[i * 4 + k];
}
}
}
Ok(s)
}
fn m33_from_m44(v: &M44d) -> M33f {
[
v[0] as f32,
v[1] as f32,
v[2] as f32,
v[4] as f32,
v[5] as f32,
v[6] as f32,
v[8] as f32,
v[9] as f32,
v[10] as f32,
]
}
pub fn invert_f33(mat33: &M33f) -> Result<M33f> {
let mut v = M44_IDENTITY;
v[0] = f64::from(mat33[0]);
v[1] = f64::from(mat33[1]);
v[2] = f64::from(mat33[2]);
v[4] = f64::from(mat33[3]);
v[5] = f64::from(mat33[4]);
v[6] = f64::from(mat33[5]);
v[8] = f64::from(mat33[6]);
v[9] = f64::from(mat33[7]);
v[10] = f64::from(mat33[8]);
Ok(m33_from_m44(&m44_inverse(&v)?))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Chromaticities {
pub xy: [f64; 2],
}
impl Chromaticities {
pub const fn new(x: f64, y: f64) -> Self {
Self { xy: [x, y] }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Primaries {
pub red: Chromaticities,
pub grn: Chromaticities,
pub blu: Chromaticities,
pub wht: Chromaticities,
}
impl Primaries {
pub const fn new(
red: Chromaticities,
grn: Chromaticities,
blu: Chromaticities,
wht: Chromaticities,
) -> Self {
Self { red, grn, blu, wht }
}
pub fn from_f32(v: &[f32; 8]) -> Self {
let c = |x: f32, y: f32| Chromaticities::new(f64::from(x), f64::from(y));
Self::new(c(v[0], v[1]), c(v[2], v[3]), c(v[4], v[5]), c(v[6], v[7]))
}
}
pub const CIE_XYZ_ILLUM_E: Primaries = Primaries::new(
Chromaticities::new(1.0, 0.0),
Chromaticities::new(0.0, 1.0),
Chromaticities::new(0.0, 0.0),
Chromaticities::new(1.0 / 3.0, 1.0 / 3.0),
);
pub const ACES_AP0: Primaries = Primaries::new(
Chromaticities::new(0.7347, 0.2653),
Chromaticities::new(0.0000, 1.0000),
Chromaticities::new(0.0001, -0.0770),
Chromaticities::new(0.32168, 0.33767),
);
pub const ACES_AP1: Primaries = Primaries::new(
Chromaticities::new(0.713, 0.293),
Chromaticities::new(0.165, 0.830),
Chromaticities::new(0.128, 0.044),
Chromaticities::new(0.32168, 0.33767),
);
fn rgb2xyz_from_xy(p: &Primaries) -> Result<M44d> {
let mut matrix = M44_IDENTITY;
matrix[0] = p.red.xy[0];
matrix[4] = p.red.xy[1];
matrix[8] = 1.0 - p.red.xy[0] - p.red.xy[1];
matrix[1] = p.grn.xy[0];
matrix[5] = p.grn.xy[1];
matrix[9] = 1.0 - p.grn.xy[0] - p.grn.xy[1];
matrix[2] = p.blu.xy[0];
matrix[6] = p.blu.xy[1];
matrix[10] = 1.0 - p.blu.xy[0] - p.blu.xy[1];
let inv = m44_inverse(&matrix)?;
let wht_xyz = [
p.wht.xy[0] / p.wht.xy[1],
1.0,
(1.0 - p.wht.xy[0] - p.wht.xy[1]) / p.wht.xy[1],
];
let mut rgb2xyz = M44_IDENTITY;
for i in 0..3 {
let gain =
wht_xyz[0] * inv[i * 4] + wht_xyz[1] * inv[i * 4 + 1] + wht_xyz[2] * inv[i * 4 + 2];
for j in 0..3 {
rgb2xyz[j * 4 + i] = gain * matrix[j * 4 + i];
}
}
Ok(rgb2xyz)
}
fn build_conversion_matrix_no_adaptation(src: &Primaries, dst: &Primaries) -> Result<M44d> {
let src_rgb2xyz = rgb2xyz_from_xy(src)?;
let dst_rgb2xyz = rgb2xyz_from_xy(dst)?;
let dst_xyz2rgb = m44_inverse(&dst_rgb2xyz)?;
Ok(m44_inner(&dst_xyz2rgb, &src_rgb2xyz))
}
pub fn rgb_to_xyz_f33(c: &Primaries) -> Result<M33f> {
Ok(m33_from_m44(&build_conversion_matrix_no_adaptation(
c,
&CIE_XYZ_ILLUM_E,
)?))
}
pub fn xyz_to_rgb_f33(c: &Primaries) -> Result<M33f> {
let m = build_conversion_matrix_no_adaptation(c, &CIE_XYZ_ILLUM_E)?;
Ok(m33_from_m44(&m44_inverse(&m)?))
}
pub fn rgb_to_rgb_f33(src: &Primaries, dst: &Primaries) -> Result<M33f> {
Ok(mult_f33_f33(&xyz_to_rgb_f33(dst)?, &rgb_to_xyz_f33(src)?))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn inverse_roundtrip() {
let m: M33f = [2.0, 1.0, 0.5, 0.0, 3.0, 1.0, 1.0, 0.0, 4.0];
let inv = invert_f33(&m).unwrap();
let id = mult_f33_f33(&m, &inv);
for (a, b) in id.iter().zip(IDENTITY_M33.iter()) {
assert!((a - b).abs() < 1e-6);
}
assert!(invert_f33(&[0.0; 9]).is_err());
}
#[test]
fn ap0_to_xyz() {
let m = rgb_to_xyz_f33(&ACES_AP0).unwrap();
let w = mult_f3_f33(&[1.0, 1.0, 1.0], &m);
assert!((w[1] - 1.0).abs() < 1e-6);
assert!((w[0] - (0.32168 / 0.33767) as f32).abs() < 1e-6);
}
}