diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 0000000..6114e9f --- /dev/null +++ b/src/common.rs @@ -0,0 +1,184 @@ +//! Shared types and traits between CZ# files + +use std::{ + io::{self, Cursor, Read, Seek, Write}, path::PathBuf +}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use image::Rgba; +use thiserror::Error; + +#[derive(Error, Debug)] +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("Failed to read/write input/output")] + IoError(#[from] io::Error), +} + +pub trait CzHeader { + fn new(bytes: &mut T) -> Result + where + Self: Sized; + + /// Turn the header into bytes equivalent to the original header from the file + fn to_bytes(&self) -> Result, io::Error>; + + /// The version of the [CzImage] file + fn version(&self) -> u8; + + /// The length of the header in bytes + fn length(&self) -> usize; + + /// The width of the image + fn width(&self) -> u16; + + /// The height of the image + fn height(&self) -> u16; + + /// The bit depth of the image (BPP) + fn depth(&self) -> u16; + + /// An unknown value? + fn color_block(&self) -> u8; +} + +/// The common first part of a header of a CZ# file +#[derive(Debug, Clone, Copy)] +pub struct CommonHeader { + /// Format version from the magic bytes, (eg. CZ3, CZ4) + pub version: u8, + + /// Length of the header in bytes + pub length: u32, + + /// Width of the image in pixels + pub width: u16, + + /// Height of the image in pixels + pub height: u16, + + /// Bit depth in Bits Per Pixel (BPP) + pub depth: u16, + + /// Color block + pub color_block: u8, +} + +impl CzHeader for CommonHeader { + fn new(bytes: &mut T) -> Result + where + Self: Sized, + { + let mut magic = [0u8; 4]; + bytes.read_exact(&mut magic)?; + + Ok(Self { + version: magic[2] - b'0', + length: bytes.read_u32::()?, + width: bytes.read_u16::()?, + height: bytes.read_u16::()?, + depth: bytes.read_u16::()?, + color_block: bytes.read_u8()?, + }) + } + + fn version(&self) -> u8 { + self.version + } + + fn 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 + } + + fn color_block(&self) -> u8 { + self.color_block + } + + fn to_bytes(&self) -> Result, io::Error> { + let mut buf = vec![]; + + let magic_bytes = [b'C', b'Z', b'0' + self.version, 0]; + buf.write_all(&magic_bytes)?; + buf.write_u32::(self.length() as u32)?; + buf.write_u16::(self.width())?; + buf.write_u16::(self.height())?; + buf.write_u16::(self.depth())?; + buf.write_u8(self.color_block())?; + + Ok(buf) + } +} + +pub trait CzImage { + type Header; + + /// Create a [CZImage] from bytes + fn decode(bytes: &mut T) -> Result + where + Self: Sized; + + /// Save the image as a PNG + fn save_as_png(&self, name: &str) -> Result<(), image::error::ImageError>; + + /// Save the image as its corresponding CZ# type + fn save_as_cz>(&self, path: T) -> Result<(), CzError>; + + /// Get the header for metadata + fn header(&self) -> &Self::Header; + + /// Set the header with its metadata + fn set_header(&mut self, header: Self::Header); + + /// Get the raw underlying bitmap for an image + fn into_bitmap(self) -> Vec; + + /// Set the bitmap the image contains + fn set_bitmap(&mut self, bitmap: &[u8], header: &Self::Header); +} + +pub fn parse_colormap( + input: &mut T, + num_colors: usize, +) -> Result>, CzError> { + let mut colormap = Vec::with_capacity(num_colors); + let mut rgba_buf = [0u8; 4]; + + for _ in 0..num_colors { + input.read_exact(&mut rgba_buf)?; + colormap.push(Rgba(rgba_buf)); + } + + Ok(colormap) +} + +pub fn apply_palette(input: &mut Vec, palette: &[Rgba]) -> Vec { + let mut output_map = Vec::new(); + + for byte in input.iter() { + let color = palette[*byte as usize].0; + output_map.extend_from_slice(&color); + } + + output_map +} diff --git a/src/compression.rs b/src/compression.rs new file mode 100644 index 0000000..c5479b1 --- /dev/null +++ b/src/compression.rs @@ -0,0 +1,119 @@ +use std::io::{Read, Seek}; +use byteorder::{LittleEndian, ReadBytesExt}; +use crate::common::CzError; + +#[derive(Debug, Clone, Copy)] +pub struct ChunkInfo { + pub size_compressed: usize, + pub size_raw: usize, +} + +#[derive(Debug, Clone)] +pub struct CompressionInfo { + pub chunk_count: usize, + pub total_size_compressed: usize, + pub total_size_raw: usize, + pub chunks: Vec, + + /// Length of the compression chunk info + pub length: usize, +} + +/// Get info about the compression chunks +pub fn parse_chunk_info(bytes: &mut T) -> Result { + let parts_count = bytes.read_u32::()?; + + let mut part_sizes = vec![]; + let mut total_size = 0; + let mut total_size_raw = 0; + + // Loop over the compressed bytes + for _ in 0..parts_count { + let compressed_size = bytes.read_u32::()? * 2; + total_size += compressed_size; + + let raw_size = bytes.read_u32::()?; + total_size_raw += raw_size; + + part_sizes.push(ChunkInfo { + size_compressed: compressed_size as usize, + size_raw: raw_size as usize, + }); + } + + Ok(CompressionInfo { + chunk_count: parts_count as usize, + total_size_compressed: total_size as usize, + total_size_raw: total_size_raw as usize, + chunks: part_sizes, + length: bytes.stream_position()? as usize, + }) +} + +/// Decompress an LZW compressed stream, like CZ1 +pub fn decompress( + input: &mut T, + 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 { + let mut part = vec![0u8; chunk.size_compressed]; + input.read_exact(&mut part)?; + + for j in (0..part.len()).step_by(2) { + let ctl = part[j + 1]; + + if ctl == 0 { + bitmap[m_dst] = part[j]; + m_dst += 1; + } else { + m_dst += copy_range(&mut bitmap, &part, get_offset(&part, j), m_dst); + } + } + } + + Ok(bitmap) +} + +fn get_offset(input: &[u8], src: usize) -> usize { + (((input[src] as usize) | (input[src + 1] as usize) << 8) - 0x101) * 2 +} + +fn copy_range(bitmap: &mut Vec, input: &[u8], src: usize, dst: usize) -> usize { + let mut dst = dst; + let start_pos = dst; + + if input[src + 1] == 0 { + bitmap[dst] = input[src]; + dst += 1; + } else if get_offset(input, src) == src { + bitmap[dst] = 0; + dst += 1; + } else { + dst += copy_range(bitmap, input, get_offset(input, src), dst); + } + + if input[src + 3] == 0 { + bitmap[dst] = input[src + 2]; + dst += 1; + } else if get_offset(input, src + 2) == src { + bitmap[dst] = bitmap[start_pos]; + dst += 1; + } else { + bitmap[dst] = copy_one(input, get_offset(input, src + 2)); + dst += 1; + } + + dst - start_pos +} + +fn copy_one(input: &[u8], src: usize) -> u8 { + if input[src + 1] == 0 { + input[src] + } else if get_offset(input, src) == src { + 0 + } else { + copy_one(input, get_offset(input, src)) + } +} diff --git a/src/cz_common.rs b/src/cz_common.rs deleted file mode 100644 index 2119832..0000000 --- a/src/cz_common.rs +++ /dev/null @@ -1,262 +0,0 @@ -//! Shared types and traits between CZ# files - -use std::{ - collections::HashMap, - io::{self, Cursor, Read}, -}; - -use byteorder::{LittleEndian, ReadBytesExt}; -use image::Rgba; -use thiserror::Error; - -#[derive(Error, Debug)] -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("Failed to read input")] - ReadError(#[from] io::Error), -} - -pub trait CzHeader { - fn new(bytes: &mut Cursor<&[u8]>) -> Result - where - Self: Sized; - - fn version(&self) -> u8; - - fn header_length(&self) -> usize; - - fn width(&self) -> u16; - - fn height(&self) -> u16; - - fn depth(&self) -> u16; -} - -/// The common first part of a header of a CZ# file -#[derive(Debug, Clone, Copy)] -pub struct CommonHeader { - /// Format version from the magic bytes, (eg. CZ3, CZ4) - version: u8, - - /// Length of the header in bytes - length: u32, - - /// Width of the image in pixels - width: u16, - - /// Height of the image in pixels - height: u16, - - /// Bit depth in Bits Per Pixel (BPP) - depth: u16, - - color_block: u8, -} - -impl CzHeader for CommonHeader { - fn new(bytes: &mut Cursor<&[u8]>) -> Result - where - Self: Sized, - { - let mut magic = [0u8; 4]; - bytes.read_exact(&mut magic)?; - - Ok(Self { - version: magic[2] - b'0', - length: bytes.read_u32::()?, - width: bytes.read_u16::()?, - height: bytes.read_u16::()?, - depth: bytes.read_u16::()?, - color_block: bytes.read_u8()?, - }) - } - - 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; - - /// Save the image as a PNG - fn save_as_png(&self, name: &str) -> Result<(), image::error::ImageError>; - - /// Save the image as its corresponding CZ# type - fn save_as_cz(&self) -> Result<(), CzError>; - - /// Get the header for metadata - fn header(&self) -> &Self::Header; - - /// Set the header with its metadata - fn set_header(&mut self, header: Self::Header); - - /// Get the raw underlying bitmap for an image - fn into_bitmap(self) -> Vec; - - /// Set the bitmap the image contains - fn set_bitmap(&mut self, bitmap: Vec, header: Self::Header); -} - -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]; - - for _ in 0..num_colors { - input.read_exact(&mut rgba_buf)?; - colormap.push(Rgba(rgba_buf)); - } - - Ok(colormap) -} - -#[derive(Debug)] -pub struct ChunkInfo { - pub size_compressed: usize, - pub size_raw: usize, -} - -#[derive(Debug)] -pub struct CompressionInfo { - pub chunk_count: usize, - pub total_size_compressed: usize, - pub total_size_raw: usize, - pub chunks: Vec, - - /// Length of the compression chunk info - pub length: usize, -} - -/// Get info about the compression chunks -pub fn parse_chunk_info(bytes: &mut Cursor<&[u8]>) -> 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; - - for _ in 0..parts_count { - let compressed_size = bytes.read_u32::()? * 2; - total_size += compressed_size; - - let raw_size = bytes.read_u32::()? * 4; - total_size_raw += raw_size; - - part_sizes.push(ChunkInfo { - size_compressed: compressed_size as usize, - size_raw: raw_size as usize, - }); - } - - dbg!(&part_sizes); - - Ok(CompressionInfo { - chunk_count: parts_count as usize, - total_size_compressed: total_size as usize, - total_size_raw: total_size_raw as usize, - chunks: part_sizes, - length: bytes.position() as usize, - }) -} - -/// 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 { - let mut part = vec![0u8; chunk.size_compressed]; - input.read_exact(&mut part)?; - - for j in (0..part.len()).step_by(2) { - let ctl = part[j + 1]; - - if ctl == 0 { - bitmap[m_dst] = part[j]; - m_dst += 1; - } else { - m_dst += copy_range(&mut bitmap, &part, get_offset(&part, j), m_dst); - } - } - } - - Ok(bitmap) -} - -fn get_offset(input: &[u8], src: usize) -> usize { - (((input[src] as usize) | (input[src + 1] as usize) << 8) - 0x101) * 2 -} - -fn copy_range(bitmap: &mut Vec, input: &[u8], src: usize, dst: usize) -> usize { - let mut dst = dst; - let start_pos = dst; - - if input[src + 1] == 0 { - bitmap[dst] = input[src]; - dst += 1; - } else if get_offset(input, src) == src { - bitmap[dst] = 0; - dst += 1; - } else { - dst += copy_range(bitmap, input, get_offset(input, src), dst); - } - - if input[src + 3] == 0 { - bitmap[dst] = input[src + 2]; - dst += 1; - } else if get_offset(input, src + 2) == src { - bitmap[dst] = bitmap[start_pos]; - dst += 1; - } else { - bitmap[dst] = copy_one(input, get_offset(input, src + 2)); - dst += 1; - } - - dst - start_pos -} - -fn copy_one(input: &[u8], src: usize) -> u8 { - if input[src + 1] == 0 { - input[src] - } else if get_offset(input, src) == src { - 0 - } else { - copy_one(input, get_offset(input, src)) - } -} diff --git a/src/formats/cz0.rs b/src/formats/cz0.rs index 1952dec..77db778 100644 --- a/src/formats/cz0.rs +++ b/src/formats/cz0.rs @@ -1,13 +1,16 @@ -use std::io::{self, Cursor, Read}; +use std::{fs::File, io::{self, BufWriter, Cursor, Read, Seek, Write}, path::PathBuf}; -use byteorder::{LittleEndian, ReadBytesExt}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use crate::cz_common::{CommonHeader, CzError, CzHeader, CzImage}; +use crate::common::{CommonHeader, CzError, CzHeader, CzImage}; -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct Cz0Header { /// Common CZ# header - common: CommonHeader, + pub common: CommonHeader, + + /// Unknown bytes + unknown_1: [u8; 5], /// Width of cropped image area pub crop_width: u16, @@ -26,6 +29,8 @@ pub struct Cz0Header { /// Offset height pub offset_height: Option, + + unknown_2: Option<[u8; 4]>, } #[derive(Debug)] @@ -35,7 +40,7 @@ pub struct Cz0Image { } impl CzHeader for Cz0Header { - fn new(bytes: &mut Cursor<&[u8]>) -> Result + fn new(bytes: &mut T) -> Result where Self: Sized, { @@ -45,8 +50,8 @@ impl CzHeader for Cz0Header { return Err(CzError::VersionMismatch); } - let _unknown = bytes.read_u32::()?; - let _unknown = bytes.read_u8()?; + let mut unknown_1 = [0u8; 5]; + bytes.read_exact(&mut unknown_1)?; let crop_width = bytes.read_u16::()?; let crop_height = bytes.read_u16::()?; @@ -56,14 +61,22 @@ impl CzHeader for Cz0Header { let mut offset_width = None; let mut offset_height = None; - if common.header_length() > 28 { + let mut unknown_2 = None; + if common.length() > 28 { offset_width = Some(bytes.read_u16::()?); offset_height = Some(bytes.read_u16::()?); + + let mut un_2 = [0u8; 4]; + bytes.read_exact(&mut un_2)?; + + unknown_2 = Some(un_2); } Ok(Self { common, + unknown_1, + crop_width, crop_height, @@ -72,6 +85,8 @@ impl CzHeader for Cz0Header { offset_width, offset_height, + + unknown_2, }) } @@ -79,8 +94,8 @@ impl CzHeader for Cz0Header { self.common.version() } - fn header_length(&self) -> usize { - self.common.header_length() + fn length(&self) -> usize { + self.common.length() } fn width(&self) -> u16 { @@ -94,32 +109,64 @@ impl CzHeader for Cz0Header { fn depth(&self) -> u16 { self.common.depth() } + + fn color_block(&self) -> u8 { + self.common.color_block() + } + + fn to_bytes(&self) -> Result, io::Error> { + let mut buf = vec![]; + + buf.write_all(&self.common.to_bytes()?)?; + buf.write_all(&self.unknown_1)?; + buf.write_u16::(self.crop_width)?; + buf.write_u16::(self.crop_height)?; + buf.write_u16::(self.bounds_width)?; + buf.write_u16::(self.bounds_height)?; + + if self.length() > 28 { + buf.write_u16::(self.offset_width.unwrap())?; + buf.write_u16::(self.offset_height.unwrap())?; + buf.write_all(&self.unknown_2.unwrap())?; + } + + Ok(buf) + } } impl CzImage for Cz0Image { type Header = Cz0Header; - fn decode(bytes: &[u8]) -> Result { - let mut input = Cursor::new(bytes); - + fn decode(bytes: &mut T) -> Result { // Get the header from the input - let header = Cz0Header::new(&mut input)?; + let header = Cz0Header::new(bytes)?; + bytes.seek(io::SeekFrom::Start(header.length() as u64)); // Get the rest of the file, which is the bitmap - let mut bitmap = vec![0u8; bytes.len() - header.header_length()]; - input.read_exact(&mut bitmap)?; + let mut bitmap = vec![]; + bytes.read_to_end(&mut bitmap)?; Ok(Self { header, bitmap }) } fn save_as_png(&self, name: &str) -> Result<(), image::error::ImageError> { - Ok(image::save_buffer( + image::save_buffer( name, &self.bitmap, self.header.width() as u32, self.header.height() as u32, image::ExtendedColorType::Rgba8, - )?) + ) + } + + fn save_as_cz>(&self, path: T) -> Result<(), CzError> { + let mut output_file = BufWriter::new(File::create(path.into())?); + + output_file.write_all(&self.header().to_bytes()?)?; + output_file.write_all(&self.bitmap)?; + output_file.flush()?; + + Ok(()) } fn header(&self) -> &Self::Header { @@ -134,14 +181,10 @@ impl CzImage for Cz0Image { self.bitmap } - fn save_as_cz(&self) -> Result<(), CzError> { - todo!() - } + fn set_bitmap(&mut self, bitmap: &[u8], header: &Self::Header) { + self.bitmap = bitmap.to_vec(); - fn set_bitmap(&mut self, bitmap: Vec, header: Self::Header) { - self.bitmap = bitmap; - - self.header = header; + self.header = *header; } } diff --git a/src/formats/cz1.rs b/src/formats/cz1.rs index b618776..154af5d 100644 --- a/src/formats/cz1.rs +++ b/src/formats/cz1.rs @@ -1,12 +1,14 @@ -use crate::cz_common::{ - decompress, parse_chunk_info, parse_colormap, CommonHeader, CzError, CzHeader, CzImage, -}; +use byteorder::ReadBytesExt; use image::{ImageFormat, Rgba}; -use std::io::Cursor; +use std::{fs::File, io::{BufWriter, Cursor, Read, Seek, SeekFrom, Write}, path::PathBuf}; + +use crate::compression::{decompress, parse_chunk_info}; +use crate::common::{apply_palette, parse_colormap, CommonHeader, CzError, CzHeader, CzImage}; #[derive(Debug, Clone)] pub struct Cz1Image { header: CommonHeader, + raw_bitmap: Option>, bitmap: Vec, palette: Vec>, } @@ -14,37 +16,48 @@ pub struct Cz1Image { impl CzImage for Cz1Image { type Header = CommonHeader; - fn decode(bytes: &[u8]) -> Result { - let mut input = Cursor::new(bytes); - + fn decode(bytes: &mut T) -> Result { // Get the header from the input - let header = CommonHeader::new(&mut input).unwrap(); + let header = CommonHeader::new(bytes).unwrap(); + bytes.seek(SeekFrom::Start(header.length() as u64)); + + if header.version() != 1 { + return Err(CzError::VersionMismatch) + } // 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())?); + palette = Some(parse_colormap(bytes, 1 << header.depth())?); } - let chunk_info = parse_chunk_info(&mut input)?; + let chunk_info = parse_chunk_info(bytes)?; - if chunk_info.total_size_compressed as usize > bytes.len() { + /* + if chunk_info.total_size_compressed as usize > bytes. { return Err(CzError::InvalidFormat { expected: chunk_info.total_size_compressed, got: bytes.len(), }); } + */ - let mut bitmap = decompress(&mut input, chunk_info).unwrap(); + let mut bitmap = decompress(bytes, &chunk_info).unwrap(); + let mut raw_bitmap = None; // Apply the palette if it exists if let Some(pal) = &palette { - apply_palette(&mut bitmap, pal); + if let Some(raw) = &mut raw_bitmap { + bitmap.clone_into(raw); + } + + bitmap = apply_palette(&mut bitmap, pal); } let image = Self { header, bitmap, + raw_bitmap, palette: palette.unwrap(), }; @@ -52,16 +65,14 @@ impl CzImage for Cz1Image { } fn save_as_png(&self, name: &str) -> Result<(), image::error::ImageError> { - let img = image::RgbaImage::from_raw( + image::save_buffer_with_format( + name, + &self.bitmap, self.header.width() as u32, self.header.height() as u32, - self.bitmap.clone(), + image::ExtendedColorType::Rgba8, + ImageFormat::Png ) - .expect("Creating image failed"); - - img.save_with_format(name, ImageFormat::Png)?; - - Ok(()) } fn header(&self) -> &Self::Header { @@ -76,22 +87,17 @@ impl CzImage for Cz1Image { self.bitmap } - fn save_as_cz(&self) -> Result<(), CzError> { - todo!() + fn save_as_cz>(&self, path: T) -> Result<(), CzError> { + let mut output_file = BufWriter::new(File::create(path.into())?); + + output_file.write_all(&self.header.to_bytes()?)?; + + output_file.flush()?; + + Ok(()) } - fn set_bitmap(&mut self, bitmap: Vec, header: Self::Header) { + fn set_bitmap(&mut self, bitmap: &[u8], header: &Self::Header) { todo!() } } - -fn apply_palette(input: &mut Vec, palette: &[Rgba]) { - 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 -} diff --git a/src/formats/cz3.rs b/src/formats/cz3.rs index 3480cec..c1d9447 100644 --- a/src/formats/cz3.rs +++ b/src/formats/cz3.rs @@ -1,9 +1,9 @@ -use std::io::Cursor; +use std::{io::{self, Cursor, Read, Seek, SeekFrom}, path::PathBuf}; use byteorder::{LittleEndian, ReadBytesExt}; -use image::ImageFormat; -use crate::cz_common::{decompress, parse_chunk_info, CommonHeader, CzError, CzHeader, CzImage}; +use crate::compression::{decompress, parse_chunk_info}; +use crate::common::{CommonHeader, CzError, CzHeader, CzImage}; #[derive(Debug, Clone, Copy)] pub struct Cz3Header { @@ -30,7 +30,7 @@ pub struct Cz3Header { } impl CzHeader for Cz3Header { - fn new(bytes: &mut Cursor<&[u8]>) -> Result + fn new(bytes: &mut T) -> Result where Self: Sized, { @@ -40,7 +40,8 @@ impl CzHeader for Cz3Header { return Err(CzError::VersionMismatch); } - let _unknown = bytes.read_u48::()?; + let mut unknown_1 = [0u8; 5]; + bytes.read_exact(&mut unknown_1)?; let crop_width = bytes.read_u16::()?; let crop_height = bytes.read_u16::()?; @@ -50,7 +51,7 @@ impl CzHeader for Cz3Header { let mut offset_width = None; let mut offset_height = None; - if common.header_length() > 28 { + if common.length() > 28 { offset_width = Some(bytes.read_u16::()?); offset_height = Some(bytes.read_u16::()?); } @@ -73,8 +74,8 @@ impl CzHeader for Cz3Header { self.common.version() } - fn header_length(&self) -> usize { - self.common.header_length() + fn length(&self) -> usize { + self.common.length() } fn width(&self) -> u16 { @@ -88,6 +89,14 @@ impl CzHeader for Cz3Header { fn depth(&self) -> u16 { self.common.depth() } + + fn color_block(&self) -> u8 { + self.common.color_block() + } + + fn to_bytes(&self) -> Result, io::Error> { + todo!() + } } #[derive(Debug, Clone)] @@ -99,14 +108,13 @@ pub struct Cz3Image { impl CzImage for Cz3Image { type Header = Cz3Header; - fn decode(bytes: &[u8]) -> Result { - let mut input = Cursor::new(bytes); - let header = Cz3Header::new(&mut input)?; - input.set_position(header.header_length() as u64); + fn decode(bytes: &mut T) -> Result { + let header = Cz3Header::new(bytes)?; + bytes.seek(SeekFrom::Start(header.length() as u64)); - let block_info = parse_chunk_info(&mut input)?; + let block_info = parse_chunk_info(bytes)?; - let mut bitmap = decompress(&mut input, block_info)?; + let mut bitmap = decompress(bytes, &block_info)?; let stride = (header.width() * (header.depth() / 8)) as usize; let third = ((header.height() + 2) / 3) as usize; @@ -123,16 +131,13 @@ impl CzImage for Cz3Image { } fn save_as_png(&self, name: &str) -> Result<(), image::error::ImageError> { - let img = image::RgbaImage::from_raw( + image::save_buffer( + name, + &self.bitmap, self.header.width() as u32, self.header.height() as u32, - self.bitmap.clone(), + image::ExtendedColorType::Rgba8, ) - .expect("Creating image failed"); - - img.save_with_format(name, ImageFormat::Png)?; - - Ok(()) } fn header(&self) -> &Self::Header { @@ -147,11 +152,11 @@ impl CzImage for Cz3Image { self.bitmap } - fn save_as_cz(&self) -> Result<(), CzError> { + fn save_as_cz>(&self, path: T) -> Result<(), CzError> { todo!() } - fn set_bitmap(&mut self, bitmap: Vec, header: Self::Header) { + fn set_bitmap(&mut self, bitmap: &[u8], header: &Self::Header) { todo!() } } diff --git a/src/main.rs b/src/main.rs index 2f982f3..bcdaa3d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,23 @@ -pub mod cz_common; +pub mod common; +pub mod compression; pub mod formats { pub mod cz0; pub mod cz1; pub mod cz3; } +use common::CzImage; +pub use formats::cz0::Cz0Image; +pub use formats::cz1::Cz1Image; +pub use formats::cz3::Cz3Image; + // Generic tools use std::fs; -use crate::{cz_common::CzImage, formats::{cz0::Cz0Image, cz3::Cz3Image}}; - fn main() { - let input = fs::read("../test_files/Old_TestFiles/782.cz0").expect("Error, could not open image"); - let img_file = Cz0Image::decode(&input).unwrap(); - println!("{:#?}", img_file.header()); + let mut input = fs::File::open("../test_files/font_files/24-style1.cz1").unwrap(); + let img_file = Cz1Image::decode(&mut input).unwrap(); - img_file.save_as_png("test.png").unwrap(); + img_file.save_as_cz("test1.cz1").unwrap(); + img_file.save_as_png("test1.png").unwrap(); }