use core::ptr;
use apple_cf::cf::{CFDictionary, CFType};
use apple_cf::cv::CVPixelBuffer;
use crate::error::VTError;
use crate::ffi;
use crate::session;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Rotation {
None,
Clockwise90,
Half180,
CounterClockwise90,
}
impl Rotation {
fn as_cf_string(self) -> ffi::CFStringRef {
unsafe {
match self {
Self::None => ffi::kVTRotation_0,
Self::Clockwise90 => ffi::kVTRotation_CW90,
Self::Half180 => ffi::kVTRotation_180,
Self::CounterClockwise90 => ffi::kVTRotation_CCW90,
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScalingMode {
Normal,
CropSourceToCleanAperture,
Letterbox,
Trim,
}
impl ScalingMode {
fn as_cf_string(self) -> ffi::CFStringRef {
unsafe {
match self {
Self::Normal => ffi::kVTScalingMode_Normal,
Self::CropSourceToCleanAperture => ffi::kVTScalingMode_CropSourceToCleanAperture,
Self::Letterbox => ffi::kVTScalingMode_Letterbox,
Self::Trim => ffi::kVTScalingMode_Trim,
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DownsamplingMode {
Decimate,
Average,
}
impl DownsamplingMode {
fn as_cf_string(self) -> ffi::CFStringRef {
unsafe {
match self {
Self::Decimate => ffi::kVTDownsamplingMode_Decimate,
Self::Average => ffi::kVTDownsamplingMode_Average,
}
}
}
}
pub struct PixelTransferSession {
session: ffi::VTPixelTransferSessionRef,
}
unsafe impl Send for PixelTransferSession {}
crate::utils::retained::vt_retained!(
PixelTransferSession,
field = session,
invalidate = ffi::VTPixelTransferSessionInvalidate,
release = ffi::CFRelease,
);
impl PixelTransferSession {
#[must_use]
pub fn type_id() -> usize {
unsafe { ffi::VTPixelTransferSessionGetTypeID() }
}
pub fn new() -> Result<Self, VTError> {
let mut session: ffi::VTPixelTransferSessionRef = ptr::null_mut();
let status = unsafe {
ffi::VTPixelTransferSessionCreate(ffi::kCFAllocatorDefault, &raw mut session)
};
if status != 0 || session.is_null() {
return Err(VTError::SessionCreateFailed(status));
}
Ok(Self { session })
}
pub unsafe fn copy_property(&self, key: ffi::CFStringRef) -> Result<Option<CFType>, VTError> {
session::copy_property(self.session.cast(), key)
}
pub fn supported_property_dictionary(&self) -> Result<CFDictionary, VTError> {
unsafe { session::copy_supported_property_dictionary(self.session.cast()) }
}
pub fn serializable_properties(&self) -> Result<CFDictionary, VTError> {
unsafe { session::copy_serializable_properties(self.session.cast()) }
}
pub fn set_properties(&self, properties: &CFDictionary) -> Result<(), VTError> {
unsafe { session::set_properties(self.session.cast(), properties) }
}
pub fn set_scaling_mode(&self, mode: ScalingMode) -> Result<(), VTError> {
unsafe {
self.set_property(
ffi::kVTPixelTransferPropertyKey_ScalingMode,
mode.as_cf_string().cast(),
)
}
}
pub fn set_downsampling_mode(&self, mode: DownsamplingMode) -> Result<(), VTError> {
unsafe {
self.set_property(
ffi::kVTPixelTransferPropertyKey_DownsamplingMode,
mode.as_cf_string().cast(),
)
}
}
pub fn set_real_time(&self, real_time: bool) -> Result<(), VTError> {
let cf_value = unsafe {
if real_time {
ffi::kCFBooleanTrue
} else {
ffi::kCFBooleanFalse
}
};
unsafe { self.set_property(ffi::kVTPixelTransferPropertyKey_RealTime, cf_value.cast()) }
}
pub fn transfer(&self, src: &CVPixelBuffer, dst: &CVPixelBuffer) -> Result<(), VTError> {
let status = unsafe {
ffi::VTPixelTransferSessionTransferImage(
self.session,
src.as_ptr().cast(),
dst.as_ptr().cast(),
)
};
if status == 0 {
Ok(())
} else {
Err(VTError::EncodeFailed(status))
}
}
pub unsafe fn set_property(
&self,
key: ffi::CFStringRef,
value: ffi::CFTypeRef,
) -> Result<(), VTError> {
let status = ffi::VTSessionSetProperty(self.session, key, value);
if status != 0 {
return Err(VTError::SetPropertyFailed {
key: "<custom>".to_string(),
status,
});
}
Ok(())
}
}
pub struct PixelRotationSession {
session: ffi::VTPixelRotationSessionRef,
}
unsafe impl Send for PixelRotationSession {}
crate::utils::retained::vt_retained!(
PixelRotationSession,
field = session,
invalidate = ffi::VTPixelRotationSessionInvalidate,
release = ffi::CFRelease,
);
impl PixelRotationSession {
#[must_use]
pub fn type_id() -> usize {
unsafe { ffi::VTPixelRotationSessionGetTypeID() }
}
pub fn new() -> Result<Self, VTError> {
let mut session: ffi::VTPixelRotationSessionRef = ptr::null_mut();
let status = unsafe {
ffi::VTPixelRotationSessionCreate(ffi::kCFAllocatorDefault, &raw mut session)
};
if status != 0 || session.is_null() {
return Err(VTError::SessionCreateFailed(status));
}
Ok(Self { session })
}
pub unsafe fn copy_property(&self, key: ffi::CFStringRef) -> Result<Option<CFType>, VTError> {
session::copy_property(self.session.cast(), key)
}
pub fn supported_property_dictionary(&self) -> Result<CFDictionary, VTError> {
unsafe { session::copy_supported_property_dictionary(self.session.cast()) }
}
pub fn serializable_properties(&self) -> Result<CFDictionary, VTError> {
unsafe { session::copy_serializable_properties(self.session.cast()) }
}
pub fn set_properties(&self, properties: &CFDictionary) -> Result<(), VTError> {
unsafe { session::set_properties(self.session.cast(), properties) }
}
pub fn set_rotation(&self, rotation: Rotation) -> Result<(), VTError> {
let v = rotation.as_cf_string();
let status = unsafe {
ffi::VTSessionSetProperty(
self.session,
ffi::kVTPixelRotationPropertyKey_Rotation,
v.cast(),
)
};
if status != 0 {
return Err(VTError::SetPropertyFailed {
key: "Rotation".to_string(),
status,
});
}
Ok(())
}
pub fn set_flip_horizontal(&self, flip: bool) -> Result<(), VTError> {
let cf = unsafe {
if flip {
ffi::kCFBooleanTrue
} else {
ffi::kCFBooleanFalse
}
};
let status = unsafe {
ffi::VTSessionSetProperty(
self.session,
ffi::kVTPixelRotationPropertyKey_FlipHorizontalOrientation,
cf.cast(),
)
};
if status != 0 {
return Err(VTError::SetPropertyFailed {
key: "FlipHorizontalOrientation".to_string(),
status,
});
}
Ok(())
}
pub fn set_flip_vertical(&self, flip: bool) -> Result<(), VTError> {
let cf = unsafe {
if flip {
ffi::kCFBooleanTrue
} else {
ffi::kCFBooleanFalse
}
};
let status = unsafe {
ffi::VTSessionSetProperty(
self.session,
ffi::kVTPixelRotationPropertyKey_FlipVerticalOrientation,
cf.cast(),
)
};
if status != 0 {
return Err(VTError::SetPropertyFailed {
key: "FlipVerticalOrientation".to_string(),
status,
});
}
Ok(())
}
pub fn rotate(&self, src: &CVPixelBuffer, dst: &CVPixelBuffer) -> Result<(), VTError> {
let status = unsafe {
ffi::VTPixelRotationSessionRotateImage(
self.session,
src.as_ptr().cast(),
dst.as_ptr().cast(),
)
};
if status == 0 {
Ok(())
} else {
Err(VTError::EncodeFailed(status))
}
}
}
#[cfg(test)]
mod tests {
use super::{DownsamplingMode, Rotation, ScalingMode};
use crate::ffi;
#[test]
fn rotation_variants_map_to_expected_cfstring_constants() {
assert_eq!(Rotation::None.as_cf_string(), unsafe { ffi::kVTRotation_0 });
assert_eq!(Rotation::Clockwise90.as_cf_string(), unsafe {
ffi::kVTRotation_CW90
});
assert_eq!(Rotation::Half180.as_cf_string(), unsafe {
ffi::kVTRotation_180
});
assert_eq!(Rotation::CounterClockwise90.as_cf_string(), unsafe {
ffi::kVTRotation_CCW90
});
}
#[test]
fn scaling_modes_map_to_expected_cfstring_constants() {
assert_eq!(ScalingMode::Normal.as_cf_string(), unsafe {
ffi::kVTScalingMode_Normal
});
assert_eq!(
ScalingMode::CropSourceToCleanAperture.as_cf_string(),
unsafe { ffi::kVTScalingMode_CropSourceToCleanAperture }
);
assert_eq!(ScalingMode::Letterbox.as_cf_string(), unsafe {
ffi::kVTScalingMode_Letterbox
});
assert_eq!(ScalingMode::Trim.as_cf_string(), unsafe {
ffi::kVTScalingMode_Trim
});
}
#[test]
fn downsampling_modes_map_to_expected_cfstring_constants() {
assert_eq!(DownsamplingMode::Decimate.as_cf_string(), unsafe {
ffi::kVTDownsamplingMode_Decimate
});
assert_eq!(DownsamplingMode::Average.as_cf_string(), unsafe {
ffi::kVTDownsamplingMode_Average
});
}
}