#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u16)]
pub enum Primaries {
Bt709 = 1,
Unspecified = 2,
Bt470M = 4,
Bt470Bg = 5,
Bt601 = 6,
Smpte240 = 7,
GenericFilm = 8,
Bt2020 = 9,
Xyz = 10,
Smpte431 = 11,
Smpte432 = 12,
Ebu3213 = 22,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u16)]
pub enum TransferFunction {
Bt709 = 1,
Unspecified = 2,
Bt470M = 4,
Bt470Bg = 5,
Bt601 = 6,
Smpte240 = 7,
Linear = 8,
Log100 = 9,
Log100sqrt10 = 10,
Iec61966 = 11,
Bt1361 = 12,
Srgb = 13,
Bt202010bit = 14,
Bt202012bit = 15,
Smpte2084 = 16,
Smpte428 = 17,
Hlg = 18,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u16)]
pub enum MatrixCoefficients {
Identity = 0,
Bt709 = 1,
Unspecified = 2,
Fcc = 4,
Bt470Bg = 5,
Smpte170m = 6,
Smpte240m = 7,
YCgCo = 8,
Bt2020Ncl = 9,
Bt2020Cl = 10,
Smpte2085 = 11,
ChromaticityDerivedNcl = 12,
ChromaticityDerivedCl = 13,
ICtCp = 14,
IPtC2 = 15,
YCgCoRe = 16,
YCgCoRo = 17,
}
impl Primaries {
pub fn from_u16(v: u16) -> Self {
match v {
1 => Primaries::Bt709,
4 => Primaries::Bt470M,
5 => Primaries::Bt470Bg,
6 => Primaries::Bt601,
7 => Primaries::Smpte240,
8 => Primaries::GenericFilm,
9 => Primaries::Bt2020,
10 => Primaries::Xyz,
11 => Primaries::Smpte431,
12 => Primaries::Smpte432,
22 => Primaries::Ebu3213,
_ => Primaries::Unspecified,
}
}
}
impl TransferFunction {
pub fn from_u16(v: u16) -> Self {
match v {
1 => TransferFunction::Bt709,
4 => TransferFunction::Bt470M,
5 => TransferFunction::Bt470Bg,
6 => TransferFunction::Bt601,
7 => TransferFunction::Smpte240,
8 => TransferFunction::Linear,
9 => TransferFunction::Log100,
10 => TransferFunction::Log100sqrt10,
11 => TransferFunction::Iec61966,
12 => TransferFunction::Bt1361,
13 => TransferFunction::Srgb,
14 => TransferFunction::Bt202010bit,
15 => TransferFunction::Bt202012bit,
16 => TransferFunction::Smpte2084,
17 => TransferFunction::Smpte428,
18 => TransferFunction::Hlg,
_ => TransferFunction::Unspecified,
}
}
}
impl MatrixCoefficients {
pub fn from_u16(v: u16) -> Self {
match v {
0 => MatrixCoefficients::Identity,
1 => MatrixCoefficients::Bt709,
4 => MatrixCoefficients::Fcc,
5 => MatrixCoefficients::Bt470Bg,
6 => MatrixCoefficients::Smpte170m,
7 => MatrixCoefficients::Smpte240m,
8 => MatrixCoefficients::YCgCo,
9 => MatrixCoefficients::Bt2020Ncl,
10 => MatrixCoefficients::Bt2020Cl,
11 => MatrixCoefficients::Smpte2085,
12 => MatrixCoefficients::ChromaticityDerivedNcl,
13 => MatrixCoefficients::ChromaticityDerivedCl,
14 => MatrixCoefficients::ICtCp,
15 => MatrixCoefficients::IPtC2,
16 => MatrixCoefficients::YCgCoRe,
17 => MatrixCoefficients::YCgCoRo,
_ => MatrixCoefficients::Unspecified,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Cicp {
pub primaries: Primaries,
pub transfer: TransferFunction,
pub matrix: MatrixCoefficients,
pub full_range: bool,
}
impl Cicp {
pub const fn garnetash_default() -> Self {
Cicp {
primaries: Primaries::Bt709,
transfer: TransferFunction::Srgb,
matrix: MatrixCoefficients::Smpte170m,
full_range: true,
}
}
pub const fn srgb() -> Self {
Cicp {
primaries: Primaries::Bt709,
transfer: TransferFunction::Srgb,
matrix: MatrixCoefficients::Bt709,
full_range: true,
}
}
pub const fn bt709() -> Self {
Cicp {
primaries: Primaries::Bt709,
transfer: TransferFunction::Bt709,
matrix: MatrixCoefficients::Bt709,
full_range: true,
}
}
pub const fn bt2020_pq() -> Self {
Cicp {
primaries: Primaries::Bt2020,
transfer: TransferFunction::Smpte2084,
matrix: MatrixCoefficients::Bt2020Ncl,
full_range: true,
}
}
pub const fn unspecified() -> Self {
Cicp {
primaries: Primaries::Unspecified,
transfer: TransferFunction::Unspecified,
matrix: MatrixCoefficients::Unspecified,
full_range: true,
}
}
pub(crate) fn nclx_payload(&self) -> Vec<u8> {
let mut p = Vec::with_capacity(11);
p.extend_from_slice(b"nclx");
p.extend_from_slice(&(self.primaries as u16).to_be_bytes());
p.extend_from_slice(&(self.transfer as u16).to_be_bytes());
p.extend_from_slice(&(self.matrix as u16).to_be_bytes());
p.push(if self.full_range { 0x80 } else { 0x00 });
p
}
pub(crate) fn from_nclx_payload(p: &[u8]) -> Option<Cicp> {
if p.len() < 11 || &p[0..4] != b"nclx" {
return None;
}
Some(Cicp {
primaries: Primaries::from_u16(u16::from_be_bytes([p[4], p[5]])),
transfer: TransferFunction::from_u16(u16::from_be_bytes([p[6], p[7]])),
matrix: MatrixCoefficients::from_u16(u16::from_be_bytes([p[8], p[9]])),
full_range: p[10] & 0x80 != 0,
})
}
}
impl Default for Cicp {
fn default() -> Self {
Cicp::garnetash_default()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ColorMetadata {
pub cicp: Option<Cicp>,
pub icc: Option<Vec<u8>>,
}
impl ColorMetadata {
pub fn cicp(c: Cicp) -> Self {
ColorMetadata {
cicp: Some(c),
icc: None,
}
}
pub fn icc(profile: Vec<u8>) -> Self {
ColorMetadata {
cicp: None,
icc: Some(profile),
}
}
pub fn cicp_and_icc(c: Cicp, profile: Vec<u8>) -> Self {
ColorMetadata {
cicp: Some(c),
icc: Some(profile),
}
}
pub fn with_cicp(mut self, c: Cicp) -> Self {
self.cicp = Some(c);
self
}
pub fn with_icc(mut self, profile: Vec<u8>) -> Self {
self.icc = Some(profile);
self
}
pub(crate) fn effective_cicp(&self) -> Cicp {
self.cicp.unwrap_or_else(Cicp::garnetash_default)
}
pub(crate) fn has_secondary_colr(&self) -> bool {
self.cicp.is_some() && self.icc.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nclx_payload_matches_vui() {
let p = Cicp::garnetash_default().nclx_payload();
assert_eq!(&p[0..4], b"nclx");
assert_eq!(u16::from_be_bytes([p[4], p[5]]), 1); assert_eq!(u16::from_be_bytes([p[6], p[7]]), 13); assert_eq!(u16::from_be_bytes([p[8], p[9]]), 6); assert_eq!(p[10], 0x80); }
#[test]
fn secondary_colr_only_when_both() {
assert!(!ColorMetadata::cicp(Cicp::srgb()).has_secondary_colr());
assert!(!ColorMetadata::icc(vec![0; 4]).has_secondary_colr());
assert!(ColorMetadata::cicp_and_icc(Cicp::srgb(), vec![0; 4]).has_secondary_colr());
}
#[test]
fn matrix_coefficients_extended_round_trip() {
for (v, m) in [
(15u16, MatrixCoefficients::IPtC2),
(16, MatrixCoefficients::YCgCoRe),
(17, MatrixCoefficients::YCgCoRo),
] {
assert_eq!(MatrixCoefficients::from_u16(v), m);
assert_eq!(m as u16, v);
}
assert_eq!(
MatrixCoefficients::from_u16(3),
MatrixCoefficients::Unspecified
);
assert_eq!(
MatrixCoefficients::from_u16(99),
MatrixCoefficients::Unspecified
);
}
}