Skip to main content

apple_vision/objectness_saliency/
mod.rs

1#![allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
2#![allow(clippy::too_long_first_doc_paragraph)]
3//! `VNGenerateObjectnessBasedSaliencyImageRequest` — discrete object
4//! regions an attention model thinks are salient.
5
6use std::ffi::{CStr, CString};
7use std::path::Path;
8use std::ptr;
9
10use crate::error::VisionError;
11use crate::ffi;
12
13/// One salient object region.
14#[derive(Debug, Clone, Copy, PartialEq)]
15pub struct ObjectnessRegion {
16    pub x: f64,
17    pub y: f64,
18    pub w: f64,
19    pub h: f64,
20    pub confidence: f32,
21}
22
23/// Detect objectness-based salient regions in the image at `path`.
24///
25/// # Errors
26///
27/// Returns [`VisionError`] when the image fails to load or the
28/// Vision request errors.
29pub fn objectness_saliency(path: impl AsRef<Path>) -> Result<Vec<ObjectnessRegion>, VisionError> {
30    let path_str = path
31        .as_ref()
32        .to_str()
33        .ok_or_else(|| VisionError::InvalidArgument("non-UTF-8 path".into()))?;
34    let cpath = CString::new(path_str)
35        .map_err(|e| VisionError::InvalidArgument(format!("path NUL byte: {e}")))?;
36    let mut rects_ptr: *mut ffi::SimpleRectRaw = ptr::null_mut();
37    let mut count: isize = 0;
38    let mut err: *mut std::ffi::c_char = ptr::null_mut();
39    // SAFETY: all pointer arguments are valid stack locations or null-initialised out-params; strings are valid C strings for the duration of the call.
40    let status = unsafe {
41        ffi::vn_objectness_saliency_in_path(cpath.as_ptr(), &mut rects_ptr, &mut count, &mut err)
42    };
43    if status != ffi::status::OK {
44        // SAFETY: the error pointer is either null or a bridge-allocated C string; `take_err` copies and frees it.
45        let msg = unsafe { take_err(err) };
46        return Err(VisionError::RequestFailed(msg));
47    }
48    let mut out = Vec::with_capacity(count.max(0) as usize);
49    for i in 0..count {
50        // SAFETY: the pointer is valid for the reported element count; the index is in bounds.
51        let r = unsafe { rects_ptr.offset(i).read() };
52        out.push(ObjectnessRegion {
53            x: r.x,
54            y: r.y,
55            w: r.w,
56            h: r.h,
57            confidence: r.confidence,
58        });
59    }
60    if !rects_ptr.is_null() {
61        // SAFETY: the pointer/count pair was allocated by the bridge and is freed exactly once here.
62        unsafe { ffi::vn_simple_rects_free(rects_ptr, count) };
63    }
64    Ok(out)
65}
66
67/// Extract an error string from a bridge-allocated C string and free it.
68///
69/// # Safety
70///
71/// `p` must be either null or a valid null-terminated C string heap-allocated
72/// (via `malloc`) by the Swift bridge. After this call `p` is invalid.
73unsafe fn take_err(p: *mut std::ffi::c_char) -> String {
74    if p.is_null() {
75        return String::new();
76    }
77    // SAFETY: the C string pointer is non-null (checked above) and valid for the duration of this borrow.
78    let s = unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned();
79    // SAFETY: `p` was malloc-allocated by the bridge and has not been freed yet.
80    unsafe { libc::free(p.cast()) };
81    s
82}