mirror of
https://github.com/G2-Games/lbee-utils.git
synced 2025-04-19 15:22:53 -05:00
Fixed CZ1 implementation, added preliminary CZ3
This commit is contained in:
parent
d11272463b
commit
eea4e58e2b
5 changed files with 148 additions and 66 deletions
|
@ -1,5 +1,6 @@
|
||||||
//! Shared types and traits between CZ# files
|
//! Shared types and traits between CZ# files
|
||||||
|
|
||||||
|
use image::Rgba;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
@ -26,7 +27,7 @@ pub trait CzHeader {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The common first part of a header of a CZ# file
|
/// The common first part of a header of a CZ# file
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub(crate) struct CommonHeader {
|
pub(crate) struct CommonHeader {
|
||||||
/// Format version from the magic bytes, (eg. CZ3, CZ4)
|
/// Format version from the magic bytes, (eg. CZ3, CZ4)
|
||||||
pub version: u8,
|
pub version: u8,
|
||||||
|
@ -69,17 +70,17 @@ pub trait CzImage {
|
||||||
fn header(&self) -> &Self::Header;
|
fn header(&self) -> &Self::Header;
|
||||||
|
|
||||||
/// Get the raw underlying bitmap for an image
|
/// Get the raw underlying bitmap for an image
|
||||||
fn raw_bitmap(&self) -> &Vec<u8>;
|
fn into_bitmap(self) -> Vec<u8>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_colormap(input: &[u8], num_colors: usize) -> (Vec<[u8; 4]>, usize) {
|
pub fn parse_colormap(input: &[u8], num_colors: usize) -> Vec<Rgba<u8>> {
|
||||||
let mut colormap = Vec::with_capacity(num_colors);
|
let mut colormap = Vec::with_capacity(num_colors);
|
||||||
|
|
||||||
let input_iter = input.windows(4).step_by(4).take(num_colors);
|
let input_iter = input.windows(4).step_by(4).take(num_colors);
|
||||||
|
|
||||||
for color in input_iter {
|
for color in input_iter {
|
||||||
colormap.push(color.try_into().unwrap());
|
colormap.push(Rgba(color.try_into().unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
(colormap, num_colors * 4)
|
colormap
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,7 @@ impl CzImage for Cz0Image {
|
||||||
&self.header
|
&self.header
|
||||||
}
|
}
|
||||||
|
|
||||||
fn raw_bitmap(&self) -> &Vec<u8> {
|
fn into_bitmap(self) -> Vec<u8> {
|
||||||
&self.bitmap
|
self.bitmap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,28 @@
|
||||||
use std::io::Read;
|
use image::{ImageFormat, Rgba};
|
||||||
|
|
||||||
use crate::cz_common::{parse_colormap, CommonHeader, CzError, CzHeader, CzImage};
|
use crate::cz_common::{parse_colormap, CommonHeader, CzError, CzHeader, CzImage};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Cz1Header {
|
pub struct Cz1Header {
|
||||||
/// Common CZ# header
|
/// Common CZ# header
|
||||||
common: CommonHeader,
|
common: CommonHeader,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Cz1Image {
|
pub struct Cz1Image {
|
||||||
header: Cz1Header,
|
header: Cz1Header,
|
||||||
bitmap: Vec<u8>,
|
bitmap: Vec<u8>,
|
||||||
palette: Vec<[u8; 4]>,
|
palette: Option<Vec<Rgba<u8>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CzHeader for Cz1Header {
|
impl CzHeader for Cz1Header {
|
||||||
fn new(bytes: &[u8]) -> Result<Self, CzError> {
|
fn new(bytes: &[u8]) -> Result<Self, CzError> {
|
||||||
let common = CommonHeader::new(bytes);
|
let common = CommonHeader::new(bytes);
|
||||||
|
|
||||||
|
/*
|
||||||
if common.version != 1 {
|
if common.version != 1 {
|
||||||
return Err(CzError::VersionMismatch)
|
return Err(CzError::VersionMismatch)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
common,
|
common,
|
||||||
|
@ -60,25 +61,31 @@ impl CzImage for Cz1Image {
|
||||||
position += header.header_length();
|
position += header.header_length();
|
||||||
|
|
||||||
// The color palette
|
// The color palette
|
||||||
let (palette, palette_length) = parse_colormap(&bytes[position..], 0x100);
|
let mut palette = None;
|
||||||
position += palette_length;
|
if header.common.depth == 8 {
|
||||||
|
let temp_palette = parse_colormap(&bytes[position..], 0x100);
|
||||||
dbg!(&bytes[position..position + 4]);
|
position += temp_palette.len() * 4;
|
||||||
|
palette = Some(temp_palette);
|
||||||
|
}
|
||||||
|
|
||||||
let parts_count = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap());
|
let parts_count = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap());
|
||||||
position += 4;
|
position += 4;
|
||||||
dbg!(parts_count);
|
dbg!(parts_count);
|
||||||
let mut part_sizes = vec![0; parts_count as usize];
|
let mut part_sizes = vec![0; parts_count as usize];
|
||||||
let mut total_size = 0;
|
let mut total_size = 0;
|
||||||
|
let mut decompressed_size = 0;
|
||||||
|
|
||||||
for size in &mut part_sizes {
|
for size in &mut part_sizes {
|
||||||
let part_size = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap()) * 2;
|
let part_size = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap()) * 2;
|
||||||
*size = part_size;
|
*size = part_size;
|
||||||
total_size += part_size;
|
total_size += part_size;
|
||||||
|
position += 4;
|
||||||
|
|
||||||
dbg!(part_size);
|
let orig_size = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap()) * 4;
|
||||||
|
decompressed_size += orig_size;
|
||||||
|
position += 4;
|
||||||
|
|
||||||
position += 8;
|
dbg!(part_size, orig_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if position + total_size as usize > bytes.len() {
|
if position + total_size as usize > bytes.len() {
|
||||||
|
@ -86,13 +93,7 @@ impl CzImage for Cz1Image {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut m_dst = 0;
|
let mut m_dst = 0;
|
||||||
let bitmap = vec![0; 4882176];
|
let mut bitmap = vec![0; decompressed_size as usize];
|
||||||
|
|
||||||
let mut image = Self {
|
|
||||||
header,
|
|
||||||
bitmap,
|
|
||||||
palette
|
|
||||||
};
|
|
||||||
|
|
||||||
for size in part_sizes {
|
for size in part_sizes {
|
||||||
let part = &bytes[position..position + size as usize];
|
let part = &bytes[position..position + size as usize];
|
||||||
|
@ -102,68 +103,87 @@ impl CzImage for Cz1Image {
|
||||||
let ctl = part[j + 1];
|
let ctl = part[j + 1];
|
||||||
|
|
||||||
if ctl == 0 {
|
if ctl == 0 {
|
||||||
image.bitmap[m_dst] = part[j];
|
bitmap[m_dst] = part[j];
|
||||||
m_dst += 1;
|
m_dst += 1;
|
||||||
} else {
|
} else {
|
||||||
m_dst += image.copy_range(part, get_offset(part, j), m_dst);
|
m_dst += copy_range(&mut bitmap, part, get_offset(part, j), m_dst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(temp_palette) = &palette {
|
||||||
|
apply_palette(&mut bitmap, &temp_palette);
|
||||||
|
}
|
||||||
|
|
||||||
|
let image = Self {
|
||||||
|
header,
|
||||||
|
bitmap,
|
||||||
|
palette,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(image)
|
Ok(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_as_png(&self, name: &str) {
|
fn save_as_png(&self, name: &str) {
|
||||||
image::save_buffer(
|
let img = image::RgbaImage::from_raw(
|
||||||
name,
|
|
||||||
&self.bitmap,
|
|
||||||
self.header.common.width as u32,
|
self.header.common.width as u32,
|
||||||
self.header.common.height as u32,
|
self.header.common.height as u32,
|
||||||
image::ExtendedColorType::Rgba8
|
self.bitmap.clone()
|
||||||
).unwrap()
|
).unwrap();
|
||||||
|
|
||||||
|
img.save_with_format(name, ImageFormat::Png).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header(&self) -> &Self::Header {
|
fn header(&self) -> &Self::Header {
|
||||||
&self.header
|
&self.header
|
||||||
}
|
}
|
||||||
|
|
||||||
fn raw_bitmap(&self) -> &Vec<u8> {
|
fn into_bitmap(self) -> Vec<u8> {
|
||||||
&self.bitmap
|
self.bitmap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_offset(input: &[u8], src: usize) -> usize {
|
fn get_offset(input: &[u8], src: usize) -> usize {
|
||||||
(((input[src] as usize) | (input[src+1] as usize) << 8) - 0x101) * 2
|
(((input[src] as usize) | (input[src + 1] as usize) << 8) - 0x101) * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cz1Image {
|
fn apply_palette(input: &mut Vec<u8>, palette: &Vec<Rgba<u8>>) {
|
||||||
fn copy_range(&mut self, input: &[u8], src: usize, dst: usize) -> usize {
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_range(bitmap: &mut Vec<u8>, input: &[u8], src: usize, dst: usize) -> usize {
|
||||||
let mut dst = dst;
|
let mut dst = dst;
|
||||||
let start_pos = dst;
|
let start_pos = dst;
|
||||||
|
|
||||||
if input[src + 1] == 0 {
|
if input[src + 1] == 0 {
|
||||||
self.bitmap[dst] = input[src];
|
bitmap[dst] = input[src];
|
||||||
dst += 1;
|
dst += 1;
|
||||||
} else if get_offset(input, src) == src {
|
} else if get_offset(input, src) == src {
|
||||||
self.bitmap[dst] = 0;
|
bitmap[dst] = 0;
|
||||||
dst += 1;
|
dst += 1;
|
||||||
} else {
|
} else {
|
||||||
dst += self.copy_range(input, get_offset(input, src), dst);
|
dst += copy_range(bitmap, input, get_offset(input, src), dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
if input[src + 3] == 0 {
|
if input[src + 3] == 0 {
|
||||||
self.bitmap[dst] = input[src + 2];
|
bitmap[dst] = input[src + 2];
|
||||||
dst += 1;
|
dst += 1;
|
||||||
} else if get_offset(input, src + 2) == src {
|
} else if get_offset(input, src + 2) == src {
|
||||||
self.bitmap[dst] = self.bitmap[start_pos];
|
bitmap[dst] = bitmap[start_pos];
|
||||||
dst += 1;
|
dst += 1;
|
||||||
} else {
|
} else {
|
||||||
self.bitmap[dst] = copy_one(input, get_offset(input, src + 2));
|
bitmap[dst] = copy_one(input, get_offset(input, src + 2));
|
||||||
dst += 1;
|
dst += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
dst - start_pos
|
dst - start_pos
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_one(input: &[u8], src: usize) -> u8 {
|
fn copy_one(input: &[u8], src: usize) -> u8 {
|
||||||
|
|
60
src/formats/cz3.rs
Normal file
60
src/formats/cz3.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
use image::ImageFormat;
|
||||||
|
|
||||||
|
use crate::cz_common::{CzError, CzHeader, CzImage};
|
||||||
|
|
||||||
|
use super::cz1::Cz1Header;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Cz3Image {
|
||||||
|
header: Cz1Header,
|
||||||
|
bitmap: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CzImage for Cz3Image {
|
||||||
|
type Header = Cz1Header;
|
||||||
|
|
||||||
|
fn decode(bytes: &[u8]) -> Result<Self, CzError> {
|
||||||
|
let cz1_image = crate::formats::cz1::Cz1Image::decode(bytes)?;
|
||||||
|
|
||||||
|
let header = cz1_image.header().clone();
|
||||||
|
let mut bitmap = cz1_image.into_bitmap();
|
||||||
|
|
||||||
|
dbg!(bitmap.len());
|
||||||
|
|
||||||
|
let stride = (header.width() * (header.depth() / 8)) as usize;
|
||||||
|
let third = ((header.height() + 2) / 3) as usize;
|
||||||
|
for y in 0..header.height() as usize {
|
||||||
|
let dst = y * stride;
|
||||||
|
if y % third != 0 {
|
||||||
|
for x in 0..stride {
|
||||||
|
bitmap[dst + x] += bitmap[dst + x - stride];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg!(bitmap.len());
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
header,
|
||||||
|
bitmap
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_as_png(&self, name: &str) {
|
||||||
|
let img = image::RgbaImage::from_raw(
|
||||||
|
self.header.width() as u32,
|
||||||
|
self.header.height() as u32,
|
||||||
|
self.bitmap.clone()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
img.save_with_format(name, ImageFormat::Png).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header(&self) -> &Self::Header {
|
||||||
|
&self.header
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_bitmap(self) -> Vec<u8> {
|
||||||
|
self.bitmap
|
||||||
|
}
|
||||||
|
}
|
11
src/main.rs
11
src/main.rs
|
@ -2,17 +2,18 @@ pub mod cz_common;
|
||||||
pub mod formats{
|
pub mod formats{
|
||||||
pub mod cz0;
|
pub mod cz0;
|
||||||
pub mod cz1;
|
pub mod cz1;
|
||||||
|
pub mod cz3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic tools
|
// Generic tools
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use crate::{cz_common::CzImage, formats::cz1::Cz1Image};
|
use crate::{cz_common::CzImage, formats::cz3::Cz3Image};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let input = fs::read("../test_files/x5a3bvy.cz1").expect("Error, could not open image");
|
let input = fs::read("../test_files/Old_TestFiles/129.CZ3").expect("Error, could not open image");
|
||||||
let cz1_file = Cz1Image::decode(&input).unwrap();
|
let cz3_file = Cz3Image::decode(&input).unwrap();
|
||||||
println!("{:#?}", cz1_file.header());
|
println!("{:#?}", cz3_file.header());
|
||||||
|
|
||||||
cz1_file.save_as_png("test.png")
|
cz3_file.save_as_png("test.png")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue