diff --git a/.gitignore b/.gitignore index 550ea27..269c4cd 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ Cargo.lock *.cz* *.CZ* *.png + +test_files/* diff --git a/src/cz_common.rs b/src/cz_common.rs index 9ba0a3b..0948bb6 100644 --- a/src/cz_common.rs +++ b/src/cz_common.rs @@ -5,7 +5,10 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum CzError { #[error("Version in header does not match expected version")] - VersionMismatch + VersionMismatch, + + #[error("Format of supplied file is incorrect")] + InvalidFormat, } pub trait CzHeader { @@ -19,7 +22,7 @@ pub trait CzHeader { fn height(&self) -> u16; - fn depth(&self) -> u8; + fn depth(&self) -> u16; } /// The common first part of a header of a CZ# file @@ -29,7 +32,7 @@ pub(crate) struct CommonHeader { pub version: u8, /// Length of the header in bytes - pub length: u8, + pub length: u32, /// Width of the image in pixels pub width: u16, @@ -38,17 +41,17 @@ pub(crate) struct CommonHeader { pub height: u16, /// Bit depth in Bits Per Pixel (BPP) - pub depth: u8, + pub depth: u16, } impl CommonHeader { pub fn new(bytes: &[u8]) -> Self { Self { version: bytes[2] - b'0', - length: bytes[4], + length: u32::from_le_bytes(bytes[4..8].try_into().unwrap()), width: u16::from_le_bytes(bytes[8..10].try_into().unwrap()), height: u16::from_le_bytes(bytes[10..12].try_into().unwrap()), - depth: bytes[12], + depth: u16::from_le_bytes(bytes[12..14].try_into().unwrap()), } } } @@ -68,3 +71,15 @@ pub trait CzImage { /// Get the raw underlying bitmap for an image fn raw_bitmap(&self) -> &Vec; } + +pub fn parse_colormap(input: &[u8], num_colors: usize) -> (Vec<[u8; 4]>, usize) { + let mut colormap = Vec::with_capacity(num_colors); + + let input_iter = input.windows(4).step_by(4).take(num_colors); + + for color in input_iter { + colormap.push(color.try_into().unwrap()); + } + + (colormap, num_colors * 4) +} diff --git a/src/formats/cz0.rs b/src/formats/cz0.rs index 1b7e9da..96029f8 100644 --- a/src/formats/cz0.rs +++ b/src/formats/cz0.rs @@ -75,7 +75,7 @@ impl CzHeader for Cz0Header { self.common.height } - fn depth(&self) -> u8 { + fn depth(&self) -> u16 { self.common.depth } } @@ -88,7 +88,7 @@ impl CzImage for Cz0Image { let header = Cz0Header::new(bytes)?; // Get the rest of the file, which is the bitmap - let bitmap = bytes[header.header_length() as usize..].to_vec(); + let bitmap = bytes[header.header_length()..].to_vec(); Ok(Self { header, diff --git a/src/formats/cz1.rs b/src/formats/cz1.rs new file mode 100644 index 0000000..eab8a1e --- /dev/null +++ b/src/formats/cz1.rs @@ -0,0 +1,177 @@ +use std::io::Read; + +use crate::cz_common::{parse_colormap, CommonHeader, CzError, CzHeader, CzImage}; + +#[derive(Debug)] +pub struct Cz1Header { + /// Common CZ# header + common: CommonHeader, +} + +#[derive(Debug)] +pub struct Cz1Image { + header: Cz1Header, + bitmap: Vec, + palette: Vec<[u8; 4]>, +} + +impl CzHeader for Cz1Header { + fn new(bytes: &[u8]) -> Result { + let common = CommonHeader::new(bytes); + + if common.version != 1 { + return Err(CzError::VersionMismatch) + } + + Ok(Self { + common, + }) + } + + fn version(&self) -> u8 { + self.common.version + } + + fn header_length(&self) -> usize { + self.common.length as usize + } + + fn width(&self) -> u16 { + self.common.width + } + + fn height(&self) -> u16 { + self.common.height + } + + fn depth(&self) -> u16 { + self.common.depth + } +} + +impl CzImage for Cz1Image { + type Header = Cz1Header; + + fn decode(bytes: &[u8]) -> Result { + let mut position = 0; + + // Get the header from the input + let header = Cz1Header::new(bytes)?; + position += header.header_length(); + + // The color palette + let (palette, palette_length) = parse_colormap(&bytes[position..], 0x100); + position += palette_length; + + dbg!(&bytes[position..position + 4]); + + let parts_count = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap()); + position += 4; + dbg!(parts_count); + let mut part_sizes = vec![0; parts_count as usize]; + let mut total_size = 0; + + for size in &mut part_sizes { + let part_size = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap()) * 2; + *size = part_size; + total_size += part_size; + + dbg!(part_size); + + position += 8; + } + + if position + total_size as usize > bytes.len() { + return Err(CzError::InvalidFormat) + } + + let mut m_dst = 0; + let bitmap = vec![0; 4882176]; + + let mut image = Self { + header, + bitmap, + palette + }; + + for size in part_sizes { + let part = &bytes[position..position + size as usize]; + position += size as usize; + + for j in (0..part.len()).step_by(2) { + let ctl = part[j + 1]; + + if ctl == 0 { + image.bitmap[m_dst] = part[j]; + m_dst += 1; + } else { + m_dst += image.copy_range(part, get_offset(part, j), m_dst); + } + } + } + + Ok(image) + } + + fn save_as_png(&self, name: &str) { + image::save_buffer( + name, + &self.bitmap, + self.header.common.width as u32, + self.header.common.height as u32, + image::ExtendedColorType::Rgba8 + ).unwrap() + } + + fn header(&self) -> &Self::Header { + &self.header + } + + fn raw_bitmap(&self) -> &Vec { + &self.bitmap + } +} + +fn get_offset(input: &[u8], src: usize) -> usize { + (((input[src] as usize) | (input[src+1] as usize) << 8) - 0x101) * 2 +} + +impl Cz1Image { + fn copy_range(&mut self, input: &[u8], src: usize, dst: usize) -> usize { + let mut dst = dst; + let start_pos = dst; + + if input[src + 1] == 0 { + self.bitmap[dst] = input[src]; + dst += 1; + } else if get_offset(input, src) == src { + self.bitmap[dst] = 0; + dst += 1; + } else { + dst += self.copy_range(input, get_offset(input, src), dst); + } + + if input[src + 3] == 0 { + self.bitmap[dst] = input[src + 2]; + dst += 1; + } else if get_offset(input, src + 2) == src { + self.bitmap[dst] = self.bitmap[start_pos]; + dst += 1; + } else { + self.bitmap[dst] = copy_one(input, get_offset(input, src + 2)); + dst += 1; + } + + dst - start_pos + } +} + +fn copy_one(input: &[u8], src: usize) -> u8 { + if input[src + 1] == 0 { + input[src] + } else if get_offset(input, src) == src { + 0 + } else { + copy_one(input, get_offset(input, src)) + } +} diff --git a/src/main.rs b/src/main.rs index 97b38ba..eb37bca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,18 @@ pub mod cz_common; pub mod formats{ pub mod cz0; + pub mod cz1; } // Generic tools use std::fs; -use crate::{cz_common::CzImage, formats::cz0::Cz0Image}; +use crate::{cz_common::CzImage, formats::cz1::Cz1Image}; fn main() { - let input = fs::read("../test_files/Old_TestFiles/EX_PT.CZ0").expect("Error, could not open image"); - let cz0_file = Cz0Image::decode(&input).unwrap(); - println!("{:#?}", cz0_file.header()); + let input = fs::read("../test_files/x5a3bvy.cz1").expect("Error, could not open image"); + let cz1_file = Cz1Image::decode(&input).unwrap(); + println!("{:#?}", cz1_file.header()); - cz0_file.save_as_png("test.png") + cz1_file.save_as_png("test.png") }