Skip to main content

apple_vision/registration/
mod.rs

1#![allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
2#![allow(clippy::too_long_first_doc_paragraph)]
3//! `VNTranslationalImageRegistrationRequest` +
4//! `VNHomographicImageRegistrationRequest` — pixel-space alignment
5//! between two images.
6
7use std::ffi::{CStr, CString};
8use std::path::Path;
9use std::ptr;
10
11use crate::error::VisionError;
12use crate::ffi;
13use crate::request_base::ImageAlignmentObservation;
14
15/// 2D translation in source-image coordinates needed to align the
16/// floating image to the target.
17#[derive(Debug, Clone, Copy, PartialEq)]
18pub struct TranslationalAlignment {
19    pub tx: f64,
20    pub ty: f64,
21}
22
23/// 3×3 row-major homography matrix that warps the floating image
24/// into the target's coordinate system.
25#[derive(Debug, Clone, Copy, PartialEq)]
26pub struct HomographicAlignment {
27    pub matrix: [[f32; 3]; 3],
28}
29
30/// Compute a translational alignment from `floating` to `target`.
31///
32/// # Errors
33///
34/// Returns [`VisionError`] when either image fails to load or the
35/// Vision request errors.
36pub fn register_translational(
37    target: impl AsRef<Path>,
38    floating: impl AsRef<Path>,
39) -> Result<TranslationalAlignment, VisionError> {
40    let target_str = target
41        .as_ref()
42        .to_str()
43        .ok_or_else(|| VisionError::InvalidArgument("non-UTF-8 target path".into()))?;
44    let tp = CString::new(target_str)
45        .map_err(|e| VisionError::InvalidArgument(format!("target path NUL byte: {e}")))?;
46    let floating_str = floating
47        .as_ref()
48        .to_str()
49        .ok_or_else(|| VisionError::InvalidArgument("non-UTF-8 floating path".into()))?;
50    let fp = CString::new(floating_str)
51        .map_err(|e| VisionError::InvalidArgument(format!("floating path NUL byte: {e}")))?;
52    let mut out = ffi::TranslationalAlignmentRaw { tx: 0.0, ty: 0.0 };
53    let mut err: *mut std::ffi::c_char = ptr::null_mut();
54    // SAFETY: `tp`, `fp` are valid C strings; `out` and `err` are valid out-params.
55    let status = unsafe {
56        ffi::vn_register_translational_in_paths(tp.as_ptr(), fp.as_ptr(), &mut out, &mut err)
57    };
58    if status != ffi::status::OK {
59        // SAFETY: `err` is either null or a malloc'd C string from the bridge.
60        let msg = unsafe { take_err(err) };
61        return Err(VisionError::RequestFailed(msg));
62    }
63    Ok(TranslationalAlignment {
64        tx: out.tx,
65        ty: out.ty,
66    })
67}
68
69/// Compute a `VNImageAlignmentObservation` wrapper backed by a
70/// `VNImageTranslationAlignmentObservation`.
71///
72/// # Errors
73///
74/// Returns [`VisionError`] when either image fails to load or the Vision
75/// request errors.
76pub fn register_translational_observation(
77    target: impl AsRef<Path>,
78    floating: impl AsRef<Path>,
79) -> Result<ImageAlignmentObservation, VisionError> {
80    register_translational(target, floating).map(ImageAlignmentObservation::translational)
81}
82
83/// Compute a homographic (perspective) alignment from `floating` to
84/// `target`.
85///
86/// # Errors
87///
88/// Returns [`VisionError`] when either image fails to load or the
89/// Vision request errors.
90pub fn register_homographic(
91    target: impl AsRef<Path>,
92    floating: impl AsRef<Path>,
93) -> Result<HomographicAlignment, VisionError> {
94    let target_str = target
95        .as_ref()
96        .to_str()
97        .ok_or_else(|| VisionError::InvalidArgument("non-UTF-8 target path".into()))?;
98    let tp = CString::new(target_str)
99        .map_err(|e| VisionError::InvalidArgument(format!("target path NUL byte: {e}")))?;
100    let floating_str = floating
101        .as_ref()
102        .to_str()
103        .ok_or_else(|| VisionError::InvalidArgument("non-UTF-8 floating path".into()))?;
104    let fp = CString::new(floating_str)
105        .map_err(|e| VisionError::InvalidArgument(format!("floating path NUL byte: {e}")))?;
106    let mut out = ffi::HomographicAlignmentRaw {
107        m00: 0.0,
108        m01: 0.0,
109        m02: 0.0,
110        m10: 0.0,
111        m11: 0.0,
112        m12: 0.0,
113        m20: 0.0,
114        m21: 0.0,
115        m22: 0.0,
116        _pad: 0.0,
117    };
118    let mut err: *mut std::ffi::c_char = ptr::null_mut();
119    // SAFETY: `tp`, `fp` are valid C strings; `out` and `err` are valid out-params.
120    let status = unsafe {
121        ffi::vn_register_homographic_in_paths(tp.as_ptr(), fp.as_ptr(), &mut out, &mut err)
122    };
123    if status != ffi::status::OK {
124        // SAFETY: `err` is either null or a malloc'd C string from the bridge.
125        let msg = unsafe { take_err(err) };
126        return Err(VisionError::RequestFailed(msg));
127    }
128    Ok(HomographicAlignment {
129        matrix: [
130            [out.m00, out.m01, out.m02],
131            [out.m10, out.m11, out.m12],
132            [out.m20, out.m21, out.m22],
133        ],
134    })
135}
136
137/// Compute a `VNImageAlignmentObservation` wrapper backed by a
138/// `VNImageHomographicAlignmentObservation`.
139///
140/// # Errors
141///
142/// Returns [`VisionError`] when either image fails to load or the Vision
143/// request errors.
144pub fn register_homographic_observation(
145    target: impl AsRef<Path>,
146    floating: impl AsRef<Path>,
147) -> Result<ImageAlignmentObservation, VisionError> {
148    register_homographic(target, floating).map(ImageAlignmentObservation::homographic)
149}
150
151/// Extract an error string from a bridge-allocated C string and free it.
152///
153/// # Safety
154///
155/// `p` must be either null or a valid null-terminated C string heap-allocated
156/// (via `malloc`) by the Swift bridge. After this call `p` is invalid.
157unsafe fn take_err(p: *mut std::ffi::c_char) -> String {
158    if p.is_null() {
159        return String::new();
160    }
161    // SAFETY: `p` is a valid C string per the function contract.
162    let s = unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned();
163    // SAFETY: `p` was malloc-allocated by the bridge and is not yet freed.
164    unsafe { libc::free(p.cast()) };
165    s
166}