mirror of
https://github.com/G2-Games/lbee-utils.git
synced 2025-04-19 23:32:55 -05:00
Implemented CZ2 reading
This commit is contained in:
parent
a77e68b554
commit
b7ff624898
10 changed files with 325 additions and 20 deletions
|
@ -11,3 +11,4 @@ A encoder/decoder for CZ# image files used in
|
||||||
image = "0.25"
|
image = "0.25"
|
||||||
byteorder = "1.5.0"
|
byteorder = "1.5.0"
|
||||||
thiserror = "1.0.59"
|
thiserror = "1.0.59"
|
||||||
|
bitstream-io = "2.2.0"
|
||||||
|
|
65
cz/src/binio.rs
Normal file
65
cz/src/binio.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
pub struct BitIO {
|
||||||
|
data: Vec<u8>,
|
||||||
|
byte_offset: usize,
|
||||||
|
bit_offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BitIO {
|
||||||
|
pub fn new(data: Vec<u8>) -> Self {
|
||||||
|
Self {
|
||||||
|
data,
|
||||||
|
byte_offset: 0,
|
||||||
|
bit_offset: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn byte_offset(&self) -> usize {
|
||||||
|
self.byte_offset
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bit_offset(&self) -> usize {
|
||||||
|
self.byte_offset
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_vec(self) -> Vec<u8> {
|
||||||
|
self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_bit(&mut self, bit_len: usize) -> u64 {
|
||||||
|
//print!("{}: ", bit_len);
|
||||||
|
if bit_len > 8 * 8 {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
|
||||||
|
if bit_len % 8 == 0 && self.bit_offset == 0 {
|
||||||
|
return self.read(bit_len / 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = 0;
|
||||||
|
for i in 0..bit_len {
|
||||||
|
let bit_value = ((self.data[self.byte_offset] as usize >> self.bit_offset as usize) & 1) as u64;
|
||||||
|
self.bit_offset += 1;
|
||||||
|
|
||||||
|
if self.bit_offset == 8 {
|
||||||
|
self.byte_offset += 1;
|
||||||
|
self.bit_offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
result |= bit_value << i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&mut self, byte_len: usize) -> u64 {
|
||||||
|
if byte_len > 8 {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut padded_slice = [0u8; 8];
|
||||||
|
padded_slice.copy_from_slice(&self.data[self.byte_offset..self.byte_offset + byte_len]);
|
||||||
|
self.byte_offset += byte_len;
|
||||||
|
|
||||||
|
u64::from_le_bytes(padded_slice)
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,14 +11,10 @@ use thiserror::Error;
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum CzError {
|
pub enum CzError {
|
||||||
#[error("Version in header does not match expected version")]
|
#[error("Version in header does not match expected version")]
|
||||||
VersionMismatch,
|
VersionMismatch(u8, u8),
|
||||||
|
|
||||||
#[error(
|
#[error("Format of supplied file is not a CZ#")]
|
||||||
"Format of supplied file is incorrect; expected {} bytes, got {}",
|
InvalidFormat,
|
||||||
expected,
|
|
||||||
got
|
|
||||||
)]
|
|
||||||
InvalidFormat { expected: usize, got: usize },
|
|
||||||
|
|
||||||
#[error("Failed to read/write input/output")]
|
#[error("Failed to read/write input/output")]
|
||||||
IoError(#[from] io::Error),
|
IoError(#[from] io::Error),
|
||||||
|
@ -81,6 +77,10 @@ impl CzHeader for CommonHeader {
|
||||||
let mut magic = [0u8; 4];
|
let mut magic = [0u8; 4];
|
||||||
bytes.read_exact(&mut magic)?;
|
bytes.read_exact(&mut magic)?;
|
||||||
|
|
||||||
|
if magic[0..2] != [b'C', b'Z'] {
|
||||||
|
return Err(CzError::InvalidFormat)
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
version: magic[2] - b'0',
|
version: magic[2] - b'0',
|
||||||
length: bytes.read_u32::<LittleEndian>()?,
|
length: bytes.read_u32::<LittleEndian>()?,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use std::io::{Read, Seek};
|
use std::{collections::BTreeMap, io::{Cursor, Read, Seek, Write}};
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
|
use bitstream_io::{read::BitReader, BitRead};
|
||||||
|
|
||||||
use crate::common::CzError;
|
use crate::common::CzError;
|
||||||
|
use crate::binio::BitIO;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct ChunkInfo {
|
pub struct ChunkInfo {
|
||||||
|
@ -29,7 +32,7 @@ pub fn parse_chunk_info<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<
|
||||||
|
|
||||||
// Loop over the compressed bytes
|
// Loop over the compressed bytes
|
||||||
for _ in 0..parts_count {
|
for _ in 0..parts_count {
|
||||||
let compressed_size = bytes.read_u32::<LittleEndian>()? * 2;
|
let compressed_size = bytes.read_u32::<LittleEndian>()?;
|
||||||
total_size += compressed_size;
|
total_size += compressed_size;
|
||||||
|
|
||||||
let raw_size = bytes.read_u32::<LittleEndian>()?;
|
let raw_size = bytes.read_u32::<LittleEndian>()?;
|
||||||
|
@ -50,7 +53,7 @@ pub fn parse_chunk_info<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decompress an LZW compressed stream, like CZ1
|
/// Decompress an LZW compressed stream like CZ1
|
||||||
pub fn decompress<T: Seek + ReadBytesExt + Read>(
|
pub fn decompress<T: Seek + ReadBytesExt + Read>(
|
||||||
input: &mut T,
|
input: &mut T,
|
||||||
chunk_info: &CompressionInfo,
|
chunk_info: &CompressionInfo,
|
||||||
|
@ -58,7 +61,7 @@ pub fn decompress<T: Seek + ReadBytesExt + Read>(
|
||||||
let mut m_dst = 0;
|
let mut m_dst = 0;
|
||||||
let mut bitmap = vec![0; chunk_info.total_size_raw];
|
let mut bitmap = vec![0; chunk_info.total_size_raw];
|
||||||
for chunk in &chunk_info.chunks {
|
for chunk in &chunk_info.chunks {
|
||||||
let mut part = vec![0u8; chunk.size_compressed];
|
let mut part = vec![0u8; chunk.size_compressed * 2];
|
||||||
input.read_exact(&mut part)?;
|
input.read_exact(&mut part)?;
|
||||||
|
|
||||||
for j in (0..part.len()).step_by(2) {
|
for j in (0..part.len()).step_by(2) {
|
||||||
|
@ -76,6 +79,75 @@ pub fn decompress<T: Seek + ReadBytesExt + Read>(
|
||||||
Ok(bitmap)
|
Ok(bitmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decompress an LZW compressed stream like CZ2
|
||||||
|
pub fn decompress_2<T: Seek + ReadBytesExt + Read>(
|
||||||
|
input: &mut T,
|
||||||
|
chunk_info: &CompressionInfo,
|
||||||
|
) -> Result<Vec<u8>, CzError> {
|
||||||
|
let mut output_buf: Vec<u8> = vec![];
|
||||||
|
|
||||||
|
for block in &chunk_info.chunks {
|
||||||
|
let mut buffer = vec![0u8; block.size_compressed];
|
||||||
|
input.read_exact(&mut buffer).unwrap();
|
||||||
|
|
||||||
|
let raw_buf = decompress_lzw2(&buffer, block.size_raw);
|
||||||
|
|
||||||
|
output_buf.write_all(&raw_buf).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output_buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decompress_lzw2(input_data: &[u8], size: usize) -> Vec<u8> {
|
||||||
|
let mut data = input_data.to_vec();
|
||||||
|
data[0..2].copy_from_slice(&[0, 0]);
|
||||||
|
let mut dictionary = BTreeMap::new();
|
||||||
|
for i in 0..256 {
|
||||||
|
dictionary.insert(i as u64, vec![i as u8]);
|
||||||
|
}
|
||||||
|
let mut dictionary_count = dictionary.len() as u64;
|
||||||
|
let mut result = Vec::with_capacity(size);
|
||||||
|
|
||||||
|
let data_size = input_data.len();
|
||||||
|
data.extend_from_slice(&[0, 0]);
|
||||||
|
let mut bit_io = BitIO::new(data);
|
||||||
|
let mut w = dictionary.get(&0).unwrap().clone();
|
||||||
|
|
||||||
|
let mut element;
|
||||||
|
loop {
|
||||||
|
let flag = bit_io.read_bit(1);
|
||||||
|
if flag == 0 {
|
||||||
|
element = bit_io.read_bit(15);
|
||||||
|
} else {
|
||||||
|
element = bit_io.read_bit(18);
|
||||||
|
}
|
||||||
|
|
||||||
|
if bit_io.byte_offset() > data_size {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut entry;
|
||||||
|
if let Some(x) = dictionary.get(&element) {
|
||||||
|
// If the element was already in the dict, get it
|
||||||
|
entry = x.clone()
|
||||||
|
} else if element == dictionary_count {
|
||||||
|
entry = w.clone();
|
||||||
|
entry.push(w[0])
|
||||||
|
} else {
|
||||||
|
panic!("Bad compressed element: {}", element)
|
||||||
|
}
|
||||||
|
|
||||||
|
//println!("{}", element);
|
||||||
|
|
||||||
|
result.write(&entry).unwrap();
|
||||||
|
w.push(entry[0]);
|
||||||
|
dictionary.insert(dictionary_count, w.clone());
|
||||||
|
dictionary_count += 1;
|
||||||
|
w = entry.clone();
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
fn get_offset(input: &[u8], src: usize) -> usize {
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ impl CzHeader for Cz0Header {
|
||||||
let common = CommonHeader::new(bytes)?;
|
let common = CommonHeader::new(bytes)?;
|
||||||
|
|
||||||
if common.version() != 0 {
|
if common.version() != 0 {
|
||||||
return Err(CzError::VersionMismatch);
|
return Err(CzError::VersionMismatch(common.version(), 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut unknown_1 = [0u8; 5];
|
let mut unknown_1 = [0u8; 5];
|
||||||
|
|
|
@ -26,7 +26,7 @@ impl CzImage for Cz1Image {
|
||||||
bytes.seek(SeekFrom::Start(header.length() as u64))?;
|
bytes.seek(SeekFrom::Start(header.length() as u64))?;
|
||||||
|
|
||||||
if header.version() != 1 {
|
if header.version() != 1 {
|
||||||
return Err(CzError::VersionMismatch)
|
return Err(CzError::VersionMismatch(header.version(), 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// The color palette, gotten for 8 and 4 BPP images
|
// The color palette, gotten for 8 and 4 BPP images
|
||||||
|
|
158
cz/src/formats/cz2.rs
Normal file
158
cz/src/formats/cz2.rs
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||||
|
use image::{ImageFormat, Rgba};
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{BufWriter, Read, Seek, SeekFrom, Write},
|
||||||
|
path::PathBuf
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::compression::{decompress, decompress_2, parse_chunk_info};
|
||||||
|
use crate::common::{apply_palette, parse_colormap, CommonHeader, CzError, CzHeader, CzImage};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Cz2Header {
|
||||||
|
common: CommonHeader,
|
||||||
|
unknown_1: u8,
|
||||||
|
unknown_2: u8,
|
||||||
|
unknown_3: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CzHeader for Cz2Header {
|
||||||
|
fn new<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError>
|
||||||
|
where
|
||||||
|
Self: Sized
|
||||||
|
{
|
||||||
|
let common = CommonHeader::new(bytes)?;
|
||||||
|
|
||||||
|
if common.version() != 2 {
|
||||||
|
return Err(CzError::VersionMismatch(common.version(), 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
common,
|
||||||
|
unknown_1: bytes.read_u8()?,
|
||||||
|
unknown_2: bytes.read_u8()?,
|
||||||
|
unknown_3: bytes.read_u8()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
|
||||||
|
let mut buf = vec![];
|
||||||
|
|
||||||
|
buf.write_all(&self.common.to_bytes()?)?;
|
||||||
|
buf.write_u8(self.unknown_1)?;
|
||||||
|
buf.write_u8(self.unknown_2)?;
|
||||||
|
buf.write_u8(self.unknown_3)?;
|
||||||
|
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn version(&self) -> u8 {
|
||||||
|
self.common.version()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn length(&self) -> usize {
|
||||||
|
self.common.length()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn width(&self) -> u16 {
|
||||||
|
self.common.width()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> u16 {
|
||||||
|
self.common.height()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn depth(&self) -> u16 {
|
||||||
|
self.common.depth()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_block(&self) -> u8 {
|
||||||
|
self.common.color_block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Cz2Image {
|
||||||
|
header: Cz2Header,
|
||||||
|
bitmap: Vec<u8>,
|
||||||
|
palette: Vec<Rgba<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CzImage for Cz2Image {
|
||||||
|
type Header = Cz2Header;
|
||||||
|
|
||||||
|
fn decode<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError> {
|
||||||
|
let header = Cz2Header::new(bytes).unwrap();
|
||||||
|
bytes.seek(SeekFrom::Start(header.length() as u64))?;
|
||||||
|
|
||||||
|
if header.version() != 2 {
|
||||||
|
return Err(CzError::VersionMismatch(header.version(), 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg!(header);
|
||||||
|
|
||||||
|
// The color palette, gotten for 8 and 4 BPP images
|
||||||
|
let mut palette = None;
|
||||||
|
if header.depth() == 8 || header.depth() == 4 {
|
||||||
|
palette = Some(parse_colormap(bytes, 1 << header.depth())?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunk_info = parse_chunk_info(bytes)?;
|
||||||
|
bytes.seek(SeekFrom::Start(chunk_info.length as u64))?;
|
||||||
|
|
||||||
|
let mut bitmap = decompress_2(bytes, &chunk_info).unwrap();
|
||||||
|
|
||||||
|
// Apply the palette if it exists
|
||||||
|
if let Some(pal) = &palette {
|
||||||
|
bitmap = apply_palette(&mut bitmap, pal);
|
||||||
|
}
|
||||||
|
|
||||||
|
let image = Self {
|
||||||
|
header,
|
||||||
|
bitmap,
|
||||||
|
palette: palette.unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_as_png(&self, name: &str) -> Result<(), image::error::ImageError> {
|
||||||
|
let img = image::RgbaImage::from_raw(
|
||||||
|
self.header.width() as u32 - 1,
|
||||||
|
self.header.height() as u32,
|
||||||
|
self.bitmap.clone(),
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
img.save(name)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header(&self) -> &Self::Header {
|
||||||
|
&self.header
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_header(&mut self, header: Self::Header) {
|
||||||
|
self.header = header
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_bitmap(self) -> Vec<u8> {
|
||||||
|
self.bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_as_cz<T: Into<PathBuf>>(&self, path: T) -> Result<(), CzError> {
|
||||||
|
let mut output_file = BufWriter::new(File::create(path.into())?);
|
||||||
|
|
||||||
|
output_file.write_all(&self.header.to_bytes()?)?;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
output_file.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_bitmap(&mut self, bitmap: &[u8], header: &Self::Header) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,7 @@
|
||||||
use std::{io::{self, Cursor, Read, Seek, SeekFrom}, path::PathBuf};
|
use std::{
|
||||||
|
io::{self, Read, Seek, SeekFrom},
|
||||||
|
path::PathBuf
|
||||||
|
};
|
||||||
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
|
|
||||||
|
@ -37,7 +40,7 @@ impl CzHeader for Cz3Header {
|
||||||
let common = CommonHeader::new(bytes)?;
|
let common = CommonHeader::new(bytes)?;
|
||||||
|
|
||||||
if common.version() != 3 {
|
if common.version() != 3 {
|
||||||
return Err(CzError::VersionMismatch);
|
return Err(CzError::VersionMismatch(common.version(), 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut unknown_1 = [0u8; 5];
|
let mut unknown_1 = [0u8; 5];
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod compression;
|
mod binio;
|
||||||
|
mod compression;
|
||||||
pub mod formats {
|
pub mod formats {
|
||||||
pub mod cz0;
|
pub mod cz0;
|
||||||
pub mod cz1;
|
pub mod cz1;
|
||||||
|
pub mod cz2;
|
||||||
pub mod cz3;
|
pub mod cz3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +13,8 @@ pub use formats::cz0::Cz0Image;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use formats::cz1::Cz1Image;
|
pub use formats::cz1::Cz1Image;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
|
pub use formats::cz2::Cz2Image;
|
||||||
|
#[doc(inline)]
|
||||||
pub use formats::cz3::Cz3Image;
|
pub use formats::cz3::Cz3Image;
|
||||||
|
|
||||||
/// Traits for CZ# images
|
/// Traits for CZ# images
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use cz::{Cz2Image, CzImage};
|
||||||
use cz::{Cz3Image, CzImage};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut input = fs::File::open("../../test_files/Old_TestFiles/129.CZ3").unwrap();
|
let mut input = fs::File::open("../../test_files/font_files/24.cz2")
|
||||||
let img_file = Cz3Image::decode(&mut input).unwrap();
|
.expect("Failed to open file");
|
||||||
|
|
||||||
|
let img_file = Cz2Image::decode(&mut input)
|
||||||
|
.expect("Failed to decode image");
|
||||||
|
|
||||||
img_file.save_as_png("test1.png").unwrap();
|
img_file.save_as_png("test1.png").unwrap();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue