#[allow(deprecated)] use crate::colour::legacy::{
delinearize_srgb, encode_bt709, linearize_bt709, linearize_srgb_u16, linearize_srgb_u8,
};
use crate::colour::rec2020::REC2020_2VSF_RGB;
use crate::colour::{LMS2PHOTOPIC, VSF_RGB2LMS};
use crate::types::VsfType;
#[cfg(feature = "spirix")]
use spirix::ScalarF4E4 as S44;
#[cfg(feature = "spirix")]
use spirix::sf;
pub trait ColourValue<Linear: Copy>: Copy {
fn to_linear_srgb(self) -> Linear;
fn from_linear_srgb(linear: Linear) -> Self;
fn to_linear_rec709(self) -> Linear;
fn from_linear_rec709(linear: Linear) -> Self;
fn to_linear_rec2020(self) -> Linear {
self.to_linear_rec709()
}
fn from_linear_rec2020(linear: Linear) -> Self {
Self::from_linear_rec709(linear)
}
fn to_linear_adobe_rgb(self) -> Linear;
fn from_linear_adobe_rgb(linear: Linear) -> Self;
fn to_linear_gamma2(self) -> Linear;
fn from_linear_gamma2(linear: Linear) -> Self;
}
impl ColourValue<f32> for f32 {
#[inline]
fn to_linear_srgb(self) -> f32 {
self
}
#[inline]
fn from_linear_srgb(linear: f32) -> Self {
linear
}
#[inline]
fn to_linear_rec709(self) -> f32 {
self
}
#[inline]
fn from_linear_rec709(linear: f32) -> Self {
linear
}
#[inline]
fn to_linear_adobe_rgb(self) -> f32 {
self
}
#[inline]
fn from_linear_adobe_rgb(linear: f32) -> Self {
linear
}
#[inline]
fn to_linear_gamma2(self) -> f32 {
self
}
#[inline]
fn from_linear_gamma2(linear: f32) -> Self {
linear
}
}
#[cfg(feature = "spirix")]
impl ColourValue<S44> for S44 {
#[inline]
fn to_linear_srgb(self) -> S44 {
self
}
#[inline]
fn from_linear_srgb(linear: S44) -> Self {
linear
}
#[inline]
fn to_linear_rec709(self) -> S44 {
self
}
#[inline]
fn from_linear_rec709(linear: S44) -> Self {
linear
}
#[inline]
fn to_linear_adobe_rgb(self) -> S44 {
self
}
#[inline]
fn from_linear_adobe_rgb(linear: S44) -> Self {
linear
}
#[inline]
fn to_linear_gamma2(self) -> S44 {
self
}
#[inline]
fn from_linear_gamma2(linear: S44) -> Self {
linear
}
}
impl ColourValue<f32> for u8 {
#[inline]
fn to_linear_srgb(self) -> f32 {
linearize_srgb_u8(self)
}
#[inline]
fn from_linear_srgb(linear: f32) -> Self {
(delinearize_srgb(linear) * 255.).round() as u8
}
#[inline]
fn to_linear_rec709(self) -> f32 {
let normalized = (self as f32 - 16.) / 219.; linearize_bt709(normalized.clamp(0., 1.))
}
#[inline]
fn from_linear_rec709(linear: f32) -> Self {
((encode_bt709(linear) * 219.).round() as u8).min(219) + 16
}
#[inline]
fn to_linear_adobe_rgb(self) -> f32 {
use crate::colour::legacy::transfer::adobe_rgb_eotf;
let normalized = self as f32 / 255.;
adobe_rgb_eotf(normalized)
}
#[inline]
fn from_linear_adobe_rgb(linear: f32) -> Self {
use crate::colour::legacy::transfer::adobe_rgb_oetf;
(adobe_rgb_oetf(linear) * 255.).round() as u8
}
#[inline]
fn to_linear_gamma2(self) -> f32 {
linearize_gamma2_u8_f32(self)
}
#[inline]
fn from_linear_gamma2(linear: f32) -> Self {
delinearize_gamma2_u8_f32(linear)
}
}
#[cfg(feature = "spirix")]
mod colour_tf_consts {
use spirix::ScalarF4E4 as S44;
use spirix::sf;
pub const SRGB_EOTF_THRESH: S44 = sf!(0.04045);
pub const SRGB_OETF_THRESH: S44 = sf!(0.0031308);
pub const SRGB_LINEAR_SLOPE: S44 = sf!(12.92);
pub const SRGB_GAMMA: S44 = sf!(2.4);
pub const SRGB_INV_GAMMA: S44 = sf!(1.0 / 2.4);
pub const SRGB_A: S44 = sf!(1.055);
pub const SRGB_B: S44 = sf!(0.055);
pub const BT709_EOTF_THRESH: S44 = sf!(0.081);
pub const BT709_OETF_THRESH: S44 = sf!(0.018);
pub const BT709_LINEAR_SLOPE: S44 = sf!(4.5);
pub const BT709_GAMMA: S44 = sf!(0.45);
pub const BT709_INV_GAMMA: S44 = sf!(1.0 / 0.45);
pub const BT709_A: S44 = sf!(1.099);
pub const BT709_B: S44 = sf!(0.099);
pub const ADOBE_GAMMA: S44 = sf!(2.2);
pub const ADOBE_INV_GAMMA: S44 = sf!(1.0 / 2.2);
}
#[cfg(feature = "spirix")]
impl ColourValue<S44> for u8 {
#[inline]
fn to_linear_srgb(self) -> S44 {
use colour_tf_consts::*;
let normalized = S44::from(self) / 255i16;
if normalized <= SRGB_EOTF_THRESH {
normalized / SRGB_LINEAR_SLOPE
} else {
((normalized + SRGB_B) / SRGB_A).pow(SRGB_GAMMA)
}
}
#[inline]
fn from_linear_srgb(linear: S44) -> Self {
use colour_tf_consts::*;
let encoded: S44 = if linear <= SRGB_OETF_THRESH {
linear * SRGB_LINEAR_SLOPE
} else {
linear.pow(SRGB_INV_GAMMA) * SRGB_A - SRGB_B
};
(encoded * 255i16).round().to_u8()
}
#[inline]
fn to_linear_rec709(self) -> S44 {
use colour_tf_consts::*;
let normalized = (S44::from(self) - 16i16) / 219i16; let normalized = normalized.clamp(0i16, 1i16);
if normalized < BT709_EOTF_THRESH {
normalized / BT709_LINEAR_SLOPE
} else {
((normalized + BT709_B) / BT709_A).pow(BT709_INV_GAMMA)
}
}
#[inline]
fn from_linear_rec709(linear: S44) -> Self {
use colour_tf_consts::*;
let encoded: S44 = if linear < BT709_OETF_THRESH {
linear * BT709_LINEAR_SLOPE
} else {
linear.pow(BT709_GAMMA) * BT709_A - BT709_B
};
(encoded * 219i16).round().to_u8() + 16
}
#[inline]
fn to_linear_adobe_rgb(self) -> S44 {
use colour_tf_consts::*;
let normalized = S44::from(self) / 255i16;
normalized.pow(ADOBE_GAMMA)
}
#[inline]
fn from_linear_adobe_rgb(linear: S44) -> Self {
use colour_tf_consts::*;
let encoded = linear.pow(ADOBE_INV_GAMMA);
(encoded).round().to_u8()
}
#[inline]
fn to_linear_gamma2(self) -> S44 {
let normalized = S44::from(self) >> 8isize;
normalized.square()
}
#[inline]
fn from_linear_gamma2(linear: S44) -> Self {
let encoded = linear.sqrt();
(encoded << 8isize).to_u8()
}
}
impl ColourValue<f32> for u16 {
#[inline]
fn to_linear_srgb(self) -> f32 {
linearize_srgb_u16(self)
}
#[inline]
fn from_linear_srgb(linear: f32) -> Self {
(delinearize_srgb(linear) * 65535.).round() as u16
}
#[inline]
fn to_linear_rec709(self) -> f32 {
let normalized = (self as f32 - 4096.) / 56064.; linearize_bt709(normalized.clamp(0., 1.))
}
#[inline]
fn from_linear_rec709(linear: f32) -> Self {
((encode_bt709(linear) * 56064.).round() as u16).min(56064) + 4096
}
#[inline]
fn to_linear_adobe_rgb(self) -> f32 {
use crate::colour::legacy::transfer::adobe_rgb_eotf;
let normalized = self as f32 / 65535.;
adobe_rgb_eotf(normalized)
}
#[inline]
fn from_linear_adobe_rgb(linear: f32) -> Self {
use crate::colour::legacy::transfer::adobe_rgb_oetf;
(adobe_rgb_oetf(linear) * 65535.).round() as u16
}
#[inline]
fn to_linear_gamma2(self) -> f32 {
linearize_gamma2_u16_f32(self)
}
#[inline]
fn from_linear_gamma2(linear: f32) -> Self {
delinearize_gamma2_u16_f32(linear)
}
}
#[cfg(feature = "spirix")]
impl ColourValue<S44> for u16 {
#[inline]
fn to_linear_srgb(self) -> S44 {
use colour_tf_consts::*;
let normalized = S44::from(self) / 65535i32;
if normalized <= SRGB_EOTF_THRESH {
normalized / SRGB_LINEAR_SLOPE
} else {
((normalized + SRGB_B) / SRGB_A).pow(SRGB_GAMMA)
}
}
#[inline]
fn from_linear_srgb(linear: S44) -> Self {
use colour_tf_consts::*;
let encoded: S44 = if linear <= SRGB_OETF_THRESH {
linear * SRGB_LINEAR_SLOPE
} else {
linear.pow(SRGB_INV_GAMMA) * SRGB_A - SRGB_B
};
(encoded * 65535i32).round().to_u16()
}
#[inline]
fn to_linear_rec709(self) -> S44 {
use colour_tf_consts::*;
let normalized = (S44::from(self) - 4096i32) / 56064i32; let normalized = normalized.clamp(0i16, 1i16);
if normalized < BT709_EOTF_THRESH {
normalized / BT709_LINEAR_SLOPE
} else {
((normalized + BT709_B) / BT709_A).pow(BT709_INV_GAMMA)
}
}
#[inline]
fn from_linear_rec709(linear: S44) -> Self {
use colour_tf_consts::*;
let encoded: S44 = if linear < BT709_OETF_THRESH {
linear * BT709_LINEAR_SLOPE
} else {
linear.pow(BT709_GAMMA) * BT709_A - BT709_B
};
(encoded * 56064i32).round().to_u16() + 4096
}
#[inline]
fn to_linear_adobe_rgb(self) -> S44 {
use colour_tf_consts::*;
let normalized = S44::from(self) / 65535i32;
normalized.pow(ADOBE_GAMMA)
}
#[inline]
fn from_linear_adobe_rgb(linear: S44) -> Self {
use colour_tf_consts::*;
let encoded = linear.pow(ADOBE_INV_GAMMA);
(encoded * 65535i32).round().to_u16()
}
#[inline]
fn to_linear_gamma2(self) -> S44 {
let normalized = S44::from(self) >> 16isize;
normalized.square()
}
#[inline]
fn from_linear_gamma2(linear: S44) -> Self {
let encoded = linear.sqrt();
(encoded << 16isize).to_u16()
}
}
pub fn invert_matrix_3x3_f32(m: &[f32; 9]) -> [f32; 9] {
let d = 1.
/ (m[0] * (m[4] * m[8] - m[5] * m[7]) - m[1] * (m[3] * m[8] - m[5] * m[6])
+ m[2] * (m[3] * m[7] - m[4] * m[6]));
[
(m[4] * m[8] - m[5] * m[7]) * d,
(m[2] * m[7] - m[1] * m[8]) * d,
(m[1] * m[5] - m[2] * m[4]) * d,
(m[5] * m[6] - m[3] * m[8]) * d,
(m[0] * m[8] - m[2] * m[6]) * d,
(m[2] * m[3] - m[0] * m[5]) * d,
(m[3] * m[7] - m[4] * m[6]) * d,
(m[1] * m[6] - m[0] * m[7]) * d,
(m[0] * m[4] - m[1] * m[3]) * d,
]
}
#[cfg(feature = "spirix")]
pub fn invert_matrix_3x3_s44(m: &[S44; 9]) -> [S44; 9] {
let d = (m[0] * (m[4] * m[8] - m[5] * m[7]) - m[1] * (m[3] * m[8] - m[5] * m[6])
+ m[2] * (m[3] * m[7] - m[4] * m[6]))
.reciprocal();
[
(m[4] * m[8] - m[5] * m[7]) * d,
(m[2] * m[7] - m[1] * m[8]) * d,
(m[1] * m[5] - m[2] * m[4]) * d,
(m[5] * m[6] - m[3] * m[8]) * d,
(m[0] * m[8] - m[2] * m[6]) * d,
(m[2] * m[3] - m[0] * m[5]) * d,
(m[3] * m[7] - m[4] * m[6]) * d,
(m[1] * m[6] - m[0] * m[7]) * d,
(m[0] * m[4] - m[1] * m[3]) * d,
]
}
pub fn apply_matrix_3x3_f32(cmx: &[f32], colour: &[f32; 3]) -> [f32; 3] {
[
cmx[0] * colour[0] + cmx[3] * colour[1] + cmx[6] * colour[2], cmx[1] * colour[0] + cmx[4] * colour[1] + cmx[7] * colour[2], cmx[2] * colour[0] + cmx[5] * colour[1] + cmx[8] * colour[2], ]
}
#[cfg(feature = "spirix")]
pub fn apply_matrix_3x3_s44(cmx: &[S44], colour: &[S44; 3]) -> [S44; 3] {
[
cmx[0] * colour[0] + cmx[3] * colour[1] + cmx[6] * colour[2], cmx[1] * colour[0] + cmx[4] * colour[1] + cmx[7] * colour[2], cmx[2] * colour[0] + cmx[5] * colour[1] + cmx[8] * colour[2], ]
}
pub fn convert_matrix_3x3_f32(a: &[f32], b: &[f32]) -> [f32; 9] {
[
a[0] * b[0] + a[3] * b[1] + a[6] * b[2], a[1] * b[0] + a[4] * b[1] + a[7] * b[2], a[2] * b[0] + a[5] * b[1] + a[8] * b[2], a[0] * b[3] + a[3] * b[4] + a[6] * b[5], a[1] * b[3] + a[4] * b[4] + a[7] * b[5], a[2] * b[3] + a[5] * b[4] + a[8] * b[5], a[0] * b[6] + a[3] * b[7] + a[6] * b[8], a[1] * b[6] + a[4] * b[7] + a[7] * b[8], a[2] * b[6] + a[5] * b[7] + a[8] * b[8], ]
}
#[cfg(feature = "spirix")]
pub fn convert_matrix_3x3_s44(a: &[S44], b: &[S44]) -> [S44; 9] {
[
a[0] * b[0] + a[3] * b[1] + a[6] * b[2], a[1] * b[0] + a[4] * b[1] + a[7] * b[2], a[2] * b[0] + a[5] * b[1] + a[8] * b[2], a[0] * b[3] + a[3] * b[4] + a[6] * b[5], a[1] * b[3] + a[4] * b[4] + a[7] * b[5], a[2] * b[3] + a[5] * b[4] + a[8] * b[5], a[0] * b[6] + a[3] * b[7] + a[6] * b[8], a[1] * b[6] + a[4] * b[7] + a[7] * b[8], a[2] * b[6] + a[5] * b[7] + a[8] * b[8], ]
}
#[inline]
pub fn scale_to_gamut_f32(mut r: f32, mut g: f32, mut b: f32) -> (f32, f32, f32) {
let max = r.max(g).max(b);
if max > 1. {
let scale = 1. / max;
r *= scale;
g *= scale;
b *= scale;
}
(r, g, b)
}
#[cfg(feature = "spirix")]
#[inline]
pub fn scale_to_gamut_s44(mut r: S44, mut g: S44, mut b: S44) -> (S44, S44, S44) {
let max = r.max(g).max(b);
if max > 1 {
let scale = max.reciprocal();
r *= scale;
g *= scale;
b *= scale;
}
(r, g, b)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RgbLinearF32 {
pub r: f32,
pub g: f32,
pub b: f32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RgbaLinearF32 {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
#[cfg(feature = "spirix")]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RgbLinearS44 {
pub r: S44,
pub g: S44,
pub b: S44,
}
#[cfg(feature = "spirix")]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RgbaLinearS44 {
pub r: S44,
pub g: S44,
pub b: S44,
pub a: S44,
}
impl VsfType {
pub fn to_rgb_linear_f32(&self) -> Option<RgbLinearF32> {
match self {
VsfType::rck => Some(RgbLinearF32 {
r: 0.,
g: 0.,
b: 0.,
}), VsfType::rcw => Some(RgbLinearF32 {
r: 1.,
g: 1.,
b: 1.,
}), VsfType::rcg => Some(RgbLinearF32 {
r: 0.25,
g: 0.25,
b: 0.25,
}), VsfType::rcr => Some(RgbLinearF32 {
r: 1.,
g: 0.,
b: 0.,
}), VsfType::rcn => Some(RgbLinearF32 {
r: 0.,
g: 1.,
b: 0.,
}), VsfType::rcb => Some(RgbLinearF32 {
r: 0.,
g: 0.,
b: 1.,
}), VsfType::rcc => Some(RgbLinearF32 {
r: 0.,
g: 1.,
b: 1.,
}), VsfType::rcj => Some(RgbLinearF32 {
r: 1.,
g: 0.,
b: 1.,
}), VsfType::rcy => Some(RgbLinearF32 {
r: 1.,
g: 1.,
b: 0.,
}), VsfType::rco => Some(RgbLinearF32 {
r: 1.,
g: 0.5,
b: 0.,
}), VsfType::rcl => Some(RgbLinearF32 {
r: 0.5,
g: 1.,
b: 0.,
}), VsfType::rcq => Some(RgbLinearF32 {
r: 0.,
g: 1.,
b: 0.5,
}), VsfType::rcv => Some(RgbLinearF32 {
r: 0.25,
g: 0.,
b: 1.,
}),
VsfType::re(grey) => {
let lin = linearize_gamma2_u8_f32(*grey);
Some(RgbLinearF32 {
r: lin,
g: lin,
b: lin,
})
}
VsfType::rx(grey) => {
let lin = linearize_gamma2_u16_f32(*grey);
Some(RgbLinearF32 {
r: lin,
g: lin,
b: lin,
})
}
VsfType::rz(grey) => {
Some(RgbLinearF32 {
r: *grey,
g: *grey,
b: *grey,
})
}
VsfType::ri(packed) => {
let (r, g, b) = unpack_rgb_676_linear_f32(*packed);
Some(RgbLinearF32 { r, g, b })
}
VsfType::rp(packed) => {
let (r, g, b) = unpack_rgb_565_linear_f32(*packed);
Some(RgbLinearF32 { r, g, b })
}
VsfType::ru([r, g, b]) => Some(RgbLinearF32 {
r: linearize_gamma2_u8_f32(*r),
g: linearize_gamma2_u8_f32(*g),
b: linearize_gamma2_u8_f32(*b),
}),
VsfType::rs([r, g, b]) => Some(RgbLinearF32 {
r: linearize_gamma2_u16_f32(*r),
g: linearize_gamma2_u16_f32(*g),
b: linearize_gamma2_u16_f32(*b),
}),
VsfType::rf([r, g, b]) => Some(RgbLinearF32 {
r: *r,
g: *g,
b: *b,
}),
VsfType::ra([r, g, b, _]) => Some(RgbLinearF32 {
r: linearize_gamma2_u8_f32(*r),
g: linearize_gamma2_u8_f32(*g),
b: linearize_gamma2_u8_f32(*b),
}),
VsfType::rt([r, g, b, _]) => Some(RgbLinearF32 {
r: linearize_gamma2_u16_f32(*r),
g: linearize_gamma2_u16_f32(*g),
b: linearize_gamma2_u16_f32(*b),
}),
VsfType::rh([r, g, b, _]) => Some(RgbLinearF32 {
r: *r,
g: *g,
b: *b,
}),
_ => None,
}
}
pub fn to_rgba_linear_f32(&self) -> Option<RgbaLinearF32> {
match self {
VsfType::ra([r, g, b, a]) => Some(RgbaLinearF32 {
r: linearize_gamma2_u8_f32(*r),
g: linearize_gamma2_u8_f32(*g),
b: linearize_gamma2_u8_f32(*b),
a: *a as f32 / 255.0, }),
VsfType::rt([r, g, b, a]) => Some(RgbaLinearF32 {
r: linearize_gamma2_u16_f32(*r),
g: linearize_gamma2_u16_f32(*g),
b: linearize_gamma2_u16_f32(*b),
a: *a as f32 / 65535.0, }),
VsfType::rh([r, g, b, a]) => Some(RgbaLinearF32 {
r: *r,
g: *g,
b: *b,
a: *a,
}),
_ => self.to_rgb_linear_f32().map(|rgb| RgbaLinearF32 {
r: rgb.r,
g: rgb.g,
b: rgb.b,
a: 1., }),
}
}
#[cfg(feature = "spirix")]
pub fn to_rgb_linear_s44(&self) -> Option<RgbLinearS44> {
const QUARTER: S44 = sf!(0.25);
const HALF: S44 = sf!(0.5);
match self {
VsfType::rck => Some(RgbLinearS44 {
r: S44::ZERO,
g: S44::ZERO,
b: S44::ZERO,
}),
VsfType::rcw => Some(RgbLinearS44 {
r: S44::ONE,
g: S44::ONE,
b: S44::ONE,
}),
VsfType::rcg => Some(RgbLinearS44 {
r: QUARTER,
g: QUARTER,
b: QUARTER,
}),
VsfType::rcr => Some(RgbLinearS44 {
r: S44::ONE,
g: S44::ZERO,
b: S44::ZERO,
}),
VsfType::rcn => Some(RgbLinearS44 {
r: S44::ZERO,
g: S44::ONE,
b: S44::ZERO,
}),
VsfType::rcb => Some(RgbLinearS44 {
r: S44::ZERO,
g: S44::ZERO,
b: S44::ONE,
}),
VsfType::rcc => Some(RgbLinearS44 {
r: S44::ZERO,
g: S44::ONE,
b: S44::ONE,
}),
VsfType::rcj => Some(RgbLinearS44 {
r: S44::ONE,
g: S44::ZERO,
b: S44::ONE,
}),
VsfType::rcy => Some(RgbLinearS44 {
r: S44::ONE,
g: S44::ONE,
b: S44::ZERO,
}),
VsfType::rco => Some(RgbLinearS44 {
r: S44::ONE,
g: HALF,
b: S44::ZERO,
}),
VsfType::rcl => Some(RgbLinearS44 {
r: HALF,
g: S44::ONE,
b: S44::ZERO,
}),
VsfType::rcq => Some(RgbLinearS44 {
r: S44::ZERO,
g: S44::ONE,
b: HALF,
}),
VsfType::rcv => Some(RgbLinearS44 {
r: QUARTER,
g: S44::ZERO,
b: S44::ONE,
}),
VsfType::re(grey) => {
let lin = linearize_gamma2_u8_s44(*grey);
Some(RgbLinearS44 {
r: lin,
g: lin,
b: lin,
})
}
VsfType::rx(grey) => {
let lin = linearize_gamma2_u16_s44(*grey);
Some(RgbLinearS44 {
r: lin,
g: lin,
b: lin,
})
}
VsfType::rz(grey) => Some(RgbLinearS44 {
r: S44::from_f32(*grey),
g: S44::from_f32(*grey),
b: S44::from_f32(*grey),
}),
VsfType::ri(packed) => {
let (r, g, b) = unpack_rgb_676_linear_s44(*packed);
Some(RgbLinearS44 { r, g, b })
}
VsfType::rp(packed) => {
let (r, g, b) = unpack_rgb_565_linear_s44(*packed);
Some(RgbLinearS44 { r, g, b })
}
VsfType::ru([r, g, b]) => Some(RgbLinearS44 {
r: linearize_gamma2_u8_s44(*r),
g: linearize_gamma2_u8_s44(*g),
b: linearize_gamma2_u8_s44(*b),
}),
VsfType::rs([r, g, b]) => Some(RgbLinearS44 {
r: linearize_gamma2_u16_s44(*r),
g: linearize_gamma2_u16_s44(*g),
b: linearize_gamma2_u16_s44(*b),
}),
VsfType::rf([r, g, b]) => Some(RgbLinearS44 {
r: S44::from_f32(*r),
g: S44::from_f32(*g),
b: S44::from_f32(*b),
}),
VsfType::ra([r, g, b, _]) => Some(RgbLinearS44 {
r: linearize_gamma2_u8_s44(*r),
g: linearize_gamma2_u8_s44(*g),
b: linearize_gamma2_u8_s44(*b),
}),
VsfType::rt([r, g, b, _]) => Some(RgbLinearS44 {
r: linearize_gamma2_u16_s44(*r),
g: linearize_gamma2_u16_s44(*g),
b: linearize_gamma2_u16_s44(*b),
}),
VsfType::rh([r, g, b, _]) => Some(RgbLinearS44 {
r: S44::from_f32(*r),
g: S44::from_f32(*g),
b: S44::from_f32(*b),
}),
_ => None,
}
}
#[cfg(feature = "spirix")]
pub fn to_rgba_linear_s44(&self) -> Option<RgbaLinearS44> {
match self {
VsfType::ra([r, g, b, a]) => Some(RgbaLinearS44 {
r: linearize_gamma2_u8_s44(*r),
g: linearize_gamma2_u8_s44(*g),
b: linearize_gamma2_u8_s44(*b),
a: S44::from(a) >> 8, }),
VsfType::rt([r, g, b, a]) => Some(RgbaLinearS44 {
r: linearize_gamma2_u16_s44(*r),
g: linearize_gamma2_u16_s44(*g),
b: linearize_gamma2_u16_s44(*b),
a: S44::from(a) >> 16, }),
VsfType::rh([r, g, b, a]) => Some(RgbaLinearS44 {
r: S44::from_f32(*r),
g: S44::from_f32(*g),
b: S44::from_f32(*b),
a: S44::from_f32(*a),
}),
_ => self.to_rgb_linear_s44().map(|rgb| RgbaLinearS44 {
r: rgb.r,
g: rgb.g,
b: rgb.b,
a: S44::ONE,
}),
}
}
pub fn to_grey8_f32(&self) -> Option<u8> {
match self {
VsfType::re(grey) => Some(*grey),
VsfType::rx(grey) => Some((*grey >> 8) as u8),
VsfType::rz(grey) => Some(delinearize_gamma2_u8_f32(*grey)),
_ => self.to_rgb_linear_f32().map(|rgb| {
let lum = vsf_rgb_to_photopic_f32(rgb.r, rgb.g, rgb.b);
delinearize_gamma2_u8_f32(lum)
}),
}
}
#[cfg(feature = "spirix")]
pub fn to_grey8_s44(&self) -> Option<u8> {
match self {
VsfType::re(grey) => Some(*grey),
VsfType::rx(grey) => Some((*grey >> 8) as u8),
VsfType::rz(grey) => Some(delinearize_gamma2_u8_s44(S44::from_f32(*grey))),
_ => self.to_rgb_linear_s44().map(|rgb| {
let lum = vsf_rgb_to_photopic_s44(rgb.r, rgb.g, rgb.b);
delinearize_gamma2_u8_s44(lum)
}),
}
}
pub fn to_rgb_u8_f32(&self) -> Option<(u8, u8, u8)> {
let rgb = self.to_rgb_linear_f32()?;
let (r, g, b) = scale_to_gamut_f32(rgb.r, rgb.g, rgb.b);
Some((
delinearize_gamma2_u8_f32(r),
delinearize_gamma2_u8_f32(g),
delinearize_gamma2_u8_f32(b),
))
}
#[cfg(feature = "spirix")]
pub fn to_rgb_u8_s44(&self) -> Option<(u8, u8, u8)> {
let rgb = self.to_rgb_linear_s44()?;
let (r, g, b) = scale_to_gamut_s44(rgb.r, rgb.g, rgb.b);
Some((
delinearize_gamma2_u8_s44(r),
delinearize_gamma2_u8_s44(g),
delinearize_gamma2_u8_s44(b),
))
}
pub fn to_lms_linear_f32(&self) -> Option<(f32, f32, f32)> {
let rgb = self.to_rgb_linear_f32()?;
use crate::colour::VSF_RGB2LMS;
let result = apply_matrix_3x3_f32(&VSF_RGB2LMS, &[rgb.r, rgb.g, rgb.b]);
Some((result[0], result[1], result[2]))
}
pub fn to_xyz_linear_f32(&self) -> Option<(f32, f32, f32)> {
let rgb = self.to_rgb_linear_f32()?;
use crate::colour::VSF_RGB2XYZ;
let result = apply_matrix_3x3_f32(&VSF_RGB2XYZ, &[rgb.r, rgb.g, rgb.b]);
Some((result[0], result[1], result[2]))
}
#[cfg(feature = "spirix")]
pub fn to_lms_linear_s44(&self) -> Option<(S44, S44, S44)> {
let rgb = self.to_rgb_linear_s44()?;
use crate::colour::VSF_RGB2LMS_S44;
let result = apply_matrix_3x3_s44(&VSF_RGB2LMS_S44, &[rgb.r, rgb.g, rgb.b]);
Some((result[0], result[1], result[2]))
}
#[cfg(feature = "spirix")]
pub fn to_xyz_linear_s44(&self) -> Option<(S44, S44, S44)> {
let rgb = self.to_rgb_linear_s44()?;
use crate::colour::VSF_RGB2XYZ_S44;
let result = apply_matrix_3x3_s44(&VSF_RGB2XYZ_S44, &[rgb.r, rgb.g, rgb.b]);
Some((result[0], result[1], result[2]))
}
pub fn to_srgb_linear_f32(&self) -> Option<(f32, f32, f32)> {
let rgb = self.to_rgb_linear_f32()?;
use crate::colour::VSF_RGB2SRGB;
let result = apply_matrix_3x3_f32(&VSF_RGB2SRGB, &[rgb.r, rgb.g, rgb.b]);
Some((result[0], result[1], result[2]))
}
pub fn to_rec2020_linear_f32(&self) -> Option<(f32, f32, f32)> {
let rgb = self.to_rgb_linear_f32()?;
use crate::colour::VSF_RGB2REC2020;
let result = apply_matrix_3x3_f32(&VSF_RGB2REC2020, &[rgb.r, rgb.g, rgb.b]);
Some((result[0], result[1], result[2]))
}
#[cfg(feature = "spirix")]
pub fn to_srgb_linear_s44(&self) -> Option<(S44, S44, S44)> {
let rgb = self.to_rgb_linear_s44()?;
use crate::colour::VSF_RGB2SRGB_S44;
let result = apply_matrix_3x3_s44(&VSF_RGB2SRGB_S44, &[rgb.r, rgb.g, rgb.b]);
Some((result[0], result[1], result[2]))
}
#[cfg(feature = "spirix")]
pub fn to_rec2020_linear_s44(&self) -> Option<(S44, S44, S44)> {
let rgb = self.to_rgb_linear_s44()?;
use crate::colour::VSF_RGB2REC2020_S44;
let result = apply_matrix_3x3_s44(&VSF_RGB2REC2020_S44, &[rgb.r, rgb.g, rgb.b]);
Some((result[0], result[1], result[2]))
}
#[cfg(feature = "spirix")]
pub fn to_adobe_rgb_linear_s44(&self) -> Option<(S44, S44, S44)> {
let rgb = self.to_rgb_linear_s44()?;
use crate::colour::VSF_RGB2ADOBE_RGB_S44;
let result = apply_matrix_3x3_s44(&VSF_RGB2ADOBE_RGB_S44, &[rgb.r, rgb.g, rgb.b]);
Some((result[0], result[1], result[2]))
}
pub fn to_srgb_u8_f32(&self) -> Option<(u8, u8, u8)> {
use crate::colour::legacy::transfer::srgb_oetf;
let (r, g, b) = self.to_srgb_linear_f32()?;
let (r, g, b) = scale_to_gamut_f32(r, g, b);
Some((
(srgb_oetf(r) * 255.).round() as u8,
(srgb_oetf(g) * 255.).round() as u8,
(srgb_oetf(b) * 255.).round() as u8,
))
}
#[cfg(feature = "spirix")]
pub fn to_srgb_u8_s44(&self) -> Option<(u8, u8, u8)> {
let (r, g, b) = self.to_srgb_linear_s44()?;
let (r, g, b) = scale_to_gamut_s44(r, g, b);
Some((
(srgb_oetf_s44(r) * 255i16).round().to_u8(),
(srgb_oetf_s44(g) * 255i16).round().to_u8(),
(srgb_oetf_s44(b) * 255i16).round().to_u8(),
))
}
pub fn to_srgb_u16_f32(&self) -> Option<(u16, u16, u16)> {
let (r, g, b) = self.to_srgb_linear_f32()?;
let (r, g, b) = scale_to_gamut_f32(r, g, b);
Some((
(delinearize_srgb(r) * 65535.).round() as u16,
(delinearize_srgb(g) * 65535.).round() as u16,
(delinearize_srgb(b) * 65535.).round() as u16,
))
}
#[cfg(feature = "spirix")]
pub fn to_srgb_u16_s44(&self) -> Option<(u16, u16, u16)> {
let (r, g, b) = self.to_srgb_linear_s44()?;
let (r, g, b) = scale_to_gamut_s44(r, g, b);
Some((
(srgb_oetf_s44(r) * 65535i32).round().to_u16(),
(srgb_oetf_s44(g) * 65535i32).round().to_u16(),
(srgb_oetf_s44(b) * 65535i32).round().to_u16(),
))
}
pub fn to_rec709_u8_f32(&self) -> Option<(u8, u8, u8)> {
let (r, g, b) = self.to_srgb_linear_f32()?;
let (r, g, b) = scale_to_gamut_f32(r, g, b);
Some((
(encode_bt709(r) * 219.) as u8 + 16,
(encode_bt709(g) * 219.) as u8 + 16,
(encode_bt709(b) * 219.) as u8 + 16,
))
}
#[cfg(feature = "spirix")]
pub fn to_rec709_u8_s44(&self) -> Option<(u8, u8, u8)> {
let (r, g, b) = self.to_srgb_linear_s44()?;
let (r, g, b) = scale_to_gamut_s44(r, g, b);
Some((
(encode_bt709_s44(r) * 219i16).round().to_u8() + 16,
(encode_bt709_s44(g) * 219i16).round().to_u8() + 16,
(encode_bt709_s44(b) * 219i16).round().to_u8() + 16,
))
}
pub fn to_rec709_u16_f32(&self) -> Option<(u16, u16, u16)> {
let (r, g, b) = self.to_srgb_linear_f32()?;
let (r, g, b) = scale_to_gamut_f32(r, g, b);
Some((
(encode_bt709(r) * 56064.) as u16 + 4096,
(encode_bt709(g) * 56064.) as u16 + 4096,
(encode_bt709(b) * 56064.) as u16 + 4096,
))
}
#[cfg(feature = "spirix")]
pub fn to_rec709_u16_s44(&self) -> Option<(u16, u16, u16)> {
let (r, g, b) = self.to_srgb_linear_s44()?;
let (r, g, b) = scale_to_gamut_s44(r, g, b);
Some((
(encode_bt709_s44(r) * 56064i32).round().to_u16() + 4096,
(encode_bt709_s44(g) * 56064i32).round().to_u16() + 4096,
(encode_bt709_s44(b) * 56064i32).round().to_u16() + 4096,
))
}
pub fn to_rec2020_u8_f32(&self) -> Option<(u8, u8, u8)> {
let (r, g, b) = self.to_rec2020_linear_f32()?;
let (r, g, b) = scale_to_gamut_f32(r, g, b);
Some((
(encode_bt709(r) * 219.) as u8 + 16,
(encode_bt709(g) * 219.) as u8 + 16,
(encode_bt709(b) * 219.) as u8 + 16,
))
}
#[cfg(feature = "spirix")]
pub fn to_rec2020_u8_s44(&self) -> Option<(u8, u8, u8)> {
let (r, g, b) = self.to_rec2020_linear_s44()?;
let (r, g, b) = scale_to_gamut_s44(r, g, b);
Some((
(encode_bt709_s44(r) * 219i16).round().to_u8() + 16,
(encode_bt709_s44(g) * 219i16).round().to_u8() + 16,
(encode_bt709_s44(b) * 219i16).round().to_u8() + 16,
))
}
pub fn to_rec2020_u16_f32(&self) -> Option<(u16, u16, u16)> {
let (r, g, b) = self.to_rec2020_linear_f32()?;
let (r, g, b) = scale_to_gamut_f32(r, g, b);
Some((
(encode_bt709(r) * 56064.) as u16 + 4096,
(encode_bt709(g) * 56064.) as u16 + 4096,
(encode_bt709(b) * 56064.) as u16 + 4096,
))
}
#[cfg(feature = "spirix")]
pub fn to_rec2020_u16_s44(&self) -> Option<(u16, u16, u16)> {
let (r, g, b) = self.to_rec2020_linear_s44()?;
let (r, g, b) = scale_to_gamut_s44(r, g, b);
Some((
(encode_bt709_s44(r) * 56064i32).round().to_u16() + 4096,
(encode_bt709_s44(g) * 56064i32).round().to_u16() + 4096,
(encode_bt709_s44(b) * 56064i32).round().to_u16() + 4096,
))
}
pub fn to_adobe_rgb_linear_f32(&self) -> Option<(f32, f32, f32)> {
let rgb = self.to_rgb_linear_f32()?;
use crate::colour::VSF_RGB2ADOBE_RGB;
let result = apply_matrix_3x3_f32(&VSF_RGB2ADOBE_RGB, &[rgb.r, rgb.g, rgb.b]);
Some((result[0], result[1], result[2]))
}
pub fn to_adobe_rgb_u8_f32(&self) -> Option<(u8, u8, u8)> {
use crate::colour::legacy::transfer::adobe_rgb_oetf;
let (r, g, b) = self.to_adobe_rgb_linear_f32()?;
let (r, g, b) = scale_to_gamut_f32(r, g, b);
Some((
(adobe_rgb_oetf(r) * 255.).round() as u8,
(adobe_rgb_oetf(g) * 255.).round() as u8,
(adobe_rgb_oetf(b) * 255.).round() as u8,
))
}
#[cfg(feature = "spirix")]
pub fn to_adobe_rgb_u8_s44(&self) -> Option<(u8, u8, u8)> {
let (r, g, b) = self.to_adobe_rgb_linear_s44()?;
let (r, g, b) = scale_to_gamut_s44(r, g, b);
Some((
(adobe_rgb_oetf_s44(r) * 255i16).round().to_u8(),
(adobe_rgb_oetf_s44(g) * 255i16).round().to_u8(),
(adobe_rgb_oetf_s44(b) * 255i16).round().to_u8(),
))
}
pub fn to_adobe_rgb_u16_f32(&self) -> Option<(u16, u16, u16)> {
use crate::colour::legacy::transfer::adobe_rgb_oetf;
let (r, g, b) = self.to_adobe_rgb_linear_f32()?;
let (r, g, b) = scale_to_gamut_f32(r, g, b);
Some((
(adobe_rgb_oetf(r) * 65535.).round() as u16,
(adobe_rgb_oetf(g) * 65535.).round() as u16,
(adobe_rgb_oetf(b) * 65535.).round() as u16,
))
}
#[cfg(feature = "spirix")]
pub fn to_adobe_rgb_u16_s44(&self) -> Option<(u16, u16, u16)> {
let (r, g, b) = self.to_adobe_rgb_linear_s44()?;
let (r, g, b) = scale_to_gamut_s44(r, g, b);
Some((
(adobe_rgb_oetf_s44(r) * 65535i32).round().to_u16(),
(adobe_rgb_oetf_s44(g) * 65535i32).round().to_u16(),
(adobe_rgb_oetf_s44(b) * 65535i32).round().to_u16(),
))
}
pub fn from_srgb_f32<T: ColourValue<f32>>(r: T, g: T, b: T, format: ColourFormat) -> Self {
let r_lin = r.to_linear_srgb();
let g_lin = g.to_linear_srgb();
let b_lin = b.to_linear_srgb();
use crate::colour::SRGB2VSF_RGB;
let result = apply_matrix_3x3_f32(&SRGB2VSF_RGB, &[r_lin, g_lin, b_lin]);
let (vsf_r, vsf_g, vsf_b) = (result[0], result[1], result[2]);
Self::from_rgb_linear_f32(vsf_r, vsf_g, vsf_b, format)
}
#[cfg(feature = "spirix")]
pub fn from_srgb_s44<T: ColourValue<S44>>(r: T, g: T, b: T, format: ColourFormat) -> Self {
let r_lin = r.to_linear_srgb();
let g_lin = g.to_linear_srgb();
let b_lin = b.to_linear_srgb();
use crate::colour::SRGB2VSF_RGB_S44;
let result = apply_matrix_3x3_s44(&SRGB2VSF_RGB_S44, &[r_lin, g_lin, b_lin]);
let (vsf_r, vsf_g, vsf_b) = (result[0], result[1], result[2]);
Self::from_rgb_linear_s44(vsf_r, vsf_g, vsf_b, format)
}
pub fn from_rec709_f32<T: ColourValue<f32>>(r: T, g: T, b: T, format: ColourFormat) -> Self {
let r_lin = r.to_linear_rec709();
let g_lin = g.to_linear_rec709();
let b_lin = b.to_linear_rec709();
use crate::colour::SRGB2VSF_RGB;
let result = apply_matrix_3x3_f32(&SRGB2VSF_RGB, &[r_lin, g_lin, b_lin]);
let (vsf_r, vsf_g, vsf_b) = (result[0], result[1], result[2]);
Self::from_rgb_linear_f32(vsf_r, vsf_g, vsf_b, format)
}
#[cfg(feature = "spirix")]
pub fn from_rec709_s44<T: ColourValue<S44>>(r: T, g: T, b: T, format: ColourFormat) -> Self {
let r_lin = r.to_linear_rec709();
let g_lin = g.to_linear_rec709();
let b_lin = b.to_linear_rec709();
use crate::colour::SRGB2VSF_RGB_S44;
let result = apply_matrix_3x3_s44(&SRGB2VSF_RGB_S44, &[r_lin, g_lin, b_lin]);
let (vsf_r, vsf_g, vsf_b) = (result[0], result[1], result[2]);
Self::from_rgb_linear_s44(vsf_r, vsf_g, vsf_b, format)
}
pub fn from_rec2020_f32<T: ColourValue<f32>>(r: T, g: T, b: T, format: ColourFormat) -> Self {
let r_lin = r.to_linear_rec709(); let g_lin = g.to_linear_rec709();
let b_lin = b.to_linear_rec709();
let result = apply_matrix_3x3_f32(&REC2020_2VSF_RGB, &[r_lin, g_lin, b_lin]);
let (vsf_r, vsf_g, vsf_b) = (result[0], result[1], result[2]);
Self::from_rgb_linear_f32(vsf_r, vsf_g, vsf_b, format)
}
#[cfg(feature = "spirix")]
pub fn from_rec2020_s44<T: ColourValue<S44>>(r: T, g: T, b: T, format: ColourFormat) -> Self {
let r_lin = r.to_linear_rec709();
let g_lin = g.to_linear_rec709();
let b_lin = b.to_linear_rec709();
use crate::colour::rec2020::REC2020_2VSF_RGB_S44;
let result = apply_matrix_3x3_s44(&REC2020_2VSF_RGB_S44, &[r_lin, g_lin, b_lin]);
let (vsf_r, vsf_g, vsf_b) = (result[0], result[1], result[2]);
Self::from_rgb_linear_s44(vsf_r, vsf_g, vsf_b, format)
}
pub fn from_lms_f32(l: f32, m: f32, s: f32, format: ColourFormat) -> Self {
use crate::colour::LMS2VSF_RGB;
let result = apply_matrix_3x3_f32(&LMS2VSF_RGB, &[l, m, s]);
let (vsf_r, vsf_g, vsf_b) = (result[0], result[1], result[2]);
Self::from_rgb_linear_f32(vsf_r, vsf_g, vsf_b, format)
}
pub fn from_xyz_f32(x: f32, y: f32, z: f32, format: ColourFormat) -> Self {
use crate::colour::XYZ2VSF_RGB;
let result = apply_matrix_3x3_f32(&XYZ2VSF_RGB, &[x, y, z]);
let (vsf_r, vsf_g, vsf_b) = (result[0], result[1], result[2]);
Self::from_rgb_linear_f32(vsf_r, vsf_g, vsf_b, format)
}
#[cfg(feature = "spirix")]
pub fn from_lms_s44(l: S44, m: S44, s: S44, format: ColourFormat) -> Self {
use crate::colour::LMS2VSF_RGB_S44;
let result = apply_matrix_3x3_s44(&LMS2VSF_RGB_S44, &[l, m, s]);
let (vsf_r, vsf_g, vsf_b) = (result[0], result[1], result[2]);
Self::from_rgb_linear_s44(vsf_r, vsf_g, vsf_b, format)
}
#[cfg(feature = "spirix")]
pub fn from_xyz_s44(x: S44, y: S44, z: S44, format: ColourFormat) -> Self {
use crate::colour::XYZ2VSF_RGB_S44;
let result = apply_matrix_3x3_s44(&XYZ2VSF_RGB_S44, &[x, y, z]);
let (vsf_r, vsf_g, vsf_b) = (result[0], result[1], result[2]);
Self::from_rgb_linear_s44(vsf_r, vsf_g, vsf_b, format)
}
fn from_rgb_linear_f32(r: f32, g: f32, b: f32, format: ColourFormat) -> Self {
match format {
ColourFormat::Rf => VsfType::rf([r, g, b]),
ColourFormat::Ru => {
let (r, g, b) = scale_to_gamut_f32(r, g, b);
VsfType::ru([
delinearize_gamma2_u8_f32(r),
delinearize_gamma2_u8_f32(g),
delinearize_gamma2_u8_f32(b),
])
}
ColourFormat::Rs => {
let (r, g, b) = scale_to_gamut_f32(r, g, b);
VsfType::rs([
delinearize_gamma2_u16_f32(r),
delinearize_gamma2_u16_f32(g),
delinearize_gamma2_u16_f32(b),
])
}
ColourFormat::Ri => {
let (r, g, b) = scale_to_gamut_f32(r, g, b);
VsfType::ri(pack_rgb_676_linear_f32(r, g, b))
}
ColourFormat::Rp => {
let (r, g, b) = scale_to_gamut_f32(r, g, b);
VsfType::rp(pack_rgb_565_linear_f32(r, g, b))
}
ColourFormat::Re => {
let (r, g, b) = scale_to_gamut_f32(r, g, b);
let lum = vsf_rgb_to_photopic_f32(r, g, b);
VsfType::re(delinearize_gamma2_u8_f32(lum))
}
ColourFormat::Rx => {
let (r, g, b) = scale_to_gamut_f32(r, g, b);
let lum = vsf_rgb_to_photopic_f32(r, g, b);
VsfType::rx(delinearize_gamma2_u16_f32(lum))
}
ColourFormat::Rz => {
let lum = vsf_rgb_to_photopic_f32(r, g, b);
VsfType::rz(lum)
}
ColourFormat::Ra => {
let (r, g, b) = scale_to_gamut_f32(r, g, b);
VsfType::ra([
delinearize_gamma2_u8_f32(r),
delinearize_gamma2_u8_f32(g),
delinearize_gamma2_u8_f32(b),
255,
])
}
ColourFormat::Rt => {
let (r, g, b) = scale_to_gamut_f32(r, g, b);
VsfType::rt([
delinearize_gamma2_u16_f32(r),
delinearize_gamma2_u16_f32(g),
delinearize_gamma2_u16_f32(b),
0xFFFF,
])
}
ColourFormat::Rh => VsfType::rh([r, g, b, 1.0]),
#[cfg(feature = "spirix")]
ColourFormat::Rd => {
let lum = vsf_rgb_to_photopic_f32(r, g, b);
VsfType::rd(S44::from(lum))
}
#[cfg(feature = "spirix")]
ColourFormat::Rb => VsfType::rb([S44::from(r), S44::from(g), S44::from(b)]),
#[cfg(feature = "spirix")]
ColourFormat::Rw => VsfType::rw([S44::from(r), S44::from(g), S44::from(b), S44::ONE]),
}
}
#[cfg(feature = "spirix")]
fn from_rgb_linear_s44(r: S44, g: S44, b: S44, format: ColourFormat) -> Self {
match format {
ColourFormat::Rf => VsfType::rf([r.into(), g.into(), b.into()]),
ColourFormat::Ru => {
let (r, g, b) = scale_to_gamut_s44(r, g, b);
VsfType::ru([
delinearize_gamma2_u8_s44(r),
delinearize_gamma2_u8_s44(g),
delinearize_gamma2_u8_s44(b),
])
}
ColourFormat::Rs => {
let (r, g, b) = scale_to_gamut_s44(r, g, b);
VsfType::rs([
delinearize_gamma2_u16_s44(r),
delinearize_gamma2_u16_s44(g),
delinearize_gamma2_u16_s44(b),
])
}
ColourFormat::Ri => {
let (r, g, b) = scale_to_gamut_s44(r, g, b);
VsfType::ri(pack_rgb_676_linear_s44(r, g, b))
}
ColourFormat::Rp => {
let (r, g, b) = scale_to_gamut_s44(r, g, b);
VsfType::rp(pack_rgb_565_linear_s44(r, g, b))
}
ColourFormat::Re => {
let (r, g, b) = scale_to_gamut_s44(r, g, b);
let lum = vsf_rgb_to_photopic_s44(r, g, b);
VsfType::re(delinearize_gamma2_u8_s44(lum))
}
ColourFormat::Rx => {
let (r, g, b) = scale_to_gamut_s44(r, g, b);
let lum = vsf_rgb_to_photopic_s44(r, g, b);
VsfType::rx(delinearize_gamma2_u16_s44(lum))
}
ColourFormat::Rz => {
let lum = vsf_rgb_to_photopic_s44(r, g, b);
VsfType::rz(lum.into())
}
#[cfg(feature = "spirix")]
ColourFormat::Rd => {
let lum = vsf_rgb_to_photopic_s44(r, g, b);
VsfType::rd(lum)
}
#[cfg(feature = "spirix")]
ColourFormat::Rb => VsfType::rb([r, g, b]),
ColourFormat::Ra => {
let (r, g, b) = scale_to_gamut_s44(r, g, b);
VsfType::ra([
delinearize_gamma2_u8_s44(r),
delinearize_gamma2_u8_s44(g),
delinearize_gamma2_u8_s44(b),
255,
])
}
ColourFormat::Rt => {
let (r, g, b) = scale_to_gamut_s44(r, g, b);
VsfType::rt([
delinearize_gamma2_u16_s44(r),
delinearize_gamma2_u16_s44(g),
delinearize_gamma2_u16_s44(b),
0xFFFF,
])
}
ColourFormat::Rh => VsfType::rh([r.into(), g.into(), b.into(), 1.0]),
#[cfg(feature = "spirix")]
ColourFormat::Rw => VsfType::rw([r, g, b, S44::ONE]),
}
}
pub fn from_rgb8_f32(r: u8, g: u8, b: u8, format: ColourFormat) -> Self {
let r_lin = linearize_gamma2_u8_f32(r);
let g_lin = linearize_gamma2_u8_f32(g);
let b_lin = linearize_gamma2_u8_f32(b);
match format {
ColourFormat::Ru => VsfType::ru([r, g, b]),
ColourFormat::Rs => VsfType::rs([
(r as u16) << 8 | r as u16,
(g as u16) << 8 | g as u16,
(b as u16) << 8 | b as u16,
]),
ColourFormat::Rf => VsfType::rf([r_lin, g_lin, b_lin]),
ColourFormat::Ri => VsfType::ri(pack_rgb_676_linear_f32(r_lin, g_lin, b_lin)),
ColourFormat::Rp => VsfType::rp(pack_rgb_565_linear_f32(r_lin, g_lin, b_lin)),
ColourFormat::Re => {
let lum = vsf_rgb_to_photopic_f32(r_lin, g_lin, b_lin);
VsfType::re(delinearize_gamma2_u8_f32(lum))
}
ColourFormat::Rx => {
let lum = vsf_rgb_to_photopic_f32(r_lin, g_lin, b_lin);
VsfType::rx(delinearize_gamma2_u16_f32(lum))
}
ColourFormat::Rz => {
let lum = vsf_rgb_to_photopic_f32(r_lin, g_lin, b_lin);
VsfType::rz(lum)
}
ColourFormat::Ra => VsfType::ra([r, g, b, 255]),
ColourFormat::Rt => VsfType::rt([
(r as u16) << 8 | r as u16,
(g as u16) << 8 | g as u16,
(b as u16) << 8 | b as u16,
0xFFFF,
]),
ColourFormat::Rh => VsfType::rh([r_lin, g_lin, b_lin, 1.0]),
#[cfg(feature = "spirix")]
ColourFormat::Rd => {
let lum = vsf_rgb_to_photopic_f32(r_lin, g_lin, b_lin);
VsfType::rd(S44::from(lum))
}
#[cfg(feature = "spirix")]
ColourFormat::Rb => VsfType::rb([S44::from(r_lin), S44::from(g_lin), S44::from(b_lin)]),
#[cfg(feature = "spirix")]
ColourFormat::Rw => VsfType::rw([
S44::from(r_lin),
S44::from(g_lin),
S44::from(b_lin),
S44::ONE,
]),
}
}
#[cfg(feature = "spirix")]
pub fn from_rgb8_s44(r: u8, g: u8, b: u8, format: ColourFormat) -> Self {
let r_lin = linearize_gamma2_u8_s44(r);
let g_lin = linearize_gamma2_u8_s44(g);
let b_lin = linearize_gamma2_u8_s44(b);
match format {
ColourFormat::Ru => VsfType::ru([r, g, b]),
ColourFormat::Rs => VsfType::rs([
(r as u16) << 8 | r as u16,
(g as u16) << 8 | g as u16,
(b as u16) << 8 | b as u16,
]),
ColourFormat::Rf => VsfType::rf([r_lin.into(), g_lin.into(), b_lin.into()]),
ColourFormat::Ri => VsfType::ri(pack_rgb_676_linear_s44(r_lin, g_lin, b_lin)),
ColourFormat::Rp => VsfType::rp(pack_rgb_565_linear_s44(r_lin, g_lin, b_lin)),
ColourFormat::Re => {
let lum = vsf_rgb_to_photopic_s44(r_lin, g_lin, b_lin);
VsfType::re(delinearize_gamma2_u8_s44(lum))
}
ColourFormat::Rx => {
let lum = vsf_rgb_to_photopic_s44(r_lin, g_lin, b_lin);
VsfType::rx(delinearize_gamma2_u16_s44(lum))
}
ColourFormat::Rz => {
let lum = vsf_rgb_to_photopic_s44(r_lin, g_lin, b_lin);
VsfType::rz(lum.into())
}
ColourFormat::Rd => {
let lum = vsf_rgb_to_photopic_s44(r_lin, g_lin, b_lin);
VsfType::rd(lum)
}
ColourFormat::Rb => VsfType::rb([r_lin, g_lin, b_lin]),
ColourFormat::Ra => VsfType::ra([r, g, b, 255]),
ColourFormat::Rt => VsfType::rt([
(r as u16) << 8 | r as u16,
(g as u16) << 8 | g as u16,
(b as u16) << 8 | b as u16,
0xFFFF,
]),
ColourFormat::Rh => VsfType::rh([r_lin.into(), g_lin.into(), b_lin.into(), 1.0]),
ColourFormat::Rw => VsfType::rw([r_lin, g_lin, b_lin, S44::ONE]),
}
}
pub fn from_rgba8_f32(r: u8, g: u8, b: u8, a: u8, format: ColourFormat) -> Self {
let r_lin = linearize_gamma2_u8_f32(r);
let g_lin = linearize_gamma2_u8_f32(g);
let b_lin = linearize_gamma2_u8_f32(b);
let a_lin = a as f32 / 256.;
match format {
ColourFormat::Ra => VsfType::ra([r, g, b, a]),
ColourFormat::Rt => VsfType::rt([
(r as u16) << 8 | r as u16,
(g as u16) << 8 | g as u16,
(b as u16) << 8 | b as u16,
(a as u16) << 8 | a as u16,
]),
ColourFormat::Rh => VsfType::rh([r_lin, g_lin, b_lin, a_lin]),
_ => Self::from_rgb8_f32(r, g, b, format),
}
}
#[cfg(feature = "spirix")]
pub fn from_rgba8_s44(r: u8, g: u8, b: u8, a: u8, format: ColourFormat) -> Self {
let r_lin = linearize_gamma2_u8_s44(r);
let g_lin = linearize_gamma2_u8_s44(g);
let b_lin = linearize_gamma2_u8_s44(b);
let a_lin = S44::from(a) >> 8isize;
match format {
ColourFormat::Ra => VsfType::ra([r, g, b, a]),
ColourFormat::Rt => VsfType::rt([
(r as u16) << 8 | r as u16,
(g as u16) << 8 | g as u16,
(b as u16) << 8 | b as u16,
(a as u16) << 8 | a as u16,
]),
ColourFormat::Rh => {
VsfType::rh([r_lin.into(), g_lin.into(), b_lin.into(), a_lin.into()])
}
ColourFormat::Rw => VsfType::rw([r_lin, g_lin, b_lin, a_lin]),
_ => Self::from_rgb8_s44(r, g, b, format),
}
}
pub fn convert_colour_f32(&self, target: ColourFormat) -> Option<Self> {
let rgba = self.to_rgba_linear_f32()?;
let r_gamma = delinearize_gamma2_u8_f32(rgba.r);
let g_gamma = delinearize_gamma2_u8_f32(rgba.g);
let b_gamma = delinearize_gamma2_u8_f32(rgba.b);
let a_u8 = (rgba.a * 256.) as u8;
Some(Self::from_rgba8_f32(
r_gamma, g_gamma, b_gamma, a_u8, target,
))
}
#[cfg(feature = "spirix")]
pub fn convert_colour_s44(&self, target: ColourFormat) -> Option<Self> {
let rgba = self.to_rgba_linear_s44()?;
let r_gamma = delinearize_gamma2_u8_s44(rgba.r);
let g_gamma = delinearize_gamma2_u8_s44(rgba.g);
let b_gamma = delinearize_gamma2_u8_s44(rgba.b);
let a_u8 = (rgba.a << 8isize).to_u8();
Some(Self::from_rgba8_s44(
r_gamma, g_gamma, b_gamma, a_u8, target,
))
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ColourFormat {
Re, Rx, Rz, #[cfg(feature = "spirix")]
Rd,
Ri, Rp,
Ru, Rs, Rf, #[cfg(feature = "spirix")]
Rb,
Ra, Rt, Rh, #[cfg(feature = "spirix")]
Rw, }
fn unpack_rgb_676_linear_f32(packed: u8) -> (f32, f32, f32) {
let b = packed % 6;
let temp = packed / 6;
let g = temp % 7;
let r = temp / 7;
let r_gamma = r as f32 / 5.;
let g_gamma = g as f32 / 6.;
let b_gamma = b as f32 / 5.;
(
linearize_gamma2_f32(r_gamma),
linearize_gamma2_f32(g_gamma),
linearize_gamma2_f32(b_gamma),
)
}
fn pack_rgb_676_linear_f32(r: f32, g: f32, b: f32) -> u8 {
let r_gamma = delinearize_gamma2_f32(r);
let g_gamma = delinearize_gamma2_f32(g);
let b_gamma = delinearize_gamma2_f32(b);
let r6 = (r_gamma * 5.).min(5.) as u8;
let g7 = (g_gamma * 6.).min(6.) as u8;
let b6 = (b_gamma * 5.).min(5.) as u8;
((r6 * 7) + g7) * 6 + b6
}
fn unpack_rgb_565_linear_f32(packed: u16) -> (f32, f32, f32) {
let r5 = (packed >> 11) & 0x1F;
let g6 = (packed >> 5) & 0x3F;
let b5 = packed & 0x1F;
let r_gamma = r5 as f32 / 32.;
let g_gamma = g6 as f32 / 64.;
let b_gamma = b5 as f32 / 32.;
(
linearize_gamma2_f32(r_gamma),
linearize_gamma2_f32(g_gamma),
linearize_gamma2_f32(b_gamma),
)
}
fn pack_rgb_565_linear_f32(r: f32, g: f32, b: f32) -> u16 {
let r_gamma = delinearize_gamma2_f32(r);
let g_gamma = delinearize_gamma2_f32(g);
let b_gamma = delinearize_gamma2_f32(b);
let r5 = (r_gamma.min(1.) * 32.) as u16;
let g6 = (g_gamma.min(1.) * 64.) as u16;
let b5 = (b_gamma.min(1.) * 32.) as u16;
(r5 << 11) | (g6 << 5) | b5
}
#[cfg(feature = "spirix")]
fn unpack_rgb_676_linear_s44(packed: u8) -> (S44, S44, S44) {
let b = packed % 6;
let temp = packed / 6;
let g = temp % 7;
let r = temp / 7;
let r_gamma = S44::from(r) / 5;
let g_gamma = S44::from(g) / 6;
let b_gamma = S44::from(b) / 5;
(r_gamma * r_gamma, g_gamma * g_gamma, b_gamma * b_gamma)
}
#[cfg(feature = "spirix")]
fn pack_rgb_676_linear_s44(r: S44, g: S44, b: S44) -> u8 {
let r_gamma = r.sqrt();
let g_gamma = g.sqrt();
let b_gamma = b.sqrt();
let r_scaled: S44 = r_gamma * 5;
let g_scaled: S44 = g_gamma * 6;
let b_scaled: S44 = b_gamma * 5;
let r6: u8 = r_scaled.to_u8();
let g7: u8 = g_scaled.to_u8();
let b6: u8 = b_scaled.to_u8();
((r6 * 7) + g7) * 6 + b6
}
#[cfg(feature = "spirix")]
fn unpack_rgb_565_linear_s44(packed: u16) -> (S44, S44, S44) {
let r5 = (packed >> 11) & 0x1F;
let g6 = (packed >> 5) & 0x3F;
let b5 = packed & 0x1F;
let r_gamma = S44::from(r5) >> 5;
let g_gamma = S44::from(g6) >> 6;
let b_gamma = S44::from(b5) >> 5;
(r_gamma * r_gamma, g_gamma * g_gamma, b_gamma * b_gamma)
}
#[cfg(feature = "spirix")]
fn pack_rgb_565_linear_s44(r: S44, g: S44, b: S44) -> u16 {
let r_gamma = r.sqrt();
let g_gamma = g.sqrt();
let b_gamma = b.sqrt();
let r_clamped: S44 = r_gamma.min(1);
let g_clamped: S44 = g_gamma.min(1);
let b_clamped: S44 = b_gamma.min(1);
let r_scaled: S44 = r_clamped * 32;
let g_scaled: S44 = g_clamped * 64;
let b_scaled: S44 = b_clamped * 32;
let r5: u16 = r_scaled.to_u16();
let g6: u16 = g_scaled.to_u16();
let b5: u16 = b_scaled.to_u16();
(r5 << 11) | (g6 << 5) | b5
}
const PHOTOPIC_WHITE_NORM: f32 = {
let l_white = VSF_RGB2LMS[0] + VSF_RGB2LMS[1] + VSF_RGB2LMS[2];
let m_white = VSF_RGB2LMS[3] + VSF_RGB2LMS[4] + VSF_RGB2LMS[5];
let s_white = VSF_RGB2LMS[6] + VSF_RGB2LMS[7] + VSF_RGB2LMS[8];
LMS2PHOTOPIC[0] * l_white + LMS2PHOTOPIC[1] * m_white + LMS2PHOTOPIC[2] * s_white
};
pub fn vsf_rgb_to_photopic_f32(r: f32, g: f32, b: f32) -> f32 {
let l = VSF_RGB2LMS[0] * r + VSF_RGB2LMS[1] * g + VSF_RGB2LMS[2] * b;
let m = VSF_RGB2LMS[3] * r + VSF_RGB2LMS[4] * g + VSF_RGB2LMS[5] * b;
let s = VSF_RGB2LMS[6] * r + VSF_RGB2LMS[7] * g + VSF_RGB2LMS[8] * b;
let photopic_raw = LMS2PHOTOPIC[0] * l + LMS2PHOTOPIC[1] * m + LMS2PHOTOPIC[2] * s;
photopic_raw / PHOTOPIC_WHITE_NORM
}
#[cfg(feature = "spirix")]
pub fn vsf_rgb_to_photopic_s44(r: S44, g: S44, b: S44) -> S44 {
use crate::colour::{LMS2PHOTOPIC_S44, VSF_RGB2LMS_S44};
const PHOTOPIC_WHITE_NORM_S44: S44 = sf!(PHOTOPIC_WHITE_NORM);
let l = VSF_RGB2LMS_S44[0] * r + VSF_RGB2LMS_S44[1] * g + VSF_RGB2LMS_S44[2] * b;
let m = VSF_RGB2LMS_S44[3] * r + VSF_RGB2LMS_S44[4] * g + VSF_RGB2LMS_S44[5] * b;
let s = VSF_RGB2LMS_S44[6] * r + VSF_RGB2LMS_S44[7] * g + VSF_RGB2LMS_S44[8] * b;
let photopic_raw = LMS2PHOTOPIC_S44[0] * l + LMS2PHOTOPIC_S44[1] * m + LMS2PHOTOPIC_S44[2] * s;
photopic_raw / PHOTOPIC_WHITE_NORM_S44
}
pub fn linearize_gamma2_f32(encoded: f32) -> f32 {
encoded * encoded
}
pub fn delinearize_gamma2_f32(linear: f32) -> f32 {
linear.sqrt()
}
pub fn linearize_gamma2_u8_f32(encoded: u8) -> f32 {
let normalized = encoded as f32 / 256.;
linearize_gamma2_f32(normalized)
}
pub fn delinearize_gamma2_u8_f32(linear: f32) -> u8 {
let encoded = delinearize_gamma2_f32(linear);
(encoded * 256.) as u8
}
pub fn linearize_gamma2_u16_f32(encoded: u16) -> f32 {
let normalized = encoded as f32 / 65536.;
linearize_gamma2_f32(normalized)
}
pub fn delinearize_gamma2_u16_f32(linear: f32) -> u16 {
let encoded = delinearize_gamma2_f32(linear);
(encoded * 65536.) as u16
}
pub fn linearize_gamma2_rgb_f32(r: u8, g: u8, b: u8) -> (f32, f32, f32) {
(
linearize_gamma2_u8_f32(r),
linearize_gamma2_u8_f32(g),
linearize_gamma2_u8_f32(b),
)
}
pub fn delinearize_gamma2_rgb_f32(r: f32, g: f32, b: f32) -> (u8, u8, u8) {
(
delinearize_gamma2_u8_f32(r),
delinearize_gamma2_u8_f32(g),
delinearize_gamma2_u8_f32(b),
)
}
#[cfg(feature = "spirix")]
#[inline]
pub fn linearize_gamma2_s44(encoded: S44) -> S44 {
encoded.square()
}
#[cfg(feature = "spirix")]
#[inline]
pub fn delinearize_gamma2_s44(linear: S44) -> S44 {
linear.sqrt()
}
#[cfg(feature = "spirix")]
#[inline]
pub fn linearize_gamma2_u8_s44(encoded: u8) -> S44 {
let normalized = S44::from(encoded) >> 8isize;
normalized.square()
}
#[cfg(feature = "spirix")]
#[inline]
pub fn delinearize_gamma2_u8_s44(linear: S44) -> u8 {
let encoded = linear.sqrt();
let scaled = encoded << 8isize;
scaled.to_u8()
}
#[cfg(feature = "spirix")]
#[inline]
pub fn linearize_gamma2_u16_s44(encoded: u16) -> S44 {
let normalized = S44::from(encoded) >> 16isize;
normalized.square()
}
#[cfg(feature = "spirix")]
#[inline]
pub fn delinearize_gamma2_u16_s44(linear: S44) -> u16 {
let encoded = linear.sqrt();
let scaled: S44 = encoded << 16;
scaled.to_u16()
}
#[cfg(feature = "spirix")]
pub fn linearize_gamma2_rgb_s44(r: u8, g: u8, b: u8) -> (S44, S44, S44) {
(
linearize_gamma2_u8_s44(r),
linearize_gamma2_u8_s44(g),
linearize_gamma2_u8_s44(b),
)
}
#[cfg(feature = "spirix")]
pub fn delinearize_gamma2_rgb_s44(r: S44, g: S44, b: S44) -> (u8, u8, u8) {
(
delinearize_gamma2_u8_s44(r),
delinearize_gamma2_u8_s44(g),
delinearize_gamma2_u8_s44(b),
)
}
#[cfg(feature = "spirix")]
#[inline]
pub fn srgb_oetf_s44(linear: S44) -> S44 {
const THRESH: S44 = sf!(0.0031308);
const A: S44 = sf!(12.92);
const B: S44 = sf!(1.055);
const GAMMA: S44 = sf!(1.0 / 2.4);
const C: S44 = sf!(0.055);
if linear <= THRESH {
A * linear
} else {
B * linear.pow(GAMMA) - C
}
}
#[cfg(feature = "spirix")]
#[inline]
pub fn encode_bt709_s44(linear: S44) -> S44 {
const THRESH: S44 = sf!(0.018);
const A: S44 = sf!(4.5);
const B: S44 = sf!(1.099);
const GAMMA: S44 = sf!(0.45);
const C: S44 = sf!(0.099);
if linear < THRESH {
linear * A
} else {
B * linear.pow(GAMMA) - C
}
}
#[cfg(feature = "spirix")]
#[inline]
pub fn adobe_rgb_oetf_s44(linear: S44) -> S44 {
const GAMMA: S44 = sf!(1.0 / 2.2);
linear.pow(GAMMA)
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(deprecated)]
use crate::colour::legacy::{delinearize_srgb_u8, linearize_srgb};
#[test]
fn test_named_shortcuts_to_rgb() {
assert_eq!(
VsfType::rck.to_rgb_linear_f32(),
Some(RgbLinearF32 {
r: 0.,
g: 0.,
b: 0.
})
);
assert_eq!(
VsfType::rcw.to_rgb_linear_f32(),
Some(RgbLinearF32 {
r: 1.,
g: 1.,
b: 1.
})
);
assert_eq!(
VsfType::rcr.to_rgb_linear_f32(),
Some(RgbLinearF32 {
r: 1.,
g: 0.,
b: 0.
})
);
}
#[test]
fn test_srgb_piecewise_correctness() {
assert_eq!(linearize_srgb(0.0), 0.0);
assert_eq!(delinearize_srgb(0.0), 0.0);
let white_linear = linearize_srgb(1.0);
assert!((white_linear - 1.0).abs() < 0.001);
let threshold_encoded = 0.04045;
let linear = linearize_srgb(threshold_encoded);
let roundtrip = delinearize_srgb(linear);
assert!((roundtrip - threshold_encoded).abs() < 0.001);
let test_values = [0u8, 10, 50, 128, 200, 255];
for &val in &test_values {
let linear = linearize_srgb_u8(val);
let roundtrip = delinearize_srgb_u8(linear);
assert!(
(roundtrip as i16 - val as i16).abs() <= 1,
"Failed roundtrip for {}: got {}",
val,
roundtrip
);
}
}
#[test]
fn test_srgb_vsf_rgb_conversion() {
let test_colours = [
(0u8, 0u8, 0u8), (255u8, 255u8, 255u8), (128u8, 128u8, 128u8), (255u8, 0u8, 0u8), (0u8, 255u8, 0u8), (0u8, 0u8, 255u8), (200u8, 100u8, 50u8), ];
for &(r_in, g_in, b_in) in &test_colours {
let vsf = VsfType::from_srgb_f32(r_in, g_in, b_in, ColourFormat::Rf);
if let Some((r_out, g_out, b_out)) = vsf.to_srgb_u8_f32() {
let r_diff = (r_out as i16 - r_in as i16).abs();
let g_diff = (g_out as i16 - g_in as i16).abs();
let b_diff = (b_out as i16 - b_in as i16).abs();
assert!(
r_diff <= 2 && g_diff <= 2 && b_diff <= 2,
"sRGB roundtrip failed for ({}, {}, {}): got ({}, {}, {}), diffs: r={}, g={}, b={}",
r_in, g_in, b_in, r_out, g_out, b_out, r_diff, g_diff, b_diff
);
} else {
panic!("to_srgb() returned None for valid colour");
}
}
}
#[test]
fn test_packed_rgb_roundtrip() {
let original_u8 = (130u8, 60u8, 200u8);
let packed = VsfType::from_rgb8_f32(
original_u8.0,
original_u8.1,
original_u8.2,
ColourFormat::Ri,
);
let unpacked = packed.to_rgb_linear_f32().unwrap();
let unpacked_u8 = (
delinearize_gamma2_u8_f32(unpacked.r),
delinearize_gamma2_u8_f32(unpacked.g),
delinearize_gamma2_u8_f32(unpacked.b),
);
assert!((unpacked_u8.0 as i16 - original_u8.0 as i16).abs() < 50);
assert!((unpacked_u8.1 as i16 - original_u8.1 as i16).abs() < 40);
assert!((unpacked_u8.2 as i16 - original_u8.2 as i16).abs() < 50);
}
#[test]
fn test_colour_conversion() {
let red_rgb = VsfType::ru([255, 0, 0]);
let red_rgba = red_rgb.convert_colour_f32(ColourFormat::Ra).unwrap();
let result = red_rgba.to_rgba_linear_f32().unwrap();
assert!((result.r - 1.0).abs() < 0.01, "r={}", result.r);
assert!(result.g < 0.01, "g={}", result.g);
assert!(result.b < 0.01, "b={}", result.b);
assert!((result.a - 1.0).abs() < 0.01, "a={}", result.a);
}
#[test]
fn test_gamma2_roundtrip() {
let values = [0., 0.25, 0.5, 0.75, 1.];
for &v in &values {
let delinearized = delinearize_gamma2_f32(v);
let linearized = linearize_gamma2_f32(delinearized);
assert!(
(linearized - v).abs() < 1e-7,
"v={} → {} → {} (diff={})",
v,
delinearized,
linearized,
(linearized - v).abs()
);
}
}
#[test]
fn test_gamma2_u8_roundtrip() {
let values = [0u8, 64, 128, 192, 255];
for &v in &values {
let linearized = linearize_gamma2_u8_f32(v);
let delinearized = delinearize_gamma2_u8_f32(linearized);
assert!(
(delinearized as i16 - v as i16).abs() <= 1,
"Value {} → {} → {} (diff={})",
v,
linearized,
delinearized,
(delinearized as i16 - v as i16)
);
}
}
#[test]
fn test_bt2020_conversion() {
let bt2020_white = VsfType::from_rec2020_f32(235u8, 235u8, 235u8, ColourFormat::Ru);
let vsf_white = bt2020_white.to_rgb_linear_f32().unwrap();
assert!(vsf_white.r > 0.8 && vsf_white.r < 0.9, "r={}", vsf_white.r);
assert!(vsf_white.g > 0.9 && vsf_white.g <= 1.0, "g={}", vsf_white.g);
assert!(vsf_white.b > 0.9 && vsf_white.b <= 1.0, "b={}", vsf_white.b);
let bt2020_black = VsfType::from_rec2020_f32(16u8, 16u8, 16u8, ColourFormat::Ru);
let vsf_black = bt2020_black.to_rgb_linear_f32().unwrap();
assert!(vsf_black.r < 0.01);
assert!(vsf_black.g < 0.01);
assert!(vsf_black.b < 0.01);
let bt2020_red = VsfType::from_rec2020_f32(235u8, 16u8, 16u8, ColourFormat::Ru);
let vsf_red = bt2020_red.to_rgb_linear_f32().unwrap();
assert!(vsf_red.r > vsf_red.g && vsf_red.r > vsf_red.b);
}
#[test]
#[cfg(feature = "spirix")]
fn test_scalar_f4e4_conversions() {
use super::*;
use spirix::S44;
let u8_val = 128u8;
let s44_linear: S44 = <u8 as ColourValue<S44>>::to_linear_srgb(u8_val);
let s44_f32 = s44_linear.to_f32();
assert!(s44_f32 > 0.15 && s44_f32 < 0.25, "s44_linear = {}", s44_f32);
let roundtrip = <u8 as ColourValue<S44>>::from_linear_srgb(s44_linear);
assert!(
(roundtrip as i16 - u8_val as i16).abs() <= 2,
"Roundtrip failed: {} → {} → {}",
u8_val,
s44_f32,
roundtrip
);
let s44_val = S44::from(0.5);
let s44_out: S44 = <S44 as ColourValue<S44>>::to_linear_srgb(s44_val);
assert!((s44_out.to_f32() - 0.5).abs() < 0.001, "Passthru failed");
let u8_gamma = 180u8;
let s44_gamma2: S44 = <u8 as ColourValue<S44>>::to_linear_gamma2(u8_gamma);
let u8_back = <u8 as ColourValue<S44>>::from_linear_gamma2(s44_gamma2);
assert!(
(u8_back as i16 - u8_gamma as i16).abs() <= 1,
"Gamma2 roundtrip failed: {} → {} → {}",
u8_gamma,
s44_gamma2.to_f32(),
u8_back
);
}
}