screencapturekit/output/pixel_buffer.rs
1//! Pixel buffer wrapper with RAII lock guards
2//!
3//! Provides safe access to `CVPixelBuffer` and `IOSurface` with automatic locking/unlocking.
4//! The lock guard pattern ensures buffers are always properly unlocked, even in case of panics.
5//!
6//! # Examples
7//!
8//! ```
9//! use screencapturekit::prelude::*;
10//! use screencapturekit::output::{CVImageBufferLockExt, PixelBufferLockFlags};
11//!
12//! # fn example() -> SCResult<()> {
13//! // Create a test pixel buffer
14//! let buffer = screencapturekit::cm::CVPixelBuffer::create(100, 100, 0x42475241)
15//! .map_err(|_| SCError::internal_error("Failed to create buffer"))?;
16//!
17//! // Lock for reading (automatically unlocks on drop)
18//! let guard = buffer.lock(PixelBufferLockFlags::ReadOnly)?;
19//!
20//! // Access pixel data
21//! let width = guard.width();
22//! let height = guard.height();
23//! let pixels = guard.as_slice();
24//!
25//! println!("Got {}x{} frame with {} bytes", width, height, pixels.len());
26//!
27//! // Buffer automatically unlocked here when guard drops
28//! # Ok(())
29//! # }
30//! # example().unwrap();
31//! ```
32
33use std::ffi::c_void;
34use std::io::{self, Read, Seek, SeekFrom};
35use std::ops::Deref;
36use std::ptr::NonNull;
37
38/// Lock options for pixel buffer access
39///
40/// Specifies the access mode when locking a pixel buffer.
41#[repr(u64)]
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43pub enum PixelBufferLockFlags {
44 /// Read-only access to the buffer
45 ///
46 /// Use this flag when you only need to read pixel data, not modify it.
47 /// This is the most common use case for screen capture.
48 ReadOnly = 0x0000_0001,
49}
50
51impl PixelBufferLockFlags {
52 /// Convert to u64 representation
53 pub const fn as_u64(self) -> u64 {
54 self as u64
55 }
56
57 /// Convert to u32 representation (used by FFI)
58 pub const fn as_u32(self) -> u32 {
59 self as u32
60 }
61}
62
63/// A guard that provides access to locked pixel buffer memory
64///
65/// This guard implements RAII (Resource Acquisition Is Initialization) pattern.
66/// The buffer is automatically unlocked when this guard is dropped, ensuring
67/// proper cleanup even if an error occurs or panic happens.
68///
69/// # Safety
70///
71/// The guard ensures:
72/// - Buffer is locked before access
73/// - Buffer is unlocked when guard is dropped
74/// - No data races (single owner at a time)
75/// - Memory is valid for the guard's lifetime
76///
77/// # Examples
78///
79/// ```
80/// use screencapturekit::output::{CVImageBufferLockExt, PixelBufferLockFlags};
81/// # use screencapturekit::cm::CVPixelBuffer;
82/// # use screencapturekit::prelude::*;
83///
84/// # fn example() -> SCResult<()> {
85/// // Create a test buffer
86/// let buffer = CVPixelBuffer::create(100, 100, 0x42475241)
87/// .map_err(|_| SCError::internal_error("Failed to create buffer"))?;
88///
89/// // Lock the buffer
90/// let guard = buffer.lock(PixelBufferLockFlags::ReadOnly)?;
91///
92/// // Access properties
93/// println!("Width: {}", guard.width());
94/// println!("Height: {}", guard.height());
95/// println!("Bytes per row: {}", guard.bytes_per_row());
96///
97/// // Access raw pixel data
98/// let pixels: &[u8] = guard.as_slice();
99/// println!("Total bytes: {}", pixels.len());
100///
101/// // Buffer automatically unlocked here
102/// # Ok(())
103/// # }
104/// # example().unwrap();
105/// ```
106pub struct PixelBufferLockGuard<'a> {
107 buffer_ptr: *mut c_void,
108 base_address: NonNull<u8>,
109 width: usize,
110 height: usize,
111 bytes_per_row: usize,
112 flags: PixelBufferLockFlags,
113 _phantom: std::marker::PhantomData<&'a ()>,
114}
115
116impl PixelBufferLockGuard<'_> {
117 /// Create a new lock guard (used internally)
118 pub(crate) unsafe fn new(
119 buffer_ptr: *mut c_void,
120 flags: PixelBufferLockFlags,
121 ) -> Result<Self, i32> {
122 // Lock the buffer
123 let result = crate::cm::ffi::cv_pixel_buffer_lock_base_address(buffer_ptr, flags.as_u32());
124 if result != 0 {
125 return Err(result);
126 }
127
128 // Get buffer info
129 let base_ptr = crate::cm::ffi::cv_pixel_buffer_get_base_address(buffer_ptr);
130 let base_address = NonNull::new(base_ptr.cast::<u8>()).ok_or(-1)?;
131 let width = crate::cm::ffi::cv_pixel_buffer_get_width(buffer_ptr) as usize;
132 let height = crate::cm::ffi::cv_pixel_buffer_get_height(buffer_ptr) as usize;
133 let bytes_per_row = crate::cm::ffi::cv_pixel_buffer_get_bytes_per_row(buffer_ptr) as usize;
134
135 Ok(Self {
136 buffer_ptr,
137 base_address,
138 width,
139 height,
140 bytes_per_row,
141 flags,
142 _phantom: std::marker::PhantomData,
143 })
144 }
145
146 /// Get the width in pixels
147 pub const fn width(&self) -> usize {
148 self.width
149 }
150
151 /// Get the height in pixels
152 pub const fn height(&self) -> usize {
153 self.height
154 }
155
156 /// Get bytes per row
157 pub const fn bytes_per_row(&self) -> usize {
158 self.bytes_per_row
159 }
160
161 /// Get raw pointer to buffer data
162 pub fn as_ptr(&self) -> *const u8 {
163 self.base_address.as_ptr()
164 }
165
166 /// Get mutable raw pointer to buffer data (for read-only locks, use carefully)
167 pub fn as_mut_ptr(&mut self) -> *mut u8 {
168 self.base_address.as_ptr()
169 }
170
171 /// Get buffer data as a byte slice
172 pub fn as_slice(&self) -> &[u8] {
173 unsafe {
174 std::slice::from_raw_parts(self.base_address.as_ptr(), self.height * self.bytes_per_row)
175 }
176 }
177
178 /// Get a specific row as a slice
179 pub fn row(&self, row_index: usize) -> Option<&[u8]> {
180 if row_index >= self.height {
181 return None;
182 }
183 unsafe {
184 let row_ptr = self
185 .base_address
186 .as_ptr()
187 .add(row_index * self.bytes_per_row);
188 Some(std::slice::from_raw_parts(row_ptr, self.bytes_per_row))
189 }
190 }
191
192 /// Access buffer with a cursor for reading bytes
193 ///
194 /// Returns a standard `std::io::Cursor` over the buffer data.
195 /// The cursor implements `Read` and `Seek` traits for convenient data access.
196 ///
197 /// # Examples
198 ///
199 /// ```
200 /// use std::io::{Read, Seek, SeekFrom};
201 /// use screencapturekit::output::{CVImageBufferLockExt, PixelBufferLockFlags};
202 /// # use screencapturekit::cm::CVPixelBuffer;
203 /// # use screencapturekit::prelude::*;
204 ///
205 /// # fn example() -> SCResult<()> {
206 /// let buffer = CVPixelBuffer::create(100, 100, 0x42475241)
207 /// .map_err(|_| SCError::internal_error("Failed to create buffer"))?;
208 /// let guard = buffer.lock(PixelBufferLockFlags::ReadOnly)?;
209 ///
210 /// let mut cursor = guard.cursor();
211 ///
212 /// // Read using standard Read trait
213 /// let mut pixel = [0u8; 4];
214 /// cursor.read_exact(&mut pixel).unwrap();
215 ///
216 /// // Seek using standard Seek trait
217 /// cursor.seek(SeekFrom::Start(0)).unwrap();
218 /// # Ok(())
219 /// # }
220 /// # example().unwrap();
221 /// ```
222 pub fn cursor(&self) -> io::Cursor<&[u8]> {
223 io::Cursor::new(self.as_slice())
224 }
225
226 /// Access buffer with a cursor using a closure (for backward compatibility)
227 ///
228 /// This method is provided for backward compatibility. Consider using
229 /// `cursor()` directly for more flexibility.
230 pub fn with_cursor<F, R>(&self, f: F) -> R
231 where
232 F: FnOnce(io::Cursor<&[u8]>) -> R,
233 {
234 f(self.cursor())
235 }
236}
237
238impl Drop for PixelBufferLockGuard<'_> {
239 fn drop(&mut self) {
240 unsafe {
241 crate::cm::ffi::cv_pixel_buffer_unlock_base_address(
242 self.buffer_ptr,
243 self.flags.as_u32(),
244 );
245 }
246 }
247}
248
249impl Deref for PixelBufferLockGuard<'_> {
250 type Target = [u8];
251
252 fn deref(&self) -> &Self::Target {
253 self.as_slice()
254 }
255}
256
257/// Extension trait for `io::Cursor` to add pixel buffer specific operations
258pub trait PixelBufferCursorExt {
259 /// Seek to a specific pixel coordinate (x, y)
260 ///
261 /// Assumes 4 bytes per pixel (BGRA format).
262 ///
263 /// # Errors
264 ///
265 /// Returns an I/O error if the seek operation fails.
266 fn seek_to_pixel(&mut self, x: usize, y: usize, bytes_per_row: usize) -> io::Result<u64>;
267
268 /// Read a single pixel (4 bytes: BGRA)
269 ///
270 /// # Errors
271 ///
272 /// Returns an I/O error if the read operation fails.
273 fn read_pixel(&mut self) -> io::Result<[u8; 4]>;
274}
275
276impl<T: AsRef<[u8]>> PixelBufferCursorExt for io::Cursor<T> {
277 fn seek_to_pixel(&mut self, x: usize, y: usize, bytes_per_row: usize) -> io::Result<u64> {
278 let pos = y * bytes_per_row + x * 4; // 4 bytes per pixel (BGRA)
279 self.seek(SeekFrom::Start(pos as u64))
280 }
281
282 fn read_pixel(&mut self) -> io::Result<[u8; 4]> {
283 let mut pixel = [0u8; 4];
284 self.read_exact(&mut pixel)?;
285 Ok(pixel)
286 }
287}
288
289/// Extension trait for `CVImageBuffer` with lock guards
290/// Extension trait for locking pixel buffers
291pub trait CVImageBufferLockExt {
292 /// Lock the buffer and provide a guard for safe access
293 ///
294 /// # Errors
295 ///
296 /// Returns an `SCError` if the lock operation fails.
297 fn lock(
298 &self,
299 flags: PixelBufferLockFlags,
300 ) -> Result<PixelBufferLockGuard<'_>, crate::error::SCError>;
301}
302
303// Implementation for our CVPixelBuffer
304impl CVImageBufferLockExt for crate::cm::CVPixelBuffer {
305 fn lock(
306 &self,
307 flags: PixelBufferLockFlags,
308 ) -> Result<PixelBufferLockGuard<'_>, crate::error::SCError> {
309 unsafe {
310 PixelBufferLockGuard::new(self.as_ptr(), flags).map_err(|code| {
311 crate::error::SCError::buffer_lock_error(format!(
312 "Failed to lock pixel buffer (error code: {code})"
313 ))
314 })
315 }
316 }
317}