Preliminary implementation of CZ0 encoding

This commit is contained in:
G2-Games 2023-04-18 22:50:54 -05:00
parent e8a3dbd987
commit 9ff891b02e

View file

@ -1,46 +1,50 @@
use sdl2::pixels::Color;
use sdl2::rect::Point;
//use sdl2::render::Canvas;
//use sdl2::video::Window;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use image::{RgbaImage, ImageFormat, open}; use image::{RgbaImage, ImageFormat, open};
use std::io; use std::io;
use std::io::Write;
use std::fs; use std::fs;
use std::thread; use std::fs::File;
use std::time::Duration;
struct Header { #[derive(Copy, Clone)]
struct HeaderCZ0 {
magic: [u8; 3], // The magic bytes, can be CZ0, (CZ1, CZ2?) magic: [u8; 3], // The magic bytes, can be CZ0, (CZ1, CZ2?)
length: u8,
res: (i16, i16), // The width in the header res: (i16, i16), // The width in the header
depth: i16, // Bit depth depth: u8, // Bit depth
crop: (i16, i16), // Crop dimensions crop: (i16, i16), // Crop dimensions
bounds: (i16, i16), // Bounding box dimensions bounds: (i16, i16), // Bounding box dimensions
offset: (i16, i16), // Offset coordinates offset: (i16, i16), // Offset coordinates
} }
struct Image { struct CZ0File {
header: Header, header: HeaderCZ0,
bitmap: Vec<u8>, bitmap: Vec<u8>,
} }
// Converts 8 bit bytes to a 16 bit little endian word // Converts 8 bit bytes to a 16 bit little endian word or
fn bytes_to_word(first:u8, second:u8) -> i16 { pub fn bytes_to_word(first:u8, second:u8) -> i16 {
let final_value = ((second as i16) << 8) | (first as i16); let final_value = ((second as i16) << 8) | (first as i16);
return final_value; return final_value;
} }
fn extract_header(header_vec: &Vec<u8>) -> (Header, usize) { // Converts a 16 bit little endian word to 8 bit bytes
pub fn word_to_bytes(word:i16) -> [u8; 2] {
let first: u8 = (word & 0xFF) as u8; // Extract the first byte
let second: u8 = ((word >> 8) & 0xFF) as u8; // Extract the second byte
return [first, second];
}
/// Extract all the header information from a CZ0 file
fn extract_header_cz0(header_vec: &Vec<u8>) -> (HeaderCZ0, usize) {
// Get the magic bytes // Get the magic bytes
let magic: [u8; 3] = header_vec[0..3].try_into().unwrap(); let magic: [u8; 3] = header_vec[0..3].try_into().unwrap();
println!("Magic Bytes : {:?}", magic); println!("Magic Bytes : {:?}", magic);
// Get the length of the header // Get the length of the header
let header_length = header_vec[4]; let length = header_vec[4];
println!("Header Length: {:?}", header_length); println!("Header Length: {:?}", length);
// Convert the width and height to i16 values // Convert the width and height to i16 values
let width = bytes_to_word(header_vec[8], header_vec[9]); let width = bytes_to_word(header_vec[8], header_vec[9]);
@ -48,7 +52,7 @@ fn extract_header(header_vec: &Vec<u8>) -> (Header, usize) {
println!("Resolution : {}x{}", width, height); println!("Resolution : {}x{}", width, height);
// Get the bit depth // Get the bit depth
let depth = bytes_to_word(header_vec[12], header_vec[13]); let depth = header_vec[12];
println!("Bit Depth : {} bits", depth); println!("Bit Depth : {} bits", depth);
// Get the crop resolution // Get the crop resolution
@ -66,8 +70,9 @@ fn extract_header(header_vec: &Vec<u8>) -> (Header, usize) {
let offset_y = bytes_to_word(header_vec[30], header_vec[31]); let offset_y = bytes_to_word(header_vec[30], header_vec[31]);
println!("Offset Coords: {}x{}", offset_x, offset_y); println!("Offset Coords: {}x{}", offset_x, offset_y);
let image_header = Header { let image_header = HeaderCZ0 {
magic, magic,
length,
res: (width, height), res: (width, height),
depth, depth,
crop: (crop_width, crop_height), crop: (crop_width, crop_height),
@ -75,19 +80,24 @@ fn extract_header(header_vec: &Vec<u8>) -> (Header, usize) {
offset: (offset_x, offset_y), offset: (offset_x, offset_y),
}; };
return (image_header, header_length as usize); return (image_header, length as usize);
} }
// Provided a bitstream, extract the header information and /// Provided a bitstream, extract the header information and
// the rest of the metadata about a CZ file /// the rest of the metadata about a CZ0 file, returning a
fn decode_cz(mut input:Vec<u8>) -> Image { /// struct containing the header information and bitmap
fn decode_cz0(input_filename:&str) -> CZ0File {
println!("Reading input file...");
let mut input = fs::read(input_filename).unwrap();
println!("Decoding input...");
// TODO Research the header more! // TODO Research the header more!
let (header, header_length)= extract_header(&input); let (header, header_length) = extract_header_cz0(&input);
input.drain(..header_length); input.drain(..header_length);
// Construct the image struct // Construct the output CZ0 image
let final_image = Image { let final_image = CZ0File {
header, header,
bitmap: input, bitmap: input,
}; };
@ -95,70 +105,81 @@ fn decode_cz(mut input:Vec<u8>) -> Image {
return final_image; return final_image;
} }
/* /// Provided an image, extract the bitstream and create the
fn encode_cz() -> Image { /// header information from a previous CZ0 file in order to
let on_top = open("path/to/some.png").unwrap().into_rgb8(); /// replace it
fn encode_cz0(original_file:CZ0File, in_name:&str, out_name:&str) -> io::Result<()> {
let input_image = open(in_name).unwrap().into_rgba8();
let (input_width, input_height) = input_image.dimensions();
// Construct the image struct let width_diff = input_width as i16 - original_file.header.res.0;
let final_image = Image { let height_diff = input_height as i16 - original_file.header.res.1;
header,
bitmap: input, // Crop
let crop_width = input_width as i16;
let crop_height = input_height as i16;
// Offset
let offset_x = original_file.header.offset.0 + width_diff;
let offset_y = original_file.header.offset.1 + height_diff;
// Construct the header
let header = HeaderCZ0 {
magic: [67, 90, 48],
length: 36,
res: (input_width as i16, input_height as i16),
depth: 32,
crop: (crop_width, crop_height),
bounds: (original_file.header.bounds.0, original_file.header.bounds.1),
offset: (offset_x, offset_y),
}; };
return Image; let bitmap = input_image.to_vec();
}*/ let mut file = File::create(out_name)?;
fn main() -> io::Result<()> { // Write magic and length
let sdl_context = sdl2::init().unwrap(); file.write_all(&header.magic)?;
let video_subsystem = sdl_context.video().unwrap(); file.write_all(&[0])?;
file.write_all(&[header.length])?;
file.write_all(&vec![0u8; 3])?;
// Read all bytes of the CZ image to an array // Write width and height
println!("Reading image..."); file.write_all(&word_to_bytes(header.res.0))?;
let image_raw = fs::read("775.cz0")?; file.write_all(&word_to_bytes(header.res.1))?;
let image = decode_cz(image_raw); // Write bit depth
file.write_all(&[header.depth])?;
file.write_all(&vec![0u8; 7])?;
let width = image.header.res.0 as i32; // Write crop width and height
let height = image.header.res.1 as i32; file.write_all(&word_to_bytes(header.crop.0))?;
file.write_all(&word_to_bytes(header.crop.1))?;
println!("{}", width*height); // Write bound width and height
println!("{}", image.bitmap.len() / 4); file.write_all(&word_to_bytes(header.bounds.0))?;
file.write_all(&word_to_bytes(header.bounds.1))?;
// Build the window // Write offset width and height
let window = video_subsystem.window("SDL2 Rust", width as u32, height as u32) file.write_all(&word_to_bytes(header.offset.0))?;
.position_centered() file.write_all(&word_to_bytes(header.offset.1))?;
.build()
.unwrap();
let mut canvas = window.into_canvas().build().unwrap(); // Write unknown padding bytes
canvas.set_draw_color(Color::RGBA(255, 255, 255, 255)); file.write_all(&vec![0u8; 4])?;
canvas.clear();
thread::sleep(Duration::from_millis(40)); // Sleep to allow the window to display // Write the actual image data
file.write_all(&bitmap)?;
// Draw the bitmap to the canvas return Ok(());
for (i, chunk) in image.bitmap.chunks(4).enumerate() {
let a = chunk[3] / 255;
// Get the pixel colors
let r = chunk[0] * a;
let g = chunk[1] * a;
let b = chunk[2] * a;
canvas.set_draw_color(Color::RGBA(r, g, b, a));
// Get the pixel location
let y = i as i32/width;
let x = i as i32%width;
canvas.draw_point(Point::new(x, y)).unwrap();
} }
canvas.present(); // Display the image on the SDL2 canvas fn main() -> io::Result<()> {
let image = decode_cz0("775.cz0");
// Create and save a PNG of the image data /* Create and save a PNG of the image data
// This errors if the image data is too short // This errors if the image data is too short
let tmp = match RgbaImage::from_raw( let tmp = match RgbaImage::from_raw(
width as u32, image.header.res.0 as u32,
height as u32, image.header.res.1 as u32,
image.bitmap, image.bitmap,
) { ) {
Some(img) => img, Some(img) => img,
@ -175,25 +196,10 @@ fn main() -> io::Result<()> {
eprintln!("ERROR SAVING IMAGE: {}", e); eprintln!("ERROR SAVING IMAGE: {}", e);
eprintln!("You probably have an image with the CZ0 offset bug!") eprintln!("You probably have an image with the CZ0 offset bug!")
} }
} }*/
encode_cz0(image, "melon_test.png", "test.cz0").unwrap();
decode_cz0("test.cz0");
// Wait for the user to press escape
let mut event_pump = sdl_context.event_pump().unwrap();
loop {
for event in event_pump.poll_iter() {
match event {
Event::Quit { .. } | Event::KeyDown {
keycode: Some(Keycode::Escape),
..
} => {
println!("Exiting");
return Ok(()); return Ok(());
} }
_ => {
continue;
}
}
}
}
}