use crate::meta::Colr;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CicpTriple {
pub colour_primaries: u16,
pub transfer_characteristics: u16,
pub matrix_coefficients: u16,
pub full_range: bool,
}
impl CicpTriple {
#[allow(clippy::too_many_arguments)]
pub fn new(
colour_primaries: u16,
transfer_characteristics: u16,
matrix_coefficients: u16,
full_range: bool,
) -> Self {
Self {
colour_primaries,
transfer_characteristics,
matrix_coefficients,
full_range,
}
}
}
impl CicpTriple {
pub(crate) fn to_colr(self) -> oxideav_heif::props::Colr {
oxideav_heif::props::Colr::Nclx {
primaries: self.colour_primaries,
transfer: self.transfer_characteristics,
matrix: self.matrix_coefficients,
full_range: self.full_range,
}
}
pub const UNSPECIFIED: Self = Self {
colour_primaries: 2,
transfer_characteristics: 2,
matrix_coefficients: 2,
full_range: false,
};
pub const ALPHA: Self = Self {
colour_primaries: 2,
transfer_characteristics: 2,
matrix_coefficients: 2,
full_range: true,
};
pub const fn for_alpha() -> Self {
Self::ALPHA
}
pub fn is_identity_matrix(&self) -> bool {
self.matrix_coefficients == 0
}
pub fn is_unspecified(&self) -> bool {
self.colour_primaries == 2
&& self.transfer_characteristics == 2
&& self.matrix_coefficients == 2
}
pub fn has_reserved(&self) -> bool {
is_primaries_reserved(self.colour_primaries)
|| is_transfer_reserved(self.transfer_characteristics)
|| is_matrix_reserved(self.matrix_coefficients)
}
pub fn is_sdr_srgb_bt601_default(&self) -> bool {
self.colour_primaries == 1
&& self.transfer_characteristics == 13
&& self.matrix_coefficients == 6
}
}
pub fn effective_cicp(colr: Option<&Colr>) -> CicpTriple {
match colr {
Some(Colr::Nclx {
colour_primaries,
transfer_characteristics,
matrix_coefficients,
full_range,
}) => CicpTriple {
colour_primaries: *colour_primaries,
transfer_characteristics: *transfer_characteristics,
matrix_coefficients: *matrix_coefficients,
full_range: *full_range,
},
Some(Colr::Icc(_)) | Some(Colr::Unknown(_)) | None => CicpTriple::UNSPECIFIED,
}
}
pub fn primaries_name(code: u16) -> Option<&'static str> {
match code {
1 => Some("BT.709"),
2 => Some("Unspecified"),
4 => Some("BT.470 System M (NTSC 1953)"),
5 => Some("BT.470 System B/G (PAL/SECAM)"),
6 => Some("BT.601 525 (SMPTE 170M)"),
7 => Some("SMPTE 240M"),
8 => Some("Generic film (illuminant C)"),
9 => Some("BT.2020 / BT.2100"),
10 => Some("SMPTE ST 428-1 (CIE 1931 XYZ)"),
11 => Some("SMPTE RP 431-2 (DCI P3)"),
12 => Some("SMPTE EG 432-1 (Display P3)"),
22 => Some("EBU Tech. 3213-E"),
_ => None,
}
}
pub fn transfer_name(code: u16) -> Option<&'static str> {
match code {
1 => Some("BT.709"),
2 => Some("Unspecified"),
4 => Some("BT.470 System M (gamma 2.2)"),
5 => Some("BT.470 System B/G (gamma 2.8)"),
6 => Some("BT.601"),
7 => Some("SMPTE 240M"),
8 => Some("Linear"),
9 => Some("Logarithmic 100:1"),
10 => Some("Logarithmic 100*sqrt(10):1"),
11 => Some("IEC 61966-2-4 (xvYCC)"),
12 => Some("BT.1361"),
13 => Some("sRGB / IEC 61966-2-1"),
14 => Some("BT.2020 10-bit"),
15 => Some("BT.2020 12-bit"),
16 => Some("SMPTE ST 2084 (PQ)"),
17 => Some("SMPTE ST 428-1"),
18 => Some("ARIB STD-B67 (HLG)"),
_ => None,
}
}
pub fn matrix_name(code: u16) -> Option<&'static str> {
match code {
0 => Some("Identity (RGB / YCgCo)"),
1 => Some("BT.709"),
2 => Some("Unspecified"),
4 => Some("FCC 73.682 (US NTSC)"),
5 => Some("BT.470 System B/G"),
6 => Some("BT.601"),
7 => Some("SMPTE 240M"),
8 => Some("YCgCo"),
9 => Some("BT.2020 NCL"),
10 => Some("BT.2020 CL"),
11 => Some("SMPTE ST 2085"),
12 => Some("Chromaticity-derived NCL"),
13 => Some("Chromaticity-derived CL"),
14 => Some("ICtCp"),
_ => None,
}
}
pub fn is_primaries_reserved(code: u16) -> bool {
matches!(code, 3 | 13..=21 | 23..=255) || code > 255
}
pub fn is_transfer_reserved(code: u16) -> bool {
matches!(code, 3 | 19..=255) || code > 255
}
pub fn is_matrix_reserved(code: u16) -> bool {
matches!(code, 3 | 15..=255) || code > 255
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn effective_cicp_none_is_unspecified() {
let triple = effective_cicp(None);
assert_eq!(triple, CicpTriple::UNSPECIFIED);
assert!(triple.is_unspecified());
assert!(!triple.full_range);
}
#[test]
fn effective_cicp_nclx_passes_through() {
let colr = Colr::Nclx {
colour_primaries: 1,
transfer_characteristics: 13,
matrix_coefficients: 6,
full_range: false,
};
let triple = effective_cicp(Some(&colr));
assert_eq!(triple.colour_primaries, 1);
assert_eq!(triple.transfer_characteristics, 13);
assert_eq!(triple.matrix_coefficients, 6);
assert!(!triple.full_range);
assert!(triple.is_sdr_srgb_bt601_default());
}
#[test]
fn effective_cicp_icc_falls_back_to_unspecified() {
let colr = Colr::Icc(vec![0u8; 16]);
let triple = effective_cicp(Some(&colr));
assert_eq!(triple, CicpTriple::UNSPECIFIED);
}
#[test]
fn effective_cicp_unknown_colr_type_unspecified() {
let colr = Colr::Unknown([b'x'; 4]);
let triple = effective_cicp(Some(&colr));
assert_eq!(triple, CicpTriple::UNSPECIFIED);
}
#[test]
fn primaries_names_cover_canonical_triples() {
assert_eq!(primaries_name(1), Some("BT.709"));
assert_eq!(primaries_name(2), Some("Unspecified"));
assert_eq!(primaries_name(9), Some("BT.2020 / BT.2100"));
assert_eq!(primaries_name(12), Some("SMPTE EG 432-1 (Display P3)"));
assert_eq!(primaries_name(3), None);
assert_eq!(primaries_name(20), None);
assert_eq!(primaries_name(255), None);
}
#[test]
fn transfer_names_cover_hdr_curves() {
assert_eq!(transfer_name(13), Some("sRGB / IEC 61966-2-1"));
assert_eq!(transfer_name(16), Some("SMPTE ST 2084 (PQ)"));
assert_eq!(transfer_name(18), Some("ARIB STD-B67 (HLG)"));
assert_eq!(transfer_name(2), Some("Unspecified"));
assert_eq!(transfer_name(3), None); }
#[test]
fn matrix_names_cover_ycbcr_and_identity() {
assert_eq!(matrix_name(0), Some("Identity (RGB / YCgCo)"));
assert_eq!(matrix_name(1), Some("BT.709"));
assert_eq!(matrix_name(6), Some("BT.601"));
assert_eq!(matrix_name(9), Some("BT.2020 NCL"));
assert_eq!(matrix_name(14), Some("ICtCp"));
assert_eq!(matrix_name(15), None); }
#[test]
fn reserved_predicates_match_h273_ranges() {
assert!(is_primaries_reserved(3));
assert!(is_primaries_reserved(13));
assert!(is_primaries_reserved(21));
assert!(is_primaries_reserved(23));
assert!(is_primaries_reserved(100));
assert!(is_primaries_reserved(256));
assert!(!is_primaries_reserved(1));
assert!(!is_primaries_reserved(2));
assert!(!is_primaries_reserved(9));
assert!(!is_primaries_reserved(12));
assert!(!is_primaries_reserved(22));
assert!(is_transfer_reserved(3));
assert!(is_transfer_reserved(19));
assert!(is_transfer_reserved(100));
assert!(is_transfer_reserved(256));
assert!(!is_transfer_reserved(1));
assert!(!is_transfer_reserved(13));
assert!(!is_transfer_reserved(18));
assert!(is_matrix_reserved(3));
assert!(is_matrix_reserved(15));
assert!(is_matrix_reserved(100));
assert!(is_matrix_reserved(256));
assert!(!is_matrix_reserved(0));
assert!(!is_matrix_reserved(1));
assert!(!is_matrix_reserved(14));
}
#[test]
fn is_unspecified_requires_all_three() {
assert!(CicpTriple::UNSPECIFIED.is_unspecified());
let partial = CicpTriple {
colour_primaries: 1,
transfer_characteristics: 2,
matrix_coefficients: 2,
full_range: false,
};
assert!(!partial.is_unspecified());
}
#[test]
fn identity_matrix_flagged_for_rgb_advanced_profile() {
let rgb = CicpTriple {
colour_primaries: 1,
transfer_characteristics: 13,
matrix_coefficients: 0,
full_range: true,
};
assert!(rgb.is_identity_matrix());
assert!(!rgb.is_unspecified());
let bt709 = CicpTriple {
colour_primaries: 1,
transfer_characteristics: 1,
matrix_coefficients: 1,
full_range: false,
};
assert!(!bt709.is_identity_matrix());
}
#[test]
fn alpha_cicp_carries_full_range() {
let alpha = CicpTriple::for_alpha();
assert_eq!(alpha, CicpTriple::ALPHA);
assert!(alpha.full_range);
assert_eq!(alpha.colour_primaries, 2);
assert_eq!(alpha.transfer_characteristics, 2);
assert_eq!(alpha.matrix_coefficients, 2);
}
#[test]
fn has_reserved_detects_each_axis() {
let mut t = CicpTriple {
colour_primaries: 3, transfer_characteristics: 13,
matrix_coefficients: 6,
full_range: false,
};
assert!(t.has_reserved());
t.colour_primaries = 1;
t.transfer_characteristics = 19; assert!(t.has_reserved());
t.transfer_characteristics = 13;
t.matrix_coefficients = 15; assert!(t.has_reserved());
t.matrix_coefficients = 6;
assert!(!t.has_reserved());
}
#[test]
fn sdr_srgb_default_triple_check_strict() {
let exact = CicpTriple {
colour_primaries: 1,
transfer_characteristics: 13,
matrix_coefficients: 6,
full_range: false,
};
assert!(exact.is_sdr_srgb_bt601_default());
let close = CicpTriple {
colour_primaries: 1,
transfer_characteristics: 13,
matrix_coefficients: 5,
full_range: false,
};
assert!(!close.is_sdr_srgb_bt601_default());
}
}