apple_cf/cv/pixel_buffer.rs
1//! `CVPixelBuffer` - Video pixel buffer
2
3use crate::ffi;
4use crate::iosurface::IOSurface;
5use std::fmt;
6use std::io::{self, Read, Seek, SeekFrom};
7
8/// Lock flags for `CVPixelBuffer`
9///
10/// This is a bitmask type matching Apple's `CVPixelBufferLockFlags`.
11///
12/// # Examples
13///
14/// ```
15/// use apple_cf::cv::CVPixelBufferLockFlags;
16///
17/// // Read-only lock
18/// let flags = CVPixelBufferLockFlags::READ_ONLY;
19/// assert!(flags.is_read_only());
20///
21/// // Read-write lock (default)
22/// let flags = CVPixelBufferLockFlags::NONE;
23/// assert!(!flags.is_read_only());
24/// ```
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
26pub struct CVPixelBufferLockFlags(u32);
27
28impl CVPixelBufferLockFlags {
29 /// No special options (read-write lock)
30 pub const NONE: Self = Self(0);
31
32 /// Read-only lock - use when you only need to read data.
33 /// This allows Core Video to keep caches valid.
34 pub const READ_ONLY: Self = Self(0x0000_0001);
35
36 /// Create from a raw u32 value
37 #[must_use]
38 pub const fn from_bits(bits: u32) -> Self {
39 Self(bits)
40 }
41
42 /// Convert to u32 for FFI
43 #[must_use]
44 pub const fn as_u32(self) -> u32 {
45 self.0
46 }
47
48 /// Check if this is a read-only lock
49 #[must_use]
50 pub const fn is_read_only(self) -> bool {
51 (self.0 & Self::READ_ONLY.0) != 0
52 }
53
54 /// Check if no flags are set (read-write lock)
55 #[must_use]
56 pub const fn is_empty(self) -> bool {
57 self.0 == 0
58 }
59}
60
61impl From<CVPixelBufferLockFlags> for u32 {
62 fn from(flags: CVPixelBufferLockFlags) -> Self {
63 flags.0
64 }
65}
66
67#[derive(Debug)]
68pub struct CVPixelBuffer(*mut std::ffi::c_void);
69
70impl PartialEq for CVPixelBuffer {
71 fn eq(&self, other: &Self) -> bool {
72 self.0 == other.0
73 }
74}
75
76impl Eq for CVPixelBuffer {}
77
78impl std::hash::Hash for CVPixelBuffer {
79 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
80 unsafe {
81 let hash_value = ffi::cv_pixel_buffer_hash(self.0);
82 hash_value.hash(state);
83 }
84 }
85}
86
87impl CVPixelBuffer {
88 pub fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
89 if ptr.is_null() {
90 None
91 } else {
92 Some(Self(ptr))
93 }
94 }
95
96 /// # Safety
97 /// The caller must ensure the pointer is a valid `CVPixelBuffer` pointer.
98 pub const unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
99 Self(ptr)
100 }
101
102 #[must_use]
103 pub const fn as_ptr(&self) -> *mut std::ffi::c_void {
104 self.0
105 }
106
107 /// Create a new pixel buffer with the specified dimensions and pixel format
108 ///
109 /// # Arguments
110 ///
111 /// * `width` - Width of the pixel buffer in pixels
112 /// * `height` - Height of the pixel buffer in pixels
113 /// * `pixel_format` - Pixel format type (e.g., 0x42475241 for BGRA)
114 ///
115 /// # Errors
116 ///
117 /// Returns a Core Video error code if the pixel buffer creation fails.
118 ///
119 /// # Examples
120 ///
121 /// ```
122 /// use apple_cf::cv::CVPixelBuffer;
123 ///
124 /// // Create a 1920x1080 BGRA pixel buffer
125 /// let buffer = CVPixelBuffer::create(1920, 1080, 0x42475241)
126 /// .expect("Failed to create pixel buffer");
127 ///
128 /// assert_eq!(buffer.width(), 1920);
129 /// assert_eq!(buffer.height(), 1080);
130 /// assert_eq!(buffer.pixel_format(), 0x42475241);
131 /// ```
132 pub fn create(width: usize, height: usize, pixel_format: u32) -> Result<Self, i32> {
133 unsafe {
134 let mut pixel_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
135 let status =
136 ffi::cv_pixel_buffer_create(width, height, pixel_format, &mut pixel_buffer_ptr);
137
138 if status == 0 && !pixel_buffer_ptr.is_null() {
139 Ok(Self(pixel_buffer_ptr))
140 } else {
141 Err(status)
142 }
143 }
144 }
145
146 /// Create a pixel buffer from existing memory
147 ///
148 /// # Arguments
149 ///
150 /// * `width` - Width of the pixel buffer in pixels
151 /// * `height` - Height of the pixel buffer in pixels
152 /// * `pixel_format` - Pixel format type (e.g., 0x42475241 for BGRA)
153 /// * `base_address` - Pointer to pixel data
154 /// * `bytes_per_row` - Number of bytes per row
155 ///
156 /// # Safety
157 ///
158 /// The caller must ensure that:
159 /// - `base_address` points to valid memory
160 /// - Memory remains valid for the lifetime of the pixel buffer
161 /// - `bytes_per_row` correctly represents the memory layout
162 ///
163 /// # Errors
164 ///
165 /// Returns a Core Video error code if the pixel buffer creation fails.
166 ///
167 /// # Examples
168 ///
169 /// ```
170 /// use apple_cf::cv::CVPixelBuffer;
171 ///
172 /// // Create pixel data (100x100 BGRA image)
173 /// let width = 100;
174 /// let height = 100;
175 /// let bytes_per_pixel = 4; // BGRA
176 /// let bytes_per_row = width * bytes_per_pixel;
177 /// let mut pixel_data = vec![0u8; width * height * bytes_per_pixel];
178 ///
179 /// // Fill with blue color
180 /// for y in 0..height {
181 /// for x in 0..width {
182 /// let offset = y * bytes_per_row + x * bytes_per_pixel;
183 /// pixel_data[offset] = 255; // B
184 /// pixel_data[offset + 1] = 0; // G
185 /// pixel_data[offset + 2] = 0; // R
186 /// pixel_data[offset + 3] = 255; // A
187 /// }
188 /// }
189 ///
190 /// // Create pixel buffer from the data
191 /// let buffer = unsafe {
192 /// CVPixelBuffer::create_with_bytes(
193 /// width,
194 /// height,
195 /// 0x42475241, // BGRA
196 /// pixel_data.as_mut_ptr() as *mut std::ffi::c_void,
197 /// bytes_per_row,
198 /// )
199 /// }.expect("Failed to create pixel buffer");
200 ///
201 /// assert_eq!(buffer.width(), width);
202 /// assert_eq!(buffer.height(), height);
203 /// ```
204 pub unsafe fn create_with_bytes(
205 width: usize,
206 height: usize,
207 pixel_format: u32,
208 base_address: *mut std::ffi::c_void,
209 bytes_per_row: usize,
210 ) -> Result<Self, i32> {
211 let mut pixel_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
212 let status = ffi::cv_pixel_buffer_create_with_bytes(
213 width,
214 height,
215 pixel_format,
216 base_address,
217 bytes_per_row,
218 &mut pixel_buffer_ptr,
219 );
220
221 if status == 0 && !pixel_buffer_ptr.is_null() {
222 Ok(Self(pixel_buffer_ptr))
223 } else {
224 Err(status)
225 }
226 }
227
228 /// Fill the extended pixels of a pixel buffer
229 ///
230 /// This is useful for pixel buffers that have been created with extended pixels
231 /// enabled, to ensure proper edge handling for effects and filters.
232 ///
233 /// # Errors
234 ///
235 /// Returns a Core Video error code if the operation fails.
236 pub fn fill_extended_pixels(&self) -> Result<(), i32> {
237 unsafe {
238 let status = ffi::cv_pixel_buffer_fill_extended_pixels(self.0);
239 if status == 0 {
240 Ok(())
241 } else {
242 Err(status)
243 }
244 }
245 }
246
247 /// Create a pixel buffer with planar bytes
248 ///
249 /// # Safety
250 ///
251 /// The caller must ensure that:
252 /// - `plane_base_addresses` points to valid memory for each plane
253 /// - Memory remains valid for the lifetime of the pixel buffer
254 /// - All plane parameters correctly represent the memory layout
255 ///
256 /// # Errors
257 ///
258 /// Returns a Core Video error code if the pixel buffer creation fails.
259 pub unsafe fn create_with_planar_bytes(
260 width: usize,
261 height: usize,
262 pixel_format: u32,
263 plane_base_addresses: &[*mut std::ffi::c_void],
264 plane_widths: &[usize],
265 plane_heights: &[usize],
266 plane_bytes_per_row: &[usize],
267 ) -> Result<Self, i32> {
268 if plane_base_addresses.len() != plane_widths.len()
269 || plane_widths.len() != plane_heights.len()
270 || plane_heights.len() != plane_bytes_per_row.len()
271 {
272 return Err(-50); // paramErr
273 }
274
275 let mut pixel_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
276 let status = ffi::cv_pixel_buffer_create_with_planar_bytes(
277 width,
278 height,
279 pixel_format,
280 plane_base_addresses.len(),
281 plane_base_addresses.as_ptr(),
282 plane_widths.as_ptr(),
283 plane_heights.as_ptr(),
284 plane_bytes_per_row.as_ptr(),
285 &mut pixel_buffer_ptr,
286 );
287
288 if status == 0 && !pixel_buffer_ptr.is_null() {
289 Ok(Self(pixel_buffer_ptr))
290 } else {
291 Err(status)
292 }
293 }
294
295 /// Create a pixel buffer from an `IOSurface`
296 ///
297 /// # Errors
298 ///
299 /// Returns a Core Video error code if the pixel buffer creation fails.
300 pub fn create_with_io_surface(surface: &IOSurface) -> Result<Self, i32> {
301 unsafe {
302 let mut pixel_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
303 let status = ffi::cv_pixel_buffer_create_with_io_surface(
304 surface.as_ptr(),
305 &mut pixel_buffer_ptr,
306 );
307
308 if status == 0 && !pixel_buffer_ptr.is_null() {
309 Ok(Self(pixel_buffer_ptr))
310 } else {
311 Err(status)
312 }
313 }
314 }
315
316 /// Get the Core Foundation type ID for `CVPixelBuffer`
317 #[must_use]
318 pub fn type_id() -> usize {
319 unsafe { ffi::cv_pixel_buffer_get_type_id() }
320 }
321
322 /// Get the data size of the pixel buffer
323 #[must_use]
324 pub fn data_size(&self) -> usize {
325 unsafe { ffi::cv_pixel_buffer_get_data_size(self.0) }
326 }
327
328 /// Check if the pixel buffer is planar
329 #[must_use]
330 pub fn is_planar(&self) -> bool {
331 unsafe { ffi::cv_pixel_buffer_is_planar(self.0) }
332 }
333
334 /// Get the number of planes in the pixel buffer
335 #[must_use]
336 pub fn plane_count(&self) -> usize {
337 unsafe { ffi::cv_pixel_buffer_get_plane_count(self.0) }
338 }
339
340 /// Get the width of a specific plane
341 #[must_use]
342 pub fn width_of_plane(&self, plane_index: usize) -> usize {
343 unsafe { ffi::cv_pixel_buffer_get_width_of_plane(self.0, plane_index) }
344 }
345
346 /// Get the height of a specific plane
347 #[must_use]
348 pub fn height_of_plane(&self, plane_index: usize) -> usize {
349 unsafe { ffi::cv_pixel_buffer_get_height_of_plane(self.0, plane_index) }
350 }
351
352 /// Get the base address of a specific plane (internal use only)
353 ///
354 /// # Safety
355 /// Caller must ensure the buffer is locked before accessing the returned pointer.
356 fn base_address_of_plane_raw(&self, plane_index: usize) -> Option<*mut u8> {
357 unsafe {
358 let ptr = ffi::cv_pixel_buffer_get_base_address_of_plane(self.0, plane_index);
359 if ptr.is_null() {
360 None
361 } else {
362 Some(ptr.cast::<u8>())
363 }
364 }
365 }
366
367 /// Get the bytes per row of a specific plane
368 #[must_use]
369 pub fn bytes_per_row_of_plane(&self, plane_index: usize) -> usize {
370 unsafe { ffi::cv_pixel_buffer_get_bytes_per_row_of_plane(self.0, plane_index) }
371 }
372
373 /// Get the extended pixel information (left, right, top, bottom)
374 #[must_use]
375 pub fn extended_pixels(&self) -> (usize, usize, usize, usize) {
376 unsafe {
377 let mut left: usize = 0;
378 let mut right: usize = 0;
379 let mut top: usize = 0;
380 let mut bottom: usize = 0;
381 ffi::cv_pixel_buffer_get_extended_pixels(
382 self.0,
383 &mut left,
384 &mut right,
385 &mut top,
386 &mut bottom,
387 );
388 (left, right, top, bottom)
389 }
390 }
391
392 /// Check if the pixel buffer is backed by an `IOSurface`
393 #[must_use]
394 pub fn is_backed_by_io_surface(&self) -> bool {
395 self.io_surface().is_some()
396 }
397
398 /// Get the width of the pixel buffer in pixels
399 #[must_use]
400 pub fn width(&self) -> usize {
401 unsafe { ffi::cv_pixel_buffer_get_width(self.0) }
402 }
403
404 #[must_use]
405 pub fn height(&self) -> usize {
406 unsafe { ffi::cv_pixel_buffer_get_height(self.0) }
407 }
408
409 #[must_use]
410 pub fn pixel_format(&self) -> u32 {
411 unsafe { ffi::cv_pixel_buffer_get_pixel_format_type(self.0) }
412 }
413
414 #[must_use]
415 pub fn bytes_per_row(&self) -> usize {
416 unsafe { ffi::cv_pixel_buffer_get_bytes_per_row(self.0) }
417 }
418
419 /// Lock the base address for raw access
420 ///
421 /// # Errors
422 ///
423 /// Returns a Core Video error code if the lock operation fails.
424 pub fn lock_raw(&self, flags: CVPixelBufferLockFlags) -> Result<(), i32> {
425 unsafe {
426 let result = ffi::cv_pixel_buffer_lock_base_address(self.0, flags.as_u32());
427 if result == 0 {
428 Ok(())
429 } else {
430 Err(result)
431 }
432 }
433 }
434
435 /// Unlock the base address after raw access
436 ///
437 /// # Errors
438 ///
439 /// Returns a Core Video error code if the unlock operation fails.
440 pub fn unlock_raw(&self, flags: CVPixelBufferLockFlags) -> Result<(), i32> {
441 unsafe {
442 let result = ffi::cv_pixel_buffer_unlock_base_address(self.0, flags.as_u32());
443 if result == 0 {
444 Ok(())
445 } else {
446 Err(result)
447 }
448 }
449 }
450
451 /// Get the base address (internal use only)
452 ///
453 /// # Safety
454 /// Caller must ensure the buffer is locked before accessing the returned pointer.
455 fn base_address_raw(&self) -> Option<*mut u8> {
456 unsafe {
457 let ptr = ffi::cv_pixel_buffer_get_base_address(self.0);
458 if ptr.is_null() {
459 None
460 } else {
461 Some(ptr.cast::<u8>())
462 }
463 }
464 }
465
466 /// Get the `IOSurface` backing this pixel buffer
467 #[must_use]
468 pub fn io_surface(&self) -> Option<IOSurface> {
469 unsafe {
470 let ptr = ffi::cv_pixel_buffer_get_io_surface(self.0);
471 IOSurface::from_raw(ptr)
472 }
473 }
474
475 /// Lock the base address and return a guard for RAII-style access
476 ///
477 /// # Arguments
478 ///
479 /// * `flags` - Lock flags (use `CVPixelBufferLockFlags::READ_ONLY` for read-only access)
480 ///
481 /// # Errors
482 ///
483 /// Returns a Core Video error code if the lock operation fails.
484 ///
485 /// # Examples
486 ///
487 /// ```no_run
488 /// use apple_cf::cv::{CVPixelBuffer, CVPixelBufferLockFlags};
489 ///
490 /// fn read_buffer(buffer: &CVPixelBuffer) {
491 /// let guard = buffer.lock(CVPixelBufferLockFlags::READ_ONLY).unwrap();
492 /// let data = guard.as_slice();
493 /// println!("Buffer has {} bytes", data.len());
494 /// // Buffer automatically unlocked when guard drops
495 /// }
496 /// ```
497 pub fn lock(&self, flags: CVPixelBufferLockFlags) -> Result<CVPixelBufferLockGuard<'_>, i32> {
498 self.lock_raw(flags)?;
499 Ok(CVPixelBufferLockGuard {
500 buffer: self,
501 flags,
502 })
503 }
504
505 /// Lock the base address for read-only access
506 ///
507 /// This is a convenience method equivalent to `lock(CVPixelBufferLockFlags::READ_ONLY)`.
508 ///
509 /// # Errors
510 ///
511 /// Returns a Core Video error code if the lock operation fails.
512 pub fn lock_read_only(&self) -> Result<CVPixelBufferLockGuard<'_>, i32> {
513 self.lock(CVPixelBufferLockFlags::READ_ONLY)
514 }
515
516 /// Lock the base address for read-write access
517 ///
518 /// This is a convenience method equivalent to `lock(CVPixelBufferLockFlags::NONE)`.
519 ///
520 /// # Errors
521 ///
522 /// Returns a Core Video error code if the lock operation fails.
523 pub fn lock_read_write(&self) -> Result<CVPixelBufferLockGuard<'_>, i32> {
524 self.lock(CVPixelBufferLockFlags::NONE)
525 }
526}
527
528/// RAII guard for locked `CVPixelBuffer` base address
529pub struct CVPixelBufferLockGuard<'a> {
530 buffer: &'a CVPixelBuffer,
531 flags: CVPixelBufferLockFlags,
532}
533
534impl CVPixelBufferLockGuard<'_> {
535 /// Get the base address of the locked buffer
536 #[must_use]
537 pub fn base_address(&self) -> *const u8 {
538 self.buffer
539 .base_address_raw()
540 .unwrap_or(std::ptr::null_mut())
541 .cast_const()
542 }
543
544 /// Get mutable base address (only valid for read-write locks)
545 ///
546 /// Returns `None` if this is a read-only lock.
547 pub fn base_address_mut(&mut self) -> Option<*mut u8> {
548 if self.flags.is_read_only() {
549 None
550 } else {
551 self.buffer.base_address_raw()
552 }
553 }
554
555 /// Get the base address of a specific plane
556 ///
557 /// For multi-planar formats like YCbCr 4:2:0:
558 /// - Plane 0: Y (luminance) data
559 /// - Plane 1: `CbCr` (chrominance) data
560 ///
561 /// Returns `None` if the plane index is out of bounds.
562 pub fn base_address_of_plane(&self, plane_index: usize) -> Option<*const u8> {
563 self.buffer
564 .base_address_of_plane_raw(plane_index)
565 .map(<*mut u8>::cast_const)
566 }
567
568 /// Get the mutable base address of a specific plane
569 ///
570 /// Returns `None` if this is a read-only lock or the plane index is out of bounds.
571 pub fn base_address_of_plane_mut(&mut self, plane_index: usize) -> Option<*mut u8> {
572 if self.flags.is_read_only() {
573 return None;
574 }
575 self.buffer.base_address_of_plane_raw(plane_index)
576 }
577
578 /// Get the width of the buffer
579 #[must_use]
580 pub fn width(&self) -> usize {
581 self.buffer.width()
582 }
583
584 /// Get the height of the buffer
585 #[must_use]
586 pub fn height(&self) -> usize {
587 self.buffer.height()
588 }
589
590 /// Get bytes per row
591 #[must_use]
592 pub fn bytes_per_row(&self) -> usize {
593 self.buffer.bytes_per_row()
594 }
595
596 /// Get the data size in bytes
597 ///
598 /// This provides API parity with `IOSurfaceLockGuard::data_size()`.
599 #[must_use]
600 pub fn data_size(&self) -> usize {
601 self.buffer.data_size()
602 }
603
604 /// Get the number of planes
605 #[must_use]
606 pub fn plane_count(&self) -> usize {
607 self.buffer.plane_count()
608 }
609
610 /// Get the width of a specific plane
611 #[must_use]
612 pub fn width_of_plane(&self, plane_index: usize) -> usize {
613 self.buffer.width_of_plane(plane_index)
614 }
615
616 /// Get the height of a specific plane
617 #[must_use]
618 pub fn height_of_plane(&self, plane_index: usize) -> usize {
619 self.buffer.height_of_plane(plane_index)
620 }
621
622 /// Get the bytes per row of a specific plane
623 #[must_use]
624 pub fn bytes_per_row_of_plane(&self, plane_index: usize) -> usize {
625 self.buffer.bytes_per_row_of_plane(plane_index)
626 }
627
628 /// Get data as a byte slice
629 ///
630 /// The lock guard ensures the buffer is locked for the lifetime of the slice.
631 #[must_use]
632 pub fn as_slice(&self) -> &[u8] {
633 let ptr = self.base_address();
634 let len = self.buffer.height() * self.buffer.bytes_per_row();
635 if ptr.is_null() || len == 0 {
636 &[]
637 } else {
638 unsafe { std::slice::from_raw_parts(ptr, len) }
639 }
640 }
641
642 /// Get data as a mutable byte slice (only valid for read-write locks)
643 ///
644 /// Returns `None` if this is a read-only lock.
645 pub fn as_slice_mut(&mut self) -> Option<&mut [u8]> {
646 let ptr = self.base_address_mut()?;
647 let len = self.buffer.height() * self.buffer.bytes_per_row();
648 if ptr.is_null() || len == 0 {
649 Some(&mut [])
650 } else {
651 Some(unsafe { std::slice::from_raw_parts_mut(ptr, len) })
652 }
653 }
654
655 /// Get a slice of plane data
656 ///
657 /// Returns the data for a specific plane as a byte slice.
658 ///
659 /// Returns `None` if the plane index is out of bounds.
660 #[must_use]
661 pub fn plane_data(&self, plane_index: usize) -> Option<&[u8]> {
662 let base = self.base_address_of_plane(plane_index)?;
663 let height = self.buffer.height_of_plane(plane_index);
664 let bytes_per_row = self.buffer.bytes_per_row_of_plane(plane_index);
665 Some(unsafe { std::slice::from_raw_parts(base, height * bytes_per_row) })
666 }
667
668 /// Get a specific row from a plane as a slice
669 ///
670 /// Returns `None` if the plane or row index is out of bounds.
671 #[must_use]
672 pub fn plane_row(&self, plane_index: usize, row_index: usize) -> Option<&[u8]> {
673 if !self.buffer.is_planar() || plane_index >= self.buffer.plane_count() {
674 return None;
675 }
676 let height = self.buffer.height_of_plane(plane_index);
677 if row_index >= height {
678 return None;
679 }
680 let base = self.base_address_of_plane(plane_index)?;
681 let bytes_per_row = self.buffer.bytes_per_row_of_plane(plane_index);
682 Some(unsafe {
683 std::slice::from_raw_parts(base.add(row_index * bytes_per_row), bytes_per_row)
684 })
685 }
686
687 /// Get a specific row as a slice
688 ///
689 /// Returns `None` if the row index is out of bounds.
690 #[must_use]
691 pub fn row(&self, row_index: usize) -> Option<&[u8]> {
692 if row_index >= self.height() {
693 return None;
694 }
695 let ptr = self.base_address();
696 if ptr.is_null() {
697 return None;
698 }
699 unsafe {
700 let row_ptr = ptr.add(row_index * self.bytes_per_row());
701 Some(std::slice::from_raw_parts(row_ptr, self.bytes_per_row()))
702 }
703 }
704
705 /// Access buffer with a standard `std::io::Cursor`
706 ///
707 /// Returns a cursor over the buffer data that implements `Read` and `Seek`.
708 ///
709 /// # Examples
710 ///
711 /// ```no_run
712 /// use apple_cf::cv::{CVPixelBuffer, CVPixelBufferLockFlags};
713 /// use std::io::{Read, Seek, SeekFrom};
714 ///
715 /// fn read_buffer(buffer: &CVPixelBuffer) {
716 /// let guard = buffer.lock(CVPixelBufferLockFlags::READ_ONLY).unwrap();
717 /// let mut cursor = guard.cursor();
718 ///
719 /// // Read first 4 bytes
720 /// let mut pixel = [0u8; 4];
721 /// cursor.read_exact(&mut pixel).unwrap();
722 ///
723 /// // Seek to row 10
724 /// let offset = 10 * guard.bytes_per_row();
725 /// cursor.seek(SeekFrom::Start(offset as u64)).unwrap();
726 /// }
727 /// ```
728 #[must_use]
729 pub fn cursor(&self) -> io::Cursor<&[u8]> {
730 io::Cursor::new(self.as_slice())
731 }
732
733 /// Get raw pointer to buffer data
734 #[must_use]
735 pub fn as_ptr(&self) -> *const u8 {
736 self.base_address()
737 }
738
739 /// Get mutable raw pointer to buffer data (only valid for read-write locks)
740 ///
741 /// Returns `None` if this is a read-only lock.
742 pub fn as_mut_ptr(&mut self) -> Option<*mut u8> {
743 self.base_address_mut()
744 }
745
746 /// Check if this is a read-only lock
747 #[must_use]
748 pub const fn is_read_only(&self) -> bool {
749 self.flags.is_read_only()
750 }
751
752 /// Get the lock options
753 #[must_use]
754 pub const fn options(&self) -> CVPixelBufferLockFlags {
755 self.flags
756 }
757
758 /// Get the pixel format
759 #[must_use]
760 pub fn pixel_format(&self) -> u32 {
761 self.buffer.pixel_format()
762 }
763}
764
765impl Drop for CVPixelBufferLockGuard<'_> {
766 fn drop(&mut self) {
767 let _ = self.buffer.unlock_raw(self.flags);
768 }
769}
770
771impl std::fmt::Debug for CVPixelBufferLockGuard<'_> {
772 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
773 f.debug_struct("CVPixelBufferLockGuard")
774 .field("flags", &self.flags)
775 .field("buffer_size", &(self.buffer.width(), self.buffer.height()))
776 .finish()
777 }
778}
779
780impl std::ops::Deref for CVPixelBufferLockGuard<'_> {
781 type Target = [u8];
782
783 fn deref(&self) -> &Self::Target {
784 self.as_slice()
785 }
786}
787
788impl Clone for CVPixelBuffer {
789 fn clone(&self) -> Self {
790 unsafe {
791 let ptr = ffi::cv_pixel_buffer_retain(self.0);
792 Self(ptr)
793 }
794 }
795}
796
797impl Drop for CVPixelBuffer {
798 fn drop(&mut self) {
799 if !self.0.is_null() {
800 unsafe {
801 ffi::cv_pixel_buffer_release(self.0);
802 }
803 }
804 }
805}
806
807// SAFETY: `CVPixelBufferRef` is a Core Foundation type whose retain/release
808// operations are thread-safe. Our wrapper only holds the opaque pointer; pixel
809// data access is gated behind a lock guard, so sharing across threads is safe.
810unsafe impl Send for CVPixelBuffer {}
811unsafe impl Sync for CVPixelBuffer {}
812
813impl fmt::Display for CVPixelBuffer {
814 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
815 write!(
816 f,
817 "CVPixelBuffer({}x{}, format: 0x{:08X})",
818 self.width(),
819 self.height(),
820 self.pixel_format()
821 )
822 }
823}
824
825/// Opaque handle to `CVPixelBufferPool`
826#[repr(transparent)]
827#[derive(Debug)]
828pub struct CVPixelBufferPool(*mut std::ffi::c_void);
829
830impl PartialEq for CVPixelBufferPool {
831 fn eq(&self, other: &Self) -> bool {
832 self.0 == other.0
833 }
834}
835
836impl Eq for CVPixelBufferPool {}
837
838impl std::hash::Hash for CVPixelBufferPool {
839 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
840 unsafe {
841 let hash_value = ffi::cv_pixel_buffer_pool_hash(self.0);
842 hash_value.hash(state);
843 }
844 }
845}
846
847impl CVPixelBufferPool {
848 pub fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
849 if ptr.is_null() {
850 None
851 } else {
852 Some(Self(ptr))
853 }
854 }
855
856 /// # Safety
857 /// The caller must ensure the pointer is a valid `CVPixelBufferPool` pointer.
858 pub const unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
859 Self(ptr)
860 }
861
862 #[must_use]
863 pub const fn as_ptr(&self) -> *mut std::ffi::c_void {
864 self.0
865 }
866
867 /// Create a new pixel buffer pool
868 ///
869 /// # Arguments
870 ///
871 /// * `width` - Width of pixel buffers in the pool
872 /// * `height` - Height of pixel buffers in the pool
873 /// * `pixel_format` - Pixel format type
874 /// * `max_buffers` - Maximum number of buffers in the pool (0 for unlimited)
875 ///
876 /// # Errors
877 ///
878 /// Returns a Core Video error code if the pool creation fails.
879 pub fn create(
880 width: usize,
881 height: usize,
882 pixel_format: u32,
883 max_buffers: usize,
884 ) -> Result<Self, i32> {
885 unsafe {
886 let mut pool_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
887 let status = ffi::cv_pixel_buffer_pool_create(
888 width,
889 height,
890 pixel_format,
891 max_buffers,
892 &mut pool_ptr,
893 );
894
895 if status == 0 && !pool_ptr.is_null() {
896 Ok(Self(pool_ptr))
897 } else {
898 Err(status)
899 }
900 }
901 }
902
903 /// Create a pixel buffer from the pool
904 ///
905 /// # Errors
906 ///
907 /// Returns a Core Video error code if the buffer creation fails.
908 pub fn create_pixel_buffer(&self) -> Result<CVPixelBuffer, i32> {
909 unsafe {
910 let mut pixel_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
911 let status =
912 ffi::cv_pixel_buffer_pool_create_pixel_buffer(self.0, &mut pixel_buffer_ptr);
913
914 if status == 0 && !pixel_buffer_ptr.is_null() {
915 Ok(CVPixelBuffer(pixel_buffer_ptr))
916 } else {
917 Err(status)
918 }
919 }
920 }
921
922 /// Flush the pixel buffer pool
923 ///
924 /// Releases all available pixel buffers in the pool
925 pub fn flush(&self) {
926 unsafe {
927 ffi::cv_pixel_buffer_pool_flush(self.0);
928 }
929 }
930
931 /// Get the Core Foundation type ID for `CVPixelBufferPool`
932 #[must_use]
933 pub fn type_id() -> usize {
934 unsafe { ffi::cv_pixel_buffer_pool_get_type_id() }
935 }
936
937 /// Create a pixel buffer from the pool with auxiliary attributes
938 ///
939 /// This allows specifying additional attributes for the created buffer
940 ///
941 /// # Errors
942 ///
943 /// Returns a Core Video error code if the buffer creation fails.
944 pub fn create_pixel_buffer_with_aux_attributes(
945 &self,
946 aux_attributes: Option<&std::collections::HashMap<String, u32>>,
947 ) -> Result<CVPixelBuffer, i32> {
948 // For now, ignore aux_attributes since we don't have a way to pass them through
949 // In a full implementation, this would convert the HashMap to a CFDictionary
950 let _ = aux_attributes;
951 self.create_pixel_buffer()
952 }
953
954 /// Try to create a pixel buffer from the pool without blocking
955 ///
956 /// Returns None if no buffers are available
957 #[must_use]
958 pub fn try_create_pixel_buffer(&self) -> Option<CVPixelBuffer> {
959 self.create_pixel_buffer().ok()
960 }
961
962 /// Flush the pool with specific options
963 ///
964 /// Releases buffers based on the provided flags
965 pub fn flush_with_options(&self, _flags: u32) {
966 // For now, just call regular flush
967 // In a full implementation, this would pass flags to the Swift side
968 self.flush();
969 }
970
971 /// Check if the pool is empty (no available buffers)
972 ///
973 /// Note: This is an approximation based on whether we can create a buffer
974 #[must_use]
975 pub fn is_empty(&self) -> bool {
976 self.try_create_pixel_buffer().is_none()
977 }
978
979 /// Get the pool attributes
980 ///
981 /// Returns the raw pointer to the `CFDictionary` containing pool attributes
982 #[must_use]
983 pub fn attributes(&self) -> Option<*const std::ffi::c_void> {
984 unsafe {
985 let ptr = ffi::cv_pixel_buffer_pool_get_attributes(self.0);
986 if ptr.is_null() {
987 None
988 } else {
989 Some(ptr)
990 }
991 }
992 }
993
994 /// Get the pixel buffer attributes
995 ///
996 /// Returns the raw pointer to the `CFDictionary` containing pixel buffer attributes
997 #[must_use]
998 pub fn pixel_buffer_attributes(&self) -> Option<*const std::ffi::c_void> {
999 unsafe {
1000 let ptr = ffi::cv_pixel_buffer_pool_get_pixel_buffer_attributes(self.0);
1001 if ptr.is_null() {
1002 None
1003 } else {
1004 Some(ptr)
1005 }
1006 }
1007 }
1008}
1009
1010impl Clone for CVPixelBufferPool {
1011 fn clone(&self) -> Self {
1012 unsafe {
1013 let ptr = ffi::cv_pixel_buffer_pool_retain(self.0);
1014 Self(ptr)
1015 }
1016 }
1017}
1018
1019impl Drop for CVPixelBufferPool {
1020 fn drop(&mut self) {
1021 if !self.0.is_null() {
1022 unsafe {
1023 ffi::cv_pixel_buffer_pool_release(self.0);
1024 }
1025 }
1026 }
1027}
1028
1029// SAFETY: `CVPixelBufferPoolRef` is a Core Foundation type whose retain/release
1030// operations are thread-safe. Pool creation and pixel-buffer allocation use
1031// Apple's thread-safe primitives.
1032unsafe impl Send for CVPixelBufferPool {}
1033unsafe impl Sync for CVPixelBufferPool {}
1034
1035impl fmt::Display for CVPixelBufferPool {
1036 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1037 write!(f, "CVPixelBufferPool")
1038 }
1039}
1040
1041/// Extension trait for `io::Cursor` to add pixel buffer specific operations
1042pub trait PixelBufferCursorExt {
1043 /// Seek to a specific pixel coordinate (x, y)
1044 ///
1045 /// Assumes 4 bytes per pixel (BGRA format).
1046 ///
1047 /// # Errors
1048 ///
1049 /// Returns an I/O error if the seek operation fails.
1050 fn seek_to_pixel(&mut self, x: usize, y: usize, bytes_per_row: usize) -> io::Result<u64>;
1051
1052 /// Read a single pixel (4 bytes: BGRA)
1053 ///
1054 /// # Errors
1055 ///
1056 /// Returns an I/O error if the read operation fails.
1057 fn read_pixel(&mut self) -> io::Result<[u8; 4]>;
1058}
1059
1060impl<T: AsRef<[u8]>> PixelBufferCursorExt for io::Cursor<T> {
1061 fn seek_to_pixel(&mut self, x: usize, y: usize, bytes_per_row: usize) -> io::Result<u64> {
1062 let pos = y * bytes_per_row + x * 4; // 4 bytes per pixel (BGRA)
1063 self.seek(SeekFrom::Start(pos as u64))
1064 }
1065
1066 fn read_pixel(&mut self) -> io::Result<[u8; 4]> {
1067 let mut pixel = [0u8; 4];
1068 self.read_exact(&mut pixel)?;
1069 Ok(pixel)
1070 }
1071}