#![cfg_attr(feature = "nightly", doc(cfg(feature = "output")))]
#![allow(clippy::redundant_allocation)]
#![allow(clippy::new_ret_no_self)]
use crate::argument::CallbackPtr;
use std::{
ffi::CStr,
mem::size_of,
os::raw::{c_char, c_int, c_void},
};
pub mod pixel_format;
pub use pixel_format::*;
pub mod pixel_type;
pub use pixel_type::*;
pub static FERRIS_F32: &str = "ferris_f32";
pub static FERRIS_U32: &str = "ferris_u32";
pub static FERRIS_I32: &str = "ferris_i32";
pub static FERRIS_U16: &str = "ferris_u16";
pub static FERRIS_I16: &str = "ferris_i16";
pub static FERRIS_U8: &str = "ferris_u8";
pub static FERRIS_I8: &str = "ferris_i8";
#[deprecated(
since = "0.9.0",
note = "Use FERRIS_F32, FERRIS_U16, etc. for type-specific drivers"
)]
pub static FERRIS: &str = "ferris_f32";
#[repr(u32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, num_enum::IntoPrimitive)]
pub enum Error {
None = ndspy_sys::PtDspyError::None as _,
NoMemory = ndspy_sys::PtDspyError::NoMemory as _,
Unsupported = ndspy_sys::PtDspyError::Unsupported as _,
BadParameters = ndspy_sys::PtDspyError::BadParams as _,
NoResource = ndspy_sys::PtDspyError::NoResource as _,
Undefined = ndspy_sys::PtDspyError::Undefined as _,
Stop = ndspy_sys::PtDspyError::Stop as _,
}
impl From<Error> for ndspy_sys::PtDspyError {
fn from(item: Error) -> ndspy_sys::PtDspyError {
match item {
Error::None => ndspy_sys::PtDspyError::None,
Error::NoMemory => ndspy_sys::PtDspyError::NoMemory,
Error::Unsupported => ndspy_sys::PtDspyError::Unsupported,
Error::BadParameters => ndspy_sys::PtDspyError::BadParams,
Error::NoResource => ndspy_sys::PtDspyError::NoResource,
Error::Undefined => ndspy_sys::PtDspyError::Undefined,
Error::Stop => ndspy_sys::PtDspyError::Stop,
}
}
}
pub trait FnOpen<'a>: FnMut(
&str,
usize,
usize,
&PixelFormat,
) -> Error
+ 'a {}
#[doc(hidden)]
impl<'a, T: FnMut(&str, usize, usize, &PixelFormat) -> Error + 'a> FnOpen<'a>
for T
{
}
pub trait FnWrite<'a, T: PixelType>: FnMut(
&str,
usize,
usize,
usize,
usize,
usize,
usize,
&PixelFormat,
&[T],
) -> Error
+ 'a {}
#[doc(hidden)]
impl<'a, T: PixelType, F> FnWrite<'a, T> for F where
F: FnMut(
&str,
usize,
usize,
usize,
usize,
usize,
usize,
&PixelFormat,
&[T],
) -> Error
+ 'a
{
}
pub trait FnFinish<'a>: FnMut(
String,
usize,
usize,
PixelFormat,
) -> Error
+ 'a {}
#[doc(hidden)]
impl<'a, F> FnFinish<'a> for F where
F: FnMut(String, usize, usize, PixelFormat) -> Error + 'a
{
}
enum Query {}
trait FnQuery<'a>: FnMut(Query) -> Error + 'a {}
impl<'a, T: FnMut(Query) -> Error + 'a> FnQuery<'a> for T {}
pub struct OpenCallback<'a>(Box<Box<Box<dyn FnOpen<'a>>>>);
impl<'a> OpenCallback<'a> {
pub fn new<F>(fn_open: F) -> Self
where
F: FnOpen<'a>,
{
OpenCallback(Box::new(Box::new(Box::new(fn_open))))
}
}
impl CallbackPtr for OpenCallback<'_> {
#[doc(hidden)]
fn to_ptr(self) -> *const core::ffi::c_void {
Box::into_raw(self.0) as *const _ as _
}
}
pub struct WriteCallback<'a, T: PixelType>(Box<Box<Box<dyn FnWrite<'a, T>>>>);
impl<'a, T: PixelType> WriteCallback<'a, T> {
pub fn new<F>(fn_write: F) -> Self
where
F: FnWrite<'a, T>,
{
WriteCallback(Box::new(Box::new(Box::new(fn_write))))
}
}
impl<T: PixelType> CallbackPtr for WriteCallback<'_, T> {
#[doc(hidden)]
fn to_ptr(self) -> *const core::ffi::c_void {
Box::into_raw(self.0) as *const _ as _
}
}
pub struct FinishCallback<'a>(Box<Box<Box<dyn FnFinish<'a>>>>);
impl<'a> FinishCallback<'a> {
pub fn new<F>(fn_finish: F) -> Self
where
F: FnFinish<'a>,
{
FinishCallback(Box::new(Box::new(Box::new(fn_finish))))
}
}
impl CallbackPtr for FinishCallback<'_> {
#[doc(hidden)]
fn to_ptr(self) -> *const core::ffi::c_void {
Box::into_raw(self.0) as *const _ as _
}
}
struct DisplayData<'a, T: PixelType> {
name: String,
width: usize,
height: usize,
pixel_format: PixelFormat,
fn_write: Option<Box<Box<Box<dyn FnWrite<'a, T>>>>>,
fn_finish: Option<Box<Box<Box<dyn FnFinish<'a>>>>>,
fn_query: Option<Box<Box<Box<dyn FnQuery<'a>>>>>,
_phantom: std::marker::PhantomData<T>,
}
fn extract_callback<T: ?Sized>(
name: &str,
type_: u8,
len: usize,
parameters: &[ndspy_sys::UserParameter],
) -> Option<Box<Box<Box<T>>>> {
for p in parameters.iter() {
if p.name.is_null() {
continue;
}
let p_name = match unsafe { CStr::from_ptr(p.name) }.to_str() {
Ok(name) => name,
Err(_) => continue,
};
if name == p_name
&& type_ == p.valueType as _
&& len == p.valueCount as _
{
if !p.value.is_null() {
return Some(unsafe {
Box::from_raw(p.value as *mut Box<Box<T>>)
});
} else {
break;
}
}
}
None
}
pub(crate) extern "C" fn image_open<T: PixelType>(
image_handle_ptr: *mut ndspy_sys::PtDspyImageHandle,
_driver_name: *const c_char,
output_filename: *const c_char,
width: c_int,
height: c_int,
parameters_count: c_int,
parameters: *const ndspy_sys::UserParameter,
format_count: c_int,
format: *mut ndspy_sys::PtDspyDevFormat,
flag_stuff: *mut ndspy_sys::PtFlagStuff,
) -> ndspy_sys::PtDspyError {
match std::panic::catch_unwind(|| {
if (image_handle_ptr.is_null())
|| (output_filename.is_null())
|| format.is_null()
|| (format_count <= 0)
|| ((parameters_count > 0) && parameters.is_null())
{
return Error::BadParameters.into();
}
let parameters = unsafe {
std::slice::from_raw_parts(parameters, parameters_count as _)
};
let mut display_data = Box::new(DisplayData::<T> {
name: {
let c_str = unsafe { CStr::from_ptr(output_filename) };
c_str.to_string_lossy().into_owned()
},
width: width as _,
height: height as _,
pixel_format: PixelFormat::default(),
fn_write: extract_callback::<dyn FnWrite<T>>(
"callback.write",
b'p',
1,
parameters,
),
fn_finish: extract_callback::<dyn FnFinish>(
"callback.finish",
b'p',
1,
parameters,
),
fn_query: None,
_phantom: std::marker::PhantomData,
});
let format = unsafe {
std::slice::from_raw_parts_mut(format, format_count as _)
};
format.iter_mut().for_each(|f| f.type_ = T::NDSPY_TYPE);
display_data.pixel_format = PixelFormat::new(format);
let error = if let Some(mut fn_open) =
extract_callback::<dyn FnOpen>("callback.open", b'p', 1, parameters)
{
let error = fn_open(
&display_data.name,
width as _,
height as _,
&display_data.pixel_format,
);
Box::leak(fn_open);
error
} else {
Error::None
};
unsafe {
*image_handle_ptr = Box::into_raw(display_data) as _;
(*flag_stuff).flags &=
!(ndspy_sys::PkDspyFlagsWantsEmptyBuckets as i32);
}
error.into()
}) {
Ok(result) => result,
Err(_) => {
Error::Undefined.into()
}
}
}
#[unsafe(no_mangle)]
pub(crate) extern "C" fn image_query(
_image_handle_ptr: ndspy_sys::PtDspyImageHandle,
query_type: ndspy_sys::PtDspyQueryType,
data_len: c_int,
data: *mut c_void,
) -> ndspy_sys::PtDspyError {
match std::panic::catch_unwind(|| {
match query_type {
ndspy_sys::PtDspyQueryType::RenderProgress => {
if (data_len as usize)
< core::mem::size_of::<ndspy_sys::PtDspyRenderProgressFuncPtr>()
|| data.is_null()
{
Error::BadParameters
} else {
unsafe {
let func_ptr = data as *mut ndspy_sys::PtDspyRenderProgressFuncPtr;
*func_ptr = Some(image_progress);
}
Error::None
}
}
ndspy_sys::PtDspyQueryType::Progressive => {
if (data_len as usize) < size_of::<ndspy_sys::PtDspyProgressiveInfo>()
|| data.is_null()
{
Error::BadParameters
} else {
unsafe {
let info = data as *mut ndspy_sys::PtDspyProgressiveInfo;
(*info).acceptProgressive = 1;
}
Error::None
}
}
ndspy_sys::PtDspyQueryType::Thread => {
if (data_len as usize) < size_of::<ndspy_sys::PtDspyThreadInfo>()
|| data.is_null()
{
Error::BadParameters
} else {
unsafe {
let info = data as *mut ndspy_sys::PtDspyThreadInfo;
(*info).multithread = 1;
}
Error::None
}
}
ndspy_sys::PtDspyQueryType::Cooked => {
if (data_len as usize) < size_of::<ndspy_sys::PtDspyCookedInfo>()
|| data.is_null()
{
Error::BadParameters
} else {
unsafe {
let info = data as *mut ndspy_sys::PtDspyCookedInfo;
(*info).cooked = 1;
}
Error::None
}
}
ndspy_sys::PtDspyQueryType::Stop => Error::None,
ndspy_sys::PtDspyQueryType::Overwrite => {
if (data_len as usize) < size_of::<ndspy_sys::PtDspyOverwriteInfo>()
|| data.is_null()
{
Error::BadParameters
} else {
unsafe {
let info = data as *mut ndspy_sys::PtDspyOverwriteInfo;
(*info).overwrite = 1;
}
Error::None
}
}
_ => Error::Unsupported,
}
.into()
}) {
Ok(result) => result,
Err(_) => {
Error::Undefined.into()
}
}
}
pub(crate) extern "C" fn image_write<T: PixelType>(
image_handle_ptr: ndspy_sys::PtDspyImageHandle,
x_min: c_int,
x_max_plus_one: c_int,
y_min: c_int,
y_max_plus_one: c_int,
_entry_size: c_int,
pixel_data: *const u8,
) -> ndspy_sys::PtDspyError {
match std::panic::catch_unwind(|| {
if image_handle_ptr.is_null() {
return Error::BadParameters.into();
}
let display_data =
unsafe { &mut *(image_handle_ptr as *mut DisplayData<T>) };
let channels = display_data.pixel_format.channels();
let bucket_width = (x_max_plus_one - x_min) as usize;
let bucket_height = (y_max_plus_one - y_min) as usize;
let bucket_pixel_count = bucket_width * bucket_height * channels;
if pixel_data.is_null() {
return Error::None.into();
}
let bucket_data = unsafe {
std::slice::from_raw_parts(
pixel_data as *const T,
bucket_pixel_count,
)
};
if let Some(ref mut fn_write) = display_data.fn_write {
fn_write(
&display_data.name,
display_data.width,
display_data.height,
x_min as _,
x_max_plus_one as _,
y_min as _,
y_max_plus_one as _,
&display_data.pixel_format,
bucket_data, )
} else {
Error::None
}
.into()
}) {
Ok(result) => result,
Err(_) => {
Error::Undefined.into()
}
}
}
pub(crate) extern "C" fn image_close<T: PixelType>(
image_handle_ptr: ndspy_sys::PtDspyImageHandle,
) -> ndspy_sys::PtDspyError {
match std::panic::catch_unwind(|| {
if image_handle_ptr.is_null() {
return Error::BadParameters.into();
}
let mut display_data =
unsafe { Box::from_raw(image_handle_ptr as *mut DisplayData<T>) };
let error = if let Some(ref mut fn_finish) = display_data.fn_finish {
fn_finish(
std::mem::take(&mut display_data.name),
display_data.width,
display_data.height,
std::mem::take(&mut display_data.pixel_format),
)
} else {
Error::None
};
if let Some(fn_write) = display_data.fn_write.take() {
Box::leak(fn_write);
}
if let Some(fn_query) = display_data.fn_query.take() {
Box::leak(fn_query);
}
if let Some(fn_finish) = display_data.fn_finish.take() {
Box::leak(fn_finish);
}
error.into()
}) {
Ok(result) => result,
Err(_) => {
Error::Undefined.into()
}
}
}
#[unsafe(no_mangle)]
extern "C" fn image_progress(
_image_handle_ptr: ndspy_sys::PtDspyImageHandle,
_progress: f32,
) -> ndspy_sys::PtDspyError {
Error::None.into()
}
pub struct AccumulatingCallbacks<T: PixelType> {
_phantom: std::marker::PhantomData<T>,
}
impl<T: PixelType> AccumulatingCallbacks<T> {
pub fn new<'a, F>(
mut on_finish: F,
) -> (WriteCallback<'a, T>, FinishCallback<'a>)
where
F: FnMut(String, usize, usize, PixelFormat, Vec<T>) -> Error + 'a,
{
use std::sync::{Arc, Mutex};
struct AccumState<T: PixelType> {
buffer: Vec<T>,
width: usize,
height: usize,
channels: usize,
initialized: bool,
}
let state = Arc::new(Mutex::new(AccumState::<T> {
buffer: Vec::new(),
width: 0,
height: 0,
channels: 0,
initialized: false,
}));
let write_state = state.clone();
let finish_state = state;
let write = WriteCallback::new(
move |_name: &str,
width: usize,
height: usize,
x_min: usize,
x_max_plus_one: usize,
y_min: usize,
y_max_plus_one: usize,
format: &PixelFormat,
bucket_data: &[T]| {
let mut state = write_state.lock().unwrap();
if !state.initialized {
state.width = width;
state.height = height;
state.channels = format.channels();
state.buffer =
vec![T::default(); width * height * state.channels];
state.initialized = true;
}
let bucket_width = x_max_plus_one - x_min;
let channels = state.channels;
for y in y_min..y_max_plus_one {
let src_start = (y - y_min) * bucket_width * channels;
let dst_start = (y * state.width + x_min) * channels;
let row_len = bucket_width * channels;
state.buffer[dst_start..dst_start + row_len]
.copy_from_slice(
&bucket_data[src_start..src_start + row_len],
);
}
Error::None
},
);
let finish = FinishCallback::new(
move |name: String,
width: usize,
height: usize,
format: PixelFormat| {
let mut state = finish_state.lock().unwrap();
let buffer = std::mem::take(&mut state.buffer);
drop(state);
on_finish(name, width, height, format, buffer)
},
);
(write, finish)
}
}