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