Added CZ4 read implementation

This commit is contained in:
G2-Games 2024-05-05 23:04:51 -05:00
parent cf55210c4c
commit a1fbb3368a
5 changed files with 229 additions and 18 deletions

View file

@ -1,8 +1,8 @@
use std::{collections::BTreeMap, io::{Cursor, Read, Seek, Write}};
use std::{collections::BTreeMap, io::{Read, Seek, Write}};
use byteorder::{LittleEndian, ReadBytesExt};
use bitstream_io::{read::BitReader, BitRead};
use image::{buffer, ColorType, DynamicImage, GenericImage, GenericImageView, RgbImage, Rgba, RgbaImage};
use crate::common::CzError;
use crate::common::{CzError, CzHeader};
use crate::binio::BitIO;
#[derive(Debug, Clone, Copy)]
@ -76,6 +76,8 @@ pub fn decompress<T: Seek + ReadBytesExt + Read>(
}
}
bitmap.truncate(chunk_info.total_size_raw);
Ok(bitmap)
}
@ -189,3 +191,93 @@ fn copy_one(input: &[u8], src: usize) -> u8 {
copy_one(input, get_offset(input, src))
}
}
pub fn line_diff<T: CzHeader>(header_info: &T, data: &[u8]) -> Vec<u8> {
let width = header_info.width() as u32;
let height = header_info.height() as u32;
let mut pic = image::RgbaImage::new(width, height);
let block_height = (f32::ceil(height as f32 / 4 as f32) as u16) as usize;
let pixel_byte_count = header_info.depth() >> 3;
let line_byte_count = (width * pixel_byte_count as u32) as usize;
let mut curr_line: Vec<u8>;
let mut prev_line: Vec<u8> = Vec::with_capacity(line_byte_count);
let mut i = 0;
for y in 0..height {
curr_line = data[i..i+line_byte_count].to_vec();
dbg!(curr_line.len());
if y % block_height as u32 != 0 {
for x in 0..line_byte_count {
curr_line[x] += prev_line[x]
}
}
prev_line = curr_line.clone();
if header_info.version() == 4 {
for x in 0..width {
pic.get_pixel_mut(x as u32, y).0[3] = curr_line[x as usize];
}
} else if pixel_byte_count == 4 {
let mut raw = pic.into_raw();
raw[i..i+line_byte_count].copy_from_slice(&curr_line);
pic = RgbaImage::from_raw(width, height, raw).unwrap();
} else if pixel_byte_count == 3 {
for x in 0..line_byte_count {
pic.get_pixel_mut((x/3) as u32, y).0 = [curr_line[x], curr_line[x + 1], curr_line[x + 2], 0xFF];
}
}
i += line_byte_count;
}
pic.into_vec()
}
pub fn line_diff_cz4(picture: &mut RgbaImage, color_block: u8, pixel_byte_count: usize, data: &[u8]) {
let width = picture.width() as u32;
let height = picture.height() as u32;
let block_height = (f32::ceil(height as f32 / color_block as f32) as u16) as u32;
let mut curr_line = vec![0u8; width as usize * pixel_byte_count];
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] += 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) = Rgba([
curr_line[x * pixel_byte_count + 0],
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) = Rgba([
curr_line[x * pixel_byte_count + 0],
curr_line[x * pixel_byte_count + 1],
curr_line[x * pixel_byte_count + 2],
0xFF
]);
}
}
prev_line = curr_line.clone();
i += width as usize * pixel_byte_count;
}
}

View file

@ -5,7 +5,7 @@ use std::{
use byteorder::{LittleEndian, ReadBytesExt};
use crate::compression::{decompress, parse_chunk_info};
use crate::compression::{decompress, line_diff, parse_chunk_info};
use crate::common::{CommonHeader, CzError, CzHeader, CzImage};
#[derive(Debug, Clone, Copy)]
@ -117,18 +117,9 @@ impl CzImage for Cz3Image {
let block_info = parse_chunk_info(bytes)?;
let mut bitmap = decompress(bytes, &block_info)?;
let bitmap = decompress(bytes, &block_info)?;
let stride = (header.width() * (header.depth() / 8)) as usize;
let third = ((header.height() + 2) / 3) as usize;
for y in 0..header.height() as usize {
let dst = y * stride;
if y % third != 0 {
for x in 0..stride {
bitmap[dst + x] += bitmap[dst + x - stride];
}
}
}
let bitmap = line_diff(&header, &bitmap);
Ok(Self { header, bitmap })
}

123
cz/src/formats/cz4.rs Normal file
View file

@ -0,0 +1,123 @@
use std::{
io::{self, Read, Seek, SeekFrom},
path::PathBuf
};
use byteorder::{LittleEndian, ReadBytesExt};
use image::DynamicImage;
use crate::compression::{decompress, line_diff, line_diff_cz4, parse_chunk_info};
use crate::common::{CommonHeader, CzError, CzHeader, CzImage};
#[derive(Debug, Clone, Copy)]
pub struct Cz4Header {
/// Common CZ# header
common: CommonHeader,
}
impl CzHeader for Cz4Header {
fn new<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError>
where
Self: Sized,
{
let common = CommonHeader::new(bytes)?;
if common.version() != 4 {
return Err(CzError::VersionMismatch(common.version(), 3));
}
Ok(Self {
common,
})
}
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()
}
fn to_bytes(&self) -> Result<Vec<u8>, io::Error> {
todo!()
}
}
#[derive(Debug, Clone)]
pub struct Cz4Image {
header: Cz4Header,
bitmap: Vec<u8>,
}
impl CzImage for Cz4Image {
type Header = Cz4Header;
fn decode<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError> {
let header = Cz4Header::new(bytes)?;
bytes.seek(SeekFrom::Start(header.length() as u64))?;
let block_info = parse_chunk_info(bytes)?;
bytes.seek(SeekFrom::Start(block_info.length as u64))?;
let bitmap = decompress(bytes, &block_info)?;
let mut picture = image::RgbaImage::new(header.width() as u32, header.height() as u32);
let pixel_byte_count = 3;
line_diff_cz4(&mut picture, 3, pixel_byte_count, &bitmap);
Ok(Self {
header,
bitmap: picture.into_vec()
})
}
fn save_as_png(&self, name: &str) -> Result<(), image::error::ImageError> {
let img = image::RgbaImage::from_raw(
self.header.width() as u32,
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> {
todo!()
}
fn set_bitmap(&mut self, bitmap: &[u8], header: &Self::Header) {
todo!()
}
}

View file

@ -6,6 +6,7 @@ pub mod formats {
pub mod cz1;
pub mod cz2;
pub mod cz3;
pub mod cz4;
}
#[doc(inline)]
@ -16,6 +17,8 @@ pub use formats::cz1::Cz1Image;
pub use formats::cz2::Cz2Image;
#[doc(inline)]
pub use formats::cz3::Cz3Image;
#[doc(inline)]
pub use formats::cz4::Cz4Image;
/// Traits for CZ# images
#[doc(inline)]

View file

@ -1,12 +1,14 @@
use std::fs;
use cz::{Cz2Image, CzImage};
use cz::{Cz4Image, CzImage};
fn main() {
let mut input = fs::File::open("../../test_files/font_files/24.cz2")
let mut input = fs::File::open("../../test_files/BAD_BG_011_10.cz4")
.expect("Failed to open file");
let img_file = Cz2Image::decode(&mut input)
let timer = std::time::Instant::now();
let img_file = Cz4Image::decode(&mut input)
.expect("Failed to decode image");
println!("{:?}", timer.elapsed());
img_file.save_as_png("test1.png").unwrap();
}