use crate::adaptation::{Bradford, ChromaticAdaptation, adapt};
use crate::illuminant::{AcesWhitePoint, D50, D65, DciWhite, Illuminant};
use crate::math::Mat3;
pub const fn derive_rgb_to_xyz(r: [f32; 2], g: [f32; 2], b: [f32; 2], w_xyz: [f32; 3]) -> Mat3 {
let [xr, yr] = r;
let zr = 1.0 - xr - yr;
let [xg, yg] = g;
let zg = 1.0 - xg - yg;
let [xb, yb] = b;
let zb = 1.0 - xb - yb;
let det = xr * (yg * zb - yb * zg) - xg * (yr * zb - yb * zr) + xb * (yr * zg - yg * zr);
let inv00 = (yg * zb - yb * zg) / det;
let inv01 = (xb * zg - xg * zb) / det;
let inv02 = (xg * yb - xb * yg) / det;
let inv10 = (yb * zr - yr * zb) / det;
let inv11 = (xr * zb - xb * zr) / det;
let inv12 = (xb * yr - xr * yb) / det;
let inv20 = (yr * zg - yg * zr) / det;
let inv21 = (xg * zr - xr * zg) / det;
let inv22 = (xr * yg - xg * yr) / det;
let [xw, yw, zw] = w_xyz;
let sr = inv00 * xw + inv01 * yw + inv02 * zw;
let sg = inv10 * xw + inv11 * yw + inv12 * zw;
let sb = inv20 * xw + inv21 * yw + inv22 * zw;
Mat3 {
col0: [sr * xr, sr * yr, sr * zr, 0.0],
col1: [sg * xg, sg * yg, sg * zg, 0.0],
col2: [sb * xb, sb * yb, sb * zb, 0.0],
}
}
pub trait Primaries: 'static {
type Native: Illuminant;
const R: [f32; 2];
const G: [f32; 2];
const B: [f32; 2];
const TO_XYZ_NATIVE: Mat3;
const FROM_XYZ_NATIVE: Mat3;
}
pub trait PrimariesToXyz<W: Illuminant, A: ChromaticAdaptation = Bradford>: Primaries {
const TO_XYZ: Mat3;
const FROM_XYZ: Mat3;
const LUMINANCE_WEIGHTS: [f32; 3];
}
macro_rules! impl_native {
($P:ty, $W:ty) => {
impl PrimariesToXyz<$W> for $P {
const TO_XYZ: Mat3 = <$P as Primaries>::TO_XYZ_NATIVE;
const FROM_XYZ: Mat3 = <$P as Primaries>::FROM_XYZ_NATIVE;
const LUMINANCE_WEIGHTS: [f32; 3] = <$P as Primaries>::TO_XYZ_NATIVE.luminance_weights();
}
};
}
macro_rules! impl_adapted {
($P:ty, $from:ty, $to:ty) => {
impl_adapted!($P, $from, $to, Bradford);
};
($P:ty, $from:ty, $to:ty, $A:ty) => {
impl PrimariesToXyz<$to, $A> for $P {
const TO_XYZ: Mat3 = {
const ADAPT: Mat3 = adapt::<$A>(
<$from as Illuminant>::WHITE_POINT_XYZ,
<$to as Illuminant>::WHITE_POINT_XYZ,
);
Mat3::mul(&ADAPT, &<$P as Primaries>::TO_XYZ_NATIVE)
};
const FROM_XYZ: Mat3 = {
const ADAPT: Mat3 = adapt::<$A>(
<$to as Illuminant>::WHITE_POINT_XYZ,
<$from as Illuminant>::WHITE_POINT_XYZ,
);
Mat3::mul(&<$P as Primaries>::FROM_XYZ_NATIVE, &ADAPT)
};
const LUMINANCE_WEIGHTS: [f32; 3] = <$P as PrimariesToXyz<$to, $A>>::TO_XYZ.luminance_weights();
}
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SrgbPrimaries;
const SRGB_TO_XYZ: Mat3 = derive_rgb_to_xyz(
[0.6400, 0.3300],
[0.3000, 0.6000],
[0.1500, 0.0600],
D65::WHITE_POINT_XYZ,
);
const SRGB_FROM_XYZ: Mat3 = Mat3::invert(&SRGB_TO_XYZ);
impl Primaries for SrgbPrimaries {
type Native = D65;
const R: [f32; 2] = [0.6400, 0.3300];
const G: [f32; 2] = [0.3000, 0.6000];
const B: [f32; 2] = [0.1500, 0.0600];
const TO_XYZ_NATIVE: Mat3 = SRGB_TO_XYZ;
const FROM_XYZ_NATIVE: Mat3 = SRGB_FROM_XYZ;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct P3Primaries;
const P3_TO_XYZ: Mat3 = derive_rgb_to_xyz(
[0.6800, 0.3200],
[0.2650, 0.6900],
[0.1500, 0.0600],
D65::WHITE_POINT_XYZ,
);
const P3_FROM_XYZ: Mat3 = Mat3::invert(&P3_TO_XYZ);
impl Primaries for P3Primaries {
type Native = D65;
const R: [f32; 2] = [0.6800, 0.3200];
const G: [f32; 2] = [0.2650, 0.6900];
const B: [f32; 2] = [0.1500, 0.0600];
const TO_XYZ_NATIVE: Mat3 = P3_TO_XYZ;
const FROM_XYZ_NATIVE: Mat3 = P3_FROM_XYZ;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Rec2020Primaries;
const REC2020_TO_XYZ: Mat3 = derive_rgb_to_xyz(
[0.7080, 0.2920],
[0.1700, 0.7970],
[0.1310, 0.0460],
D65::WHITE_POINT_XYZ,
);
const REC2020_FROM_XYZ: Mat3 = Mat3::invert(&REC2020_TO_XYZ);
impl Primaries for Rec2020Primaries {
type Native = D65;
const R: [f32; 2] = [0.7080, 0.2920];
const G: [f32; 2] = [0.1700, 0.7970];
const B: [f32; 2] = [0.1310, 0.0460];
const TO_XYZ_NATIVE: Mat3 = REC2020_TO_XYZ;
const FROM_XYZ_NATIVE: Mat3 = REC2020_FROM_XYZ;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct AcesAp1Primaries;
const AP1_TO_XYZ: Mat3 = derive_rgb_to_xyz(
[0.71300, 0.29300],
[0.16500, 0.83000],
[0.12800, 0.04400],
AcesWhitePoint::WHITE_POINT_XYZ,
);
const AP1_FROM_XYZ: Mat3 = Mat3::invert(&AP1_TO_XYZ);
impl Primaries for AcesAp1Primaries {
type Native = AcesWhitePoint;
const R: [f32; 2] = [0.71300, 0.29300];
const G: [f32; 2] = [0.16500, 0.83000];
const B: [f32; 2] = [0.12800, 0.04400];
const TO_XYZ_NATIVE: Mat3 = AP1_TO_XYZ;
const FROM_XYZ_NATIVE: Mat3 = AP1_FROM_XYZ;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct AcesAp0Primaries;
const AP0_TO_XYZ: Mat3 = derive_rgb_to_xyz(
[0.73470, 0.26530],
[0.00000, 1.00000],
[0.00010, -0.07700],
AcesWhitePoint::WHITE_POINT_XYZ,
);
const AP0_FROM_XYZ: Mat3 = Mat3::invert(&AP0_TO_XYZ);
impl Primaries for AcesAp0Primaries {
type Native = AcesWhitePoint;
const R: [f32; 2] = [0.73470, 0.26530];
const G: [f32; 2] = [0.00000, 1.00000];
const B: [f32; 2] = [0.00010, -0.07700];
const TO_XYZ_NATIVE: Mat3 = AP0_TO_XYZ;
const FROM_XYZ_NATIVE: Mat3 = AP0_FROM_XYZ;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ProPhotoPrimaries;
const PRO_PHOTO_TO_XYZ: Mat3 = derive_rgb_to_xyz(
[0.7347, 0.2653],
[0.1596, 0.8404],
[0.0366, 0.0001],
D50::WHITE_POINT_XYZ,
);
const PRO_PHOTO_FROM_XYZ: Mat3 = Mat3::invert(&PRO_PHOTO_TO_XYZ);
impl Primaries for ProPhotoPrimaries {
type Native = D50;
const R: [f32; 2] = [0.7347, 0.2653];
const G: [f32; 2] = [0.1596, 0.8404];
const B: [f32; 2] = [0.0366, 0.0001];
const TO_XYZ_NATIVE: Mat3 = PRO_PHOTO_TO_XYZ;
const FROM_XYZ_NATIVE: Mat3 = PRO_PHOTO_FROM_XYZ;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct DciP3Primaries;
const DCI_P3_TO_XYZ: Mat3 = derive_rgb_to_xyz(
[0.6800, 0.3200],
[0.2650, 0.6900],
[0.1500, 0.0600],
DciWhite::WHITE_POINT_XYZ,
);
const DCI_P3_FROM_XYZ: Mat3 = Mat3::invert(&DCI_P3_TO_XYZ);
impl Primaries for DciP3Primaries {
type Native = DciWhite;
const R: [f32; 2] = [0.6800, 0.3200];
const G: [f32; 2] = [0.2650, 0.6900];
const B: [f32; 2] = [0.1500, 0.0600];
const TO_XYZ_NATIVE: Mat3 = DCI_P3_TO_XYZ;
const FROM_XYZ_NATIVE: Mat3 = DCI_P3_FROM_XYZ;
}
impl_native!(SrgbPrimaries, D65);
impl_native!(P3Primaries, D65);
impl_native!(Rec2020Primaries, D65);
impl_native!(AcesAp1Primaries, AcesWhitePoint);
impl_native!(AcesAp0Primaries, AcesWhitePoint);
impl_native!(ProPhotoPrimaries, D50);
impl_native!(DciP3Primaries, DciWhite);
impl_adapted!(AcesAp1Primaries, AcesWhitePoint, D65);
impl_adapted!(AcesAp0Primaries, AcesWhitePoint, D65);
impl_adapted!(AcesAp1Primaries, AcesWhitePoint, D50);
impl_adapted!(AcesAp0Primaries, AcesWhitePoint, D50);
impl_adapted!(ProPhotoPrimaries, D50, D65);
impl_adapted!(SrgbPrimaries, D65, D50);
impl_adapted!(P3Primaries, D65, D50);
impl_adapted!(Rec2020Primaries, D65, D50);
impl_adapted!(DciP3Primaries, DciWhite, D65);