screencapturekit/stream/configuration/pixel_format.rs
1//! Pixel format enumeration
2//!
3//! Defines the available pixel formats for captured frames.
4
5use core::fmt;
6use std::fmt::{Display, Formatter};
7
8use crate::utils::four_char_code::FourCharCode;
9
10/// Pixel format for captured video frames
11///
12/// Specifies the layout and encoding of pixel data in captured frames.
13///
14/// This enum is `#[non_exhaustive]`. Apple may add new pixel formats in
15/// future macOS releases; downstream code that exhaustively matches on
16/// `PixelFormat` must include a wildcard arm. Pixel formats this crate
17/// does not yet recognise are surfaced via the [`PixelFormat::Unknown`]
18/// variant rather than being silently coerced to [`PixelFormat::BGRA`]
19/// (which would mislead callers that branch on the format).
20///
21/// # Equality and hashing
22///
23/// `PixelFormat` compares and hashes by its underlying [`FourCharCode`]
24/// rather than by enum variant. This means
25/// `PixelFormat::BGRA == PixelFormat::Unknown(FourCharCode::from_bytes(*b"BGRA"))`
26/// — both round-trip to the same wire-level format — and they hash the
27/// same. Without this normalisation, the user-facing `Unknown(known_code)`
28/// footgun (constructing the variant with a code that already maps to a
29/// named variant) would silently produce two `PixelFormat` values that
30/// represent the same underlying format but compared unequal and indexed
31/// `HashMap`s twice.
32///
33/// # Examples
34///
35/// ```
36/// use screencapturekit::stream::configuration::PixelFormat;
37/// use screencapturekit::FourCharCode;
38///
39/// let format = PixelFormat::BGRA;
40/// println!("Format: {}", format); // Prints "BGRA"
41///
42/// // Equality normalises through FourCharCode:
43/// let synonym = PixelFormat::Unknown(FourCharCode::from_bytes(*b"BGRA"));
44/// assert_eq!(PixelFormat::BGRA, synonym);
45/// ```
46#[allow(non_camel_case_types)]
47#[derive(Debug, Clone, Copy, Default)]
48#[non_exhaustive]
49pub enum PixelFormat {
50 /// Packed little endian ARGB8888 (most common)
51 #[default]
52 BGRA,
53 /// Packed little endian ARGB2101010 (10-bit color)
54 l10r,
55 /// Two-plane "video" range YCbCr 4:2:0
56 YCbCr_420v,
57 /// Two-plane "full" range YCbCr 4:2:0
58 YCbCr_420f,
59 /// Two-plane "full" range `YCbCr10` 4:4:4 (10-bit)
60 xf44,
61 /// 64-bit RGBA IEEE half-precision float, 16-bit little-endian (HDR)
62 RGhA,
63 /// A pixel format reported by `ScreenCaptureKit` that this crate does not
64 /// model as a named variant. The wrapped [`FourCharCode`] preserves the
65 /// raw four-character code so callers can branch on it explicitly or
66 /// log it for diagnostics.
67 Unknown(FourCharCode),
68}
69
70// `PixelFormat` is `Eq`/`Hash` via its underlying FourCharCode so that
71// `Unknown(known_code)` and the corresponding named variant compare and
72// hash identically. See the type-level docs for the rationale.
73impl PartialEq for PixelFormat {
74 fn eq(&self, other: &Self) -> bool {
75 FourCharCode::from(*self) == FourCharCode::from(*other)
76 }
77}
78
79impl Eq for PixelFormat {}
80
81impl std::hash::Hash for PixelFormat {
82 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
83 FourCharCode::from(*self).hash(state);
84 }
85}
86impl Display for PixelFormat {
87 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
88 let c: FourCharCode = (*self).into();
89 write!(f, "{}", c.display())
90 }
91}
92
93impl From<PixelFormat> for FourCharCode {
94 fn from(val: PixelFormat) -> Self {
95 // Use infallible byte array constructor for compile-time constants
96 match val {
97 PixelFormat::BGRA => Self::from_bytes(*b"BGRA"),
98 PixelFormat::l10r => Self::from_bytes(*b"l10r"),
99 PixelFormat::YCbCr_420v => Self::from_bytes(*b"420v"),
100 PixelFormat::YCbCr_420f => Self::from_bytes(*b"420f"),
101 PixelFormat::xf44 => Self::from_bytes(*b"xf44"),
102 PixelFormat::RGhA => Self::from_bytes(*b"RGhA"),
103 PixelFormat::Unknown(code) => code,
104 }
105 }
106}
107impl From<u32> for PixelFormat {
108 fn from(value: u32) -> Self {
109 // FourCharCode stores u32 directly, no byte conversion needed
110 let c = FourCharCode::from_u32(value);
111 c.into()
112 }
113}
114impl From<FourCharCode> for PixelFormat {
115 fn from(val: FourCharCode) -> Self {
116 match val.display().as_str() {
117 "BGRA" => Self::BGRA,
118 "l10r" => Self::l10r,
119 "420v" => Self::YCbCr_420v,
120 "420f" => Self::YCbCr_420f,
121 "xf44" => Self::xf44,
122 "RGhA" => Self::RGhA,
123 // Preserve the raw code rather than silently coercing to BGRA.
124 // Callers that branched on the format would otherwise misread
125 // YUV/HDR/etc. samples as BGRA.
126 _ => Self::Unknown(val),
127 }
128 }
129}