This commit is contained in:
2025-10-28 15:30:36 +08:00
parent 62e17ddda9
commit 971a7c0d56
5 changed files with 333 additions and 2 deletions

2
Cargo.lock generated
View File

@@ -164,7 +164,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libtlg-rs"
version = "0.2.2"
version = "0.2.3"
dependencies = [
"lazy_static",
"overf",

View File

@@ -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
View File

@@ -0,0 +1 @@
language = "C"

327
libtlg-rs/src/capi.rs Normal file
View 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,
}
}

View File

@@ -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;