Broken cz1 implementation

This commit is contained in:
G2-Games 2024-05-01 23:21:36 -05:00
parent dc14fd1d65
commit d11272463b
5 changed files with 208 additions and 13 deletions

2
.gitignore vendored
View file

@ -13,3 +13,5 @@ Cargo.lock
*.cz*
*.CZ*
*.png
test_files/*

View file

@ -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<u8>;
}
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)
}

View file

@ -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,

177
src/formats/cz1.rs Normal file
View file

@ -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<u8>,
palette: Vec<[u8; 4]>,
}
impl CzHeader for Cz1Header {
fn new(bytes: &[u8]) -> Result<Self, CzError> {
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<Self, CzError> {
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<u8> {
&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))
}
}

View file

@ -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")
}