Skip to main content

apple_vision/person_instance_mask/
mod.rs

1#![allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
2#![allow(clippy::too_long_first_doc_paragraph)]
3//! `VNGeneratePersonInstanceMaskRequest` — per-person instance mask
4//! (macOS 14+).
5
6use std::ffi::{CStr, CString};
7use std::path::Path;
8use std::ptr;
9
10use crate::error::VisionError;
11use crate::ffi;
12
13/// A returned 8-bit grayscale mask.
14pub struct PersonInstanceMask {
15    pub width: usize,
16    pub height: usize,
17    pub bytes_per_row: usize,
18    data: *mut u8,
19}
20
21impl PersonInstanceMask {
22    /// Row-major byte view into the mask buffer.
23    #[must_use]
24    pub const fn as_bytes(&self) -> &[u8] {
25        // SAFETY: `self.data` is a valid, non-null pointer to `bytes_per_row * height` bytes
26        // allocated by the Swift bridge. The lifetime of the slice is tied to `&self`, so it
27        // cannot outlive the allocation.
28        unsafe { core::slice::from_raw_parts(self.data, self.bytes_per_row * self.height) }
29    }
30}
31
32impl Drop for PersonInstanceMask {
33    fn drop(&mut self) {
34        if !self.data.is_null() {
35            let size = (self.bytes_per_row * self.height) as isize;
36            // SAFETY: `self.data` is the pointer returned by the Swift bridge and has not
37            // been freed yet; `size` matches the allocation size. This is the unique drop site.
38            unsafe { ffi::vn_mask_buffer_free(self.data, size) };
39            self.data = ptr::null_mut();
40        }
41    }
42}
43
44// SAFETY: `PersonInstanceMask` owns a heap-allocated buffer from the Swift bridge.
45// The buffer is not aliased elsewhere; transferring ownership across thread boundaries
46// is safe.  Shared references do not mutate the buffer, so `Sync` also holds.
47unsafe impl Send for PersonInstanceMask {}
48unsafe impl Sync for PersonInstanceMask {}
49
50/// Generate a person-instance mask for the image at `path`.
51///
52/// # Errors
53///
54/// Returns [`VisionError`] when the image fails to load or the
55/// Vision request errors.
56pub fn person_instance_mask(
57    path: impl AsRef<Path>,
58) -> Result<Option<PersonInstanceMask>, VisionError> {
59    let path_str = path
60        .as_ref()
61        .to_str()
62        .ok_or_else(|| VisionError::InvalidArgument("non-UTF-8 path".into()))?;
63    let cpath = CString::new(path_str)
64        .map_err(|e| VisionError::InvalidArgument(format!("path NUL byte: {e}")))?;
65    let mut w: isize = 0;
66    let mut h: isize = 0;
67    let mut bpr: isize = 0;
68    let mut data: *mut u8 = ptr::null_mut();
69    let mut err: *mut std::ffi::c_char = ptr::null_mut();
70    // SAFETY: All pointer arguments are either null or valid out-parameters
71    // populated by the Swift bridge on return.
72    let status = unsafe {
73        ffi::vn_person_instance_mask_in_path(
74            cpath.as_ptr(),
75            &mut w,
76            &mut h,
77            &mut bpr,
78            &mut data,
79            &mut err,
80        )
81    };
82    if status != ffi::status::OK {
83        // SAFETY: `err` is either null or a malloc'd C string produced by the bridge.
84        let msg = unsafe { take_err(err) };
85        return Err(VisionError::RequestFailed(msg));
86    }
87    if data.is_null() {
88        return Ok(None);
89    }
90    Ok(Some(PersonInstanceMask {
91        width: w.max(0) as usize,
92        height: h.max(0) as usize,
93        bytes_per_row: bpr.max(0) as usize,
94        data,
95    }))
96}
97
98/// Extract an error string from a bridge-allocated C string and free it.
99///
100/// # Safety
101///
102/// `p` must be either null or a pointer to a valid null-terminated C string
103/// that was heap-allocated by the Swift bridge (i.e., by `malloc`).
104/// After this call `p` is invalid; the caller must not use it again.
105unsafe fn take_err(p: *mut std::ffi::c_char) -> String {
106    if p.is_null() {
107        return String::new();
108    }
109    // SAFETY: `p` is a valid C string per the function contract.
110    let s = unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned();
111    // SAFETY: `p` was malloc-allocated by the bridge and has not been freed yet.
112    unsafe { libc::free(p.cast()) };
113    s
114}