Skip to main content

apple_cf/cv/
pixel_buffer.rs

1//! `CVPixelBuffer` - Video pixel buffer
2
3use crate::cf::{AsCFType, CFDictionary, CFNumber, CFString};
4use crate::iosurface::IOSurface;
5use crate::{ffi, raw};
6use std::collections::HashMap;
7use std::fmt;
8use std::io::{self, Read, Seek, SeekFrom};
9use std::sync::Arc;
10
11/// Lock flags for `CVPixelBuffer`
12///
13/// This is a bitmask type matching Apple's `CVPixelBufferLockFlags`.
14///
15/// # Examples
16///
17/// ```
18/// use apple_cf::cv::CVPixelBufferLockFlags;
19///
20/// // Read-only lock
21/// let flags = CVPixelBufferLockFlags::READ_ONLY;
22/// assert!(flags.is_read_only());
23///
24/// // Read-write lock (default)
25/// let flags = CVPixelBufferLockFlags::NONE;
26/// assert!(!flags.is_read_only());
27/// ```
28#[repr(transparent)]
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
30pub struct CVPixelBufferLockFlags(u64);
31
32impl CVPixelBufferLockFlags {
33    /// No special options (read-write lock)
34    pub const NONE: Self = Self(0);
35
36    /// Read-only lock - use when you only need to read data.
37    /// This allows Core Video to keep caches valid.
38    pub const READ_ONLY: Self = Self(0x0000_0001);
39
40    /// Create from a raw `CVOptionFlags` value.
41    #[must_use]
42    pub const fn from_bits(bits: u64) -> Self {
43        Self(bits)
44    }
45
46    /// Return the raw `CVOptionFlags` bits.
47    #[must_use]
48    pub const fn bits(self) -> u64 {
49        self.0
50    }
51
52    /// Check if this is a read-only lock
53    #[must_use]
54    pub const fn is_read_only(self) -> bool {
55        (self.0 & Self::READ_ONLY.0) != 0
56    }
57
58    /// Check if no flags are set (read-write lock)
59    #[must_use]
60    pub const fn is_empty(self) -> bool {
61        self.0 == 0
62    }
63}
64
65impl From<CVPixelBufferLockFlags> for u64 {
66    fn from(flags: CVPixelBufferLockFlags) -> Self {
67        flags.0
68    }
69}
70
71#[derive(Debug)]
72/// Owned wrapper around Apple's `CVPixelBufferRef`.
73pub struct CVPixelBuffer(*mut std::ffi::c_void);
74
75impl PartialEq for CVPixelBuffer {
76    fn eq(&self, other: &Self) -> bool {
77        self.0 == other.0
78    }
79}
80
81impl Eq for CVPixelBuffer {}
82
83impl std::hash::Hash for CVPixelBuffer {
84    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
85        unsafe {
86            let hash_value = ffi::cv_pixel_buffer_hash(self.0);
87            hash_value.hash(state);
88        }
89    }
90}
91
92impl CVPixelBuffer {
93    /// Adopts a +1 retained `CVPixelBufferRef` and returns `None` for null.
94    ///
95    /// # Safety
96    ///
97    /// A non-null `ptr` must be a live `CVPixelBufferRef` of the exact type
98    /// carrying one retain transferred to this wrapper. The caller must not
99    /// release or separately adopt that transferred retain.
100    pub unsafe fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
101        if ptr.is_null() {
102            None
103        } else {
104            Some(Self(ptr))
105        }
106    }
107
108    /// Retains a +0 borrowed `CVPixelBufferRef` and returns an owned wrapper.
109    ///
110    /// # Safety
111    ///
112    /// A non-null `ptr` must be a live `CVPixelBufferRef` of the exact type for
113    /// the duration of the retain call.
114    #[must_use]
115    pub unsafe fn from_raw_borrowed(ptr: *mut std::ffi::c_void) -> Option<Self> {
116        if ptr.is_null() {
117            None
118        } else {
119            let retained = unsafe { ffi::cv_pixel_buffer_retain(ptr) };
120            unsafe { Self::from_raw(retained) }
121        }
122    }
123
124    /// Wraps a raw `CVPixelBufferRef` by taking ownership without retaining it.
125    ///
126    /// # Safety
127    /// `ptr` must be a non-null, live `CVPixelBufferRef` of the exact type
128    /// carrying one retain transferred to this wrapper.
129    pub const unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
130        Self(ptr)
131    }
132
133    /// Borrows the raw +0 `CVPixelBufferRef` while `self` remains alive.
134    #[must_use]
135    pub const fn as_ptr(&self) -> *mut std::ffi::c_void {
136        self.0
137    }
138
139    /// Create a new pixel buffer with the specified dimensions and pixel format
140    ///
141    /// # Arguments
142    ///
143    /// * `width` - Width of the pixel buffer in pixels
144    /// * `height` - Height of the pixel buffer in pixels
145    /// * `pixel_format` - Pixel format type (e.g., 0x42475241 for BGRA)
146    ///
147    /// # Errors
148    ///
149    /// Returns a Core Video error code if the pixel buffer creation fails.
150    ///
151    /// # Examples
152    ///
153    /// ```
154    /// use apple_cf::cv::CVPixelBuffer;
155    ///
156    /// // Create a 1920x1080 BGRA pixel buffer
157    /// let buffer = CVPixelBuffer::create(1920, 1080, 0x42475241)
158    ///     .expect("Failed to create pixel buffer");
159    ///
160    /// assert_eq!(buffer.width(), 1920);
161    /// assert_eq!(buffer.height(), 1080);
162    /// assert_eq!(buffer.pixel_format(), 0x42475241);
163    /// ```
164    pub fn create(width: usize, height: usize, pixel_format: u32) -> Result<Self, i32> {
165        unsafe {
166            let mut pixel_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
167            let status =
168                ffi::cv_pixel_buffer_create(width, height, pixel_format, &mut pixel_buffer_ptr);
169
170            if status == 0 && !pixel_buffer_ptr.is_null() {
171                Ok(Self(pixel_buffer_ptr))
172            } else {
173                Err(status)
174            }
175        }
176    }
177
178    /// Create a pixel buffer from existing memory
179    ///
180    /// # Arguments
181    ///
182    /// * `width` - Width of the pixel buffer in pixels
183    /// * `height` - Height of the pixel buffer in pixels
184    /// * `pixel_format` - Pixel format type (e.g., 0x42475241 for BGRA)
185    /// * `base_address` - Pointer to pixel data
186    /// * `bytes_per_row` - Number of bytes per row
187    ///
188    /// # Safety
189    ///
190    /// The caller must ensure that:
191    /// - `base_address` points to valid memory
192    /// - Memory remains valid for the lifetime of the pixel buffer
193    /// - `bytes_per_row` correctly represents the memory layout
194    ///
195    /// # Errors
196    ///
197    /// Returns a Core Video error code if the pixel buffer creation fails.
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// use apple_cf::cv::CVPixelBuffer;
203    ///
204    /// // Create pixel data (100x100 BGRA image)
205    /// let width = 100;
206    /// let height = 100;
207    /// let bytes_per_pixel = 4; // BGRA
208    /// let bytes_per_row = width * bytes_per_pixel;
209    /// let mut pixel_data = vec![0u8; width * height * bytes_per_pixel];
210    ///
211    /// // Fill with blue color
212    /// for y in 0..height {
213    ///     for x in 0..width {
214    ///         let offset = y * bytes_per_row + x * bytes_per_pixel;
215    ///         pixel_data[offset] = 255;     // B
216    ///         pixel_data[offset + 1] = 0;   // G
217    ///         pixel_data[offset + 2] = 0;   // R
218    ///         pixel_data[offset + 3] = 255; // A
219    ///     }
220    /// }
221    ///
222    /// // Create pixel buffer from the data
223    /// let buffer = unsafe {
224    ///     CVPixelBuffer::create_with_bytes(
225    ///         width,
226    ///         height,
227    ///         0x42475241, // BGRA
228    ///         pixel_data.as_mut_ptr() as *mut std::ffi::c_void,
229    ///         bytes_per_row,
230    ///     )
231    /// }.expect("Failed to create pixel buffer");
232    ///
233    /// assert_eq!(buffer.width(), width);
234    /// assert_eq!(buffer.height(), height);
235    /// ```
236    pub unsafe fn create_with_bytes(
237        width: usize,
238        height: usize,
239        pixel_format: u32,
240        base_address: *mut std::ffi::c_void,
241        bytes_per_row: usize,
242    ) -> Result<Self, i32> {
243        let mut pixel_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
244        let status = ffi::cv_pixel_buffer_create_with_bytes(
245            width,
246            height,
247            pixel_format,
248            base_address,
249            bytes_per_row,
250            &mut pixel_buffer_ptr,
251        );
252
253        if status == 0 && !pixel_buffer_ptr.is_null() {
254            Ok(Self(pixel_buffer_ptr))
255        } else {
256            Err(status)
257        }
258    }
259
260    /// Fill the extended pixels of a pixel buffer
261    ///
262    /// This is useful for pixel buffers that have been created with extended pixels
263    /// enabled, to ensure proper edge handling for effects and filters.
264    ///
265    /// # Errors
266    ///
267    /// Returns a Core Video error code if the operation fails.
268    pub fn fill_extended_pixels(&self) -> Result<(), i32> {
269        unsafe {
270            let status = ffi::cv_pixel_buffer_fill_extended_pixels(self.0);
271            if status == 0 {
272                Ok(())
273            } else {
274                Err(status)
275            }
276        }
277    }
278
279    /// Create a pixel buffer with planar bytes
280    ///
281    /// # Safety
282    ///
283    /// The caller must ensure that:
284    /// - `plane_base_addresses` points to valid memory for each plane
285    /// - Memory remains valid for the lifetime of the pixel buffer
286    /// - All plane parameters correctly represent the memory layout
287    ///
288    /// # Errors
289    ///
290    /// Returns a Core Video error code if the pixel buffer creation fails.
291    pub unsafe fn create_with_planar_bytes(
292        width: usize,
293        height: usize,
294        pixel_format: u32,
295        plane_base_addresses: &[*mut std::ffi::c_void],
296        plane_widths: &[usize],
297        plane_heights: &[usize],
298        plane_bytes_per_row: &[usize],
299    ) -> Result<Self, i32> {
300        if plane_base_addresses.len() != plane_widths.len()
301            || plane_widths.len() != plane_heights.len()
302            || plane_heights.len() != plane_bytes_per_row.len()
303        {
304            return Err(-50); // paramErr
305        }
306
307        let mut pixel_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
308        let status = ffi::cv_pixel_buffer_create_with_planar_bytes(
309            width,
310            height,
311            pixel_format,
312            plane_base_addresses.len(),
313            plane_base_addresses.as_ptr(),
314            plane_widths.as_ptr(),
315            plane_heights.as_ptr(),
316            plane_bytes_per_row.as_ptr(),
317            &mut pixel_buffer_ptr,
318        );
319
320        if status == 0 && !pixel_buffer_ptr.is_null() {
321            Ok(Self(pixel_buffer_ptr))
322        } else {
323            Err(status)
324        }
325    }
326
327    /// Create a pixel buffer from an `IOSurface`
328    ///
329    /// # Errors
330    ///
331    /// Returns a Core Video error code if the pixel buffer creation fails.
332    pub fn create_with_io_surface(surface: &IOSurface) -> Result<Self, i32> {
333        unsafe {
334            let mut pixel_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
335            let status = ffi::cv_pixel_buffer_create_with_io_surface(
336                surface.as_ptr(),
337                &mut pixel_buffer_ptr,
338            );
339
340            if status == 0 && !pixel_buffer_ptr.is_null() {
341                Ok(Self(pixel_buffer_ptr))
342            } else {
343                Err(status)
344            }
345        }
346    }
347
348    /// Get the Core Foundation type ID for `CVPixelBuffer`
349    #[must_use]
350    pub fn type_id() -> usize {
351        unsafe { ffi::cv_pixel_buffer_get_type_id() }
352    }
353
354    /// Get the data size of the pixel buffer
355    #[must_use]
356    pub fn data_size(&self) -> usize {
357        unsafe { ffi::cv_pixel_buffer_get_data_size(self.0) }
358    }
359
360    /// Check if the pixel buffer is planar
361    #[must_use]
362    pub fn is_planar(&self) -> bool {
363        unsafe { ffi::cv_pixel_buffer_is_planar(self.0) }
364    }
365
366    /// Get the number of planes in the pixel buffer
367    #[must_use]
368    pub fn plane_count(&self) -> usize {
369        unsafe { ffi::cv_pixel_buffer_get_plane_count(self.0) }
370    }
371
372    /// Get the width of a specific plane
373    #[must_use]
374    pub fn width_of_plane(&self, plane_index: usize) -> usize {
375        unsafe { ffi::cv_pixel_buffer_get_width_of_plane(self.0, plane_index) }
376    }
377
378    /// Get the height of a specific plane
379    #[must_use]
380    pub fn height_of_plane(&self, plane_index: usize) -> usize {
381        unsafe { ffi::cv_pixel_buffer_get_height_of_plane(self.0, plane_index) }
382    }
383
384    /// Get the base address of a specific plane (internal use only)
385    ///
386    /// # Safety
387    /// Caller must ensure the buffer is locked before accessing the returned pointer.
388    fn base_address_of_plane_raw(&self, plane_index: usize) -> Option<*mut u8> {
389        unsafe {
390            let ptr = ffi::cv_pixel_buffer_get_base_address_of_plane(self.0, plane_index);
391            if ptr.is_null() {
392                None
393            } else {
394                Some(ptr.cast::<u8>())
395            }
396        }
397    }
398
399    /// Get the bytes per row of a specific plane
400    #[must_use]
401    pub fn bytes_per_row_of_plane(&self, plane_index: usize) -> usize {
402        unsafe { ffi::cv_pixel_buffer_get_bytes_per_row_of_plane(self.0, plane_index) }
403    }
404
405    /// Get the extended pixel information (left, right, top, bottom)
406    #[must_use]
407    pub fn extended_pixels(&self) -> (usize, usize, usize, usize) {
408        unsafe {
409            let mut left: usize = 0;
410            let mut right: usize = 0;
411            let mut top: usize = 0;
412            let mut bottom: usize = 0;
413            ffi::cv_pixel_buffer_get_extended_pixels(
414                self.0,
415                &mut left,
416                &mut right,
417                &mut top,
418                &mut bottom,
419            );
420            (left, right, top, bottom)
421        }
422    }
423
424    /// Check if the pixel buffer is backed by an `IOSurface`
425    #[must_use]
426    pub fn is_backed_by_io_surface(&self) -> bool {
427        self.io_surface().is_some()
428    }
429
430    /// Get the width of the pixel buffer in pixels
431    #[must_use]
432    pub fn width(&self) -> usize {
433        unsafe { ffi::cv_pixel_buffer_get_width(self.0) }
434    }
435
436    /// Returns the height of the pixel buffer in pixels.
437    #[must_use]
438    pub fn height(&self) -> usize {
439        unsafe { ffi::cv_pixel_buffer_get_height(self.0) }
440    }
441
442    /// Returns the Core Video pixel format type.
443    #[must_use]
444    pub fn pixel_format(&self) -> u32 {
445        unsafe { ffi::cv_pixel_buffer_get_pixel_format_type(self.0) }
446    }
447
448    /// Returns the number of bytes in each row.
449    #[must_use]
450    pub fn bytes_per_row(&self) -> usize {
451        unsafe { ffi::cv_pixel_buffer_get_bytes_per_row(self.0) }
452    }
453
454    /// Lock the base address for raw access.
455    ///
456    /// This is a native synchronization/mapping operation and does not grant
457    /// Rust-exclusive access to the backing bytes.
458    ///
459    /// # Safety
460    ///
461    /// Every successful call must be paired exactly once with
462    /// [`Self::unlock_raw`] using identical flags. The caller must not mix this
463    /// protocol with a live RAII guard or allow any derived access to outlive
464    /// the matching unlock.
465    ///
466    /// # Errors
467    ///
468    /// Returns a Core Video error code if the lock operation fails.
469    pub unsafe fn lock_raw(&self, flags: CVPixelBufferLockFlags) -> Result<(), i32> {
470        let result = unsafe { raw::CVPixelBufferLockBaseAddress(self.0.cast(), flags.bits()) };
471        if result == 0 {
472            Ok(())
473        } else {
474            Err(result)
475        }
476    }
477
478    /// Unlock the base address after raw access.
479    ///
480    /// # Safety
481    ///
482    /// This call must balance exactly one successful [`Self::lock_raw`] call
483    /// with identical flags. No pointer or reference derived from that mapping
484    /// may be accessed after this call, and the mapping must not belong to an
485    /// RAII guard.
486    ///
487    /// # Errors
488    ///
489    /// Returns a Core Video error code if the unlock operation fails.
490    pub unsafe fn unlock_raw(&self, flags: CVPixelBufferLockFlags) -> Result<(), i32> {
491        let result = unsafe { raw::CVPixelBufferUnlockBaseAddress(self.0.cast(), flags.bits()) };
492        if result == 0 {
493            Ok(())
494        } else {
495            Err(result)
496        }
497    }
498
499    /// Get the base address (internal use only)
500    ///
501    /// # Safety
502    /// Caller must ensure the buffer is locked before accessing the returned pointer.
503    fn base_address_raw(&self) -> Option<*mut u8> {
504        unsafe {
505            let ptr = ffi::cv_pixel_buffer_get_base_address(self.0);
506            if ptr.is_null() {
507                None
508            } else {
509                Some(ptr.cast::<u8>())
510            }
511        }
512    }
513
514    /// Get the `IOSurface` backing this pixel buffer
515    #[must_use]
516    pub fn io_surface(&self) -> Option<IOSurface> {
517        unsafe {
518            let ptr = ffi::cv_pixel_buffer_get_io_surface(self.0);
519            IOSurface::from_raw(ptr)
520        }
521    }
522
523    /// Lock the base address and return a guard for RAII-style access
524    ///
525    /// # Arguments
526    ///
527    /// * `flags` - Lock flags (use `CVPixelBufferLockFlags::READ_ONLY` for read-only access)
528    ///
529    /// # Errors
530    ///
531    /// Returns a Core Video error code if the lock operation fails.
532    ///
533    /// # Examples
534    ///
535    /// ```no_run
536    /// use apple_cf::cv::{CVPixelBuffer, CVPixelBufferLockFlags};
537    ///
538    /// fn read_buffer(buffer: &CVPixelBuffer) {
539    ///     let guard = buffer.lock(CVPixelBufferLockFlags::READ_ONLY).unwrap();
540    ///     // SAFETY: this scope excludes every native and retained alias that
541    ///     // could mutate or remap the pixel bytes.
542    ///     let data = unsafe { guard.as_slice() }.unwrap();
543    ///     println!("Buffer has {} bytes", data.len());
544    ///     // Buffer automatically unlocked when guard drops
545    /// }
546    /// ```
547    pub fn lock(&self, flags: CVPixelBufferLockFlags) -> Result<CVPixelBufferLockGuard<'_>, i32> {
548        unsafe { self.lock_raw(flags)? };
549        Ok(CVPixelBufferLockGuard {
550            buffer: self,
551            flags,
552        })
553    }
554
555    /// Lock the base address for read-only access
556    ///
557    /// This is a convenience method equivalent to `lock(CVPixelBufferLockFlags::READ_ONLY)`.
558    ///
559    /// # Errors
560    ///
561    /// Returns a Core Video error code if the lock operation fails.
562    pub fn lock_read_only(&self) -> Result<CVPixelBufferLockGuard<'_>, i32> {
563        self.lock(CVPixelBufferLockFlags::READ_ONLY)
564    }
565
566    /// Lock the base address for read-write access
567    ///
568    /// This is a convenience method equivalent to `lock(CVPixelBufferLockFlags::NONE)`.
569    ///
570    /// # Errors
571    ///
572    /// Returns a Core Video error code if the lock operation fails.
573    pub fn lock_read_write(&self) -> Result<CVPixelBufferLockGuard<'_>, i32> {
574        self.lock(CVPixelBufferLockFlags::NONE)
575    }
576}
577
578/// RAII guard for locked `CVPixelBuffer` base address
579pub struct CVPixelBufferLockGuard<'a> {
580    buffer: &'a CVPixelBuffer,
581    flags: CVPixelBufferLockFlags,
582}
583
584impl CVPixelBufferLockGuard<'_> {
585    fn non_planar_data_len(&self) -> Option<usize> {
586        if self.buffer.is_planar() {
587            return None;
588        }
589        let len = self.height().checked_mul(self.bytes_per_row())?;
590        (len <= self.data_size() && isize::try_from(len).is_ok()).then_some(len)
591    }
592
593    /// Get the base address of the locked buffer.
594    ///
595    /// Dereferencing the returned pointer is unsafe. The native lock keeps the
596    /// mapping synchronized but does not guarantee Rust aliasing or immutability.
597    #[must_use]
598    pub fn base_address(&self) -> *const u8 {
599        self.buffer
600            .base_address_raw()
601            .unwrap_or(std::ptr::null_mut())
602            .cast_const()
603    }
604
605    /// Get mutable base address (only valid for read-write locks).
606    ///
607    /// Returns `None` if this is a read-only lock.
608    /// Dereferencing the returned pointer requires unique access to the bytes
609    /// across all Rust, native, retained, GPU, and cross-process aliases.
610    pub fn base_address_mut(&mut self) -> Option<*mut u8> {
611        if self.flags.is_read_only() {
612            None
613        } else {
614            self.buffer.base_address_raw()
615        }
616    }
617
618    /// Get the base address of a specific plane.
619    ///
620    /// For multi-planar formats like YCbCr 4:2:0:
621    /// - Plane 0: Y (luminance) data
622    /// - Plane 1: `CbCr` (chrominance) data
623    ///
624    /// Returns `None` if the plane index is out of bounds. Dereferencing the
625    /// returned pointer is unsafe because the lock does not establish Rust
626    /// immutability.
627    pub fn base_address_of_plane(&self, plane_index: usize) -> Option<*const u8> {
628        self.buffer
629            .base_address_of_plane_raw(plane_index)
630            .map(<*mut u8>::cast_const)
631    }
632
633    /// Get the mutable base address of a specific plane.
634    ///
635    /// Returns `None` if this is a read-only lock or the plane index is out of
636    /// bounds. Dereferencing requires unique access across every alias.
637    pub fn base_address_of_plane_mut(&mut self, plane_index: usize) -> Option<*mut u8> {
638        if self.flags.is_read_only() {
639            return None;
640        }
641        self.buffer.base_address_of_plane_raw(plane_index)
642    }
643
644    /// Get the width of the buffer
645    #[must_use]
646    pub fn width(&self) -> usize {
647        self.buffer.width()
648    }
649
650    /// Get the height of the buffer
651    #[must_use]
652    pub fn height(&self) -> usize {
653        self.buffer.height()
654    }
655
656    /// Get bytes per row
657    #[must_use]
658    pub fn bytes_per_row(&self) -> usize {
659        self.buffer.bytes_per_row()
660    }
661
662    /// Get the data size in bytes
663    ///
664    /// This provides API parity with `IOSurfaceLockGuard::data_size()`.
665    #[must_use]
666    pub fn data_size(&self) -> usize {
667        self.buffer.data_size()
668    }
669
670    /// Get the number of planes
671    #[must_use]
672    pub fn plane_count(&self) -> usize {
673        self.buffer.plane_count()
674    }
675
676    /// Get the width of a specific plane
677    #[must_use]
678    pub fn width_of_plane(&self, plane_index: usize) -> usize {
679        self.buffer.width_of_plane(plane_index)
680    }
681
682    /// Get the height of a specific plane
683    #[must_use]
684    pub fn height_of_plane(&self, plane_index: usize) -> usize {
685        self.buffer.height_of_plane(plane_index)
686    }
687
688    /// Get the bytes per row of a specific plane
689    #[must_use]
690    pub fn bytes_per_row_of_plane(&self, plane_index: usize) -> usize {
691        self.buffer.bytes_per_row_of_plane(plane_index)
692    }
693
694    /// Get non-planar data as a byte slice.
695    ///
696    /// Returns `None` for planar buffers, missing base addresses, or lengths
697    /// that cannot be represented by a Rust slice.
698    ///
699    /// # Safety
700    ///
701    /// For the returned reference's lifetime, the mapped range must remain
702    /// allocated, initialized, and immovable, and no Rust or native alias may
703    /// mutate or remap any byte in it. The caller must also prevent any manual
704    /// unlock of this mapping.
705    #[must_use]
706    pub unsafe fn as_slice(&self) -> Option<&[u8]> {
707        let ptr = self.base_address();
708        let len = self.non_planar_data_len()?;
709        if len == 0 {
710            return Some(&[]);
711        }
712        if ptr.is_null() {
713            return None;
714        }
715        Some(unsafe { std::slice::from_raw_parts(ptr, len) })
716    }
717
718    /// Get non-planar data as a mutable byte slice.
719    ///
720    /// Returns `None` for read-only locks, planar buffers, missing base
721    /// addresses, or lengths that cannot be represented by a Rust slice.
722    ///
723    /// # Safety
724    ///
725    /// For the returned reference's lifetime, this caller must have unique
726    /// access to the full mapped range across every Rust, native, retained,
727    /// GPU, and cross-process alias. The mapping must remain allocated,
728    /// initialized, and locked.
729    pub unsafe fn as_slice_mut(&mut self) -> Option<&mut [u8]> {
730        let len = self.non_planar_data_len()?;
731        if len == 0 {
732            return Some(&mut []);
733        }
734        let ptr = self.base_address_mut()?;
735        Some(unsafe { std::slice::from_raw_parts_mut(ptr, len) })
736    }
737
738    /// Get a slice of plane data.
739    ///
740    /// Returns the data for a specific plane as a byte slice.
741    ///
742    /// Returns `None` if the plane index is out of bounds or its byte length
743    /// cannot be represented.
744    ///
745    /// # Safety
746    ///
747    /// For the returned reference's lifetime, the plane must remain allocated,
748    /// initialized, locked, and immutable through every Rust and native alias.
749    #[must_use]
750    pub unsafe fn plane_data(&self, plane_index: usize) -> Option<&[u8]> {
751        if !self.buffer.is_planar() || plane_index >= self.buffer.plane_count() {
752            return None;
753        }
754        let base = self.base_address_of_plane(plane_index)?;
755        let height = self.buffer.height_of_plane(plane_index);
756        let bytes_per_row = self.buffer.bytes_per_row_of_plane(plane_index);
757        let len = height.checked_mul(bytes_per_row)?;
758        if isize::try_from(len).is_err() {
759            return None;
760        }
761        Some(unsafe { std::slice::from_raw_parts(base, len) })
762    }
763
764    /// Get a specific row from a plane as a slice.
765    ///
766    /// Returns `None` if the plane or row index is out of bounds.
767    ///
768    /// # Safety
769    ///
770    /// For the returned reference's lifetime, the row must remain allocated,
771    /// initialized, locked, and immutable through every Rust and native alias.
772    #[must_use]
773    pub unsafe fn plane_row(&self, plane_index: usize, row_index: usize) -> Option<&[u8]> {
774        if !self.buffer.is_planar() || plane_index >= self.buffer.plane_count() {
775            return None;
776        }
777        let height = self.buffer.height_of_plane(plane_index);
778        if row_index >= height {
779            return None;
780        }
781        let base = self.base_address_of_plane(plane_index)?;
782        let bytes_per_row = self.buffer.bytes_per_row_of_plane(plane_index);
783        let plane_len = height.checked_mul(bytes_per_row)?;
784        let offset = row_index.checked_mul(bytes_per_row)?;
785        let end = offset.checked_add(bytes_per_row)?;
786        if end > plane_len || isize::try_from(bytes_per_row).is_err() {
787            return None;
788        }
789        Some(unsafe { std::slice::from_raw_parts(base.add(offset), bytes_per_row) })
790    }
791
792    /// Get a specific non-planar row as a slice.
793    ///
794    /// Returns `None` if the row index is out of bounds.
795    ///
796    /// # Safety
797    ///
798    /// For the returned reference's lifetime, the row must remain allocated,
799    /// initialized, locked, and immutable through every Rust and native alias.
800    #[must_use]
801    pub unsafe fn row(&self, row_index: usize) -> Option<&[u8]> {
802        if row_index >= self.height() {
803            return None;
804        }
805        let len = self.non_planar_data_len()?;
806        let ptr = self.base_address();
807        if ptr.is_null() {
808            return None;
809        }
810        let bytes_per_row = self.bytes_per_row();
811        let offset = row_index.checked_mul(bytes_per_row)?;
812        let end = offset.checked_add(bytes_per_row)?;
813        if end > len || isize::try_from(bytes_per_row).is_err() {
814            return None;
815        }
816        Some(unsafe { std::slice::from_raw_parts(ptr.add(offset), bytes_per_row) })
817    }
818
819    /// Access buffer with a standard `std::io::Cursor`
820    ///
821    /// Returns a cursor over the buffer data that implements `Read` and `Seek`.
822    ///
823    /// # Examples
824    ///
825    /// ```no_run
826    /// use apple_cf::cv::{CVPixelBuffer, CVPixelBufferLockFlags};
827    /// use std::io::{Read, Seek, SeekFrom};
828    ///
829    /// fn read_buffer(buffer: &CVPixelBuffer) {
830    ///     let guard = buffer.lock(CVPixelBufferLockFlags::READ_ONLY).unwrap();
831    ///     // SAFETY: no alias can mutate or remap the bytes while the cursor lives.
832    ///     let mut cursor = unsafe { guard.cursor() }.unwrap();
833    ///
834    ///     // Read first 4 bytes
835    ///     let mut pixel = [0u8; 4];
836    ///     cursor.read_exact(&mut pixel).unwrap();
837    ///
838    ///     // Seek to row 10
839    ///     let offset = 10 * guard.bytes_per_row();
840    ///     cursor.seek(SeekFrom::Start(offset as u64)).unwrap();
841    /// }
842    /// ```
843    ///
844    /// # Safety
845    ///
846    /// The same immutability and mapping guarantees as [`Self::as_slice`] must
847    /// hold for the cursor's lifetime.
848    #[must_use]
849    pub unsafe fn cursor(&self) -> Option<io::Cursor<&[u8]>> {
850        unsafe { self.as_slice() }.map(io::Cursor::new)
851    }
852
853    /// Get raw pointer to buffer data
854    #[must_use]
855    pub fn as_ptr(&self) -> *const u8 {
856        self.base_address()
857    }
858
859    /// Get mutable raw pointer to buffer data (only valid for read-write locks)
860    ///
861    /// Returns `None` if this is a read-only lock.
862    pub fn as_mut_ptr(&mut self) -> Option<*mut u8> {
863        self.base_address_mut()
864    }
865
866    /// Check if this is a read-only lock
867    #[must_use]
868    pub const fn is_read_only(&self) -> bool {
869        self.flags.is_read_only()
870    }
871
872    /// Get the lock options
873    #[must_use]
874    pub const fn options(&self) -> CVPixelBufferLockFlags {
875        self.flags
876    }
877
878    /// Get the pixel format
879    #[must_use]
880    pub fn pixel_format(&self) -> u32 {
881        self.buffer.pixel_format()
882    }
883}
884
885impl Drop for CVPixelBufferLockGuard<'_> {
886    fn drop(&mut self) {
887        let _ = unsafe { self.buffer.unlock_raw(self.flags) };
888    }
889}
890
891impl std::fmt::Debug for CVPixelBufferLockGuard<'_> {
892    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
893        f.debug_struct("CVPixelBufferLockGuard")
894            .field("flags", &self.flags)
895            .field("buffer_size", &(self.buffer.width(), self.buffer.height()))
896            .finish()
897    }
898}
899
900crate::utils::retained::cf_retained!(
901    CVPixelBuffer,
902    retain = ffi::cv_pixel_buffer_retain,
903    release = ffi::cv_pixel_buffer_release,
904);
905
906// SAFETY: `CVPixelBufferRef` is a Core Foundation type whose retain/release
907// operations are thread-safe. Our wrapper only holds the opaque pointer, and
908// native mapping operations are thread-safe. Byte dereferencing has a separate
909// unsafe contract because a lock does not establish Rust aliasing guarantees.
910unsafe impl Send for CVPixelBuffer {}
911unsafe impl Sync for CVPixelBuffer {}
912
913impl fmt::Display for CVPixelBuffer {
914    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
915        write!(
916            f,
917            "CVPixelBuffer({}x{}, format: 0x{:08X})",
918            self.width(),
919            self.height(),
920            self.pixel_format()
921        )
922    }
923}
924
925/// Flags controlling which unused buffers a pool flushes.
926#[repr(transparent)]
927#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
928pub struct CVPixelBufferPoolFlushFlags(u64);
929
930impl CVPixelBufferPoolFlushFlags {
931    /// Flush only buffers that have aged out.
932    pub const NONE: Self = Self(0);
933
934    /// Flush every unused buffer regardless of age.
935    pub const EXCESS_BUFFERS: Self = Self(1);
936
937    /// Create flags from raw `CVOptionFlags` bits.
938    #[must_use]
939    pub const fn from_bits(bits: u64) -> Self {
940        Self(bits)
941    }
942
943    /// Return the raw `CVOptionFlags` bits.
944    #[must_use]
945    pub const fn bits(self) -> u64 {
946        self.0
947    }
948}
949
950impl std::ops::BitOr for CVPixelBufferPoolFlushFlags {
951    type Output = Self;
952
953    fn bitor(self, rhs: Self) -> Self::Output {
954        Self(self.0 | rhs.0)
955    }
956}
957
958impl std::ops::BitOrAssign for CVPixelBufferPoolFlushFlags {
959    fn bitor_assign(&mut self, rhs: Self) {
960        self.0 |= rhs.0;
961    }
962}
963
964impl From<CVPixelBufferPoolFlushFlags> for u64 {
965    fn from(flags: CVPixelBufferPoolFlushFlags) -> Self {
966        flags.bits()
967    }
968}
969
970const CV_RETURN_WOULD_EXCEED_ALLOCATION_THRESHOLD: i32 = -6689;
971const PARAM_ERR: i32 = -50;
972
973#[derive(Debug)]
974struct CVPixelBufferPoolPolicy {
975    max_buffers: Option<usize>,
976    allocation_attributes: Option<CFDictionary>,
977}
978
979// SAFETY: The policy is immutable after construction. Its cached dictionary is
980// immutable and only shared for Core Foundation reads and retain/release.
981unsafe impl Send for CVPixelBufferPoolPolicy {}
982unsafe impl Sync for CVPixelBufferPoolPolicy {}
983
984impl CVPixelBufferPoolPolicy {
985    const fn unlimited() -> Self {
986        Self {
987            max_buffers: None,
988            allocation_attributes: None,
989        }
990    }
991
992    fn new(max_buffers: usize) -> Result<Self, i32> {
993        if max_buffers == 0 {
994            return Ok(Self::unlimited());
995        }
996
997        let threshold = i64::try_from(max_buffers).map_err(|_| PARAM_ERR)?;
998        let key = retained_cf_string(
999            unsafe { raw::kCVPixelBufferPoolAllocationThresholdKey },
1000            "kCVPixelBufferPoolAllocationThresholdKey",
1001        );
1002        let value = CFNumber::from_i64(threshold);
1003        let attributes = CFDictionary::from_pairs(&[(&key, &value)]);
1004
1005        Ok(Self {
1006            max_buffers: Some(max_buffers),
1007            allocation_attributes: Some(attributes),
1008        })
1009    }
1010
1011    const fn max_buffers(&self) -> usize {
1012        match self.max_buffers {
1013            Some(max_buffers) => max_buffers,
1014            None => 0,
1015        }
1016    }
1017}
1018
1019fn retained_cf_string(ptr: raw::CFStringRef, symbol: &'static str) -> CFString {
1020    unsafe { CFString::from_raw_borrowed(ptr.cast_mut().cast()) }
1021        .unwrap_or_else(|| panic!("{symbol} was NULL"))
1022}
1023
1024fn pool_pixel_buffer_attributes(
1025    width: usize,
1026    height: usize,
1027    pixel_format: u32,
1028) -> Result<CFDictionary, i32> {
1029    let width = u64::try_from(width).map_err(|_| PARAM_ERR)?;
1030    let height = u64::try_from(height).map_err(|_| PARAM_ERR)?;
1031    let width_key = retained_cf_string(
1032        unsafe { raw::kCVPixelBufferWidthKey },
1033        "kCVPixelBufferWidthKey",
1034    );
1035    let height_key = retained_cf_string(
1036        unsafe { raw::kCVPixelBufferHeightKey },
1037        "kCVPixelBufferHeightKey",
1038    );
1039    let pixel_format_key = retained_cf_string(
1040        unsafe { raw::kCVPixelBufferPixelFormatTypeKey },
1041        "kCVPixelBufferPixelFormatTypeKey",
1042    );
1043    let io_surface_key = retained_cf_string(
1044        unsafe { raw::kCVPixelBufferIOSurfacePropertiesKey },
1045        "kCVPixelBufferIOSurfacePropertiesKey",
1046    );
1047    let width_value = CFNumber::from_u64(width);
1048    let height_value = CFNumber::from_u64(height);
1049    let pixel_format_value = CFNumber::from_u64(u64::from(pixel_format));
1050    let io_surface_properties = CFDictionary::from_pairs(&[]);
1051    let pairs: [(&dyn AsCFType, &dyn AsCFType); 4] = [
1052        (&width_key, &width_value),
1053        (&height_key, &height_value),
1054        (&pixel_format_key, &pixel_format_value),
1055        (&io_surface_key, &io_surface_properties),
1056    ];
1057    Ok(CFDictionary::from_pairs(&pairs))
1058}
1059
1060/// Opaque handle to a native `CVPixelBufferPoolRef`.
1061pub struct CVPixelBufferPool {
1062    ptr: *mut std::ffi::c_void,
1063    policy: Arc<CVPixelBufferPoolPolicy>,
1064}
1065
1066// SAFETY: Core Video pool retain/release, allocation, and flush operations are
1067// thread-safe. The wrapper's shared allocation policy is immutable and
1068// thread-safe.
1069unsafe impl Send for CVPixelBufferPool {}
1070unsafe impl Sync for CVPixelBufferPool {}
1071
1072impl PartialEq for CVPixelBufferPool {
1073    fn eq(&self, other: &Self) -> bool {
1074        self.ptr == other.ptr
1075    }
1076}
1077
1078impl Eq for CVPixelBufferPool {}
1079
1080impl std::hash::Hash for CVPixelBufferPool {
1081    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1082        unsafe { ffi::cf_type_hash(self.ptr) }.hash(state);
1083    }
1084}
1085
1086impl CVPixelBufferPool {
1087    /// Adopts a +1 retained native pool with no wrapper allocation threshold.
1088    ///
1089    /// # Safety
1090    ///
1091    /// A non-null `ptr` must be a live `CVPixelBufferPoolRef` carrying one
1092    /// retain transferred to this wrapper. The caller must not release or
1093    /// separately adopt that transferred retain.
1094    pub unsafe fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
1095        if ptr.is_null() {
1096            None
1097        } else {
1098            Some(Self {
1099                ptr,
1100                policy: Arc::new(CVPixelBufferPoolPolicy::unlimited()),
1101            })
1102        }
1103    }
1104
1105    /// Adopts a +1 retained native pool and applies `max_buffers` to wrapper allocations.
1106    ///
1107    /// A zero threshold means unlimited. Native callers using [`Self::as_ptr`]
1108    /// can bypass this wrapper policy.
1109    ///
1110    /// # Safety
1111    ///
1112    /// A non-null `ptr` must be a live `CVPixelBufferPoolRef` carrying one
1113    /// retain transferred to this wrapper. On an error, ownership remains with
1114    /// the caller.
1115    ///
1116    /// # Errors
1117    ///
1118    /// Returns `paramErr` if `max_buffers` cannot be represented by Core Video.
1119    pub unsafe fn from_raw_with_max_buffers(
1120        ptr: *mut std::ffi::c_void,
1121        max_buffers: usize,
1122    ) -> Result<Option<Self>, i32> {
1123        if ptr.is_null() {
1124            return Ok(None);
1125        }
1126        let policy = Arc::new(CVPixelBufferPoolPolicy::new(max_buffers)?);
1127        Ok(Some(Self { ptr, policy }))
1128    }
1129
1130    /// Retains a +0 borrowed native pool with no wrapper allocation threshold.
1131    ///
1132    /// # Safety
1133    ///
1134    /// A non-null `ptr` must be a live `CVPixelBufferPoolRef` for the duration
1135    /// of the retain call.
1136    #[must_use]
1137    pub unsafe fn from_raw_borrowed(ptr: *mut std::ffi::c_void) -> Option<Self> {
1138        if ptr.is_null() {
1139            None
1140        } else {
1141            let retained = unsafe { raw::CVPixelBufferPoolRetain(ptr.cast()) };
1142            unsafe { Self::from_raw(retained.cast()) }
1143        }
1144    }
1145
1146    /// Retains a +0 borrowed native pool and applies `max_buffers` to wrapper allocations.
1147    ///
1148    /// # Safety
1149    ///
1150    /// A non-null `ptr` must be a live `CVPixelBufferPoolRef` for the duration
1151    /// of the retain call.
1152    ///
1153    /// # Errors
1154    ///
1155    /// Returns `paramErr` if `max_buffers` cannot be represented by Core Video.
1156    pub unsafe fn from_raw_borrowed_with_max_buffers(
1157        ptr: *mut std::ffi::c_void,
1158        max_buffers: usize,
1159    ) -> Result<Option<Self>, i32> {
1160        if ptr.is_null() {
1161            return Ok(None);
1162        }
1163        let policy = Arc::new(CVPixelBufferPoolPolicy::new(max_buffers)?);
1164        let retained = unsafe { raw::CVPixelBufferPoolRetain(ptr.cast()) };
1165        Ok(Some(Self {
1166            ptr: retained.cast(),
1167            policy,
1168        }))
1169    }
1170
1171    /// Wraps a raw `CVPixelBufferPoolRef` by taking ownership without retaining it.
1172    ///
1173    /// # Safety
1174    /// `ptr` must be a non-null, live `CVPixelBufferPoolRef` carrying one retain
1175    /// transferred to this wrapper.
1176    pub unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
1177        Self {
1178            ptr,
1179            policy: Arc::new(CVPixelBufferPoolPolicy::unlimited()),
1180        }
1181    }
1182
1183    /// Borrows the raw +0 native pool pointer while `self` remains alive.
1184    ///
1185    /// Allocations performed directly through this pointer bypass the wrapper's
1186    /// configured [`Self::max_buffers`] threshold.
1187    #[must_use]
1188    pub const fn as_ptr(&self) -> *mut std::ffi::c_void {
1189        self.ptr
1190    }
1191
1192    /// Wrapper-enforced allocation threshold, or zero when unlimited.
1193    #[must_use]
1194    pub fn max_buffers(&self) -> usize {
1195        self.policy.max_buffers()
1196    }
1197
1198    /// Create a new pixel buffer pool
1199    ///
1200    /// # Arguments
1201    ///
1202    /// * `width` - Width of pixel buffers in the pool
1203    /// * `height` - Height of pixel buffers in the pool
1204    /// * `pixel_format` - Pixel format type
1205    /// * `max_buffers` - Maximum number of buffers in the pool (0 for unlimited)
1206    ///
1207    /// # Errors
1208    ///
1209    /// Returns a Core Video error code if the pool creation fails.
1210    pub fn create(
1211        width: usize,
1212        height: usize,
1213        pixel_format: u32,
1214        max_buffers: usize,
1215    ) -> Result<Self, i32> {
1216        let policy = Arc::new(CVPixelBufferPoolPolicy::new(max_buffers)?);
1217        let pool_attributes = CFDictionary::from_pairs(&[]);
1218        let pixel_buffer_attributes = pool_pixel_buffer_attributes(width, height, pixel_format)?;
1219        let mut pool_ptr: raw::CVPixelBufferPoolRef = std::ptr::null_mut();
1220        unsafe {
1221            let status = raw::CVPixelBufferPoolCreate(
1222                std::ptr::null(),
1223                pool_attributes.as_ptr().cast(),
1224                pixel_buffer_attributes.as_ptr().cast(),
1225                &mut pool_ptr,
1226            );
1227
1228            if status == 0 && !pool_ptr.is_null() {
1229                Ok(Self {
1230                    ptr: pool_ptr.cast(),
1231                    policy,
1232                })
1233            } else {
1234                Err(status)
1235            }
1236        }
1237    }
1238
1239    fn create_pixel_buffer_with_dictionary(
1240        &self,
1241        auxiliary_attributes: Option<&CFDictionary>,
1242    ) -> Result<CVPixelBuffer, i32> {
1243        let mut pixel_buffer_ptr: raw::CVPixelBufferRef = std::ptr::null_mut();
1244        let status = unsafe {
1245            if let Some(attributes) = auxiliary_attributes {
1246                raw::CVPixelBufferPoolCreatePixelBufferWithAuxAttributes(
1247                    std::ptr::null(),
1248                    self.ptr.cast(),
1249                    attributes.as_ptr().cast(),
1250                    &mut pixel_buffer_ptr,
1251                )
1252            } else {
1253                raw::CVPixelBufferPoolCreatePixelBuffer(
1254                    std::ptr::null(),
1255                    self.ptr.cast(),
1256                    &mut pixel_buffer_ptr,
1257                )
1258            }
1259        };
1260
1261        if status == 0 && !pixel_buffer_ptr.is_null() {
1262            unsafe { CVPixelBuffer::from_raw(pixel_buffer_ptr.cast()) }.ok_or(status)
1263        } else {
1264            Err(status)
1265        }
1266    }
1267
1268    /// Create a pixel buffer while enforcing the configured allocation threshold.
1269    ///
1270    /// # Errors
1271    ///
1272    /// Returns a Core Video error code if the buffer creation fails.
1273    pub fn create_pixel_buffer(&self) -> Result<CVPixelBuffer, i32> {
1274        self.create_pixel_buffer_with_dictionary(self.policy.allocation_attributes.as_ref())
1275    }
1276
1277    /// Flush buffers that have aged out of the pool.
1278    pub fn flush(&self) {
1279        self.flush_with_flags(CVPixelBufferPoolFlushFlags::NONE);
1280    }
1281
1282    /// Flush unused buffers according to native Core Video flags.
1283    pub fn flush_with_flags(&self, flags: CVPixelBufferPoolFlushFlags) {
1284        unsafe { raw::CVPixelBufferPoolFlush(self.ptr.cast(), flags.bits()) };
1285    }
1286
1287    /// Flush every unused buffer regardless of age.
1288    pub fn flush_excess_buffers(&self) {
1289        self.flush_with_flags(CVPixelBufferPoolFlushFlags::EXCESS_BUFFERS);
1290    }
1291
1292    /// Get the Core Foundation type ID for `CVPixelBufferPool`
1293    #[must_use]
1294    pub fn type_id() -> usize {
1295        #[allow(clippy::cast_possible_truncation)]
1296        {
1297            unsafe { raw::CVPixelBufferPoolGetTypeID() as usize }
1298        }
1299    }
1300
1301    /// Create a pixel buffer from the pool with per-call auxiliary attributes.
1302    ///
1303    /// String keys become `CFString` keys and values become `CFNumber` values.
1304    /// A per-call allocation threshold can tighten but not loosen the
1305    /// threshold configured when the wrapper was created.
1306    ///
1307    /// # Errors
1308    ///
1309    /// Returns `paramErr` for an attribute key containing a NUL byte or an
1310    /// unrepresentable threshold, otherwise returns the Core Video allocation
1311    /// status.
1312    pub fn create_pixel_buffer_with_aux_attributes(
1313        &self,
1314        aux_attributes: Option<&HashMap<String, u32>>,
1315    ) -> Result<CVPixelBuffer, i32> {
1316        let Some(aux_attributes) = aux_attributes.filter(|attributes| !attributes.is_empty())
1317        else {
1318            return self.create_pixel_buffer();
1319        };
1320
1321        let threshold_key = retained_cf_string(
1322            unsafe { raw::kCVPixelBufferPoolAllocationThresholdKey },
1323            "kCVPixelBufferPoolAllocationThresholdKey",
1324        );
1325        let mut keys = Vec::with_capacity(aux_attributes.len() + 1);
1326        let mut values = Vec::with_capacity(aux_attributes.len() + 1);
1327        let mut requested_threshold = None;
1328
1329        for (key, value) in aux_attributes {
1330            if key.as_bytes().contains(&0) {
1331                return Err(PARAM_ERR);
1332            }
1333            let key = CFString::new(key);
1334            if key == threshold_key {
1335                requested_threshold = Some(usize::try_from(*value).map_err(|_| PARAM_ERR)?);
1336            } else {
1337                keys.push(key);
1338                values.push(CFNumber::from_u64(u64::from(*value)));
1339            }
1340        }
1341
1342        let effective_threshold = match (self.policy.max_buffers, requested_threshold) {
1343            (Some(configured), Some(requested)) => Some(configured.min(requested)),
1344            (Some(configured), None) => Some(configured),
1345            (None, requested) => requested,
1346        };
1347
1348        if let Some(threshold) = effective_threshold {
1349            let threshold = i64::try_from(threshold).map_err(|_| PARAM_ERR)?;
1350            keys.push(threshold_key);
1351            values.push(CFNumber::from_i64(threshold));
1352        }
1353
1354        let pairs: Vec<(&dyn AsCFType, &dyn AsCFType)> = keys
1355            .iter()
1356            .zip(&values)
1357            .map(|(key, value)| (key as &dyn AsCFType, value as &dyn AsCFType))
1358            .collect();
1359        let attributes = CFDictionary::from_pairs(&pairs);
1360        self.create_pixel_buffer_with_dictionary(Some(&attributes))
1361    }
1362
1363    /// Try to create a pixel buffer without exceeding the allocation threshold.
1364    ///
1365    /// Only `kCVReturnWouldExceedAllocationThreshold` maps to `Ok(None)`;
1366    /// every other Core Video error is preserved.
1367    ///
1368    /// # Errors
1369    ///
1370    /// Returns any Core Video allocation error other than threshold exhaustion.
1371    pub fn try_create_pixel_buffer(&self) -> Result<Option<CVPixelBuffer>, i32> {
1372        match self.create_pixel_buffer() {
1373            Ok(buffer) => Ok(Some(buffer)),
1374            Err(CV_RETURN_WOULD_EXCEED_ALLOCATION_THRESHOLD) => Ok(None),
1375            Err(status) => Err(status),
1376        }
1377    }
1378
1379    /// Copy the pool attributes into an independently owned dictionary.
1380    #[must_use]
1381    pub fn attributes(&self) -> Option<CFDictionary> {
1382        let ptr = unsafe { raw::CVPixelBufferPoolGetAttributes(self.ptr.cast()) };
1383        unsafe { CFDictionary::from_raw_borrowed(ptr.cast_mut().cast()) }
1384    }
1385
1386    /// Copy the pixel-buffer attributes into an independently owned dictionary.
1387    #[must_use]
1388    pub fn pixel_buffer_attributes(&self) -> Option<CFDictionary> {
1389        let ptr = unsafe { raw::CVPixelBufferPoolGetPixelBufferAttributes(self.ptr.cast()) };
1390        unsafe { CFDictionary::from_raw_borrowed(ptr.cast_mut().cast()) }
1391    }
1392}
1393
1394impl Clone for CVPixelBufferPool {
1395    fn clone(&self) -> Self {
1396        let ptr = unsafe { raw::CVPixelBufferPoolRetain(self.ptr.cast()) };
1397        Self {
1398            ptr: ptr.cast(),
1399            policy: Arc::clone(&self.policy),
1400        }
1401    }
1402}
1403
1404impl Drop for CVPixelBufferPool {
1405    fn drop(&mut self) {
1406        if !self.ptr.is_null() {
1407            unsafe { raw::CVPixelBufferPoolRelease(self.ptr.cast()) };
1408        }
1409    }
1410}
1411
1412impl fmt::Debug for CVPixelBufferPool {
1413    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1414        f.debug_struct("CVPixelBufferPool")
1415            .field("ptr", &self.ptr)
1416            .field("max_buffers", &self.max_buffers())
1417            .finish_non_exhaustive()
1418    }
1419}
1420
1421impl fmt::Display for CVPixelBufferPool {
1422    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1423        write!(f, "CVPixelBufferPool")
1424    }
1425}
1426
1427/// Extension trait for `io::Cursor` to add pixel buffer specific operations
1428pub trait PixelBufferCursorExt {
1429    /// Seek to a specific pixel coordinate (x, y)
1430    ///
1431    /// Assumes 4 bytes per pixel (BGRA format).
1432    ///
1433    /// # Errors
1434    ///
1435    /// Returns an I/O error if the seek operation fails.
1436    fn seek_to_pixel(&mut self, x: usize, y: usize, bytes_per_row: usize) -> io::Result<u64>;
1437
1438    /// Read a single pixel (4 bytes: BGRA)
1439    ///
1440    /// # Errors
1441    ///
1442    /// Returns an I/O error if the read operation fails.
1443    fn read_pixel(&mut self) -> io::Result<[u8; 4]>;
1444}
1445
1446impl<T: AsRef<[u8]>> PixelBufferCursorExt for io::Cursor<T> {
1447    fn seek_to_pixel(&mut self, x: usize, y: usize, bytes_per_row: usize) -> io::Result<u64> {
1448        let pos = y * bytes_per_row + x * 4; // 4 bytes per pixel (BGRA)
1449        self.seek(SeekFrom::Start(pos as u64))
1450    }
1451
1452    fn read_pixel(&mut self) -> io::Result<[u8; 4]> {
1453        let mut pixel = [0u8; 4];
1454        self.read_exact(&mut pixel)?;
1455        Ok(pixel)
1456    }
1457}