diff --git a/cz/src/common.rs b/cz/src/common.rs index 6b00e1d..aa5eb5d 100644 --- a/cz/src/common.rs +++ b/cz/src/common.rs @@ -341,7 +341,8 @@ pub fn rgba_to_indexed(input: &[u8], palette: &[[u8; 4]]) -> Result, CzE let mut output_map = Vec::new(); for rgba in input.windows(4).step_by(4) { - dbg!(rgba); + let index = palette.iter().position(|e| e == rgba).unwrap_or_default(); + output_map.push(index as u8); } Ok(output_map) diff --git a/cz/src/compression.rs b/cz/src/compression.rs index fb8fb82..2f2abde 100644 --- a/cz/src/compression.rs +++ b/cz/src/compression.rs @@ -1,7 +1,6 @@ -use byteorder::{LittleEndian, ReadBytesExt}; -use image::RgbaImage; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashMap}, io::{Read, Seek, Write}, }; @@ -37,6 +36,34 @@ pub struct CompressionInfo { pub length: usize, } +impl Default for CompressionInfo { + fn default() -> Self { + Self { + chunk_count: 0, + total_size_compressed: 0, + total_size_raw: 0, + chunks: Vec::new(), + length: 0 + } + } +} + +impl CompressionInfo { + pub fn write_into( + &self, + output: &mut T, + ) -> Result<(), std::io::Error> { + output.write_u32::(self.chunk_count as u32)?; + + for chunk in &self.chunks { + output.write_u32::(chunk.size_compressed as u32)?; + output.write_u32::(chunk.size_raw as u32)?; + } + + Ok(()) + } +} + /// Get info about the compression chunks /// /// These are defined by a length value, followed by the number of data chunks @@ -123,7 +150,7 @@ pub fn decompress_2( pub fn decompress_lzw2(input_data: &[u8], size: usize) -> Vec { let mut data = input_data.to_vec(); data[0..2].copy_from_slice(&[0, 0]); - let mut dictionary = BTreeMap::new(); + let mut dictionary = HashMap::new(); for i in 0..256 { dictionary.insert(i as u64, vec![i as u8]); } @@ -174,7 +201,12 @@ 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 { +fn copy_range( + bitmap: &mut Vec, + input: &[u8], + src: usize, + dst: usize +) -> usize { let mut dst = dst; let start_pos = dst; @@ -257,45 +289,101 @@ pub fn line_diff(header: &T, data: &[u8]) -> Vec { output_buf } -pub fn line_diff_cz4(picture: &mut RgbaImage, pixel_byte_count: usize, data: &[u8]) { - let width = picture.width(); - let height = picture.height(); - let block_height = (f32::ceil(height as f32 / 3.0) as u16) as u32; - let mut curr_line; - let mut prev_line = vec![0u8; width as usize * pixel_byte_count]; - - let mut i = 0; - for y in 0..height { - curr_line = data[i..i + width as usize * pixel_byte_count].to_vec(); - - if y % block_height != 0 { - for x in 0..(width as usize * pixel_byte_count) { - curr_line[x] = u8::wrapping_add(curr_line[x], prev_line[x]) - } - } - - for x in 0..width as usize { - if pixel_byte_count == 1 { - picture.get_pixel_mut(x as u32, y).0[3] = curr_line[x]; - } else if pixel_byte_count == 4 { - picture.get_pixel_mut(x as u32, y).0 = [ - curr_line[x * pixel_byte_count], - curr_line[x * pixel_byte_count + 1], - curr_line[x * pixel_byte_count + 2], - curr_line[x * pixel_byte_count + 3], - ]; - } else if pixel_byte_count == 3 { - picture.get_pixel_mut(x as u32, y).0 = [ - curr_line[x * pixel_byte_count], - curr_line[x * pixel_byte_count + 1], - curr_line[x * pixel_byte_count + 2], - 0xFF, - ]; - } - } - - prev_line.clone_from(&curr_line); - i += width as usize * pixel_byte_count; +pub fn compress( + data: &[u8], + size: usize, +) -> (Vec, CompressionInfo) { + let mut size = size; + if size == 0 { + size = 0xFEFD } + + let mut part_data; + + let mut offset = 0; + let mut count = 0; + let mut last = String::new(); + + let mut output_buf: Vec = vec![]; + let mut output_info = CompressionInfo { + total_size_raw: data.len(), + ..Default::default() + }; + + loop { + (count, part_data, last) = compress_lzw(&data[offset..], size, last); + if count == 0 { + break + } + offset += count; + + for d in &part_data { + output_buf.write(&d.to_le_bytes()).unwrap(); + } + + output_info.chunks.push(ChunkInfo { + size_compressed: part_data.len(), + size_raw: count + }); + + output_info.chunk_count += 1; + } + + output_info.total_size_compressed = output_buf.len() / 2; + + (output_buf, output_info) +} + +fn compress_lzw(data: &[u8], size: usize, last: String) -> (usize, Vec, String) { + let mut count = 0; + let mut dictionary = HashMap::new(); + for i in 0..256 { + dictionary.insert(i.to_string(), i as u16); + } + let mut dictionary_count = (dictionary.len() + 1) as u16; + + + let mut element = String::new(); + if last.len() != 0 { + element = last.clone() + } + + let mut compressed = Vec::with_capacity(size); + for c in data { + let mut entry = element.clone(); + entry.push_str(&c.to_string()); + + if dictionary.get(&entry).is_some() { + element = entry + } else { + compressed.push(*dictionary.get(&element).unwrap()); + dictionary.insert(entry, dictionary_count); + element = c.to_string(); + dictionary_count += 1; + } + + count += 1; + + if size > 0 && compressed.len() == size { + break + } + } + + let last_element = element; + if compressed.len() == 0 { + if last_element.len() != 0 { + for c in last_element.bytes() { + compressed.push(*dictionary.get(&c.to_string()).unwrap()); + } + } + return (count, compressed, String::new()) + } else if compressed.len() < size { + if last_element.len() != 0 { + compressed.push(*dictionary.get(&last_element).unwrap()); + } + return (count, compressed, String::new()) + } + + (count, compressed, last_element) } diff --git a/cz/src/dynamic.rs b/cz/src/dynamic.rs index 7b9454b..c210b43 100644 --- a/cz/src/dynamic.rs +++ b/cz/src/dynamic.rs @@ -99,14 +99,18 @@ impl DynamicCz { let output_bitmap; match &self.palette { Some(pal) if self.header_common.depth() <= 8 => { - output_bitmap = rgba_to_indexed(&self.bitmap(), &pal)? + output_bitmap = rgba_to_indexed(&self.bitmap(), &pal)?; + + for rgba in pal { + out_file.write_all(rgba)?; + } }, _ => output_bitmap = self.bitmap().clone() } match self.header_common.version() { - CzVersion::CZ0 => cz0::encode(&mut out_file, &self.bitmap)?, - CzVersion::CZ1 => todo!(), + CzVersion::CZ0 => cz0::encode(&mut out_file, &output_bitmap)?, + CzVersion::CZ1 => cz1::encode(&mut out_file, &output_bitmap)?, CzVersion::CZ2 => todo!(), CzVersion::CZ3 => todo!(), CzVersion::CZ4 => todo!(), diff --git a/cz/src/formats/cz1.rs b/cz/src/formats/cz1.rs index 2265165..b52b44b 100644 --- a/cz/src/formats/cz1.rs +++ b/cz/src/formats/cz1.rs @@ -2,13 +2,15 @@ use byteorder::{ReadBytesExt, WriteBytesExt}; use std::io::{Read, Seek, SeekFrom, Write}; use crate::common::CzError; -use crate::compression::{decompress, get_chunk_info}; +use crate::compression::{compress, decompress, get_chunk_info}; pub fn decode(bytes: &mut T) -> Result, CzError> { // Get the information about the compressed chunks let block_info = get_chunk_info(bytes)?; bytes.seek(SeekFrom::Start(block_info.length as u64))?; + dbg!(&block_info); + // Get the bitmap let bitmap = decompress(bytes, &block_info).unwrap(); @@ -19,7 +21,13 @@ pub fn encode( output: &mut T, bitmap: &[u8] ) -> Result<(), CzError> { - output.write_all(bitmap)?; + let (compressed_data, compressed_info) = compress(bitmap, 65277); + + dbg!(&compressed_info); + + compressed_info.write_into(output)?; + + output.write_all(&compressed_data)?; Ok(()) } diff --git a/cz/src/formats/cz4.rs b/cz/src/formats/cz4.rs index 7dff272..ab02f55 100644 --- a/cz/src/formats/cz4.rs +++ b/cz/src/formats/cz4.rs @@ -1,8 +1,9 @@ use std::io::{Read, Seek, SeekFrom}; use byteorder::ReadBytesExt; +use image::RgbaImage; use crate::common::{CommonHeader, CzError, CzHeader}; -use crate::compression::{decompress, line_diff_cz4, get_chunk_info}; +use crate::compression::{decompress, get_chunk_info}; pub fn decode(bytes: &mut T, header: &CommonHeader) -> Result, CzError> { let block_info = get_chunk_info(bytes)?; @@ -22,3 +23,49 @@ pub fn decode(bytes: &mut T, header: &CommonHeade Ok(picture.into_raw()) } + +pub fn line_diff_cz4( + picture: &mut RgbaImage, + pixel_byte_count: usize, data: &[u8] +) { + let width = picture.width(); + let height = picture.height(); + let block_height = (f32::ceil(height as f32 / 3.0) as u16) as u32; + + let mut curr_line; + let mut prev_line = vec![0u8; width as usize * pixel_byte_count]; + + let mut i = 0; + for y in 0..height { + curr_line = data[i..i + width as usize * pixel_byte_count].to_vec(); + + if y % block_height != 0 { + for x in 0..(width as usize * pixel_byte_count) { + curr_line[x] = u8::wrapping_add(curr_line[x], prev_line[x]) + } + } + + for x in 0..width as usize { + if pixel_byte_count == 1 { + picture.get_pixel_mut(x as u32, y).0[3] = curr_line[x]; + } else if pixel_byte_count == 4 { + picture.get_pixel_mut(x as u32, y).0 = [ + curr_line[x * pixel_byte_count], + curr_line[x * pixel_byte_count + 1], + curr_line[x * pixel_byte_count + 2], + curr_line[x * pixel_byte_count + 3], + ]; + } else if pixel_byte_count == 3 { + picture.get_pixel_mut(x as u32, y).0 = [ + curr_line[x * pixel_byte_count], + curr_line[x * pixel_byte_count + 1], + curr_line[x * pixel_byte_count + 2], + 0xFF, + ]; + } + } + + prev_line.clone_from(&curr_line); + i += width as usize * pixel_byte_count; + } +} diff --git a/utils/src/main.rs b/utils/src/main.rs index 3cbe0ee..59f5e94 100644 --- a/utils/src/main.rs +++ b/utils/src/main.rs @@ -1,35 +1,7 @@ -use std::{fs::DirBuilder, time::{Duration, Instant}}; - use cz::dynamic::DynamicCz; -use walkdir::WalkDir; fn main() { - let _ = DirBuilder::new().create("test"); + let img = DynamicCz::open("../../test_files/x5a3bvy.cz1").unwrap(); - let mut total_time = Duration::default(); - let mut num_images = 0; - for entry in WalkDir::new("../../test_files/") { - let entry = entry.unwrap(); - if entry.path().is_dir() { - continue; - } - - let timer = Instant::now(); - let img = match DynamicCz::open(entry.path()) { - Ok(img) => img, - Err(err) => { - println!("{}: {:?}", entry.path().file_name().unwrap().to_string_lossy(), err); - continue; - }, - }; - let elapsed = timer.elapsed(); - total_time += elapsed; - num_images += 1; - - img.save_as_png( - &format!("test/{}.png", entry.path().file_name().unwrap().to_string_lossy()) - ).unwrap(); - } - - dbg!(total_time / num_images); + img.save_as_cz("test.cz1").unwrap(); }