Skip to main content

visionkit/
image_analyzer.rs

1use core::ffi::{c_char, c_void};
2use core::ops::{BitOr, BitOrAssign};
3use core::ptr;
4use std::path::Path;
5
6use serde::{Deserialize, Serialize};
7
8use crate::error::VisionKitError;
9use crate::ffi;
10use crate::image_analysis::ImageAnalysis;
11use crate::private::{error_from_status, json_cstring, parse_json_ptr, path_to_cstring};
12
13type AnalyzePathFn = unsafe extern "C" fn(
14    token: *mut c_void,
15    path: *const c_char,
16    orientation_raw: u32,
17    configuration_json: *const c_char,
18    out_analysis_token: *mut *mut c_void,
19    out_error_message: *mut *mut c_char,
20) -> i32;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
23#[serde(transparent)]
24pub struct ImageAnalysisTypes(u64);
25
26impl ImageAnalysisTypes {
27    pub const NONE: Self = Self(0);
28    pub const TEXT: Self = Self(1);
29    pub const MACHINE_READABLE_CODE: Self = Self(2);
30    pub const VISUAL_LOOK_UP: Self = Self(4);
31    pub const ALL: Self =
32        Self(Self::TEXT.0 | Self::MACHINE_READABLE_CODE.0 | Self::VISUAL_LOOK_UP.0);
33
34    #[must_use]
35    pub const fn new(raw: u64) -> Self {
36        Self(raw)
37    }
38
39    #[must_use]
40    pub const fn bits(self) -> u64 {
41        self.0
42    }
43
44    #[must_use]
45    pub const fn contains(self, other: Self) -> bool {
46        (self.0 & other.0) == other.0
47    }
48}
49
50impl BitOr for ImageAnalysisTypes {
51    type Output = Self;
52
53    fn bitor(self, rhs: Self) -> Self::Output {
54        Self(self.0 | rhs.0)
55    }
56}
57
58impl BitOrAssign for ImageAnalysisTypes {
59    fn bitor_assign(&mut self, rhs: Self) {
60        self.0 |= rhs.0;
61    }
62}
63
64impl Default for ImageAnalysisTypes {
65    fn default() -> Self {
66        Self::NONE
67    }
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
71pub enum ImageOrientation {
72    #[default]
73    Up,
74    UpMirrored,
75    Down,
76    DownMirrored,
77    LeftMirrored,
78    Right,
79    RightMirrored,
80    Left,
81}
82
83impl ImageOrientation {
84    #[must_use]
85    pub const fn raw_value(self) -> u32 {
86        match self {
87            Self::Up => 1,
88            Self::UpMirrored => 2,
89            Self::Down => 3,
90            Self::DownMirrored => 4,
91            Self::LeftMirrored => 5,
92            Self::Right => 6,
93            Self::RightMirrored => 7,
94            Self::Left => 8,
95        }
96    }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100#[serde(rename_all = "camelCase")]
101pub struct ImageAnalyzerConfiguration {
102    analysis_types: ImageAnalysisTypes,
103    locales: Vec<String>,
104}
105
106impl ImageAnalyzerConfiguration {
107    #[must_use]
108    pub fn new(analysis_types: ImageAnalysisTypes) -> Self {
109        Self {
110            analysis_types,
111            locales: Vec::new(),
112        }
113    }
114
115    #[must_use]
116    pub fn analysis_types(&self) -> ImageAnalysisTypes {
117        self.analysis_types
118    }
119
120    #[must_use]
121    pub fn locales(&self) -> &[String] {
122        &self.locales
123    }
124
125    #[must_use]
126    pub fn with_locales<I, S>(mut self, locales: I) -> Self
127    where
128        I: IntoIterator<Item = S>,
129        S: Into<String>,
130    {
131        self.locales = locales.into_iter().map(Into::into).collect();
132        self
133    }
134}
135
136pub struct ImageAnalyzer {
137    token: *mut c_void,
138}
139
140impl Drop for ImageAnalyzer {
141    fn drop(&mut self) {
142        if !self.token.is_null() {
143            unsafe { ffi::image_analyzer::vk_image_analyzer_release(self.token) };
144            self.token = ptr::null_mut();
145        }
146    }
147}
148
149impl ImageAnalyzer {
150    pub fn new() -> Result<Self, VisionKitError> {
151        let token = unsafe { ffi::image_analyzer::vk_image_analyzer_new() };
152        if token.is_null() {
153            return Err(VisionKitError::UnavailableOnThisMacOS(
154                "ImageAnalyzer requires macOS 13+".to_owned(),
155            ));
156        }
157        Ok(Self { token })
158    }
159
160    #[must_use]
161    pub fn is_supported() -> bool {
162        unsafe { ffi::image_analyzer::vk_image_analyzer_is_supported() != 0 }
163    }
164
165    pub fn supported_text_recognition_languages() -> Result<Vec<String>, VisionKitError> {
166        let mut languages_json: *mut c_char = ptr::null_mut();
167        let mut err_msg: *mut c_char = ptr::null_mut();
168        let status = unsafe {
169            ffi::image_analyzer::vk_image_analyzer_supported_text_recognition_languages_json(
170                &mut languages_json,
171                &mut err_msg,
172            )
173        };
174        if status == ffi::status::OK {
175            let mut languages: Vec<String> =
176                unsafe { parse_json_ptr(languages_json, "supported text recognition languages") }?;
177            languages.sort();
178            Ok(languages)
179        } else {
180            Err(unsafe { error_from_status(status, err_msg) })
181        }
182    }
183
184    pub fn analyze_image_at_path<P: AsRef<Path>>(
185        &self,
186        path: P,
187        orientation: ImageOrientation,
188        configuration: &ImageAnalyzerConfiguration,
189    ) -> Result<ImageAnalysis, VisionKitError> {
190        self.analyze_with_loader(
191            path,
192            orientation,
193            configuration,
194            ffi::image_analyzer::vk_image_analyzer_analyze_image_at_path,
195            "file URL image analysis",
196        )
197    }
198
199    pub fn analyze_ns_image_at_path<P: AsRef<Path>>(
200        &self,
201        path: P,
202        orientation: ImageOrientation,
203        configuration: &ImageAnalyzerConfiguration,
204    ) -> Result<ImageAnalysis, VisionKitError> {
205        self.analyze_with_loader(
206            path,
207            orientation,
208            configuration,
209            ffi::image_analyzer::vk_image_analyzer_analyze_ns_image_at_path,
210            "NSImage image analysis",
211        )
212    }
213
214    pub fn analyze_cg_image_at_path<P: AsRef<Path>>(
215        &self,
216        path: P,
217        orientation: ImageOrientation,
218        configuration: &ImageAnalyzerConfiguration,
219    ) -> Result<ImageAnalysis, VisionKitError> {
220        self.analyze_with_loader(
221            path,
222            orientation,
223            configuration,
224            ffi::image_analyzer::vk_image_analyzer_analyze_cg_image_at_path,
225            "CGImage image analysis",
226        )
227    }
228
229    pub fn analyze_ci_image_at_path<P: AsRef<Path>>(
230        &self,
231        path: P,
232        orientation: ImageOrientation,
233        configuration: &ImageAnalyzerConfiguration,
234    ) -> Result<ImageAnalysis, VisionKitError> {
235        self.analyze_with_loader(
236            path,
237            orientation,
238            configuration,
239            ffi::image_analyzer::vk_image_analyzer_analyze_ci_image_at_path,
240            "CIImage image analysis",
241        )
242    }
243
244    pub fn analyze_pixel_buffer_at_path<P: AsRef<Path>>(
245        &self,
246        path: P,
247        orientation: ImageOrientation,
248        configuration: &ImageAnalyzerConfiguration,
249    ) -> Result<ImageAnalysis, VisionKitError> {
250        self.analyze_with_loader(
251            path,
252            orientation,
253            configuration,
254            ffi::image_analyzer::vk_image_analyzer_analyze_pixel_buffer_at_path,
255            "CVPixelBuffer image analysis",
256        )
257    }
258
259    fn analyze_with_loader<P: AsRef<Path>>(
260        &self,
261        path: P,
262        orientation: ImageOrientation,
263        configuration: &ImageAnalyzerConfiguration,
264        analyze_fn: AnalyzePathFn,
265        context: &str,
266    ) -> Result<ImageAnalysis, VisionKitError> {
267        let path = path_to_cstring(path.as_ref())?;
268        let configuration_json = json_cstring(configuration)?;
269        let mut analysis_token: *mut c_void = ptr::null_mut();
270        let mut err_msg: *mut c_char = ptr::null_mut();
271        let status = unsafe {
272            analyze_fn(
273                self.token,
274                path.as_ptr(),
275                orientation.raw_value(),
276                configuration_json.as_ptr(),
277                &mut analysis_token,
278                &mut err_msg,
279            )
280        };
281        if status == ffi::status::OK {
282            if analysis_token.is_null() {
283                return Err(VisionKitError::Unknown(format!(
284                    "Swift bridge returned an empty image analysis token for {context}"
285                )));
286            }
287            Ok(ImageAnalysis::from_token(analysis_token))
288        } else {
289            Err(unsafe { error_from_status(status, err_msg) })
290        }
291    }
292}