#![allow(clippy::missing_safety_doc)]
use std::cell::RefCell;
use std::ffi::{CStr, CString, c_char, c_int, c_uint};
use std::path::PathBuf;
use std::ptr;
use std::slice;
use crate::{Error, Frame, Image, KnownFormat, Loader, MemoryFormat, SandboxSelector, Texture};
#[repr(C)]
pub struct GlycinNgLoader {
inner: Option<Loader>,
}
#[repr(C)]
pub struct GlycinNgImage {
inner: Image,
}
thread_local! {
static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };
}
fn set_error<E: std::fmt::Display>(e: E) {
let msg = CString::new(e.to_string()).unwrap_or_else(|_| CString::new("error").unwrap());
LAST_ERROR.with(|cell| *cell.borrow_mut() = Some(msg));
}
fn clear_error() {
LAST_ERROR.with(|cell| *cell.borrow_mut() = None);
}
#[unsafe(no_mangle)]
pub extern "C" fn glycin_ng_last_error() -> *const c_char {
LAST_ERROR.with(|cell| {
cell.borrow()
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null())
})
}
#[unsafe(no_mangle)]
pub extern "C" fn glycin_ng_clear_last_error() {
clear_error();
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_loader_free(loader: *mut GlycinNgLoader) {
if !loader.is_null() {
drop(unsafe { Box::from_raw(loader) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_loader_new_path(path: *const c_char) -> *mut GlycinNgLoader {
clear_error();
if path.is_null() {
set_error("path is null");
return ptr::null_mut();
}
let cstr = unsafe { CStr::from_ptr(path) };
let pb = PathBuf::from(cstr.to_string_lossy().as_ref());
let loader = Loader::new_path(pb);
Box::into_raw(Box::new(GlycinNgLoader {
inner: Some(loader),
}))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_loader_new_bytes(
data: *const u8,
len: usize,
) -> *mut GlycinNgLoader {
clear_error();
if data.is_null() && len != 0 {
set_error("data is null but len is non-zero");
return ptr::null_mut();
}
let bytes: Vec<u8> = if len == 0 {
Vec::new()
} else {
unsafe { slice::from_raw_parts(data, len) }.to_vec()
};
let loader = Loader::new_bytes(bytes);
Box::into_raw(Box::new(GlycinNgLoader {
inner: Some(loader),
}))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_loader_sandbox(
loader: *mut GlycinNgLoader,
landlock: c_int,
seccomp: c_int,
rlimit: c_int,
strict: c_int,
) -> c_int {
clear_error();
let Some(handle) = (unsafe { loader.as_mut() }) else {
set_error("loader is null");
return -1;
};
let Some(inner) = handle.inner.take() else {
set_error("loader has already been consumed");
return -1;
};
let selector = SandboxSelector {
landlock: landlock != 0,
seccomp: seccomp != 0,
rlimit: rlimit != 0,
strict: strict != 0,
};
handle.inner = Some(inner.sandbox_selector(selector));
0
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_loader_format_hint(
loader: *mut GlycinNgLoader,
format: c_uint,
) -> c_int {
clear_error();
let Some(handle) = (unsafe { loader.as_mut() }) else {
set_error("loader is null");
return -1;
};
let Some(inner) = handle.inner.take() else {
set_error("loader has already been consumed");
return -1;
};
let f = match c_uint_to_format(format) {
Some(f) => f,
None => {
handle.inner = Some(inner);
set_error("unknown format constant");
return -1;
}
};
handle.inner = Some(inner.format_hint(f));
0
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_loader_load(loader: *mut GlycinNgLoader) -> *mut GlycinNgImage {
clear_error();
if loader.is_null() {
set_error("loader is null");
return ptr::null_mut();
}
let mut handle = unsafe { Box::from_raw(loader) };
let Some(inner) = handle.inner.take() else {
set_error("loader has already been consumed");
return ptr::null_mut();
};
match inner.load() {
Ok(image) => Box::into_raw(Box::new(GlycinNgImage { inner: image })),
Err(e) => {
set_error(e);
ptr::null_mut()
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_image_free(image: *mut GlycinNgImage) {
if !image.is_null() {
drop(unsafe { Box::from_raw(image) });
}
}
fn image_ref<'a>(image: *const GlycinNgImage) -> Option<&'a Image> {
unsafe { image.as_ref() }.map(|h| &h.inner)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_image_width(image: *const GlycinNgImage) -> u32 {
image_ref(image).map(|i| i.width()).unwrap_or(0)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_image_height(image: *const GlycinNgImage) -> u32 {
image_ref(image).map(|i| i.height()).unwrap_or(0)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_image_frame_count(image: *const GlycinNgImage) -> usize {
image_ref(image).map(|i| i.frames().len()).unwrap_or(0)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_image_format_name(image: *const GlycinNgImage) -> *const c_char {
let Some(img) = image_ref(image) else {
return ptr::null();
};
format_name_cstr(img.format_name())
}
fn format_name_cstr(name: &'static str) -> *const c_char {
const NAMES: &[(&str, &CStr)] = &[
("png", c"png"),
("jpeg", c"jpeg"),
("gif", c"gif"),
("webp", c"webp"),
("tiff", c"tiff"),
("bmp", c"bmp"),
("ico", c"ico"),
("tga", c"tga"),
("qoi", c"qoi"),
("exr", c"exr"),
("pnm", c"pnm"),
("dds", c"dds"),
("jxl", c"jxl"),
];
for (rust, cstr) in NAMES {
if *rust == name {
return cstr.as_ptr();
}
}
ptr::null()
}
fn frame_ref<'a>(image: *const GlycinNgImage, index: usize) -> Option<&'a Frame> {
let img = image_ref(image)?;
img.frames().get(index)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_image_texture(
image: *const GlycinNgImage,
index: usize,
) -> *const Texture {
match frame_ref(image, index) {
Some(frame) => frame.texture() as *const Texture,
None => ptr::null(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_image_frame_delay_ms(
image: *const GlycinNgImage,
index: usize,
) -> u64 {
frame_ref(image, index)
.and_then(|f| f.delay())
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_texture_width(texture: *const Texture) -> u32 {
unsafe { texture.as_ref() }.map(|t| t.width()).unwrap_or(0)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_texture_height(texture: *const Texture) -> u32 {
unsafe { texture.as_ref() }.map(|t| t.height()).unwrap_or(0)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_texture_stride(texture: *const Texture) -> u32 {
unsafe { texture.as_ref() }.map(|t| t.stride()).unwrap_or(0)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_texture_format(texture: *const Texture) -> c_uint {
let Some(t) = (unsafe { texture.as_ref() }) else {
return GLYCIN_NG_FORMAT_UNKNOWN;
};
memory_format_to_c_uint(t.format())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_texture_data(texture: *const Texture) -> *const u8 {
unsafe { texture.as_ref() }
.map(|t| t.data().as_ptr())
.unwrap_or(ptr::null())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn glycin_ng_texture_data_len(texture: *const Texture) -> usize {
unsafe { texture.as_ref() }
.map(|t| t.data().len())
.unwrap_or(0)
}
pub const GLYCIN_NG_FORMAT_UNKNOWN: c_uint = 0;
pub const GLYCIN_NG_FORMAT_G8: c_uint = 1;
pub const GLYCIN_NG_FORMAT_G8A8: c_uint = 2;
pub const GLYCIN_NG_FORMAT_G8A8_PRE: c_uint = 3;
pub const GLYCIN_NG_FORMAT_G16: c_uint = 4;
pub const GLYCIN_NG_FORMAT_G16A16: c_uint = 5;
pub const GLYCIN_NG_FORMAT_G16A16_PRE: c_uint = 6;
pub const GLYCIN_NG_FORMAT_R8G8B8: c_uint = 10;
pub const GLYCIN_NG_FORMAT_R8G8B8A8: c_uint = 11;
pub const GLYCIN_NG_FORMAT_R8G8B8A8_PRE: c_uint = 12;
pub const GLYCIN_NG_FORMAT_B8G8R8: c_uint = 13;
pub const GLYCIN_NG_FORMAT_B8G8R8A8: c_uint = 14;
pub const GLYCIN_NG_FORMAT_B8G8R8A8_PRE: c_uint = 15;
pub const GLYCIN_NG_FORMAT_A8R8G8B8: c_uint = 16;
pub const GLYCIN_NG_FORMAT_A8R8G8B8_PRE: c_uint = 17;
pub const GLYCIN_NG_FORMAT_A8B8G8R8: c_uint = 18;
pub const GLYCIN_NG_FORMAT_R16G16B16: c_uint = 20;
pub const GLYCIN_NG_FORMAT_R16G16B16A16: c_uint = 21;
pub const GLYCIN_NG_FORMAT_R16G16B16A16_PRE: c_uint = 22;
pub const GLYCIN_NG_FORMAT_R16G16B16_F: c_uint = 23;
pub const GLYCIN_NG_FORMAT_R16G16B16A16_F: c_uint = 24;
pub const GLYCIN_NG_FORMAT_R32G32B32_F: c_uint = 25;
pub const GLYCIN_NG_FORMAT_R32G32B32A32_F: c_uint = 26;
pub const GLYCIN_NG_FORMAT_R32G32B32A32_F_PRE: c_uint = 27;
fn memory_format_to_c_uint(f: MemoryFormat) -> c_uint {
match f {
MemoryFormat::G8 => GLYCIN_NG_FORMAT_G8,
MemoryFormat::G8a8 => GLYCIN_NG_FORMAT_G8A8,
MemoryFormat::G8a8Premultiplied => GLYCIN_NG_FORMAT_G8A8_PRE,
MemoryFormat::G16 => GLYCIN_NG_FORMAT_G16,
MemoryFormat::G16a16 => GLYCIN_NG_FORMAT_G16A16,
MemoryFormat::G16a16Premultiplied => GLYCIN_NG_FORMAT_G16A16_PRE,
MemoryFormat::R8g8b8 => GLYCIN_NG_FORMAT_R8G8B8,
MemoryFormat::R8g8b8a8 => GLYCIN_NG_FORMAT_R8G8B8A8,
MemoryFormat::R8g8b8a8Premultiplied => GLYCIN_NG_FORMAT_R8G8B8A8_PRE,
MemoryFormat::B8g8r8 => GLYCIN_NG_FORMAT_B8G8R8,
MemoryFormat::B8g8r8a8 => GLYCIN_NG_FORMAT_B8G8R8A8,
MemoryFormat::B8g8r8a8Premultiplied => GLYCIN_NG_FORMAT_B8G8R8A8_PRE,
MemoryFormat::A8r8g8b8 => GLYCIN_NG_FORMAT_A8R8G8B8,
MemoryFormat::A8r8g8b8Premultiplied => GLYCIN_NG_FORMAT_A8R8G8B8_PRE,
MemoryFormat::A8b8g8r8 => GLYCIN_NG_FORMAT_A8B8G8R8,
MemoryFormat::R16g16b16 => GLYCIN_NG_FORMAT_R16G16B16,
MemoryFormat::R16g16b16a16 => GLYCIN_NG_FORMAT_R16G16B16A16,
MemoryFormat::R16g16b16a16Premultiplied => GLYCIN_NG_FORMAT_R16G16B16A16_PRE,
MemoryFormat::R16g16b16Float => GLYCIN_NG_FORMAT_R16G16B16_F,
MemoryFormat::R16g16b16a16Float => GLYCIN_NG_FORMAT_R16G16B16A16_F,
MemoryFormat::R32g32b32Float => GLYCIN_NG_FORMAT_R32G32B32_F,
MemoryFormat::R32g32b32a32Float => GLYCIN_NG_FORMAT_R32G32B32A32_F,
MemoryFormat::R32g32b32a32FloatPremultiplied => GLYCIN_NG_FORMAT_R32G32B32A32_F_PRE,
}
}
pub const GLYCIN_NG_KFMT_PNG: c_uint = 1;
pub const GLYCIN_NG_KFMT_JPEG: c_uint = 2;
pub const GLYCIN_NG_KFMT_GIF: c_uint = 3;
pub const GLYCIN_NG_KFMT_WEBP: c_uint = 4;
pub const GLYCIN_NG_KFMT_TIFF: c_uint = 5;
pub const GLYCIN_NG_KFMT_BMP: c_uint = 6;
pub const GLYCIN_NG_KFMT_ICO: c_uint = 7;
pub const GLYCIN_NG_KFMT_TGA: c_uint = 8;
pub const GLYCIN_NG_KFMT_QOI: c_uint = 9;
pub const GLYCIN_NG_KFMT_EXR: c_uint = 10;
pub const GLYCIN_NG_KFMT_PNM: c_uint = 11;
pub const GLYCIN_NG_KFMT_DDS: c_uint = 12;
pub const GLYCIN_NG_KFMT_JXL: c_uint = 13;
fn c_uint_to_format(value: c_uint) -> Option<KnownFormat> {
Some(match value {
GLYCIN_NG_KFMT_PNG => KnownFormat::Png,
GLYCIN_NG_KFMT_JPEG => KnownFormat::Jpeg,
GLYCIN_NG_KFMT_GIF => KnownFormat::Gif,
GLYCIN_NG_KFMT_WEBP => KnownFormat::WebP,
GLYCIN_NG_KFMT_TIFF => KnownFormat::Tiff,
GLYCIN_NG_KFMT_BMP => KnownFormat::Bmp,
GLYCIN_NG_KFMT_ICO => KnownFormat::Ico,
GLYCIN_NG_KFMT_TGA => KnownFormat::Tga,
GLYCIN_NG_KFMT_QOI => KnownFormat::Qoi,
GLYCIN_NG_KFMT_EXR => KnownFormat::Exr,
GLYCIN_NG_KFMT_PNM => KnownFormat::Pnm,
GLYCIN_NG_KFMT_DDS => KnownFormat::Dds,
GLYCIN_NG_KFMT_JXL => KnownFormat::Jxl,
_ => return None,
})
}
#[allow(dead_code)]
fn _dummy(_: &Error) {}