diff --git a/cz/Cargo.toml b/cz/Cargo.toml index e011fde..2d48d13 100644 --- a/cz/Cargo.toml +++ b/cz/Cargo.toml @@ -10,4 +10,4 @@ A encoder/decoder for CZ# image files used in the LUCA System Engine. byteorder = "1.5.0" thiserror = "1.0.59" image = { version = "0.25.1", default-features = false, features = ["png"] } -quantizr = "1.4.2" +imagequant = "4.3.1" diff --git a/cz/src/color.rs b/cz/src/color.rs index 0de0d69..644335d 100644 --- a/cz/src/color.rs +++ b/cz/src/color.rs @@ -4,15 +4,28 @@ use std::{ }; use byteorder::ReadBytesExt; -use image::Rgba; -use quantizr::Image; +use imagequant::Attributes; use crate::common::{CommonHeader, CzError}; +#[derive(Debug)] +pub struct Rgba(pub [u8; 4]); + +impl From<[u8; 4]> for Rgba { + fn from(value: [u8; 4]) -> Self { + Self([value[0], value[1], value[2], value[3]]) + } +} + +#[derive(Debug)] +pub struct Palette { + pub colors: Vec +} + pub fn get_palette( input: &mut T, num_colors: usize, -) -> Result>, CzError> { +) -> Result { let mut colormap = Vec::with_capacity(num_colors); let mut rgba_buf = [0u8; 4]; @@ -21,16 +34,16 @@ pub fn get_palette( colormap.push(rgba_buf.into()); } - Ok(colormap) + Ok(Palette { colors: colormap }) } /// Take a bitmap of indicies, and map a given palette to it, returning a new /// RGBA bitmap -pub fn apply_palette(input: &[u8], palette: &[Rgba]) -> Result, CzError> { +pub fn apply_palette(input: &[u8], palette: &Palette) -> Result, CzError> { let mut output_map = Vec::new(); for byte in input.iter() { - let color = palette.get(*byte as usize); + let color = palette.colors.get(*byte as usize); if let Some(color) = color { output_map.extend_from_slice(&color.0); } else { @@ -41,7 +54,7 @@ pub fn apply_palette(input: &[u8], palette: &[Rgba]) -> Result, CzEr Ok(output_map) } -pub fn rgba_to_indexed(input: &[u8], palette: &[Rgba]) -> Result, CzError> { +pub fn rgba_to_indexed(input: &[u8], palette: &Palette) -> Result, CzError> { let mut output_map = Vec::new(); let mut cache = HashMap::new(); @@ -49,7 +62,7 @@ pub fn rgba_to_indexed(input: &[u8], palette: &[Rgba]) -> Result, Cz let value = match cache.get(rgba) { Some(val) => *val, None => { - let value = palette.iter().position(|e| e.0 == rgba).unwrap_or_default() as u8; + let value = palette.colors.iter().position(|e| e.0 == rgba).unwrap_or_default() as u8; cache.insert(rgba, value); value } @@ -64,28 +77,32 @@ pub fn rgba_to_indexed(input: &[u8], palette: &[Rgba]) -> Result, Cz pub fn indexed_gen_palette( input: &[u8], header: &CommonHeader, -) -> Result<(Vec, Vec>), CzError> { +) -> Result<(Vec, Vec), CzError> { let size = (header.width() as u32 * header.height() as u32) * 4; - let mut buf = vec![0; size as usize]; + let mut buf: Vec = vec![0; size as usize]; buf[..input.len()].copy_from_slice(input); + let buf: Vec = buf + .windows(4) + .step_by(4) + .map(|c| imagequant::RGBA::new(c[0], c[1], c[2], c[3])) + .collect(); - let image = Image::new(&buf, header.width() as usize, header.height() as usize).unwrap(); + let mut quant = Attributes::new(); + quant.set_speed(1).unwrap(); - let mut opts = quantizr::Options::default(); - opts.set_max_colors(1 << header.depth()).unwrap(); + let mut image = quant.new_image( + buf, + header.width() as usize, + header.height() as usize, + 0.0 + ).unwrap(); - let mut result = quantizr::QuantizeResult::quantize(&image, &opts); - result.set_dithering_level(0.5).unwrap(); + let mut quant_result = quant.quantize(&mut image).unwrap(); - let mut indicies = vec![0u8; header.width() as usize * header.height() as usize]; - result.remap_image(&image, indicies.as_mut_slice()).unwrap(); - - let palette = result.get_palette(); + let (palette, indicies) = quant_result.remapped(&mut image).unwrap(); let gen_palette = palette - .entries - .as_slice() .iter() .map(|c| Rgba([c.r, c.g, c.b, c.a])) .collect(); @@ -93,7 +110,7 @@ pub fn indexed_gen_palette( Ok((indicies, gen_palette)) } -pub fn _default_palette() -> Vec> { +pub fn _default_palette() -> Vec { let mut colormap = Vec::new(); for i in 0..=0xFF { diff --git a/cz/src/dynamic.rs b/cz/src/dynamic.rs index 8ee564f..07ac7fd 100644 --- a/cz/src/dynamic.rs +++ b/cz/src/dynamic.rs @@ -1,5 +1,4 @@ use byteorder::ReadBytesExt; -use image::Rgba; use std::{ fs::File, io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write}, @@ -7,7 +6,7 @@ use std::{ }; use crate::{ - color::{apply_palette, get_palette, indexed_gen_palette, rgba_to_indexed}, + color::{apply_palette, get_palette, indexed_gen_palette, rgba_to_indexed, Palette}, common::{CommonHeader, CzError, CzVersion, ExtendedHeader}, formats::{cz0, cz1, cz2, cz3, cz4}, }; @@ -18,7 +17,7 @@ use crate::{ pub struct DynamicCz { header_common: CommonHeader, header_extended: Option, - palette: Option>>, + palette: Option, bitmap: Vec, } @@ -136,7 +135,7 @@ impl DynamicCz { // Use the existing palette to palette the image output_bitmap = rgba_to_indexed(self.bitmap(), pal)?; - for rgba in pal { + for rgba in &pal.colors { out_file.write_all(&rgba.0)?; } } else { @@ -258,13 +257,13 @@ impl DynamicCz { /// Retrieve a reference to the palette if it exists, otherwise [`None`] /// is returned - pub fn palette(&self) -> &Option>> { + pub fn palette(&self) -> &Option { &self.palette } /// Retrieve a mutable reference to the palette if it exists, otherwise /// [`None`] is returned - pub fn palette_mut(&mut self) -> &mut Option>> { + pub fn palette_mut(&mut self) -> &mut Option { &mut self.palette } diff --git a/utils/src/main.rs b/utils/src/main.rs index 70a871a..718647c 100644 --- a/utils/src/main.rs +++ b/utils/src/main.rs @@ -204,6 +204,18 @@ fn main() { cz.set_bitmap(repl_img.into_raw()); cz.remove_palette(); + if let Some(ver) = version { + match cz.header_mut().set_version(*ver) { + Ok(_) => (), + Err(_) => { + Error::raw( + ErrorKind::ValueValidation, + format!("Invalid CZ Version {}; expected 0, 1, 2, 3, or 4\n", ver) + ).exit() + }, + }; + } + cz.save_as_cz(&final_path).unwrap(); } } else {