apple_vision/contours/
mod.rs1use core::ffi::c_char;
4use core::ptr;
5use std::ffi::CString;
6use std::path::Path;
7
8use crate::error::{from_swift, VisionError};
9use crate::face_landmarks::LandmarkPoint;
10use crate::ffi;
11
12#[derive(Debug, Clone, PartialEq)]
15pub struct Contour {
16 pub points: Vec<LandmarkPoint>,
18 pub child_count: isize,
20 pub aspect_ratio: f32,
22}
23
24pub type VisionContour = Contour;
26
27#[derive(Debug, Clone, PartialEq)]
29pub struct ContoursObservation {
30 pub contour_count: usize,
31 pub top_level_contour_count: usize,
32 pub top_level_contours: Vec<Contour>,
33}
34
35impl ContoursObservation {
36 #[must_use]
37 pub fn into_top_level_contours(self) -> Vec<Contour> {
38 self.top_level_contours
39 }
40}
41
42#[derive(Debug, Clone, Copy)]
44pub struct ContourOptions {
45 pub contrast_adjustment: f32,
48 pub detects_dark_on_light: bool,
51}
52
53impl Default for ContourOptions {
54 fn default() -> Self {
55 Self {
56 contrast_adjustment: 2.0,
57 detects_dark_on_light: true,
58 }
59 }
60}
61
62pub fn detect_contours_observation_in_path(
68 path: impl AsRef<Path>,
69 options: ContourOptions,
70) -> Result<ContoursObservation, VisionError> {
71 let path_str = path
72 .as_ref()
73 .to_str()
74 .ok_or_else(|| VisionError::InvalidArgument("non-UTF-8 path".into()))?;
75 let path_c = CString::new(path_str)
76 .map_err(|e| VisionError::InvalidArgument(format!("path NUL byte: {e}")))?;
77
78 let mut out_array: *mut core::ffi::c_void = ptr::null_mut();
79 let mut out_count: usize = 0;
80 let mut err_msg: *mut c_char = ptr::null_mut();
81 let status = unsafe {
83 ffi::vn_detect_contours_in_path(
84 path_c.as_ptr(),
85 options.contrast_adjustment,
86 options.detects_dark_on_light,
87 &mut out_array,
88 &mut out_count,
89 &mut err_msg,
90 )
91 };
92 if status != ffi::status::OK {
93 return Err(unsafe { from_swift(status, err_msg) });
95 }
96 if out_array.is_null() || out_count == 0 {
97 return Ok(ContoursObservation {
98 contour_count: 0,
99 top_level_contour_count: 0,
100 top_level_contours: Vec::new(),
101 });
102 }
103 let typed = out_array.cast::<ffi::ContourRaw>();
104 let mut v = Vec::with_capacity(out_count);
105 for i in 0..out_count {
106 let raw = unsafe { &*typed.add(i) };
108 let mut pts = Vec::with_capacity(raw.point_count);
109 for k in 0..raw.point_count {
110 let x = unsafe { *raw.point_xs.add(k) };
112 let y = unsafe { *raw.point_ys.add(k) };
114 pts.push(LandmarkPoint { x, y });
115 }
116 v.push(Contour {
117 points: pts,
118 child_count: raw.child_count,
119 aspect_ratio: raw.aspect_ratio,
120 });
121 }
122 unsafe { ffi::vn_contours_free(out_array, out_count) };
124 let contour_count = v
125 .iter()
126 .map(|contour| usize::try_from(contour.child_count).unwrap_or_default())
127 .sum::<usize>()
128 + v.len();
129 Ok(ContoursObservation {
130 contour_count,
131 top_level_contour_count: v.len(),
132 top_level_contours: v,
133 })
134}
135
136pub fn detect_contours_in_path(
146 path: impl AsRef<Path>,
147 options: ContourOptions,
148) -> Result<Vec<Contour>, VisionError> {
149 detect_contours_observation_in_path(path, options)
150 .map(ContoursObservation::into_top_level_contours)
151}