mirror of
https://github.com/G2-Games/lbee-utils.git
synced 2025-04-19 07:12:55 -05:00
206 lines
5 KiB
Rust
206 lines
5 KiB
Rust
use std::{fs::File, io::{self, BufWriter, Cursor, Read, Seek, Write}, path::PathBuf};
|
|
|
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
|
|
|
use crate::common::{CommonHeader, CzError, CzHeader, CzImage};
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct Cz0Header {
|
|
/// Common CZ# header
|
|
pub common: CommonHeader,
|
|
|
|
/// Unknown bytes
|
|
unknown_1: [u8; 5],
|
|
|
|
/// Width of cropped image area
|
|
pub crop_width: u16,
|
|
|
|
/// Height of cropped image area
|
|
pub crop_height: u16,
|
|
|
|
/// Bounding box width
|
|
pub bounds_width: u16,
|
|
|
|
/// Bounding box height
|
|
pub bounds_height: u16,
|
|
|
|
/// Offset width
|
|
pub offset_width: Option<u16>,
|
|
|
|
/// Offset height
|
|
pub offset_height: Option<u16>,
|
|
|
|
unknown_2: Option<[u8; 4]>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Cz0Image {
|
|
header: Cz0Header,
|
|
bitmap: Vec<u8>,
|
|
}
|
|
|
|
impl CzHeader for Cz0Header {
|
|
fn new<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError>
|
|
where
|
|
Self: Sized,
|
|
{
|
|
let common = CommonHeader::new(bytes)?;
|
|
|
|
if common.version() != 0 {
|
|
return Err(CzError::VersionMismatch);
|
|
}
|
|
|
|
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>()?;
|
|
|
|
let bounds_width = bytes.read_u16::<LittleEndian>()?;
|
|
let bounds_height = bytes.read_u16::<LittleEndian>()?;
|
|
|
|
let mut offset_width = None;
|
|
let mut offset_height = None;
|
|
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,
|
|
|
|
bounds_width,
|
|
bounds_height,
|
|
|
|
offset_width,
|
|
offset_height,
|
|
|
|
unknown_2,
|
|
})
|
|
}
|
|
|
|
fn version(&self) -> u8 {
|
|
self.common.version()
|
|
}
|
|
|
|
fn length(&self) -> usize {
|
|
self.common.length()
|
|
}
|
|
|
|
fn width(&self) -> u16 {
|
|
self.common.width()
|
|
}
|
|
|
|
fn height(&self) -> u16 {
|
|
self.common.height()
|
|
}
|
|
|
|
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<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError> {
|
|
// Get the header from the 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![];
|
|
bytes.read_to_end(&mut bitmap)?;
|
|
|
|
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 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 {
|
|
&self.header
|
|
}
|
|
|
|
fn set_header(&mut self, header: Self::Header) {
|
|
self.header = header
|
|
}
|
|
|
|
fn into_bitmap(self) -> Vec<u8> {
|
|
self.bitmap
|
|
}
|
|
|
|
fn set_bitmap(&mut self, bitmap: &[u8], header: &Self::Header) {
|
|
self.bitmap = bitmap.to_vec();
|
|
|
|
self.header = *header;
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&[u8]> for Cz0Image {
|
|
type Error = CzError;
|
|
|
|
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
|
let mut input = Cursor::new(value);
|
|
|
|
// Get the header from the input
|
|
let header = Cz0Header::new(&mut input)?;
|
|
|
|
// Get the rest of the file, which is the bitmap
|
|
let mut bitmap = vec![];
|
|
input.read_to_end(&mut bitmap)?;
|
|
|
|
Ok(Self { header, bitmap })
|
|
}
|
|
}
|