mod oklab;
mod srlab2;
mod transfer;
mod yuv;
use crate::color_matrix::{ColMatrix, RowMatrix};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Color {
Rgb {
primary: Primaries,
transfer: Transfer,
whitepoint: Whitepoint,
luminance: Luminance,
},
Luma {
transfer: Transfer,
whitepoint: Whitepoint,
luminance: Luminance,
},
#[cfg(any())] LumaDigital {
primary: Primaries,
transfer: Transfer,
whitepoint: Whitepoint,
luminance: Luminance,
},
Yuv {
primary: Primaries,
whitepoint: Whitepoint,
transfer: Transfer,
luminance: Luminance,
differencing: Differencing,
},
Oklab,
Scalars {
transfer: Transfer,
},
SrLab2 { whitepoint: Whitepoint },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ColorChannelModel {
Rgb,
Luma,
Yuv,
Lab,
Lch,
Cmyk,
Hsv,
Hsl,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ColorChannel {
R,
G,
B,
Luma,
Alpha,
Cb,
Cr,
L,
LABa,
LABb,
C,
LABh,
X,
Y,
Z,
Scalar0,
Scalar1,
Scalar2,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(u8)]
#[non_exhaustive]
pub enum Transfer {
Bt709,
Bt470M,
Bt470,
Bt601,
Smpte240,
Linear,
Srgb,
Bt2020_10bit,
Bt2020_12bit,
Smpte2084,
Bt2100Pq,
Bt2100Hlg,
Bt2100Scene,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum LightnessModel {
Linear,
Digital,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Luminance {
Sdr,
Hdr,
AdobeRgb,
DciP3,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Primaries {
Xyz,
Bt601_525,
Bt601_625,
Bt709,
Smpte240,
Bt2020,
Bt2100,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Differencing {
Bt407MPal,
Bt407MPalPrecise,
Bt601,
Bt601Quantized,
Bt601FullSwing,
Bt709,
Bt709Quantized,
Bt709FullSwing,
YDbDr,
Bt2020,
Bt2100,
YCoCg,
}
#[non_exhaustive]
pub enum DifferencingYiq {
Ntsc1953,
SmpteC,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Whitepoint {
A,
B,
C,
D50,
D55,
D65,
D75,
E,
F2,
F7,
F11,
}
impl Color {
pub const SRGB: Color = Color::Rgb {
luminance: Luminance::Sdr,
primary: Primaries::Bt709,
transfer: Transfer::Srgb,
whitepoint: Whitepoint::D65,
};
pub const SRGB_LUMA: Color = Color::Luma {
luminance: Luminance::Sdr,
transfer: Transfer::Srgb,
whitepoint: Whitepoint::D65,
};
pub const RGB_ITU_BT2020: Color = Color::Rgb {
luminance: Luminance::Sdr,
primary: Primaries::Bt2020,
transfer: Transfer::Bt2020_10bit,
whitepoint: Whitepoint::D65,
};
pub const RGB_ITU_BT470_525: Color = Color::Rgb {
luminance: Luminance::Sdr,
primary: Primaries::Bt601_525,
transfer: Transfer::Bt470,
whitepoint: Whitepoint::D65,
};
pub const RGB_ITU_BT470_625: Color = Color::Rgb {
luminance: Luminance::Sdr,
primary: Primaries::Bt601_625,
transfer: Transfer::Bt470,
whitepoint: Whitepoint::D65,
};
pub const BT709_RGB: Color = Color::Rgb {
luminance: Luminance::Sdr,
primary: Primaries::Bt709,
transfer: Transfer::Bt709,
whitepoint: Whitepoint::D65,
};
#[allow(deprecated)]
pub const BT709: Color = Color::Yuv {
luminance: Luminance::Sdr,
primary: Primaries::Bt709,
transfer: Transfer::Bt709,
whitepoint: Whitepoint::D65,
differencing: Differencing::Bt709,
};
pub(crate) fn to_xyz_slice(&self, pixel: &[[f32; 4]], xyz: &mut [[f32; 4]]) {
if let Color::Rgb {
primary,
transfer,
whitepoint,
luminance: _,
} = self
{
let to_xyz = primary.to_xyz(*whitepoint);
if let Some(eo_transfer) = transfer.to_optical_display_slice() {
eo_transfer(pixel, xyz);
for target_xyz in xyz {
let [r, g, b, a] = *target_xyz;
let [x, y, z] = to_xyz.mul_vec([r, g, b]);
*target_xyz = [x, y, z, a];
}
} else {
for (target_xyz, src_pix) in xyz.iter_mut().zip(pixel) {
*target_xyz = transfer.to_optical_display(*src_pix);
}
for target_xyz in xyz {
let [r, g, b, a] = *target_xyz;
let [x, y, z] = to_xyz.mul_vec([r, g, b]);
*target_xyz = [x, y, z, a];
}
}
return;
} else if let Color::Luma {
transfer,
whitepoint,
luminance: _,
} = self
{
let [x, y, z] = whitepoint.to_xyz();
if let Some(eo_transfer) = transfer.to_optical_display_slice() {
eo_transfer(pixel, xyz);
for target_xyz in xyz {
let [l, _, _, a] = *target_xyz;
*target_xyz = [l * x, l * y, l * z, a];
}
} else {
for (target_xyz, src_pix) in xyz.iter_mut().zip(pixel) {
let [l, _, _, a] = transfer.to_optical_display(*src_pix);
*target_xyz = [l * x, l * y, l * z, a];
}
}
return;
} else if let Color::Oklab {} = self {
return oklab::to_xyz_slice(pixel, xyz);
} else if let Color::SrLab2 { whitepoint } = self {
return srlab2::to_xyz_slice(pixel, xyz, *whitepoint);
}
for (src_pix, target_xyz) in pixel.iter().zip(xyz) {
*target_xyz = self.to_xyz_once(*src_pix)
}
}
pub(crate) fn from_xyz_slice(&self, xyz: &[[f32; 4]], pixel: &mut [[f32; 4]]) {
if let Color::Rgb {
primary,
transfer,
whitepoint,
luminance: _,
} = self
{
let from_xyz = primary.to_xyz(*whitepoint).inv();
if let Some(oe_transfer) = transfer.from_optical_display_slice() {
for (target_pix, src_xyz) in pixel.iter_mut().zip(xyz) {
let [x, y, z, a] = *src_xyz;
let [r, g, b] = from_xyz.mul_vec([x, y, z]);
*target_pix = [r, g, b, a];
}
oe_transfer(pixel);
} else {
for (target_pix, src_xyz) in pixel.iter_mut().zip(xyz) {
let [x, y, z, a] = *src_xyz;
let [r, g, b] = from_xyz.mul_vec([x, y, z]);
*target_pix = [r, g, b, a];
}
for target_pix in pixel {
*target_pix = transfer.from_optical_display(*target_pix);
}
}
return;
} else if let Color::Luma {
transfer,
whitepoint: _,
luminance: _,
} = self
{
if let Some(oe_transfer) = transfer.from_optical_display_slice() {
for (target_pix, src_xyz) in pixel.iter_mut().zip(xyz) {
let [_, y, _, a] = *src_xyz;
*target_pix = [y, 0.0, 0.0, a];
}
oe_transfer(pixel);
} else {
for (target_pix, src_xyz) in pixel.iter_mut().zip(xyz) {
let [_, y, _, a] = *src_xyz;
*target_pix = transfer.from_optical_display([y, 0.0, 0.0, a]);
}
}
} else if let Color::Oklab {} = self {
return oklab::from_xyz_slice(xyz, pixel);
} else if let Color::SrLab2 { whitepoint } = self {
return srlab2::from_xyz_slice(xyz, pixel, *whitepoint);
}
for (target_pix, src_xyz) in pixel.iter_mut().zip(xyz) {
*target_pix = self.from_xyz_once(*src_xyz)
}
}
pub(crate) fn to_xyz_once(&self, value: [f32; 4]) -> [f32; 4] {
match self {
Color::Oklab => oklab::oklab_to_xyz(value),
Color::Rgb {
primary,
transfer,
whitepoint,
luminance: _,
} => {
let [r, g, b, a] = transfer.to_optical_display(value);
let to_xyz = primary.to_xyz(*whitepoint);
let [x, y, z] = to_xyz.mul_vec([r, g, b]);
[x, y, z, a]
}
Color::Luma {
transfer,
whitepoint,
luminance: _,
} => {
let [l, _, _, a] = transfer.to_optical_display(value);
let [x, y, z] = whitepoint.to_xyz();
[l * x, l * y, l * z, a]
}
#[cfg(any())]
Color::LumaDigital {
primary,
transfer,
whitepoint,
luminance: _,
} => {
let [l, _, _, a] = value;
let to_xyz = primary.to_xyz(*whitepoint);
let [r, g, b] = to_xyz.mul_vec([l, l, l]);
let [r, g, b, a] = transfer.to_optical_display([r, g, b, a]);
let [x, y, z] = to_xyz.mul_vec([r, g, b]);
[x, y, z, a]
}
Color::Yuv {
primary,
transfer,
whitepoint,
luminance: _,
differencing,
} => {
let mut yuv = value;
yuv::to_rgb_slice(core::slice::from_mut(&mut yuv), *transfer, *differencing);
let [r, g, b, a] = yuv;
let from_xyz = primary.to_xyz(*whitepoint);
let [x, y, z] = from_xyz.mul_vec([r, g, b]);
[x, y, z, a]
}
Color::Scalars { transfer } => transfer.to_optical_display(value),
Color::SrLab2 { whitepoint } => {
let [x, y, z, a] = value;
let [x, y, z] = srlab2::srlab_to_xyz([x, y, z], *whitepoint);
[x, y, z, a]
}
}
}
pub(crate) fn from_xyz_once(&self, value: [f32; 4]) -> [f32; 4] {
match self {
Color::Oklab => oklab::oklab_from_xyz(value),
Color::Rgb {
primary,
transfer,
whitepoint,
luminance: _,
} => {
let [x, y, z, a] = value;
let from_xyz = primary.to_xyz(*whitepoint).inv();
let [r, g, b] = from_xyz.mul_vec([x, y, z]);
transfer.from_optical_display([r, g, b, a])
}
Color::Luma {
transfer,
whitepoint: _,
luminance: _,
} => {
let [_, y, _, a] = value;
transfer.from_optical_display([y, 0.0, 0.0, a])
}
#[cfg(any())]
Color::LumaDigital {
primary,
transfer,
whitepoint,
luminance: _,
} => {
let [x, y, z, a] = value;
let from_xyz = primary.to_xyz(*whitepoint).inv();
let [r, g, b] = from_xyz.mul_vec([x, y, z]);
let [r, g, b, a] = transfer.from_optical_display([r, g, b, a]);
let [_, l, _] = primary.to_xyz(*whitepoint).mul_vec([r, g, b]);
[l, 0.0, 0.0, a]
}
Color::Yuv {
primary,
transfer,
whitepoint,
luminance: _,
differencing,
} => {
let [x, y, z, a] = value;
let from_xyz = primary.to_xyz(*whitepoint).inv();
let [r, g, b] = from_xyz.mul_vec([x, y, z]);
let mut rgb = [r, g, b, a];
yuv::from_rgb_slice(core::slice::from_mut(&mut rgb), *transfer, *differencing);
rgb
}
Color::Scalars { transfer } => transfer.from_optical_display(value),
Color::SrLab2 { whitepoint } => {
let [x, y, z, a] = value;
let [x, y, z] = srlab2::srlab_from_xyz([x, y, z], *whitepoint);
[x, y, z, a]
}
}
}
pub fn model(&self) -> Option<ColorChannelModel> {
Some(match self {
Color::Rgb { .. } => ColorChannelModel::Rgb,
Color::Luma { .. } => ColorChannelModel::Luma,
#[cfg(any())]
Color::LumaDigital { .. } => ColorChannelModel::Luma,
Color::Oklab | Color::SrLab2 { .. } => ColorChannelModel::Lab,
Color::Yuv { .. } => ColorChannelModel::Yuv,
Color::Scalars { .. } => return None,
})
}
}
impl ColorChannelModel {
pub const fn contains(self, channel: ColorChannel) -> bool {
channel.in_model(self)
}
const _STATIC_ASSERTIONS: () = {
fn _all_models(model: ColorChannelModel) {
use ColorChannelModel::*;
match model {
Rgb => {
const _: () = {
assert!(Rgb.contains(ColorChannel::R));
assert!(Rgb.contains(ColorChannel::G));
assert!(Rgb.contains(ColorChannel::B));
assert!(Rgb.contains(ColorChannel::Alpha));
};
}
Luma => {
const _: () = {
assert!(Luma.contains(ColorChannel::Luma));
assert!(Luma.contains(ColorChannel::Alpha));
};
}
Yuv => {
const _: () = {
assert!(Yuv.contains(ColorChannel::Luma));
assert!(Yuv.contains(ColorChannel::Cb));
assert!(Yuv.contains(ColorChannel::Cr));
assert!(Yuv.contains(ColorChannel::Alpha));
};
}
Lab => {
const _: () = {
assert!(Lab.contains(ColorChannel::L));
assert!(Lab.contains(ColorChannel::LABa));
assert!(Lab.contains(ColorChannel::LABb));
assert!(Lab.contains(ColorChannel::Alpha));
};
}
ColorChannelModel::Lch => {
const _: () = {
assert!(Lch.contains(ColorChannel::Alpha));
};
}
ColorChannelModel::Cmyk => {
const _: () = {
assert!(!Cmyk.contains(ColorChannel::Alpha));
};
}
ColorChannelModel::Hsv => {
const _: () = {
assert!(Lch.contains(ColorChannel::Alpha));
};
}
ColorChannelModel::Hsl => {
const _: () = {
assert!(Lch.contains(ColorChannel::Alpha));
};
}
}
}
};
}
impl Transfer {
pub(crate) fn to_optical_display(self, value: [f32; 4]) -> [f32; 4] {
use self::transfer::*;
let [r, g, b, a] = value;
let rgb = [r, g, b];
let [r, g, b] = match self {
Transfer::Bt709 => rgb.map(transfer_eo_bt709),
Transfer::Bt470M => rgb.map(transfer_eo_bt470m),
Transfer::Bt470 => rgb.map(transfer_eo_bt470),
Transfer::Bt601 => rgb.map(transfer_eo_bt601),
Transfer::Smpte240 => rgb.map(transfer_eo_smpte240),
Transfer::Linear => rgb,
Transfer::Srgb => rgb.map(transfer_eo_srgb),
Transfer::Bt2020_10bit => rgb.map(transfer_eo_bt2020_10b),
Transfer::Bt2020_12bit => {
todo!()
}
Transfer::Smpte2084 => rgb.map(transfer_eo_smpte2084),
Transfer::Bt2100Pq => {
todo!()
}
Transfer::Bt2100Hlg => {
todo!()
}
Transfer::Bt2100Scene => {
todo!()
}
};
[r, g, b, a]
}
pub(crate) fn from_optical_display(self, value: [f32; 4]) -> [f32; 4] {
use self::transfer::*;
let [r, g, b, a] = value;
let rgb = [r, g, b];
let [r, g, b] = match self {
Transfer::Bt709 => rgb.map(transfer_oe_bt709),
Transfer::Bt470M => rgb.map(transfer_oe_bt470m),
Transfer::Bt470 => rgb.map(transfer_oe_bt470),
Transfer::Bt601 => rgb.map(transfer_oe_bt601),
Transfer::Smpte240 => rgb.map(transfer_oe_smpte240),
Transfer::Linear => rgb,
Transfer::Srgb => rgb.map(transfer_oe_srgb),
Transfer::Bt2020_10bit => rgb.map(transfer_oe_bt2020_10b),
Transfer::Bt2020_12bit => {
todo!()
}
Transfer::Smpte2084 => rgb.map(transfer_oe_smpte2084),
Transfer::Bt2100Pq => {
todo!()
}
Transfer::Bt2100Hlg => {
todo!()
}
Transfer::Bt2100Scene => {
todo!()
}
};
[r, g, b, a]
}
pub(crate) fn to_optical_display_slice(self) -> Option<fn(&[[f32; 4]], &mut [[f32; 4]])> {
macro_rules! optical_by_display {
($what:ident: $($pattern:pat => $transfer:path,)*) => {
match $what {
$($pattern => return optical_by_display! {@ $transfer },)*
_ => return None,
}
};
(@ $transfer:path) => {
Some(|texels: &[[f32; 4]], pixels: &mut [[f32; 4]]| {
for (texel, target_pix) in texels.iter().zip(pixels) {
let [r, g, b, a] = *texel;
let [r, g, b] = [r, g, b].map($transfer);
*target_pix = [r, g, b, a];
}
})
};
}
if let Transfer::Linear = self {
return Some(|x, y| y.copy_from_slice(x));
}
use self::transfer::*;
optical_by_display!(self:
Transfer::Bt709 => transfer_eo_bt709,
Transfer::Bt470M => transfer_eo_bt470m,
Transfer::Bt470 => transfer_eo_bt470,
Transfer::Bt601 => transfer_eo_bt601,
Transfer::Smpte240 => transfer_eo_smpte240,
Transfer::Srgb => transfer_eo_srgb,
Transfer::Bt2020_10bit => transfer_eo_bt2020_10b,
);
}
pub(crate) fn to_optical_display_slice_inplace(self) -> Option<fn(&mut [[f32; 4]])> {
macro_rules! optical_by_display {
($what:ident: $($pattern:pat => $transfer:path,)*) => {
match $what {
$($pattern => return optical_by_display! {@ $transfer },)*
_ => return None,
}
};
(@ $transfer:path) => {
Some(|pixels: &mut [[f32; 4]]| {
for pix in pixels {
let [r, g, b, a] = *pix;
let [r, g, b] = [r, g, b].map($transfer);
*pix = [r, g, b, a];
}
})
};
}
if let Transfer::Linear = self {
return Some(|_| {});
}
use self::transfer::*;
optical_by_display!(self:
Transfer::Bt709 => transfer_eo_bt709,
Transfer::Bt470M => transfer_eo_bt470m,
Transfer::Bt470 => transfer_eo_bt470,
Transfer::Bt601 => transfer_eo_bt601,
Transfer::Smpte240 => transfer_eo_smpte240,
Transfer::Srgb => transfer_eo_srgb,
Transfer::Bt2020_10bit => transfer_eo_bt2020_10b,
);
}
pub(crate) fn from_optical_display_slice(self) -> Option<fn(&mut [[f32; 4]])> {
macro_rules! optical_by_display {
($what:ident: $($pattern:pat => $transfer:path,)*) => {
match $what {
$($pattern => return optical_by_display! {@ $transfer },)*
_ => return None,
}
};
(@ $transfer:path) => {
Some(|pixels: &mut [[f32; 4]]| {
for target_pix in pixels.iter_mut() {
let [r, g, b, a] = *target_pix;
let [r, g, b] = [r, g, b].map($transfer);
*target_pix = [r, g, b, a];
}
})
};
}
if let Transfer::Linear = self {
return Some(|_| {});
}
use self::transfer::*;
optical_by_display!(self:
Transfer::Bt709 => transfer_oe_bt709,
Transfer::Bt470M => transfer_oe_bt470m,
Transfer::Bt601 => transfer_oe_bt601,
Transfer::Smpte240 => transfer_oe_smpte240,
Transfer::Srgb => transfer_oe_srgb,
Transfer::Bt2020_10bit => transfer_oe_bt2020_10b,
);
}
}
impl Whitepoint {
pub fn to_xyz(self) -> [f32; 3] {
use Whitepoint::*;
match self {
A => [1.09850, 1.00000, 0.35585],
B => [0.99072, 1.00000, 0.85223],
C => [0.98074, 1.00000, 1.18232],
D50 => [0.96422, 1.00000, 0.82521],
D55 => [0.95682, 1.00000, 0.92149],
D65 => [0.95047, 1.00000, 1.08883],
D75 => [0.94972, 1.00000, 1.22638],
E => [1.00000, 1.00000, 1.00000],
F2 => [0.99186, 1.00000, 0.67393],
F7 => [0.95041, 1.00000, 1.08747],
F11 => [1.00962, 1.00000, 0.64350],
}
}
}
#[rustfmt::skip]
impl Primaries {
pub(crate) fn to_xyz(self, white: Whitepoint) -> RowMatrix {
use Primaries::*;
let xy: [[f32; 2]; 3] = match self {
Bt601_525 | Smpte240 => [[0.63, 0.34], [0.31, 0.595], [0.155, 0.07]],
Bt601_625 => [[0.64, 0.33], [0.29, 0.6], [0.15, 0.06]],
Bt709 => [[0.64, 0.33], [0.30, 0.60], [0.15, 0.06]],
Bt2020 | Bt2100 => [[0.708, 0.292], [0.170, 0.797], [0.131, 0.046]],
Xyz => todo!(),
};
let xyz = |[x, y]: [f32; 2]| {
[x / y, 1.0, (1.0 - x - y)/y]
};
let xyz_r = xyz(xy[0]);
let xyz_g = xyz(xy[1]);
let xyz_b = xyz(xy[2]);
let n1 = ColMatrix([xyz_r, xyz_g, xyz_b]).inv();
let w = white.to_xyz();
let s = n1.mul_vec(w);
RowMatrix([
s[0]*xyz_r[0], s[1]*xyz_g[0], s[2]*xyz_b[0],
s[0]*xyz_r[1], s[1]*xyz_g[1], s[2]*xyz_b[1],
s[0]*xyz_r[2], s[1]*xyz_g[2], s[2]*xyz_b[2],
])
}
pub fn to_xyz_row_matrix(self, white: Whitepoint) -> [f32; 9] {
self.to_xyz(white).into_inner()
}
pub fn from_xyz_row_matrix(self, white: Whitepoint) -> [f32; 9] {
self.to_xyz(white).inv().into_inner()
}
}
#[test]
fn inverse() {
const RGBA: [f32; 4] = [1.0, 1.0, 0.0, 1.0];
let color = Color::SRGB;
let _rgba = color.from_xyz_once(color.to_xyz_once(RGBA));
}
#[test]
fn xyz_once_equal_to_multiple() {
const POINTS: &[[f32; 4]] = &[
[0.0, 0.0, 0.0, 1.0],
[1.0, 0.0, 0.0, 1.0],
[0.0, 1.0, 0.0, 1.0],
[0.0, 0.0, 1.0, 1.0],
[0.5, 0.5, 0.5, 1.0],
[0.8, 0.5, 0.5, 1.0],
[0.5, 0.8, 0.5, 1.0],
[0.5, 0.5, 0.8, 1.0],
[0.8, 0.5, 0.8, 1.0],
[0.8, 0.8, 0.5, 1.0],
[0.5, 0.8, 0.8, 1.0],
[0.8, 0.5, 0.8, 0.4],
[0.8, 0.8, 0.5, 0.4],
[0.5, 0.8, 0.8, 0.4],
];
const COLORS: &[Color] = &[
Color::SRGB,
Color::RGB_ITU_BT2020,
Color::RGB_ITU_BT470_525,
Color::RGB_ITU_BT470_625,
Color::BT709_RGB,
Color::BT709,
];
let mut xyz_buffer = [[0.0; 4]; 1];
let mut pbuf = [0.0; 4];
for color in COLORS {
for &point in POINTS {
let xyz = color.to_xyz_once(point);
color.to_xyz_slice(core::slice::from_ref(&point), &mut xyz_buffer);
assert_eq!(
xyz_buffer[0], xyz,
"Color {:?} failed for point {:?}",
color, point
);
color.from_xyz_slice(&xyz_buffer, core::slice::from_mut(&mut pbuf));
let backpoint = color.from_xyz_once(xyz);
assert_eq!(
backpoint, pbuf,
"Color {:?} failed for point {:?} XYZ {xyz:?}",
color, point
);
}
}
}
#[test]
fn xyz_test_vectors() {
struct TestCase {
color: Color,
points: &'static [([f32; 3], [f32; 3])],
name: &'static str,
}
fn somewhat_eq(a: [f32; 3], b: [f32; 3]) -> bool {
a.iter().zip(b).all(|(x, y)| (x - y).abs() < 0.001)
}
fn test(case: TestCase) {
let check_color_pair = |rgb: [f32; 3], result: [f32; 3]| {
let [r, g, b] = rgb;
let [x, y, z, _] = case.color.to_xyz_once([r, g, b, 1.0]);
assert!(
somewhat_eq([x, y, z], result),
"{:?} - {:?} at ({})",
[x, y, z],
result,
case.name
);
};
for (lhs, rhs) in case.points {
check_color_pair(*lhs, *rhs);
}
}
let cases = [
TestCase {
color: Color::SRGB,
points: &[
([0.9, 0.9, 0.9], [0.74843538, 0.78741229, 0.85749198]),
],
name: "sRGB",
},
];
cases.into_iter().for_each(test);
}