diff --git a/src/cz_utils.rs b/src/cz_utils.rs index 105639f..5ad6746 100644 --- a/src/cz_utils.rs +++ b/src/cz_utils.rs @@ -1,16 +1,41 @@ use image::{ImageFormat, RgbaImage}; -/// The header of a CZ* file +/// The header of a CZ# file #[derive(Debug)] -struct CZHeader { - magic: [u8; 3], // The magic bytes, can be CZ0, (CZ1, CZ2?) +pub struct CZHeader { + version: u8, // The version from the magic bytes, (eg. CZ3, CZ4) length: u8, - res: (i16, i16), // The width in the header + res: (u16, u16), // The width in the header depth: u8, // Bit depth - mystery: Vec<u8>, - crop: (i16, i16), // Crop dimensions - bounds: (i16, i16), // Bounding box dimensions - offset: (i16, i16), // Offset coordinates + crop: (u16, u16), // Crop dimensions + bounds: (u16, u16), // Bounding box dimensions + offset: (u16, u16), // Offset coordinates +} + +impl CZHeader { + pub fn new(bytes: &[u8]) -> Self { + CZHeader { + version: bytes[2] - b'0', + length: bytes[4], + res: ( + u16::from_le_bytes(bytes[8..10].try_into().unwrap()), + u16::from_le_bytes(bytes[10..12].try_into().unwrap()) + ), + depth: bytes[12], + crop: ( + u16::from_le_bytes(bytes[20..22].try_into().unwrap()), + u16::from_le_bytes(bytes[22..24].try_into().unwrap()) + ), + bounds: ( + u16::from_le_bytes(bytes[24..26].try_into().unwrap()), + u16::from_le_bytes(bytes[26..28].try_into().unwrap()) + ), + offset: ( + u16::from_le_bytes(bytes[28..30].try_into().unwrap()), + u16::from_le_bytes(bytes[30..32].try_into().unwrap()) + ), + } + } } /// Defines a full file that has a header of type `CZHeader` and a vector bitmap as the body @@ -26,13 +51,11 @@ impl CZFile { pub fn to_rgba8(&self) -> RgbaImage { let process_bitmap = self.bitmap.clone(); - let image_data = RgbaImage::from_raw( + RgbaImage::from_raw( self.header.res.0 as u32, self.header.res.1 as u32, process_bitmap, - ).expect("Error encoding the image"); - - return image_data; + ).expect("Error encoding the image") } pub fn to_png(&self, out_name:&str) { @@ -54,14 +77,13 @@ impl CZFile { println!("\n--IMAGE INFORMATION--"); println!("Image size : {:.2} KB", image_size); - println!("Magic Bytes : {:?}", self.header.magic); + println!("Version : {:?}", self.header.version); println!("Header Length : {:?} bytes", self.header.length); println!( "Resolution : {}x{}", self.header.res.0, self.header.res.1 ); println!("Bit Depth : {} bits", self.header.depth); - println!("Mystery Bytes : {:?}", self.header.mystery); println!( "Crop Coords : {}x{}", self.header.crop.0, self.header.crop.1 @@ -80,67 +102,17 @@ impl CZFile { /// Utilities for manipulating CZ0 images pub mod cz0 { use std::fs; - use std::fs::File; - use std::io; - use std::io::Write; - use crate::cz_utils::{CZFile, CZHeader}; - use crate::utils::*; - use image::DynamicImage; - - /// Extract all the header information from a CZ0 file - fn extract_header_cz0(header_vec: &Vec<u8>) -> (CZHeader, usize) { - // Get the magic bytes - let magic: [u8; 3] = header_vec[0..3].try_into().unwrap(); - - // Get the length of the header - let length = header_vec[4]; - - // Convert the width and height to i16 values - let width = bytes_to_word(header_vec[8], header_vec[9]); - let height = bytes_to_word(header_vec[10], header_vec[11]); - - // Get the bit depth - let depth = header_vec[12]; - - // Get the mystery bytes - let mystery = header_vec[13..20].to_vec(); - - // Get the crop resolution - let crop_width = bytes_to_word(header_vec[20], header_vec[21]); - let crop_height = bytes_to_word(header_vec[22], header_vec[23]); - - // Get bounding box - let bound_width = bytes_to_word(header_vec[24], header_vec[25]); - let bound_height = bytes_to_word(header_vec[26], header_vec[27]); - - // Get offset coordinates - let offset_x = bytes_to_word(header_vec[28], header_vec[29]); - let offset_y = bytes_to_word(header_vec[30], header_vec[31]); - - let image_header = CZHeader { - magic, - length, - res: (width, height), - depth, - mystery, - crop: (crop_width, crop_height), - bounds: (bound_width, bound_height), - offset: (offset_x, offset_y), - }; - - return (image_header, length as usize); - } /// Provided a bitstream, extract the header information and the rest of the metadata about a CZ0 file, returning a struct containing the header information and bitmap pub fn decode_cz0(input_filename: &str) -> CZFile { let mut input = fs::read(input_filename).expect("Error, could not open image"); // TODO Research the header more! - let (header, header_length) = extract_header_cz0(&input); + let header = CZHeader::new(&input); // Chop off the header and keep only the bitmap after it - input.drain(..header_length); + input.drain(..header.length as usize); // Construct the output CZ0 image let final_image = CZFile { @@ -149,69 +121,6 @@ pub mod cz0 { }; println!("Decoded {}", input_filename); - return final_image; - } - - /// Provided an image, extract the bitstream and create the header information using a previous CZ0 file in order to replace it - pub fn encode_cz0(original_file: CZFile, input_image: DynamicImage, out_name: &str) -> io::Result<()> { - - let original_image = input_image.to_rgba8(); - let (input_width, input_height) = original_image.dimensions(); - - // Construct the header - let header = CZHeader { - magic: [67, 90, 48], - length: original_file.header.length, - res: (input_width as i16, input_height as i16), - depth: original_file.header.depth, - mystery: original_file.header.mystery, - crop: (input_width as i16, input_height as i16), - bounds: (original_file.header.bounds.0, original_file.header.bounds.1), - offset: (original_file.header.offset.0, original_file.header.offset.1), - }; - - let mut file = File::create(out_name)?; - - // Assemble the header - let mut header_assembled = [ - &header.magic[..], - &[0], - &[header.length], - &vec![0u8; 3], - &word_to_bytes(header.res.0), - &word_to_bytes(header.res.1), - &[header.depth], - &header.mystery, - &word_to_bytes(header.crop.0), - &word_to_bytes(header.crop.1), - &word_to_bytes(header.bounds.0), - &word_to_bytes(header.bounds.1), - &word_to_bytes(header.offset.0), - &word_to_bytes(header.offset.1), - &vec![0u8; 4], - ].concat(); - - // Cut off unnecessary information from the header - // past the length defined in the header itself - header_assembled.drain(header.length as usize..); - - // Write the header to the file - file.write_all(&header_assembled)?; - - // Turn the image data into a vector - let bitmap = original_image.to_vec(); - - // Write the actual image data - file.write_all(&bitmap)?; - - let actual_size = input_width * input_height; - - if actual_size > bitmap.len() as u32 { - let size_diff = bitmap.len() as u32 - actual_size; - file.write_all(&vec![0u8; size_diff as usize])?; - } - - println!("Encoded {}", out_name); - return Ok(()); + final_image } } diff --git a/src/main.rs b/src/main.rs index 0923879..1d8f6e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,19 @@ // Create the modules -pub mod cz_utils; // CZ file tools -pub mod utils; // Generic tools -use cz_utils::cz0::*; +pub mod cz_utils; +pub mod utils; + +// Generic tools +use std::fs; + +use crate::cz_utils::CZHeader; + fn main() { - let original_image = decode_cz0("../test_files/782.cz0"); - let replacement_image = image::open("../test_files/melon_test.png").expect("Failed to open image file"); + let input = fs::read( + "/home/g2/Documents/projects/lbee-utils/test_files/GOOD_extra_bg.cz3" + ).expect("Error, could not open image"); - encode_cz0(original_image, replacement_image, "test.cz0").expect("Error encoding the image"); + let header = CZHeader::new(&input); - let image = decode_cz0("test.cz0"); - image.info(); - image.to_png("tmp.png"); + dbg!(header); } diff --git a/src/utils.rs b/src/utils.rs index d22058b..f89e9b9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,14 +1,22 @@ /// Converts 8 bit bytes to a 16 bit little endian word -pub fn bytes_to_word(first: u8, second: u8) -> i16 { - let final_value = ((second as i16) << 8) | (first as i16); - - return final_value; +pub fn bytes_to_word(first: u8, second: u8) -> u16 { + ((second as u16) << 8) | (first as u16) } /// Converts a 16 bit little endian word to 8 bit bytes -pub fn word_to_bytes(word: i16) -> [u8; 2] { +pub fn word_to_bytes(word: u16) -> [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]; + [first, second] +} + +pub fn get_bytes<const S: usize>(iterator: &mut std::vec::IntoIter<u8>) -> [u8; S] { + let mut bytes = [0; S]; + + for byte in bytes.iter_mut().take(S) { + *byte = iterator.next().unwrap(); + } + + bytes } diff --git a/test_files/blue.png b/test_files/blue.png deleted file mode 100644 index 88f302d..0000000 Binary files a/test_files/blue.png and /dev/null differ diff --git a/test_files/melon_test.png b/test_files/melon_test.png deleted file mode 100644 index 424b282..0000000 Binary files a/test_files/melon_test.png and /dev/null differ