use crate::{
observer::Observer,
rgb::{rgbspace::RgbSpace, Rgb},
xyz::XYZ,
};
use approx::AbsDiffEq;
use nalgebra::Vector3;
#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WideRgb {
pub(crate) space: RgbSpace,
pub(crate) observer: Observer,
pub(crate) rgb: Vector3<f64>,
}
impl WideRgb {
pub fn new(
r: f64,
g: f64,
b: f64,
observer: Option<Observer>,
space: Option<RgbSpace>,
) -> Self {
let observer = observer.unwrap_or_default();
let space = space.unwrap_or_default();
WideRgb {
rgb: Vector3::new(r, g, b),
observer,
space,
}
}
pub fn r(&self) -> f64 {
self.rgb.x
}
pub fn g(&self) -> f64 {
self.rgb.y
}
pub fn b(&self) -> f64 {
self.rgb.z
}
pub fn to_array(&self) -> [f64; 3] {
*self.rgb.as_ref()
}
pub fn xyz(&self) -> XYZ {
const YW: f64 = 100.0;
let xyz = self.observer.rgb2xyz_matrix(self.space) * self.rgb;
XYZ {
observer: self.observer,
xyz: xyz.map(|v| v * YW),
}
}
pub fn is_in_gamut(&self) -> bool {
self.to_array().iter().all(|v| (0.0..=1.0).contains(v))
}
pub fn is_within_gamut(&self, epsilon: f64) -> bool {
self.to_array().iter().all(|&v| v < 1.0 - epsilon)
}
pub fn is_black(&self, epsilon: f64) -> bool {
self.rgb.iter().all(|&v| v.abs() < epsilon)
}
pub fn to_rgb(self) -> Option<Rgb> {
if self.is_in_gamut() {
Some(Rgb {
rgb: self.rgb,
observer: self.observer,
space: self.space,
})
} else {
None
}
}
pub fn clamp(&self) -> Rgb {
let rgb = self.rgb.map(|v| v.clamp(0.0, 1.0));
Rgb {
rgb,
observer: self.observer,
space: self.space,
}
}
pub fn compress(&self) -> Rgb {
let translate = -self.rgb.min().min(0.0);
let scale = (self.rgb.max() + translate).max(1.0);
let in_gamut_rgb = if translate != 0.0 || scale != 1.0 {
self.rgb.add_scalar(translate) / scale
} else {
self.rgb
};
Rgb {
rgb: in_gamut_rgb,
observer: self.observer,
space: self.space,
}
}
}
impl AsRef<Vector3<f64>> for WideRgb {
fn as_ref(&self) -> &Vector3<f64> {
&self.rgb
}
}
impl From<WideRgb> for [f64; 3] {
fn from(rgb: WideRgb) -> Self {
rgb.to_array()
}
}
impl AbsDiffEq for WideRgb {
type Epsilon = f64;
fn default_epsilon() -> Self::Epsilon {
f64::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.observer == other.observer && self.rgb.abs_diff_eq(&other.rgb, epsilon)
}
}
impl approx::UlpsEq for WideRgb {
fn default_max_ulps() -> u32 {
f64::default_max_ulps()
}
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.observer == other.observer && self.rgb.ulps_eq(&other.rgb, epsilon, max_ulps)
}
}
#[cfg(test)]
mod rgb_tests {
use crate::rgb::WideRgb;
#[test]
fn get_values() {
let rgb = WideRgb::new(0.1, 0.2, 0.3, None, None);
let [r, g, b] = rgb.to_array();
assert_eq!(r, 0.1);
assert_eq!(g, 0.2);
assert_eq!(b, 0.3);
assert_eq!(rgb.r(), 0.1);
assert_eq!(rgb.g(), 0.2);
assert_eq!(rgb.b(), 0.3);
assert_eq!(<[f64; 3]>::from(rgb), rgb.to_array());
}
#[test]
fn out_of_gamut_values() {
let rgb = WideRgb::new(-0.8, 2.7, 0.8, None, None);
let [r, g, b] = rgb.to_array();
assert_eq!(r, -0.8);
assert_eq!(g, 2.7);
assert_eq!(b, 0.8);
}
#[test]
fn compress_in_gamut() {
let rgb = WideRgb::new(0.0, 0.0, 0.0, None, None).compress();
assert_eq!(rgb.to_array(), [0.0, 0.0, 0.0]);
let rgb = WideRgb::new(1.0, 1.0, 1.0, None, None).compress();
assert_eq!(rgb.to_array(), [1.0, 1.0, 1.0]);
let rgb = WideRgb::new(0.75, 0.75, 0.75, None, None).compress();
assert_eq!(rgb.to_array(), [0.75, 0.75, 0.75]);
let rgb = WideRgb::new(0.1, 0.3, 1.0, None, None).compress();
assert_eq!(rgb.to_array(), [0.1, 0.3, 1.0]);
let rgb = WideRgb::new(0.0, 0.3, 1.0, None, None).compress();
assert_eq!(rgb.to_array(), [0.0, 0.3, 1.0]);
}
#[test]
fn compress_out_of_gamut() {
let rgb = WideRgb::new(1.2, 1.2, 1.2, None, None).compress();
assert_eq!(rgb.to_array(), [1.0, 1.0, 1.0]);
let rgb = WideRgb::new(-0.5, -0.5, -0.5, None, None).compress();
assert_eq!(rgb.to_array(), [0.0, 0.0, 0.0]);
let rgb = WideRgb::new(1.0, 2.0, 3.0, None, None).compress();
assert_eq!(rgb.to_array(), [1.0 / 3.0, 2.0 / 3.0, 1.0]);
let rgb = WideRgb::new(-0.1, 0.9, 1.1, None, None).compress();
assert_eq!(rgb.to_array(), [0.0, (0.9 + 0.1) / (1.1 + 0.1), 1.0]);
let rgb = WideRgb::new(-0.5, 0.4, 0.6, None, None).compress();
assert_eq!(rgb.to_array(), [0.0, (0.4 + 0.5) / (0.6 + 0.5), 1.0]);
let rgb = WideRgb::new(-0.5, 0.4, 0.4, None, None).compress();
assert_eq!(rgb.to_array(), [0.0, 0.9, 0.9]);
}
}