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