mirror of
https://github.com/G2-Games/lbee-utils.git
synced 2025-04-19 07:12:55 -05:00
Fixed issues, improved layout of files
This commit is contained in:
parent
920fb8c697
commit
d5ae80005e
7 changed files with 451 additions and 352 deletions
184
src/common.rs
Normal file
184
src/common.rs
Normal file
|
@ -0,0 +1,184 @@
|
|||
//! Shared types and traits between CZ# files
|
||||
|
||||
use std::{
|
||||
io::{self, Cursor, Read, Seek, Write}, path::PathBuf
|
||||
};
|
||||
|
||||
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")]
|
||||
VersionMismatch,
|
||||
|
||||
#[error(
|
||||
"Format of supplied file is incorrect; expected {} bytes, got {}",
|
||||
expected,
|
||||
got
|
||||
)]
|
||||
InvalidFormat { expected: usize, got: usize },
|
||||
|
||||
#[error("Failed to read/write input/output")]
|
||||
IoError(#[from] io::Error),
|
||||
}
|
||||
|
||||
pub trait CzHeader {
|
||||
fn new<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Turn the header into bytes equivalent to the original header from the file
|
||||
fn to_bytes(&self) -> Result<Vec<u8>, io::Error>;
|
||||
|
||||
/// The version of the [CzImage] file
|
||||
fn version(&self) -> u8;
|
||||
|
||||
/// The length of the header in bytes
|
||||
fn length(&self) -> usize;
|
||||
|
||||
/// The width of the image
|
||||
fn width(&self) -> u16;
|
||||
|
||||
/// The height of the image
|
||||
fn height(&self) -> u16;
|
||||
|
||||
/// The bit depth of the image (BPP)
|
||||
fn depth(&self) -> u16;
|
||||
|
||||
/// An unknown value?
|
||||
fn color_block(&self) -> u8;
|
||||
}
|
||||
|
||||
/// The common first part of a header of a CZ# file
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CommonHeader {
|
||||
/// Format version from the magic bytes, (eg. CZ3, CZ4)
|
||||
pub version: u8,
|
||||
|
||||
/// Length of the header in bytes
|
||||
pub length: u32,
|
||||
|
||||
/// Width of the image in pixels
|
||||
pub width: u16,
|
||||
|
||||
/// Height of the image in pixels
|
||||
pub height: u16,
|
||||
|
||||
/// Bit depth in Bits Per Pixel (BPP)
|
||||
pub depth: u16,
|
||||
|
||||
/// Color block
|
||||
pub color_block: u8,
|
||||
}
|
||||
|
||||
impl CzHeader for CommonHeader {
|
||||
fn new<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut magic = [0u8; 4];
|
||||
bytes.read_exact(&mut magic)?;
|
||||
|
||||
Ok(Self {
|
||||
version: magic[2] - b'0',
|
||||
length: bytes.read_u32::<LittleEndian>()?,
|
||||
width: bytes.read_u16::<LittleEndian>()?,
|
||||
height: bytes.read_u16::<LittleEndian>()?,
|
||||
depth: bytes.read_u16::<LittleEndian>()?,
|
||||
color_block: bytes.read_u8()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn version(&self) -> u8 {
|
||||
self.version
|
||||
}
|
||||
|
||||
fn 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
|
||||
}
|
||||
|
||||
fn color_block(&self) -> u8 {
|
||||
self.color_block
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Result<Vec<u8>, io::Error> {
|
||||
let mut buf = vec![];
|
||||
|
||||
let magic_bytes = [b'C', b'Z', b'0' + self.version, 0];
|
||||
buf.write_all(&magic_bytes)?;
|
||||
buf.write_u32::<LittleEndian>(self.length() as u32)?;
|
||||
buf.write_u16::<LittleEndian>(self.width())?;
|
||||
buf.write_u16::<LittleEndian>(self.height())?;
|
||||
buf.write_u16::<LittleEndian>(self.depth())?;
|
||||
buf.write_u8(self.color_block())?;
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CzImage {
|
||||
type Header;
|
||||
|
||||
/// Create a [CZImage] from bytes
|
||||
fn decode<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError>
|
||||
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<T: Into<PathBuf>>(&self, path: T) -> Result<(), CzError>;
|
||||
|
||||
/// Get the header for metadata
|
||||
fn header(&self) -> &Self::Header;
|
||||
|
||||
/// Set the header with its metadata
|
||||
fn set_header(&mut self, header: Self::Header);
|
||||
|
||||
/// Get the raw underlying bitmap for an image
|
||||
fn into_bitmap(self) -> Vec<u8>;
|
||||
|
||||
/// Set the bitmap the image contains
|
||||
fn set_bitmap(&mut self, bitmap: &[u8], header: &Self::Header);
|
||||
}
|
||||
|
||||
pub fn parse_colormap<T: Seek + ReadBytesExt + Read>(
|
||||
input: &mut T,
|
||||
num_colors: usize,
|
||||
) -> Result<Vec<Rgba<u8>>, 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));
|
||||
}
|
||||
|
||||
Ok(colormap)
|
||||
}
|
||||
|
||||
pub fn apply_palette(input: &mut Vec<u8>, palette: &[Rgba<u8>]) -> Vec<u8> {
|
||||
let mut output_map = Vec::new();
|
||||
|
||||
for byte in input.iter() {
|
||||
let color = palette[*byte as usize].0;
|
||||
output_map.extend_from_slice(&color);
|
||||
}
|
||||
|
||||
output_map
|
||||
}
|
119
src/compression.rs
Normal file
119
src/compression.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
use std::io::{Read, Seek};
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use crate::common::CzError;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ChunkInfo {
|
||||
pub size_compressed: usize,
|
||||
pub size_raw: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CompressionInfo {
|
||||
pub chunk_count: usize,
|
||||
pub total_size_compressed: usize,
|
||||
pub total_size_raw: usize,
|
||||
pub chunks: Vec<ChunkInfo>,
|
||||
|
||||
/// Length of the compression chunk info
|
||||
pub length: usize,
|
||||
}
|
||||
|
||||
/// Get info about the compression chunks
|
||||
pub fn parse_chunk_info<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<CompressionInfo, CzError> {
|
||||
let parts_count = bytes.read_u32::<LittleEndian>()?;
|
||||
|
||||
let mut part_sizes = vec![];
|
||||
let mut total_size = 0;
|
||||
let mut total_size_raw = 0;
|
||||
|
||||
// Loop over the compressed bytes
|
||||
for _ in 0..parts_count {
|
||||
let compressed_size = bytes.read_u32::<LittleEndian>()? * 2;
|
||||
total_size += compressed_size;
|
||||
|
||||
let raw_size = bytes.read_u32::<LittleEndian>()?;
|
||||
total_size_raw += raw_size;
|
||||
|
||||
part_sizes.push(ChunkInfo {
|
||||
size_compressed: compressed_size as usize,
|
||||
size_raw: raw_size as usize,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(CompressionInfo {
|
||||
chunk_count: parts_count as usize,
|
||||
total_size_compressed: total_size as usize,
|
||||
total_size_raw: total_size_raw as usize,
|
||||
chunks: part_sizes,
|
||||
length: bytes.stream_position()? as usize,
|
||||
})
|
||||
}
|
||||
|
||||
/// Decompress an LZW compressed stream, like CZ1
|
||||
pub fn decompress<T: Seek + ReadBytesExt + Read>(
|
||||
input: &mut T,
|
||||
chunk_info: &CompressionInfo,
|
||||
) -> Result<Vec<u8>, CzError> {
|
||||
let mut m_dst = 0;
|
||||
let mut bitmap = vec![0; chunk_info.total_size_raw];
|
||||
for chunk in &chunk_info.chunks {
|
||||
let mut part = vec![0u8; chunk.size_compressed];
|
||||
input.read_exact(&mut part)?;
|
||||
|
||||
for j in (0..part.len()).step_by(2) {
|
||||
let ctl = part[j + 1];
|
||||
|
||||
if ctl == 0 {
|
||||
bitmap[m_dst] = part[j];
|
||||
m_dst += 1;
|
||||
} else {
|
||||
m_dst += copy_range(&mut bitmap, &part, get_offset(&part, j), m_dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bitmap)
|
||||
}
|
||||
|
||||
fn get_offset(input: &[u8], src: usize) -> usize {
|
||||
(((input[src] as usize) | (input[src + 1] as usize) << 8) - 0x101) * 2
|
||||
}
|
||||
|
||||
fn copy_range(bitmap: &mut Vec<u8>, input: &[u8], src: usize, dst: usize) -> usize {
|
||||
let mut dst = dst;
|
||||
let start_pos = dst;
|
||||
|
||||
if input[src + 1] == 0 {
|
||||
bitmap[dst] = input[src];
|
||||
dst += 1;
|
||||
} else if get_offset(input, src) == src {
|
||||
bitmap[dst] = 0;
|
||||
dst += 1;
|
||||
} else {
|
||||
dst += copy_range(bitmap, input, get_offset(input, src), dst);
|
||||
}
|
||||
|
||||
if input[src + 3] == 0 {
|
||||
bitmap[dst] = input[src + 2];
|
||||
dst += 1;
|
||||
} else if get_offset(input, src + 2) == src {
|
||||
bitmap[dst] = bitmap[start_pos];
|
||||
dst += 1;
|
||||
} else {
|
||||
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))
|
||||
}
|
||||
}
|
262
src/cz_common.rs
262
src/cz_common.rs
|
@ -1,262 +0,0 @@
|
|||
//! Shared types and traits between CZ# files
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{self, Cursor, Read},
|
||||
};
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use image::Rgba;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CzError {
|
||||
#[error("Version in header does not match expected version")]
|
||||
VersionMismatch,
|
||||
|
||||
#[error(
|
||||
"Format of supplied file is incorrect; expected {} bytes, got {}",
|
||||
expected,
|
||||
got
|
||||
)]
|
||||
InvalidFormat { expected: usize, got: usize },
|
||||
|
||||
#[error("Failed to read input")]
|
||||
ReadError(#[from] io::Error),
|
||||
}
|
||||
|
||||
pub trait CzHeader {
|
||||
fn new(bytes: &mut Cursor<&[u8]>) -> Result<Self, CzError>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn version(&self) -> u8;
|
||||
|
||||
fn header_length(&self) -> usize;
|
||||
|
||||
fn width(&self) -> u16;
|
||||
|
||||
fn height(&self) -> u16;
|
||||
|
||||
fn depth(&self) -> u16;
|
||||
}
|
||||
|
||||
/// The common first part of a header of a CZ# file
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CommonHeader {
|
||||
/// Format version from the magic bytes, (eg. CZ3, CZ4)
|
||||
version: u8,
|
||||
|
||||
/// Length of the header in bytes
|
||||
length: u32,
|
||||
|
||||
/// Width of the image in pixels
|
||||
width: u16,
|
||||
|
||||
/// Height of the image in pixels
|
||||
height: u16,
|
||||
|
||||
/// Bit depth in Bits Per Pixel (BPP)
|
||||
depth: u16,
|
||||
|
||||
color_block: u8,
|
||||
}
|
||||
|
||||
impl CzHeader for CommonHeader {
|
||||
fn new(bytes: &mut Cursor<&[u8]>) -> Result<Self, CzError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut magic = [0u8; 4];
|
||||
bytes.read_exact(&mut magic)?;
|
||||
|
||||
Ok(Self {
|
||||
version: magic[2] - b'0',
|
||||
length: bytes.read_u32::<LittleEndian>()?,
|
||||
width: bytes.read_u16::<LittleEndian>()?,
|
||||
height: bytes.read_u16::<LittleEndian>()?,
|
||||
depth: bytes.read_u16::<LittleEndian>()?,
|
||||
color_block: bytes.read_u8()?,
|
||||
})
|
||||
}
|
||||
|
||||
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 {
|
||||
type Header;
|
||||
|
||||
/// Create a [CZImage] from bytes
|
||||
fn decode(bytes: &[u8]) -> Result<Self, CzError>
|
||||
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) -> Result<(), CzError>;
|
||||
|
||||
/// Get the header for metadata
|
||||
fn header(&self) -> &Self::Header;
|
||||
|
||||
/// Set the header with its metadata
|
||||
fn set_header(&mut self, header: Self::Header);
|
||||
|
||||
/// Get the raw underlying bitmap for an image
|
||||
fn into_bitmap(self) -> Vec<u8>;
|
||||
|
||||
/// Set the bitmap the image contains
|
||||
fn set_bitmap(&mut self, bitmap: Vec<u8>, header: Self::Header);
|
||||
}
|
||||
|
||||
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 rgba_buf = [0u8; 4];
|
||||
|
||||
for _ in 0..num_colors {
|
||||
input.read_exact(&mut rgba_buf)?;
|
||||
colormap.push(Rgba(rgba_buf));
|
||||
}
|
||||
|
||||
Ok(colormap)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChunkInfo {
|
||||
pub size_compressed: usize,
|
||||
pub size_raw: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CompressionInfo {
|
||||
pub chunk_count: usize,
|
||||
pub total_size_compressed: usize,
|
||||
pub total_size_raw: usize,
|
||||
pub chunks: Vec<ChunkInfo>,
|
||||
|
||||
/// Length of the compression chunk info
|
||||
pub length: usize,
|
||||
}
|
||||
|
||||
/// Get info about the compression chunks
|
||||
pub fn parse_chunk_info(bytes: &mut Cursor<&[u8]>) -> Result<CompressionInfo, CzError> {
|
||||
let parts_count = bytes.read_u32::<LittleEndian>()?;
|
||||
dbg!(parts_count);
|
||||
|
||||
let mut part_sizes = vec![];
|
||||
let mut total_size = 0;
|
||||
let mut total_size_raw = 0;
|
||||
|
||||
for _ in 0..parts_count {
|
||||
let compressed_size = bytes.read_u32::<LittleEndian>()? * 2;
|
||||
total_size += compressed_size;
|
||||
|
||||
let raw_size = bytes.read_u32::<LittleEndian>()? * 4;
|
||||
total_size_raw += raw_size;
|
||||
|
||||
part_sizes.push(ChunkInfo {
|
||||
size_compressed: compressed_size as usize,
|
||||
size_raw: raw_size as usize,
|
||||
});
|
||||
}
|
||||
|
||||
dbg!(&part_sizes);
|
||||
|
||||
Ok(CompressionInfo {
|
||||
chunk_count: parts_count as usize,
|
||||
total_size_compressed: total_size as usize,
|
||||
total_size_raw: total_size_raw as usize,
|
||||
chunks: part_sizes,
|
||||
length: bytes.position() as usize,
|
||||
})
|
||||
}
|
||||
|
||||
/// Decompress an LZW compressed stream, like CZ1
|
||||
pub fn decompress(
|
||||
input: &mut Cursor<&[u8]>,
|
||||
chunk_info: CompressionInfo,
|
||||
) -> Result<Vec<u8>, CzError> {
|
||||
let mut m_dst = 0;
|
||||
let mut bitmap = vec![0; chunk_info.total_size_raw];
|
||||
for chunk in chunk_info.chunks {
|
||||
let mut part = vec![0u8; chunk.size_compressed];
|
||||
input.read_exact(&mut part)?;
|
||||
|
||||
for j in (0..part.len()).step_by(2) {
|
||||
let ctl = part[j + 1];
|
||||
|
||||
if ctl == 0 {
|
||||
bitmap[m_dst] = part[j];
|
||||
m_dst += 1;
|
||||
} else {
|
||||
m_dst += copy_range(&mut bitmap, &part, get_offset(&part, j), m_dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bitmap)
|
||||
}
|
||||
|
||||
fn get_offset(input: &[u8], src: usize) -> usize {
|
||||
(((input[src] as usize) | (input[src + 1] as usize) << 8) - 0x101) * 2
|
||||
}
|
||||
|
||||
fn copy_range(bitmap: &mut Vec<u8>, input: &[u8], src: usize, dst: usize) -> usize {
|
||||
let mut dst = dst;
|
||||
let start_pos = dst;
|
||||
|
||||
if input[src + 1] == 0 {
|
||||
bitmap[dst] = input[src];
|
||||
dst += 1;
|
||||
} else if get_offset(input, src) == src {
|
||||
bitmap[dst] = 0;
|
||||
dst += 1;
|
||||
} else {
|
||||
dst += copy_range(bitmap, input, get_offset(input, src), dst);
|
||||
}
|
||||
|
||||
if input[src + 3] == 0 {
|
||||
bitmap[dst] = input[src + 2];
|
||||
dst += 1;
|
||||
} else if get_offset(input, src + 2) == src {
|
||||
bitmap[dst] = bitmap[start_pos];
|
||||
dst += 1;
|
||||
} else {
|
||||
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))
|
||||
}
|
||||
}
|
|
@ -1,13 +1,16 @@
|
|||
use std::io::{self, Cursor, Read};
|
||||
use std::{fs::File, io::{self, BufWriter, Cursor, Read, Seek, Write}, path::PathBuf};
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::cz_common::{CommonHeader, CzError, CzHeader, CzImage};
|
||||
use crate::common::{CommonHeader, CzError, CzHeader, CzImage};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Cz0Header {
|
||||
/// Common CZ# header
|
||||
common: CommonHeader,
|
||||
pub common: CommonHeader,
|
||||
|
||||
/// Unknown bytes
|
||||
unknown_1: [u8; 5],
|
||||
|
||||
/// Width of cropped image area
|
||||
pub crop_width: u16,
|
||||
|
@ -26,6 +29,8 @@ pub struct Cz0Header {
|
|||
|
||||
/// Offset height
|
||||
pub offset_height: Option<u16>,
|
||||
|
||||
unknown_2: Option<[u8; 4]>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -35,7 +40,7 @@ pub struct Cz0Image {
|
|||
}
|
||||
|
||||
impl CzHeader for Cz0Header {
|
||||
fn new(bytes: &mut Cursor<&[u8]>) -> Result<Self, CzError>
|
||||
fn new<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -45,8 +50,8 @@ impl CzHeader for Cz0Header {
|
|||
return Err(CzError::VersionMismatch);
|
||||
}
|
||||
|
||||
let _unknown = bytes.read_u32::<LittleEndian>()?;
|
||||
let _unknown = bytes.read_u8()?;
|
||||
let mut unknown_1 = [0u8; 5];
|
||||
bytes.read_exact(&mut unknown_1)?;
|
||||
|
||||
let crop_width = bytes.read_u16::<LittleEndian>()?;
|
||||
let crop_height = bytes.read_u16::<LittleEndian>()?;
|
||||
|
@ -56,14 +61,22 @@ impl CzHeader for Cz0Header {
|
|||
|
||||
let mut offset_width = None;
|
||||
let mut offset_height = None;
|
||||
if common.header_length() > 28 {
|
||||
let mut unknown_2 = None;
|
||||
if common.length() > 28 {
|
||||
offset_width = Some(bytes.read_u16::<LittleEndian>()?);
|
||||
offset_height = Some(bytes.read_u16::<LittleEndian>()?);
|
||||
|
||||
let mut un_2 = [0u8; 4];
|
||||
bytes.read_exact(&mut un_2)?;
|
||||
|
||||
unknown_2 = Some(un_2);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
common,
|
||||
|
||||
unknown_1,
|
||||
|
||||
crop_width,
|
||||
crop_height,
|
||||
|
||||
|
@ -72,6 +85,8 @@ impl CzHeader for Cz0Header {
|
|||
|
||||
offset_width,
|
||||
offset_height,
|
||||
|
||||
unknown_2,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -79,8 +94,8 @@ impl CzHeader for Cz0Header {
|
|||
self.common.version()
|
||||
}
|
||||
|
||||
fn header_length(&self) -> usize {
|
||||
self.common.header_length()
|
||||
fn length(&self) -> usize {
|
||||
self.common.length()
|
||||
}
|
||||
|
||||
fn width(&self) -> u16 {
|
||||
|
@ -94,32 +109,64 @@ impl CzHeader for Cz0Header {
|
|||
fn depth(&self) -> u16 {
|
||||
self.common.depth()
|
||||
}
|
||||
|
||||
fn color_block(&self) -> u8 {
|
||||
self.common.color_block()
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Result<Vec<u8>, io::Error> {
|
||||
let mut buf = vec![];
|
||||
|
||||
buf.write_all(&self.common.to_bytes()?)?;
|
||||
buf.write_all(&self.unknown_1)?;
|
||||
buf.write_u16::<LittleEndian>(self.crop_width)?;
|
||||
buf.write_u16::<LittleEndian>(self.crop_height)?;
|
||||
buf.write_u16::<LittleEndian>(self.bounds_width)?;
|
||||
buf.write_u16::<LittleEndian>(self.bounds_height)?;
|
||||
|
||||
if self.length() > 28 {
|
||||
buf.write_u16::<LittleEndian>(self.offset_width.unwrap())?;
|
||||
buf.write_u16::<LittleEndian>(self.offset_height.unwrap())?;
|
||||
buf.write_all(&self.unknown_2.unwrap())?;
|
||||
}
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl CzImage for Cz0Image {
|
||||
type Header = Cz0Header;
|
||||
|
||||
fn decode(bytes: &[u8]) -> Result<Self, CzError> {
|
||||
let mut input = Cursor::new(bytes);
|
||||
|
||||
fn decode<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError> {
|
||||
// Get the header from the input
|
||||
let header = Cz0Header::new(&mut input)?;
|
||||
let header = Cz0Header::new(bytes)?;
|
||||
bytes.seek(io::SeekFrom::Start(header.length() as u64));
|
||||
|
||||
// Get the rest of the file, which is the bitmap
|
||||
let mut bitmap = vec![0u8; bytes.len() - header.header_length()];
|
||||
input.read_exact(&mut bitmap)?;
|
||||
let mut bitmap = vec![];
|
||||
bytes.read_to_end(&mut bitmap)?;
|
||||
|
||||
Ok(Self { header, bitmap })
|
||||
}
|
||||
|
||||
fn save_as_png(&self, name: &str) -> Result<(), image::error::ImageError> {
|
||||
Ok(image::save_buffer(
|
||||
image::save_buffer(
|
||||
name,
|
||||
&self.bitmap,
|
||||
self.header.width() as u32,
|
||||
self.header.height() as u32,
|
||||
image::ExtendedColorType::Rgba8,
|
||||
)?)
|
||||
)
|
||||
}
|
||||
|
||||
fn save_as_cz<T: Into<PathBuf>>(&self, path: T) -> Result<(), CzError> {
|
||||
let mut output_file = BufWriter::new(File::create(path.into())?);
|
||||
|
||||
output_file.write_all(&self.header().to_bytes()?)?;
|
||||
output_file.write_all(&self.bitmap)?;
|
||||
output_file.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn header(&self) -> &Self::Header {
|
||||
|
@ -134,14 +181,10 @@ impl CzImage for Cz0Image {
|
|||
self.bitmap
|
||||
}
|
||||
|
||||
fn save_as_cz(&self) -> Result<(), CzError> {
|
||||
todo!()
|
||||
}
|
||||
fn set_bitmap(&mut self, bitmap: &[u8], header: &Self::Header) {
|
||||
self.bitmap = bitmap.to_vec();
|
||||
|
||||
fn set_bitmap(&mut self, bitmap: Vec<u8>, header: Self::Header) {
|
||||
self.bitmap = bitmap;
|
||||
|
||||
self.header = header;
|
||||
self.header = *header;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use crate::cz_common::{
|
||||
decompress, parse_chunk_info, parse_colormap, CommonHeader, CzError, CzHeader, CzImage,
|
||||
};
|
||||
use byteorder::ReadBytesExt;
|
||||
use image::{ImageFormat, Rgba};
|
||||
use std::io::Cursor;
|
||||
use std::{fs::File, io::{BufWriter, Cursor, Read, Seek, SeekFrom, Write}, path::PathBuf};
|
||||
|
||||
use crate::compression::{decompress, parse_chunk_info};
|
||||
use crate::common::{apply_palette, parse_colormap, CommonHeader, CzError, CzHeader, CzImage};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cz1Image {
|
||||
header: CommonHeader,
|
||||
raw_bitmap: Option<Vec<u8>>,
|
||||
bitmap: Vec<u8>,
|
||||
palette: Vec<Rgba<u8>>,
|
||||
}
|
||||
|
@ -14,37 +16,48 @@ pub struct Cz1Image {
|
|||
impl CzImage for Cz1Image {
|
||||
type Header = CommonHeader;
|
||||
|
||||
fn decode(bytes: &[u8]) -> Result<Self, CzError> {
|
||||
let mut input = Cursor::new(bytes);
|
||||
|
||||
fn decode<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError> {
|
||||
// Get the header from the input
|
||||
let header = CommonHeader::new(&mut input).unwrap();
|
||||
let header = CommonHeader::new(bytes).unwrap();
|
||||
bytes.seek(SeekFrom::Start(header.length() as u64));
|
||||
|
||||
if header.version() != 1 {
|
||||
return Err(CzError::VersionMismatch)
|
||||
}
|
||||
|
||||
// The color palette, gotten for 8 and 4 BPP images
|
||||
let mut palette = None;
|
||||
if header.depth() == 8 || header.depth() == 4 {
|
||||
palette = Some(parse_colormap(&mut input, 1 << header.depth())?);
|
||||
palette = Some(parse_colormap(bytes, 1 << header.depth())?);
|
||||
}
|
||||
|
||||
let chunk_info = parse_chunk_info(&mut input)?;
|
||||
let chunk_info = parse_chunk_info(bytes)?;
|
||||
|
||||
if chunk_info.total_size_compressed as usize > bytes.len() {
|
||||
/*
|
||||
if chunk_info.total_size_compressed as usize > bytes. {
|
||||
return Err(CzError::InvalidFormat {
|
||||
expected: chunk_info.total_size_compressed,
|
||||
got: bytes.len(),
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
let mut bitmap = decompress(&mut input, chunk_info).unwrap();
|
||||
let mut bitmap = decompress(bytes, &chunk_info).unwrap();
|
||||
let mut raw_bitmap = None;
|
||||
|
||||
// Apply the palette if it exists
|
||||
if let Some(pal) = &palette {
|
||||
apply_palette(&mut bitmap, pal);
|
||||
if let Some(raw) = &mut raw_bitmap {
|
||||
bitmap.clone_into(raw);
|
||||
}
|
||||
|
||||
bitmap = apply_palette(&mut bitmap, pal);
|
||||
}
|
||||
|
||||
let image = Self {
|
||||
header,
|
||||
bitmap,
|
||||
raw_bitmap,
|
||||
palette: palette.unwrap(),
|
||||
};
|
||||
|
||||
|
@ -52,16 +65,14 @@ impl CzImage for Cz1Image {
|
|||
}
|
||||
|
||||
fn save_as_png(&self, name: &str) -> Result<(), image::error::ImageError> {
|
||||
let img = image::RgbaImage::from_raw(
|
||||
image::save_buffer_with_format(
|
||||
name,
|
||||
&self.bitmap,
|
||||
self.header.width() as u32,
|
||||
self.header.height() as u32,
|
||||
self.bitmap.clone(),
|
||||
image::ExtendedColorType::Rgba8,
|
||||
ImageFormat::Png
|
||||
)
|
||||
.expect("Creating image failed");
|
||||
|
||||
img.save_with_format(name, ImageFormat::Png)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn header(&self) -> &Self::Header {
|
||||
|
@ -76,22 +87,17 @@ impl CzImage for Cz1Image {
|
|||
self.bitmap
|
||||
}
|
||||
|
||||
fn save_as_cz(&self) -> Result<(), CzError> {
|
||||
todo!()
|
||||
fn save_as_cz<T: Into<PathBuf>>(&self, path: T) -> Result<(), CzError> {
|
||||
let mut output_file = BufWriter::new(File::create(path.into())?);
|
||||
|
||||
output_file.write_all(&self.header.to_bytes()?)?;
|
||||
|
||||
output_file.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_bitmap(&mut self, bitmap: Vec<u8>, header: Self::Header) {
|
||||
fn set_bitmap(&mut self, bitmap: &[u8], header: &Self::Header) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_palette(input: &mut Vec<u8>, palette: &[Rgba<u8>]) {
|
||||
let mut output_map = Vec::new();
|
||||
|
||||
for byte in input.iter() {
|
||||
let color = palette[*byte as usize].0;
|
||||
output_map.extend_from_slice(&color);
|
||||
}
|
||||
|
||||
*input = output_map
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::io::Cursor;
|
||||
use std::{io::{self, Cursor, Read, Seek, SeekFrom}, path::PathBuf};
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use image::ImageFormat;
|
||||
|
||||
use crate::cz_common::{decompress, parse_chunk_info, CommonHeader, CzError, CzHeader, CzImage};
|
||||
use crate::compression::{decompress, parse_chunk_info};
|
||||
use crate::common::{CommonHeader, CzError, CzHeader, CzImage};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Cz3Header {
|
||||
|
@ -30,7 +30,7 @@ pub struct Cz3Header {
|
|||
}
|
||||
|
||||
impl CzHeader for Cz3Header {
|
||||
fn new(bytes: &mut Cursor<&[u8]>) -> Result<Self, CzError>
|
||||
fn new<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -40,7 +40,8 @@ impl CzHeader for Cz3Header {
|
|||
return Err(CzError::VersionMismatch);
|
||||
}
|
||||
|
||||
let _unknown = bytes.read_u48::<LittleEndian>()?;
|
||||
let mut unknown_1 = [0u8; 5];
|
||||
bytes.read_exact(&mut unknown_1)?;
|
||||
|
||||
let crop_width = bytes.read_u16::<LittleEndian>()?;
|
||||
let crop_height = bytes.read_u16::<LittleEndian>()?;
|
||||
|
@ -50,7 +51,7 @@ impl CzHeader for Cz3Header {
|
|||
|
||||
let mut offset_width = None;
|
||||
let mut offset_height = None;
|
||||
if common.header_length() > 28 {
|
||||
if common.length() > 28 {
|
||||
offset_width = Some(bytes.read_u16::<LittleEndian>()?);
|
||||
offset_height = Some(bytes.read_u16::<LittleEndian>()?);
|
||||
}
|
||||
|
@ -73,8 +74,8 @@ impl CzHeader for Cz3Header {
|
|||
self.common.version()
|
||||
}
|
||||
|
||||
fn header_length(&self) -> usize {
|
||||
self.common.header_length()
|
||||
fn length(&self) -> usize {
|
||||
self.common.length()
|
||||
}
|
||||
|
||||
fn width(&self) -> u16 {
|
||||
|
@ -88,6 +89,14 @@ impl CzHeader for Cz3Header {
|
|||
fn depth(&self) -> u16 {
|
||||
self.common.depth()
|
||||
}
|
||||
|
||||
fn color_block(&self) -> u8 {
|
||||
self.common.color_block()
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Result<Vec<u8>, io::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -99,14 +108,13 @@ pub struct Cz3Image {
|
|||
impl CzImage for Cz3Image {
|
||||
type Header = Cz3Header;
|
||||
|
||||
fn decode(bytes: &[u8]) -> Result<Self, CzError> {
|
||||
let mut input = Cursor::new(bytes);
|
||||
let header = Cz3Header::new(&mut input)?;
|
||||
input.set_position(header.header_length() as u64);
|
||||
fn decode<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError> {
|
||||
let header = Cz3Header::new(bytes)?;
|
||||
bytes.seek(SeekFrom::Start(header.length() as u64));
|
||||
|
||||
let block_info = parse_chunk_info(&mut input)?;
|
||||
let block_info = parse_chunk_info(bytes)?;
|
||||
|
||||
let mut bitmap = decompress(&mut input, block_info)?;
|
||||
let mut bitmap = decompress(bytes, &block_info)?;
|
||||
|
||||
let stride = (header.width() * (header.depth() / 8)) as usize;
|
||||
let third = ((header.height() + 2) / 3) as usize;
|
||||
|
@ -123,16 +131,13 @@ impl CzImage for Cz3Image {
|
|||
}
|
||||
|
||||
fn save_as_png(&self, name: &str) -> Result<(), image::error::ImageError> {
|
||||
let img = image::RgbaImage::from_raw(
|
||||
image::save_buffer(
|
||||
name,
|
||||
&self.bitmap,
|
||||
self.header.width() as u32,
|
||||
self.header.height() as u32,
|
||||
self.bitmap.clone(),
|
||||
image::ExtendedColorType::Rgba8,
|
||||
)
|
||||
.expect("Creating image failed");
|
||||
|
||||
img.save_with_format(name, ImageFormat::Png)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn header(&self) -> &Self::Header {
|
||||
|
@ -147,11 +152,11 @@ impl CzImage for Cz3Image {
|
|||
self.bitmap
|
||||
}
|
||||
|
||||
fn save_as_cz(&self) -> Result<(), CzError> {
|
||||
fn save_as_cz<T: Into<PathBuf>>(&self, path: T) -> Result<(), CzError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn set_bitmap(&mut self, bitmap: Vec<u8>, header: Self::Header) {
|
||||
fn set_bitmap(&mut self, bitmap: &[u8], header: &Self::Header) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
18
src/main.rs
18
src/main.rs
|
@ -1,19 +1,23 @@
|
|||
pub mod cz_common;
|
||||
pub mod common;
|
||||
pub mod compression;
|
||||
pub mod formats {
|
||||
pub mod cz0;
|
||||
pub mod cz1;
|
||||
pub mod cz3;
|
||||
}
|
||||
|
||||
use common::CzImage;
|
||||
pub use formats::cz0::Cz0Image;
|
||||
pub use formats::cz1::Cz1Image;
|
||||
pub use formats::cz3::Cz3Image;
|
||||
|
||||
// Generic tools
|
||||
use std::fs;
|
||||
|
||||
use crate::{cz_common::CzImage, formats::{cz0::Cz0Image, cz3::Cz3Image}};
|
||||
|
||||
fn main() {
|
||||
let input = fs::read("../test_files/Old_TestFiles/782.cz0").expect("Error, could not open image");
|
||||
let img_file = Cz0Image::decode(&input).unwrap();
|
||||
println!("{:#?}", img_file.header());
|
||||
let mut input = fs::File::open("../test_files/font_files/24-style1.cz1").unwrap();
|
||||
let img_file = Cz1Image::decode(&mut input).unwrap();
|
||||
|
||||
img_file.save_as_png("test.png").unwrap();
|
||||
img_file.save_as_cz("test1.cz1").unwrap();
|
||||
img_file.save_as_png("test1.png").unwrap();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue