Skip to main content

apple_accelerate/
vimage.rs

1use crate::bridge;
2use crate::error::{Error, Result};
3use core::ffi::c_void;
4use core::marker::PhantomData;
5
6/// Common `vImage_Flags` values.
7pub mod vimage_flags {
8    pub const NO_FLAGS: u32 = 0;
9    pub const BACKGROUND_COLOR_FILL: u32 = 4;
10    pub const EDGE_EXTEND: u32 = 8;
11    pub const HIGH_QUALITY_RESAMPLING: u32 = 32;
12}
13
14fn vimage_result(status: isize) -> Result<()> {
15    if status == 0 {
16        Ok(())
17    } else {
18        Err(Error::VImageError(status))
19    }
20}
21
22fn ensure_same_dimensions(lhs: &ImageBuffer<'_>, rhs: &ImageBuffer<'_>) -> Result<()> {
23    if lhs.width() == rhs.width() && lhs.height() == rhs.height() {
24        Ok(())
25    } else {
26        Err(Error::InvalidValue(
27            "vImage buffers must share the same width and height",
28        ))
29    }
30}
31
32/// Borrowed wrapper around a caller-owned image buffer.
33pub struct ImageBuffer<'a> {
34    data: *mut u8,
35    width: usize,
36    height: usize,
37    row_bytes: usize,
38    _marker: PhantomData<&'a mut [u8]>,
39}
40
41impl<'a> ImageBuffer<'a> {
42    pub fn from_argb8888(data: &'a mut [u8], width: usize, height: usize) -> Result<Self> {
43        let expected = width
44            .checked_mul(height)
45            .and_then(|pixels| pixels.checked_mul(4))
46            .ok_or(Error::OperationFailed("image dimensions overflowed"))?;
47        if data.len() < expected {
48            return Err(Error::InvalidLength {
49                expected,
50                actual: data.len(),
51            });
52        }
53
54        Ok(Self {
55            data: data.as_mut_ptr(),
56            width,
57            height,
58            row_bytes: width * 4,
59            _marker: PhantomData,
60        })
61    }
62
63    pub fn from_planar8(data: &'a mut [u8], width: usize, height: usize) -> Result<Self> {
64        let expected = width
65            .checked_mul(height)
66            .ok_or(Error::OperationFailed("image dimensions overflowed"))?;
67        if data.len() < expected {
68            return Err(Error::InvalidLength {
69                expected,
70                actual: data.len(),
71            });
72        }
73
74        Ok(Self {
75            data: data.as_mut_ptr(),
76            width,
77            height,
78            row_bytes: width,
79            _marker: PhantomData,
80        })
81    }
82
83    fn data_ptr(&self) -> *mut c_void {
84        self.data.cast()
85    }
86
87    fn width(&self) -> usize {
88        self.width
89    }
90
91    fn height(&self) -> usize {
92        self.height
93    }
94
95    fn row_bytes(&self) -> usize {
96        self.row_bytes
97    }
98}
99
100pub fn rotate_argb8888(
101    src: &ImageBuffer<'_>,
102    dst: &mut ImageBuffer<'_>,
103    angle_radians: f32,
104    background_color: [u8; 4],
105    flags: u32,
106) -> Result<()> {
107    // SAFETY: Source and destination buffers remain valid for the duration of the call.
108    let status = unsafe {
109        bridge::acc_vimage_rotate_argb8888(
110            src.data_ptr(),
111            src.width(),
112            src.height(),
113            src.row_bytes(),
114            dst.data_ptr(),
115            dst.width(),
116            dst.height(),
117            dst.row_bytes(),
118            angle_radians,
119            background_color.as_ptr(),
120            flags,
121        )
122    };
123    vimage_result(status)
124}
125
126pub fn box_convolve_argb8888(
127    src: &ImageBuffer<'_>,
128    dst: &mut ImageBuffer<'_>,
129    kernel_height: u32,
130    kernel_width: u32,
131    background_color: [u8; 4],
132    flags: u32,
133) -> Result<()> {
134    // SAFETY: Source and destination buffers remain valid for the duration of the call.
135    let status = unsafe {
136        bridge::acc_vimage_box_convolve_argb8888(
137            src.data_ptr(),
138            src.width(),
139            src.height(),
140            src.row_bytes(),
141            dst.data_ptr(),
142            dst.width(),
143            dst.height(),
144            dst.row_bytes(),
145            kernel_height,
146            kernel_width,
147            background_color.as_ptr(),
148            flags,
149        )
150    };
151    vimage_result(status)
152}
153
154pub fn scale_argb8888(src: &ImageBuffer<'_>, dst: &mut ImageBuffer<'_>, flags: u32) -> Result<()> {
155    // SAFETY: Source and destination buffers remain valid for the duration of the call.
156    let status = unsafe {
157        bridge::acc_vimage_scale_argb8888(
158            src.data_ptr(),
159            src.width(),
160            src.height(),
161            src.row_bytes(),
162            dst.data_ptr(),
163            dst.width(),
164            dst.height(),
165            dst.row_bytes(),
166            flags,
167        )
168    };
169    vimage_result(status)
170}
171
172pub fn contrast_stretch_planar8(
173    src: &ImageBuffer<'_>,
174    dst: &mut ImageBuffer<'_>,
175    flags: u32,
176) -> Result<()> {
177    // SAFETY: Source and destination buffers remain valid for the duration of the call.
178    let status = unsafe {
179        bridge::acc_vimage_contrast_stretch_planar8(
180            src.data_ptr(),
181            src.width(),
182            src.height(),
183            src.row_bytes(),
184            dst.data_ptr(),
185            dst.width(),
186            dst.height(),
187            dst.row_bytes(),
188            flags,
189        )
190    };
191    vimage_result(status)
192}
193
194pub fn alpha_blend_argb8888(
195    src_top: &ImageBuffer<'_>,
196    src_bottom: &ImageBuffer<'_>,
197    dst: &mut ImageBuffer<'_>,
198    flags: u32,
199) -> Result<()> {
200    ensure_same_dimensions(src_top, src_bottom)?;
201    ensure_same_dimensions(src_top, dst)?;
202
203    // SAFETY: Buffers share a common geometry and remain valid for the duration of the call.
204    let status = unsafe {
205        bridge::acc_vimage_alpha_blend_argb8888(
206            src_top.data_ptr(),
207            src_top.width(),
208            src_top.height(),
209            src_top.row_bytes(),
210            src_bottom.data_ptr(),
211            src_bottom.width(),
212            src_bottom.height(),
213            src_bottom.row_bytes(),
214            dst.data_ptr(),
215            dst.width(),
216            dst.height(),
217            dst.row_bytes(),
218            flags,
219        )
220    };
221    vimage_result(status)
222}
223
224pub fn clip_to_alpha_argb8888(
225    src: &ImageBuffer<'_>,
226    dst: &mut ImageBuffer<'_>,
227    flags: u32,
228) -> Result<()> {
229    ensure_same_dimensions(src, dst)?;
230
231    // SAFETY: Buffers share a common geometry and remain valid for the duration of the call.
232    let status = unsafe {
233        bridge::acc_vimage_clip_to_alpha_argb8888(
234            src.data_ptr(),
235            src.width(),
236            src.height(),
237            src.row_bytes(),
238            dst.data_ptr(),
239            dst.width(),
240            dst.height(),
241            dst.row_bytes(),
242            flags,
243        )
244    };
245    vimage_result(status)
246}
247
248pub fn premultiply_argb8888(
249    src: &ImageBuffer<'_>,
250    dst: &mut ImageBuffer<'_>,
251    flags: u32,
252) -> Result<()> {
253    ensure_same_dimensions(src, dst)?;
254
255    // SAFETY: Buffers share a common geometry and remain valid for the duration of the call.
256    let status = unsafe {
257        bridge::acc_vimage_premultiply_argb8888(
258            src.data_ptr(),
259            src.width(),
260            src.height(),
261            src.row_bytes(),
262            dst.data_ptr(),
263            dst.width(),
264            dst.height(),
265            dst.row_bytes(),
266            flags,
267        )
268    };
269    vimage_result(status)
270}
271
272pub fn unpremultiply_argb8888(
273    src: &ImageBuffer<'_>,
274    dst: &mut ImageBuffer<'_>,
275    flags: u32,
276) -> Result<()> {
277    ensure_same_dimensions(src, dst)?;
278
279    // SAFETY: Buffers share a common geometry and remain valid for the duration of the call.
280    let status = unsafe {
281        bridge::acc_vimage_unpremultiply_argb8888(
282            src.data_ptr(),
283            src.width(),
284            src.height(),
285            src.row_bytes(),
286            dst.data_ptr(),
287            dst.width(),
288            dst.height(),
289            dst.row_bytes(),
290            flags,
291        )
292    };
293    vimage_result(status)
294}
295
296pub fn convert_planar8_to_argb8888(
297    src_alpha: &ImageBuffer<'_>,
298    src_red: &ImageBuffer<'_>,
299    src_green: &ImageBuffer<'_>,
300    src_blue: &ImageBuffer<'_>,
301    dst: &mut ImageBuffer<'_>,
302    flags: u32,
303) -> Result<()> {
304    ensure_same_dimensions(src_alpha, src_red)?;
305    ensure_same_dimensions(src_alpha, src_green)?;
306    ensure_same_dimensions(src_alpha, src_blue)?;
307    ensure_same_dimensions(src_alpha, dst)?;
308
309    // SAFETY: Buffers share a common geometry and remain valid for the duration of the call.
310    let status = unsafe {
311        bridge::acc_vimage_convert_planar8_to_argb8888(
312            src_alpha.data_ptr(),
313            src_alpha.width(),
314            src_alpha.height(),
315            src_alpha.row_bytes(),
316            src_red.data_ptr(),
317            src_red.width(),
318            src_red.height(),
319            src_red.row_bytes(),
320            src_green.data_ptr(),
321            src_green.width(),
322            src_green.height(),
323            src_green.row_bytes(),
324            src_blue.data_ptr(),
325            src_blue.width(),
326            src_blue.height(),
327            src_blue.row_bytes(),
328            dst.data_ptr(),
329            dst.width(),
330            dst.height(),
331            dst.row_bytes(),
332            flags,
333        )
334    };
335    vimage_result(status)
336}
337
338pub fn convert_argb8888_to_planar8(
339    src: &ImageBuffer<'_>,
340    dst_alpha: &mut ImageBuffer<'_>,
341    dst_red: &mut ImageBuffer<'_>,
342    dst_green: &mut ImageBuffer<'_>,
343    dst_blue: &mut ImageBuffer<'_>,
344    flags: u32,
345) -> Result<()> {
346    ensure_same_dimensions(src, dst_alpha)?;
347    ensure_same_dimensions(src, dst_red)?;
348    ensure_same_dimensions(src, dst_green)?;
349    ensure_same_dimensions(src, dst_blue)?;
350
351    // SAFETY: Buffers share a common geometry and remain valid for the duration of the call.
352    let status = unsafe {
353        bridge::acc_vimage_convert_argb8888_to_planar8(
354            src.data_ptr(),
355            src.width(),
356            src.height(),
357            src.row_bytes(),
358            dst_alpha.data_ptr(),
359            dst_alpha.width(),
360            dst_alpha.height(),
361            dst_alpha.row_bytes(),
362            dst_red.data_ptr(),
363            dst_red.width(),
364            dst_red.height(),
365            dst_red.row_bytes(),
366            dst_green.data_ptr(),
367            dst_green.width(),
368            dst_green.height(),
369            dst_green.row_bytes(),
370            dst_blue.data_ptr(),
371            dst_blue.width(),
372            dst_blue.height(),
373            dst_blue.row_bytes(),
374            flags,
375        )
376    };
377    vimage_result(status)
378}