use std::{
ffi::{c_int, c_void},
fs::File,
io::{BufWriter, Write},
mem::MaybeUninit,
num::NonZeroUsize,
path::Path,
};
use crate::{color::ColorFmt, Error};
pub trait ImageDest {
fn store<Fmt: ColorFmt>(self, image: &libplum_sys::plum_image)
-> std::io::Result<NonZeroUsize>;
}
impl<F: FnMut(&[u8]) -> std::io::Result<u16>> ImageDest for F {
fn store<Fmt: ColorFmt>(
self,
image: &libplum_sys::plum_image,
) -> std::io::Result<NonZeroUsize> {
struct Userdata<E, F>(F, MaybeUninit<E>);
let mut userdata = Userdata(self, MaybeUninit::<std::io::Error>::uninit());
let callback_struct = libplum_sys::plum_callback {
callback: Some(callback_wrapper::<F>),
userdata: &mut userdata as *mut _ as *mut c_void,
};
extern "C" fn callback_wrapper<F: FnMut(&[u8]) -> std::io::Result<u16>>(
userdata: *mut c_void,
buffer: *mut c_void,
buf_len: c_int,
) -> c_int {
let userdata = userdata.cast::<Userdata<_, F>>();
let Userdata(callback, error_return) = unsafe { &mut *userdata };
let buffer = buffer.cast();
let buf_len = buf_len as u16;
let buffer = unsafe { std::slice::from_raw_parts(buffer, buf_len.into()) };
let ret = callback(buffer);
match ret {
Ok(size) => {
assert!(size <= buf_len, "Wrote more bytes than were available!?");
size.into()
}
Err(err) => {
error_return.write(err);
-1
}
}
}
let ptr = &callback_struct as *const _;
let mut error_id: std::ffi::c_uint = 0;
let ret = unsafe {
libplum_sys::plum_store_image(
image as *const _,
ptr as *mut c_void,
libplum_sys::PLUM_MODE_CALLBACK,
&mut error_id as _,
)
};
NonZeroUsize::new(ret).ok_or_else(|| {
#[allow(trivial_numeric_casts)] let error = Error::from_raw(error_id as _);
std::io::Error::new(std::io::ErrorKind::Other, error)
})
}
}
#[derive(Debug)]
pub struct Output<W: Write>(pub W);
impl<W: Write> From<W> for Output<W> {
fn from(value: W) -> Self {
Self(value)
}
}
impl<W: Write> ImageDest for Output<W> {
fn store<Fmt: ColorFmt>(
mut self,
image: &libplum_sys::plum_image,
) -> std::io::Result<NonZeroUsize> {
let callback = |buffer: &[u8]| {
self.0
.write(buffer)
.map(|len| len as u16)
};
callback.store::<Fmt>(image)
}
}
macro_rules! write_shortcut {
($(
impl$(< $($ty_param:ident $(: $bound:tt $(+ $bounds:tt)* )? ),+ $(,)? >)?
ImageDest for $t:ty {}
)*) => {$(
impl$(< $($ty_param $(: $bound $(+ $bounds)* )? ),+ >)?
ImageDest for $t {
fn store<Fmt: ColorFmt>(
self,
image: &libplum_sys::plum_image,
) -> std::io::Result<NonZeroUsize> {
Output(self).store::<Fmt>(image)
}
}
)*};
}
write_shortcut! {
impl ImageDest for File {}
impl ImageDest for &File {}
impl ImageDest for &mut File {}
impl<W: Write> ImageDest for BufWriter<W> {}
impl<W: Write> ImageDest for &mut BufWriter<W> {}
}
impl ImageDest for &mut [u8] {
fn store<Fmt: ColorFmt>(
self,
image: &libplum_sys::plum_image,
) -> std::io::Result<NonZeroUsize> {
let buffer = self as *mut _;
let size = self.len();
let mut error_id: std::ffi::c_uint = 0;
let ret = unsafe {
libplum_sys::plum_store_image(
image as *const _,
buffer as *mut c_void,
size,
&mut error_id as _,
)
};
NonZeroUsize::new(ret).ok_or_else(|| {
#[allow(trivial_numeric_casts)] let error = Error::from_raw(error_id as _);
std::io::Error::new(std::io::ErrorKind::Other, error)
})
}
}
impl ImageDest for &Path {
fn store<Fmt: ColorFmt>(
self,
image: &libplum_sys::plum_image,
) -> std::io::Result<NonZeroUsize> {
let file = File::create(self)?;
Output(file).store::<Fmt>(image)
}
}
impl ImageDest for &str {
fn store<Fmt: ColorFmt>(
self,
image: &libplum_sys::plum_image,
) -> std::io::Result<NonZeroUsize> {
let file = File::create(self)?;
Output(file).store::<Fmt>(image)
}
}