use colr_types::model::ColorMatchingFunctions;
use crate::BackingStore;
use crate::ChannelMap;
use crate::Color;
use crate::illuminant::{D65, Illuminant};
use crate::math::Mat3;
use crate::model::{
IsRadiance, IsReflectance, IsTransmittance, LCh, Lab, Luma, LumaAlpha, Oklab, Oklch, Rgb,
Spectral, WavelengthGrid, Xyz, YCbCr,
};
use crate::primaries::Primaries;
use crate::transfer::Linear;
use crate::transfer::TransferFunction;
impl<P, L> From<Color<[f32; 3], Rgb<P, Linear, L>>> for Color<[f32; 3], Xyz<P::Native>>
where
P: Primaries,
L: BackingStore<[f32; 3]> + ChannelMap<3>,
{
fn from(src: Color<[f32; 3], Rgb<P, Linear, L>>) -> Self {
let s = src.inner();
let [ri, gi, bi] = L::INDICES;
let xyz = P::TO_XYZ_NATIVE.apply([s[ri], s[gi], s[bi]]);
Color::new(xyz)
}
}
impl<P, L> From<Color<[f32; 4], Rgb<P, Linear, L>>> for Color<[f32; 4], Xyz<P::Native>>
where
P: Primaries,
L: BackingStore<[f32; 4]> + ChannelMap<4>,
Xyz<P::Native>: BackingStore<[f32; 4]>,
{
fn from(src: Color<[f32; 4], Rgb<P, Linear, L>>) -> Self {
let s = src.inner();
let [ri, gi, bi, ai] = L::INDICES;
let xyz = P::TO_XYZ_NATIVE.apply([s[ri], s[gi], s[bi]]);
Color::new([xyz[0], xyz[1], xyz[2], s[ai]])
}
}
#[cfg(feature = "glam")]
impl<P, L> From<Color<glam::Vec3A, Rgb<P, Linear, L>>> for Color<glam::Vec3A, Xyz<P::Native>>
where
P: Primaries,
L: BackingStore<glam::Vec3A> + ChannelMap<3>,
Xyz<P::Native>: BackingStore<glam::Vec3A>,
{
fn from(src: Color<glam::Vec3A, Rgb<P, Linear, L>>) -> Self {
let s = src.inner().to_array();
let [ri, gi, bi] = L::INDICES;
let xyz = P::TO_XYZ_NATIVE.apply([s[ri], s[gi], s[bi]]);
Color::new(glam::Vec3A::from_array(xyz))
}
}
#[cfg(feature = "glam")]
impl<P, L> From<Color<glam::Vec4, Rgb<P, Linear, L>>> for Color<glam::Vec4, Xyz<P::Native>>
where
P: Primaries,
L: BackingStore<glam::Vec4> + ChannelMap<4>,
Xyz<P::Native>: BackingStore<glam::Vec4>,
{
fn from(src: Color<glam::Vec4, Rgb<P, Linear, L>>) -> Self {
let s = src.inner().to_array();
let [ri, gi, bi, ai] = L::INDICES;
let xyz = P::TO_XYZ_NATIVE.apply([s[ri], s[gi], s[bi]]);
Color::new(glam::Vec4::new(xyz[0], xyz[1], xyz[2], s[ai]))
}
}
impl<P, L> From<Color<[f32; 3], Xyz<P::Native>>> for Color<[f32; 3], Rgb<P, Linear, L>>
where
P: Primaries,
L: BackingStore<[f32; 3]> + ChannelMap<3>,
{
fn from(src: Color<[f32; 3], Xyz<P::Native>>) -> Self {
let rgb = P::FROM_XYZ_NATIVE.apply(src.inner());
let [ri, gi, bi] = L::INDICES;
let mut out = [0.0f32; 3];
out[ri] = rgb[0];
out[gi] = rgb[1];
out[bi] = rgb[2];
Color::new(out)
}
}
impl<P, L> From<Color<[f32; 4], Xyz<P::Native>>> for Color<[f32; 4], Rgb<P, Linear, L>>
where
P: Primaries,
L: BackingStore<[f32; 4]> + ChannelMap<4>,
Xyz<P::Native>: BackingStore<[f32; 4]>,
{
fn from(src: Color<[f32; 4], Xyz<P::Native>>) -> Self {
let [x, y, z, a] = src.inner();
let rgb = P::FROM_XYZ_NATIVE.apply([x, y, z]);
let [ri, gi, bi, ai] = L::INDICES;
let mut out = [0.0f32; 4];
out[ri] = rgb[0];
out[gi] = rgb[1];
out[bi] = rgb[2];
out[ai] = a;
Color::new(out)
}
}
#[cfg(feature = "glam")]
impl<P, L> From<Color<glam::Vec3A, Xyz<P::Native>>> for Color<glam::Vec3A, Rgb<P, Linear, L>>
where
P: Primaries,
L: BackingStore<glam::Vec3A> + ChannelMap<3>,
Xyz<P::Native>: BackingStore<glam::Vec3A>,
{
fn from(src: Color<glam::Vec3A, Xyz<P::Native>>) -> Self {
let rgb = P::FROM_XYZ_NATIVE.apply(src.inner().to_array());
let [ri, gi, bi] = L::INDICES;
let mut out = [0.0f32; 3];
out[ri] = rgb[0];
out[gi] = rgb[1];
out[bi] = rgb[2];
Color::new(glam::Vec3A::from_array(out))
}
}
#[cfg(feature = "glam")]
impl<P, L> From<Color<glam::Vec4, Xyz<P::Native>>> for Color<glam::Vec4, Rgb<P, Linear, L>>
where
P: Primaries,
L: BackingStore<glam::Vec4> + ChannelMap<4>,
Xyz<P::Native>: BackingStore<glam::Vec4>,
{
fn from(src: Color<glam::Vec4, Xyz<P::Native>>) -> Self {
let [x, y, z, a] = src.inner().to_array();
let rgb = P::FROM_XYZ_NATIVE.apply([x, y, z]);
let [ri, gi, bi, ai] = L::INDICES;
let mut out = [0.0f32; 4];
out[ri] = rgb[0];
out[gi] = rgb[1];
out[bi] = rgb[2];
out[ai] = a;
Color::new(glam::Vec4::from_array(out))
}
}
#[inline(always)]
fn lab_f(t: f32) -> f32 {
const DELTA: f32 = 6.0 / 29.0;
const DELTA_SQ: f32 = DELTA * DELTA;
const DELTA_CB: f32 = DELTA * DELTA_SQ;
if t > DELTA_CB {
(t).cbrt()
} else {
t / (3.0 * DELTA_SQ) + 4.0 / 29.0
}
}
#[inline(always)]
fn lab_f_inv(t: f32) -> f32 {
const DELTA: f32 = 6.0 / 29.0;
const DELTA_SQ: f32 = DELTA * DELTA;
if t > DELTA {
t * t * t
} else {
3.0 * DELTA_SQ * (t - 4.0 / 29.0)
}
}
impl<W: Illuminant> From<Color<[f32; 3], Xyz<W>>> for Color<[f32; 3], Lab<W>> {
fn from(src: Color<[f32; 3], Xyz<W>>) -> Self {
let [x, y, z] = src.inner();
let [xn, yn, zn] = W::WHITE_POINT_XYZ;
let fx = lab_f(x / xn);
let fy = lab_f(y / yn);
let fz = lab_f(z / zn);
Color::new([116.0 * fy - 16.0, 500.0 * (fx - fy), 200.0 * (fy - fz)])
}
}
impl<W: Illuminant, const O: usize> From<Color<[f32; 4], Xyz<W>>> for Color<[f32; 4], Lab<W, O>> {
fn from(src: Color<[f32; 4], Xyz<W>>) -> Self {
let [x, y, z, alpha] = src.inner();
let [xn, yn, zn] = W::WHITE_POINT_XYZ;
let fx = lab_f(x / xn);
let fy = lab_f(y / yn);
let fz = lab_f(z / zn);
let mut out = [0.0f32; 4];
out[O] = 116.0 * fy - 16.0;
out[O + 1] = 500.0 * (fx - fy);
out[O + 2] = 200.0 * (fy - fz);
out[3 - O * 3] = alpha;
Color::new(out)
}
}
impl<W: Illuminant> From<Color<[f32; 3], Lab<W>>> for Color<[f32; 3], Xyz<W>> {
fn from(src: Color<[f32; 3], Lab<W>>) -> Self {
let [l, a, b] = src.inner();
let [xn, yn, zn] = W::WHITE_POINT_XYZ;
let fy = (l + 16.0) / 116.0;
Color::new([
xn * lab_f_inv(a / 500.0 + fy),
yn * lab_f_inv(fy),
zn * lab_f_inv(fy - b / 200.0),
])
}
}
impl<W: Illuminant, const O: usize> From<Color<[f32; 4], Lab<W, O>>> for Color<[f32; 4], Xyz<W>> {
fn from(src: Color<[f32; 4], Lab<W, O>>) -> Self {
let s = src.inner();
let (l, a, b, alpha) = (s[O], s[O + 1], s[O + 2], s[3 - O * 3]);
let [xn, yn, zn] = W::WHITE_POINT_XYZ;
let fy = (l + 16.0) / 116.0;
Color::new([
xn * lab_f_inv(a / 500.0 + fy),
yn * lab_f_inv(fy),
zn * lab_f_inv(fy - b / 200.0),
alpha,
])
}
}
#[allow(clippy::excessive_precision)]
const OKLAB_M1: Mat3 = Mat3 {
col0: [0.8189330101, 0.0329845436, 0.0482003018, 0.0],
col1: [0.3618667424, 0.9293118715, 0.2643662691, 0.0],
col2: [-0.1288597137, 0.0361456387, 0.6338517070, 0.0],
};
#[allow(clippy::excessive_precision)]
const OKLAB_M2: Mat3 = Mat3 {
col0: [0.2104542553, 1.9779984951, 0.0259040371, 0.0],
col1: [0.7936177850, -2.4285922050, 0.7827717662, 0.0],
col2: [-0.0040720468, 0.4505937099, -0.8086757660, 0.0],
};
#[allow(clippy::excessive_precision)]
const OKLAB_M1_INV: Mat3 = Mat3 {
col0: [1.2270138511, -0.0405801784, -0.0763812845, 0.0],
col1: [-0.5577999807, 1.1122568696, -0.4214819784, 0.0],
col2: [0.2812561490, -0.0716766787, 1.5861632204, 0.0],
};
#[allow(clippy::excessive_precision)]
const OKLAB_M2_INV: Mat3 = Mat3 {
col0: [1.0, 1.0, 1.0, 0.0],
col1: [0.3963377774, -0.1055613458, -0.0894841775, 0.0],
col2: [0.2158037573, -0.0638541728, -1.2914855480, 0.0],
};
impl From<Color<[f32; 3], Xyz<D65>>> for Color<[f32; 3], Oklab> {
fn from(src: Color<[f32; 3], Xyz<D65>>) -> Self {
let xyz = src.inner();
let lms = OKLAB_M1.apply(xyz);
let lms_g = [(lms[0]).cbrt(), (lms[1]).cbrt(), (lms[2]).cbrt()];
Color::new(OKLAB_M2.apply(lms_g))
}
}
impl<const O: usize> From<Color<[f32; 4], Xyz<D65>>> for Color<[f32; 4], Oklab<O>> {
fn from(src: Color<[f32; 4], Xyz<D65>>) -> Self {
let [x, y, z, alpha] = src.inner();
let lms = OKLAB_M1.apply([x, y, z]);
let lms_g = [lms[0].cbrt(), lms[1].cbrt(), lms[2].cbrt()];
let [l, a, b] = OKLAB_M2.apply(lms_g);
let mut out = [0.0f32; 4];
out[O] = l;
out[O + 1] = a;
out[O + 2] = b;
out[3 - O * 3] = alpha;
Color::new(out)
}
}
impl From<Color<[f32; 3], Oklab>> for Color<[f32; 3], Xyz<D65>> {
fn from(src: Color<[f32; 3], Oklab>) -> Self {
let lab = src.inner();
let lms_g = OKLAB_M2_INV.apply(lab);
let lms = [lms_g[0].powi(3), lms_g[1].powi(3), lms_g[2].powi(3)];
Color::new(OKLAB_M1_INV.apply(lms))
}
}
impl<const O: usize> From<Color<[f32; 4], Oklab<O>>> for Color<[f32; 4], Xyz<D65>> {
fn from(src: Color<[f32; 4], Oklab<O>>) -> Self {
let s = src.inner();
let (l, a, b, alpha) = (s[O], s[O + 1], s[O + 2], s[3 - O * 3]);
let lms_g = OKLAB_M2_INV.apply([l, a, b]);
let lms = [lms_g[0].powi(3), lms_g[1].powi(3), lms_g[2].powi(3)];
let [x, y, z] = OKLAB_M1_INV.apply(lms);
Color::new([x, y, z, alpha])
}
}
impl<W: Illuminant> From<Color<[f32; 3], Lab<W>>> for Color<[f32; 3], LCh<W>> {
fn from(src: Color<[f32; 3], Lab<W>>) -> Self {
let [l, a, b] = src.inner();
let c = (a * a + b * b).sqrt();
let h = (b).atan2(a).rem_euclid(core::f32::consts::TAU);
Color::new([l, c, h])
}
}
impl<W: Illuminant, const O: usize> From<Color<[f32; 4], Lab<W, O>>>
for Color<[f32; 4], LCh<W, O>>
{
fn from(src: Color<[f32; 4], Lab<W, O>>) -> Self {
let s = src.inner();
let (l, a, b, alpha) = (s[O], s[O + 1], s[O + 2], s[3 - O * 3]);
let c = (a * a + b * b).sqrt();
let h = b.atan2(a).rem_euclid(core::f32::consts::TAU);
let mut out = [0.0f32; 4];
out[O] = l;
out[O + 1] = c;
out[O + 2] = h;
out[3 - O * 3] = alpha;
Color::new(out)
}
}
impl<W: Illuminant> From<Color<[f32; 3], LCh<W>>> for Color<[f32; 3], Lab<W>> {
fn from(src: Color<[f32; 3], LCh<W>>) -> Self {
let [l, c, h] = src.inner();
Color::new([l, c * h.cos(), c * h.sin()])
}
}
impl<W: Illuminant, const O: usize> From<Color<[f32; 4], LCh<W, O>>>
for Color<[f32; 4], Lab<W, O>>
{
fn from(src: Color<[f32; 4], LCh<W, O>>) -> Self {
let s = src.inner();
let (l, c, h, alpha) = (s[O], s[O + 1], s[O + 2], s[3 - O * 3]);
let mut out = [0.0f32; 4];
out[O] = l;
out[O + 1] = c * h.cos();
out[O + 2] = c * h.sin();
out[3 - O * 3] = alpha;
Color::new(out)
}
}
impl From<Color<[f32; 3], Oklab>> for Color<[f32; 3], Oklch> {
fn from(src: Color<[f32; 3], Oklab>) -> Self {
let [l, a, b] = src.inner();
let c = (a * a + b * b).sqrt();
let h = (b).atan2(a).rem_euclid(core::f32::consts::TAU);
Color::new([l, c, h])
}
}
impl<const O: usize> From<Color<[f32; 4], Oklab<O>>> for Color<[f32; 4], Oklch<O>> {
fn from(src: Color<[f32; 4], Oklab<O>>) -> Self {
let s = src.inner();
let (l, a, b, alpha) = (s[O], s[O + 1], s[O + 2], s[3 - O * 3]);
let c = (a * a + b * b).sqrt();
let h = b.atan2(a).rem_euclid(core::f32::consts::TAU);
let mut out = [0.0f32; 4];
out[O] = l;
out[O + 1] = c;
out[O + 2] = h;
out[3 - O * 3] = alpha;
Color::new(out)
}
}
impl From<Color<[f32; 3], Oklch>> for Color<[f32; 3], Oklab> {
fn from(src: Color<[f32; 3], Oklch>) -> Self {
let [l, c, h] = src.inner();
Color::new([l, c * h.cos(), c * h.sin()])
}
}
impl<const O: usize> From<Color<[f32; 4], Oklch<O>>> for Color<[f32; 4], Oklab<O>> {
fn from(src: Color<[f32; 4], Oklch<O>>) -> Self {
let s = src.inner();
let (l, c, h, alpha) = (s[O], s[O + 1], s[O + 2], s[3 - O * 3]);
let mut out = [0.0f32; 4];
out[O] = l;
out[O + 1] = c * h.cos();
out[O + 2] = c * h.sin();
out[3 - O * 3] = alpha;
Color::new(out)
}
}
impl From<Color<[f32; 3], Xyz<D65>>> for Color<[f32; 3], Oklch> {
fn from(src: Color<[f32; 3], Xyz<D65>>) -> Self {
let lms = OKLAB_M1.apply(src.inner());
let lms_g = [lms[0].cbrt(), lms[1].cbrt(), lms[2].cbrt()];
let [l, a, b] = OKLAB_M2.apply(lms_g);
let c = (a * a + b * b).sqrt();
let h = b.atan2(a).rem_euclid(core::f32::consts::TAU);
Color::new([l, c, h])
}
}
impl From<Color<[f32; 3], Oklch>> for Color<[f32; 3], Xyz<D65>> {
fn from(src: Color<[f32; 3], Oklch>) -> Self {
let [l, c, h] = src.inner();
let lms_g = OKLAB_M2_INV.apply([l, c * h.cos(), c * h.sin()]);
let lms = [lms_g[0].powi(3), lms_g[1].powi(3), lms_g[2].powi(3)];
Color::new(OKLAB_M1_INV.apply(lms))
}
}
impl<P, TF, L> From<Color<[f32; 3], Rgb<P, TF, L>>> for Color<f32, Luma<P, TF>>
where
P: Primaries,
TF: TransferFunction,
L: BackingStore<[f32; 3]> + ChannelMap<3>,
{
fn from(src: Color<[f32; 3], Rgb<P, TF, L>>) -> Self {
let s = src.inner();
let [ri, gi, bi] = L::INDICES;
let [kr, kg, kb] = P::LUMA_WEIGHTS;
Color::new(kr * s[ri] + kg * s[gi] + kb * s[bi])
}
}
impl<P, TF, L, A> From<Color<[f32; 4], Rgb<P, TF, L>>> for Color<[f32; 2], LumaAlpha<P, TF, A>>
where
P: Primaries,
TF: TransferFunction,
L: BackingStore<[f32; 4]> + ChannelMap<4>,
A: crate::AlphaState,
{
fn from(src: Color<[f32; 4], Rgb<P, TF, L>>) -> Self {
let s = src.inner();
let [ri, gi, bi, ai] = L::INDICES;
let [kr, kg, kb] = P::LUMA_WEIGHTS;
Color::new([kr * s[ri] + kg * s[gi] + kb * s[bi], s[ai]])
}
}
use crate::layout::YCbCrLayout;
#[inline(always)]
fn rgb_to_ycbcr(r: f32, g: f32, b: f32, kr: f32, kg: f32, kb: f32) -> (f32, f32, f32) {
let y = kr * r + kg * g + kb * b;
let cb = (b - y) / (2.0 * (1.0 - kb));
let cr = (r - y) / (2.0 * (1.0 - kr));
(y, cb, cr)
}
#[inline(always)]
fn ycbcr_to_rgb(y: f32, cb: f32, cr: f32, kr: f32, kg: f32, kb: f32) -> (f32, f32, f32) {
let r = y + cr * 2.0 * (1.0 - kr);
let b = y + cb * 2.0 * (1.0 - kb);
let g = if kg.abs() > f32::EPSILON {
(y - kr * r - kb * b) / kg
} else {
0.0
};
(r, g, b)
}
impl<P, TF, L, L2> From<Color<[f32; 3], Rgb<P, TF, L>>> for Color<[f32; 3], YCbCr<P, TF, L2>>
where
P: Primaries,
TF: TransferFunction,
L: BackingStore<[f32; 3]> + ChannelMap<3>,
L2: YCbCrLayout + BackingStore<[f32; 3]> + ChannelMap<3>,
{
fn from(src: Color<[f32; 3], Rgb<P, TF, L>>) -> Self {
let s = src.inner();
let [ri, gi, bi] = L::INDICES;
let [yi, cbi, cri] = L2::INDICES;
let [kr, kg, kb] = P::LUMA_WEIGHTS;
let (y, cb, cr) = rgb_to_ycbcr(s[ri], s[gi], s[bi], kr, kg, kb);
let mut out = [0.0f32; 3];
out[yi] = y;
out[cbi] = cb;
out[cri] = cr;
Color::new(out)
}
}
impl<P, TF, L, L2> From<Color<[f32; 4], Rgb<P, TF, L>>> for Color<[f32; 4], YCbCr<P, TF, L2>>
where
P: Primaries,
TF: TransferFunction,
L: BackingStore<[f32; 4]> + ChannelMap<4>,
L2: YCbCrLayout + BackingStore<[f32; 4]> + ChannelMap<4>,
{
fn from(src: Color<[f32; 4], Rgb<P, TF, L>>) -> Self {
let s = src.inner();
let [ri, gi, bi, ai] = L::INDICES;
let [yi, cbi, cri, ai2] = L2::INDICES;
let [kr, kg, kb] = P::LUMA_WEIGHTS;
let (y, cb, cr) = rgb_to_ycbcr(s[ri], s[gi], s[bi], kr, kg, kb);
let mut out = [0.0f32; 4];
out[yi] = y;
out[cbi] = cb;
out[cri] = cr;
out[ai2] = s[ai];
Color::new(out)
}
}
impl<P, TF, L, L2> From<Color<[f32; 3], YCbCr<P, TF, L2>>> for Color<[f32; 3], Rgb<P, TF, L>>
where
P: Primaries,
TF: TransferFunction,
L: BackingStore<[f32; 3]> + ChannelMap<3>,
L2: YCbCrLayout + BackingStore<[f32; 3]> + ChannelMap<3>,
{
fn from(src: Color<[f32; 3], YCbCr<P, TF, L2>>) -> Self {
let s = src.inner();
let [yi, cbi, cri] = L2::INDICES;
let [ri, gi, bi] = L::INDICES;
let [kr, kg, kb] = P::LUMA_WEIGHTS;
let (r, g, b) = ycbcr_to_rgb(s[yi], s[cbi], s[cri], kr, kg, kb);
let mut out = [0.0f32; 3];
out[ri] = r;
out[gi] = g;
out[bi] = b;
Color::new(out)
}
}
impl<P, TF, L, L2> From<Color<[f32; 4], YCbCr<P, TF, L2>>> for Color<[f32; 4], Rgb<P, TF, L>>
where
P: Primaries,
TF: TransferFunction,
L: BackingStore<[f32; 4]> + ChannelMap<4>,
L2: YCbCrLayout + BackingStore<[f32; 4]> + ChannelMap<4>,
{
fn from(src: Color<[f32; 4], YCbCr<P, TF, L2>>) -> Self {
let s = src.inner();
let [yi, cbi, cri, ai] = L2::INDICES;
let [ri, gi, bi, ai2] = L::INDICES;
let [kr, kg, kb] = P::LUMA_WEIGHTS;
let (r, g, b) = ycbcr_to_rgb(s[yi], s[cbi], s[cri], kr, kg, kb);
let mut out = [0.0f32; 4];
out[ri] = r;
out[gi] = g;
out[bi] = b;
out[ai2] = s[ai];
Color::new(out)
}
}
impl<const BANDS: usize, G> Color<[f32; BANDS], Spectral<BANDS, G, IsRadiance>>
where
G: WavelengthGrid<BANDS>,
{
pub fn to_xyz<W>(self) -> Color<[f32; 3], Xyz<W>>
where
W: Illuminant,
G: ColorMatchingFunctions<BANDS, W::Observer>,
{
let spd = self.inner();
let mut x = 0.0f32;
let mut y = 0.0f32;
let mut z = 0.0f32;
let cmf_x = <G as ColorMatchingFunctions<BANDS, W::Observer>>::CMF_X;
let cmf_y = <G as ColorMatchingFunctions<BANDS, W::Observer>>::CMF_Y;
let cmf_z = <G as ColorMatchingFunctions<BANDS, W::Observer>>::CMF_Z;
for ((&s, &cx), (&cy, &cz)) in spd.iter().zip(cmf_x).zip(cmf_y.iter().zip(cmf_z)) {
x += s * cx;
y += s * cy;
z += s * cz;
}
Color::new([x * G::STEP_NM, y * G::STEP_NM, z * G::STEP_NM])
}
}
impl<const BANDS: usize, G> Color<[f32; BANDS], Spectral<BANDS, G, IsReflectance>>
where
G: WavelengthGrid<BANDS>,
{
pub fn to_xyz_under<W>(
self,
illuminant: Color<[f32; BANDS], Spectral<BANDS, G, IsRadiance>>,
) -> Color<[f32; 3], Xyz<W>>
where
W: Illuminant,
G: ColorMatchingFunctions<BANDS, W::Observer>,
{
let refl = self.inner();
let spd = illuminant.inner();
let cmf_x = <G as ColorMatchingFunctions<BANDS, W::Observer>>::CMF_X;
let cmf_y = <G as ColorMatchingFunctions<BANDS, W::Observer>>::CMF_Y;
let cmf_z = <G as ColorMatchingFunctions<BANDS, W::Observer>>::CMF_Z;
let mut k_inv = 0.0f32;
for (&s, &cy) in spd.iter().zip(cmf_y) {
k_inv += s * cy;
}
let k = 100.0 / (k_inv * G::STEP_NM);
let mut x = 0.0f32;
let mut y = 0.0f32;
let mut z = 0.0f32;
for (((&s, &r), &cx), (&cy, &cz)) in spd
.iter()
.zip(&refl)
.zip(cmf_x)
.zip(cmf_y.iter().zip(cmf_z))
{
let sr = s * r;
x += sr * cx;
y += sr * cy;
z += sr * cz;
}
Color::new([k * x * G::STEP_NM, k * y * G::STEP_NM, k * z * G::STEP_NM])
}
}
impl<const BANDS: usize, G> Color<[f32; BANDS], Spectral<BANDS, G, IsTransmittance>>
where
G: WavelengthGrid<BANDS>,
{
pub fn to_xyz_through<W>(
self,
illuminant: Color<[f32; BANDS], Spectral<BANDS, G, IsRadiance>>,
) -> Color<[f32; 3], Xyz<W>>
where
W: Illuminant,
G: ColorMatchingFunctions<BANDS, W::Observer>,
{
let trans = self.inner();
let spd = illuminant.inner();
let cmf_x = <G as ColorMatchingFunctions<BANDS, W::Observer>>::CMF_X;
let cmf_y = <G as ColorMatchingFunctions<BANDS, W::Observer>>::CMF_Y;
let cmf_z = <G as ColorMatchingFunctions<BANDS, W::Observer>>::CMF_Z;
let mut k_inv = 0.0f32;
for (&s, &cy) in spd.iter().zip(cmf_y) {
k_inv += s * cy;
}
let k = 100.0 / (k_inv * G::STEP_NM);
let mut x = 0.0f32;
let mut y = 0.0f32;
let mut z = 0.0f32;
for (((&s, &t), &cx), (&cy, &cz)) in spd
.iter()
.zip(&trans)
.zip(cmf_x)
.zip(cmf_y.iter().zip(cmf_z))
{
let st = s * t;
x += st * cx;
y += st * cy;
z += st * cz;
}
Color::new([k * x * G::STEP_NM, k * y * G::STEP_NM, k * z * G::STEP_NM])
}
}
impl<W: Illuminant> From<Color<[f32; 3], Xyz<W>>> for Color<[f32; 4], Xyz<W>> {
fn from(src: Color<[f32; 3], Xyz<W>>) -> Self {
let [x, y, z] = src.inner();
Color::new([x, y, z, 1.0])
}
}
impl<W: Illuminant> From<Color<[f32; 4], Xyz<W>>> for Color<[f32; 3], Xyz<W>> {
fn from(src: Color<[f32; 4], Xyz<W>>) -> Self {
let [x, y, z, _] = src.inner();
Color::new([x, y, z])
}
}
impl<W: Illuminant, const O: usize> From<Color<[f32; 3], Lab<W, O>>>
for Color<[f32; 4], Lab<W, O>>
{
fn from(src: Color<[f32; 3], Lab<W, O>>) -> Self {
let [c0, c1, c2] = src.inner();
let mut out = [0.0f32; 4];
out[O] = c0;
out[O + 1] = c1;
out[O + 2] = c2;
out[3 - O * 3] = 1.0;
Color::new(out)
}
}
impl<W: Illuminant, const O: usize> From<Color<[f32; 4], Lab<W, O>>>
for Color<[f32; 3], Lab<W, O>>
{
fn from(src: Color<[f32; 4], Lab<W, O>>) -> Self {
let s = src.inner();
Color::new([s[O], s[O + 1], s[O + 2]])
}
}
impl<W: Illuminant, const O: usize> From<Color<[f32; 3], LCh<W, O>>>
for Color<[f32; 4], LCh<W, O>>
{
fn from(src: Color<[f32; 3], LCh<W, O>>) -> Self {
let [c0, c1, c2] = src.inner();
let mut out = [0.0f32; 4];
out[O] = c0;
out[O + 1] = c1;
out[O + 2] = c2;
out[3 - O * 3] = 1.0;
Color::new(out)
}
}
impl<W: Illuminant, const O: usize> From<Color<[f32; 4], LCh<W, O>>>
for Color<[f32; 3], LCh<W, O>>
{
fn from(src: Color<[f32; 4], LCh<W, O>>) -> Self {
let s = src.inner();
Color::new([s[O], s[O + 1], s[O + 2]])
}
}
impl<const O: usize> From<Color<[f32; 3], Oklab<O>>> for Color<[f32; 4], Oklab<O>> {
fn from(src: Color<[f32; 3], Oklab<O>>) -> Self {
let [c0, c1, c2] = src.inner();
let mut out = [0.0f32; 4];
out[O] = c0;
out[O + 1] = c1;
out[O + 2] = c2;
out[3 - O * 3] = 1.0;
Color::new(out)
}
}
impl<const O: usize> From<Color<[f32; 4], Oklab<O>>> for Color<[f32; 3], Oklab<O>> {
fn from(src: Color<[f32; 4], Oklab<O>>) -> Self {
let s = src.inner();
Color::new([s[O], s[O + 1], s[O + 2]])
}
}
impl<const O: usize> From<Color<[f32; 3], Oklch<O>>> for Color<[f32; 4], Oklch<O>> {
fn from(src: Color<[f32; 3], Oklch<O>>) -> Self {
let [c0, c1, c2] = src.inner();
let mut out = [0.0f32; 4];
out[O] = c0;
out[O + 1] = c1;
out[O + 2] = c2;
out[3 - O * 3] = 1.0;
Color::new(out)
}
}
impl<const O: usize> From<Color<[f32; 4], Oklch<O>>> for Color<[f32; 3], Oklch<O>> {
fn from(src: Color<[f32; 4], Oklch<O>>) -> Self {
let s = src.inner();
Color::new([s[O], s[O + 1], s[O + 2]])
}
}
impl<M> From<Color<[u8; 3], M>> for Color<[f32; 3], M>
where
M: BackingStore<[u8; 3]> + BackingStore<[f32; 3]>,
{
fn from(src: Color<[u8; 3], M>) -> Self {
let [r, g, b] = src.inner();
Color::new([r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0])
}
}
impl<M> From<Color<[u8; 4], M>> for Color<[f32; 4], M>
where
M: BackingStore<[u8; 4]> + BackingStore<[f32; 4]>,
{
fn from(src: Color<[u8; 4], M>) -> Self {
let [r, g, b, a] = src.inner();
Color::new([
r as f32 / 255.0,
g as f32 / 255.0,
b as f32 / 255.0,
a as f32 / 255.0,
])
}
}