#[cfg(any(feature = "alloc", feature = "std"))]
use crate::Vec;
pub mod tag;
pub mod product_type;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ChromaticityPoint12 {
pub x_raw: u16,
pub y_raw: u16,
}
impl ChromaticityPoint12 {
pub fn x(&self) -> f32 {
self.x_raw as f32 / 4096.0
}
pub fn y(&self) -> f32 {
self.y_raw as f32 / 4096.0
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Chromaticity12 {
pub primary1: ChromaticityPoint12,
pub primary2: ChromaticityPoint12,
pub primary3: ChromaticityPoint12,
pub white: ChromaticityPoint12,
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Default)]
pub struct DisplayParamsV2 {
pub chromaticity: Chromaticity12,
pub color_space_cie1976: bool,
pub max_luminance_full: Option<f32>,
pub max_luminance_10pct: Option<f32>,
pub min_luminance: Option<f32>,
pub luminance_guidance: bool,
pub color_bit_depth: Option<u8>,
pub display_technology: DisplayTechnology,
pub gamma: Option<f32>,
pub scan_orientation: ScanOrientation,
pub audio_external: bool,
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DisplayTechnology {
#[default]
Unspecified,
Amlcd,
Amoled,
#[non_exhaustive]
Other(u8),
}
impl DisplayTechnology {
pub fn from_byte(b: u8) -> Self {
match b {
0x00 => Self::Unspecified,
0x01 => Self::Amlcd,
0x02 => Self::Amoled,
other => Self::Other(other),
}
}
pub fn other(b: u8) -> Option<Self> {
match b {
0x00..=0x02 => None,
x => Some(Self::Other(x)),
}
}
pub fn as_byte(self) -> u8 {
match self {
Self::Unspecified => 0x00,
Self::Amlcd => 0x01,
Self::Amoled => 0x02,
Self::Other(b) => b,
}
}
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ScanOrientation {
#[default]
LeftRightTopBottom,
RightLeftTopBottom,
TopBottomRightLeft,
BottomTopRightLeft,
RightLeftBottomTop,
LeftRightBottomTop,
BottomTopLeftRight,
TopBottomLeftRight,
}
impl ScanOrientation {
pub fn from_bits(b: u8) -> Self {
match b & 0b111 {
0b000 => Self::LeftRightTopBottom,
0b001 => Self::RightLeftTopBottom,
0b010 => Self::TopBottomRightLeft,
0b011 => Self::BottomTopRightLeft,
0b100 => Self::RightLeftBottomTop,
0b101 => Self::LeftRightBottomTop,
0b110 => Self::BottomTopLeftRight,
_ => Self::TopBottomLeftRight,
}
}
pub fn as_bits(self) -> u8 {
match self {
Self::LeftRightTopBottom => 0b000,
Self::RightLeftTopBottom => 0b001,
Self::TopBottomRightLeft => 0b010,
Self::BottomTopRightLeft => 0b011,
Self::RightLeftBottomTop => 0b100,
Self::LeftRightBottomTop => 0b101,
Self::BottomTopLeftRight => 0b110,
Self::TopBottomLeftRight => 0b111,
}
}
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct DynamicTimingRange {
pub min_pixel_clock_khz: u32,
pub max_pixel_clock_khz: u32,
pub min_v_rate_hz: u8,
pub max_v_rate_hz: u16,
pub vrr_supported: bool,
}
bitflags::bitflags! {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ColorDepthsFull: u8 {
const BPC_6 = 0x01;
const BPC_8 = 0x02;
const BPC_10 = 0x04;
const BPC_12 = 0x08;
const BPC_14 = 0x10;
const BPC_16 = 0x20;
}
}
bitflags::bitflags! {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ColorDepthsSubsampled: u8 {
const BPC_8 = 0x01;
const BPC_10 = 0x02;
const BPC_12 = 0x04;
const BPC_14 = 0x08;
const BPC_16 = 0x10;
}
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CustomColorSpaceEotfCombo {
pub color_space: u8,
pub eotf: u8,
}
impl CustomColorSpaceEotfCombo {
pub fn new(color_space: u8, eotf: u8) -> Self {
Self { color_space, eotf }
}
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StereoTimingCodeType {
Dmt,
Vic,
HdmiVic,
Reserved,
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StereoTimingCode {
pub code_type: StereoTimingCodeType,
pub code: u8,
}
impl StereoTimingCode {
pub fn new(code_type: StereoTimingCodeType, code: u8) -> Self {
Self { code_type, code }
}
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct DisplayInterfaceFeatures {
pub color_depth_rgb: ColorDepthsFull,
pub color_depth_ycbcr444: ColorDepthsFull,
pub color_depth_ycbcr422: ColorDepthsSubsampled,
pub color_depth_ycbcr420: ColorDepthsSubsampled,
pub min_ycbcr420_pixel_rate: u8,
pub audio_flags: u8,
pub color_space_eotf_combos: u8,
pub custom_color_space_eotf_combos: [CustomColorSpaceEotfCombo; 7],
pub custom_color_space_eotf_count: u8,
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StereoEye {
Left,
Right,
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DualInterfaceMirroring {
None,
LeftRight,
TopBottom,
Reserved,
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StereoTimingScopeV2 {
ExplicitTimingsOnly,
ExplicitAndListedTimings,
AllListedTimings,
ListedTimingCodesOnly,
}
impl StereoTimingScopeV2 {
pub fn from_revision(revision: u8) -> Self {
match revision >> 6 {
0b00 => StereoTimingScopeV2::ExplicitTimingsOnly,
0b01 => StereoTimingScopeV2::ExplicitAndListedTimings,
0b10 => StereoTimingScopeV2::AllListedTimings,
_ => StereoTimingScopeV2::ListedTimingCodesOnly,
}
}
pub fn has_timing_codes(self) -> bool {
matches!(
self,
StereoTimingScopeV2::ExplicitAndListedTimings
| StereoTimingScopeV2::ListedTimingCodesOnly
)
}
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StereoViewingMethodV2 {
FieldSequential {
eye_on_high_half: StereoEye,
},
SideBySide {
left_half: StereoEye,
},
PixelInterleaved {
pattern: [u8; 8],
},
DualInterface {
eye: StereoEye,
mirroring: DualInterfaceMirroring,
},
MultiView {
view_count: u8,
interleaving_method_code: u8,
},
StackedFrame {
top_half: StereoEye,
},
Proprietary,
Reserved(u8),
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DisplayIdStereoInterfaceV2 {
pub timing_scope: StereoTimingScopeV2,
pub method: StereoViewingMethodV2,
#[cfg(any(feature = "alloc", feature = "std"))]
pub timing_codes: crate::prelude::Vec<StereoTimingCode>,
}
impl Default for DisplayIdStereoInterfaceV2 {
fn default() -> Self {
Self {
timing_scope: StereoTimingScopeV2::ExplicitTimingsOnly,
method: StereoViewingMethodV2::Proprietary,
#[cfg(any(feature = "alloc", feature = "std"))]
timing_codes: crate::prelude::Vec::new(),
}
}
}
impl DisplayIdStereoInterfaceV2 {
pub fn has_timing_codes(&self) -> bool {
self.timing_scope.has_timing_codes()
}
}
#[non_exhaustive]
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct DisplayIdVendorSpecific {
pub oui: [u8; 3],
pub data: Vec<u8>,
}
#[non_exhaustive]
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub struct DisplayIdCapabilities {
pub version: u8,
pub product_type: u8,
pub manufacturer_oui: Option<[u8; 3]>,
pub display_params_v2: Option<DisplayParamsV2>,
pub dynamic_timing_range: Option<DynamicTimingRange>,
pub interface_features: Option<DisplayInterfaceFeatures>,
pub stereo_interface_v2: Option<DisplayIdStereoInterfaceV2>,
pub container_id: Option<[u8; 16]>,
pub vendor_specific: Vec<DisplayIdVendorSpecific>,
}
#[cfg(any(feature = "alloc", feature = "std"))]
impl DisplayIdCapabilities {
pub fn new(version: u8, product_type: u8) -> Self {
Self {
version,
product_type,
manufacturer_oui: None,
display_params_v2: None,
dynamic_timing_range: None,
interface_features: None,
stereo_interface_v2: None,
container_id: None,
vendor_specific: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chromaticity_point_normalises_raw_to_unit_interval() {
let white_d65 = ChromaticityPoint12 {
x_raw: 1294, y_raw: 1347, };
assert!((white_d65.x() - 0.31591797).abs() < 1e-6);
assert!((white_d65.y() - 0.32885742).abs() < 1e-6);
}
#[test]
fn chromaticity_point_endpoints() {
let zero = ChromaticityPoint12::default();
assert_eq!(zero.x(), 0.0);
assert_eq!(zero.y(), 0.0);
let max = ChromaticityPoint12 {
x_raw: 4095,
y_raw: 4095,
};
assert!(max.x() < 1.0);
assert!(max.y() < 1.0);
}
#[test]
fn display_technology_decodes_known_bytes() {
assert_eq!(
DisplayTechnology::from_byte(0x00),
DisplayTechnology::Unspecified
);
assert_eq!(DisplayTechnology::from_byte(0x01), DisplayTechnology::Amlcd);
assert_eq!(
DisplayTechnology::from_byte(0x02),
DisplayTechnology::Amoled
);
}
#[test]
fn display_technology_preserves_unknown_bytes() {
assert_eq!(
DisplayTechnology::from_byte(0x42),
DisplayTechnology::Other(0x42)
);
assert_eq!(DisplayTechnology::Other(0x42).as_byte(), 0x42);
}
#[test]
fn display_technology_round_trips() {
for b in [0x00u8, 0x01, 0x02, 0x55, 0xFF] {
let t = DisplayTechnology::from_byte(b);
assert_eq!(t.as_byte(), b);
assert_eq!(DisplayTechnology::from_byte(t.as_byte()), t);
}
}
#[test]
fn display_technology_other_rejects_canonical_bytes() {
assert_eq!(DisplayTechnology::other(0x00), None);
assert_eq!(DisplayTechnology::other(0x01), None);
assert_eq!(DisplayTechnology::other(0x02), None);
}
#[test]
fn display_technology_other_accepts_reserved_bytes() {
assert_eq!(
DisplayTechnology::other(0x42),
Some(DisplayTechnology::Other(0x42))
);
assert_eq!(
DisplayTechnology::other(0xFF),
Some(DisplayTechnology::Other(0xFF))
);
}
#[test]
fn scan_orientation_round_trips_all_eight_codes() {
for bits in 0u8..8 {
let orient = ScanOrientation::from_bits(bits);
assert_eq!(orient.as_bits(), bits);
}
}
#[test]
fn scan_orientation_ignores_upper_bits() {
assert_eq!(
ScanOrientation::from_bits(0b1111_1000),
ScanOrientation::LeftRightTopBottom
);
assert_eq!(
ScanOrientation::from_bits(0b1111_1111),
ScanOrientation::TopBottomLeftRight
);
}
#[test]
fn defaults_match_raster_convention() {
assert_eq!(DisplayTechnology::default(), DisplayTechnology::Unspecified);
assert_eq!(
ScanOrientation::default(),
ScanOrientation::LeftRightTopBottom
);
}
#[test]
fn color_depths_full_bit_layout() {
let depths = ColorDepthsFull::from_bits_truncate(0b0010_1010);
assert!(depths.contains(ColorDepthsFull::BPC_8));
assert!(depths.contains(ColorDepthsFull::BPC_12));
assert!(depths.contains(ColorDepthsFull::BPC_16));
assert!(!depths.contains(ColorDepthsFull::BPC_6));
assert!(!depths.contains(ColorDepthsFull::BPC_10));
assert!(!depths.contains(ColorDepthsFull::BPC_14));
assert_eq!(depths.bits(), 0b0010_1010);
}
#[test]
fn color_depths_subsampled_bit_layout() {
let depths = ColorDepthsSubsampled::from_bits_truncate(0b0001_0101);
assert!(depths.contains(ColorDepthsSubsampled::BPC_8));
assert!(depths.contains(ColorDepthsSubsampled::BPC_12));
assert!(depths.contains(ColorDepthsSubsampled::BPC_16));
assert!(!depths.contains(ColorDepthsSubsampled::BPC_10));
assert!(!depths.contains(ColorDepthsSubsampled::BPC_14));
assert_eq!(depths.bits(), 0b0001_0101);
}
#[test]
fn color_depths_default_is_empty() {
assert!(ColorDepthsFull::default().is_empty());
assert!(ColorDepthsSubsampled::default().is_empty());
assert!(
DisplayInterfaceFeatures::default()
.color_depth_rgb
.is_empty()
);
}
#[test]
fn color_depths_subsampled_truncates_reserved_bits() {
let depths = ColorDepthsSubsampled::from_bits_truncate(0b0011_1111);
assert_eq!(depths.bits(), 0b0001_1111);
}
}