mod gamma;
mod rgbspace;
mod widergb;
pub use gamma::GammaCurve;
pub use rgbspace::RgbSpace;
pub use widergb::WideRgb;
use crate::{
colorant::Colorant,
error::Error,
observer::Observer::{self, Cie1931},
spectrum::Spectrum,
stimulus::Stimulus,
traits::{Filter, Light},
xyz::XYZ,
};
use approx::AbsDiffEq;
use nalgebra::Vector3;
use std::borrow::Cow;
#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Rgb {
pub(crate) space: RgbSpace,
pub(crate) observer: Observer,
pub(crate) rgb: Vector3<f64>,
}
impl Rgb {
pub fn new(
r: f64,
g: f64,
b: f64,
opt_observer: Option<Observer>,
opt_rgbspace: Option<RgbSpace>,
) -> Result<Self, Error> {
if (0.0..=1.0).contains(&r) && (0.0..=1.0).contains(&g) && (0.0..=1.0).contains(&b) {
let observer = opt_observer.unwrap_or_default();
let space = opt_rgbspace.unwrap_or_default();
Ok(Rgb {
rgb: Vector3::new(r, g, b),
observer,
space,
})
} else {
Err(Error::InvalidRgbValue)
}
}
pub fn from_u8(
r_u8: u8,
g_u8: u8,
b_u8: u8,
observer: Option<Observer>,
space: Option<RgbSpace>,
) -> Self {
let space = space.unwrap_or_default();
let [r, g, b] = [r_u8, g_u8, b_u8]
.map(|v| (v as f64 / 255.0).clamp(0.0, 1.0))
.map(|v| space.gamma().decode(v));
Rgb::new(r, g, b, observer, Some(space)).unwrap()
}
pub fn from_u16(
r_u16: u16,
g_u16: u16,
b_u16: u16,
observer: Option<Observer>,
space: Option<RgbSpace>,
) -> Self {
let space = space.unwrap_or_default();
let [r, g, b] = [r_u16, g_u16, b_u16]
.map(|v| (v as f64 / 65_535.0).clamp(0.0, 1.0))
.map(|v| space.gamma().decode(v));
Rgb::new(r, g, b, observer, Some(space)).unwrap()
}
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),
}
}
}
impl Light for Rgb {
fn spectrum(&self) -> Cow<'_, Spectrum> {
let prim = self.space.primaries();
let rgb2xyz = self.observer.rgb2xyz_matrix(self.space);
let yrgb = rgb2xyz.row(1);
let s = self
.rgb
.iter()
.zip(yrgb.iter())
.zip(prim.iter())
.fold(Spectrum::default(), |acc, ((&v, &w), s)| acc + v * w * s.0);
Cow::Owned(s)
}
}
impl Filter for Rgb {
fn spectrum(&self) -> Cow<'_, Spectrum> {
let prim = self.space.primaries_as_colorants();
let rgb2xyz = self.observer.rgb2xyz_matrix(self.space);
let yrgb = rgb2xyz.row(1);
let s = self
.rgb
.iter()
.zip(yrgb.iter())
.zip(prim.iter())
.fold(Spectrum::default(), |acc, ((&v, &w), s)| acc + v * w * s.0);
Cow::Owned(s)
}
}
impl AsRef<Vector3<f64>> for Rgb {
fn as_ref(&self) -> &Vector3<f64> {
&self.rgb
}
}
impl From<Rgb> for [u8; 3] {
fn from(rgb: Rgb) -> Self {
let data: &[f64; 3] = rgb.rgb.as_ref();
data.map(|v| (rgb.space.gamma().encode(v.clamp(0.0, 1.0)) * 255.0).round() as u8)
}
}
impl From<Rgb> for [f64; 3] {
fn from(rgb: Rgb) -> Self {
rgb.to_array()
}
}
impl AbsDiffEq for Rgb {
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 Rgb {
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)
}
}
pub fn gaussian_filtered_primaries(
white: &Spectrum,
red: [f64; 3],
green: [f64; 2],
blue: [f64; 2],
) -> [Stimulus; 3] {
let [rc, rw, f] = red;
let [gc, gw] = green;
let [bc, bw] = blue;
[
Stimulus(
Stimulus(&*Colorant::gaussian(bc, bw).spectrum() * white)
.set_luminance(Cie1931, 100.0)
.0
* f
+ Stimulus(&*Colorant::gaussian(rc, rw).spectrum() * white)
.set_luminance(Cie1931, 100.0)
.0
* (1.0 - f),
),
Stimulus(&*Colorant::gaussian(gc, gw).spectrum() * white).set_luminance(Cie1931, 100.0),
Stimulus(&*Colorant::gaussian(bc, bw).spectrum() * white).set_luminance(Cie1931, 100.0),
]
}
#[cfg(test)]
mod rgb_tests {
use crate::rgb::Rgb;
#[test]
fn get_values_f64() {
let rgb = Rgb::new(0.1, 0.2, 0.3, None, None).unwrap();
let [r, g, b] = <[f64; 3]>::from(rgb);
assert_eq!(r, 0.1);
assert_eq!(g, 0.2);
assert_eq!(b, 0.3);
}
#[test]
fn get_values_u8() {
let rgb = Rgb::new(0.1, 0.2, 0.3, None, None).unwrap();
let [r, g, b] = <[u8; 3]>::from(rgb);
assert_eq!(r, 89);
assert_eq!(g, 124);
assert_eq!(b, 149);
}
}