use edgefirst_tensor::{
ColorEncoding, ColorRange, ColorSpace, ColorTransfer, Colorimetry, TensorDyn,
};
const HD_THRESHOLD: usize = 720;
pub(crate) fn effective_colorimetry(t: &TensorDyn) -> Colorimetry {
resolve_colorimetry(t.colorimetry(), t.height())
}
pub(crate) fn resolve_colorimetry(
colorimetry: Option<Colorimetry>,
height: Option<usize>,
) -> Colorimetry {
let hd = height.map(|h| h >= HD_THRESHOLD).unwrap_or(true);
let src = colorimetry.unwrap_or_default();
Colorimetry {
space: src.space.or(Some(if hd {
ColorSpace::Bt709
} else {
ColorSpace::Smpte170m
})),
transfer: src.transfer.or(Some(ColorTransfer::Bt709)),
encoding: src.encoding.or(Some(if hd {
ColorEncoding::Bt709
} else {
ColorEncoding::Bt601
})),
range: src.range.or(Some(ColorRange::Limited)),
}
}
pub(crate) fn yuv_matrix(e: ColorEncoding) -> yuv::YuvStandardMatrix {
match e {
ColorEncoding::Bt601 => yuv::YuvStandardMatrix::Bt601,
ColorEncoding::Bt709 => yuv::YuvStandardMatrix::Bt709,
ColorEncoding::Bt2020 => yuv::YuvStandardMatrix::Bt2020,
_ => yuv::YuvStandardMatrix::Bt709,
}
}
pub(crate) fn yuv_range(r: ColorRange) -> yuv::YuvRange {
match r {
ColorRange::Full => yuv::YuvRange::Full,
ColorRange::Limited => yuv::YuvRange::Limited,
_ => yuv::YuvRange::Limited,
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct YuvToRgbCoeffs {
pub y_offset: f32,
pub y_scale: f32,
pub c_vr: f32,
pub c_ug: f32,
pub c_vg: f32,
pub c_ub: f32,
}
pub(crate) fn yuv_to_rgb_coeffs(encoding: ColorEncoding, range: ColorRange) -> YuvToRgbCoeffs {
let w = encoding.luma_weights();
let (kr, kb) = (w.kr as f32, w.kb as f32);
let kg = 1.0 - kr - kb;
let s = range.scaling();
let y_offset = s.y_offset as f32 / 255.0;
let y_scale = 255.0 / s.y_swing as f32;
let c_scale = 255.0 / s.c_swing as f32;
YuvToRgbCoeffs {
y_offset,
y_scale,
c_vr: 2.0 * (1.0 - kr) * c_scale,
c_ub: 2.0 * (1.0 - kb) * c_scale,
c_ug: (2.0 * (1.0 - kb) * kb / kg) * c_scale,
c_vg: (2.0 * (1.0 - kr) * kr / kg) * c_scale,
}
}
#[cfg(test)]
mod tests {
use super::*;
use edgefirst_tensor::{ColorEncoding, ColorRange, Colorimetry, DType, PixelFormat, TensorDyn};
#[test]
fn heuristic_hd_is_709_limited_sd_is_601_limited() {
let hd = TensorDyn::image(1280, 720, PixelFormat::Nv12, DType::U8, None).unwrap();
let r = effective_colorimetry(&hd);
assert_eq!(r.encoding, Some(ColorEncoding::Bt709));
assert_eq!(r.range, Some(ColorRange::Limited));
let sd = TensorDyn::image(640, 480, PixelFormat::Nv12, DType::U8, None).unwrap();
let r = effective_colorimetry(&sd);
assert_eq!(r.encoding, Some(ColorEncoding::Bt601));
assert_eq!(r.range, Some(ColorRange::Limited));
}
#[test]
fn specified_axes_win_only_missing_filled() {
let mut t = TensorDyn::image(1280, 720, PixelFormat::Nv12, DType::U8, None).unwrap();
t.set_colorimetry(Some(Colorimetry::default().with_range(ColorRange::Full)));
let r = effective_colorimetry(&t);
assert_eq!(r.range, Some(ColorRange::Full)); assert_eq!(r.encoding, Some(ColorEncoding::Bt709)); }
#[test]
fn resolver_does_not_mutate_tensor() {
let t = TensorDyn::image(1280, 720, PixelFormat::Nv12, DType::U8, None).unwrap();
let _ = effective_colorimetry(&t);
assert_eq!(t.colorimetry(), None); }
#[test]
fn yuv_to_rgb_coeffs_match_reference_values() {
fn close(a: f32, b: f32, what: &str) {
assert!((a - b).abs() < 2e-3, "{what}: {a} vs expected {b}");
}
let c = yuv_to_rgb_coeffs(ColorEncoding::Bt601, ColorRange::Limited);
close(c.y_offset, 16.0 / 255.0, "601L y_offset");
close(c.y_scale, 255.0 / 219.0, "601L y_scale");
close(c.c_vr, 1.596, "601L c_vr");
close(c.c_ub, 2.017, "601L c_ub");
close(c.c_ug, 0.391, "601L c_ug");
close(c.c_vg, 0.813, "601L c_vg");
let c = yuv_to_rgb_coeffs(ColorEncoding::Bt709, ColorRange::Limited);
close(c.c_vr, 1.793, "709L c_vr");
close(c.c_ub, 2.113, "709L c_ub");
close(c.c_ug, 0.213, "709L c_ug");
close(c.c_vg, 0.533, "709L c_vg");
let c = yuv_to_rgb_coeffs(ColorEncoding::Bt601, ColorRange::Full);
close(c.y_offset, 0.0, "601F y_offset");
close(c.y_scale, 1.0, "601F y_scale");
close(c.c_vr, 2.0 * (1.0 - 0.299), "601F c_vr");
close(c.c_ub, 2.0 * (1.0 - 0.114), "601F c_ub");
}
#[test]
fn encoding_maps_to_yuv_matrix() {
assert!(matches!(
yuv_matrix(ColorEncoding::Bt601),
yuv::YuvStandardMatrix::Bt601
));
assert!(matches!(
yuv_matrix(ColorEncoding::Bt709),
yuv::YuvStandardMatrix::Bt709
));
assert!(matches!(yuv_range(ColorRange::Full), yuv::YuvRange::Full));
assert!(matches!(
yuv_range(ColorRange::Limited),
yuv::YuvRange::Limited
));
}
}