mirror of
https://github.com/G2-Games/lbee-utils.git
synced 2025-05-01 13:12:53 -05:00
Fixed bit depth saving and opening, added documentation
This commit is contained in:
parent
233219d70b
commit
c33c54beeb
10 changed files with 324 additions and 261 deletions
|
@ -11,3 +11,4 @@ A encoder/decoder for CZ# image files used in
|
|||
byteorder = "1.5.0"
|
||||
thiserror = "1.0.59"
|
||||
image = {version = "0.25.1", default-features = false}
|
||||
quantizr = "1.4.2"
|
||||
|
|
|
@ -7,6 +7,7 @@ use std::{
|
|||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use image::Rgba;
|
||||
use quantizr::Image;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -99,9 +100,7 @@ pub trait CzHeader {
|
|||
/// The bit depth of the image (BPP)
|
||||
fn depth(&self) -> u16;
|
||||
|
||||
fn set_depth(&mut self) {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_depth(&mut self, depth: u16);
|
||||
|
||||
/// An unknown value?
|
||||
fn color_block(&self) -> u8;
|
||||
|
@ -218,6 +217,10 @@ impl CzHeader for CommonHeader {
|
|||
self.depth
|
||||
}
|
||||
|
||||
fn set_depth(&mut self, depth: u16) {
|
||||
self.depth = depth
|
||||
}
|
||||
|
||||
fn color_block(&self) -> u8 {
|
||||
self.unknown
|
||||
}
|
||||
|
@ -384,6 +387,8 @@ pub fn get_palette<T: Seek + ReadBytesExt + Read>(
|
|||
Ok(colormap)
|
||||
}
|
||||
|
||||
/// Take a bitmap of indicies, and map a given palette to it, returning a new
|
||||
/// RGBA bitmap
|
||||
pub fn apply_palette(
|
||||
input: &[u8],
|
||||
palette: &[Rgba<u8>]
|
||||
|
@ -395,7 +400,6 @@ pub fn apply_palette(
|
|||
if let Some(color) = color {
|
||||
output_map.extend_from_slice(&color.0);
|
||||
} else {
|
||||
dbg!(byte);
|
||||
return Err(CzError::PaletteError);
|
||||
}
|
||||
}
|
||||
|
@ -426,6 +430,33 @@ pub fn rgba_to_indexed(
|
|||
Ok(output_map)
|
||||
}
|
||||
|
||||
pub fn indexed_gen_palette(
|
||||
input: &[u8],
|
||||
header: &CommonHeader,
|
||||
) -> Result<(Vec<u8>, Vec<image::Rgba<u8>>), CzError> {
|
||||
|
||||
let image = Image::new(
|
||||
input,
|
||||
header.width() as usize,
|
||||
header.height() as usize
|
||||
).unwrap();
|
||||
|
||||
let mut opts = quantizr::Options::default();
|
||||
opts.set_max_colors(1 << header.depth()).unwrap();
|
||||
|
||||
let mut result = quantizr::QuantizeResult::quantize(&image, &opts);
|
||||
result.set_dithering_level(0.5).unwrap();
|
||||
|
||||
let mut indicies = vec![0u8; header.width() as usize * header.height() as usize];
|
||||
result.remap_image(&image, indicies.as_mut_slice()).unwrap();
|
||||
|
||||
let palette = result.get_palette();
|
||||
|
||||
let gen_palette = palette.entries.as_slice().iter().map(|c| Rgba([c.r, c.g, c.b, c.a])).collect();
|
||||
|
||||
Ok((indicies, gen_palette))
|
||||
}
|
||||
|
||||
pub fn default_palette() -> Vec<Rgba<u8>> {
|
||||
let mut colormap = Vec::new();
|
||||
|
||||
|
|
|
@ -8,12 +8,16 @@ use std::{
|
|||
|
||||
use crate::{
|
||||
common::{
|
||||
apply_palette, get_palette, rgba_to_indexed, CommonHeader, CzError, CzHeader, CzVersion,
|
||||
ExtendedHeader,
|
||||
apply_palette, get_palette, indexed_gen_palette,
|
||||
rgba_to_indexed, CommonHeader, CzError, CzHeader,
|
||||
CzVersion, ExtendedHeader
|
||||
},
|
||||
formats::{cz0, cz1, cz2, cz3, cz4},
|
||||
};
|
||||
|
||||
/// A CZ# interface which abstracts the CZ# generic file interface for
|
||||
/// convenience.
|
||||
#[derive(Debug)]
|
||||
pub struct DynamicCz {
|
||||
header_common: CommonHeader,
|
||||
header_extended: Option<ExtendedHeader>,
|
||||
|
@ -22,67 +26,19 @@ pub struct DynamicCz {
|
|||
}
|
||||
|
||||
impl DynamicCz {
|
||||
/// Open a CZ# file from a path
|
||||
pub fn open<P: ?Sized + AsRef<Path>>(path: &P) -> Result<Self, CzError> {
|
||||
let mut img_file = BufReader::new(std::fs::File::open(path)?);
|
||||
|
||||
Self::decode(&mut img_file)
|
||||
}
|
||||
|
||||
pub fn save_as_png<P: ?Sized + AsRef<Path>>(
|
||||
&self,
|
||||
path: &P,
|
||||
) -> Result<(), image::error::EncodingError> {
|
||||
let image = image::RgbaImage::from_raw(
|
||||
self.header_common.width() as u32,
|
||||
self.header_common.height() as u32,
|
||||
self.bitmap.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
image
|
||||
.save_with_format(path, image::ImageFormat::Png)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn from_raw(
|
||||
version: CzVersion,
|
||||
width: u16,
|
||||
height: u16,
|
||||
bitmap: Vec<u8>
|
||||
) -> Self {
|
||||
let header_common = CommonHeader::new(version, width, height);
|
||||
|
||||
Self {
|
||||
header_common,
|
||||
header_extended: None,
|
||||
palette: None,
|
||||
bitmap,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_header(mut self, header: CommonHeader) -> Self {
|
||||
self.header_common = header;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_extended_header(mut self, ext_header: ExtendedHeader) -> Self {
|
||||
if ext_header.offset_width.is_some() {
|
||||
self.header_common.set_length(36)
|
||||
} else {
|
||||
self.header_common.set_length(28)
|
||||
}
|
||||
|
||||
self.header_extended = Some(ext_header);
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl DynamicCz {
|
||||
pub fn decode<T: Seek + ReadBytesExt + Read>(
|
||||
/// Decode a CZ# file from anything which implements [`Read`] and [`Seek`]
|
||||
///
|
||||
/// The input must begin with the
|
||||
/// [magic bytes](https://en.wikipedia.org/wiki/File_format#Magic_number)
|
||||
/// of the file
|
||||
fn decode<T: Seek + ReadBytesExt + Read>(
|
||||
input: &mut T
|
||||
) -> Result<Self, CzError> {
|
||||
// Get the header common to all CZ images
|
||||
|
@ -117,8 +73,26 @@ impl DynamicCz {
|
|||
return Err(CzError::Corrupt);
|
||||
}
|
||||
|
||||
match header_common.depth() {
|
||||
4 => {
|
||||
todo!()
|
||||
}
|
||||
8 => {
|
||||
if let Some(palette) = &palette {
|
||||
bitmap = apply_palette(&bitmap, palette)?;
|
||||
} else {
|
||||
return Err(CzError::PaletteError)
|
||||
}
|
||||
},
|
||||
24 => {
|
||||
bitmap = bitmap
|
||||
.windows(3)
|
||||
.step_by(3)
|
||||
.flat_map(|p| [p[0], p[1], p[2], 0xFF])
|
||||
.collect();
|
||||
}
|
||||
32 => (),
|
||||
_ => panic!()
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
|
@ -129,6 +103,9 @@ impl DynamicCz {
|
|||
})
|
||||
}
|
||||
|
||||
/// Save the `DynamicCz` as a CZ# file. The format saved in is determined
|
||||
/// from the format in the header. Check [`CommonHeader::set_version()`]
|
||||
/// to change the CZ# version.
|
||||
pub fn save_as_cz<T: Into<std::path::PathBuf>>(
|
||||
&self,
|
||||
path: T
|
||||
|
@ -142,6 +119,11 @@ impl DynamicCz {
|
|||
}
|
||||
|
||||
let output_bitmap;
|
||||
match self.header_common.depth() {
|
||||
4 => {
|
||||
todo!()
|
||||
}
|
||||
8 => {
|
||||
match &self.palette {
|
||||
Some(pal) if self.header_common.depth() <= 8 => {
|
||||
output_bitmap = rgba_to_indexed(self.bitmap(), pal)?;
|
||||
|
@ -149,9 +131,35 @@ impl DynamicCz {
|
|||
for rgba in pal {
|
||||
out_file.write_all(&rgba.0)?;
|
||||
}
|
||||
},
|
||||
// Generate a palette if there is none
|
||||
None if self.header_common.depth() <= 8 => {
|
||||
let result = indexed_gen_palette(
|
||||
self.bitmap(),
|
||||
self.header()
|
||||
)?;
|
||||
|
||||
output_bitmap = result.0;
|
||||
let palette = result.1;
|
||||
|
||||
for rgba in palette {
|
||||
out_file.write_all(&rgba.0)?;
|
||||
}
|
||||
},
|
||||
_ => output_bitmap = self.bitmap().clone(),
|
||||
}
|
||||
},
|
||||
24 => {
|
||||
output_bitmap = self.bitmap
|
||||
.windows(4)
|
||||
.step_by(4)
|
||||
.flat_map(|p| &p[0..3])
|
||||
.copied()
|
||||
.collect();
|
||||
},
|
||||
32 => output_bitmap = self.bitmap.clone(),
|
||||
_ => return Err(CzError::Corrupt)
|
||||
}
|
||||
|
||||
match self.header_common.version() {
|
||||
CzVersion::CZ0 => cz0::encode(&mut out_file, &output_bitmap)?,
|
||||
|
@ -165,6 +173,92 @@ impl DynamicCz {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Save the CZ# image as a lossless PNG file.
|
||||
///
|
||||
/// Internally, the [`DynamicCz`] struct operates on 32-bit RGBA values,
|
||||
/// which is the highest encountered in CZ# files, therefore saving them
|
||||
/// as a PNG of the same or better quality is lossless.
|
||||
pub fn save_as_png<P: ?Sized + AsRef<Path>>(
|
||||
&self,
|
||||
path: &P,
|
||||
) -> Result<(), image::error::EncodingError> {
|
||||
let image = image::RgbaImage::from_raw(
|
||||
self.header_common.width() as u32,
|
||||
self.header_common.height() as u32,
|
||||
self.bitmap.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
image
|
||||
.save_with_format(path, image::ImageFormat::Png)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a CZ# image from RGBA bytes. The bytes *must* be RGBA, as it is
|
||||
/// used internally for operations
|
||||
pub fn from_raw(
|
||||
version: CzVersion,
|
||||
depth: u16,
|
||||
width: u16,
|
||||
height: u16,
|
||||
bitmap: Vec<u8>
|
||||
) -> Self {
|
||||
let mut header_common = CommonHeader::new(
|
||||
version,
|
||||
width,
|
||||
height
|
||||
);
|
||||
header_common.set_depth(depth);
|
||||
|
||||
Self {
|
||||
header_common,
|
||||
header_extended: None,
|
||||
palette: None,
|
||||
bitmap,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a specific header for the image, this basica
|
||||
pub fn with_header(mut self, header: CommonHeader) -> Self {
|
||||
self.header_common = header;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an [`ExtendedHeader`] to the image. This header controls things like
|
||||
/// cropping and offsets in the game engine
|
||||
pub fn with_extended_header(mut self, ext_header: ExtendedHeader) -> Self {
|
||||
if ext_header.offset_width.is_some() {
|
||||
self.header_common.set_length(36)
|
||||
} else {
|
||||
self.header_common.set_length(28)
|
||||
}
|
||||
|
||||
self.header_extended = Some(ext_header);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Retrieve a reference to the palette if it exists, otherwise [`None`]
|
||||
/// is returned
|
||||
pub fn palette(&self) -> &Option<Vec<Rgba<u8>>> {
|
||||
&self.palette
|
||||
}
|
||||
|
||||
/// Retrieve a mutable reference to the palette if it exists, otherwise
|
||||
/// [`None`] is returned
|
||||
pub fn palette_mut(&mut self) -> &mut Option<Vec<Rgba<u8>>> {
|
||||
&mut self.palette
|
||||
}
|
||||
|
||||
/// Remove the image palette, which forces palette regeneration on save
|
||||
/// for bit depths 8 or less
|
||||
pub fn remove_palette(&mut self) {
|
||||
*self.palette_mut() = None
|
||||
}
|
||||
|
||||
pub fn header(&self) -> &CommonHeader {
|
||||
&self.header_common
|
||||
}
|
||||
|
@ -185,16 +279,7 @@ impl DynamicCz {
|
|||
self.bitmap
|
||||
}
|
||||
|
||||
pub fn set_bitmap(&mut self, bitmap: Vec<u8>, width: u16, height: u16) -> Result<(), CzError> {
|
||||
if bitmap.len() != width as usize * height as usize {
|
||||
return Err(CzError::BitmapFormat);
|
||||
}
|
||||
|
||||
self.bitmap = bitmap;
|
||||
|
||||
self.header_mut().set_width(width);
|
||||
self.header_mut().set_height(height);
|
||||
|
||||
Ok(())
|
||||
pub fn set_bitmap(&mut self, bitmap: Vec<u8>) {
|
||||
self.bitmap = bitmap
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,16 +63,21 @@ fn line_diff<T: CzHeader>(header: &T, data: &[u8]) -> Vec<u8> {
|
|||
if pixel_byte_count == 4 {
|
||||
output_buf[i..i + line_byte_count].copy_from_slice(&curr_line);
|
||||
} else if pixel_byte_count == 3 {
|
||||
for x in 0..line_byte_count {
|
||||
let loc = ((y * width) as usize + x) * 4;
|
||||
for x in (0..line_byte_count).step_by(3) {
|
||||
let loc = (y * 3 * width) as usize + x;
|
||||
|
||||
output_buf[loc..loc + 4].copy_from_slice(&[
|
||||
output_buf[loc..loc + 3].copy_from_slice(&[
|
||||
curr_line[x],
|
||||
curr_line[x + 1],
|
||||
curr_line[x + 2],
|
||||
0xFF,
|
||||
])
|
||||
}
|
||||
} else if pixel_byte_count == 1 {
|
||||
for x in 0..line_byte_count {
|
||||
let loc = (y * width) as usize + x;
|
||||
|
||||
output_buf[loc] = curr_line[x];
|
||||
}
|
||||
}
|
||||
|
||||
i += line_byte_count;
|
||||
|
|
|
@ -4,14 +4,17 @@ mod compression;
|
|||
pub mod common;
|
||||
pub mod dynamic;
|
||||
|
||||
pub mod formats {
|
||||
pub mod cz0;
|
||||
pub mod cz1;
|
||||
pub mod cz2;
|
||||
pub mod cz3;
|
||||
pub mod cz4;
|
||||
mod formats {
|
||||
pub(crate) mod cz0;
|
||||
pub(crate) mod cz1;
|
||||
pub(crate) mod cz2;
|
||||
pub(crate) mod cz3;
|
||||
pub(crate) mod cz4;
|
||||
}
|
||||
|
||||
#[doc(inline)]
|
||||
pub use dynamic::DynamicCz;
|
||||
|
||||
/*
|
||||
#[doc(inline)]
|
||||
pub use formats::cz0::Cz0Image;
|
||||
|
|
|
@ -5,6 +5,8 @@ edition = "2021"
|
|||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bimap = "0.6.3"
|
||||
byteorder = "1.5.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
use std::{fs, io, path::Path};
|
||||
|
||||
use bimap::BiMap;
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FontInfo {
|
||||
font_size: u16,
|
||||
font_box: u16,
|
||||
character_count: u16,
|
||||
character_count2: u16,
|
||||
position_map: BiMap<char, u16>,
|
||||
draw_sizes: Vec<DrawSize>,
|
||||
char_sizes: Vec<CharSize>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DrawSize {
|
||||
x: u8, // x offset
|
||||
w: u8, // width
|
||||
y: u8, // y offset
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CharSize {
|
||||
x: u8, // x offset
|
||||
w: u8, // width
|
||||
}
|
||||
|
||||
fn parse_info<P: ?Sized + AsRef<Path>>(
|
||||
path: &P
|
||||
) -> Result<FontInfo, io::Error> {
|
||||
let mut file = fs::File::open(path).unwrap();
|
||||
|
||||
let font_size = file.read_u16::<LittleEndian>().unwrap();
|
||||
let font_box = file.read_u16::<LittleEndian>().unwrap();
|
||||
|
||||
let character_count = file.read_u16::<LittleEndian>().unwrap();
|
||||
let character_count2 = file.read_u16::<LittleEndian>().unwrap();
|
||||
|
||||
// If the character count is 100, the other character count is correct?
|
||||
let real_char_count = if character_count == 100 {
|
||||
character_count2
|
||||
} else {
|
||||
character_count
|
||||
};
|
||||
|
||||
let mut draw_sizes = Vec::new();
|
||||
for _ in 0..real_char_count {
|
||||
draw_sizes.push(DrawSize {
|
||||
x: file.read_u8().unwrap(),
|
||||
w: file.read_u8().unwrap(),
|
||||
y: file.read_u8().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
let mut utf16_index = BiMap::new();
|
||||
utf16_index.insert(' ', 0);
|
||||
let mut list = vec![];
|
||||
for index in 0..65535 {
|
||||
let map_position = file.read_u16::<LittleEndian>().unwrap();
|
||||
if map_position == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
list.push((char::from_u32(index).unwrap(), map_position));
|
||||
utf16_index.insert(char::from_u32(index).unwrap(), map_position);
|
||||
}
|
||||
dbg!(utf16_index.get_by_left(&'!'));
|
||||
|
||||
let mut char_sizes = vec![];
|
||||
for _ in 0..65535 {
|
||||
char_sizes.push(CharSize {
|
||||
x: file.read_u8().unwrap(),
|
||||
w: file.read_u8().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
Ok(FontInfo {
|
||||
font_size,
|
||||
font_box,
|
||||
character_count,
|
||||
character_count2,
|
||||
position_map: utf16_index,
|
||||
draw_sizes,
|
||||
char_sizes,
|
||||
})
|
||||
}
|
|
@ -6,10 +6,7 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bimap = "0.6.3"
|
||||
byteorder = "1.5.0"
|
||||
cz = { path = "../cz" }
|
||||
encoding_rs = "0.8.34"
|
||||
fontdue = { version = "0.8.0", features = ["parallel"] }
|
||||
image = "0.25.1"
|
||||
walkdir = "2.5.0"
|
||||
|
|
|
@ -1,175 +1,26 @@
|
|||
mod font_generation;
|
||||
|
||||
use std::{fs::{self, File}, io::{self, Write}, path::Path};
|
||||
use bimap::BiMap;
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use cz::{common::default_palette, dynamic::DynamicCz};
|
||||
use font_generation::load_font;
|
||||
use image::{ColorType, DynamicImage, GenericImage, GenericImageView};
|
||||
use cz::{
|
||||
common::{CzHeader, CzVersion},
|
||||
dynamic::DynamicCz
|
||||
};
|
||||
|
||||
fn main() {
|
||||
DynamicCz::open("24-style1.cz1").unwrap().save_as_png("24-style1.png").unwrap();
|
||||
// Open the desired PNG
|
||||
let new_bitmap = image::open("CGGALLERY_CH01_003.png")
|
||||
.unwrap()
|
||||
.to_rgba8();
|
||||
|
||||
parse_info("info24-lbee").unwrap();
|
||||
let mut gallery_cz = DynamicCz::open("CGGALLERY_CH01_003").unwrap();
|
||||
|
||||
let font = load_font("RodinNTLG Pro M.otf").unwrap();
|
||||
let fallback = load_font("NotoSans-Regular.ttf").unwrap();
|
||||
gallery_cz.set_bitmap(new_bitmap.into_vec());
|
||||
gallery_cz.header_mut().set_depth(32);
|
||||
gallery_cz.header_mut().set_version(CzVersion::CZ0);
|
||||
gallery_cz.save_as_cz("CGGALLERY_CH01_003-MODIFIED").unwrap();
|
||||
|
||||
let characters = fs::read_to_string("character_list").unwrap();
|
||||
// Open that same CZ3 again to test decoding
|
||||
let cz_image_test = DynamicCz::open("CGGALLERY_CH01_003-MODIFIED").unwrap();
|
||||
|
||||
const FONT_SIZE: f32 = 24.0;
|
||||
const BASELINE: f32 = FONT_BOX * 0.84;
|
||||
const FONT_BOX: f32 = 25.0;
|
||||
|
||||
let mut font_grid = DynamicImage::new(2504, 1800, ColorType::L8);
|
||||
|
||||
let mut x_offset = 0;
|
||||
let mut y_offset = 0;
|
||||
for (_i, character) in characters.chars().enumerate() {
|
||||
if character == '\n' {
|
||||
continue
|
||||
}
|
||||
|
||||
let (metrics, char_bitmap) = match font.has_glyph(character) {
|
||||
true => font.rasterize(character, FONT_SIZE),
|
||||
false => fallback.rasterize(character, FONT_SIZE),
|
||||
};
|
||||
|
||||
let char_image: DynamicImage = image::GrayImage::from_raw(
|
||||
metrics.width as u32,
|
||||
metrics.height as u32,
|
||||
char_bitmap
|
||||
).unwrap().into();
|
||||
|
||||
let char_x_offset = metrics.xmin as i32;
|
||||
let char_y_offset = ((BASELINE as isize - metrics.height as isize) - metrics.ymin as isize) as i32;
|
||||
|
||||
for y in 0..char_image.height() as i32 {
|
||||
for x in 0..char_image.width() as i32 {
|
||||
let x_pos = x + x_offset + char_x_offset;
|
||||
let y_pos = y + y_offset + char_y_offset;
|
||||
|
||||
if !font_grid.in_bounds(
|
||||
x_pos as u32,
|
||||
y_pos as u32
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
if x_pos > x_offset + FONT_BOX as i32 || x_pos < x_offset {
|
||||
continue
|
||||
} else if y_pos > y_offset + FONT_BOX as i32 || y_pos < y_offset {
|
||||
continue
|
||||
}
|
||||
|
||||
font_grid.put_pixel(
|
||||
x_pos as u32,
|
||||
y_pos as u32,
|
||||
char_image.get_pixel(x as u32, y as u32)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
x_offset += FONT_BOX as i32;
|
||||
if x_offset + FONT_BOX as i32 >= font_grid.width() as i32 {
|
||||
x_offset = 0;
|
||||
y_offset += FONT_BOX as i32;
|
||||
}
|
||||
}
|
||||
|
||||
let result_image = cz::common::apply_palette(font_grid.as_bytes(), &default_palette()).unwrap();
|
||||
let cz1_font = DynamicCz::from_raw(
|
||||
cz::common::CzVersion::CZ1,
|
||||
font_grid.width() as u16,
|
||||
font_grid.height() as u16,
|
||||
result_image
|
||||
);
|
||||
|
||||
cz1_font.save_as_cz("replacement_24.cz1").unwrap();
|
||||
|
||||
cz1_font.save_as_png("grid.png").unwrap();
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FontInfo {
|
||||
font_size: u16,
|
||||
font_box: u16,
|
||||
character_count: u16,
|
||||
character_count2: u16,
|
||||
position_map: BiMap<char, u16>,
|
||||
draw_sizes: Vec<DrawSize>,
|
||||
char_sizes: Vec<CharSize>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DrawSize {
|
||||
x: u8, // x offset
|
||||
w: u8, // width
|
||||
y: u8, // y offset
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CharSize {
|
||||
x: u8, // x offset
|
||||
w: u8, // width
|
||||
}
|
||||
|
||||
fn parse_info<P: ?Sized + AsRef<Path>>(
|
||||
path: &P
|
||||
) -> Result<FontInfo, io::Error> {
|
||||
let mut file = fs::File::open(path).unwrap();
|
||||
|
||||
let font_size = file.read_u16::<LittleEndian>().unwrap();
|
||||
let font_box = file.read_u16::<LittleEndian>().unwrap();
|
||||
|
||||
let character_count = file.read_u16::<LittleEndian>().unwrap();
|
||||
let character_count2 = file.read_u16::<LittleEndian>().unwrap();
|
||||
|
||||
// If the character count is 100, the other character count is correct?
|
||||
let real_char_count = if character_count == 100 {
|
||||
character_count2
|
||||
} else {
|
||||
character_count
|
||||
};
|
||||
|
||||
let mut draw_sizes = Vec::new();
|
||||
for _ in 0..real_char_count {
|
||||
draw_sizes.push(DrawSize {
|
||||
x: file.read_u8().unwrap(),
|
||||
w: file.read_u8().unwrap(),
|
||||
y: file.read_u8().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
let mut utf16_index = BiMap::new();
|
||||
utf16_index.insert(' ', 0);
|
||||
let mut list = vec![];
|
||||
for index in 0..65535 {
|
||||
let map_position = file.read_u16::<LittleEndian>().unwrap();
|
||||
if map_position == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
list.push((char::from_u32(index).unwrap(), map_position));
|
||||
utf16_index.insert(char::from_u32(index).unwrap(), map_position);
|
||||
}
|
||||
dbg!(utf16_index.get_by_left(&'!'));
|
||||
|
||||
let mut char_sizes = vec![];
|
||||
for _ in 0..65535 {
|
||||
char_sizes.push(CharSize {
|
||||
x: file.read_u8().unwrap(),
|
||||
w: file.read_u8().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
Ok(FontInfo {
|
||||
font_size,
|
||||
font_box,
|
||||
character_count,
|
||||
character_count2,
|
||||
position_map: utf16_index,
|
||||
draw_sizes,
|
||||
char_sizes,
|
||||
})
|
||||
// Save the newly decoded CZ3 as another PNG as a test
|
||||
cz_image_test.save_as_png("CGGALLERY_CH01_003-MODIFIED.png").unwrap();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue