From 3911c73761b7ef25b4bd9918cbcdc9f332460b21 Mon Sep 17 00:00:00 2001 From: G2-Games Date: Mon, 1 Jul 2024 14:40:52 -0500 Subject: [PATCH] Increased speed of CZ3 and 4 decoding, feature gate for `image` --- cz/Cargo.toml | 5 +++- cz/src/binio.rs | 23 ++++++++++++---- cz/src/compression.rs | 8 +++--- cz/src/dynamic.rs | 1 + cz/src/formats/cz3.rs | 16 ++++++++---- cz/src/formats/cz4.rs | 61 +++++++++++++++++++++++-------------------- 6 files changed, 71 insertions(+), 43 deletions(-) diff --git a/cz/Cargo.toml b/cz/Cargo.toml index 2d48d13..c866a3e 100644 --- a/cz/Cargo.toml +++ b/cz/Cargo.toml @@ -6,8 +6,11 @@ description=""" A encoder/decoder for CZ# image files used in the LUCA System Engine. """ +[features] +png = ["dep:image"] + [dependencies] byteorder = "1.5.0" thiserror = "1.0.59" -image = { version = "0.25.1", default-features = false, features = ["png"] } imagequant = "4.3.1" +image = { version = "0.25", default-features = false, features = ["png"], optional = true } diff --git a/cz/src/binio.rs b/cz/src/binio.rs index 68be0e0..1d2d7cd 100644 --- a/cz/src/binio.rs +++ b/cz/src/binio.rs @@ -1,4 +1,9 @@ -pub struct BitIO { +pub enum BitError { + InputLength +} + + +pub struct BitIo { data: Vec, byte_offset: usize, bit_offset: usize, @@ -6,7 +11,8 @@ pub struct BitIO { byte_size: usize, } -impl BitIO { +impl BitIo { + /// Create a new BitIO reader and writer over some data pub fn new(data: Vec) -> Self { Self { data, @@ -16,20 +22,23 @@ impl BitIO { } } + /// Get the byte offset of the reader pub fn byte_offset(&self) -> usize { self.byte_offset } + /// Get the byte size of the reader pub fn byte_size(&self) -> usize { self.byte_size } + /// Get the current bytes up to `byte_size` in the reader pub fn bytes(&self) -> Vec { self.data[..self.byte_size].to_vec() } + /// Read some bits from the buffer pub fn read_bit(&mut self, bit_len: usize) -> u64 { - //print!("{}: ", bit_len); if bit_len > 8 * 8 { panic!() } @@ -54,6 +63,7 @@ impl BitIO { result } + /// Read some bytes from the buffer pub fn read(&mut self, byte_len: usize) -> u64 { if byte_len > 8 { panic!() @@ -66,6 +76,7 @@ impl BitIO { u64::from_le_bytes(padded_slice) } + /// Write some bits to the buffer pub fn write_bit(&mut self, data: u64, bit_len: usize) { if bit_len > 8 * 8 { panic!(); @@ -93,9 +104,9 @@ impl BitIO { self.byte_size = self.byte_offset + (self.bit_offset + 7) / 8; } - pub fn write(&mut self, data: u64, byte_len: usize) { + pub fn write(&mut self, data: u64, byte_len: usize) -> Result<(), BitError> { if byte_len > 8 { - panic!() + return Err(BitError::InputLength); } let mut padded_slice = [0u8; 8]; @@ -106,5 +117,7 @@ impl BitIO { self.byte_offset += byte_len; self.byte_size = self.byte_offset + (self.bit_offset + 7) / 8; + + Ok(()) } } diff --git a/cz/src/compression.rs b/cz/src/compression.rs index b2f88e2..c4d1dbf 100644 --- a/cz/src/compression.rs +++ b/cz/src/compression.rs @@ -4,7 +4,7 @@ use std::{ io::{Read, Seek, Write}, }; -use crate::binio::BitIO; +use crate::binio::BitIo; use crate::common::CzError; /// The size of compressed data in each chunk @@ -189,7 +189,7 @@ fn decompress_lzw2(input_data: &[u8], size: usize) -> Vec { let data_size = input_data.len(); data.extend_from_slice(&[0, 0]); - let mut bit_io = BitIO::new(data); + let mut bit_io = BitIo::new(data); let mut w = dictionary.get(&0).unwrap().clone(); let mut element; @@ -391,8 +391,8 @@ fn compress_lzw2(data: &[u8], size: usize, last: Vec) -> (usize, Vec, Ve element = last } - let mut bit_io = BitIO::new(vec![0u8; size + 2]); - let write_bit = |bit_io: &mut BitIO, code: u64| { + let mut bit_io = BitIo::new(vec![0u8; size + 2]); + let write_bit = |bit_io: &mut BitIo, code: u64| { if code > 0x7FFF { bit_io.write_bit(1, 1); bit_io.write_bit(code, 18); diff --git a/cz/src/dynamic.rs b/cz/src/dynamic.rs index e73a440..ad3ceec 100644 --- a/cz/src/dynamic.rs +++ b/cz/src/dynamic.rs @@ -195,6 +195,7 @@ impl DynamicCz { /// Internally, the [`DynamicCz`] struct operates on 32-bit RGBA values, /// which is the highest encountered in CZ# files, therefore saving them /// as a PNG of the same or better quality is lossless. + #[cfg(feature = "png")] pub fn save_as_png>( &self, path: &P, diff --git a/cz/src/formats/cz3.rs b/cz/src/formats/cz3.rs index a598f97..3527bb3 100644 --- a/cz/src/formats/cz3.rs +++ b/cz/src/formats/cz3.rs @@ -1,5 +1,6 @@ use byteorder::{ReadBytesExt, WriteBytesExt}; use std::io::{Read, Seek, SeekFrom, Write}; +use std::time::Instant; use crate::common::{CommonHeader, CzError}; use crate::compression::{compress, decompress, get_chunk_info}; @@ -11,8 +12,13 @@ pub fn decode( let block_info = get_chunk_info(bytes)?; bytes.seek(SeekFrom::Start(block_info.length as u64))?; + let timer = Instant::now(); let bitmap = decompress(bytes, &block_info)?; + dbg!(timer.elapsed()); + + let timer = Instant::now(); let bitmap = line_diff(header, &bitmap); + dbg!(timer.elapsed()); Ok(bitmap) } @@ -46,17 +52,17 @@ fn line_diff(header: &CommonHeader, data: &[u8]) -> Vec { let pixel_byte_count = header.depth() >> 3; let line_byte_count = (width * pixel_byte_count as u32) as usize; - let mut curr_line: Vec; - let mut prev_line: Vec = Vec::with_capacity(line_byte_count); + let mut curr_line; + let mut prev_line = Vec::with_capacity(line_byte_count); let mut i = 0; for y in 0..height { curr_line = data[i..i + line_byte_count].to_vec(); if y % block_height as u32 != 0 { - for x in 0..line_byte_count { - curr_line[x] = u8::wrapping_add(curr_line[x], prev_line[x]) - } + curr_line.iter_mut().zip(&prev_line).for_each(|(curr_p, prev_p)| { + *curr_p = curr_p.wrapping_add(*prev_p) + }); } prev_line.clone_from(&curr_line); diff --git a/cz/src/formats/cz4.rs b/cz/src/formats/cz4.rs index f7c3f46..74dab07 100644 --- a/cz/src/formats/cz4.rs +++ b/cz/src/formats/cz4.rs @@ -1,6 +1,6 @@ use byteorder::{ReadBytesExt, WriteBytesExt}; -use image::RgbaImage; use std::io::{Read, Seek, SeekFrom, Write}; +use std::time::Instant; use crate::common::{CommonHeader, CzError}; use crate::compression::{compress, decompress, get_chunk_info}; @@ -12,14 +12,15 @@ pub fn decode( let block_info = get_chunk_info(bytes)?; bytes.seek(SeekFrom::Start(block_info.length as u64))?; + let timer = Instant::now(); let data = decompress(bytes, &block_info)?; + dbg!(timer.elapsed()); + let timer = Instant::now(); + let output = line_diff(header, &data); + dbg!(timer.elapsed()); - let mut picture = image::RgbaImage::new(header.width() as u32, header.height() as u32); - - line_diff(&mut picture, &data); - - Ok(picture.into_raw()) + Ok(output) } pub fn encode( @@ -38,9 +39,12 @@ pub fn encode( Ok(()) } -fn line_diff(picture: &mut RgbaImage, data: &[u8]) { - let width = picture.width(); - let height = picture.height(); +fn line_diff(header: &CommonHeader, data: &[u8]) -> Vec { + let width = header.width() as u32; + let height = header.height() as u32; + + let mut output_buf = Vec::with_capacity((width * height * 4) as usize); + let block_height = (f32::ceil(height as f32 / 3.0) as u16) as u32; let mut curr_line; @@ -49,37 +53,38 @@ fn line_diff(picture: &mut RgbaImage, data: &[u8]) { let mut curr_alpha; let mut prev_alpha = Vec::with_capacity(width as usize); - let pcount = (width * height * 3) as usize; - - let mut i = 0; - let mut z = 0; + let mut rgb_index = 0; + let mut alpha_index = (width * height * 3) as usize; for y in 0..height { - curr_line = data[i..i + width as usize * 3].to_vec(); - curr_alpha = data[pcount + z..pcount + z + width as usize].to_vec(); + curr_line = data[rgb_index..rgb_index + width as usize * 3].to_vec(); + curr_alpha = data[alpha_index..alpha_index + width as usize].to_vec(); if y % block_height != 0 { - for x in 0..(width as usize * 3) { - curr_line[x] = curr_line[x].wrapping_add(prev_line[x]) - } - for x in 0..width as usize { - curr_alpha[x] = curr_alpha[x].wrapping_add(prev_alpha[x]) - } + curr_line.iter_mut().zip(&prev_line).for_each(|(curr_p, prev_p)| { + *curr_p = curr_p.wrapping_add(*prev_p); + }); + curr_alpha.iter_mut().zip(&prev_alpha).for_each(|(curr_a, prev_a)| { + *curr_a = curr_a.wrapping_add(*prev_a); + }); } for x in 0..width as usize { - picture.get_pixel_mut(x as u32, y).0 = [ - curr_line[x * 3], - curr_line[x * 3 + 1], - curr_line[x * 3 + 2], + let pos = x * 3; + output_buf.extend_from_slice(&[ + curr_line[pos], + curr_line[pos + 1], + curr_line[pos + 2], curr_alpha[x], - ]; + ]); } prev_line.clone_from(&curr_line); prev_alpha.clone_from(&curr_alpha); - i += width as usize * 3; - z += width as usize; + rgb_index += width as usize * 3; + alpha_index += width as usize; } + + output_buf } fn diff_line(header: &CommonHeader, input: &[u8]) -> Vec {