use std::{marker::PhantomData, mem::MaybeUninit, num::NonZeroUsize};
use crate::color::ColorFmt;
mod frame;
pub use frame::{Frame, FrameMut, Frames};
mod load;
use load::PaletteMode;
pub use load::{max_nb_pixels, ImageSource, Input, LoadFlags, PaletteSort};
mod metadata;
use metadata::{Metadata, MetadataMut};
mod raw;
use raw::PlumWrapper;
mod store;
pub use store::{ImageDest, Output};
#[derive(Debug, Clone)]
pub struct DirectImage<Fmt: ColorFmt>(raw::ImgWrapper, PhantomData<Fmt>);
pub type DirectImage16 = DirectImage<crate::color::Rgb16>;
pub type DirectImage32 = DirectImage<crate::color::Rgb32>;
pub type DirectImage32X = DirectImage<crate::color::Rgb32X>;
pub type DirectImage64 = DirectImage<crate::color::Rgb64>;
#[derive(Debug, Clone)]
pub struct PalettedImage<Fmt: ColorFmt>(raw::ImgWrapper, PhantomData<Fmt>);
pub type PalettedImage16 = PalettedImage<crate::color::Rgb16>;
pub type PalettedImage32 = PalettedImage<crate::color::Rgb32>;
pub type PalettedImage32X = PalettedImage<crate::color::Rgb32X>;
pub type PalettedImage64 = PalettedImage<crate::color::Rgb64>;
#[derive(Debug, Clone)]
pub enum DynImage<Fmt: ColorFmt> {
Direct(DirectImage<Fmt>),
Paletted(PalettedImage<Fmt>),
}
pub type DynImage16 = DynImage<crate::color::Rgb16>;
pub type DynImage32 = DynImage<crate::color::Rgb32>;
pub type DynImage32X = DynImage<crate::color::Rgb32X>;
pub type DynImage64 = DynImage<crate::color::Rgb64>;
pub trait Image<Fmt: ColorFmt>: PlumWrapper<Fmt> {
fn nb_frames(&self) -> usize;
fn width(&self) -> usize;
fn height(&self) -> usize;
fn nb_pixels(&self) -> usize {
self.height() * self.width()
}
fn format(&self) -> ImageFormat;
fn set_format(&mut self, format: ImageFormat);
fn alpha_mode(&self) -> AlphaMode;
fn set_alpha_mode(&mut self, mode: AlphaMode);
fn palette(&self) -> Option<&[Fmt]>;
fn pixel(&self, frame_idx: usize, x: usize, y: usize) -> Fmt;
fn pixel_mut(&mut self, frame_idx: usize, x: usize, y: usize) -> &mut Fmt;
fn frame(&self, frame_idx: usize) -> Frame<'_, Fmt, Self>
where
Self: Sized,
{
Frame::new(self, frame_idx)
}
fn frame_mut(&mut self, frame_idx: usize) -> FrameMut<'_, Fmt, Self>
where
Self: Sized,
{
FrameMut::new(self, frame_idx)
}
fn frames(&self) -> Frames<'_, Fmt, Self>
where
Self: Sized,
{
Frames {
img: self,
begin: 0,
end: self.nb_frames(),
_pix_fmt: PhantomData,
}
}
fn foreach_frame_mut<F: FnMut(FrameMut<'_, Fmt, Self>)>(&mut self, mut f: F)
where
Self: Sized,
{
for i in 0..self.nb_frames() {
f(self.frame_mut(i))
}
}
fn try_foreach_frame_mut<E, F: FnMut(FrameMut<'_, Fmt, Self>) -> Result<(), E>>(
&mut self,
mut f: F,
) -> Result<(), E>
where
Self: Sized,
{
for i in 0..self.nb_frames() {
f(self.frame_mut(i))?
}
Ok(())
}
fn metadata(&self) -> Metadata<'_>;
fn metadata_mut(&mut self) -> MetadataMut<'_>;
fn store<Dest: ImageDest>(&self, output: Dest) -> std::io::Result<NonZeroUsize>
where
Self: Sized;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum AlphaMode {
#[default]
ZeroIsOpaque,
ZeroIsTransparent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive] pub enum ImageFormat {
None = libplum_sys::PLUM_IMAGE_NONE as isize,
Bmp = libplum_sys::PLUM_IMAGE_BMP as isize,
Gif = libplum_sys::PLUM_IMAGE_GIF as isize,
Png = libplum_sys::PLUM_IMAGE_PNG as isize,
Apng = libplum_sys::PLUM_IMAGE_APNG as isize,
Jpeg = libplum_sys::PLUM_IMAGE_JPEG as isize,
Pnm = libplum_sys::PLUM_IMAGE_PNM as isize,
}
impl<Fmt: ColorFmt> DirectImage<Fmt> {
pub fn new_zeroed(
format: ImageFormat,
alpha_mode: AlphaMode,
nb_frames: usize,
width: usize,
height: usize,
) -> Self {
let wrapper = raw::ImgWrapper::new_zeroed::<Fmt>(
nb_frames.try_into().unwrap(),
width.try_into().unwrap(),
height.try_into().unwrap(),
);
debug_assert_eq!(wrapper.palette, std::ptr::null_mut());
let mut this = Self(wrapper, PhantomData);
this.set_format(format);
this.set_alpha_mode(alpha_mode);
this
}
pub fn from_fn<F: FnMut(usize, usize, usize) -> Fmt>(
format: ImageFormat,
alpha_mode: AlphaMode,
nb_frames: usize,
width: usize,
height: usize,
mut init: F,
) -> Self {
let wrapper = unsafe {
raw::ImgWrapper::new(
nb_frames.try_into().unwrap(),
width.try_into().unwrap(),
height.try_into().unwrap(),
|pixels| {
debug_assert_eq!(nb_frames * height * width, pixels.len());
for frame in 0..nb_frames {
for y in 0..height {
for x in 0..width {
pixels[(frame * height + y) * width + x].write(init(frame, x, y));
}
}
}
},
)
};
debug_assert_eq!(wrapper.palette, std::ptr::null_mut());
let mut this = Self(wrapper, PhantomData);
this.set_format(format);
this.set_alpha_mode(alpha_mode);
this
}
pub unsafe fn new<F: FnOnce(&mut [MaybeUninit<Fmt>])>(
format: ImageFormat,
alpha_mode: AlphaMode,
nb_frames: usize,
width: usize,
height: usize,
init: F,
) -> Self {
let wrapper = unsafe {
raw::ImgWrapper::new::<Fmt, _>(
nb_frames.try_into().unwrap(),
width.try_into().unwrap(),
height.try_into().unwrap(),
init,
)
};
debug_assert_eq!(wrapper.palette, std::ptr::null_mut());
let mut this = Self(wrapper, PhantomData);
this.set_format(format);
this.set_alpha_mode(alpha_mode);
this
}
pub fn load<Src: ImageSource>(
input: Src,
flags: LoadFlags,
alpha_mode: AlphaMode,
) -> std::io::Result<Self> {
Self::load_limited(input, flags, alpha_mode, max_nb_pixels::<Fmt>())
}
pub fn load_limited<Src: ImageSource>(
input: Src,
flags: LoadFlags,
alpha_mode: AlphaMode,
nb_pixels_max: usize,
) -> std::io::Result<Self> {
let image = input.load::<Fmt>(flags, alpha_mode, PaletteMode::None, nb_pixels_max)?;
let wrapper = raw::ImgWrapper::from(image);
debug_assert_eq!(wrapper.palette, std::ptr::null_mut());
Ok(Self(wrapper, PhantomData))
}
pub fn pixel_at(&self, frame: usize, x: usize, y: usize) -> &Fmt {
let height = self.height();
let width = self.width();
&(unsafe { self.pix_array::<Fmt>() })[(frame * height + y) * width + x]
}
pub fn pixel_at_mut(&mut self, frame: usize, x: usize, y: usize) -> &mut Fmt {
let height = self.height();
let width = self.width();
&mut (unsafe { self.pix_array_mut::<Fmt>() })[(frame * height + y) * width + x]
}
}
impl<Fmt: ColorFmt> PalettedImage<Fmt> {
pub fn new_zeroed<It: IntoIterator<Item = Fmt>>(
format: ImageFormat,
alpha_mode: AlphaMode,
nb_frames: usize,
width: usize,
height: usize,
palette: It,
) -> Result<Self, usize> {
let mut wrapper = raw::ImgWrapper::new_zeroed::<Fmt>(
nb_frames.try_into().unwrap(),
width.try_into().unwrap(),
height.try_into().unwrap(),
);
debug_assert_eq!(wrapper.palette, std::ptr::null_mut());
let palette_ptr = wrapper.collect(palette);
wrapper.palette = palette_ptr.as_ptr().cast();
let slice_len = unsafe { palette_ptr.as_ref() }.len();
let Some(max_index) = slice_len.checked_sub(1).and_then(|max| max.try_into().ok()) else {
return Err(slice_len);
};
wrapper.max_palette_index = max_index;
let mut this = Self(wrapper, PhantomData);
this.set_format(format);
this.set_alpha_mode(alpha_mode);
Ok(this)
}
pub fn from_fn<F: FnMut(usize, usize, usize) -> Fmt, It: IntoIterator<Item = Fmt>>(
format: ImageFormat,
alpha_mode: AlphaMode,
nb_frames: usize,
width: usize,
height: usize,
mut init: F,
palette: It,
) -> Result<Self, usize> {
let mut wrapper = unsafe {
raw::ImgWrapper::new(
nb_frames.try_into().unwrap(),
width.try_into().unwrap(),
height.try_into().unwrap(),
|pixels| {
debug_assert_eq!(nb_frames * height * width, pixels.len());
for frame in 0..nb_frames {
for y in 0..height {
for x in 0..width {
pixels[(frame * height + y) * width + x].write(init(frame, x, y));
}
}
}
},
)
};
debug_assert_eq!(wrapper.palette, std::ptr::null_mut());
let palette_ptr = wrapper.collect(palette);
wrapper.palette = palette_ptr.as_ptr().cast();
let slice_len = unsafe { palette_ptr.as_ref() }.len();
let Some(max_index) = slice_len.checked_sub(1).and_then(|max| max.try_into().ok()) else {
return Err(slice_len);
};
wrapper.max_palette_index = max_index;
let mut this = Self(wrapper, PhantomData);
this.set_format(format);
this.set_alpha_mode(alpha_mode);
Ok(this)
}
pub unsafe fn new<F: FnOnce(&mut [MaybeUninit<Fmt>]), It: IntoIterator<Item = Fmt>>(
format: ImageFormat,
alpha_mode: AlphaMode,
nb_frames: usize,
width: usize,
height: usize,
init: F,
palette: It,
) -> Result<Self, usize> {
let mut wrapper = unsafe {
raw::ImgWrapper::new::<Fmt, _>(
nb_frames.try_into().unwrap(),
width.try_into().unwrap(),
height.try_into().unwrap(),
init,
)
};
debug_assert_eq!(wrapper.palette, std::ptr::null_mut());
let palette_ptr = wrapper.collect(palette);
wrapper.palette = palette_ptr.as_ptr().cast();
let slice_len = unsafe { palette_ptr.as_ref() }.len();
let Some(max_index) = slice_len.checked_sub(1).and_then(|max| max.try_into().ok()) else {
return Err(slice_len);
};
wrapper.max_palette_index = max_index;
let mut this = Self(wrapper, PhantomData);
this.set_format(format);
this.set_alpha_mode(alpha_mode);
Ok(this)
}
pub fn load<Src: ImageSource>(
input: Src,
flags: LoadFlags,
alpha_mode: AlphaMode,
) -> std::io::Result<Self> {
Self::load_limited(input, flags, alpha_mode, max_nb_pixels::<Fmt>())
}
pub fn load_limited<Src: ImageSource>(
input: Src,
flags: LoadFlags,
alpha_mode: AlphaMode,
nb_pixels_max: usize,
) -> std::io::Result<Self> {
let image = input.load::<Fmt>(flags, alpha_mode, PaletteMode::Force, nb_pixels_max)?;
let wrapper = raw::ImgWrapper::from(image);
debug_assert_ne!(wrapper.palette, std::ptr::null_mut());
Ok(Self(wrapper, PhantomData))
}
pub fn palette(&self) -> &[Fmt] {
debug_assert_ne!(self.as_img().palette, std::ptr::null_mut());
let ptr = self.as_img().palette.cast();
let len = usize::from(self.as_img().max_palette_index) + 1;
unsafe { std::slice::from_raw_parts(ptr, len) }
}
pub fn palette_mut(&mut self) -> &mut [Fmt] {
debug_assert_ne!(self.as_img().palette, std::ptr::null_mut());
let ptr = self.as_img().palette.cast();
let len = usize::from(self.as_img().max_palette_index) + 1;
unsafe { std::slice::from_raw_parts_mut(ptr, len) }
}
pub fn index_at(&self, frame: usize, x: usize, y: usize) -> &u8 {
let height = self.height();
let width = self.width();
&(unsafe { self.pix_array::<u8>() })[(frame * height + y) * width + x]
}
pub fn index_at_mut(&mut self, frame: usize, x: usize, y: usize) -> &mut u8 {
let height = self.height();
let width = self.width();
&mut (unsafe { self.pix_array_mut::<u8>() })[(frame * height + y) * width + x]
}
}
impl<Fmt: ColorFmt> DynImage<Fmt> {
pub fn load<Src: ImageSource>(
input: Src,
flags: LoadFlags,
alpha_mode: AlphaMode,
prefer_palette: bool,
) -> std::io::Result<Self> {
Self::load_limited(
input,
flags,
alpha_mode,
prefer_palette,
max_nb_pixels::<Fmt>(),
)
}
pub fn load_limited<Src: ImageSource>(
input: Src,
flags: LoadFlags,
alpha_mode: AlphaMode,
prefer_palette: bool,
nb_pixels_max: usize,
) -> std::io::Result<Self> {
let image = input.load::<Fmt>(
flags,
alpha_mode,
if prefer_palette {
PaletteMode::Generate
} else {
PaletteMode::Load
},
nb_pixels_max,
);
image.map(|img| {
let wrapper = raw::ImgWrapper::from(img);
if wrapper.palette.is_null() {
DirectImage(wrapper, PhantomData).into()
} else {
PalettedImage(wrapper, PhantomData).into()
}
})
}
pub fn as_dyn_image(&self) -> &dyn Image<Fmt> {
match self {
DynImage::Direct(img) => img,
DynImage::Paletted(img) => img,
}
}
pub fn as_mut_dyn_image(&mut self) -> &mut dyn Image<Fmt> {
match self {
DynImage::Direct(img) => img,
DynImage::Paletted(img) => img,
}
}
}
impl<Fmt: ColorFmt> From<DirectImage<Fmt>> for DynImage<Fmt> {
fn from(value: DirectImage<Fmt>) -> Self {
Self::Direct(value)
}
}
impl<Fmt: ColorFmt> From<PalettedImage<Fmt>> for DynImage<Fmt> {
fn from(value: PalettedImage<Fmt>) -> Self {
Self::Paletted(value)
}
}