#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum GrayExpand {
Broadcast,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum AlphaPolicy {
DiscardIfOpaque,
DiscardUnchecked,
CompositeOnto {
r: u8,
g: u8,
b: u8,
},
Forbid,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DepthPolicy {
Round,
Truncate,
Forbid,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum LumaCoefficients {
Bt709,
Bt601,
Bt2020,
DisplayP3,
}
impl LumaCoefficients {
#[inline]
pub const fn coefficients(self) -> [f32; 3] {
match self {
Self::Bt709 => [0.2126, 0.7152, 0.0722],
Self::Bt601 => [0.299, 0.587, 0.114],
Self::Bt2020 => [0.2627, 0.6780, 0.0593],
Self::DisplayP3 => [0.2289746, 0.6917385, 0.0792869],
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct ConvertOptions {
pub gray_expand: GrayExpand,
pub alpha_policy: AlphaPolicy,
pub depth_policy: DepthPolicy,
pub luma: Option<LumaCoefficients>,
pub clip_out_of_gamut: bool,
}
impl ConvertOptions {
pub const fn forbid_lossy() -> Self {
Self {
gray_expand: GrayExpand::Broadcast,
alpha_policy: AlphaPolicy::Forbid,
depth_policy: DepthPolicy::Forbid,
luma: None,
clip_out_of_gamut: true,
}
}
pub const fn permissive() -> Self {
Self {
gray_expand: GrayExpand::Broadcast,
alpha_policy: AlphaPolicy::DiscardIfOpaque,
depth_policy: DepthPolicy::Round,
luma: Some(LumaCoefficients::Bt709),
clip_out_of_gamut: true,
}
}
pub const fn with_alpha_policy(mut self, policy: AlphaPolicy) -> Self {
self.alpha_policy = policy;
self
}
pub const fn with_depth_policy(mut self, policy: DepthPolicy) -> Self {
self.depth_policy = policy;
self
}
pub const fn with_gray_expand(mut self, expand: GrayExpand) -> Self {
self.gray_expand = expand;
self
}
pub const fn with_clip_out_of_gamut(mut self, clip: bool) -> Self {
self.clip_out_of_gamut = clip;
self
}
pub const fn with_luma(mut self, luma: Option<LumaCoefficients>) -> Self {
self.luma = luma;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
#[test]
fn gray_expand_derive_traits() {
let a = GrayExpand::Broadcast;
let b = a;
#[allow(clippy::clone_on_copy)]
let c = a.clone();
assert_eq!(a, b);
assert_eq!(a, c);
let _ = format!("{a:?}");
}
#[test]
fn alpha_policy_variants() {
let discard = AlphaPolicy::DiscardIfOpaque;
let unchecked = AlphaPolicy::DiscardUnchecked;
let composite = AlphaPolicy::CompositeOnto {
r: 255,
g: 255,
b: 255,
};
let forbid = AlphaPolicy::Forbid;
assert_ne!(discard, unchecked);
assert_ne!(composite, forbid);
let composite2 = AlphaPolicy::CompositeOnto {
r: 255,
g: 255,
b: 255,
};
assert_eq!(composite, composite2);
let composite_diff = AlphaPolicy::CompositeOnto { r: 0, g: 0, b: 0 };
assert_ne!(composite, composite_diff);
}
#[test]
fn depth_policy_variants() {
assert_ne!(DepthPolicy::Round, DepthPolicy::Truncate);
assert_ne!(DepthPolicy::Round, DepthPolicy::Forbid);
let a = DepthPolicy::Truncate;
#[allow(clippy::clone_on_copy)]
let b = a.clone();
assert_eq!(a, b);
}
#[test]
fn luma_coefficients_variants() {
assert_ne!(LumaCoefficients::Bt709, LumaCoefficients::Bt601);
assert_ne!(LumaCoefficients::Bt709, LumaCoefficients::Bt2020);
assert_ne!(LumaCoefficients::Bt601, LumaCoefficients::Bt2020);
assert_ne!(LumaCoefficients::Bt709, LumaCoefficients::DisplayP3);
assert_ne!(LumaCoefficients::Bt601, LumaCoefficients::DisplayP3);
assert_ne!(LumaCoefficients::Bt2020, LumaCoefficients::DisplayP3);
let a = LumaCoefficients::Bt709;
let b = a;
assert_eq!(a, b);
}
#[test]
fn luma_coefficients_accessor_values() {
assert_eq!(
LumaCoefficients::Bt709.coefficients(),
[0.2126_f32, 0.7152, 0.0722],
);
assert_eq!(
LumaCoefficients::Bt601.coefficients(),
[0.299_f32, 0.587, 0.114],
);
assert_eq!(
LumaCoefficients::Bt2020.coefficients(),
[0.2627_f32, 0.6780, 0.0593],
);
assert_eq!(
LumaCoefficients::DisplayP3.coefficients(),
[0.2289746_f32, 0.6917385, 0.0792869],
);
}
#[test]
fn luma_coefficients_sum_to_near_unity() {
for luma in [
LumaCoefficients::Bt709,
LumaCoefficients::Bt601,
LumaCoefficients::Bt2020,
LumaCoefficients::DisplayP3,
] {
let [r, g, b] = luma.coefficients();
let sum = r + g + b;
assert!(
(sum - 1.0).abs() < 1e-6,
"{luma:?} coefficients sum to {sum}, expected ~1.0"
);
}
}
#[test]
fn convert_options_derive_traits() {
let opts = ConvertOptions {
gray_expand: GrayExpand::Broadcast,
alpha_policy: AlphaPolicy::DiscardUnchecked,
depth_policy: DepthPolicy::Round,
luma: Some(LumaCoefficients::Bt709),
clip_out_of_gamut: true,
};
#[allow(clippy::clone_on_copy)]
let opts2 = opts.clone();
assert_eq!(opts, opts2);
let _ = format!("{opts:?}");
}
#[test]
#[cfg(feature = "std")]
fn alpha_policy_hash() {
use core::hash::{Hash, Hasher};
let mut h1 = std::hash::DefaultHasher::new();
AlphaPolicy::Forbid.hash(&mut h1);
let mut h2 = std::hash::DefaultHasher::new();
AlphaPolicy::Forbid.hash(&mut h2);
assert_eq!(h1.finish(), h2.finish());
}
#[test]
#[cfg(feature = "std")]
fn convert_options_hash() {
use core::hash::{Hash, Hasher};
let opts = ConvertOptions {
gray_expand: GrayExpand::Broadcast,
alpha_policy: AlphaPolicy::Forbid,
depth_policy: DepthPolicy::Forbid,
luma: None,
clip_out_of_gamut: true,
};
let mut h = std::hash::DefaultHasher::new();
opts.hash(&mut h);
let _ = h.finish();
}
}