diff --git a/src/cz_common.rs b/src/cz_common.rs index d2a1816..2095fc9 100644 --- a/src/cz_common.rs +++ b/src/cz_common.rs @@ -1,6 +1,9 @@ //! Shared types and traits between CZ# files -use std::io::{self, Cursor, Read}; +use std::{ + collections::HashMap, + io::{self, Cursor, Read}, +}; use byteorder::{LittleEndian, ReadBytesExt}; use image::Rgba; @@ -11,15 +14,21 @@ pub enum CzError { #[error("Version in header does not match expected version")] VersionMismatch, - #[error("Format of supplied file is incorrect; expected {} bytes, got {}", expected, got)] - InvalidFormat{expected: usize, got: usize}, + #[error( + "Format of supplied file is incorrect; expected {} bytes, got {}", + expected, + got + )] + InvalidFormat { expected: usize, got: usize }, #[error("Failed to read input")] ReadError(#[from] io::Error), } pub trait CzHeader { - fn new(bytes: &[u8]) -> Result where Self: Sized; + fn new(bytes: &mut Cursor<&[u8]>) -> Result + where + Self: Sized; fn version(&self) -> u8; @@ -36,23 +45,26 @@ pub trait CzHeader { #[derive(Debug, Clone, Copy)] pub struct CommonHeader { /// Format version from the magic bytes, (eg. CZ3, CZ4) - pub version: u8, + version: u8, /// Length of the header in bytes - pub length: u32, + length: u32, /// Width of the image in pixels - pub width: u16, + width: u16, /// Height of the image in pixels - pub height: u16, + height: u16, /// Bit depth in Bits Per Pixel (BPP) - pub depth: u16, + depth: u16, } -impl CommonHeader { - pub fn new(bytes: &mut Cursor<&[u8]>) -> Result { +impl CzHeader for CommonHeader { + fn new(bytes: &mut Cursor<&[u8]>) -> Result + where + Self: Sized, + { let mut magic = [0u8; 4]; bytes.read_exact(&mut magic)?; @@ -64,13 +76,35 @@ impl CommonHeader { depth: bytes.read_u16::()?, }) } + + fn version(&self) -> u8 { + self.version + } + + fn header_length(&self) -> usize { + self.length as usize + } + + fn width(&self) -> u16 { + self.width + } + + fn height(&self) -> u16 { + self.height + } + + fn depth(&self) -> u16 { + self.depth + } } pub trait CzImage { type Header; /// Create a [CZImage] from bytes - fn decode(bytes: &[u8]) -> Result where Self: Sized; + fn decode(bytes: &[u8]) -> Result + where + Self: Sized; /// Save the image as a PNG fn save_as_png(&self, name: &str); @@ -82,7 +116,10 @@ pub trait CzImage { fn into_bitmap(self) -> Vec; } -pub fn parse_colormap(input: &mut Cursor<&[u8]>, num_colors: usize) -> Result>, CzError> { +pub fn parse_colormap( + input: &mut Cursor<&[u8]>, + num_colors: usize, +) -> Result>, CzError> { let mut colormap = Vec::with_capacity(num_colors); let mut rgba_buf = [0u8; 4]; @@ -94,11 +131,13 @@ pub fn parse_colormap(input: &mut Cursor<&[u8]>, num_colors: usize) -> Result) -> Result { let parts_count = bytes.read_u32::()?; - dbg!(parts_count); + let mut part_sizes = vec![]; let mut total_size = 0; let mut total_size_raw = 0; @@ -131,6 +170,8 @@ pub fn parse_chunk_info(bytes: &mut Cursor<&[u8]>) -> Result) -> Result, chunk_info: CompressionInfo) -> Result, CzError> { +/// Decompress an LZW compressed stream, like CZ1 +pub fn decompress( + input: &mut Cursor<&[u8]>, + chunk_info: CompressionInfo, +) -> Result, CzError> { let mut m_dst = 0; let mut bitmap = vec![0; chunk_info.total_size_raw]; for chunk in chunk_info.chunks { diff --git a/src/formats/cz0.rs b/src/formats/cz0.rs index 6492475..eeb5172 100644 --- a/src/formats/cz0.rs +++ b/src/formats/cz0.rs @@ -1,4 +1,6 @@ -use std::io::Cursor; +use std::io::{Cursor, Read}; + +use byteorder::{LittleEndian, ReadBytesExt}; use crate::cz_common::{CommonHeader, CzError, CzHeader, CzImage}; @@ -8,22 +10,22 @@ pub struct Cz0Header { common: CommonHeader, /// Width of cropped image area - crop_width: u16, + pub crop_width: u16, /// Height of cropped image area - crop_height: u16, + pub crop_height: u16, /// Bounding box width - bounds_width: u16, + pub bounds_width: u16, /// Bounding box height - bounds_height: u16, + pub bounds_height: u16, /// Offset width - offset_width: Option, + pub offset_width: Option, /// Offset height - offset_height: Option, + pub offset_height: Option, } #[derive(Debug)] @@ -33,29 +35,39 @@ pub struct Cz0Image { } impl CzHeader for Cz0Header { - fn new(bytes: &[u8]) -> Result { - let mut input = Cursor::new(bytes); - let common = CommonHeader::new(&mut input)?; + fn new(bytes: &mut Cursor<&[u8]>) -> Result + where + Self: Sized, + { + let common = CommonHeader::new(bytes)?; - if common.version != 0 { - return Err(CzError::VersionMismatch) + if common.version() != 0 { + return Err(CzError::VersionMismatch); } + let _unknown = bytes.read_u48::()?; + + let crop_width = bytes.read_u16::()?; + let crop_height = bytes.read_u16::()?; + + let bounds_width = bytes.read_u16::()?; + let bounds_height = bytes.read_u16::()?; + let mut offset_width = None; let mut offset_height = None; - if common.length > 28 { - offset_width = Some(u16::from_le_bytes(bytes[28..30].try_into().unwrap())); - offset_height = Some(u16::from_le_bytes(bytes[30..32].try_into().unwrap())); + if common.header_length() > 28 { + offset_width = Some(bytes.read_u16::()?); + offset_height = Some(bytes.read_u16::()?); } Ok(Self { common, - crop_width: u16::from_le_bytes(bytes[20..22].try_into().unwrap()), - crop_height: u16::from_le_bytes(bytes[22..24].try_into().unwrap()), + crop_width, + crop_height, - bounds_width: u16::from_le_bytes(bytes[24..26].try_into().unwrap()), - bounds_height: u16::from_le_bytes(bytes[26..28].try_into().unwrap()), + bounds_width, + bounds_height, offset_width, offset_height, @@ -63,23 +75,23 @@ impl CzHeader for Cz0Header { } fn version(&self) -> u8 { - self.common.version + self.common.version() } fn header_length(&self) -> usize { - self.common.length as usize + self.common.header_length() } fn width(&self) -> u16 { - self.common.width + self.common.width() } fn height(&self) -> u16 { - self.common.height + self.common.height() } fn depth(&self) -> u16 { - self.common.depth + self.common.depth() } } @@ -87,26 +99,27 @@ impl CzImage for Cz0Image { type Header = Cz0Header; fn decode(bytes: &[u8]) -> Result { + let mut input = Cursor::new(bytes); + // Get the header from the input - let header = Cz0Header::new(bytes)?; + let header = Cz0Header::new(&mut input)?; // Get the rest of the file, which is the bitmap - let bitmap = bytes[header.header_length()..].to_vec(); + let mut bitmap = vec![]; + input.read_to_end(&mut bitmap)?; - Ok(Self { - header, - bitmap - }) + Ok(Self { header, bitmap }) } fn save_as_png(&self, name: &str) { image::save_buffer( name, &self.bitmap, - self.header.common.width as u32, - self.header.common.height as u32, - image::ExtendedColorType::Rgba8 - ).unwrap() + self.header.width() as u32, + self.header.height() as u32, + image::ExtendedColorType::Rgba8, + ) + .unwrap() } fn header(&self) -> &Self::Header { diff --git a/src/formats/cz1.rs b/src/formats/cz1.rs index 98dfcb5..8b0e065 100644 --- a/src/formats/cz1.rs +++ b/src/formats/cz1.rs @@ -1,6 +1,8 @@ -use std::io::Cursor; +use crate::cz_common::{ + decompress, parse_chunk_info, parse_colormap, CommonHeader, CzError, CzHeader, CzImage, +}; use image::{ImageFormat, Rgba}; -use crate::cz_common::{decompress, parse_chunk_info, parse_colormap, CommonHeader, CzError, CzImage}; +use std::io::Cursor; #[derive(Debug, Clone)] pub struct Cz1Image { @@ -20,17 +22,17 @@ impl CzImage for Cz1Image { // The color palette, gotten for 8 and 4 BPP images let mut palette = None; - if header.depth == 8 || header.depth == 4 { - palette = Some(parse_colormap(&mut input, 1 << header.depth)?); + if header.depth() == 8 || header.depth() == 4 { + palette = Some(parse_colormap(&mut input, 1 << header.depth())?); } let chunk_info = parse_chunk_info(&mut input)?; if chunk_info.total_size_compressed as usize > bytes.len() { - return Err(CzError::InvalidFormat{ + return Err(CzError::InvalidFormat { expected: chunk_info.total_size_compressed, got: bytes.len(), - }) + }); } let mut bitmap = decompress(&mut input, chunk_info).unwrap(); @@ -51,10 +53,11 @@ impl CzImage for Cz1Image { 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(); + self.header.width() as u32, + self.header.height() as u32, + self.bitmap.clone(), + ) + .unwrap(); img.save_with_format(name, ImageFormat::Png).unwrap(); } diff --git a/src/formats/cz3.rs b/src/formats/cz3.rs index 9b4e31a..15a041e 100644 --- a/src/formats/cz3.rs +++ b/src/formats/cz3.rs @@ -1,5 +1,6 @@ use std::io::Cursor; +use byteorder::{LittleEndian, ReadBytesExt}; use image::ImageFormat; use crate::cz_common::{decompress, parse_chunk_info, CommonHeader, CzError, CzHeader, CzImage}; @@ -29,29 +30,39 @@ pub struct Cz3Header { } impl CzHeader for Cz3Header { - fn new(bytes: &[u8]) -> Result where Self: Sized { - let mut input = Cursor::new(bytes); - let common = CommonHeader::new(&mut input)?; + fn new(bytes: &mut Cursor<&[u8]>) -> Result + where + Self: Sized, + { + let common = CommonHeader::new(bytes)?; - if common.version != 3 { - return Err(CzError::VersionMismatch) + if common.version() != 3 { + return Err(CzError::VersionMismatch); } + let _unknown = bytes.read_u48::()?; + + let crop_width = bytes.read_u16::()?; + let crop_height = bytes.read_u16::()?; + + let bounds_width = bytes.read_u16::()?; + let bounds_height = bytes.read_u16::()?; + let mut offset_width = None; let mut offset_height = None; - if common.length > 28 { - offset_width = Some(u16::from_le_bytes(bytes[28..30].try_into().unwrap())); - offset_height = Some(u16::from_le_bytes(bytes[30..32].try_into().unwrap())); + if common.header_length() > 28 { + offset_width = Some(bytes.read_u16::()?); + offset_height = Some(bytes.read_u16::()?); } Ok(Self { common, - crop_width: u16::from_le_bytes(bytes[20..22].try_into().unwrap()), - crop_height: u16::from_le_bytes(bytes[22..24].try_into().unwrap()), + crop_width, + crop_height, - bounds_width: u16::from_le_bytes(bytes[24..26].try_into().unwrap()), - bounds_height: u16::from_le_bytes(bytes[26..28].try_into().unwrap()), + bounds_width, + bounds_height, offset_width, offset_height, @@ -59,23 +70,23 @@ impl CzHeader for Cz3Header { } fn version(&self) -> u8 { - self.common.version + self.common.version() } fn header_length(&self) -> usize { - self.common.length as usize + self.common.header_length() } fn width(&self) -> u16 { - self.common.width + self.common.width() } fn height(&self) -> u16 { - self.common.height + self.common.height() } fn depth(&self) -> u16 { - self.common.depth + self.common.depth() } } @@ -90,15 +101,13 @@ impl CzImage for Cz3Image { fn decode(bytes: &[u8]) -> Result { let mut input = Cursor::new(bytes); - let header = Cz3Header::new(bytes)?; + let header = Cz3Header::new(&mut input)?; input.set_position(header.header_length() as u64); let block_info = parse_chunk_info(&mut input)?; let mut bitmap = decompress(&mut input, block_info)?; - 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 { @@ -110,20 +119,16 @@ impl CzImage for Cz3Image { } } - dbg!(bitmap.len()); - - Ok(Self { - header, - bitmap - }) + 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(); + self.bitmap.clone(), + ) + .unwrap(); img.save_with_format(name, ImageFormat::Png).unwrap(); } diff --git a/src/main.rs b/src/main.rs index dbf8d7a..942fe85 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ pub mod cz_common; -pub mod formats{ +pub mod formats { pub mod cz0; pub mod cz1; pub mod cz3; @@ -11,9 +11,9 @@ use std::fs; use crate::{cz_common::CzImage, formats::cz3::Cz3Image}; fn main() { - let input = fs::read("../test_files/Old_TestFiles/129.CZ3").expect("Error, could not open image"); - let cz3_file = Cz3Image::decode(&input).unwrap(); - println!("{:#?}", cz3_file.header()); + let input = fs::read("../test_files/BAD_BG_011_10.cz4").expect("Error, could not open image"); + let img_file = Cz3Image::decode(&input).unwrap(); + println!("{:#?}", img_file.header()); - cz3_file.save_as_png("test.png") + img_file.save_as_png("test.png") }