Cleaned up implementations

This commit is contained in:
G2-Games 2024-05-03 18:21:30 -05:00
parent 27eea7a38a
commit e7f76a51c0
5 changed files with 158 additions and 93 deletions

View file

@ -1,6 +1,9 @@
//! Shared types and traits between CZ# files //! Shared types and traits between CZ# files
use std::io::{self, Cursor, Read}; use std::{
collections::HashMap,
io::{self, Cursor, Read},
};
use byteorder::{LittleEndian, ReadBytesExt}; use byteorder::{LittleEndian, ReadBytesExt};
use image::Rgba; use image::Rgba;
@ -11,7 +14,11 @@ pub enum CzError {
#[error("Version in header does not match expected version")] #[error("Version in header does not match expected version")]
VersionMismatch, VersionMismatch,
#[error("Format of supplied file is incorrect; expected {} bytes, got {}", expected, got)] #[error(
"Format of supplied file is incorrect; expected {} bytes, got {}",
expected,
got
)]
InvalidFormat { expected: usize, got: usize }, InvalidFormat { expected: usize, got: usize },
#[error("Failed to read input")] #[error("Failed to read input")]
@ -19,7 +26,9 @@ pub enum CzError {
} }
pub trait CzHeader { pub trait CzHeader {
fn new(bytes: &[u8]) -> Result<Self, CzError> where Self: Sized; fn new(bytes: &mut Cursor<&[u8]>) -> Result<Self, CzError>
where
Self: Sized;
fn version(&self) -> u8; fn version(&self) -> u8;
@ -36,23 +45,26 @@ pub trait CzHeader {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct CommonHeader { pub struct CommonHeader {
/// Format version from the magic bytes, (eg. CZ3, CZ4) /// Format version from the magic bytes, (eg. CZ3, CZ4)
pub version: u8, version: u8,
/// Length of the header in bytes /// Length of the header in bytes
pub length: u32, length: u32,
/// Width of the image in pixels /// Width of the image in pixels
pub width: u16, width: u16,
/// Height of the image in pixels /// Height of the image in pixels
pub height: u16, height: u16,
/// Bit depth in Bits Per Pixel (BPP) /// Bit depth in Bits Per Pixel (BPP)
pub depth: u16, depth: u16,
} }
impl CommonHeader { impl CzHeader for CommonHeader {
pub fn new(bytes: &mut Cursor<&[u8]>) -> Result<Self, io::Error> { fn new(bytes: &mut Cursor<&[u8]>) -> Result<Self, CzError>
where
Self: Sized,
{
let mut magic = [0u8; 4]; let mut magic = [0u8; 4];
bytes.read_exact(&mut magic)?; bytes.read_exact(&mut magic)?;
@ -64,13 +76,35 @@ impl CommonHeader {
depth: bytes.read_u16::<LittleEndian>()?, depth: bytes.read_u16::<LittleEndian>()?,
}) })
} }
fn version(&self) -> u8 {
self.version
}
fn header_length(&self) -> usize {
self.length as usize
}
fn width(&self) -> u16 {
self.width
}
fn height(&self) -> u16 {
self.height
}
fn depth(&self) -> u16 {
self.depth
}
} }
pub trait CzImage { pub trait CzImage {
type Header; type Header;
/// Create a [CZImage] from bytes /// Create a [CZImage] from bytes
fn decode(bytes: &[u8]) -> Result<Self, CzError> where Self: Sized; fn decode(bytes: &[u8]) -> Result<Self, CzError>
where
Self: Sized;
/// Save the image as a PNG /// Save the image as a PNG
fn save_as_png(&self, name: &str); fn save_as_png(&self, name: &str);
@ -82,7 +116,10 @@ pub trait CzImage {
fn into_bitmap(self) -> Vec<u8>; fn into_bitmap(self) -> Vec<u8>;
} }
pub fn parse_colormap(input: &mut Cursor<&[u8]>, num_colors: usize) -> Result<Vec<Rgba<u8>>, CzError> { pub fn parse_colormap(
input: &mut Cursor<&[u8]>,
num_colors: usize,
) -> Result<Vec<Rgba<u8>>, CzError> {
let mut colormap = Vec::with_capacity(num_colors); let mut colormap = Vec::with_capacity(num_colors);
let mut rgba_buf = [0u8; 4]; let mut rgba_buf = [0u8; 4];
@ -94,11 +131,13 @@ pub fn parse_colormap(input: &mut Cursor<&[u8]>, num_colors: usize) -> Result<Ve
Ok(colormap) Ok(colormap)
} }
#[derive(Debug)]
pub struct ChunkInfo { pub struct ChunkInfo {
pub size_compressed: usize, pub size_compressed: usize,
pub size_raw: usize, pub size_raw: usize,
} }
#[derive(Debug)]
pub struct CompressionInfo { pub struct CompressionInfo {
pub chunk_count: usize, pub chunk_count: usize,
pub total_size_compressed: usize, pub total_size_compressed: usize,
@ -112,8 +151,8 @@ pub struct CompressionInfo {
/// Get info about the compression chunks /// Get info about the compression chunks
pub fn parse_chunk_info(bytes: &mut Cursor<&[u8]>) -> Result<CompressionInfo, CzError> { pub fn parse_chunk_info(bytes: &mut Cursor<&[u8]>) -> Result<CompressionInfo, CzError> {
let parts_count = bytes.read_u32::<LittleEndian>()?; let parts_count = bytes.read_u32::<LittleEndian>()?;
dbg!(parts_count); dbg!(parts_count);
let mut part_sizes = vec![]; let mut part_sizes = vec![];
let mut total_size = 0; let mut total_size = 0;
let mut total_size_raw = 0; let mut total_size_raw = 0;
@ -131,6 +170,8 @@ pub fn parse_chunk_info(bytes: &mut Cursor<&[u8]>) -> Result<CompressionInfo, Cz
}); });
} }
dbg!(&part_sizes);
Ok(CompressionInfo { Ok(CompressionInfo {
chunk_count: parts_count as usize, chunk_count: parts_count as usize,
total_size_compressed: total_size as usize, total_size_compressed: total_size as usize,
@ -140,8 +181,11 @@ pub fn parse_chunk_info(bytes: &mut Cursor<&[u8]>) -> Result<CompressionInfo, Cz
}) })
} }
/// Decompress an LZW compressed stream, like CZ1
pub fn decompress(input: &mut Cursor<&[u8]>, chunk_info: CompressionInfo) -> Result<Vec<u8>, CzError> { pub fn decompress(
input: &mut Cursor<&[u8]>,
chunk_info: CompressionInfo,
) -> Result<Vec<u8>, CzError> {
let mut m_dst = 0; let mut m_dst = 0;
let mut bitmap = vec![0; chunk_info.total_size_raw]; let mut bitmap = vec![0; chunk_info.total_size_raw];
for chunk in chunk_info.chunks { for chunk in chunk_info.chunks {

View file

@ -1,4 +1,6 @@
use std::io::Cursor; use std::io::{Cursor, Read};
use byteorder::{LittleEndian, ReadBytesExt};
use crate::cz_common::{CommonHeader, CzError, CzHeader, CzImage}; use crate::cz_common::{CommonHeader, CzError, CzHeader, CzImage};
@ -8,22 +10,22 @@ pub struct Cz0Header {
common: CommonHeader, common: CommonHeader,
/// Width of cropped image area /// Width of cropped image area
crop_width: u16, pub crop_width: u16,
/// Height of cropped image area /// Height of cropped image area
crop_height: u16, pub crop_height: u16,
/// Bounding box width /// Bounding box width
bounds_width: u16, pub bounds_width: u16,
/// Bounding box height /// Bounding box height
bounds_height: u16, pub bounds_height: u16,
/// Offset width /// Offset width
offset_width: Option<u16>, pub offset_width: Option<u16>,
/// Offset height /// Offset height
offset_height: Option<u16>, pub offset_height: Option<u16>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -33,29 +35,39 @@ pub struct Cz0Image {
} }
impl CzHeader for Cz0Header { impl CzHeader for Cz0Header {
fn new(bytes: &[u8]) -> Result<Self, CzError> { fn new(bytes: &mut Cursor<&[u8]>) -> Result<Self, CzError>
let mut input = Cursor::new(bytes); where
let common = CommonHeader::new(&mut input)?; Self: Sized,
{
let common = CommonHeader::new(bytes)?;
if common.version != 0 { if common.version() != 0 {
return Err(CzError::VersionMismatch) return Err(CzError::VersionMismatch);
} }
let _unknown = bytes.read_u48::<LittleEndian>()?;
let crop_width = bytes.read_u16::<LittleEndian>()?;
let crop_height = bytes.read_u16::<LittleEndian>()?;
let bounds_width = bytes.read_u16::<LittleEndian>()?;
let bounds_height = bytes.read_u16::<LittleEndian>()?;
let mut offset_width = None; let mut offset_width = None;
let mut offset_height = None; let mut offset_height = None;
if common.length > 28 { if common.header_length() > 28 {
offset_width = Some(u16::from_le_bytes(bytes[28..30].try_into().unwrap())); offset_width = Some(bytes.read_u16::<LittleEndian>()?);
offset_height = Some(u16::from_le_bytes(bytes[30..32].try_into().unwrap())); offset_height = Some(bytes.read_u16::<LittleEndian>()?);
} }
Ok(Self { Ok(Self {
common, common,
crop_width: u16::from_le_bytes(bytes[20..22].try_into().unwrap()), crop_width,
crop_height: u16::from_le_bytes(bytes[22..24].try_into().unwrap()), crop_height,
bounds_width: u16::from_le_bytes(bytes[24..26].try_into().unwrap()), bounds_width,
bounds_height: u16::from_le_bytes(bytes[26..28].try_into().unwrap()), bounds_height,
offset_width, offset_width,
offset_height, offset_height,
@ -63,23 +75,23 @@ impl CzHeader for Cz0Header {
} }
fn version(&self) -> u8 { fn version(&self) -> u8 {
self.common.version self.common.version()
} }
fn header_length(&self) -> usize { fn header_length(&self) -> usize {
self.common.length as usize self.common.header_length()
} }
fn width(&self) -> u16 { fn width(&self) -> u16 {
self.common.width self.common.width()
} }
fn height(&self) -> u16 { fn height(&self) -> u16 {
self.common.height self.common.height()
} }
fn depth(&self) -> u16 { fn depth(&self) -> u16 {
self.common.depth self.common.depth()
} }
} }
@ -87,26 +99,27 @@ impl CzImage for Cz0Image {
type Header = Cz0Header; type Header = Cz0Header;
fn decode(bytes: &[u8]) -> Result<Self, CzError> { fn decode(bytes: &[u8]) -> Result<Self, CzError> {
let mut input = Cursor::new(bytes);
// Get the header from the input // Get the header from the input
let header = Cz0Header::new(bytes)?; let header = Cz0Header::new(&mut input)?;
// Get the rest of the file, which is the bitmap // Get the rest of the file, which is the bitmap
let bitmap = bytes[header.header_length()..].to_vec(); let mut bitmap = vec![];
input.read_to_end(&mut bitmap)?;
Ok(Self { Ok(Self { header, bitmap })
header,
bitmap
})
} }
fn save_as_png(&self, name: &str) { fn save_as_png(&self, name: &str) {
image::save_buffer( image::save_buffer(
name, name,
&self.bitmap, &self.bitmap,
self.header.common.width as u32, self.header.width() as u32,
self.header.common.height as u32, self.header.height() as u32,
image::ExtendedColorType::Rgba8 image::ExtendedColorType::Rgba8,
).unwrap() )
.unwrap()
} }
fn header(&self) -> &Self::Header { fn header(&self) -> &Self::Header {

View file

@ -1,6 +1,8 @@
use std::io::Cursor; use crate::cz_common::{
decompress, parse_chunk_info, parse_colormap, CommonHeader, CzError, CzHeader, CzImage,
};
use image::{ImageFormat, Rgba}; use image::{ImageFormat, Rgba};
use crate::cz_common::{decompress, parse_chunk_info, parse_colormap, CommonHeader, CzError, CzImage}; use std::io::Cursor;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Cz1Image { pub struct Cz1Image {
@ -20,8 +22,8 @@ impl CzImage for Cz1Image {
// The color palette, gotten for 8 and 4 BPP images // The color palette, gotten for 8 and 4 BPP images
let mut palette = None; let mut palette = None;
if header.depth == 8 || header.depth == 4 { if header.depth() == 8 || header.depth() == 4 {
palette = Some(parse_colormap(&mut input, 1 << header.depth)?); palette = Some(parse_colormap(&mut input, 1 << header.depth())?);
} }
let chunk_info = parse_chunk_info(&mut input)?; let chunk_info = parse_chunk_info(&mut input)?;
@ -30,7 +32,7 @@ impl CzImage for Cz1Image {
return Err(CzError::InvalidFormat { return Err(CzError::InvalidFormat {
expected: chunk_info.total_size_compressed, expected: chunk_info.total_size_compressed,
got: bytes.len(), got: bytes.len(),
}) });
} }
let mut bitmap = decompress(&mut input, chunk_info).unwrap(); let mut bitmap = decompress(&mut input, chunk_info).unwrap();
@ -51,10 +53,11 @@ impl CzImage for Cz1Image {
fn save_as_png(&self, name: &str) { fn save_as_png(&self, name: &str) {
let img = image::RgbaImage::from_raw( let img = image::RgbaImage::from_raw(
self.header.width as u32, self.header.width() as u32,
self.header.height as u32, self.header.height() as u32,
self.bitmap.clone() self.bitmap.clone(),
).unwrap(); )
.unwrap();
img.save_with_format(name, ImageFormat::Png).unwrap(); img.save_with_format(name, ImageFormat::Png).unwrap();
} }

View file

@ -1,5 +1,6 @@
use std::io::Cursor; use std::io::Cursor;
use byteorder::{LittleEndian, ReadBytesExt};
use image::ImageFormat; use image::ImageFormat;
use crate::cz_common::{decompress, parse_chunk_info, CommonHeader, CzError, CzHeader, CzImage}; use crate::cz_common::{decompress, parse_chunk_info, CommonHeader, CzError, CzHeader, CzImage};
@ -29,29 +30,39 @@ pub struct Cz3Header {
} }
impl CzHeader for Cz3Header { impl CzHeader for Cz3Header {
fn new(bytes: &[u8]) -> Result<Self, CzError> where Self: Sized { fn new(bytes: &mut Cursor<&[u8]>) -> Result<Self, CzError>
let mut input = Cursor::new(bytes); where
let common = CommonHeader::new(&mut input)?; Self: Sized,
{
let common = CommonHeader::new(bytes)?;
if common.version != 3 { if common.version() != 3 {
return Err(CzError::VersionMismatch) return Err(CzError::VersionMismatch);
} }
let _unknown = bytes.read_u48::<LittleEndian>()?;
let crop_width = bytes.read_u16::<LittleEndian>()?;
let crop_height = bytes.read_u16::<LittleEndian>()?;
let bounds_width = bytes.read_u16::<LittleEndian>()?;
let bounds_height = bytes.read_u16::<LittleEndian>()?;
let mut offset_width = None; let mut offset_width = None;
let mut offset_height = None; let mut offset_height = None;
if common.length > 28 { if common.header_length() > 28 {
offset_width = Some(u16::from_le_bytes(bytes[28..30].try_into().unwrap())); offset_width = Some(bytes.read_u16::<LittleEndian>()?);
offset_height = Some(u16::from_le_bytes(bytes[30..32].try_into().unwrap())); offset_height = Some(bytes.read_u16::<LittleEndian>()?);
} }
Ok(Self { Ok(Self {
common, common,
crop_width: u16::from_le_bytes(bytes[20..22].try_into().unwrap()), crop_width,
crop_height: u16::from_le_bytes(bytes[22..24].try_into().unwrap()), crop_height,
bounds_width: u16::from_le_bytes(bytes[24..26].try_into().unwrap()), bounds_width,
bounds_height: u16::from_le_bytes(bytes[26..28].try_into().unwrap()), bounds_height,
offset_width, offset_width,
offset_height, offset_height,
@ -59,23 +70,23 @@ impl CzHeader for Cz3Header {
} }
fn version(&self) -> u8 { fn version(&self) -> u8 {
self.common.version self.common.version()
} }
fn header_length(&self) -> usize { fn header_length(&self) -> usize {
self.common.length as usize self.common.header_length()
} }
fn width(&self) -> u16 { fn width(&self) -> u16 {
self.common.width self.common.width()
} }
fn height(&self) -> u16 { fn height(&self) -> u16 {
self.common.height self.common.height()
} }
fn depth(&self) -> u16 { fn depth(&self) -> u16 {
self.common.depth self.common.depth()
} }
} }
@ -90,15 +101,13 @@ impl CzImage for Cz3Image {
fn decode(bytes: &[u8]) -> Result<Self, CzError> { fn decode(bytes: &[u8]) -> Result<Self, CzError> {
let mut input = Cursor::new(bytes); let mut input = Cursor::new(bytes);
let header = Cz3Header::new(bytes)?; let header = Cz3Header::new(&mut input)?;
input.set_position(header.header_length() as u64); input.set_position(header.header_length() as u64);
let block_info = parse_chunk_info(&mut input)?; let block_info = parse_chunk_info(&mut input)?;
let mut bitmap = decompress(&mut input, block_info)?; let mut bitmap = decompress(&mut input, block_info)?;
dbg!(bitmap.len());
let stride = (header.width() * (header.depth() / 8)) as usize; let stride = (header.width() * (header.depth() / 8)) as usize;
let third = ((header.height() + 2) / 3) as usize; let third = ((header.height() + 2) / 3) as usize;
for y in 0..header.height() as usize { for y in 0..header.height() as usize {
@ -110,20 +119,16 @@ impl CzImage for Cz3Image {
} }
} }
dbg!(bitmap.len()); Ok(Self { header, bitmap })
Ok(Self {
header,
bitmap
})
} }
fn save_as_png(&self, name: &str) { fn save_as_png(&self, name: &str) {
let img = image::RgbaImage::from_raw( let img = image::RgbaImage::from_raw(
self.header.width() as u32, self.header.width() as u32,
self.header.height() as u32, self.header.height() as u32,
self.bitmap.clone() self.bitmap.clone(),
).unwrap(); )
.unwrap();
img.save_with_format(name, ImageFormat::Png).unwrap(); img.save_with_format(name, ImageFormat::Png).unwrap();
} }

View file

@ -11,9 +11,9 @@ use std::fs;
use crate::{cz_common::CzImage, formats::cz3::Cz3Image}; use crate::{cz_common::CzImage, formats::cz3::Cz3Image};
fn main() { fn main() {
let input = fs::read("../test_files/Old_TestFiles/129.CZ3").expect("Error, could not open image"); let input = fs::read("../test_files/BAD_BG_011_10.cz4").expect("Error, could not open image");
let cz3_file = Cz3Image::decode(&input).unwrap(); let img_file = Cz3Image::decode(&input).unwrap();
println!("{:#?}", cz3_file.header()); println!("{:#?}", img_file.header());
cz3_file.save_as_png("test.png") img_file.save_as_png("test.png")
} }