use bound::Bound;
use color::{Color, XYZColor};
use consts::ROMM_RGB_TRANSFORM as ROMM;
use consts::ROMM_RGB_TRANSFORM_LU as ROMM_LU;
use coord::Coord;
use illuminants::Illuminant;
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct ROMMRGBColor {
pub r: f64,
pub g: f64,
pub b: f64,
}
impl Color for ROMMRGBColor {
fn from_xyz(xyz: XYZColor) -> ROMMRGBColor {
let xyz_c = xyz.color_adapt(Illuminant::D50);
let rr_gg_bb = *ROMM * vector![xyz_c.x, xyz_c.y, xyz_c.z];
let gamma = |x: f64| {
if x < (2.0f64).powf(-9.0) {
x * 16.0
} else {
x.powf(1.0 / 1.8)
}
};
let fix_flare = |x: f64| {
if x < 0.03125 {
0.003473 + 0.0622829 * x
} else {
0.003473 + 0.996527 * x.powf(1.8)
}
};
let clamp = |x: f64| {
if x < 0.0 {
0.0
} else if x > 1.0 {
1.0
} else {
x
}
};
ROMMRGBColor {
r: fix_flare(gamma(clamp(rr_gg_bb[0]))),
g: fix_flare(gamma(clamp(rr_gg_bb[1]))),
b: fix_flare(gamma(clamp(rr_gg_bb[2]))),
}
}
fn to_xyz(&self, illuminant: Illuminant) -> XYZColor {
let gamma_inv = |x: f64| {
if x >= 0.03125 {
x.powf(1.8)
} else {
x / 16.0
}
};
let fix_flare_inv = |x: f64| {
if x >= 0.005419340625 {
((x - 0.003473) / 0.996527).powf(1.0 / 1.8)
} else {
(x - 0.003473) / 0.0622829
}
};
let r_c = gamma_inv(fix_flare_inv(self.r));
let g_c = gamma_inv(fix_flare_inv(self.g));
let b_c = gamma_inv(fix_flare_inv(self.b));
let xyz = ROMM_LU
.solve(&vector![r_c, g_c, b_c])
.expect("Matrix is invertible.");
XYZColor {
x: xyz[0],
y: xyz[1],
z: xyz[2],
illuminant: Illuminant::D50,
}
.color_adapt(illuminant)
}
}
impl From<Coord> for ROMMRGBColor {
fn from(c: Coord) -> ROMMRGBColor {
ROMMRGBColor {
r: c.x,
g: c.y,
b: c.z,
}
}
}
impl From<ROMMRGBColor> for Coord {
fn from(val: ROMMRGBColor) -> Self {
Coord {
x: val.r,
y: val.g,
z: val.b,
}
}
}
impl Bound for ROMMRGBColor {
fn bounds() -> [(f64, f64); 3] {
[(0., 1.), (0., 1.), (0., 1.)]
}
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
use consts::TEST_PRECISION;
#[test]
fn test_romm_rgb_xyz_conversion() {
let xyz = XYZColor {
x: 0.4,
y: 0.5,
z: 0.6,
illuminant: Illuminant::D50,
};
let rgb = ROMMRGBColor::from_xyz(xyz);
let xyz2: XYZColor = rgb.to_xyz(Illuminant::D50);
assert!(xyz.approx_equal(&xyz2));
assert!(xyz.distance(&xyz2) <= TEST_PRECISION);
}
#[test]
fn test_xyz_romm_rgb_conversion() {
let rgb = ROMMRGBColor {
r: 0.6,
g: 0.3,
b: 0.8,
};
let xyz = rgb.to_xyz(Illuminant::D50);
let rgb2 = ROMMRGBColor::from_xyz(xyz);
assert!((rgb.r - rgb2.r).abs() <= 0.001);
assert!((rgb.g - rgb2.g).abs() <= 0.001);
assert!((rgb.b - rgb2.b).abs() <= 0.001);
assert!(rgb.distance(&rgb2) <= TEST_PRECISION);
}
#[test]
fn test_error_accumulation_romm_rgb() {
let rgb = ROMMRGBColor {
r: 0.6,
g: 0.3,
b: 0.8,
};
let xyz = rgb.to_xyz(Illuminant::D50);
let mut rgb2 = ROMMRGBColor::from_xyz(xyz);
for _i in 0..20 {
rgb2 = ROMMRGBColor::from_xyz(rgb2.to_xyz(Illuminant::D50));
assert!((rgb.r - rgb2.r).abs() <= 1e-4);
assert!((rgb.g - rgb2.g).abs() <= 1e-4);
assert!((rgb.b - rgb2.b).abs() <= 1e-4);
assert!(rgb.distance(&rgb2) <= TEST_PRECISION);
}
}
#[test]
fn test_romm_rgb_xyz_conversion_with_gamut() {
let wp = Illuminant::D65.white_point();
let xyz = XYZColor {
x: wp[0],
y: wp[1],
z: wp[2],
illuminant: Illuminant::D65,
};
let rgb: ROMMRGBColor = ROMMRGBColor::from_xyz(xyz);
let xyz2 = rgb.to_xyz(Illuminant::D65);
assert!(xyz.approx_visually_equal(&xyz2));
assert!(xyz.distance(&xyz2) <= TEST_PRECISION);
}
}