mirror of
https://github.com/G2-Games/lbee-utils.git
synced 2025-04-19 15:22:53 -05:00
Implemented preliminary incorrect compression
This commit is contained in:
parent
3a5e27de4e
commit
c438399548
6 changed files with 201 additions and 81 deletions
|
@ -341,7 +341,8 @@ pub fn rgba_to_indexed(input: &[u8], palette: &[[u8; 4]]) -> Result<Vec<u8>, CzE
|
||||||
let mut output_map = Vec::new();
|
let mut output_map = Vec::new();
|
||||||
|
|
||||||
for rgba in input.windows(4).step_by(4) {
|
for rgba in input.windows(4).step_by(4) {
|
||||||
dbg!(rgba);
|
let index = palette.iter().position(|e| e == rgba).unwrap_or_default();
|
||||||
|
output_map.push(index as u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(output_map)
|
Ok(output_map)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use image::RgbaImage;
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::BTreeMap,
|
collections::{BTreeMap, HashMap},
|
||||||
io::{Read, Seek, Write},
|
io::{Read, Seek, Write},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,6 +36,34 @@ pub struct CompressionInfo {
|
||||||
pub length: usize,
|
pub length: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for CompressionInfo {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
chunk_count: 0,
|
||||||
|
total_size_compressed: 0,
|
||||||
|
total_size_raw: 0,
|
||||||
|
chunks: Vec::new(),
|
||||||
|
length: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompressionInfo {
|
||||||
|
pub fn write_into<T: WriteBytesExt + Write>(
|
||||||
|
&self,
|
||||||
|
output: &mut T,
|
||||||
|
) -> Result<(), std::io::Error> {
|
||||||
|
output.write_u32::<LittleEndian>(self.chunk_count as u32)?;
|
||||||
|
|
||||||
|
for chunk in &self.chunks {
|
||||||
|
output.write_u32::<LittleEndian>(chunk.size_compressed as u32)?;
|
||||||
|
output.write_u32::<LittleEndian>(chunk.size_raw as u32)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get info about the compression chunks
|
/// Get info about the compression chunks
|
||||||
///
|
///
|
||||||
/// These are defined by a length value, followed by the number of data chunks
|
/// These are defined by a length value, followed by the number of data chunks
|
||||||
|
@ -123,7 +150,7 @@ pub fn decompress_2<T: Seek + ReadBytesExt + Read>(
|
||||||
pub fn decompress_lzw2(input_data: &[u8], size: usize) -> Vec<u8> {
|
pub fn decompress_lzw2(input_data: &[u8], size: usize) -> Vec<u8> {
|
||||||
let mut data = input_data.to_vec();
|
let mut data = input_data.to_vec();
|
||||||
data[0..2].copy_from_slice(&[0, 0]);
|
data[0..2].copy_from_slice(&[0, 0]);
|
||||||
let mut dictionary = BTreeMap::new();
|
let mut dictionary = HashMap::new();
|
||||||
for i in 0..256 {
|
for i in 0..256 {
|
||||||
dictionary.insert(i as u64, vec![i as u8]);
|
dictionary.insert(i as u64, vec![i as u8]);
|
||||||
}
|
}
|
||||||
|
@ -174,7 +201,12 @@ fn get_offset(input: &[u8], src: usize) -> usize {
|
||||||
(((input[src] as usize) | (input[src + 1] as usize) << 8) - 0x101) * 2
|
(((input[src] as usize) | (input[src + 1] as usize) << 8) - 0x101) * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_range(bitmap: &mut Vec<u8>, input: &[u8], src: usize, dst: usize) -> usize {
|
fn copy_range(
|
||||||
|
bitmap: &mut Vec<u8>,
|
||||||
|
input: &[u8],
|
||||||
|
src: usize,
|
||||||
|
dst: usize
|
||||||
|
) -> usize {
|
||||||
let mut dst = dst;
|
let mut dst = dst;
|
||||||
let start_pos = dst;
|
let start_pos = dst;
|
||||||
|
|
||||||
|
@ -257,45 +289,101 @@ pub fn line_diff<T: CzHeader>(header: &T, data: &[u8]) -> Vec<u8> {
|
||||||
output_buf
|
output_buf
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn line_diff_cz4(picture: &mut RgbaImage, pixel_byte_count: usize, data: &[u8]) {
|
|
||||||
let width = picture.width();
|
|
||||||
let height = picture.height();
|
|
||||||
let block_height = (f32::ceil(height as f32 / 3.0) as u16) as u32;
|
|
||||||
|
|
||||||
let mut curr_line;
|
pub fn compress(
|
||||||
let mut prev_line = vec![0u8; width as usize * pixel_byte_count];
|
data: &[u8],
|
||||||
|
size: usize,
|
||||||
|
) -> (Vec<u8>, CompressionInfo) {
|
||||||
|
let mut size = size;
|
||||||
|
if size == 0 {
|
||||||
|
size = 0xFEFD
|
||||||
|
}
|
||||||
|
|
||||||
let mut i = 0;
|
let mut part_data;
|
||||||
for y in 0..height {
|
|
||||||
curr_line = data[i..i + width as usize * pixel_byte_count].to_vec();
|
|
||||||
|
|
||||||
if y % block_height != 0 {
|
let mut offset = 0;
|
||||||
for x in 0..(width as usize * pixel_byte_count) {
|
let mut count = 0;
|
||||||
curr_line[x] = u8::wrapping_add(curr_line[x], prev_line[x])
|
let mut last = String::new();
|
||||||
|
|
||||||
|
let mut output_buf: Vec<u8> = vec![];
|
||||||
|
let mut output_info = CompressionInfo {
|
||||||
|
total_size_raw: data.len(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
(count, part_data, last) = compress_lzw(&data[offset..], size, last);
|
||||||
|
if count == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset += count;
|
||||||
|
|
||||||
|
for d in &part_data {
|
||||||
|
output_buf.write(&d.to_le_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
output_info.chunks.push(ChunkInfo {
|
||||||
|
size_compressed: part_data.len(),
|
||||||
|
size_raw: count
|
||||||
|
});
|
||||||
|
|
||||||
|
output_info.chunk_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
output_info.total_size_compressed = output_buf.len() / 2;
|
||||||
|
|
||||||
|
(output_buf, output_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compress_lzw(data: &[u8], size: usize, last: String) -> (usize, Vec<u16>, String) {
|
||||||
|
let mut count = 0;
|
||||||
|
let mut dictionary = HashMap::new();
|
||||||
|
for i in 0..256 {
|
||||||
|
dictionary.insert(i.to_string(), i as u16);
|
||||||
|
}
|
||||||
|
let mut dictionary_count = (dictionary.len() + 1) as u16;
|
||||||
|
|
||||||
|
|
||||||
|
let mut element = String::new();
|
||||||
|
if last.len() != 0 {
|
||||||
|
element = last.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut compressed = Vec::with_capacity(size);
|
||||||
|
for c in data {
|
||||||
|
let mut entry = element.clone();
|
||||||
|
entry.push_str(&c.to_string());
|
||||||
|
|
||||||
|
if dictionary.get(&entry).is_some() {
|
||||||
|
element = entry
|
||||||
|
} else {
|
||||||
|
compressed.push(*dictionary.get(&element).unwrap());
|
||||||
|
dictionary.insert(entry, dictionary_count);
|
||||||
|
element = c.to_string();
|
||||||
|
dictionary_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
count += 1;
|
||||||
|
|
||||||
|
if size > 0 && compressed.len() == size {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for x in 0..width as usize {
|
let last_element = element;
|
||||||
if pixel_byte_count == 1 {
|
if compressed.len() == 0 {
|
||||||
picture.get_pixel_mut(x as u32, y).0[3] = curr_line[x];
|
if last_element.len() != 0 {
|
||||||
} else if pixel_byte_count == 4 {
|
for c in last_element.bytes() {
|
||||||
picture.get_pixel_mut(x as u32, y).0 = [
|
compressed.push(*dictionary.get(&c.to_string()).unwrap());
|
||||||
curr_line[x * pixel_byte_count],
|
|
||||||
curr_line[x * pixel_byte_count + 1],
|
|
||||||
curr_line[x * pixel_byte_count + 2],
|
|
||||||
curr_line[x * pixel_byte_count + 3],
|
|
||||||
];
|
|
||||||
} else if pixel_byte_count == 3 {
|
|
||||||
picture.get_pixel_mut(x as u32, y).0 = [
|
|
||||||
curr_line[x * pixel_byte_count],
|
|
||||||
curr_line[x * pixel_byte_count + 1],
|
|
||||||
curr_line[x * pixel_byte_count + 2],
|
|
||||||
0xFF,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return (count, compressed, String::new())
|
||||||
|
} else if compressed.len() < size {
|
||||||
|
if last_element.len() != 0 {
|
||||||
|
compressed.push(*dictionary.get(&last_element).unwrap());
|
||||||
|
}
|
||||||
|
return (count, compressed, String::new())
|
||||||
|
}
|
||||||
|
|
||||||
prev_line.clone_from(&curr_line);
|
(count, compressed, last_element)
|
||||||
i += width as usize * pixel_byte_count;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,14 +99,18 @@ impl DynamicCz {
|
||||||
let output_bitmap;
|
let output_bitmap;
|
||||||
match &self.palette {
|
match &self.palette {
|
||||||
Some(pal) if self.header_common.depth() <= 8 => {
|
Some(pal) if self.header_common.depth() <= 8 => {
|
||||||
output_bitmap = rgba_to_indexed(&self.bitmap(), &pal)?
|
output_bitmap = rgba_to_indexed(&self.bitmap(), &pal)?;
|
||||||
|
|
||||||
|
for rgba in pal {
|
||||||
|
out_file.write_all(rgba)?;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_ => output_bitmap = self.bitmap().clone()
|
_ => output_bitmap = self.bitmap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.header_common.version() {
|
match self.header_common.version() {
|
||||||
CzVersion::CZ0 => cz0::encode(&mut out_file, &self.bitmap)?,
|
CzVersion::CZ0 => cz0::encode(&mut out_file, &output_bitmap)?,
|
||||||
CzVersion::CZ1 => todo!(),
|
CzVersion::CZ1 => cz1::encode(&mut out_file, &output_bitmap)?,
|
||||||
CzVersion::CZ2 => todo!(),
|
CzVersion::CZ2 => todo!(),
|
||||||
CzVersion::CZ3 => todo!(),
|
CzVersion::CZ3 => todo!(),
|
||||||
CzVersion::CZ4 => todo!(),
|
CzVersion::CZ4 => todo!(),
|
||||||
|
|
|
@ -2,13 +2,15 @@ use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
|
|
||||||
use crate::common::CzError;
|
use crate::common::CzError;
|
||||||
use crate::compression::{decompress, get_chunk_info};
|
use crate::compression::{compress, decompress, get_chunk_info};
|
||||||
|
|
||||||
pub fn decode<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Vec<u8>, CzError> {
|
pub fn decode<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Vec<u8>, CzError> {
|
||||||
// Get the information about the compressed chunks
|
// Get the information about the compressed chunks
|
||||||
let block_info = get_chunk_info(bytes)?;
|
let block_info = get_chunk_info(bytes)?;
|
||||||
bytes.seek(SeekFrom::Start(block_info.length as u64))?;
|
bytes.seek(SeekFrom::Start(block_info.length as u64))?;
|
||||||
|
|
||||||
|
dbg!(&block_info);
|
||||||
|
|
||||||
// Get the bitmap
|
// Get the bitmap
|
||||||
let bitmap = decompress(bytes, &block_info).unwrap();
|
let bitmap = decompress(bytes, &block_info).unwrap();
|
||||||
|
|
||||||
|
@ -19,7 +21,13 @@ pub fn encode<T: WriteBytesExt + Write>(
|
||||||
output: &mut T,
|
output: &mut T,
|
||||||
bitmap: &[u8]
|
bitmap: &[u8]
|
||||||
) -> Result<(), CzError> {
|
) -> Result<(), CzError> {
|
||||||
output.write_all(bitmap)?;
|
let (compressed_data, compressed_info) = compress(bitmap, 65277);
|
||||||
|
|
||||||
|
dbg!(&compressed_info);
|
||||||
|
|
||||||
|
compressed_info.write_into(output)?;
|
||||||
|
|
||||||
|
output.write_all(&compressed_data)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
use byteorder::ReadBytesExt;
|
use byteorder::ReadBytesExt;
|
||||||
|
use image::RgbaImage;
|
||||||
|
|
||||||
use crate::common::{CommonHeader, CzError, CzHeader};
|
use crate::common::{CommonHeader, CzError, CzHeader};
|
||||||
use crate::compression::{decompress, line_diff_cz4, get_chunk_info};
|
use crate::compression::{decompress, get_chunk_info};
|
||||||
|
|
||||||
pub fn decode<T: Seek + ReadBytesExt + Read>(bytes: &mut T, header: &CommonHeader) -> Result<Vec<u8>, CzError> {
|
pub fn decode<T: Seek + ReadBytesExt + Read>(bytes: &mut T, header: &CommonHeader) -> Result<Vec<u8>, CzError> {
|
||||||
let block_info = get_chunk_info(bytes)?;
|
let block_info = get_chunk_info(bytes)?;
|
||||||
|
@ -22,3 +23,49 @@ pub fn decode<T: Seek + ReadBytesExt + Read>(bytes: &mut T, header: &CommonHeade
|
||||||
|
|
||||||
Ok(picture.into_raw())
|
Ok(picture.into_raw())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn line_diff_cz4(
|
||||||
|
picture: &mut RgbaImage,
|
||||||
|
pixel_byte_count: usize, data: &[u8]
|
||||||
|
) {
|
||||||
|
let width = picture.width();
|
||||||
|
let height = picture.height();
|
||||||
|
let block_height = (f32::ceil(height as f32 / 3.0) as u16) as u32;
|
||||||
|
|
||||||
|
let mut curr_line;
|
||||||
|
let mut prev_line = vec![0u8; width as usize * pixel_byte_count];
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
for y in 0..height {
|
||||||
|
curr_line = data[i..i + width as usize * pixel_byte_count].to_vec();
|
||||||
|
|
||||||
|
if y % block_height != 0 {
|
||||||
|
for x in 0..(width as usize * pixel_byte_count) {
|
||||||
|
curr_line[x] = u8::wrapping_add(curr_line[x], prev_line[x])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for x in 0..width as usize {
|
||||||
|
if pixel_byte_count == 1 {
|
||||||
|
picture.get_pixel_mut(x as u32, y).0[3] = curr_line[x];
|
||||||
|
} else if pixel_byte_count == 4 {
|
||||||
|
picture.get_pixel_mut(x as u32, y).0 = [
|
||||||
|
curr_line[x * pixel_byte_count],
|
||||||
|
curr_line[x * pixel_byte_count + 1],
|
||||||
|
curr_line[x * pixel_byte_count + 2],
|
||||||
|
curr_line[x * pixel_byte_count + 3],
|
||||||
|
];
|
||||||
|
} else if pixel_byte_count == 3 {
|
||||||
|
picture.get_pixel_mut(x as u32, y).0 = [
|
||||||
|
curr_line[x * pixel_byte_count],
|
||||||
|
curr_line[x * pixel_byte_count + 1],
|
||||||
|
curr_line[x * pixel_byte_count + 2],
|
||||||
|
0xFF,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prev_line.clone_from(&curr_line);
|
||||||
|
i += width as usize * pixel_byte_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,35 +1,7 @@
|
||||||
use std::{fs::DirBuilder, time::{Duration, Instant}};
|
|
||||||
|
|
||||||
use cz::dynamic::DynamicCz;
|
use cz::dynamic::DynamicCz;
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _ = DirBuilder::new().create("test");
|
let img = DynamicCz::open("../../test_files/x5a3bvy.cz1").unwrap();
|
||||||
|
|
||||||
let mut total_time = Duration::default();
|
img.save_as_cz("test.cz1").unwrap();
|
||||||
let mut num_images = 0;
|
|
||||||
for entry in WalkDir::new("../../test_files/") {
|
|
||||||
let entry = entry.unwrap();
|
|
||||||
if entry.path().is_dir() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let timer = Instant::now();
|
|
||||||
let img = match DynamicCz::open(entry.path()) {
|
|
||||||
Ok(img) => img,
|
|
||||||
Err(err) => {
|
|
||||||
println!("{}: {:?}", entry.path().file_name().unwrap().to_string_lossy(), err);
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let elapsed = timer.elapsed();
|
|
||||||
total_time += elapsed;
|
|
||||||
num_images += 1;
|
|
||||||
|
|
||||||
img.save_as_png(
|
|
||||||
&format!("test/{}.png", entry.path().file_name().unwrap().to_string_lossy())
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
dbg!(total_time / num_images);
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue