Skip to main content

apple_accelerate/
vimage.rs

1use crate::error::{Error, Result};
2use crate::ffi;
3use core::marker::PhantomData;
4use core::ptr;
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 pixel_count(value: usize) -> Result<u64> {
15    u64::try_from(value).map_err(|_| Error::OperationFailed("image dimension exceeds u64"))
16}
17
18fn vimage_result(status: ffi::vImage_Error) -> Result<()> {
19    if status == 0 {
20        Ok(())
21    } else {
22        Err(Error::VImageError(status))
23    }
24}
25
26/// Borrowed wrapper around a caller-owned `vImage_Buffer`.
27pub struct ImageBuffer<'a> {
28    inner: ffi::vImage_Buffer,
29    _marker: PhantomData<&'a mut [u8]>,
30}
31
32impl<'a> ImageBuffer<'a> {
33    pub fn from_argb8888(data: &'a mut [u8], width: usize, height: usize) -> Result<Self> {
34        let expected = width
35            .checked_mul(height)
36            .and_then(|pixels| pixels.checked_mul(4))
37            .ok_or(Error::OperationFailed("image dimensions overflowed"))?;
38        if data.len() < expected {
39            return Err(Error::InvalidLength {
40                expected,
41                actual: data.len(),
42            });
43        }
44
45        Ok(Self {
46            inner: ffi::vImage_Buffer {
47                data: data.as_mut_ptr().cast(),
48                height: pixel_count(height)?,
49                width: pixel_count(width)?,
50                row_bytes: width * 4,
51            },
52            _marker: PhantomData,
53        })
54    }
55
56    pub fn from_planar8(data: &'a mut [u8], width: usize, height: usize) -> Result<Self> {
57        let expected = width
58            .checked_mul(height)
59            .ok_or(Error::OperationFailed("image dimensions overflowed"))?;
60        if data.len() < expected {
61            return Err(Error::InvalidLength {
62                expected,
63                actual: data.len(),
64            });
65        }
66
67        Ok(Self {
68            inner: ffi::vImage_Buffer {
69                data: data.as_mut_ptr().cast(),
70                height: pixel_count(height)?,
71                width: pixel_count(width)?,
72                row_bytes: width,
73            },
74            _marker: PhantomData,
75        })
76    }
77
78    #[must_use]
79    pub const fn as_ptr(&self) -> *const ffi::vImage_Buffer {
80        &self.inner
81    }
82}
83
84pub fn rotate_argb8888(
85    src: &ImageBuffer<'_>,
86    dst: &mut ImageBuffer<'_>,
87    angle_radians: f32,
88    background_color: [u8; 4],
89    flags: u32,
90) -> Result<()> {
91    // SAFETY: Source and destination buffers remain valid for the duration of the call.
92    let status = unsafe {
93        ffi::vImageRotate_ARGB8888(
94            src.as_ptr(),
95            dst.as_ptr(),
96            ptr::null_mut(),
97            angle_radians,
98            background_color.as_ptr(),
99            flags,
100        )
101    };
102    vimage_result(status)
103}
104
105pub fn box_convolve_argb8888(
106    src: &ImageBuffer<'_>,
107    dst: &mut ImageBuffer<'_>,
108    kernel_height: u32,
109    kernel_width: u32,
110    background_color: [u8; 4],
111    flags: u32,
112) -> Result<()> {
113    // SAFETY: Source and destination buffers remain valid for the duration of the call.
114    let status = unsafe {
115        ffi::vImageBoxConvolve_ARGB8888(
116            src.as_ptr(),
117            dst.as_ptr(),
118            ptr::null_mut(),
119            0,
120            0,
121            kernel_height,
122            kernel_width,
123            background_color.as_ptr(),
124            flags,
125        )
126    };
127    vimage_result(status)
128}
129
130pub fn scale_argb8888(src: &ImageBuffer<'_>, dst: &mut ImageBuffer<'_>, flags: u32) -> Result<()> {
131    // SAFETY: Source and destination buffers remain valid for the duration of the call.
132    let status =
133        unsafe { ffi::vImageScale_ARGB8888(src.as_ptr(), dst.as_ptr(), ptr::null_mut(), flags) };
134    vimage_result(status)
135}
136
137pub fn contrast_stretch_planar8(
138    src: &ImageBuffer<'_>,
139    dst: &mut ImageBuffer<'_>,
140    flags: u32,
141) -> Result<()> {
142    // SAFETY: Source and destination buffers remain valid for the duration of the call.
143    let status = unsafe { ffi::vImageContrastStretch_Planar8(src.as_ptr(), dst.as_ptr(), flags) };
144    vimage_result(status)
145}