use crate::gamma_curves::TransferFunction;
use crate::rgb::Rgb;
use crate::{EuclideanDistance, Jzazbz, SRGB_TO_XYZ_D65, XYZ_TO_SRGB_D65};
use erydanos::Euclidean3DDistance;
#[derive(Copy, Clone, Debug, Default)]
pub struct Xyz {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Xyz {
#[inline]
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
#[inline]
pub fn saturate_x(x: f32) -> f32 {
x.max(0f32).min(95.047f32)
}
#[inline]
pub fn saturate_y(y: f32) -> f32 {
y.max(0f32).min(100f32)
}
#[inline]
pub fn saturate_z(z: f32) -> f32 {
z.max(0f32).min(108.883f32)
}
#[inline]
pub fn scale(&self, by: f32) -> Xyz {
Xyz {
x: self.x * by,
y: self.y * by,
z: self.z * by,
}
}
#[inline]
pub fn to_absolute_luminance(&self, display_nits: f32) -> Xyz {
let multiplier = display_nits;
return Xyz::new(
multiplier * self.x,
multiplier * self.y,
multiplier * self.z,
);
}
#[inline]
pub fn to_relative_luminance(&self, display_nits: f32) -> Xyz {
let multiplier = 1. / display_nits;
return Xyz::new(
multiplier * self.x,
multiplier * self.y,
multiplier * self.z,
);
}
}
static XYZ_SCALE_U8: f32 = 1f32 / 255f32;
impl Xyz {
#[inline]
pub fn to_jzazbz(&self) -> Jzazbz {
Jzazbz::from_xyz(*self)
}
#[inline]
pub fn from_srgb(rgb: Rgb<u8>) -> Self {
Xyz::from_rgb(rgb, &SRGB_TO_XYZ_D65, TransferFunction::Srgb)
}
#[inline]
pub fn from_rgb(
rgb: Rgb<u8>,
matrix: &[[f32; 3]; 3],
transfer_function: TransferFunction,
) -> Self {
let linear_function = transfer_function.get_linearize_function();
unsafe {
let r = linear_function(rgb.r as f32 * XYZ_SCALE_U8);
let g = linear_function(rgb.g as f32 * XYZ_SCALE_U8);
let b = linear_function(rgb.b as f32 * XYZ_SCALE_U8);
Self::new(
(*(*matrix.get_unchecked(0)).get_unchecked(0)) * r
+ (*(*matrix.get_unchecked(0)).get_unchecked(1)) * g
+ (*(*matrix.get_unchecked(0)).get_unchecked(2)) * b,
(*(*matrix.get_unchecked(1)).get_unchecked(0)) * r
+ (*(*matrix.get_unchecked(1)).get_unchecked(1)) * g
+ (*(*matrix.get_unchecked(1)).get_unchecked(2)) * b,
(*(*matrix.get_unchecked(2)).get_unchecked(0)) * r
+ (*(*matrix.get_unchecked(2)).get_unchecked(1)) * g
+ (*(*matrix.get_unchecked(2)).get_unchecked(2)) * b,
)
}
}
#[inline]
pub fn from_linear_rgb(rgb: &Rgb<f32>, matrix: &[[f32; 3]; 3]) -> Self {
unsafe {
Self::new(
(*(*matrix.get_unchecked(0)).get_unchecked(0)) * rgb.r
+ (*(*matrix.get_unchecked(0)).get_unchecked(1)) * rgb.g
+ (*(*matrix.get_unchecked(0)).get_unchecked(2)) * rgb.b,
(*(*matrix.get_unchecked(1)).get_unchecked(0)) * rgb.r
+ (*(*matrix.get_unchecked(1)).get_unchecked(1)) * rgb.g
+ (*(*matrix.get_unchecked(1)).get_unchecked(2)) * rgb.b,
(*(*matrix.get_unchecked(2)).get_unchecked(0)) * rgb.r
+ (*(*matrix.get_unchecked(2)).get_unchecked(1)) * rgb.g
+ (*(*matrix.get_unchecked(2)).get_unchecked(2)) * rgb.b,
)
}
}
pub fn scaled(&self) -> (f32, f32, f32) {
(self.x * 100f32, self.y * 100f32, self.z * 100f32)
}
}
impl Xyz {
pub fn to_srgb(&self) -> Rgb<u8> {
self.to_rgb(&XYZ_TO_SRGB_D65, TransferFunction::Srgb)
}
#[inline]
pub fn to_rgb(&self, matrix: &[[f32; 3]; 3], transfer_function: TransferFunction) -> Rgb<u8> {
let gamma_function = transfer_function.get_gamma_function();
let x = self.x;
let y = self.y;
let z = self.z;
unsafe {
let r = x * (*(*matrix.get_unchecked(0)).get_unchecked(0))
+ y * (*(*matrix.get_unchecked(0)).get_unchecked(1))
+ z * (*(*matrix.get_unchecked(0)).get_unchecked(2));
let g = x * (*(*matrix.get_unchecked(1)).get_unchecked(0))
+ y * (*(*matrix.get_unchecked(1)).get_unchecked(1))
+ z * (*(*matrix.get_unchecked(1)).get_unchecked(2));
let b = x * (*(*matrix.get_unchecked(2)).get_unchecked(0))
+ y * (*(*matrix.get_unchecked(2)).get_unchecked(1))
+ z * (*(*matrix.get_unchecked(2)).get_unchecked(2));
Rgb::<f32>::new(gamma_function(r), gamma_function(g), gamma_function(b)).to_u8()
}
}
#[inline]
pub fn to_linear_rgb(&self, matrix: &[[f32; 3]; 3]) -> Rgb<f32> {
let x = self.x;
let y = self.y;
let z = self.z;
unsafe {
let r = x * (*(*matrix.get_unchecked(0)).get_unchecked(0))
+ y * (*(*matrix.get_unchecked(0)).get_unchecked(1))
+ z * (*(*matrix.get_unchecked(0)).get_unchecked(2));
let g = x * (*(*matrix.get_unchecked(1)).get_unchecked(0))
+ y * (*(*matrix.get_unchecked(1)).get_unchecked(1))
+ z * (*(*matrix.get_unchecked(1)).get_unchecked(2));
let b = x * (*(*matrix.get_unchecked(2)).get_unchecked(0))
+ y * (*(*matrix.get_unchecked(2)).get_unchecked(1))
+ z * (*(*matrix.get_unchecked(2)).get_unchecked(2));
Rgb::<f32>::new(r, g, b)
}
}
}
impl EuclideanDistance for Xyz {
fn euclidean_distance(&self, other: Xyz) -> f32 {
(self.x - other.x).hypot3(self.y - other.y, self.z - other.z)
}
}