mirror of
https://github.com/lifegpc/libtlg-rs.git
synced 2026-07-08 01:33:22 +08:00
features(libtlg-rs): Add tlg5 encode support
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
target/
|
target/
|
||||||
|
.vscode/
|
||||||
|
|||||||
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -164,7 +164,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libtlg-rs"
|
name = "libtlg-rs"
|
||||||
version = "0.1.4"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"overf",
|
"overf",
|
||||||
@@ -253,7 +253,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tlg"
|
name = "tlg"
|
||||||
version = "0.1.4"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"libtlg-rs",
|
"libtlg-rs",
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "libtlg-rs"
|
name = "libtlg-rs"
|
||||||
version = "0.1.4"
|
version = "0.2.0"
|
||||||
description = "Rust version of libtlg"
|
description = "Rust version of libtlg"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/lifegpc/libtlg-rs"
|
repository = "https://github.com/lifegpc/libtlg-rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
encode = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
overf = "0.1"
|
overf = "0.1"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
//! A Rust library for processing TLG files.
|
//! A Rust library for processing TLG files.
|
||||||
mod load_tlg;
|
mod load_tlg;
|
||||||
|
#[cfg(feature = "encode")]
|
||||||
|
mod save_tlg;
|
||||||
|
#[cfg(feature = "encode")]
|
||||||
|
mod slide;
|
||||||
mod stream;
|
mod stream;
|
||||||
|
#[cfg(feature = "encode")]
|
||||||
|
mod tlg5_saver;
|
||||||
mod tvpgl;
|
mod tvpgl;
|
||||||
mod types;
|
mod types;
|
||||||
use std::io::{Read, Seek};
|
use std::io::{Read, Seek};
|
||||||
@@ -9,6 +15,9 @@ pub use types::{Tlg, TlgColorType, TlgError};
|
|||||||
/// The result type for TLG operations.
|
/// The result type for TLG operations.
|
||||||
pub type Result<T> = std::result::Result<T, TlgError>;
|
pub type Result<T> = std::result::Result<T, TlgError>;
|
||||||
pub use load_tlg::load_tlg;
|
pub use load_tlg::load_tlg;
|
||||||
|
#[cfg(feature = "encode")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "encode")))]
|
||||||
|
pub use save_tlg::save_tlg;
|
||||||
|
|
||||||
/// Check if it's a valid TLG.
|
/// Check if it's a valid TLG.
|
||||||
///
|
///
|
||||||
@@ -17,7 +26,9 @@ pub fn is_valid_tlg(data: &[u8]) -> bool {
|
|||||||
if data.len() < 11 {
|
if data.len() < 11 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
data.starts_with(b"TLG0.0\x00sds\x1a") || data.starts_with(b"TLG5.0\x00raw\x1a") || data.starts_with(b"TLG6.0\x00raw\x1a")
|
data.starts_with(b"TLG0.0\x00sds\x1a")
|
||||||
|
|| data.starts_with(b"TLG5.0\x00raw\x1a")
|
||||||
|
|| data.starts_with(b"TLG6.0\x00raw\x1a")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if it's a valid TLG.
|
/// Check if it's a valid TLG.
|
||||||
|
|||||||
63
libtlg-rs/src/save_tlg.rs
Normal file
63
libtlg-rs/src/save_tlg.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::stream::*;
|
||||||
|
use crate::tlg5_saver::save_tlg5;
|
||||||
|
use std::io::{Seek, Write};
|
||||||
|
|
||||||
|
/// Encode TLG image
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "encode")))]
|
||||||
|
pub fn save_tlg<W: Write + Seek>(img: &Tlg, mut writer: W) -> Result<()> {
|
||||||
|
let colors = match img.color {
|
||||||
|
TlgColorType::Bgra32 => 4,
|
||||||
|
TlgColorType::Bgr24 => 3,
|
||||||
|
TlgColorType::Grayscale8 => 1,
|
||||||
|
};
|
||||||
|
let img_size = img.width as usize * colors as usize * img.height as usize;
|
||||||
|
if img.data.len() < img_size {
|
||||||
|
return Err(TlgError::EncodeError(format!(
|
||||||
|
"Image data size too small: expected {}, got {}",
|
||||||
|
img_size,
|
||||||
|
img.data.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if img.tags.is_empty() {
|
||||||
|
if img.version == 5 {
|
||||||
|
return save_tlg5(img, &mut writer);
|
||||||
|
} else {
|
||||||
|
return Err(TlgError::EncodeError(format!(
|
||||||
|
"Unsupported TLG version: {}",
|
||||||
|
img.version
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.write_all(b"TLG0.0\x00sds\x1a")?;
|
||||||
|
let rawlenpos = writer.stream_position()?;
|
||||||
|
writer.write_u32(0)?; // Placeholder for raw data length
|
||||||
|
if img.version == 5 {
|
||||||
|
save_tlg5(img, &mut writer)?;
|
||||||
|
} else {
|
||||||
|
return Err(TlgError::EncodeError(format!(
|
||||||
|
"Unsupported TLG version: {}",
|
||||||
|
img.version
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let pos_save = writer.stream_position()?;
|
||||||
|
writer.seek(std::io::SeekFrom::Start(rawlenpos))?;
|
||||||
|
let size = pos_save - rawlenpos - 4;
|
||||||
|
writer.write_u32(size as u32)?;
|
||||||
|
writer.seek(std::io::SeekFrom::Start(pos_save))?;
|
||||||
|
writer.write_all(b"tags")?;
|
||||||
|
let mut ss = Vec::new();
|
||||||
|
for (k, v) in &img.tags {
|
||||||
|
ss.write_all(k.len().to_string().as_bytes())?;
|
||||||
|
ss.write_all(b":")?;
|
||||||
|
ss.write_all(k)?;
|
||||||
|
ss.write_all(b"=")?;
|
||||||
|
ss.write_all(v.len().to_string().as_bytes())?;
|
||||||
|
ss.write_all(b":")?;
|
||||||
|
ss.write_all(v)?;
|
||||||
|
ss.write_all(b",")?;
|
||||||
|
}
|
||||||
|
writer.write_u32(ss.len() as u32)?;
|
||||||
|
writer.write_all(&ss)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
228
libtlg-rs/src/slide.rs
Normal file
228
libtlg-rs/src/slide.rs
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
//! Slide Compressor
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Chain {
|
||||||
|
prev: i32,
|
||||||
|
next: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const SLIDE_N: usize = 4096;
|
||||||
|
const SLIDE_M: usize = 18 + 255;
|
||||||
|
const TEXT_SIZE: usize = SLIDE_N + SLIDE_M;
|
||||||
|
const MAP_SIZE: usize = 256 * 256;
|
||||||
|
|
||||||
|
pub struct SlideCompressor {
|
||||||
|
text: Vec<u8>,
|
||||||
|
map: Vec<i32>,
|
||||||
|
chains: Vec<Chain>,
|
||||||
|
text2: Vec<u8>,
|
||||||
|
map2: Vec<i32>,
|
||||||
|
chains2: Vec<Chain>,
|
||||||
|
s: i32,
|
||||||
|
s2: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SlideCompressor {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut data = Self {
|
||||||
|
text: vec![0; TEXT_SIZE],
|
||||||
|
map: vec![-1; MAP_SIZE],
|
||||||
|
chains: vec![Chain { prev: -1, next: -1 }; SLIDE_N],
|
||||||
|
text2: vec![0; TEXT_SIZE],
|
||||||
|
map2: vec![0; MAP_SIZE],
|
||||||
|
chains2: vec![Chain { prev: 0, next: 0 }; SLIDE_N],
|
||||||
|
s: 0,
|
||||||
|
s2: 0,
|
||||||
|
};
|
||||||
|
for i in (0..SLIDE_N).rev() {
|
||||||
|
data.add_map(i as i32);
|
||||||
|
}
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_map(&mut self, p: i32) {
|
||||||
|
let place = self.text[p as usize] as i32
|
||||||
|
+ ((self.text[(p as usize + 1) & (SLIDE_N - 1)] as i32) << 8);
|
||||||
|
if self.map[place as usize] == -1 {
|
||||||
|
self.map[place as usize] = p;
|
||||||
|
} else {
|
||||||
|
let old = self.map[place as usize];
|
||||||
|
self.map[place as usize] = p;
|
||||||
|
self.chains[old as usize].prev = p;
|
||||||
|
self.chains[p as usize].next = old;
|
||||||
|
self.chains[p as usize].prev = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_map(&mut self, p: i32) {
|
||||||
|
let p_us = p as usize;
|
||||||
|
let mut n = self.chains[p_us].next;
|
||||||
|
if n != -1 {
|
||||||
|
self.chains[n as usize].prev = self.chains[p_us].prev;
|
||||||
|
}
|
||||||
|
n = self.chains[p_us].prev;
|
||||||
|
if n != -1 {
|
||||||
|
self.chains[n as usize].next = self.chains[p_us].next;
|
||||||
|
} else if self.chains[p_us].next != -1 {
|
||||||
|
let place =
|
||||||
|
self.text[p_us] as i32 + ((self.text[(p_us + 1) & (SLIDE_N - 1)] as i32) << 8);
|
||||||
|
self.map[place as usize] = self.chains[p_us].next;
|
||||||
|
} else {
|
||||||
|
let place =
|
||||||
|
self.text[p_us] as i32 + ((self.text[(p_us + 1) & (SLIDE_N - 1)] as i32) << 8);
|
||||||
|
self.map[place as usize] = -1;
|
||||||
|
}
|
||||||
|
self.chains[p_us].prev = -1;
|
||||||
|
self.chains[p_us].next = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_match(&self, cur: &[u8], s: i32) -> (i32, i32) {
|
||||||
|
if cur.len() < 3 {
|
||||||
|
return (0, 0);
|
||||||
|
}
|
||||||
|
let mut curlen = cur.len() as i32;
|
||||||
|
let place = cur[0] as i32 + ((cur[1] as i32) << 8);
|
||||||
|
let mut pos = 0;
|
||||||
|
let mut maxlen = 0;
|
||||||
|
let mut head = self.map[place as usize];
|
||||||
|
if head == -1 {
|
||||||
|
return (0, 0);
|
||||||
|
}
|
||||||
|
curlen -= 1;
|
||||||
|
while head != -1 {
|
||||||
|
let place_org = head;
|
||||||
|
if s == place_org || s == ((place_org + 1) & (SLIDE_N as i32 - 1)) {
|
||||||
|
head = self.chains[place_org as usize].next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut p = place_org + 2;
|
||||||
|
let mut lim = (if (SLIDE_M as i32) < curlen {
|
||||||
|
SLIDE_M as i32
|
||||||
|
} else {
|
||||||
|
curlen
|
||||||
|
}) + place_org;
|
||||||
|
if lim >= SLIDE_N as i32 {
|
||||||
|
if place_org <= s && s < SLIDE_N as i32 {
|
||||||
|
lim = s;
|
||||||
|
} else if s < (lim & (SLIDE_N as i32 - 1)) {
|
||||||
|
lim = s + SLIDE_N as i32;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if place_org <= s && s < lim {
|
||||||
|
lim = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut c_index = 2;
|
||||||
|
while p < lim
|
||||||
|
&& (c_index as usize) < cur.len()
|
||||||
|
&& self.text[p as usize] == cur[c_index as usize]
|
||||||
|
{
|
||||||
|
p += 1;
|
||||||
|
c_index += 1;
|
||||||
|
}
|
||||||
|
let matchlen = p - place_org;
|
||||||
|
if matchlen > maxlen {
|
||||||
|
maxlen = matchlen;
|
||||||
|
pos = place_org;
|
||||||
|
if matchlen == SLIDE_M as i32 {
|
||||||
|
return (maxlen, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
head = self.chains[place_org as usize].next;
|
||||||
|
}
|
||||||
|
(maxlen, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode_into(&mut self, input: &[u8], output: &mut Vec<u8>) -> usize {
|
||||||
|
if input.is_empty() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let mut code = [0u8; 40];
|
||||||
|
let mut codeptr: usize = 1;
|
||||||
|
let mut mask: u8 = 1;
|
||||||
|
code[0] = 0;
|
||||||
|
let mut idx: usize = 0;
|
||||||
|
let mut remain = input.len();
|
||||||
|
let mut s = self.s;
|
||||||
|
while remain > 0 {
|
||||||
|
let (len, pos) = {
|
||||||
|
let (l, p) = self.get_match(&input[idx..], s);
|
||||||
|
(l, p)
|
||||||
|
};
|
||||||
|
if len >= 3 {
|
||||||
|
code[0] |= mask;
|
||||||
|
if len >= 18 {
|
||||||
|
code[codeptr] = (pos & 0xff) as u8;
|
||||||
|
codeptr += 1;
|
||||||
|
code[codeptr] = (((pos & 0xf00) >> 8) as u8) | 0xf0;
|
||||||
|
codeptr += 1;
|
||||||
|
code[codeptr] = (len - 18) as u8;
|
||||||
|
codeptr += 1;
|
||||||
|
} else {
|
||||||
|
code[codeptr] = (pos & 0xff) as u8;
|
||||||
|
codeptr += 1;
|
||||||
|
code[codeptr] = (((pos & 0xf00) >> 8) as u8) | (((len - 3) as u8) << 4);
|
||||||
|
codeptr += 1;
|
||||||
|
}
|
||||||
|
let mut l = len as usize;
|
||||||
|
while l > 0 {
|
||||||
|
let c = input[idx];
|
||||||
|
idx += 1;
|
||||||
|
remain -= 1;
|
||||||
|
l -= 1;
|
||||||
|
let s_prev = (s - 1) & (SLIDE_N as i32 - 1);
|
||||||
|
self.delete_map(s_prev);
|
||||||
|
self.delete_map(s);
|
||||||
|
if (s as usize) < SLIDE_M - 1 {
|
||||||
|
self.text[(s as usize) + SLIDE_N] = c;
|
||||||
|
}
|
||||||
|
self.text[s as usize] = c;
|
||||||
|
self.add_map(s_prev);
|
||||||
|
self.add_map(s);
|
||||||
|
s = (s + 1) & (SLIDE_N as i32 - 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let c = input[idx];
|
||||||
|
idx += 1;
|
||||||
|
remain -= 1;
|
||||||
|
let s_prev = (s - 1) & (SLIDE_N as i32 - 1);
|
||||||
|
self.delete_map(s_prev);
|
||||||
|
self.delete_map(s);
|
||||||
|
if (s as usize) < SLIDE_M - 1 {
|
||||||
|
self.text[(s as usize) + SLIDE_N] = c;
|
||||||
|
}
|
||||||
|
self.text[s as usize] = c;
|
||||||
|
self.add_map(s_prev);
|
||||||
|
self.add_map(s);
|
||||||
|
s = (s + 1) & (SLIDE_N as i32 - 1);
|
||||||
|
code[codeptr] = c;
|
||||||
|
codeptr += 1;
|
||||||
|
}
|
||||||
|
mask <<= 1;
|
||||||
|
if mask == 0 {
|
||||||
|
output.extend_from_slice(&code[..codeptr]);
|
||||||
|
mask = 1;
|
||||||
|
codeptr = 1;
|
||||||
|
code[0] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mask != 1 {
|
||||||
|
output.extend_from_slice(&code[..codeptr]);
|
||||||
|
}
|
||||||
|
self.s = s;
|
||||||
|
output.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store(&mut self) {
|
||||||
|
self.s2 = self.s;
|
||||||
|
self.text2.copy_from_slice(&self.text);
|
||||||
|
self.map2.copy_from_slice(&self.map);
|
||||||
|
self.chains2.copy_from_slice(&self.chains);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restore(&mut self) {
|
||||||
|
self.s = self.s2;
|
||||||
|
self.text.copy_from_slice(&self.text2);
|
||||||
|
self.map.copy_from_slice(&self.map2);
|
||||||
|
self.chains.copy_from_slice(&self.chains2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
#[cfg(feature = "encode")]
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
pub trait ReadExt {
|
pub trait ReadExt {
|
||||||
fn read_u32(&mut self) -> std::io::Result<u32>;
|
fn read_u32(&mut self) -> std::io::Result<u32>;
|
||||||
@@ -18,3 +20,20 @@ impl<R: Read> ReadExt for R {
|
|||||||
Ok(buf[0])
|
Ok(buf[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "encode")]
|
||||||
|
pub trait WriteExt {
|
||||||
|
fn write_u32(&mut self, value: u32) -> std::io::Result<()>;
|
||||||
|
fn write_u8(&mut self, value: u8) -> std::io::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "encode")]
|
||||||
|
impl<W: Write> WriteExt for W {
|
||||||
|
fn write_u32(&mut self, value: u32) -> std::io::Result<()> {
|
||||||
|
self.write_all(&value.to_le_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_u8(&mut self, value: u8) -> std::io::Result<()> {
|
||||||
|
self.write_all(&[value])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
107
libtlg-rs/src/tlg5_saver.rs
Normal file
107
libtlg-rs/src/tlg5_saver.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::slide::*;
|
||||||
|
use crate::stream::*;
|
||||||
|
use overf::wrapping;
|
||||||
|
use std::io::{Seek, Write};
|
||||||
|
|
||||||
|
const BLOCK_HEIGHT: usize = 4;
|
||||||
|
|
||||||
|
pub fn save_tlg5<W: Write + Seek>(tlg: &Tlg, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_all(b"TLG5.0\x00raw\x1a")?;
|
||||||
|
let colors = match tlg.color {
|
||||||
|
TlgColorType::Bgra32 => 4,
|
||||||
|
TlgColorType::Bgr24 => 3,
|
||||||
|
TlgColorType::Grayscale8 => 1,
|
||||||
|
};
|
||||||
|
writer.write_u8(colors)?;
|
||||||
|
writer.write_u32(tlg.width)?;
|
||||||
|
writer.write_u32(tlg.height)?;
|
||||||
|
writer.write_u32(BLOCK_HEIGHT as u32)?;
|
||||||
|
let blockcount = ((tlg.height as usize - 1) / BLOCK_HEIGHT) + 1;
|
||||||
|
let mut compressor = SlideCompressor::new();
|
||||||
|
let mut written = [0; 4];
|
||||||
|
let mut blocksizes = vec![0; blockcount];
|
||||||
|
let mut cmpinbuf = vec![vec![0u8; tlg.width as usize * BLOCK_HEIGHT]; colors as usize];
|
||||||
|
let blocksizepos = writer.stream_position()?;
|
||||||
|
for _ in 0..blockcount {
|
||||||
|
writer.write_all(b" ")?; // Place holders
|
||||||
|
}
|
||||||
|
let mut block = 0;
|
||||||
|
for blk_y in (0..tlg.height as usize).step_by(BLOCK_HEIGHT) {
|
||||||
|
let ylim = (blk_y + BLOCK_HEIGHT).min(tlg.height as usize);
|
||||||
|
let mut inp = 0;
|
||||||
|
for y in blk_y..ylim {
|
||||||
|
let upper = if y != 0 {
|
||||||
|
&tlg.data[(y - 1) * tlg.width as usize * colors as usize
|
||||||
|
..y * tlg.width as usize * colors as usize]
|
||||||
|
} else {
|
||||||
|
&[]
|
||||||
|
};
|
||||||
|
let mut upper_pos = 0;
|
||||||
|
let current = &tlg.data[y * tlg.width as usize * colors as usize
|
||||||
|
..(y + 1) * tlg.width as usize * colors as usize];
|
||||||
|
let mut current_pos = 0;
|
||||||
|
let mut prevcl = [0; 4];
|
||||||
|
let mut val = [0; 4];
|
||||||
|
for _ in 0..tlg.width as usize {
|
||||||
|
for c in 0..colors as usize {
|
||||||
|
let cl = if y != 0 {
|
||||||
|
let c = current[current_pos];
|
||||||
|
current_pos += 1;
|
||||||
|
let p = upper[upper_pos];
|
||||||
|
upper_pos += 1;
|
||||||
|
wrapping! { c - p }
|
||||||
|
} else {
|
||||||
|
let c = current[current_pos];
|
||||||
|
current_pos += 1;
|
||||||
|
c
|
||||||
|
} as i32;
|
||||||
|
val[c] = wrapping! { cl - prevcl[c] };
|
||||||
|
prevcl[c] = cl;
|
||||||
|
}
|
||||||
|
if colors == 1 {
|
||||||
|
cmpinbuf[0][inp] = val[0] as u8;
|
||||||
|
} else if colors == 3 {
|
||||||
|
cmpinbuf[0][inp] = wrapping! { val[0] - val[1] } as u8;
|
||||||
|
cmpinbuf[1][inp] = val[1] as u8;
|
||||||
|
cmpinbuf[2][inp] = wrapping! { val[2] - val[1] } as u8;
|
||||||
|
} else if colors == 4 {
|
||||||
|
cmpinbuf[0][inp] = wrapping! { val[0] - val[1] } as u8;
|
||||||
|
cmpinbuf[1][inp] = val[1] as u8;
|
||||||
|
cmpinbuf[2][inp] = wrapping! { val[2] - val[1] } as u8;
|
||||||
|
cmpinbuf[3][inp] = val[3] as u8;
|
||||||
|
}
|
||||||
|
inp += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// LZSS
|
||||||
|
let mut blocksize = 0;
|
||||||
|
for c in 0..colors as usize {
|
||||||
|
compressor.store();
|
||||||
|
let mut outbuf = Vec::new();
|
||||||
|
let wrote = compressor.encode_into(&cmpinbuf[c][..inp], &mut outbuf);
|
||||||
|
if wrote < inp {
|
||||||
|
writer.write_u8(0)?;
|
||||||
|
writer.write_u32(wrote as u32)?;
|
||||||
|
writer.write_all(&outbuf)?;
|
||||||
|
blocksize += wrote + 4 + 1;
|
||||||
|
} else {
|
||||||
|
compressor.restore();
|
||||||
|
writer.write_u8(1)?;
|
||||||
|
writer.write_u32(inp as u32)?;
|
||||||
|
writer.write_all(&cmpinbuf[c][..inp])?;
|
||||||
|
blocksize += inp + 4 + 1;
|
||||||
|
}
|
||||||
|
written[c] += wrote;
|
||||||
|
}
|
||||||
|
blocksizes[block] = blocksize;
|
||||||
|
block += 1;
|
||||||
|
}
|
||||||
|
let pos_save = writer.stream_position()?;
|
||||||
|
writer.seek(std::io::SeekFrom::Start(blocksizepos))?;
|
||||||
|
for i in 0..blockcount {
|
||||||
|
writer.write_u32(blocksizes[i] as u32)?;
|
||||||
|
}
|
||||||
|
writer.seek(std::io::SeekFrom::Start(pos_save))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -43,6 +43,10 @@ pub enum TlgError {
|
|||||||
UnsupportedCompressedMethod(u8),
|
UnsupportedCompressedMethod(u8),
|
||||||
/// String type error
|
/// String type error
|
||||||
Str(String),
|
Str(String),
|
||||||
|
#[cfg(feature = "encode")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "encode")))]
|
||||||
|
/// Encoding error
|
||||||
|
EncodeError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for TlgError {
|
impl std::fmt::Display for TlgError {
|
||||||
@@ -56,6 +60,8 @@ impl std::fmt::Display for TlgError {
|
|||||||
write!(f, "Unsupported compressed method: {}", m)
|
write!(f, "Unsupported compressed method: {}", m)
|
||||||
}
|
}
|
||||||
TlgError::Str(s) => write!(f, "{}", s),
|
TlgError::Str(s) => write!(f, "{}", s),
|
||||||
|
#[cfg(feature = "encode")]
|
||||||
|
TlgError::EncodeError(s) => write!(f, "Encoding error: {}", s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tlg"
|
name = "tlg"
|
||||||
version = "0.1.4"
|
version = "0.2.0"
|
||||||
description = "Tools to process TLG image file."
|
description = "Tools to process TLG image file."
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/lifegpc/libtlg-rs"
|
repository = "https://github.com/lifegpc/libtlg-rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["encode"]
|
||||||
|
encode = ["libtlg-rs/encode"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
libtlg-rs = { path = "../libtlg-rs" }
|
libtlg-rs = { path = "../libtlg-rs" }
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
mod arg;
|
mod arg;
|
||||||
|
#[cfg(feature = "encode")]
|
||||||
|
use std::io::BufRead;
|
||||||
use std::io::{Seek, Write};
|
use std::io::{Seek, Write};
|
||||||
|
|
||||||
fn convert_bgr_to_rgb(data: &mut libtlg_rs::Tlg) {
|
fn convert_bgr_to_rgb(data: &mut libtlg_rs::Tlg) {
|
||||||
@@ -66,5 +68,61 @@ fn main() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
file.rewind().expect("Failed to rewind file");
|
file.rewind().expect("Failed to rewind file");
|
||||||
|
#[cfg(feature = "encode")]
|
||||||
|
{
|
||||||
|
let decoder = png::Decoder::new(file);
|
||||||
|
let mut reader = decoder.read_info().expect("Failed to read PNG info");
|
||||||
|
let width = reader.info().width;
|
||||||
|
let height = reader.info().height;
|
||||||
|
if reader.info().bit_depth != png::BitDepth::Eight {
|
||||||
|
panic!("Unsupported bit depth: {:?}", reader.info().bit_depth);
|
||||||
|
}
|
||||||
|
let color_type = match reader.info().color_type {
|
||||||
|
png::ColorType::Rgba => libtlg_rs::TlgColorType::Bgra32,
|
||||||
|
png::ColorType::Rgb => libtlg_rs::TlgColorType::Bgr24,
|
||||||
|
png::ColorType::Grayscale => libtlg_rs::TlgColorType::Grayscale8,
|
||||||
|
_ => panic!("Unsupported color type: {:?}", reader.info().color_type),
|
||||||
|
};
|
||||||
|
let imgsize = width as usize * height as usize * reader.info().color_type.samples();
|
||||||
|
let mut data = vec![0u8; imgsize];
|
||||||
|
reader
|
||||||
|
.next_frame(&mut data)
|
||||||
|
.expect("Failed to read PNG frame");
|
||||||
|
let mut tags = std::collections::HashMap::new();
|
||||||
|
let tags_path = get_relative_path(&args.input, "tags");
|
||||||
|
if std::path::Path::new(&tags_path).exists() {
|
||||||
|
let tags_file = std::fs::File::open(&tags_path).expect("Failed to open tags file");
|
||||||
|
let mut tags_reader = std::io::BufReader::new(tags_file);
|
||||||
|
let mut line = String::new();
|
||||||
|
while tags_reader
|
||||||
|
.read_line(&mut line)
|
||||||
|
.expect("Failed to read line")
|
||||||
|
> 0
|
||||||
|
{
|
||||||
|
if let Some(eq_pos) = line.find('=') {
|
||||||
|
let key = line[..eq_pos].trim().as_bytes().to_vec();
|
||||||
|
let value = line[eq_pos + 1..].trim().as_bytes().to_vec();
|
||||||
|
tags.insert(key, value);
|
||||||
|
}
|
||||||
|
line.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut tlg = libtlg_rs::Tlg {
|
||||||
|
tags,
|
||||||
|
version: 5,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
color: color_type,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
convert_bgr_to_rgb(&mut tlg);
|
||||||
|
let output = match &args.output {
|
||||||
|
Some(output) => output.clone(),
|
||||||
|
None => get_relative_path(&args.input, "tlg"),
|
||||||
|
};
|
||||||
|
let mut output_file =
|
||||||
|
std::fs::File::create(&output).expect("Failed to create output file");
|
||||||
|
libtlg_rs::save_tlg(&tlg, &mut output_file).expect("Failed to save TLG file");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user