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 32-bit BGRA — the default pixel format for
51 /// streams created via [`crate::stream::configuration::SCStreamConfiguration::new`].
52 ///
53 /// The crate pins this format at construction time so that
54 /// `SCStreamConfiguration::new()` delivers a stable BGRA wire format
55 /// across macOS releases. Without the explicit pin Apple's runtime
56 /// chooses its own default, which on macOS 26 / Apple Silicon is
57 /// `420v` (bi-planar YCbCr) — silently breaking consumers that assume
58 /// packed BGRA samples. See issue #145.
59 #[default]
60 BGRA,
61 /// Packed little endian ARGB2101010 (10-bit color)
62 l10r,
63 /// Two-plane "video" range YCbCr 4:2:0
64 YCbCr_420v,
65 /// Two-plane "full" range YCbCr 4:2:0
66 YCbCr_420f,
67 /// Two-plane "full" range `YCbCr10` 4:4:4 (10-bit)
68 xf44,
69 /// 64-bit RGBA IEEE half-precision float, 16-bit little-endian (HDR)
70 RGhA,
71 /// A pixel format reported by `ScreenCaptureKit` that this crate does not
72 /// model as a named variant. The wrapped [`FourCharCode`] preserves the
73 /// raw four-character code so callers can branch on it explicitly or
74 /// log it for diagnostics.
75 Unknown(FourCharCode),
76}
77
78// `PixelFormat` is `Eq`/`Hash` via its underlying FourCharCode so that
79// `Unknown(known_code)` and the corresponding named variant compare and
80// hash identically. See the type-level docs for the rationale.
81impl PartialEq for PixelFormat {
82 fn eq(&self, other: &Self) -> bool {
83 FourCharCode::from(*self) == FourCharCode::from(*other)
84 }
85}
86
87impl Eq for PixelFormat {}
88
89impl std::hash::Hash for PixelFormat {
90 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
91 FourCharCode::from(*self).hash(state);
92 }
93}
94impl Display for PixelFormat {
95 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
96 let c: FourCharCode = (*self).into();
97 write!(f, "{}", c.display())
98 }
99}
100
101impl From<PixelFormat> for FourCharCode {
102 fn from(val: PixelFormat) -> Self {
103 // Use infallible byte array constructor for compile-time constants
104 match val {
105 PixelFormat::BGRA => Self::from_bytes(*b"BGRA"),
106 PixelFormat::l10r => Self::from_bytes(*b"l10r"),
107 PixelFormat::YCbCr_420v => Self::from_bytes(*b"420v"),
108 PixelFormat::YCbCr_420f => Self::from_bytes(*b"420f"),
109 PixelFormat::xf44 => Self::from_bytes(*b"xf44"),
110 PixelFormat::RGhA => Self::from_bytes(*b"RGhA"),
111 PixelFormat::Unknown(code) => code,
112 }
113 }
114}
115impl From<u32> for PixelFormat {
116 fn from(value: u32) -> Self {
117 // FourCharCode stores u32 directly, no byte conversion needed
118 let c = FourCharCode::from_u32(value);
119 c.into()
120 }
121}
122impl From<FourCharCode> for PixelFormat {
123 fn from(val: FourCharCode) -> Self {
124 match val.display().as_str() {
125 "BGRA" => Self::BGRA,
126 "l10r" => Self::l10r,
127 "420v" => Self::YCbCr_420v,
128 "420f" => Self::YCbCr_420f,
129 "xf44" => Self::xf44,
130 "RGhA" => Self::RGhA,
131 // Preserve the raw code rather than silently coercing to BGRA.
132 // Callers that branched on the format would otherwise misread
133 // YUV/HDR/etc. samples as BGRA.
134 _ => Self::Unknown(val),
135 }
136 }
137}