From 8945b8006515f156af5989b19d719ee6c47c7091 Mon Sep 17 00:00:00 2001 From: G2 Date: Mon, 6 May 2024 15:40:00 -0500 Subject: [PATCH] Added DynamicCz, added better test program, fixed error messages --- cz/Cargo.toml | 3 -- cz/src/common.rs | 37 +++++++++------ cz/src/compression.rs | 5 +-- cz/src/dynamic.rs | 102 ++++++++++++++++++++++++++++++++++++++++++ cz/src/formats/cz0.rs | 28 +++++++----- cz/src/formats/cz1.rs | 24 ++++------ cz/src/formats/cz2.rs | 38 ++++++---------- cz/src/formats/cz3.rs | 24 +++++----- cz/src/formats/cz4.rs | 33 ++++++-------- cz/src/lib.rs | 4 +- utils/Cargo.toml | 2 + utils/src/main.rs | 44 +++++++++++++++--- 12 files changed, 230 insertions(+), 114 deletions(-) create mode 100644 cz/src/dynamic.rs diff --git a/cz/Cargo.toml b/cz/Cargo.toml index 54259ab..94f69da 100644 --- a/cz/Cargo.toml +++ b/cz/Cargo.toml @@ -8,9 +8,6 @@ A encoder/decoder for CZ# image files used in """ [dependencies] -image = "0.25" byteorder = "1.5.0" thiserror = "1.0.59" -bitstream-io = "2.2.0" png = "0.17.13" -rayon = "1.10.0" diff --git a/cz/src/common.rs b/cz/src/common.rs index cef2fb1..7b0b092 100644 --- a/cz/src/common.rs +++ b/cz/src/common.rs @@ -6,19 +6,24 @@ use std::{ }; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use image::Rgba; use thiserror::Error; #[derive(Error, Debug)] pub enum CzError { - #[error("Version in header does not match expected version")] + #[error("Expected CZ{}, got CZ{}", 0, 1)] VersionMismatch(u8, u8), - #[error("Format of supplied file is not a CZ#")] - InvalidFormat, + #[error("File data is incorrect, it might be corrupt")] + Corrupt, + + #[error("File is not a CZ image")] + NotCzFile, #[error("Failed to read/write input/output")] IoError(#[from] io::Error), + + #[error("Problem while decoding file")] + DecodeError, } pub trait CzHeader { @@ -26,6 +31,9 @@ pub trait CzHeader { where Self: Sized; + /// The [CommonHeader] header from the image + fn common(&self) -> &CommonHeader; + /// Turn the header into bytes equivalent to the original header from the file fn to_bytes(&self) -> Result, io::Error>; @@ -79,7 +87,7 @@ impl CzHeader for CommonHeader { bytes.read_exact(&mut magic)?; if magic[0..2] != [b'C', b'Z'] { - return Err(CzError::InvalidFormat); + return Err(CzError::NotCzFile); } Ok(Self { @@ -92,6 +100,10 @@ impl CzHeader for CommonHeader { }) } + fn common(&self) -> &CommonHeader { + &self + } + fn version(&self) -> u8 { self.version } @@ -139,9 +151,6 @@ pub trait CzImage { where Self: Sized; - /// Save the image as a PNG - fn save_as_png(&self, name: &str) -> Result<(), image::error::ImageError>; - /// Save the image as its corresponding CZ# type fn save_as_cz>(&self, path: T) -> Result<(), CzError>; @@ -149,7 +158,9 @@ pub trait CzImage { fn header(&self) -> &Self::Header; /// Set the header with its metadata - fn set_header(&mut self, header: Self::Header); + fn set_header(&mut self, header: &Self::Header); + + fn bitmap(&self) -> &Vec; /// Get the raw underlying bitmap for an image fn into_bitmap(self) -> Vec; @@ -161,23 +172,23 @@ pub trait CzImage { pub fn parse_colormap( input: &mut T, num_colors: usize, -) -> Result>, CzError> { +) -> Result, CzError> { let mut colormap = Vec::with_capacity(num_colors); let mut rgba_buf = [0u8; 4]; for _ in 0..num_colors { input.read_exact(&mut rgba_buf)?; - colormap.push(Rgba(rgba_buf)); + colormap.push(rgba_buf); } Ok(colormap) } -pub fn apply_palette(input: &mut Vec, palette: &[Rgba]) -> Vec { +pub fn apply_palette(input: &mut Vec, palette: &[[u8; 4]]) -> Vec { let mut output_map = Vec::new(); for byte in input.iter() { - let color = palette[*byte as usize].0; + let color = palette[*byte as usize]; output_map.extend_from_slice(&color); } diff --git a/cz/src/compression.rs b/cz/src/compression.rs index ac0b328..a4c9889 100644 --- a/cz/src/compression.rs +++ b/cz/src/compression.rs @@ -1,7 +1,4 @@ use byteorder::{LittleEndian, ReadBytesExt}; -use image::{ - buffer, ColorType, DynamicImage, GenericImage, GenericImageView, RgbImage, Rgba, RgbaImage, -}; use std::{ collections::BTreeMap, io::{Read, Seek, Write}, @@ -218,7 +215,7 @@ pub fn line_diff(header: &T, data: &[u8]) -> Vec { if y % block_height as u32 != 0 { for x in 0..line_byte_count { - curr_line[x] += prev_line[x] + curr_line[x] = u8::wrapping_add(curr_line[x], prev_line[x]) } } diff --git a/cz/src/dynamic.rs b/cz/src/dynamic.rs new file mode 100644 index 0000000..81e5deb --- /dev/null +++ b/cz/src/dynamic.rs @@ -0,0 +1,102 @@ +use std::io::{Read, Seek}; +use byteorder::ReadBytesExt; + +use crate::{ + common::{CommonHeader, CzError, CzHeader}, + Cz0Image, + Cz1Image, + Cz2Image, + Cz3Image, + Cz4Image, + CzImage +}; + +pub enum DynamicCz { + CZ0(Cz0Image), + CZ1(Cz1Image), + CZ2(Cz2Image), + CZ3(Cz3Image), + CZ4(Cz4Image), +} + +impl DynamicCz { + pub fn save_as_png(&self, name: &str) -> Result<(), png::EncodingError> { + let file = std::fs::File::create(name).unwrap(); + let ref mut w = std::io::BufWriter::new(file); + + let mut encoder = png::Encoder::new( + w, + self.header().width() as u32, + self.header().height() as u32, + ); + encoder.set_color(png::ColorType::Rgba); + encoder.set_depth(png::BitDepth::Eight); + let mut writer = encoder.write_header()?; + + writer.write_image_data(&self.bitmap())?; // Save + + Ok(()) + } +} + +impl CzImage for DynamicCz { + type Header = CommonHeader; + + fn decode(input: &mut T) + -> Result + { + let common_header = CommonHeader::new(input)?; + input.seek(std::io::SeekFrom::Start(0))?; + + Ok(match common_header.version() { + 0 => DynamicCz::CZ0(Cz0Image::decode(input)?), + 1 => DynamicCz::CZ1(Cz1Image::decode(input)?), + 2 => DynamicCz::CZ2(Cz2Image::decode(input)?), + 3 => DynamicCz::CZ3(Cz3Image::decode(input)?), + 4 => DynamicCz::CZ4(Cz4Image::decode(input)?), + _ => return Err(CzError::NotCzFile), + }) + } + + fn save_as_cz>(&self, path: T) -> Result<(), CzError> { + todo!() + } + + fn header(&self) -> &Self::Header { + match self { + DynamicCz::CZ0(img) => &img.header().common(), + DynamicCz::CZ1(img) => &img.header().common(), + DynamicCz::CZ2(img) => &img.header().common(), + DynamicCz::CZ3(img) => &img.header().common(), + DynamicCz::CZ4(img) => &img.header().common(), + } + } + + fn set_header(&mut self, header: &Self::Header) { + todo!() + } + + fn bitmap(&self) -> &Vec { + match self { + DynamicCz::CZ0(img) => img.bitmap(), + DynamicCz::CZ1(img) => img.bitmap(), + DynamicCz::CZ2(img) => img.bitmap(), + DynamicCz::CZ3(img) => img.bitmap(), + DynamicCz::CZ4(img) => img.bitmap(), + } + } + + fn into_bitmap(self) -> Vec { + match self { + DynamicCz::CZ0(img) => img.into_bitmap(), + DynamicCz::CZ1(img) => img.into_bitmap(), + DynamicCz::CZ2(img) => img.into_bitmap(), + DynamicCz::CZ3(img) => img.into_bitmap(), + DynamicCz::CZ4(img) => img.into_bitmap(), + } + } + + fn set_bitmap(&mut self, bitmap: &[u8], header: &Self::Header) { + todo!() + } +} diff --git a/cz/src/formats/cz0.rs b/cz/src/formats/cz0.rs index c7f3e90..4039617 100644 --- a/cz/src/formats/cz0.rs +++ b/cz/src/formats/cz0.rs @@ -94,6 +94,10 @@ impl CzHeader for Cz0Header { }) } + fn common(&self) -> &CommonHeader { + &self.common + } + fn version(&self) -> u8 { self.common.version() } @@ -150,17 +154,13 @@ impl CzImage for Cz0Image { let mut bitmap = vec![]; bytes.read_to_end(&mut bitmap)?; - Ok(Self { header, bitmap }) - } + let bpp = (header.depth() >> 3) as usize; - fn save_as_png(&self, name: &str) -> Result<(), image::error::ImageError> { - image::save_buffer( - name, - &self.bitmap, - self.header.width() as u32, - self.header.height() as u32, - image::ExtendedColorType::Rgba8, - ) + if bitmap.len() != (header.width() as usize * header.height() as usize) * bpp { + return Err(CzError::Corrupt) + } + + Ok(Self { header, bitmap }) } fn save_as_cz>(&self, path: T) -> Result<(), CzError> { @@ -177,8 +177,12 @@ impl CzImage for Cz0Image { &self.header } - fn set_header(&mut self, header: Self::Header) { - self.header = header + fn set_header(&mut self, header: &Self::Header) { + self.header = *header + } + + fn bitmap(&self) -> &Vec { + &self.bitmap } fn into_bitmap(self) -> Vec { diff --git a/cz/src/formats/cz1.rs b/cz/src/formats/cz1.rs index 9e0a3e2..e8adb76 100644 --- a/cz/src/formats/cz1.rs +++ b/cz/src/formats/cz1.rs @@ -1,5 +1,4 @@ use byteorder::ReadBytesExt; -use image::{ImageFormat, Rgba}; use std::{ fs::File, io::{BufWriter, Read, Seek, SeekFrom, Write}, @@ -14,7 +13,7 @@ pub struct Cz1Image { header: CommonHeader, raw_bitmap: Option>, bitmap: Vec, - palette: Vec>, + palette: Vec<[u8; 4]>, } impl CzImage for Cz1Image { @@ -26,7 +25,7 @@ impl CzImage for Cz1Image { bytes.seek(SeekFrom::Start(header.length() as u64))?; if header.version() != 1 { - return Err(CzError::VersionMismatch(header.version(), 1)); + return Err(CzError::VersionMismatch(1, header.version())); } // The color palette, gotten for 8 and 4 BPP images @@ -59,23 +58,16 @@ impl CzImage for Cz1Image { Ok(image) } - fn save_as_png(&self, name: &str) -> Result<(), image::error::ImageError> { - image::save_buffer_with_format( - name, - &self.bitmap, - self.header.width() as u32, - self.header.height() as u32, - image::ExtendedColorType::Rgba8, - ImageFormat::Png, - ) - } - fn header(&self) -> &Self::Header { &self.header } - fn set_header(&mut self, header: Self::Header) { - self.header = header + fn set_header(&mut self, header:& Self::Header) { + self.header = *header + } + + fn bitmap(&self) -> &Vec { + &self.bitmap } fn into_bitmap(self) -> Vec { diff --git a/cz/src/formats/cz2.rs b/cz/src/formats/cz2.rs index 119880b..d311c08 100644 --- a/cz/src/formats/cz2.rs +++ b/cz/src/formats/cz2.rs @@ -1,5 +1,4 @@ use byteorder::{ReadBytesExt, WriteBytesExt}; -use image::{ImageFormat, Rgba}; use std::{ fs::File, io::{BufWriter, Read, Seek, SeekFrom, Write}, @@ -7,7 +6,7 @@ use std::{ }; use crate::common::{apply_palette, parse_colormap, CommonHeader, CzError, CzHeader, CzImage}; -use crate::compression::{decompress, decompress_2, parse_chunk_info}; +use crate::compression::{decompress_2, parse_chunk_info}; #[derive(Debug, Clone, Copy)] pub struct Cz2Header { @@ -25,7 +24,7 @@ impl CzHeader for Cz2Header { let common = CommonHeader::new(bytes)?; if common.version() != 2 { - return Err(CzError::VersionMismatch(common.version(), 2)); + return Err(CzError::VersionMismatch(2, common.version())); } Ok(Self { @@ -36,6 +35,10 @@ impl CzHeader for Cz2Header { }) } + fn common(&self) -> &CommonHeader { + &self.common + } + fn to_bytes(&self) -> Result, std::io::Error> { let mut buf = vec![]; @@ -76,7 +79,7 @@ impl CzHeader for Cz2Header { pub struct Cz2Image { header: Cz2Header, bitmap: Vec, - palette: Vec>, + palette: Vec<[u8; 4]>, } impl CzImage for Cz2Image { @@ -86,12 +89,6 @@ impl CzImage for Cz2Image { 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 { @@ -117,25 +114,16 @@ impl CzImage for Cz2Image { Ok(image) } - 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 set_header(&mut self, header: &Self::Header) { + self.header = *header + } + + fn bitmap(&self) -> &Vec { + &self.bitmap } fn into_bitmap(self) -> Vec { diff --git a/cz/src/formats/cz3.rs b/cz/src/formats/cz3.rs index a31b53f..a7a2f28 100644 --- a/cz/src/formats/cz3.rs +++ b/cz/src/formats/cz3.rs @@ -40,7 +40,7 @@ impl CzHeader for Cz3Header { let common = CommonHeader::new(bytes)?; if common.version() != 3 { - return Err(CzError::VersionMismatch(common.version(), 3)); + return Err(CzError::VersionMismatch(3, common.version())); } let mut unknown_1 = [0u8; 5]; @@ -73,6 +73,10 @@ impl CzHeader for Cz3Header { }) } + fn common(&self) -> &CommonHeader { + &self.common + } + fn version(&self) -> u8 { self.common.version() } @@ -124,22 +128,16 @@ impl CzImage for Cz3Image { Ok(Self { header, bitmap }) } - fn save_as_png(&self, name: &str) -> Result<(), image::error::ImageError> { - image::save_buffer( - name, - &self.bitmap, - self.header.width() as u32, - self.header.height() as u32, - image::ExtendedColorType::Rgba8, - ) - } - fn header(&self) -> &Self::Header { &self.header } - fn set_header(&mut self, header: Self::Header) { - self.header = header + fn set_header(&mut self, header: &Self::Header) { + self.header = *header + } + + fn bitmap(&self) -> &Vec { + &self.bitmap } fn into_bitmap(self) -> Vec { diff --git a/cz/src/formats/cz4.rs b/cz/src/formats/cz4.rs index 6e5193d..fe0863d 100644 --- a/cz/src/formats/cz4.rs +++ b/cz/src/formats/cz4.rs @@ -2,12 +2,10 @@ use std::{ io::{self, Read, Seek, SeekFrom}, path::PathBuf, }; - -use byteorder::{LittleEndian, ReadBytesExt}; -use image::DynamicImage; +use byteorder::ReadBytesExt; use crate::common::{CommonHeader, CzError, CzHeader, CzImage}; -use crate::compression::{decompress, line_diff, line_diff_cz4, parse_chunk_info}; +use crate::compression::{decompress, line_diff_cz4, parse_chunk_info}; #[derive(Debug, Clone, Copy)] pub struct Cz4Header { @@ -23,12 +21,16 @@ impl CzHeader for Cz4Header { let common = CommonHeader::new(bytes)?; if common.version() != 4 { - return Err(CzError::VersionMismatch(common.version(), 3)); + return Err(CzError::VersionMismatch(4, common.version())); } Ok(Self { common }) } + fn common(&self) -> &CommonHeader { + &self.common + } + fn version(&self) -> u8 { self.common.version() } @@ -81,25 +83,16 @@ impl CzImage for Cz4Image { Ok(Self { header, bitmap }) } - 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 set_header(&mut self, header: &Self::Header) { + self.header = *header + } + + fn bitmap(&self) -> &Vec { + &self.bitmap } fn into_bitmap(self) -> Vec { diff --git a/cz/src/lib.rs b/cz/src/lib.rs index f6edaa3..05dd8c2 100644 --- a/cz/src/lib.rs +++ b/cz/src/lib.rs @@ -1,6 +1,8 @@ mod binio; -pub mod common; mod compression; + +pub mod dynamic; +pub mod common; pub mod formats { pub mod cz0; pub mod cz1; diff --git a/utils/Cargo.toml b/utils/Cargo.toml index e0dddd0..c4f9f29 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -7,3 +7,5 @@ edition = "2021" [dependencies] cz = { path = "../cz" } +image = "0.25.1" +walkdir = "2.5.0" diff --git a/utils/src/main.rs b/utils/src/main.rs index e0f251f..231ba6b 100644 --- a/utils/src/main.rs +++ b/utils/src/main.rs @@ -1,13 +1,43 @@ -use cz::{Cz3Image, CzImage}; +use cz::{dynamic::DynamicCz, CzImage}; use std::fs; +use walkdir::WalkDir; fn main() { - let mut input = fs::File::open("../../test_files/GOOD_00009.cz3") - .expect("Failed to open file"); + if let Err(err) = fs::DirBuilder::new().create("test/") { + println!("{}", err); + } - let timer = std::time::Instant::now(); - let img_file = Cz3Image::decode(&mut input).expect("Failed to decode image"); - println!("{:?}", timer.elapsed()); + let mut success = 0; + let mut failure = 0; + for entry in WalkDir::new("../../test_files") { + let entry = entry.unwrap(); - img_file.save_as_png("test1.png").unwrap(); + if entry.path().is_dir() { + continue; + } + + let mut input = match fs::File::open(entry.path()) { + Ok(file) => file, + Err(_) => continue, + }; + + let img_file = match DynamicCz::decode(&mut input) { + Ok(file) => file, + Err(err) => { + println!( + "{}: {}", + entry.path().file_name().unwrap().to_string_lossy(), + err, + ); + failure += 1; + continue; + }, + }; + + success += 1; + + img_file.save_as_png( + &format!("test/z-{}.png", entry.path().file_stem().unwrap().to_string_lossy()) + ).unwrap(); + } }