#![deny(
clippy::undocumented_unsafe_blocks,
missing_abi,
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
single_use_lifetimes,
trivial_numeric_casts,
unused_lifetimes,
unsafe_op_in_unsafe_fn,
unused_unsafe,
variant_size_differences
)]
use std::{ffi::CStr, fmt::Display, write};
pub mod color;
pub mod image;
pub mod prelude {
pub use crate::color::{ColorFmt, Rgb16, Rgb32, Rgb32X, Rgb64};
pub use crate::image::{
AlphaMode, DirectImage, DirectImage16, DirectImage32, DirectImage32X, DirectImage64,
DynImage, DynImage16, DynImage32, DynImage32X, DynImage64, Image, ImageFormat, ImageSource,
LoadFlags, PaletteSort, PalettedImage, PalettedImage16, PalettedImage32, PalettedImage32X,
PalettedImage64,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive] pub enum Error {
InvalidArguments = libplum_sys::PLUM_ERR_INVALID_ARGUMENTS as _,
InvalidFileFormat = libplum_sys::PLUM_ERR_INVALID_FILE_FORMAT as _,
InvalidMetadata = libplum_sys::PLUM_ERR_INVALID_METADATA as _,
InvalidColorIndex = libplum_sys::PLUM_ERR_INVALID_COLOR_INDEX as _,
TooManyColors = libplum_sys::PLUM_ERR_TOO_MANY_COLORS as _,
UndefinedPalette = libplum_sys::PLUM_ERR_UNDEFINED_PALETTE as _,
ImageTooLarge = libplum_sys::PLUM_ERR_IMAGE_TOO_LARGE as _,
NoData = libplum_sys::PLUM_ERR_NO_DATA as _,
NoMultiFrame = libplum_sys::PLUM_ERR_NO_MULTI_FRAME as _,
FileInaccessible = libplum_sys::PLUM_ERR_FILE_INACCESSIBLE as _,
FileError = libplum_sys::PLUM_ERR_FILE_ERROR as _,
OutOfMemory = libplum_sys::PLUM_ERR_OUT_OF_MEMORY as _,
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let msg = unsafe { libplum_sys::plum_get_error_text(*self as _) };
let msg = unsafe { CStr::from_ptr::<'static>(msg) }.to_string_lossy();
write!(f, "{msg}")
}
}
impl std::error::Error for Error {}
pub type Result<T> = core::result::Result<T, Error>;
impl Error {
fn from_raw(raw: libplum_sys::plum_errors) -> Self {
match raw {
libplum_sys::PLUM_ERR_INVALID_ARGUMENTS => Self::InvalidArguments,
libplum_sys::PLUM_ERR_INVALID_FILE_FORMAT => Self::InvalidFileFormat,
libplum_sys::PLUM_ERR_INVALID_METADATA => Self::InvalidMetadata,
libplum_sys::PLUM_ERR_INVALID_COLOR_INDEX => Self::InvalidColorIndex,
libplum_sys::PLUM_ERR_TOO_MANY_COLORS => Self::TooManyColors,
libplum_sys::PLUM_ERR_UNDEFINED_PALETTE => Self::UndefinedPalette,
libplum_sys::PLUM_ERR_IMAGE_TOO_LARGE => Self::ImageTooLarge,
libplum_sys::PLUM_ERR_NO_DATA => Self::NoData,
libplum_sys::PLUM_ERR_NO_MULTI_FRAME => Self::NoMultiFrame,
libplum_sys::PLUM_ERR_FILE_ERROR => Self::FileError,
libplum_sys::PLUM_ERR_OUT_OF_MEMORY => Self::OutOfMemory,
raw => panic!("Unknown error value {raw} was returned"),
}
}
}
#[cfg(test)]
mod tests {
use std::{fs::File, ops::Deref};
use crate::{prelude::*, Error};
const Z: Rgb32 = Rgb32(0x000000FFu32.swap_bytes()); const W: Rgb32 = Rgb32(0xFFFFFFFFu32.swap_bytes()); const R: Rgb32 = Rgb32(0xFF000080u32.swap_bytes()); const G: Rgb32 = Rgb32(0x00FF0080u32.swap_bytes()); const B: Rgb32 = Rgb32(0x0000FF80u32.swap_bytes()); const Y: Rgb32 = Rgb32(0xFFFF0080u32.swap_bytes()); const TEST_SQUARE_PIXELS: [[Rgb32; 21]; 21] = [
[
Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z,
],
[
Z, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, Z,
],
[
Z, W, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, W, Z,
],
[
Z, W, Z, B, B, B, B, B, B, B, Z, R, R, R, R, R, R, R, Z, W, Z,
],
[
Z, W, Z, B, B, B, B, B, B, B, Z, R, R, R, R, R, R, R, Z, W, Z,
],
[
Z, W, Z, B, B, B, B, B, B, B, Z, R, R, R, R, R, R, R, Z, W, Z,
],
[
Z, W, Z, B, B, B, B, B, B, B, Z, R, R, R, R, R, R, R, Z, W, Z,
],
[
Z, W, Z, B, B, B, B, B, B, B, Z, R, R, R, R, R, R, R, Z, W, Z,
],
[
Z, W, Z, B, B, B, B, B, B, B, Z, R, R, R, R, R, R, R, Z, W, Z,
],
[
Z, W, Z, B, B, B, B, B, B, B, Z, R, R, R, R, R, R, R, Z, W, Z,
],
[
Z, W, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, W, Z,
],
[
Z, W, Z, Y, Y, Y, Y, Y, Y, Y, Z, G, G, G, G, G, G, G, Z, W, Z,
],
[
Z, W, Z, Y, Y, Y, Y, Y, Y, Y, Z, G, G, G, G, G, G, G, Z, W, Z,
],
[
Z, W, Z, Y, Y, Y, Y, Y, Y, Y, Z, G, G, G, G, G, G, G, Z, W, Z,
],
[
Z, W, Z, Y, Y, Y, Y, Y, Y, Y, Z, G, G, G, G, G, G, G, Z, W, Z,
],
[
Z, W, Z, Y, Y, Y, Y, Y, Y, Y, Z, G, G, G, G, G, G, G, Z, W, Z,
],
[
Z, W, Z, Y, Y, Y, Y, Y, Y, Y, Z, G, G, G, G, G, G, G, Z, W, Z,
],
[
Z, W, Z, Y, Y, Y, Y, Y, Y, Y, Z, G, G, G, G, G, G, G, Z, W, Z,
],
[
Z, W, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, W, Z,
],
[
Z, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, Z,
],
[
Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z,
],
];
#[test]
fn load_test_square_unpaletted() {
let file = File::open("libplum-sys/testsq.png").expect("Failed to open testsq.png!");
let image = DirectImage32::load(file, Default::default(), AlphaMode::ZeroIsTransparent)
.expect("Failed to load testsq.png!");
assert_eq!(image.nb_frames(), 1);
let mut frames = image.frames();
let frame = frames.next().unwrap();
assert!(frames.next().is_none());
assert!(frames.next().is_none());
assert_eq!(image.height(), TEST_SQUARE_PIXELS.len());
assert_eq!(image.width(), TEST_SQUARE_PIXELS[0].len());
for (y, row) in TEST_SQUARE_PIXELS.iter().enumerate() {
for (x, &expected) in row.iter().enumerate() {
let got = frame[(x, y)];
assert!(got == expected, "({x}, {y}): {got} != {expected}");
}
}
}
#[test]
fn load_test_square_paletted() {
let file = File::open("libplum-sys/testsq.png").expect("Failed to open testsq.png!");
let image = PalettedImage32::load(file, Default::default(), AlphaMode::ZeroIsTransparent)
.expect("Failed to load testsq.png!");
assert_eq!(image.palette(), &[B, R, Y, G, W, Z]);
assert_eq!(image.nb_frames(), 1);
let mut frames = image.frames();
let frame = frames.next().unwrap();
assert!(frames.next().is_none());
assert!(frames.next().is_none());
assert_eq!(image.height(), TEST_SQUARE_PIXELS.len());
assert_eq!(image.width(), TEST_SQUARE_PIXELS[0].len());
for (y, row) in TEST_SQUARE_PIXELS.iter().enumerate() {
for (x, &expected) in row.iter().enumerate() {
let got = frame.pixel(x, y);
assert!(got == expected, "({x}, {y}): {got} != {expected}");
}
}
}
#[test]
fn no_oom() {
const EVIL: [u8; 43] = [
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xf9, 0x04, 0x05, 0x00, 0x00, 0x01, 0x00, 0x2c,
0xff, 0x7f, 0xff, 0x7f, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00,
0x3b,
];
let result =
DirectImage32::load_limited(EVIL, Default::default(), Default::default(), 1024 * 1024);
let err = result
.expect_err("Oversized GIF should have failed to load!")
.into_inner()
.expect("Oversized GIF should not have caused an I/O error!")
.downcast::<Error>()
.expect("Oversized GIF should have been rejected by libplum!");
assert_eq!(err.deref(), &Error::ImageTooLarge);
}
}