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}