mirror of
https://github.com/lifegpc/libtlg-rs.git
synced 2026-06-13 09:29:12 +08:00
features(libtlg-rs): Add tlg5 encode support
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
target/
|
||||
.vscode/
|
||||
|
||||
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -164,7 +164,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libtlg-rs"
|
||||
version = "0.1.4"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"overf",
|
||||
@@ -253,7 +253,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tlg"
|
||||
version = "0.1.4"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"libtlg-rs",
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
[package]
|
||||
name = "libtlg-rs"
|
||||
version = "0.1.4"
|
||||
version = "0.2.0"
|
||||
description = "Rust version of libtlg"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/lifegpc/libtlg-rs"
|
||||
|
||||
[features]
|
||||
encode = []
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1"
|
||||
overf = "0.1"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
//! A Rust library for processing TLG files.
|
||||
mod load_tlg;
|
||||
#[cfg(feature = "encode")]
|
||||
mod save_tlg;
|
||||
#[cfg(feature = "encode")]
|
||||
mod slide;
|
||||
mod stream;
|
||||
#[cfg(feature = "encode")]
|
||||
mod tlg5_saver;
|
||||
mod tvpgl;
|
||||
mod types;
|
||||
use std::io::{Read, Seek};
|
||||
@@ -9,6 +15,9 @@ 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;
|
||||
#[cfg(feature = "encode")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "encode")))]
|
||||
pub use save_tlg::save_tlg;
|
||||
|
||||
/// Check if it's a valid TLG.
|
||||
///
|
||||
@@ -17,7 +26,9 @@ pub fn is_valid_tlg(data: &[u8]) -> bool {
|
||||
if data.len() < 11 {
|
||||
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.
|
||||
|
||||
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;
|
||||
#[cfg(feature = "encode")]
|
||||
use std::io::Write;
|
||||
|
||||
pub trait ReadExt {
|
||||
fn read_u32(&mut self) -> std::io::Result<u32>;
|
||||
@@ -18,3 +20,20 @@ impl<R: Read> ReadExt for R {
|
||||
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),
|
||||
/// String type error
|
||||
Str(String),
|
||||
#[cfg(feature = "encode")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "encode")))]
|
||||
/// Encoding error
|
||||
EncodeError(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TlgError {
|
||||
@@ -56,6 +60,8 @@ impl std::fmt::Display for TlgError {
|
||||
write!(f, "Unsupported compressed method: {}", m)
|
||||
}
|
||||
TlgError::Str(s) => write!(f, "{}", s),
|
||||
#[cfg(feature = "encode")]
|
||||
TlgError::EncodeError(s) => write!(f, "Encoding error: {}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
[package]
|
||||
name = "tlg"
|
||||
version = "0.1.4"
|
||||
version = "0.2.0"
|
||||
description = "Tools to process TLG image file."
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/lifegpc/libtlg-rs"
|
||||
|
||||
[features]
|
||||
default = ["encode"]
|
||||
encode = ["libtlg-rs/encode"]
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
libtlg-rs = { path = "../libtlg-rs" }
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
mod arg;
|
||||
#[cfg(feature = "encode")]
|
||||
use std::io::BufRead;
|
||||
use std::io::{Seek, Write};
|
||||
|
||||
fn convert_bgr_to_rgb(data: &mut libtlg_rs::Tlg) {
|
||||
@@ -66,5 +68,61 @@ fn main() {
|
||||
}
|
||||
} else {
|
||||
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