mirror of
https://github.com/lifegpc/libtlg-rs.git
synced 2026-07-01 19:00:20 +08:00
Add CAPI
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -164,7 +164,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libtlg-rs"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"overf",
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
[package]
|
||||
name = "libtlg-rs"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
description = "Rust version of libtlg"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/lifegpc/libtlg-rs"
|
||||
|
||||
[features]
|
||||
capi = []
|
||||
encode = []
|
||||
|
||||
[dependencies]
|
||||
|
||||
1
libtlg-rs/cbindgen.toml
Normal file
1
libtlg-rs/cbindgen.toml
Normal file
@@ -0,0 +1 @@
|
||||
language = "C"
|
||||
327
libtlg-rs/src/capi.rs
Normal file
327
libtlg-rs/src/capi.rs
Normal file
@@ -0,0 +1,327 @@
|
||||
//! C API for libtlg-rs
|
||||
//!
|
||||
//! Use follow command to build the C API library
|
||||
//! ```shell
|
||||
//! cargo install cargo-c
|
||||
//! cargo cbuild --release --features "encode"
|
||||
//! ```
|
||||
use std::ffi::{c_char, c_void};
|
||||
#[cfg(feature = "encode")]
|
||||
use std::io::Write;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
/// A readable stream for TLG processing.
|
||||
pub struct ReadableStream {
|
||||
/// User data pointer.
|
||||
userdata: *mut c_void,
|
||||
/// Read function pointer.
|
||||
read_fn: extern "C" fn(*mut c_void, *mut u8, usize) -> usize,
|
||||
/// Seek function pointer.
|
||||
seek_fn: extern "C" fn(*mut c_void, i64, i32) -> u64,
|
||||
}
|
||||
|
||||
#[cfg(feature = "encode")]
|
||||
/// A writeable stream for TLG processing.
|
||||
pub struct WriteableStream {
|
||||
/// User data pointer.
|
||||
userdata: *mut c_void,
|
||||
/// Write function pointer.
|
||||
write_fn: extern "C" fn(*mut c_void, *const u8, usize) -> usize,
|
||||
/// Flush function pointer.
|
||||
flush_fn: extern "C" fn(*mut c_void) -> i32,
|
||||
/// Seek function pointer.
|
||||
seek_fn: extern "C" fn(*mut c_void, i64, i32) -> u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
/// A tag in TLG file.
|
||||
pub struct Tag {
|
||||
/// Tag name.
|
||||
pub name: *mut c_char,
|
||||
/// Length of tag name.
|
||||
pub name_length: usize,
|
||||
/// Tag value.
|
||||
pub value: *mut c_char,
|
||||
/// Length of tag value.
|
||||
pub value_length: usize,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
/// Color type of TLG image.
|
||||
pub enum TlgColorType {
|
||||
/// Grayscale 8-bit.
|
||||
Grayscale8,
|
||||
/// BGR 24-bit.
|
||||
Bgr24,
|
||||
/// BGRA 32-bit.
|
||||
Bgra32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Tlg {
|
||||
/// TLG version. 5 or 6.
|
||||
pub version: u32,
|
||||
/// Image width.
|
||||
pub width: u32,
|
||||
/// Image height.
|
||||
pub height: u32,
|
||||
/// Color type.
|
||||
pub color_type: TlgColorType,
|
||||
/// Pointer to image data.
|
||||
pub data: *mut u8,
|
||||
/// Number of bytes in data.
|
||||
pub data_length: usize,
|
||||
/// Pointer to tags, null if no tags.
|
||||
pub tags: *mut Tag,
|
||||
/// Number of tags.
|
||||
pub tag_count: usize,
|
||||
}
|
||||
|
||||
impl Read for ReadableStream {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let ret = (self.read_fn)(self.userdata, buf.as_mut_ptr(), buf.len());
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for ReadableStream {
|
||||
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
|
||||
let (offset, whence) = match pos {
|
||||
SeekFrom::Start(off) => (off as i64, 0),
|
||||
SeekFrom::End(off) => (off, 2),
|
||||
SeekFrom::Current(off) => (off, 1),
|
||||
};
|
||||
let ret = (self.seek_fn)(self.userdata, offset, whence);
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "encode")]
|
||||
impl Write for WriteableStream {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let ret = (self.write_fn)(self.userdata, buf.as_ptr(), buf.len());
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
let ret = (self.flush_fn)(self.userdata);
|
||||
if ret == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Flush failed",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "encode")]
|
||||
impl Seek for WriteableStream {
|
||||
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
|
||||
let (offset, whence) = match pos {
|
||||
SeekFrom::Start(off) => (off as i64, 0),
|
||||
SeekFrom::End(off) => (off, 2),
|
||||
SeekFrom::Current(off) => (off, 1),
|
||||
};
|
||||
let ret = (self.seek_fn)(self.userdata, offset, whence);
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a readable stream.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn create_readable_stream(
|
||||
userdata: *mut c_void,
|
||||
read_fn: extern "C" fn(*mut c_void, *mut u8, usize) -> usize,
|
||||
seek_fn: extern "C" fn(*mut c_void, i64, i32) -> u64,
|
||||
) -> *mut ReadableStream {
|
||||
let stream = ReadableStream {
|
||||
userdata,
|
||||
read_fn,
|
||||
seek_fn,
|
||||
};
|
||||
Box::into_raw(Box::new(stream))
|
||||
}
|
||||
|
||||
/// Destroy a readable stream.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn destroy_readable_stream(stream: *mut ReadableStream) {
|
||||
if !stream.is_null() {
|
||||
drop(unsafe { Box::from_raw(stream) });
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a writeable stream.
|
||||
#[cfg(feature = "encode")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn create_writeable_stream(
|
||||
userdata: *mut c_void,
|
||||
write_fn: extern "C" fn(*mut c_void, *const u8, usize) -> usize,
|
||||
flush_fn: extern "C" fn(*mut c_void) -> i32,
|
||||
seek_fn: extern "C" fn(*mut c_void, i64, i32) -> u64,
|
||||
) -> *mut WriteableStream {
|
||||
let stream = WriteableStream {
|
||||
userdata,
|
||||
write_fn,
|
||||
flush_fn,
|
||||
seek_fn,
|
||||
};
|
||||
Box::into_raw(Box::new(stream))
|
||||
}
|
||||
|
||||
/// Destroy a writeable stream.
|
||||
#[cfg(feature = "encode")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn destroy_writeable_stream(stream: *mut WriteableStream) {
|
||||
if !stream.is_null() {
|
||||
drop(unsafe { Box::from_raw(stream) });
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if it's a valid TLG.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn check_tlg(stream: *mut ReadableStream) -> bool {
|
||||
if stream.is_null() {
|
||||
return false;
|
||||
}
|
||||
let stream = unsafe { &mut *stream };
|
||||
match crate::check_tlg(stream) {
|
||||
Ok(valid) => valid,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if it's a valid TLG.
|
||||
/// 11 bytes are needed.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn is_valid_tlg(data: *const u8, length: usize) -> bool {
|
||||
let data = unsafe { std::slice::from_raw_parts(data, length) };
|
||||
crate::is_valid_tlg(data)
|
||||
}
|
||||
|
||||
/// Load a TLG from a readable stream.
|
||||
/// Returns a pointer to Tlg struct, or null on error.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn load_tlg(stream: *mut ReadableStream) -> *mut Tlg {
|
||||
if stream.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
let stream = unsafe { &mut *stream };
|
||||
match crate::load_tlg(stream) {
|
||||
Ok(mut tlg) => {
|
||||
let color_type = match tlg.color {
|
||||
crate::TlgColorType::Grayscale8 => TlgColorType::Grayscale8,
|
||||
crate::TlgColorType::Bgr24 => TlgColorType::Bgr24,
|
||||
crate::TlgColorType::Bgra32 => TlgColorType::Bgra32,
|
||||
};
|
||||
tlg.data.shrink_to_fit();
|
||||
let data_ptr = tlg.data.as_mut_ptr();
|
||||
let data_len = tlg.data.len();
|
||||
std::mem::forget(tlg.data);
|
||||
let (tag, tag_count) = if tlg.tags.is_empty() {
|
||||
(std::ptr::null_mut(), 0)
|
||||
} else {
|
||||
let mut tags = Vec::with_capacity(tlg.tags.len());
|
||||
for (mut name, mut value) in tlg.tags.drain() {
|
||||
let name_ptr = name.as_mut_ptr();
|
||||
let name_len = name.len();
|
||||
name.shrink_to_fit();
|
||||
std::mem::forget(name);
|
||||
let value_ptr = value.as_mut_ptr();
|
||||
let value_len = value.len();
|
||||
value.shrink_to_fit();
|
||||
std::mem::forget(value);
|
||||
tags.push(Tag {
|
||||
name: name_ptr as *mut c_char,
|
||||
name_length: name_len,
|
||||
value: value_ptr as *mut c_char,
|
||||
value_length: value_len,
|
||||
});
|
||||
}
|
||||
let tag_ptr = tags.as_mut_ptr();
|
||||
let tag_count = tags.len();
|
||||
tags.shrink_to_fit();
|
||||
std::mem::forget(tags);
|
||||
(tag_ptr, tag_count)
|
||||
};
|
||||
let tlg_c = Tlg {
|
||||
version: tlg.version,
|
||||
width: tlg.width,
|
||||
height: tlg.height,
|
||||
color_type,
|
||||
data: data_ptr,
|
||||
data_length: data_len,
|
||||
tags: tag,
|
||||
tag_count,
|
||||
};
|
||||
Box::into_raw(Box::new(tlg_c))
|
||||
}
|
||||
Err(_) => std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Destroy a Tlg struct.
|
||||
/// Also frees the image data.
|
||||
/// WARN: Make sure the Tlg struct is created by `load_tlg`.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn destroy_tlg(tlg: *mut Tlg) {
|
||||
if tlg.is_null() {
|
||||
return;
|
||||
}
|
||||
let tlg = unsafe { Box::from_raw(tlg) };
|
||||
let _data = unsafe { Vec::from_raw_parts(tlg.data, tlg.data_length, tlg.data_length) };
|
||||
if !tlg.tags.is_null() {
|
||||
let tags = unsafe { Vec::from_raw_parts(tlg.tags, tlg.tag_count, tlg.tag_count) };
|
||||
for tag in tags {
|
||||
let _name = unsafe {
|
||||
Vec::from_raw_parts(tag.name as *mut u8, tag.name_length, tag.name_length)
|
||||
};
|
||||
let _value = unsafe {
|
||||
Vec::from_raw_parts(tag.value as *mut u8, tag.value_length, tag.value_length)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode a TLG image to a writeable stream.
|
||||
/// Returns true on success, false on error.
|
||||
#[cfg(feature = "encode")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn save_tlg(tlg: Tlg, stream: *mut WriteableStream) -> bool {
|
||||
if stream.is_null() || tlg.data.is_null() {
|
||||
return false;
|
||||
}
|
||||
let stream = unsafe { &mut *stream };
|
||||
let color = match tlg.color_type {
|
||||
TlgColorType::Grayscale8 => crate::TlgColorType::Grayscale8,
|
||||
TlgColorType::Bgr24 => crate::TlgColorType::Bgr24,
|
||||
TlgColorType::Bgra32 => crate::TlgColorType::Bgra32,
|
||||
};
|
||||
let data = unsafe { std::slice::from_raw_parts(tlg.data, tlg.data_length) }.to_vec();
|
||||
let mut tags = std::collections::HashMap::new();
|
||||
if !tlg.tags.is_null() && tlg.tag_count > 0 {
|
||||
let tag_slice = unsafe { std::slice::from_raw_parts(tlg.tags, tlg.tag_count) };
|
||||
for tag in tag_slice {
|
||||
let name =
|
||||
unsafe { std::slice::from_raw_parts(tag.name as *const u8, tag.name_length) }
|
||||
.to_vec();
|
||||
let value =
|
||||
unsafe { std::slice::from_raw_parts(tag.value as *const u8, tag.value_length) }
|
||||
.to_vec();
|
||||
tags.insert(name, value);
|
||||
}
|
||||
}
|
||||
let tlg_rust = crate::Tlg {
|
||||
version: tlg.version,
|
||||
width: tlg.width,
|
||||
height: tlg.height,
|
||||
color,
|
||||
data,
|
||||
tags,
|
||||
};
|
||||
match crate::save_tlg(&tlg_rust, stream) {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
//! A Rust library for processing TLG files.
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#[cfg(feature = "capi")]
|
||||
pub mod capi;
|
||||
mod load_tlg;
|
||||
#[cfg(feature = "encode")]
|
||||
mod save_tlg;
|
||||
|
||||
Reference in New Issue
Block a user