From 4f2f192fa11822ed8108109d4c9fbcf2853899f0 Mon Sep 17 00:00:00 2001 From: G2-Games Date: Thu, 4 Jul 2024 23:14:00 -0500 Subject: [PATCH] Moved to crate `rgb`, moved `cz::open()` out of `DynamicCz`, made writing generic --- Cargo.toml | 3 ++ cz/Cargo.toml | 1 + cz/src/color.rs | 65 ++++++++++++++++++++++++------------- cz/src/dynamic.rs | 70 ++++++++++++++++++++-------------------- cz/src/lib.rs | 10 ++++++ experimental/src/main.rs | 6 +++- utils/src/main.rs | 7 ++-- 7 files changed, 99 insertions(+), 63 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index afef379..4980167 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,6 @@ unsafe_code = "forbid" [profile.production] inherits = "release" lto = true +strip = true +codegen-units = 1 +panic = "abort" diff --git a/cz/Cargo.toml b/cz/Cargo.toml index c866a3e..51b9de4 100644 --- a/cz/Cargo.toml +++ b/cz/Cargo.toml @@ -14,3 +14,4 @@ byteorder = "1.5.0" thiserror = "1.0.59" imagequant = "4.3.1" image = { version = "0.25", default-features = false, features = ["png"], optional = true } +rgb = "0.8.40" diff --git a/cz/src/color.rs b/cz/src/color.rs index d8c6795..15e51b1 100644 --- a/cz/src/color.rs +++ b/cz/src/color.rs @@ -2,26 +2,39 @@ use std::{ collections::HashMap, io::{Read, Seek}, }; - use byteorder::ReadBytesExt; use imagequant::Attributes; +use rgb::{ComponentSlice, RGBA8}; use crate::common::{CommonHeader, CzError}; -#[derive(Debug, Clone, Copy)] -pub struct Rgba(pub [u8; 4]); +/// A palette of RGBA values for indexed color +#[derive(Debug, Clone)] +pub struct Palette { + colors: Vec +} -impl From<[u8; 4]> for Rgba { - fn from(value: [u8; 4]) -> Self { - Self([value[0], value[1], value[2], value[3]]) +impl Palette { + /// Get the list of colors from the palette + pub fn colors(&self) -> &Vec { + &self.colors + } + + /// Consume the palette, returning a list of colors + pub fn into_colors(self) -> Vec { + self.colors + } + + pub fn len(&self) -> usize { + self.colors.len() + } + + pub fn get(&self, index: usize) -> Option<&RGBA8> { + self.colors.get(index) } } -#[derive(Debug)] -pub struct Palette { - pub colors: Vec -} - +/// Get a palette from the input stream, beginning where the palette starts. pub fn get_palette( input: &mut T, num_colors: usize, @@ -37,15 +50,15 @@ pub fn get_palette( 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: &Palette) -> Result, CzError> { +/// Takes an indexed color bitmap and maps a given palette to it, returning an +/// RGBA bitmap. +pub fn indexed_to_rgba(input: &[u8], palette: &Palette) -> Result, CzError> { let mut output_map = Vec::new(); for byte in input.iter() { - let color = palette.colors.get(*byte as usize); + let color = palette.get(*byte as usize); if let Some(color) = color { - output_map.extend_from_slice(&color.0); + output_map.extend_from_slice(color.as_slice()); } else { return Err(CzError::PaletteError); } @@ -54,6 +67,7 @@ pub fn apply_palette(input: &[u8], palette: &Palette) -> Result, CzError Ok(output_map) } +/// Takes an RGBA bitmap and maps the colors in it to indices of an indexed bitmap. pub fn rgba_to_indexed(input: &[u8], palette: &Palette) -> Result, CzError> { let mut output_map = Vec::new(); let mut cache = HashMap::new(); @@ -62,7 +76,11 @@ pub fn rgba_to_indexed(input: &[u8], palette: &Palette) -> Result, CzErr let value = match cache.get(rgba) { Some(val) => *val, None => { - let value = palette.colors.iter().position(|e| e.0 == rgba).unwrap_or_default() as u8; + let value = palette.colors() + .iter() + .position(|e| e.as_slice() == rgba) + .unwrap_or_default() as u8; + cache.insert(rgba, value); value } @@ -74,10 +92,11 @@ pub fn rgba_to_indexed(input: &[u8], palette: &Palette) -> Result, CzErr Ok(output_map) } +/// Generate and a bitmap for a given input of RGBA pixels. 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 = vec![0; size as usize]; @@ -102,22 +121,22 @@ pub fn indexed_gen_palette( let (palette, indicies) = quant_result.remapped(&mut image).unwrap(); - let gen_palette: Vec = palette + let gen_palette: Vec = palette .iter() - .map(|c| Rgba([c.r, c.g, c.b, c.a])) + .map(|c| RGBA8::from([c.r, c.g, c.b, c.a])) .collect(); - let mut output_palette = vec![Rgba([0, 0, 0, 0]); 256]; + let mut output_palette = vec![RGBA8::from([0, 0, 0, 0]); 256]; output_palette[0..gen_palette.len()].copy_from_slice(&gen_palette); Ok((indicies, output_palette)) } -pub fn _default_palette() -> Vec { +pub fn _default_palette() -> Vec { let mut colormap = Vec::new(); for i in 0..=0xFF { - colormap.push(Rgba([0xFF, 0xFF, 0xFF, i])) + colormap.push(RGBA8::from([0xFF, 0xFF, 0xFF, i])) } colormap diff --git a/cz/src/dynamic.rs b/cz/src/dynamic.rs index 0d78bc4..1dda8d5 100644 --- a/cz/src/dynamic.rs +++ b/cz/src/dynamic.rs @@ -1,34 +1,30 @@ use byteorder::ReadBytesExt; +use rgb::ComponentSlice; use std::{ fs::File, - io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write}, - path::Path, + io::{BufWriter, Read, Seek, SeekFrom, Write}, }; use crate::{ - color::{apply_palette, get_palette, indexed_gen_palette, rgba_to_indexed, Palette}, + color::{get_palette, indexed_gen_palette, indexed_to_rgba, rgba_to_indexed, Palette}, common::{CommonHeader, CzError, CzVersion, ExtendedHeader}, formats::{cz0, cz1, cz2, cz3, cz4}, }; -/// A CZ# interface which abstracts the CZ# generic file interface for -/// convenience. -#[derive(Debug)] +/// A CZ# interface which can open and save any CZ file type. +#[derive(Debug, Clone)] pub struct DynamicCz { header_common: CommonHeader, header_extended: Option, + + /// A palette of RGBA values for indexed color palette: Option, + + /// 32bpp RGBA bitmap representation of the file contents bitmap: Vec, } impl DynamicCz { - /// Open a CZ# file from a path - pub fn open>(path: &P) -> Result { - let mut img_file = BufReader::new(std::fs::File::open(path)?); - - Self::decode(&mut img_file) - } - /// Decode a CZ# file from anything which implements [`Read`] and [`Seek`] /// /// The input must begin with the @@ -74,12 +70,11 @@ impl DynamicCz { match header_common.depth() { 4 => { - eprintln!("Files with a bit depth of 4 are not yet supported"); - todo!() + todo!("Files with a bit depth of 4 are not yet supported") } 8 => { if let Some(palette) = &palette { - bitmap = apply_palette(&bitmap, palette)?; + bitmap = indexed_to_rgba(&bitmap, palette)?; } else { return Err(CzError::PaletteError); } @@ -111,20 +106,30 @@ impl DynamicCz { /// Save the `DynamicCz` as a CZ# file. The format saved in is determined /// from the format in the header. Check [`CommonHeader::set_version()`] /// to change the CZ# version. - pub fn save_as_cz>(&self, path: T) -> Result<(), CzError> { - let mut out_file = BufWriter::new(File::create(path.into())?); + pub fn save_as_cz>( + &self, + path: &P, + ) -> Result<(), CzError> { + let mut out_file = BufWriter::new(File::create(path.as_ref())?); + + self.write(&mut out_file)?; + + Ok(()) + } + + pub fn write(&self, mut output: &mut T) -> Result<(), CzError> { let mut header = *self.header(); if header.version() == CzVersion::CZ2 { header.set_length(0x12) } - header.write_into(&mut out_file)?; + header.write_into(&mut output)?; if header.version() == CzVersion::CZ2 { // CZ2 files have this odd section instead of an extended header...? - out_file.write_all(&[0, 0, 0])?; + output.write_all(&[0, 0, 0])?; } else if let Some(ext) = self.header_extended { - ext.write_into(&mut out_file)?; + ext.write_into(&mut output)?; } let output_bitmap; @@ -139,8 +144,8 @@ impl DynamicCz { // Use the existing palette to palette the image output_bitmap = rgba_to_indexed(self.bitmap(), pal)?; - for rgba in &pal.colors { - out_file.write_all(&rgba.0)?; + for rgba in pal.colors() { + output.write_all(rgba.as_slice())?; } } else { // Generate a palette and corresponding indexed bitmap if there is none @@ -150,12 +155,7 @@ impl DynamicCz { let palette = result.1; for rgba in palette { - let mut rgba_clone = rgba.0; - if false { - // TODO: Make a toggle for this - rgba_clone[0..3].reverse(); - } - out_file.write_all(&rgba_clone)?; + output.write_all(rgba.as_slice())?; } } } @@ -179,11 +179,11 @@ impl DynamicCz { } match self.header_common.version() { - CzVersion::CZ0 => cz0::encode(&mut out_file, &output_bitmap)?, - CzVersion::CZ1 => cz1::encode(&mut out_file, &output_bitmap)?, - CzVersion::CZ2 => cz2::encode(&mut out_file, &output_bitmap)?, - CzVersion::CZ3 => cz3::encode(&mut out_file, &output_bitmap, &self.header_common)?, - CzVersion::CZ4 => cz4::encode(&mut out_file, &output_bitmap, &self.header_common)?, + CzVersion::CZ0 => cz0::encode(&mut output, &output_bitmap)?, + CzVersion::CZ1 => cz1::encode(&mut output, &output_bitmap)?, + CzVersion::CZ2 => cz2::encode(&mut output, &output_bitmap)?, + CzVersion::CZ3 => cz3::encode(&mut output, &output_bitmap, &self.header_common)?, + CzVersion::CZ4 => cz4::encode(&mut output, &output_bitmap, &self.header_common)?, CzVersion::CZ5 => todo!(), } @@ -196,7 +196,7 @@ impl DynamicCz { /// 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>( + pub fn save_as_png>( &self, path: &P, ) -> Result<(), image::error::EncodingError> { diff --git a/cz/src/lib.rs b/cz/src/lib.rs index 122581c..cdbc78a 100644 --- a/cz/src/lib.rs +++ b/cz/src/lib.rs @@ -13,6 +13,16 @@ mod formats { pub(crate) mod cz4; } +use std::{io::BufReader, path::Path}; +use common::CzError; + +/// Open a CZ# file from a path +pub fn open>(path: &P) -> Result { + let mut img_file = BufReader::new(std::fs::File::open(path)?); + + DynamicCz::decode(&mut img_file) +} + #[doc(inline)] pub use dynamic::DynamicCz; diff --git a/experimental/src/main.rs b/experimental/src/main.rs index e7a11a9..97e3916 100644 --- a/experimental/src/main.rs +++ b/experimental/src/main.rs @@ -1,3 +1,7 @@ fn main() { - println!("Hello, world!"); + let cz_file = cz::open("test_file.cz3").unwrap(); + + cz_file.save_as_png("test_file.png").unwrap(); + + cz_file.save_as_cz("test_file.cz").unwrap(); } diff --git a/utils/src/main.rs b/utils/src/main.rs index 4006811..8d15e00 100644 --- a/utils/src/main.rs +++ b/utils/src/main.rs @@ -1,4 +1,3 @@ -use cz::DynamicCz; use std::path::{Path, PathBuf}; use clap::{error::ErrorKind, Error, Parser, Subcommand}; @@ -87,7 +86,7 @@ fn main() { let mut final_path = output.clone().unwrap(); final_path.push(filename); - let cz = match DynamicCz::open(&path) { + let cz = match cz::open(&path) { Ok(cz) => cz, Err(_) => { Error::raw( @@ -101,7 +100,7 @@ fn main() { cz.save_as_png(&final_path).unwrap(); } } else { - let cz = DynamicCz::open(input).unwrap(); + let cz = cz::open(input).unwrap(); if let Some(output) = output { cz.save_as_png(output).unwrap(); @@ -209,7 +208,7 @@ fn replace_cz>( let repl_img = image::open(&replacement_path)?.to_rgba8(); // Open the original CZ file - let mut cz = DynamicCz::open(&path)?; + let mut cz = cz::open(&path)?; // Set CZ header parameters and the new bitmap cz.header_mut().set_width(repl_img.width() as u16);