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;
crate::ef_vocabulary! {
#[non_exhaustive]
#[derive(Serialize, Deserialize)]
pub enum ColorSpace {
Bt709 = 1, "bt709", BT709,
Bt2020 = 2, "bt2020", BT2020,
Srgb = 3, "srgb", SRGB,
Smpte170m = 4, "smpte170m", SMPTE170M,
}
#[doc(hidden)]
pub mod color_space_wire;
}
crate::ef_vocabulary! {
#[non_exhaustive]
#[derive(Serialize, Deserialize)]
pub enum ColorTransfer {
Bt709 = 1, "bt709", BT709,
Srgb = 2, "srgb", SRGB,
Pq = 3, "pq", PQ,
Hlg = 4, "hlg", HLG,
Linear = 5, "linear", LINEAR,
}
#[doc(hidden)]
pub mod color_transfer_wire;
}
crate::ef_vocabulary! {
#[non_exhaustive]
#[derive(Serialize, Deserialize)]
pub enum ColorEncoding {
Bt601 = 1, "bt601", BT601,
Bt709 = 2, "bt709", BT709,
Bt2020 = 3, "bt2020", BT2020,
}
#[doc(hidden)]
pub mod color_encoding_wire;
}
crate::ef_vocabulary! {
#[non_exhaustive]
#[derive(Serialize, Deserialize)]
pub enum ColorRange {
Full = 1, "full", FULL,
Limited = 2, "limited", LIMITED,
}
#[doc(hidden)]
pub mod color_range_wire;
}
impl ColorSpace {
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 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 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 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,
is_rgb_or_hsv: bool,
) -> 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(is_rgb_or_hsv, 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(is_rgb_or_hsv: bool, colorspace: u32) -> Option<ColorRange> {
if is_rgb_or_hsv {
return Some(ColorRange::Full);
}
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
}
pub fn pack(&self) -> u32 {
let space = self.space.map_or(0, ColorSpace::code);
let transfer = self.transfer.map_or(0, ColorTransfer::code);
let encoding = self.encoding.map_or(0, ColorEncoding::code);
let range = self.range.map_or(0, ColorRange::code);
space | (transfer << 8) | (encoding << 16) | (range << 24)
}
pub fn unpack(bits: u32) -> Self {
let space = ColorSpace::from_code(bits & 0xFF);
let transfer = ColorTransfer::from_code((bits >> 8) & 0xFF);
let encoding = ColorEncoding::from_code((bits >> 16) & 0xFF);
let range = ColorRange::from_code((bits >> 24) & 0xFF);
Colorimetry {
space,
transfer,
encoding,
range,
}
}
}
#[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, false); 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, false); assert_eq!(d, Colorimetry::default()); }
#[test]
fn from_v4l2_default_enc_quant_derive_from_colorspace() {
let jpeg = Colorimetry::from_v4l2(7, 0, 0, 0, false);
assert_eq!(jpeg.encoding, Some(ColorEncoding::Bt601));
assert_eq!(jpeg.range, Some(ColorRange::Full));
let rec709 = Colorimetry::from_v4l2(3, 0, 0, 0, false);
assert_eq!(rec709.encoding, Some(ColorEncoding::Bt709));
assert_eq!(rec709.range, Some(ColorRange::Limited));
let explicit = Colorimetry::from_v4l2(7, 0, 2, 2, false); assert_eq!(explicit.encoding, Some(ColorEncoding::Bt709));
assert_eq!(explicit.range, Some(ColorRange::Limited));
let unknown_enc = Colorimetry::from_v4l2(7, 0, 99, 0, false);
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_and_from_str_code_are_inverses_for_every_variant() {
for v in [
ColorSpace::Bt709,
ColorSpace::Bt2020,
ColorSpace::Srgb,
ColorSpace::Smpte170m,
] {
assert_eq!(ColorSpace::from_str_code(v.as_str()), Some(v), "{v:?}");
}
for v in [
ColorTransfer::Bt709,
ColorTransfer::Srgb,
ColorTransfer::Pq,
ColorTransfer::Hlg,
ColorTransfer::Linear,
] {
assert_eq!(ColorTransfer::from_str_code(v.as_str()), Some(v), "{v:?}");
}
for v in [
ColorEncoding::Bt601,
ColorEncoding::Bt709,
ColorEncoding::Bt2020,
] {
assert_eq!(ColorEncoding::from_str_code(v.as_str()), Some(v), "{v:?}");
}
for v in [ColorRange::Full, ColorRange::Limited] {
assert_eq!(ColorRange::from_str_code(v.as_str()), Some(v), "{v:?}");
}
}
#[test]
fn an_unknown_or_empty_label_is_unspecified_not_an_error() {
assert_eq!(ColorSpace::from_str_code(""), None);
assert_eq!(ColorSpace::from_str_code("not-a-colour-space"), None);
assert_eq!(
ColorRange::from_str_code("FULL"),
None,
"labels are case-sensitive"
);
}
#[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");
}
#[test]
fn pack_unpack_round_trips() {
assert_eq!(
Colorimetry::unpack(Colorimetry::default().pack()),
Colorimetry::default()
);
assert_eq!(Colorimetry::default().pack(), 0);
let jfif = Colorimetry::jfif();
assert_eq!(Colorimetry::unpack(jfif.pack()), jfif);
let full = Colorimetry {
space: Some(ColorSpace::Bt2020),
transfer: Some(ColorTransfer::Hlg),
encoding: Some(ColorEncoding::Bt2020),
range: Some(ColorRange::Limited),
};
assert_eq!(Colorimetry::unpack(full.pack()), full);
}
#[test]
fn unpack_maps_unrecognised_codes_to_none() {
let bits = 0xFFu32 | (0xFFu32 << 8) | (0xFFu32 << 16) | (0xFFu32 << 24);
assert_eq!(Colorimetry::unpack(bits), Colorimetry::default());
}
const SPACES: [(ColorSpace, u32); 4] = [
(ColorSpace::Bt709, 1),
(ColorSpace::Bt2020, 2),
(ColorSpace::Srgb, 3),
(ColorSpace::Smpte170m, 4),
];
const TRANSFERS: [(ColorTransfer, u32); 5] = [
(ColorTransfer::Bt709, 1),
(ColorTransfer::Srgb, 2),
(ColorTransfer::Pq, 3),
(ColorTransfer::Hlg, 4),
(ColorTransfer::Linear, 5),
];
const ENCODINGS: [(ColorEncoding, u32); 3] = [
(ColorEncoding::Bt601, 1),
(ColorEncoding::Bt709, 2),
(ColorEncoding::Bt2020, 3),
];
const RANGES: [(ColorRange, u32); 2] = [(ColorRange::Full, 1), (ColorRange::Limited, 2)];
#[test]
fn pack_places_each_axis_code_in_its_own_byte() {
assert_eq!(SPACES.len(), ColorSpace::all().len());
assert_eq!(TRANSFERS.len(), ColorTransfer::all().len());
assert_eq!(ENCODINGS.len(), ColorEncoding::all().len());
assert_eq!(RANGES.len(), ColorRange::all().len());
for (s, code) in SPACES {
let c = Colorimetry::default().with_space(s);
assert_eq!(c.pack(), code, "{s:?}");
assert_eq!(Colorimetry::unpack(code), c, "{s:?}");
}
for (t, code) in TRANSFERS {
let c = Colorimetry::default().with_transfer(t);
assert_eq!(c.pack(), code << 8, "{t:?}");
assert_eq!(Colorimetry::unpack(code << 8), c, "{t:?}");
}
for (e, code) in ENCODINGS {
let c = Colorimetry::default().with_encoding(e);
assert_eq!(c.pack(), code << 16, "{e:?}");
assert_eq!(Colorimetry::unpack(code << 16), c, "{e:?}");
}
for (r, code) in RANGES {
let c = Colorimetry::default().with_range(r);
assert_eq!(c.pack(), code << 24, "{r:?}");
assert_eq!(Colorimetry::unpack(code << 24), c, "{r:?}");
}
}
#[test]
fn unpack_decodes_a_fully_populated_word() {
assert_eq!(
Colorimetry::unpack(0x02_02_05_04),
Colorimetry {
space: Some(ColorSpace::Smpte170m),
transfer: Some(ColorTransfer::Linear),
encoding: Some(ColorEncoding::Bt709),
range: Some(ColorRange::Limited),
}
);
}
#[test]
fn every_axis_combination_round_trips_through_pack() {
for (s, _) in SPACES {
for (t, _) in TRANSFERS {
for (e, _) in ENCODINGS {
for (r, _) in RANGES {
let c = Colorimetry {
space: Some(s),
transfer: Some(t),
encoding: Some(e),
range: Some(r),
};
assert_eq!(Colorimetry::unpack(c.pack()), c);
}
}
}
}
}
#[test]
fn builders_set_exactly_their_own_axis() {
let c = Colorimetry::default()
.with_space(ColorSpace::Bt2020)
.with_transfer(ColorTransfer::Pq)
.with_encoding(ColorEncoding::Bt2020)
.with_range(ColorRange::Full);
assert_eq!(
c,
Colorimetry {
space: Some(ColorSpace::Bt2020),
transfer: Some(ColorTransfer::Pq),
encoding: Some(ColorEncoding::Bt2020),
range: Some(ColorRange::Full),
}
);
let base = Colorimetry::jfif();
assert_eq!(
base.with_space(ColorSpace::Bt709),
Colorimetry {
space: Some(ColorSpace::Bt709),
..base
}
);
assert_eq!(
base.with_transfer(ColorTransfer::Hlg),
Colorimetry {
transfer: Some(ColorTransfer::Hlg),
..base
}
);
assert_eq!(
base.with_encoding(ColorEncoding::Bt709),
Colorimetry {
encoding: Some(ColorEncoding::Bt709),
..base
}
);
assert_eq!(
base.with_range(ColorRange::Limited),
Colorimetry {
range: Some(ColorRange::Limited),
..base
}
);
}
#[test]
fn from_v4l2_derives_encoding_and_range_for_every_recognised_yuv_colorspace() {
use ColorEncoding as E;
use ColorRange as R;
let table = [
(V4L2_COLORSPACE_SMPTE170M, E::Bt601, R::Limited),
(V4L2_COLORSPACE_REC709, E::Bt709, R::Limited),
(V4L2_COLORSPACE_470_SYSTEM_M, E::Bt601, R::Limited),
(V4L2_COLORSPACE_470_SYSTEM_BG, E::Bt601, R::Limited),
(V4L2_COLORSPACE_JPEG, E::Bt601, R::Full),
(V4L2_COLORSPACE_SRGB, E::Bt601, R::Limited),
(V4L2_COLORSPACE_BT2020, E::Bt2020, R::Limited),
];
for (colorspace, encoding, range) in table {
let c = Colorimetry::from_v4l2(colorspace, 0, 0, 0, false);
assert_eq!(c.encoding, Some(encoding), "colorspace {colorspace}");
assert_eq!(c.range, Some(range), "colorspace {colorspace}");
}
for colorspace in [0, 2, 4, 9, 11, 99] {
let c = Colorimetry::from_v4l2(colorspace, 0, 0, 0, false);
assert_eq!(
(c.encoding, c.range),
(None, None),
"colorspace {colorspace}"
);
}
}
const ALL_V4L2_COLORSPACES: [u32; 14] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 99];
fn kernel_default_range(is_rgb_or_hsv: bool, colorspace: u32) -> ColorRange {
if is_rgb_or_hsv || colorspace == V4L2_COLORSPACE_JPEG {
ColorRange::Full
} else {
ColorRange::Limited
}
}
#[test]
fn from_v4l2_quantization_matches_the_kernel_for_every_colorspace_format_and_quant() {
for colorspace in ALL_V4L2_COLORSPACES {
let recognised = ColorSpace::from_v4l2(colorspace).is_some();
for is_rgb_or_hsv in [false, true] {
for quant in [
V4L2_QUANTIZATION_DEFAULT,
V4L2_QUANTIZATION_FULL_RANGE,
V4L2_QUANTIZATION_LIM_RANGE,
99,
] {
let range =
Colorimetry::from_v4l2(colorspace, 0, 0, quant, is_rgb_or_hsv).range;
let expected = match quant {
V4L2_QUANTIZATION_FULL_RANGE => Some(ColorRange::Full),
V4L2_QUANTIZATION_LIM_RANGE => Some(ColorRange::Limited),
V4L2_QUANTIZATION_DEFAULT if is_rgb_or_hsv || recognised => {
Some(kernel_default_range(is_rgb_or_hsv, colorspace))
}
_ => None,
};
assert_eq!(
range, expected,
"colorspace {colorspace}, rgb/hsv {is_rgb_or_hsv}, quant {quant}"
);
}
}
}
}
#[test]
fn rgb_default_quantization_is_full_and_yuv_follows_the_colorspace() {
let range = |colorspace, is_rgb_or_hsv| {
Colorimetry::from_v4l2(colorspace, 0, 0, V4L2_QUANTIZATION_DEFAULT, is_rgb_or_hsv).range
};
assert_eq!(range(V4L2_COLORSPACE_SRGB, true), Some(ColorRange::Full));
assert_eq!(
range(V4L2_COLORSPACE_SRGB, false),
Some(ColorRange::Limited)
);
assert_eq!(range(V4L2_COLORSPACE_JPEG, false), Some(ColorRange::Full));
assert_eq!(range(0, true), Some(ColorRange::Full));
assert_eq!(range(V4L2_COLORSPACE_REC709, true), Some(ColorRange::Full));
}
#[test]
fn from_v4l2_encoding_matches_the_kernel_for_every_recognised_colorspace() {
for colorspace in ALL_V4L2_COLORSPACES {
for is_rgb_or_hsv in [false, true] {
let encoding = Colorimetry::from_v4l2(colorspace, 0, 0, 0, is_rgb_or_hsv).encoding;
let expected = ColorSpace::from_v4l2(colorspace).map(|_| match colorspace {
V4L2_COLORSPACE_REC709 => ColorEncoding::Bt709,
V4L2_COLORSPACE_BT2020 => ColorEncoding::Bt2020,
_ => ColorEncoding::Bt601,
});
assert_eq!(encoding, expected, "colorspace {colorspace}");
}
}
}
}
#[cfg(all(test, target_os = "linux"))]
mod v4l2_uapi_tests {
use edgefirst_v4l2::uapi;
#[test]
fn colorimetry_constants_match_edgefirst_v4l2() {
assert_eq!(
super::V4L2_COLORSPACE_SMPTE170M,
uapi::V4L2_COLORSPACE_SMPTE170M
);
assert_eq!(super::V4L2_COLORSPACE_REC709, uapi::V4L2_COLORSPACE_REC709);
assert_eq!(
super::V4L2_COLORSPACE_470_SYSTEM_M,
uapi::V4L2_COLORSPACE_470_SYSTEM_M
);
assert_eq!(
super::V4L2_COLORSPACE_470_SYSTEM_BG,
uapi::V4L2_COLORSPACE_470_SYSTEM_BG
);
assert_eq!(super::V4L2_COLORSPACE_JPEG, uapi::V4L2_COLORSPACE_JPEG);
assert_eq!(super::V4L2_COLORSPACE_SRGB, uapi::V4L2_COLORSPACE_SRGB);
assert_eq!(super::V4L2_COLORSPACE_BT2020, uapi::V4L2_COLORSPACE_BT2020);
assert_eq!(
super::V4L2_XFER_FUNC_709,
u32::from(uapi::V4L2_XFER_FUNC_709)
);
assert_eq!(
super::V4L2_XFER_FUNC_SRGB,
u32::from(uapi::V4L2_XFER_FUNC_SRGB)
);
assert_eq!(
super::V4L2_XFER_FUNC_NONE,
u32::from(uapi::V4L2_XFER_FUNC_NONE)
);
assert_eq!(
super::V4L2_XFER_FUNC_SMPTE2084,
u32::from(uapi::V4L2_XFER_FUNC_SMPTE2084)
);
assert_eq!(
super::V4L2_YCBCR_ENC_DEFAULT,
u32::from(uapi::V4L2_YCBCR_ENC_DEFAULT)
);
assert_eq!(
super::V4L2_YCBCR_ENC_601,
u32::from(uapi::V4L2_YCBCR_ENC_601)
);
assert_eq!(
super::V4L2_YCBCR_ENC_709,
u32::from(uapi::V4L2_YCBCR_ENC_709)
);
assert_eq!(
super::V4L2_YCBCR_ENC_BT2020,
u32::from(uapi::V4L2_YCBCR_ENC_BT2020)
);
assert_eq!(
super::V4L2_QUANTIZATION_DEFAULT,
u32::from(uapi::V4L2_QUANTIZATION_DEFAULT)
);
assert_eq!(
super::V4L2_QUANTIZATION_FULL_RANGE,
u32::from(uapi::V4L2_QUANTIZATION_FULL_RANGE)
);
assert_eq!(
super::V4L2_QUANTIZATION_LIM_RANGE,
u32::from(uapi::V4L2_QUANTIZATION_LIM_RANGE)
);
}
}