From a1fbb3368a418e36101da86218ec3a378fcdf3c2 Mon Sep 17 00:00:00 2001 From: G2-Games Date: Sun, 5 May 2024 23:04:51 -0500 Subject: [PATCH] Added CZ4 read implementation --- cz/src/compression.rs | 98 +++++++++++++++++++++++++++++++-- cz/src/formats/cz3.rs | 15 ++---- cz/src/formats/cz4.rs | 123 ++++++++++++++++++++++++++++++++++++++++++ cz/src/lib.rs | 3 ++ utils/src/main.rs | 8 +-- 5 files changed, 229 insertions(+), 18 deletions(-) create mode 100644 cz/src/formats/cz4.rs diff --git a/cz/src/compression.rs b/cz/src/compression.rs index 12ba576..08d26f8 100644 --- a/cz/src/compression.rs +++ b/cz/src/compression.rs @@ -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( } } + 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(header_info: &T, data: &[u8]) -> Vec { + 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; + let mut prev_line: Vec = 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; + } +} diff --git a/cz/src/formats/cz3.rs b/cz/src/formats/cz3.rs index b38cf6e..e2cf66d 100644 --- a/cz/src/formats/cz3.rs +++ b/cz/src/formats/cz3.rs @@ -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 }) } diff --git a/cz/src/formats/cz4.rs b/cz/src/formats/cz4.rs new file mode 100644 index 0000000..562d9c9 --- /dev/null +++ b/cz/src/formats/cz4.rs @@ -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(bytes: &mut T) -> Result + 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, io::Error> { + todo!() + } +} + +#[derive(Debug, Clone)] +pub struct Cz4Image { + header: Cz4Header, + bitmap: Vec, +} + +impl CzImage for Cz4Image { + type Header = Cz4Header; + + fn decode(bytes: &mut T) -> Result { + 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 { + self.bitmap + } + + fn save_as_cz>(&self, path: T) -> Result<(), CzError> { + todo!() + } + + fn set_bitmap(&mut self, bitmap: &[u8], header: &Self::Header) { + todo!() + } +} diff --git a/cz/src/lib.rs b/cz/src/lib.rs index ea5d738..6ae7428 100644 --- a/cz/src/lib.rs +++ b/cz/src/lib.rs @@ -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)] diff --git a/utils/src/main.rs b/utils/src/main.rs index d9d64ec..9dbb296 100644 --- a/utils/src/main.rs +++ b/utils/src/main.rs @@ -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(); }