use {
fna3d_sys as sys,
std::{
fs::File,
io::{self, BufReader, Read, Seek, SeekFrom, Write},
os::raw::{c_char, c_void},
path::Path,
},
};
use crate::Texture;
type ReadFunc = sys::FNA3D_Image_ReadFunc;
type SkipFunc = sys::FNA3D_Image_SkipFunc;
type EofFunc = sys::FNA3D_Image_EOFFunc;
pub fn free(mem: *const u8) {
unsafe {
sys::FNA3D_Image_Free(mem as *mut _);
}
}
pub fn from_encoded_bytes(bytes: &[u8]) -> (*const u8, u32, [u32; 2]) {
let reader = std::io::Cursor::new(bytes);
self::from_reader(reader, None)
}
pub fn from_path(
path: impl AsRef<Path>,
force_size: Option<[u32; 2]>,
) -> (*const u8, u32, [u32; 2]) {
let path = path.as_ref();
let reader = File::open(path)
.ok()
.unwrap_or_else(|| panic!("failed to open file {}", path.display()));
let reader = BufReader::new(reader); self::from_reader(reader, force_size)
}
pub fn from_reader<R: Read + Seek>(
reader: R,
force_size: Option<[u32; 2]>,
) -> (*const u8, u32, [u32; 2]) {
let context = LoadContext {
reader,
is_end: false,
};
unsafe {
self::load_impl(
Some(LoadCallbacks::<R>::read),
Some(LoadCallbacks::<R>::skip),
Some(LoadCallbacks::<R>::eof),
std::mem::transmute(&context),
force_size,
)
}
}
pub fn save_png<T: Write>(
writer: T,
data: *mut Texture,
src_w: u32,
src_h: u32,
dst_w: u32,
dst_h: u32,
) {
let mut cx = SaveContext { writer };
unsafe {
fna3d_sys::FNA3D_Image_SavePNG(
Some(SaveContext::<T>::write),
&mut cx as *mut _ as _,
src_w as i32,
src_h as i32,
dst_w as i32,
dst_h as i32,
data as *mut u8,
);
}
}
pub fn save_png_to(
path: impl AsRef<Path>,
data: *mut u8,
src_w: u32,
src_h: u32,
dst_w: u32,
dst_h: u32,
) -> io::Result<()> {
let file = File::create(path)?;
let mut cx = SaveContext { writer: file };
unsafe {
fna3d_sys::FNA3D_Image_SavePNG(
Some(SaveContext::<File>::write),
&mut cx as *mut _ as _,
src_w as i32,
src_h as i32,
dst_w as i32,
dst_h as i32,
data,
);
}
Ok(())
}
struct SaveContext<T: Write> {
writer: T,
}
impl<T: Write> SaveContext<T> {
unsafe extern "C" fn write(
context: *mut ::std::os::raw::c_void,
data: *mut ::std::os::raw::c_void,
size: i32,
) {
let cx = &mut *(context as *mut Self);
let buf = std::slice::from_raw_parts(data as *mut u8, size as usize);
cx.writer.write(buf).unwrap();
}
}
struct LoadContext<R: Read + Seek> {
reader: R,
is_end: bool, }
struct LoadCallbacks<R: Read + Seek> {
phantom: std::marker::PhantomData<R>,
}
fn read_as_much(mut reader: impl Read, mut buf: &mut [u8]) -> io::Result<usize> {
let mut read = 0;
while !buf.is_empty() {
match reader.read(buf) {
Ok(0) => break,
Ok(n) => {
let tmp = buf;
buf = &mut tmp[n..];
read += n;
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(read)
}
impl<R: Read + Seek> LoadCallbacks<R> {
unsafe extern "C" fn read(context: *mut c_void, out_ptr: *mut c_char, size: i32) -> i32 {
let cx = &mut *(context as *mut LoadContext<R>);
let out = std::slice::from_raw_parts_mut(out_ptr as *mut u8, size as usize);
let len_read = self::read_as_much(&mut cx.reader, out).unwrap();
len_read as i32
}
unsafe extern "C" fn skip(context: *mut c_void, n: i32) {
let cx = &mut *(context as *mut LoadContext<R>);
cx.reader
.seek(SeekFrom::Current(n as i64))
.unwrap_or_else(|err| panic!("error in anf skip func {}", err));
}
unsafe extern "C" fn eof(context: *mut c_void) -> i32 {
let cx = &mut *(context as *mut LoadContext<R>);
log::warn!("FNA3D_Image stbi eofFunc called: is_end={}", cx.is_end);
cx.is_end as i32
}
}
unsafe fn load_impl(
read_fn: ReadFunc,
skip_fn: SkipFunc,
eof_fn: EofFunc,
context: *mut c_void,
force_size: Option<[u32; 2]>,
) -> (*const u8, u32, [u32; 2]) {
let do_zoom = force_size.is_some();
let force_size = if let Some([x, y]) = force_size {
[x as i32, y as i32]
} else {
[-1, -1]
};
let (mut w, mut h, mut len) = (0, 0, 0);
let pixels = sys::FNA3D_Image_Load(
read_fn,
skip_fn,
eof_fn,
context,
&mut w,
&mut h,
&mut len,
force_size[0],
force_size[1],
do_zoom as u8,
);
(pixels, len as u32, [w as u32, h as u32])
}