use core::fmt;
use serde::{Deserialize, Serialize};
const V4L2_COLORSPACE_SMPTE170M: u32 = 1;
const V4L2_COLORSPACE_REC709: u32 = 3;
const V4L2_COLORSPACE_470_SYSTEM_M: u32 = 5;
const V4L2_COLORSPACE_470_SYSTEM_BG: u32 = 6;
const V4L2_COLORSPACE_JPEG: u32 = 7;
const V4L2_COLORSPACE_SRGB: u32 = 8;
const V4L2_COLORSPACE_BT2020: u32 = 10;
const V4L2_XFER_FUNC_709: u32 = 1;
const V4L2_XFER_FUNC_SRGB: u32 = 2;
const V4L2_XFER_FUNC_NONE: u32 = 5;
const V4L2_XFER_FUNC_SMPTE2084: u32 = 7;
const V4L2_YCBCR_ENC_DEFAULT: u32 = 0;
const V4L2_YCBCR_ENC_601: u32 = 1;
const V4L2_YCBCR_ENC_709: u32 = 2;
const V4L2_YCBCR_ENC_BT2020: u32 = 6;
const V4L2_QUANTIZATION_DEFAULT: u32 = 0;
const V4L2_QUANTIZATION_FULL_RANGE: u32 = 1;
const V4L2_QUANTIZATION_LIM_RANGE: u32 = 2;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ColorSpace {
Bt709,
Bt2020,
Srgb,
Smpte170m,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ColorTransfer {
Bt709,
Srgb,
Pq,
Hlg,
Linear,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ColorEncoding {
Bt601,
Bt709,
Bt2020,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ColorRange {
Full,
Limited,
}
impl ColorSpace {
pub fn as_str(self) -> &'static str {
match self {
Self::Bt709 => "bt709",
Self::Bt2020 => "bt2020",
Self::Srgb => "srgb",
Self::Smpte170m => "smpte170m",
}
}
pub fn from_v4l2(v: u32) -> Option<Self> {
match v {
V4L2_COLORSPACE_SMPTE170M => Some(Self::Smpte170m),
V4L2_COLORSPACE_REC709 => Some(Self::Bt709),
V4L2_COLORSPACE_470_SYSTEM_M | V4L2_COLORSPACE_470_SYSTEM_BG => Some(Self::Smpte170m),
V4L2_COLORSPACE_JPEG | V4L2_COLORSPACE_SRGB => Some(Self::Srgb),
V4L2_COLORSPACE_BT2020 => Some(Self::Bt2020),
_ => None,
}
}
}
impl ColorTransfer {
pub fn as_str(self) -> &'static str {
match self {
Self::Bt709 => "bt709",
Self::Srgb => "srgb",
Self::Pq => "pq",
Self::Hlg => "hlg",
Self::Linear => "linear",
}
}
pub fn from_v4l2(v: u32) -> Option<Self> {
match v {
V4L2_XFER_FUNC_709 => Some(Self::Bt709),
V4L2_XFER_FUNC_SRGB => Some(Self::Srgb),
V4L2_XFER_FUNC_NONE => Some(Self::Linear),
V4L2_XFER_FUNC_SMPTE2084 => Some(Self::Pq),
_ => None,
}
}
}
impl ColorEncoding {
pub fn as_str(self) -> &'static str {
match self {
Self::Bt601 => "bt601",
Self::Bt709 => "bt709",
Self::Bt2020 => "bt2020",
}
}
pub fn from_v4l2(v: u32) -> Option<Self> {
match v {
V4L2_YCBCR_ENC_601 => Some(Self::Bt601),
V4L2_YCBCR_ENC_709 => Some(Self::Bt709),
V4L2_YCBCR_ENC_BT2020 => Some(Self::Bt2020),
_ => None,
}
}
}
impl ColorRange {
pub fn as_str(self) -> &'static str {
match self {
Self::Full => "full",
Self::Limited => "limited",
}
}
pub fn from_v4l2(v: u32) -> Option<Self> {
match v {
V4L2_QUANTIZATION_FULL_RANGE => Some(Self::Full),
V4L2_QUANTIZATION_LIM_RANGE => Some(Self::Limited),
_ => None,
}
}
}
macro_rules! display_via_as_str {
($($t:ty),*) => {$(
impl fmt::Display for $t {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
)*};
}
display_via_as_str!(ColorSpace, ColorTransfer, ColorEncoding, ColorRange);
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MatrixWeights {
pub kr: f64,
pub kb: f64,
}
impl MatrixWeights {
pub fn kg(self) -> f64 {
1.0 - self.kr - self.kb
}
}
impl ColorEncoding {
pub fn luma_weights(self) -> MatrixWeights {
match self {
Self::Bt601 => MatrixWeights {
kr: 0.299,
kb: 0.114,
},
Self::Bt709 => MatrixWeights {
kr: 0.2126,
kb: 0.0722,
},
Self::Bt2020 => MatrixWeights {
kr: 0.2627,
kb: 0.0593,
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RangeScaling {
pub y_offset: f64,
pub y_swing: f64,
pub c_swing: f64,
}
impl ColorRange {
pub fn scaling(self) -> RangeScaling {
match self {
Self::Full => RangeScaling {
y_offset: 0.0,
y_swing: 255.0,
c_swing: 255.0,
},
Self::Limited => RangeScaling {
y_offset: 16.0,
y_swing: 219.0,
c_swing: 224.0,
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub struct Colorimetry {
pub space: Option<ColorSpace>,
pub transfer: Option<ColorTransfer>,
pub encoding: Option<ColorEncoding>,
pub range: Option<ColorRange>,
}
impl Colorimetry {
pub fn jfif() -> Self {
Self {
space: Some(ColorSpace::Srgb),
transfer: Some(ColorTransfer::Srgb),
encoding: Some(ColorEncoding::Bt601),
range: Some(ColorRange::Full),
}
}
pub fn from_v4l2(colorspace: u32, xfer: u32, ycbcr_enc: u32, quant: u32) -> Self {
let encoding = ColorEncoding::from_v4l2(ycbcr_enc).or_else(|| {
if ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT {
Self::default_encoding_for_colorspace(colorspace)
} else {
None
}
});
let range = ColorRange::from_v4l2(quant).or_else(|| {
if quant == V4L2_QUANTIZATION_DEFAULT {
Self::default_range_for_colorspace(colorspace)
} else {
None
}
});
Self {
space: ColorSpace::from_v4l2(colorspace),
transfer: ColorTransfer::from_v4l2(xfer),
encoding,
range,
}
}
fn default_encoding_for_colorspace(colorspace: u32) -> Option<ColorEncoding> {
match colorspace {
V4L2_COLORSPACE_REC709 => Some(ColorEncoding::Bt709),
V4L2_COLORSPACE_BT2020 => Some(ColorEncoding::Bt2020),
V4L2_COLORSPACE_SMPTE170M
| V4L2_COLORSPACE_470_SYSTEM_M
| V4L2_COLORSPACE_470_SYSTEM_BG
| V4L2_COLORSPACE_JPEG
| V4L2_COLORSPACE_SRGB => Some(ColorEncoding::Bt601),
_ => None,
}
}
fn default_range_for_colorspace(colorspace: u32) -> Option<ColorRange> {
match colorspace {
V4L2_COLORSPACE_JPEG => Some(ColorRange::Full),
V4L2_COLORSPACE_SMPTE170M
| V4L2_COLORSPACE_REC709
| V4L2_COLORSPACE_BT2020
| V4L2_COLORSPACE_470_SYSTEM_M
| V4L2_COLORSPACE_470_SYSTEM_BG
| V4L2_COLORSPACE_SRGB => Some(ColorRange::Limited),
_ => None,
}
}
pub fn with_space(mut self, s: ColorSpace) -> Self {
self.space = Some(s);
self
}
pub fn with_transfer(mut self, t: ColorTransfer) -> Self {
self.transfer = Some(t);
self
}
pub fn with_encoding(mut self, e: ColorEncoding) -> Self {
self.encoding = Some(e);
self
}
pub fn with_range(mut self, r: ColorRange) -> Self {
self.range = Some(r);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_v4l2_known_and_default() {
assert_eq!(ColorEncoding::from_v4l2(1), Some(ColorEncoding::Bt601));
assert_eq!(ColorEncoding::from_v4l2(2), Some(ColorEncoding::Bt709));
assert_eq!(ColorEncoding::from_v4l2(6), Some(ColorEncoding::Bt2020));
assert_eq!(ColorEncoding::from_v4l2(0), None); assert_eq!(ColorEncoding::from_v4l2(3), None); assert_eq!(ColorRange::from_v4l2(1), Some(ColorRange::Full));
assert_eq!(ColorRange::from_v4l2(2), Some(ColorRange::Limited));
assert_eq!(ColorSpace::from_v4l2(7), Some(ColorSpace::Srgb)); assert_eq!(ColorTransfer::from_v4l2(5), Some(ColorTransfer::Linear));
assert_eq!(ColorSpace::from_v4l2(1), Some(ColorSpace::Smpte170m)); assert_eq!(ColorSpace::from_v4l2(3), Some(ColorSpace::Bt709)); assert_eq!(ColorSpace::from_v4l2(10), Some(ColorSpace::Bt2020)); assert_eq!(ColorSpace::from_v4l2(5), Some(ColorSpace::Smpte170m)); assert_eq!(ColorSpace::from_v4l2(6), Some(ColorSpace::Smpte170m));
assert_eq!(ColorTransfer::from_v4l2(1), Some(ColorTransfer::Bt709)); assert_eq!(ColorTransfer::from_v4l2(2), Some(ColorTransfer::Srgb)); assert_eq!(ColorTransfer::from_v4l2(7), Some(ColorTransfer::Pq)); assert_eq!(ColorTransfer::from_v4l2(3), None); assert_eq!(ColorTransfer::from_v4l2(6), None);
for v in 0u32..=10 {
assert_ne!(ColorTransfer::from_v4l2(v), Some(ColorTransfer::Hlg));
}
}
#[test]
fn jfif_is_bt601_full_srgb() {
let c = Colorimetry::jfif();
assert_eq!(c.space, Some(ColorSpace::Srgb));
assert_eq!(c.transfer, Some(ColorTransfer::Srgb));
assert_eq!(c.encoding, Some(ColorEncoding::Bt601));
assert_eq!(c.range, Some(ColorRange::Full));
}
#[test]
fn from_v4l2_struct_maps_all_axes_and_unknown_to_none() {
let c = Colorimetry::from_v4l2(3, 1, 2, 1); assert_eq!(c.space, Some(ColorSpace::Bt709));
assert_eq!(c.transfer, Some(ColorTransfer::Bt709));
assert_eq!(c.encoding, Some(ColorEncoding::Bt709));
assert_eq!(c.range, Some(ColorRange::Full));
let d = Colorimetry::from_v4l2(0, 0, 0, 0); assert_eq!(d, Colorimetry::default()); }
#[test]
fn from_v4l2_default_enc_quant_derive_from_colorspace() {
let jpeg = Colorimetry::from_v4l2(7, 0, 0, 0);
assert_eq!(jpeg.encoding, Some(ColorEncoding::Bt601));
assert_eq!(jpeg.range, Some(ColorRange::Full));
let rec709 = Colorimetry::from_v4l2(3, 0, 0, 0);
assert_eq!(rec709.encoding, Some(ColorEncoding::Bt709));
assert_eq!(rec709.range, Some(ColorRange::Limited));
let explicit = Colorimetry::from_v4l2(7, 0, 2, 2); assert_eq!(explicit.encoding, Some(ColorEncoding::Bt709));
assert_eq!(explicit.range, Some(ColorRange::Limited));
let unknown_enc = Colorimetry::from_v4l2(7, 0, 99, 0);
assert_eq!(unknown_enc.encoding, None);
assert_eq!(unknown_enc.range, Some(ColorRange::Full)); }
#[test]
fn luma_weights_are_the_canonical_bt_constants() {
assert_eq!(
ColorEncoding::Bt601.luma_weights(),
MatrixWeights {
kr: 0.299,
kb: 0.114
}
);
assert_eq!(
ColorEncoding::Bt709.luma_weights(),
MatrixWeights {
kr: 0.2126,
kb: 0.0722
}
);
assert_eq!(
ColorEncoding::Bt2020.luma_weights(),
MatrixWeights {
kr: 0.2627,
kb: 0.0593
}
);
assert!((ColorEncoding::Bt709.luma_weights().kg() - 0.7152).abs() < 1e-9);
}
#[test]
fn range_scaling_is_full_or_studio_swing() {
let full = ColorRange::Full.scaling();
assert_eq!(
full,
RangeScaling {
y_offset: 0.0,
y_swing: 255.0,
c_swing: 255.0
}
);
let limited = ColorRange::Limited.scaling();
assert_eq!(
limited,
RangeScaling {
y_offset: 16.0,
y_swing: 219.0,
c_swing: 224.0
}
);
}
#[test]
fn as_str_matches_schema() {
assert_eq!(ColorEncoding::Bt601.as_str(), "bt601");
assert_eq!(ColorRange::Full.as_str(), "full");
assert_eq!(ColorSpace::Smpte170m.as_str(), "smpte170m");
assert_eq!(ColorTransfer::Pq.as_str(), "pq");
assert_eq!(ColorTransfer::Hlg.as_str(), "hlg");
}
}