Improve API
This commit is contained in:
parent
da37d7a182
commit
0c2533efa8
|
@ -7,7 +7,7 @@ edition = "2021"
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
image = { version = "0.25.1", default-features = false, features = ["jpeg", "png", "webp", "rayon"] }
|
||||
image = { version = "0.25.1", git = "https://git.pfaff.dev/michael/image.git", default-features = false, features = ["jpeg", "png", "webp", "rayon"] }
|
||||
|
||||
[profile.release-lto]
|
||||
inherits = "release"
|
||||
|
|
329
src/lib.rs
329
src/lib.rs
|
@ -1,18 +1,45 @@
|
|||
use std::{convert::Infallible, io::Cursor, ptr::NonNull};
|
||||
use std::{borrow::Cow, convert::Infallible, io::Cursor, ptr::NonNull};
|
||||
|
||||
use image::{codecs::{jpeg::JpegDecoder, png::PngDecoder, webp::WebPDecoder}, error::{ImageFormatHint, UnsupportedError}, ImageDecoder, ImageError};
|
||||
use image::{
|
||||
codecs::{
|
||||
jpeg::{JpegDecoder, JpegEncoder},
|
||||
png::{PngDecoder, PngEncoder},
|
||||
webp::{WebPDecoder, WebPEncoder},
|
||||
},
|
||||
error::{ImageFormatHint, UnsupportedError},
|
||||
ImageDecoder, ImageEncoder, ImageError,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct ImageInfo {
|
||||
width: u32,
|
||||
height: u32,
|
||||
color_type: ColorType,
|
||||
macro_rules! ffi_enum {
|
||||
($name:ident {
|
||||
$($variant:ident),+ $(,)?
|
||||
}) => {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum $name {
|
||||
$($variant,)+
|
||||
}
|
||||
|
||||
impl $name {
|
||||
fn from_image(value: image::$name) -> Option<Self> {
|
||||
use image::$name::*;
|
||||
Some(match value {
|
||||
$($variant => Self::$variant,)+
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn to_image(self) -> image::$name {
|
||||
use image::$name::*;
|
||||
match self {
|
||||
$(Self::$variant => $variant,)+
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum ImageFormat {
|
||||
ffi_enum!(ImageFormat {
|
||||
Png,
|
||||
Jpeg,
|
||||
Gif,
|
||||
|
@ -28,35 +55,9 @@ pub enum ImageFormat {
|
|||
Farbfeld,
|
||||
Avif,
|
||||
Qoi,
|
||||
}
|
||||
});
|
||||
|
||||
impl ImageFormat {
|
||||
fn from(value: image::ImageFormat) -> Option<Self> {
|
||||
use image::ImageFormat::*;
|
||||
Some(match value {
|
||||
Png => Self::Png,
|
||||
Jpeg => Self::Jpeg,
|
||||
Gif => Self::Gif,
|
||||
WebP => Self::WebP,
|
||||
Pnm => Self::Pnm,
|
||||
Tiff => Self::Tiff,
|
||||
Tga => Self::Tga,
|
||||
Dds => Self::Dds,
|
||||
Bmp => Self::Bmp,
|
||||
Ico => Self::Ico,
|
||||
Hdr => Self::Hdr,
|
||||
OpenExr => Self::OpenExr,
|
||||
Farbfeld => Self::Farbfeld,
|
||||
Avif => Self::Avif,
|
||||
Qoi => Self::Qoi,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum ColorType {
|
||||
ffi_enum!(ColorType {
|
||||
L8,
|
||||
La8,
|
||||
Rgb8,
|
||||
|
@ -67,26 +68,36 @@ pub enum ColorType {
|
|||
Rgba16,
|
||||
Rgb32F,
|
||||
Rgba32F,
|
||||
}
|
||||
});
|
||||
|
||||
impl ColorType {
|
||||
fn from(value: image::ColorType) -> Option<Self> {
|
||||
use image::ColorType::*;
|
||||
Some(match value {
|
||||
L8 => Self::L8,
|
||||
La8 => Self::La8,
|
||||
Rgb8 => Self::Rgb8,
|
||||
Rgba8 => Self::Rgba8,
|
||||
L16 => Self::L16,
|
||||
La16 => Self::La16,
|
||||
Rgb16 => Self::Rgb16,
|
||||
Rgba16 => Self::Rgba16,
|
||||
Rgb32F => Self::Rgb32F,
|
||||
Rgba32F => Self::Rgba32F,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
ffi_enum!(ExtendedColorType {
|
||||
A8,
|
||||
L1,
|
||||
La1,
|
||||
Rgb1,
|
||||
Rgba1,
|
||||
L2,
|
||||
La2,
|
||||
Rgb2,
|
||||
Rgba2,
|
||||
L4,
|
||||
La4,
|
||||
Rgb4,
|
||||
Rgba4,
|
||||
L8,
|
||||
La8,
|
||||
Rgb8,
|
||||
Rgba8,
|
||||
L16,
|
||||
La16,
|
||||
Rgb16,
|
||||
Rgba16,
|
||||
Bgr8,
|
||||
Bgra8,
|
||||
Rgb32F,
|
||||
Rgba32F,
|
||||
Cmyk8,
|
||||
});
|
||||
|
||||
/// The allocated region must be zeroed by the implementation.
|
||||
pub type AllocFn = extern "system" fn(length: usize) -> Option<NonNull<u8>>;
|
||||
|
@ -104,63 +115,215 @@ pub union LoadedImage {
|
|||
pub struct OkLoadedImage {
|
||||
pub ptr: NonNull<u8>,
|
||||
pub len: usize,
|
||||
pub info: ImageInfo,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub color_type: ColorType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct ErrLoadedImage {
|
||||
pub marker: Option<NonNull<Infallible>>,
|
||||
// TODO: allocate a buffer to hold a message string and pass that back to the caller.
|
||||
pub message: NonNull<u8>,
|
||||
pub message_len: usize,
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn new_err_message(alloc: AllocFn, e: impl Into<Cow<'static, str>>) -> (NonNull<u8>, usize) {
|
||||
let message_tmp = match e.into() {
|
||||
Cow::Owned(s) => s,
|
||||
Cow::Borrowed(s) => return (NonNull::from(s.as_bytes()).cast(), s.len()),
|
||||
};
|
||||
let Some(message) = alloc_slice(alloc, message_tmp.len()) else {
|
||||
const FALLBACK_MESSAGE: &'static str = "Allocator returned an error, so I cannot provide you with the error message.";
|
||||
return (NonNull::from(FALLBACK_MESSAGE.as_bytes()).cast(), FALLBACK_MESSAGE.len())
|
||||
};
|
||||
message.copy_from_slice(message_tmp.as_bytes());
|
||||
let len = message.len();
|
||||
(NonNull::from(message).cast(), len)
|
||||
}
|
||||
|
||||
impl LoadedImage {
|
||||
pub const NULL: Self = Self { err: ErrLoadedImage { marker: None } };
|
||||
fn new_err(alloc: AllocFn, e: impl Into<Cow<'static, str>>) -> Self {
|
||||
let (message, message_len) = new_err_message(alloc, e);
|
||||
Self { err: ErrLoadedImage { marker: None, message, message_len } }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union EncodedImage {
|
||||
pub ptr: Option<NonNull<u8>>,
|
||||
|
||||
pub ok: OkEncodedImage,
|
||||
pub err: ErrEncodedImage,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct OkEncodedImage {
|
||||
pub ptr: NonNull<u8>,
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct ErrEncodedImage {
|
||||
pub marker: Option<NonNull<Infallible>>,
|
||||
pub message: NonNull<u8>,
|
||||
pub message_len: usize,
|
||||
}
|
||||
|
||||
impl EncodedImage {
|
||||
fn new_err(alloc: AllocFn, e: impl Into<Cow<'static, str>>) -> Self {
|
||||
let (message, message_len) = new_err_message(alloc, e);
|
||||
Self { err: ErrEncodedImage { marker: None, message, message_len } }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum EncoderProfile {
|
||||
/// Optimized for fastest encoding and least loss. Lossiness depends on format.
|
||||
Fast,
|
||||
/// Optimized for smallest output. More lossy than [`Fast`].
|
||||
Small,
|
||||
}
|
||||
|
||||
fn alloc_slice(alloc: AllocFn, len: usize) -> Option<&'static mut [u8]> {
|
||||
let Some(data) = alloc(len) else {
|
||||
return None;
|
||||
};
|
||||
Some(unsafe { NonNull::slice_from_raw_parts(data, len).as_mut() })
|
||||
}
|
||||
|
||||
fn load_image_common(alloc: AllocFn, dec: impl ImageDecoder) -> LoadedImage {
|
||||
let (width, height) = dec.dimensions();
|
||||
let Some(color_type) = ColorType::from(dec.color_type()) else {
|
||||
return LoadedImage::NULL;
|
||||
let Some(color_type) = ColorType::from_image(dec.color_type()) else {
|
||||
return LoadedImage::new_err(alloc, format!("Unsupported color type {:?}. This is a bug. Please report to the developers of libimage", dec.color_type()));
|
||||
};
|
||||
let Ok(byte_count) = usize::try_from(dec.total_bytes()) else {
|
||||
return LoadedImage::NULL;
|
||||
return LoadedImage::new_err(alloc, format!("Unsupported decoded image size {}.", dec.total_bytes()));
|
||||
};
|
||||
let Some(output) = alloc(byte_count) else {
|
||||
return LoadedImage::NULL;
|
||||
let Some(output) = alloc_slice(alloc, byte_count) else {
|
||||
return LoadedImage::new_err(alloc, "Allocation failed. This indicates that you have run out of memory or that there exists a bug in the caller of libimage::load_image.");
|
||||
};
|
||||
let output = unsafe { NonNull::slice_from_raw_parts(output, byte_count).as_mut() };
|
||||
if let Err(_) = dec.read_image(output) {
|
||||
return LoadedImage::NULL;
|
||||
if let Err(e) = dec.read_image(output) {
|
||||
return LoadedImage::new_err(alloc, format!("Image decoding failed: {e}"));
|
||||
}
|
||||
LoadedImage { ok: OkLoadedImage { ptr: NonNull::from(output).cast(), len: byte_count, info: ImageInfo {
|
||||
LoadedImage {
|
||||
ok: OkLoadedImage {
|
||||
ptr: NonNull::from(output).cast(),
|
||||
len: byte_count,
|
||||
width,
|
||||
height,
|
||||
color_type,
|
||||
} } }
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn load_image(input: *const u8, input_len: usize, alloc: AllocFn) -> LoadedImage {
|
||||
pub extern "system" fn load_image(
|
||||
input: *const u8,
|
||||
input_len: usize,
|
||||
alloc: AllocFn,
|
||||
) -> LoadedImage {
|
||||
let input = unsafe { std::slice::from_raw_parts(input, input_len) };
|
||||
let Ok(iformat) = image::guess_format(input) else {
|
||||
return LoadedImage::NULL;
|
||||
return LoadedImage::new_err(alloc, "Unable to guess the formmat of the input.");
|
||||
};
|
||||
let format = ImageFormat::from(iformat).ok_or(iformat);
|
||||
let format = ImageFormat::from_image(iformat).ok_or(iformat);
|
||||
match format {
|
||||
Ok(ImageFormat::Jpeg) => {
|
||||
JpegDecoder::new(Cursor::new(input))
|
||||
.map(|dec| load_image_common(alloc, dec))
|
||||
JpegDecoder::new(Cursor::new(input)).map(|dec| load_image_common(alloc, dec))
|
||||
}
|
||||
Ok(ImageFormat::Png) => {
|
||||
PngDecoder::new(Cursor::new(input))
|
||||
.map(|dec| load_image_common(alloc, dec))
|
||||
PngDecoder::new(Cursor::new(input)).map(|dec| load_image_common(alloc, dec))
|
||||
}
|
||||
Ok(ImageFormat::WebP) => {
|
||||
WebPDecoder::new(Cursor::new(input))
|
||||
.map(|dec| load_image_common(alloc, dec))
|
||||
WebPDecoder::new(Cursor::new(input)).map(|dec| load_image_common(alloc, dec))
|
||||
}
|
||||
Err(format) => Err(ImageError::Unsupported(UnsupportedError::from(
|
||||
ImageFormatHint::Exact(format),
|
||||
))),
|
||||
Ok(_) => Err(ImageError::Unsupported(UnsupportedError::from(
|
||||
ImageFormatHint::Exact(iformat),
|
||||
))),
|
||||
}
|
||||
.unwrap_or_else(
|
||||
#[cold]
|
||||
|e| LoadedImage::new_err(alloc, format!("Image decoding failed: {e}")),
|
||||
)
|
||||
}
|
||||
|
||||
fn check_buffer_size(size: usize, width: u32, height: u32, color_type: ExtendedColorType) -> Result<(), String> {
|
||||
let Some(expected_len) = (width as usize).checked_mul(height as usize)
|
||||
.and_then(|x| x.checked_mul(color_type.to_image().bits_per_pixel() as usize))
|
||||
.map(|x| x.div_ceil(8)) else {
|
||||
return Err(format!("{width}x{height} {color_type:?} image is too large for the host CPU architecture"));
|
||||
};
|
||||
if size != expected_len {
|
||||
return Err(format!("Expected a {width}x{height} {color_type:?} image to have a pixel buffer of length {expected_len}, but found {size}"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn encode_image(
|
||||
input: *const u8,
|
||||
input_len: usize,
|
||||
alloc: AllocFn,
|
||||
width: u32,
|
||||
height: u32,
|
||||
color_type: ExtendedColorType,
|
||||
format: ImageFormat,
|
||||
profile: EncoderProfile,
|
||||
) -> EncodedImage {
|
||||
if let Err(e) = check_buffer_size(input_len, width, height, color_type) {
|
||||
return EncodedImage::new_err(alloc, e);
|
||||
}
|
||||
let input = unsafe { std::slice::from_raw_parts(input, input_len) };
|
||||
let mut buf = Vec::new();
|
||||
let r = match format {
|
||||
ImageFormat::Jpeg => JpegEncoder::new_with_quality(
|
||||
&mut buf,
|
||||
match profile {
|
||||
EncoderProfile::Fast => 100,
|
||||
EncoderProfile::Small => 90,
|
||||
},
|
||||
)
|
||||
.write_image(input, width, height, color_type.to_image()),
|
||||
ImageFormat::Png => PngEncoder::new_with_quality(
|
||||
&mut buf,
|
||||
match profile {
|
||||
EncoderProfile::Fast => image::codecs::png::CompressionType::Fast,
|
||||
EncoderProfile::Small => image::codecs::png::CompressionType::Best,
|
||||
},
|
||||
match profile {
|
||||
EncoderProfile::Fast => image::codecs::png::FilterType::NoFilter,
|
||||
EncoderProfile::Small => image::codecs::png::FilterType::Adaptive,
|
||||
},
|
||||
)
|
||||
.write_image(input, width, height, color_type.to_image()),
|
||||
ImageFormat::WebP => WebPEncoder::new_lossless(&mut buf).write_image(
|
||||
input,
|
||||
width,
|
||||
height,
|
||||
color_type.to_image(),
|
||||
),
|
||||
_ => return EncodedImage::new_err(alloc, format!("Unsupported format for encoding: {format:?}")),
|
||||
};
|
||||
if let Err(e) = r {
|
||||
return EncodedImage::new_err(alloc, format!("Image encoding failed: {e}"));
|
||||
}
|
||||
|
||||
let Some(output) = alloc_slice(alloc, buf.len()) else {
|
||||
return EncodedImage::new_err(alloc, "Allocation failed. This indicates that you have run out of memory or that there exists a bug in the caller of libimage::encode_image.");
|
||||
};
|
||||
output.copy_from_slice(&buf);
|
||||
EncodedImage {
|
||||
ok: OkEncodedImage {
|
||||
len: output.len(),
|
||||
ptr: NonNull::from(output).cast(),
|
||||
},
|
||||
}
|
||||
Err(format) => Err(ImageError::Unsupported(UnsupportedError::from(ImageFormatHint::Exact(format)))),
|
||||
Ok(_) => Err(ImageError::Unsupported(UnsupportedError::from(ImageFormatHint::Exact(iformat)))),
|
||||
}.unwrap_or_else(#[cold] |_| LoadedImage::NULL)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue