use std::{
ffi::{c_int, c_void},
fs::File,
io::{BufReader, Read},
mem::MaybeUninit,
path::Path,
ptr::NonNull,
};
use crate::{color::ColorFmt, Error};
use super::AlphaMode;
#[derive(Debug, Clone, Copy, Default)]
pub struct LoadFlags {
pub remove_alpha: bool,
pub palette_sort: PaletteSort,
pub sort_existing: bool,
pub reduce_palette: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum PaletteSort {
#[default]
LightFirst,
DarkFirst,
}
pub trait ImageSource {
fn load<Fmt: ColorFmt>(
self,
flags: LoadFlags,
alpha_invert: AlphaMode,
palette_mode: PaletteMode,
mem_limit: usize,
) -> std::io::Result<NonNull<libplum_sys::plum_image>>;
}
impl<F: FnMut(&mut [u8]) -> std::io::Result<u16>> ImageSource for F {
fn load<Fmt: ColorFmt>(
self,
flags: LoadFlags,
alpha_invert: AlphaMode,
palette_mode: PaletteMode,
mem_limit: usize,
) -> std::io::Result<NonNull<libplum_sys::plum_image>> {
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(&mut [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;
unsafe { std::ptr::write_bytes(buffer, 0, buf_len.into()) };
let buffer = unsafe { std::slice::from_raw_parts_mut(buffer, buf_len.into()) };
let ret = callback(buffer);
match ret {
Ok(size) => {
assert!(size <= buf_len, "Read more bytes than were available?!");
size.into()
}
Err(err) => {
error_return.write(err);
-1
}
}
}
let ptr = &callback_struct as *const _;
let raw_flags = flags.to_raw::<Fmt>(alpha_invert, palette_mode);
let mut error: std::ffi::c_uint = 0;
assert!(mem_limit <= max_nb_pixels::<Fmt>());
let image = unsafe {
#[allow(trivial_numeric_casts)] libplum_sys::plum_load_image_limited(
ptr as *const c_void,
libplum_sys::PLUM_MODE_CALLBACK,
raw_flags as _,
mem_limit,
&mut error as _,
)
};
NonNull::new(image).ok_or_else(|| {
#[allow(trivial_numeric_casts)] let error = Error::from_raw(error as _);
std::io::Error::new(std::io::ErrorKind::Other, error)
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Input<R: Read>(pub R);
impl<R: Read> From<R> for Input<R> {
fn from(value: R) -> Self {
Self(value)
}
}
impl<R: Read> ImageSource for Input<R> {
fn load<Fmt: ColorFmt>(
mut self,
flags: LoadFlags,
alpha_invert: AlphaMode,
palette_mode: PaletteMode,
mem_limit: usize,
) -> std::io::Result<NonNull<libplum_sys::plum_image>> {
let callback = |buffer: &mut [u8]| {
self.0
.read(buffer)
.map(|len| len as u16)
};
callback.load::<Fmt>(flags, alpha_invert, palette_mode, mem_limit)
}
}
macro_rules! read_shortcut {
($(
impl$(< $($ty_param:ident $(: $bound:tt $(+ $bounds:tt)* )? ),+ $(,)? >)?
ImageSource for $t:ty {}
)*) => {$(
impl$(< $($ty_param $(: $bound $(+ $bounds)* )? ),+ >)?
ImageSource for $t {
fn load<Fmt: ColorFmt>(
self,
flags: LoadFlags,
alpha_invert: AlphaMode,
palette_mode: PaletteMode,
mem_limit: usize,
) -> std::io::Result<NonNull<libplum_sys::plum_image>> {
Input(self).load::<Fmt>(flags, alpha_invert, palette_mode, mem_limit)
}
}
)*};
}
read_shortcut! {
impl ImageSource for File {}
impl ImageSource for &File {}
impl ImageSource for &mut File {}
impl<R: Read> ImageSource for BufReader<R> {}
impl<R: Read> ImageSource for &mut BufReader<R> {}
}
impl ImageSource for &[u8] {
fn load<Fmt: ColorFmt>(
self,
flags: LoadFlags,
alpha_invert: AlphaMode,
palette_mode: PaletteMode,
mem_limit: usize,
) -> std::io::Result<NonNull<libplum_sys::plum_image>> {
let buffer = self as *const _ as *const c_void;
let size = self.len();
let raw_flags = flags.to_raw::<Fmt>(alpha_invert, palette_mode);
let mut error: std::ffi::c_uint = 0;
assert!(mem_limit <= max_nb_pixels::<Fmt>());
let image = unsafe {
#[allow(trivial_numeric_casts)] libplum_sys::plum_load_image_limited(
buffer,
size,
raw_flags as _,
mem_limit,
&mut error as _,
)
};
NonNull::new(image).ok_or_else(|| {
#[allow(trivial_numeric_casts)] let error = Error::from_raw(error as _);
std::io::Error::new(std::io::ErrorKind::Other, error)
})
}
}
impl ImageSource for &mut [u8] {
fn load<Fmt: ColorFmt>(
self,
flags: LoadFlags,
alpha_invert: AlphaMode,
palette_mode: PaletteMode,
mem_limit: usize,
) -> std::io::Result<NonNull<libplum_sys::plum_image>> {
ImageSource::load::<Fmt>(self as &[u8], flags, alpha_invert, palette_mode, mem_limit)
}
}
impl<const N: usize> ImageSource for [u8; N] {
fn load<Fmt: ColorFmt>(
self,
flags: LoadFlags,
alpha_invert: AlphaMode,
palette_mode: PaletteMode,
mem_limit: usize,
) -> std::io::Result<NonNull<libplum_sys::plum_image>> {
ImageSource::load::<Fmt>(
self.as_slice(),
flags,
alpha_invert,
palette_mode,
mem_limit,
)
}
}
impl<const N: usize> ImageSource for &[u8; N] {
fn load<Fmt: ColorFmt>(
self,
flags: LoadFlags,
alpha_invert: AlphaMode,
palette_mode: PaletteMode,
mem_limit: usize,
) -> std::io::Result<NonNull<libplum_sys::plum_image>> {
ImageSource::load::<Fmt>(
self.as_slice(),
flags,
alpha_invert,
palette_mode,
mem_limit,
)
}
}
impl<const N: usize> ImageSource for &mut [u8; N] {
fn load<Fmt: ColorFmt>(
self,
flags: LoadFlags,
alpha_invert: AlphaMode,
palette_mode: PaletteMode,
mem_limit: usize,
) -> std::io::Result<NonNull<libplum_sys::plum_image>> {
ImageSource::load::<Fmt>(
self.as_slice(),
flags,
alpha_invert,
palette_mode,
mem_limit,
)
}
}
impl ImageSource for &Path {
fn load<Fmt: ColorFmt>(
self,
flags: LoadFlags,
alpha_invert: AlphaMode,
palette_mode: PaletteMode,
mem_limit: usize,
) -> std::io::Result<NonNull<libplum_sys::plum_image>> {
let file = File::open(self)?;
Input(file).load::<Fmt>(flags, alpha_invert, palette_mode, mem_limit)
}
}
impl ImageSource for &str {
fn load<Fmt: ColorFmt>(
self,
flags: LoadFlags,
alpha_invert: AlphaMode,
palette_mode: PaletteMode,
mem_limit: usize,
) -> std::io::Result<NonNull<libplum_sys::plum_image>> {
let file = File::open(self)?;
Input(file).load::<Fmt>(flags, alpha_invert, palette_mode, mem_limit)
}
}
impl LoadFlags {
pub fn to_raw<Fmt: ColorFmt>(
self,
alpha_invert: AlphaMode,
palette_mode: PaletteMode,
) -> libplum_sys::plum_flags {
let mut raw = Fmt::raw_constant();
raw |= alpha_invert.to_raw();
if self.remove_alpha {
raw |= libplum_sys::PLUM_ALPHA_REMOVE;
}
raw |= palette_mode.to_raw();
raw |= self.palette_sort.to_raw();
if self.sort_existing {
raw |= libplum_sys::PLUM_SORT_EXISTING;
}
if self.reduce_palette {
raw |= libplum_sys::PLUM_PALETTE_REDUCE;
}
raw
}
}
pub const fn max_nb_pixels<Fmt: ColorFmt>() -> usize {
isize::MAX as usize / std::mem::size_of::<Fmt::Raw>()
}
#[doc(hidden)]
#[derive(Debug, Clone, Copy)]
pub enum PaletteMode {
None,
Load,
Generate,
Force,
}
impl PaletteMode {
pub fn to_raw(self) -> libplum_sys::plum_flags {
match self {
PaletteMode::None => libplum_sys::PLUM_PALETTE_NONE,
PaletteMode::Load => libplum_sys::PLUM_PALETTE_LOAD,
PaletteMode::Generate => libplum_sys::PLUM_PALETTE_GENERATE,
PaletteMode::Force => libplum_sys::PLUM_PALETTE_FORCE,
}
}
}
impl PaletteSort {
pub fn to_raw(self) -> libplum_sys::plum_flags {
match self {
PaletteSort::LightFirst => libplum_sys::PLUM_SORT_LIGHT_FIRST,
PaletteSort::DarkFirst => libplum_sys::PLUM_SORT_DARK_FIRST,
}
}
}
impl AlphaMode {
pub fn to_raw(self) -> libplum_sys::plum_flags {
match self {
AlphaMode::ZeroIsOpaque => 0,
AlphaMode::ZeroIsTransparent => libplum_sys::PLUM_ALPHA_INVERT,
}
}
}