use std::fmt;
use std::str::FromStr;
use crate::Error;
#[doc(hidden)]
#[macro_export]
macro_rules! named_enum {
($(#[$m:meta])* $name:ident { $($(#[$vm:meta])* $v:ident = $s:literal),+ $(,)? }) => {
$(#[$m])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum $name { $($(#[$vm])* $v),+ }
impl $name {
pub const fn as_str(self) -> &'static str {
match self { $(Self::$v => $s),+ }
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for $name {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s {
$($s => Ok(Self::$v),)+
_ => Err(Error::Invalid { what: stringify!($name), value: s.to_owned() }),
}
}
}
};
}
#[doc(hidden)]
pub use crate::named_enum;
named_enum!(
Primaries {
Bt709 = "bt709",
Bt2020 = "bt2020",
DisplayP3 = "smpte432",
DciP3 = "smpte431",
Bt601 = "smpte170m",
Unknown = "unknown",
}
);
named_enum!(
Transfer {
Bt709 = "bt709",
Srgb = "iec61966-2-1",
Pq = "smpte2084",
Hlg = "arib-std-b67",
Linear = "linear",
Unknown = "unknown",
}
);
named_enum!(
Matrix {
Bt709 = "bt709",
Bt2020Ncl = "bt2020nc",
Bt601 = "smpte170m",
Rgb = "gbr",
Unknown = "unknown",
}
);
named_enum!(
Range {
Limited = "tv",
Full = "pc",
Unknown = "unknown",
}
);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ColorSpace {
pub primaries: Primaries,
pub transfer: Transfer,
pub matrix: Matrix,
pub range: Range,
}
impl ColorSpace {
pub const BT709_TV: Self = Self {
primaries: Primaries::Bt709,
transfer: Transfer::Bt709,
matrix: Matrix::Bt709,
range: Range::Limited,
};
pub const BT709_RGB: Self = Self {
primaries: Primaries::Bt709,
transfer: Transfer::Bt709,
matrix: Matrix::Rgb,
range: Range::Full,
};
pub const UNKNOWN: Self = Self {
primaries: Primaries::Unknown,
transfer: Transfer::Unknown,
matrix: Matrix::Unknown,
range: Range::Unknown,
};
pub fn is_hdr(&self) -> bool {
matches!(self.transfer, Transfer::Pq | Transfer::Hlg)
}
}
impl ColorSpace {
pub const BT2020_PQ: Self = Self {
primaries: Primaries::Bt2020,
transfer: Transfer::Pq,
matrix: Matrix::Bt2020Ncl,
range: Range::Limited,
};
pub const BT2020_HLG: Self = Self {
primaries: Primaries::Bt2020,
transfer: Transfer::Hlg,
..Self::BT2020_PQ
};
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HdrMetadata {
pub primaries: [[f64; 2]; 4],
pub max_luminance: f64,
pub min_luminance: f64,
pub max_cll: u32,
pub max_fall: u32,
}
impl HdrMetadata {
pub const P3_D65_1000: Self = Self {
primaries: [
[0.680, 0.320],
[0.265, 0.690],
[0.150, 0.060],
[0.3127, 0.3290],
],
max_luminance: 1000.0,
min_luminance: 0.0001,
max_cll: 1000,
max_fall: 400,
};
#[must_use]
pub fn with_peak(self, nits: f64) -> Self {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let cap = nits.round().max(0.0) as u32;
Self {
max_luminance: nits,
max_cll: self.max_cll.min(cap),
max_fall: self.max_fall.min(cap),
..self
}
}
}
impl Default for ColorSpace {
fn default() -> Self {
Self::BT709_TV
}
}
impl fmt::Display for ColorSpace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}/{}/{}/{}",
self.matrix, self.primaries, self.transfer, self.range
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn names_round_trip() {
assert_eq!("arib-std-b67".parse::<Transfer>().unwrap(), Transfer::Hlg);
assert_eq!(Range::Full.to_string(), "pc");
assert!("nope".parse::<Primaries>().is_err());
assert_eq!(ColorSpace::BT709_TV.to_string(), "bt709/bt709/bt709/tv");
}
#[test]
fn hdr() {
let hlg = ColorSpace {
transfer: Transfer::Hlg,
..ColorSpace::BT709_TV
};
assert!(hlg.is_hdr());
assert!(ColorSpace::BT2020_PQ.is_hdr() && ColorSpace::BT2020_HLG.is_hdr());
assert_eq!(
ColorSpace::BT2020_HLG.to_string(),
"bt2020nc/bt2020/arib-std-b67/tv"
);
let m = HdrMetadata::P3_D65_1000.with_peak(600.0);
assert_eq!((m.max_cll, m.max_fall), (600, 400));
assert!(!ColorSpace::BT709_RGB.is_hdr());
}
}