mirror of
https://github.com/lifegpc/libtlg-rs.git
synced 2026-06-15 02:14:38 +08:00
Add TLG5 decode support
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
target/
|
||||
339
Cargo.lock
generated
Normal file
339
Cargo.lock
generated
Normal file
@@ -0,0 +1,339 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "libtlg-rs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"overf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
||||
|
||||
[[package]]
|
||||
name = "overf"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63f45a6333db8b6985d6648e4f6c7f2aa814c660c0855c6f58ff67fea8b9f24b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tlg"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"libtlg-rs",
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
3
Cargo.toml
Normal file
3
Cargo.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["libtlg-rs", "tlg"]
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 lifegpc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
9
libtlg-rs/Cargo.toml
Normal file
9
libtlg-rs/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "libtlg-rs"
|
||||
version = "0.1.0"
|
||||
description = "Rust version of libtlg"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
overf = "0.1"
|
||||
31
libtlg-rs/src/lib.rs
Normal file
31
libtlg-rs/src/lib.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! A Rust library for processing TLG files.
|
||||
mod load_tlg;
|
||||
mod stream;
|
||||
mod tvpgl;
|
||||
mod types;
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
pub use types::{Tlg, TlgColorType, TlgError};
|
||||
/// The result type for TLG operations.
|
||||
pub type Result<T> = std::result::Result<T, TlgError>;
|
||||
pub use load_tlg::load_tlg;
|
||||
|
||||
/// Check if it's a valid TLG.
|
||||
///
|
||||
/// 11 bytes are needed.
|
||||
pub fn is_valid_tlg(data: &[u8]) -> bool {
|
||||
if data.len() < 11 {
|
||||
return false;
|
||||
}
|
||||
data == b"TLG0.0\x00sds\x1a" || data == b"TLG5.0\x00raw\x1a" || data == b"TLG6.0\x00raw\x1a"
|
||||
}
|
||||
|
||||
/// Check if it's a valid TLG.
|
||||
///
|
||||
/// Same as [`is_valid_tlg`]
|
||||
pub fn check_tlg<T: Read + Seek>(mut data: T) -> Result<bool> {
|
||||
let mut header = [0; 11];
|
||||
data.rewind()?;
|
||||
data.read_exact(&mut header)?;
|
||||
Ok(is_valid_tlg(&header))
|
||||
}
|
||||
252
libtlg-rs/src/load_tlg.rs
Normal file
252
libtlg-rs/src/load_tlg.rs
Normal file
@@ -0,0 +1,252 @@
|
||||
use crate::stream::ReadExt;
|
||||
use crate::tvpgl::*;
|
||||
use crate::*;
|
||||
use overf::wrapping;
|
||||
use std::io::SeekFrom;
|
||||
|
||||
fn load_tlg5<T: Read + Seek>(src: &mut T) -> Result<Tlg> {
|
||||
let colors = src.read_u8()?;
|
||||
let width = src.read_u32()?;
|
||||
let height = src.read_u32()?;
|
||||
let blockheight = src.read_u32()?;
|
||||
let color = match colors {
|
||||
3 => TlgColorType::Bgr24,
|
||||
4 => TlgColorType::Bgra32,
|
||||
_ => return Err(TlgError::UnsupportedColorType(colors)),
|
||||
};
|
||||
let blockcount = ((height - 1) / blockheight) + 1;
|
||||
src.seek_relative(blockcount as i64 * 4)?;
|
||||
let stride = width as usize * colors as usize;
|
||||
let mut output_data = vec![0u8; width as usize * height as usize * colors as usize];
|
||||
let mut text = [0u8; 4096];
|
||||
let mut inbuf = vec![0u8; blockheight as usize * width as usize + 10];
|
||||
let mut outbuf = vec![vec![0u8; blockheight as usize * width as usize + 10]; colors as usize];
|
||||
let mut prevline: Option<Vec<u8>> = None;
|
||||
let mut r = 0;
|
||||
for y_blk in (0..height).step_by(blockheight as usize) {
|
||||
for c in 0..colors {
|
||||
let mark = src.read_u8()?;
|
||||
let size = src.read_u32()?;
|
||||
if mark == 0 {
|
||||
src.read_exact(&mut inbuf[..size as usize])?;
|
||||
r = tlg5_decompress_slide(
|
||||
&mut outbuf[c as usize],
|
||||
&inbuf[..size as usize],
|
||||
size as usize,
|
||||
&mut text,
|
||||
r,
|
||||
);
|
||||
} else {
|
||||
src.read_exact(&mut outbuf[c as usize][..size as usize])?;
|
||||
}
|
||||
}
|
||||
let y_lim = (y_blk + blockheight).min(height);
|
||||
let mut outbufp = Vec::new();
|
||||
for c in 0..colors {
|
||||
outbufp.push(outbuf[c as usize].as_slice());
|
||||
}
|
||||
for y in y_blk..y_lim {
|
||||
let current = &mut output_data[(y as usize * stride)..(y as usize * stride + stride)];
|
||||
match prevline.take() {
|
||||
Some(prev) => match color {
|
||||
TlgColorType::Bgr24 => {
|
||||
tlg5_compose_colors3(current, &prev, &outbufp, width);
|
||||
outbufp[0] = &outbufp[0][width as usize..];
|
||||
outbufp[1] = &outbufp[1][width as usize..];
|
||||
outbufp[2] = &outbufp[2][width as usize..];
|
||||
}
|
||||
TlgColorType::Bgra32 => {
|
||||
tlg5_compose_colors4(current, &prev, &outbufp, width);
|
||||
outbufp[0] = &outbufp[0][width as usize..];
|
||||
outbufp[1] = &outbufp[1][width as usize..];
|
||||
outbufp[2] = &outbufp[2][width as usize..];
|
||||
outbufp[3] = &outbufp[3][width as usize..];
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
None => match color {
|
||||
TlgColorType::Bgra32 => {
|
||||
let mut current_pos = 0usize;
|
||||
let mut pr = 0u8;
|
||||
let mut pg = 0u8;
|
||||
let mut pb = 0u8;
|
||||
let mut pa = 0u8;
|
||||
for x in 0..width as usize {
|
||||
let mut b = outbufp[0][x];
|
||||
let g = outbufp[1][x];
|
||||
let mut r = outbufp[2][x];
|
||||
let a = outbufp[3][x];
|
||||
wrapping! {
|
||||
b += g;
|
||||
r += g;
|
||||
pb += b;
|
||||
pg += g;
|
||||
pr += r;
|
||||
pa += a;
|
||||
}
|
||||
current[current_pos] = pb;
|
||||
current_pos += 1;
|
||||
current[current_pos] = pg;
|
||||
current_pos += 1;
|
||||
current[current_pos] = pr;
|
||||
current_pos += 1;
|
||||
current[current_pos] = pa;
|
||||
current_pos += 1;
|
||||
}
|
||||
outbufp[0] = &outbufp[0][width as usize..];
|
||||
outbufp[1] = &outbufp[1][width as usize..];
|
||||
outbufp[2] = &outbufp[2][width as usize..];
|
||||
outbufp[3] = &outbufp[3][width as usize..];
|
||||
}
|
||||
TlgColorType::Bgr24 => {
|
||||
let mut current_pos = 0usize;
|
||||
let mut pr = 0u8;
|
||||
let mut pg = 0u8;
|
||||
let mut pb = 0u8;
|
||||
for x in 0..width as usize {
|
||||
let mut b = outbufp[0][x];
|
||||
let g = outbufp[1][x];
|
||||
let mut r = outbufp[2][x];
|
||||
wrapping! {
|
||||
b += g;
|
||||
r += g;
|
||||
pb += b;
|
||||
pg += g;
|
||||
pr += r;
|
||||
}
|
||||
current[current_pos] = pb;
|
||||
current_pos += 1;
|
||||
current[current_pos] = pg;
|
||||
current_pos += 1;
|
||||
current[current_pos] = pr;
|
||||
current_pos += 1;
|
||||
}
|
||||
outbufp[0] = &outbufp[0][width as usize..];
|
||||
outbufp[1] = &outbufp[1][width as usize..];
|
||||
outbufp[2] = &outbufp[2][width as usize..];
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
prevline = Some(current.to_vec());
|
||||
}
|
||||
}
|
||||
Ok(Tlg {
|
||||
tags: Default::default(),
|
||||
version: 5,
|
||||
width,
|
||||
height,
|
||||
color,
|
||||
data: output_data,
|
||||
})
|
||||
}
|
||||
|
||||
fn load_tlg6<T: Read + Seek>(_src: &mut T) -> Result<Tlg> {
|
||||
Err(TlgError::Str("TLG6 is not supported yet".to_string()))
|
||||
}
|
||||
|
||||
fn internal_load_tlg<T: Read + Seek>(src: &mut T) -> Result<Tlg> {
|
||||
let mut mark = [0; 11];
|
||||
src.read_exact(&mut mark)?;
|
||||
if &mark == b"TLG5.0\x00raw\x1a" {
|
||||
load_tlg5(src)
|
||||
} else if &mark == b"TLG6.0\x00raw\x1a" {
|
||||
load_tlg6(src)
|
||||
} else {
|
||||
Err(TlgError::InvalidFormat)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode TLG image
|
||||
pub fn load_tlg<T: Read + Seek>(mut src: T) -> Result<Tlg> {
|
||||
src.rewind()?;
|
||||
let mut mark = [0; 11];
|
||||
src.read_exact(&mut mark)?;
|
||||
if &mark == b"TLG0.0\x00sds\x1a" {
|
||||
let rawlen = src.read_u32()?;
|
||||
let mut tlg = internal_load_tlg(&mut src)?;
|
||||
let newlen = rawlen as u64 + 15;
|
||||
src.seek(SeekFrom::Start(newlen))?;
|
||||
let mut check = true;
|
||||
while check {
|
||||
let mut chunkname = [0; 4];
|
||||
if src.read(&mut chunkname)? != 4 {
|
||||
break;
|
||||
}
|
||||
let chunksize = src.read_u32()?;
|
||||
if &chunkname == b"tags" {
|
||||
let mut tag = vec![0; chunksize as usize];
|
||||
src.read_exact(&mut tag)?;
|
||||
let mut i = 0;
|
||||
let len = tag.len();
|
||||
while i < len {
|
||||
let mut namelen = 0usize;
|
||||
let mut c = tag[i];
|
||||
let mut ok = true;
|
||||
while c >= b'0' && c <= b'9' {
|
||||
namelen = namelen * 10 + (c - b'0') as usize;
|
||||
i += 1;
|
||||
if i >= len {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
c = tag[i];
|
||||
}
|
||||
if !ok {
|
||||
break;
|
||||
}
|
||||
if c != b':' {
|
||||
check = false;
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
let name = tag[i..i + namelen].to_vec();
|
||||
i += namelen;
|
||||
if i >= len {
|
||||
break;
|
||||
}
|
||||
let mut valuelen = 0usize;
|
||||
c = tag[i];
|
||||
ok = true;
|
||||
while c >= b'0' && c <= b'9' {
|
||||
valuelen = valuelen * 10 + (c - b'0') as usize;
|
||||
i += 1;
|
||||
if i >= len {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
c = tag[i];
|
||||
}
|
||||
if !ok {
|
||||
break;
|
||||
}
|
||||
if c != b':' {
|
||||
check = false;
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
let value = tag[i..i + valuelen].to_vec();
|
||||
i += valuelen;
|
||||
if i >= len {
|
||||
check = false;
|
||||
break;
|
||||
}
|
||||
c = tag[i];
|
||||
if c != b',' {
|
||||
check = false;
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
tlg.tags.insert(name, value);
|
||||
}
|
||||
} else {
|
||||
// skip the chunk
|
||||
src.seek_relative(chunksize as i64)?;
|
||||
}
|
||||
}
|
||||
Ok(tlg)
|
||||
} else {
|
||||
src.rewind()?;
|
||||
internal_load_tlg(&mut src)
|
||||
}
|
||||
}
|
||||
20
libtlg-rs/src/stream.rs
Normal file
20
libtlg-rs/src/stream.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use std::io::Read;
|
||||
|
||||
pub trait ReadExt {
|
||||
fn read_u32(&mut self) -> std::io::Result<u32>;
|
||||
fn read_u8(&mut self) -> std::io::Result<u8>;
|
||||
}
|
||||
|
||||
impl<R: Read> ReadExt for R {
|
||||
fn read_u32(&mut self) -> std::io::Result<u32> {
|
||||
let mut buf = [0; 4];
|
||||
self.read_exact(&mut buf)?;
|
||||
Ok(u32::from_le_bytes(buf))
|
||||
}
|
||||
|
||||
fn read_u8(&mut self) -> std::io::Result<u8> {
|
||||
let mut buf = [0; 1];
|
||||
self.read_exact(&mut buf)?;
|
||||
Ok(buf[0])
|
||||
}
|
||||
}
|
||||
122
libtlg-rs/src/tvpgl.rs
Normal file
122
libtlg-rs/src/tvpgl.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
use overf::wrapping;
|
||||
|
||||
pub fn tlg5_compose_colors3(outp: &mut [u8], upper: &[u8], buf: &[&[u8]], width: u32) {
|
||||
let mut outpos = 0usize;
|
||||
let mut upper_pos = 0usize;
|
||||
let mut pr = 0u8;
|
||||
let mut pg = 0u8;
|
||||
let mut pb = 0u8;
|
||||
for x in 0..width as usize {
|
||||
let mut b = buf[0][x];
|
||||
let g = buf[1][x];
|
||||
let mut r = buf[2][x];
|
||||
wrapping! {
|
||||
b += g;
|
||||
r += g;
|
||||
}
|
||||
wrapping! {
|
||||
pb += b;
|
||||
pg += g;
|
||||
pr += r;
|
||||
}
|
||||
outp[outpos] = wrapping! { pb + upper[upper_pos]};
|
||||
outpos += 1;
|
||||
upper_pos += 1;
|
||||
outp[outpos] = wrapping! { pg + upper[upper_pos]};
|
||||
outpos += 1;
|
||||
upper_pos += 1;
|
||||
outp[outpos] = wrapping! { pr + upper[upper_pos]};
|
||||
outpos += 1;
|
||||
upper_pos += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tlg5_compose_colors4(outp: &mut [u8], upper: &[u8], buf: &[&[u8]], width: u32) {
|
||||
let mut outpos = 0usize;
|
||||
let mut upper_pos = 0usize;
|
||||
let mut pr = 0u8;
|
||||
let mut pg = 0u8;
|
||||
let mut pb = 0u8;
|
||||
let mut pa = 0u8;
|
||||
for x in 0..width as usize {
|
||||
let mut b = buf[0][x];
|
||||
let g = buf[1][x];
|
||||
let mut r = buf[2][x];
|
||||
let a = buf[3][x];
|
||||
wrapping! {
|
||||
b += g;
|
||||
r += g;
|
||||
}
|
||||
wrapping! {
|
||||
pb += b;
|
||||
pg += g;
|
||||
pr += r;
|
||||
pa += a;
|
||||
}
|
||||
outp[outpos] = wrapping! { pb + upper[upper_pos]};
|
||||
outpos += 1;
|
||||
upper_pos += 1;
|
||||
outp[outpos] = wrapping! { pg + upper[upper_pos]};
|
||||
outpos += 1;
|
||||
upper_pos += 1;
|
||||
outp[outpos] = wrapping! { pr + upper[upper_pos]};
|
||||
outpos += 1;
|
||||
upper_pos += 1;
|
||||
outp[outpos] = wrapping! { pa + upper[upper_pos]};
|
||||
outpos += 1;
|
||||
upper_pos += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tlg5_decompress_slide(
|
||||
out: &mut [u8],
|
||||
inp: &[u8],
|
||||
insize: usize,
|
||||
text: &mut [u8],
|
||||
mut r: usize,
|
||||
) -> usize {
|
||||
let mut flags = 0u32;
|
||||
let mut inpos = 0usize;
|
||||
let mut outpos = 0usize;
|
||||
while inpos < insize {
|
||||
wrapping! { flags >>= 1 };
|
||||
if flags & 256 == 0 {
|
||||
flags = inp[inpos] as u32 | 0xff00;
|
||||
inpos += 1;
|
||||
}
|
||||
if flags & 1 != 0 {
|
||||
let mut mpos =
|
||||
wrapping! { inp[inpos] as usize | ((inp[inpos + 1] as usize & 0xf) << 8) };
|
||||
let mut mlen = wrapping! { (inp[inpos + 1] as usize & 0xf0) >> 4 };
|
||||
inpos += 2;
|
||||
mlen += 3;
|
||||
if mlen == 18 {
|
||||
mlen += inp[inpos] as usize;
|
||||
inpos += 1;
|
||||
}
|
||||
while mlen > 0 {
|
||||
out[outpos] = text[mpos];
|
||||
outpos += 1;
|
||||
text[r] = text[mpos];
|
||||
r += 1;
|
||||
mpos += 1;
|
||||
wrapping! {
|
||||
mpos &= 4095;
|
||||
r &= 4095;
|
||||
}
|
||||
mlen -= 1;
|
||||
}
|
||||
} else {
|
||||
let c = inp[inpos];
|
||||
inpos += 1;
|
||||
out[outpos] = c;
|
||||
outpos += 1;
|
||||
text[r] = c;
|
||||
r += 1;
|
||||
wrapping! {
|
||||
r &= 4095;
|
||||
}
|
||||
}
|
||||
}
|
||||
r
|
||||
}
|
||||
79
libtlg-rs/src/types.rs
Normal file
79
libtlg-rs/src/types.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use std::{collections::HashMap, hash::Hash};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
/// TLG Color Type
|
||||
pub enum TlgColorType {
|
||||
/// Grayscale 8-bit
|
||||
Grayscale8,
|
||||
/// BGR 8-bit
|
||||
Bgr24,
|
||||
/// BGRA 8-bit
|
||||
Bgra32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// TLG Image
|
||||
pub struct Tlg {
|
||||
/// Tag dictionary
|
||||
pub tags: HashMap<Vec<u8>, Vec<u8>>,
|
||||
/// TLG Version: 0=unknown, 5=v5, 6=v6
|
||||
pub version: u32,
|
||||
/// Image width
|
||||
pub width: u32,
|
||||
/// Image height
|
||||
pub height: u32,
|
||||
/// Color type
|
||||
pub color: TlgColorType,
|
||||
/// Image data
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// TLG Error
|
||||
pub enum TlgError {
|
||||
/// IO Error
|
||||
Io(std::io::Error),
|
||||
/// Invalid TLG format
|
||||
InvalidFormat,
|
||||
/// Unsupported color type
|
||||
UnsupportedColorType(u8),
|
||||
/// String type error
|
||||
Str(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TlgError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
TlgError::Io(e) => write!(f, "IO Error: {}", e),
|
||||
TlgError::InvalidFormat => write!(f, "Invalid TLG format"),
|
||||
TlgError::UnsupportedColorType(c) => write!(f, "Unsupported color type: {}", c),
|
||||
TlgError::Str(s) => write!(f, "{}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for TlgError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
TlgError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for TlgError {
|
||||
fn from(err: String) -> Self {
|
||||
TlgError::Str(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for TlgError {
|
||||
fn from(err: &str) -> Self {
|
||||
TlgError::Str(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&String> for TlgError {
|
||||
fn from(err: &String) -> Self {
|
||||
TlgError::Str(err.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for TlgError {}
|
||||
11
tlg/Cargo.toml
Normal file
11
tlg/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "tlg"
|
||||
version = "0.1.0"
|
||||
description = "Tools to process TLG image file."
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
libtlg-rs = { path = "../libtlg-rs" }
|
||||
png = "0.17"
|
||||
17
tlg/src/arg.rs
Normal file
17
tlg/src/arg.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about)]
|
||||
/// A command line tool to process TLG files.
|
||||
pub struct Arg {
|
||||
/// Path to the input TLG/PNG file.
|
||||
pub input: String,
|
||||
/// Path to the output TLG/PNG file.
|
||||
pub output: Option<String>,
|
||||
}
|
||||
|
||||
impl Arg {
|
||||
pub fn parse() -> Self {
|
||||
Parser::parse()
|
||||
}
|
||||
}
|
||||
54
tlg/src/main.rs
Normal file
54
tlg/src/main.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
mod arg;
|
||||
use std::io::Seek;
|
||||
|
||||
fn convert_bgr_to_rgb(data: &mut libtlg_rs::Tlg) {
|
||||
match data.color {
|
||||
libtlg_rs::TlgColorType::Bgra32 => {
|
||||
for i in (0..data.data.len()).step_by(4) {
|
||||
let b = data.data[i];
|
||||
data.data[i] = data.data[i + 2];
|
||||
data.data[i + 2] = b; // Swap red and blue
|
||||
}
|
||||
}
|
||||
libtlg_rs::TlgColorType::Bgr24 => {
|
||||
for i in (0..data.data.len()).step_by(3) {
|
||||
let b = data.data[i];
|
||||
data.data[i] = data.data[i + 2];
|
||||
data.data[i + 2] = b; // Swap red and blue
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = arg::Arg::parse();
|
||||
let file = std::fs::File::open(&args.input).expect("Failed to open input file");
|
||||
let mut file = std::io::BufReader::new(file);
|
||||
if libtlg_rs::check_tlg(&mut file).expect("Failed to check TLG format") {
|
||||
let mut tlg = libtlg_rs::load_tlg(&mut file).expect("Failed to load TLG file");
|
||||
let output = match &args.output {
|
||||
Some(output) => output.clone(),
|
||||
None => {
|
||||
let mut pb = std::path::PathBuf::from(&args.input);
|
||||
pb.set_extension("png");
|
||||
pb.to_string_lossy().to_string()
|
||||
}
|
||||
};
|
||||
convert_bgr_to_rgb(&mut tlg);
|
||||
let mut output_file = std::fs::File::create(&output).expect("Failed to create output file");
|
||||
let mut encoder = png::Encoder::new(&mut output_file, tlg.width, tlg.height);
|
||||
encoder.set_color(match tlg.color {
|
||||
libtlg_rs::TlgColorType::Bgra32 => png::ColorType::Rgba,
|
||||
libtlg_rs::TlgColorType::Bgr24 => png::ColorType::Rgb,
|
||||
libtlg_rs::TlgColorType::Grayscale8 => png::ColorType::Grayscale,
|
||||
});
|
||||
encoder.set_depth(png::BitDepth::Eight);
|
||||
let mut writer = encoder.write_header().expect("Failed to write PNG header");
|
||||
writer
|
||||
.write_image_data(&tlg.data)
|
||||
.expect("Failed to write PNG image data");
|
||||
} else {
|
||||
file.rewind().expect("Failed to rewind file");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user