Skip to main content

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}