diff --git a/src/icon.rs b/src/icon.rs index 6a6d430..17ad63e 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -1,6 +1,8 @@ use crate::{error, ztxt, RawDmi}; +use image::codecs::png; use image::imageops; use image::GenericImageView; +use image::ImageEncoder; use std::collections::HashMap; use std::io::prelude::*; use std::io::Cursor; @@ -335,9 +337,13 @@ impl Icon { signature.push_str("# END DMI\n"); - let max_index = (sprites.len() as f64).sqrt().ceil() as u32; + // We try to make a square png as output + let states_rooted = (sprites.len() as f64).sqrt().ceil(); + // Then if it turns out we would have empty rows, we remove them + let cell_width = states_rooted as u32; + let cell_height = ((sprites.len() as f64) / states_rooted).ceil() as u32; let mut new_png = - image::DynamicImage::new_rgba8(max_index * self.width, max_index * self.height); + image::DynamicImage::new_rgba8(cell_width * self.width, cell_height * self.height); for image in sprites.iter().enumerate() { let index = image.0 as u32; @@ -345,13 +351,22 @@ impl Icon { imageops::replace( &mut new_png, *image, - (self.width * (index % max_index)).into(), - (self.height * (index / max_index)).into(), + (self.width * (index % cell_width)).into(), + (self.height * (index / cell_width)).into(), ); } let mut dmi_data = Cursor::new(vec![]); - new_png.write_to(&mut dmi_data, image::ImageOutputFormat::Png)?; + // We're futzing around with pngs directly here so we can use the best possible compression + let bytes = new_png.as_bytes(); + let (width, height) = new_png.dimensions(); + let color = new_png.color(); + let encoder = png::PngEncoder::new_with_quality( + &mut dmi_data, + png::CompressionType::Default, + png::FilterType::Adaptive, + ); + encoder.write_image(bytes, width, height, color)?; let mut new_dmi = RawDmi::load(&dmi_data.into_inner()[..])?; let new_ztxt = ztxt::create_ztxt_chunk(signature.as_bytes())?; diff --git a/tests/dmi_ops.rs b/tests/dmi_ops.rs new file mode 100644 index 0000000..cc2e2a6 --- /dev/null +++ b/tests/dmi_ops.rs @@ -0,0 +1,18 @@ +use dmi::icon::Icon; +use std::fs::File; +use std::path::PathBuf; + +#[test] +fn load_and_save_dmi() { + let mut load_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + load_path.push("tests/resources/load_test.dmi"); + let load_file = + File::open(load_path.as_path()).unwrap_or_else(|_| panic!("No lights dmi: {load_path:?}")); + let lights_icon = Icon::load(&load_file).expect("Unable to load lights dmi"); + let mut write_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + write_path.push("tests/resources/save_test.dmi"); + let mut write_file = File::create(write_path.as_path()).expect("Failed to create dmi file"); + let _written_dmi = lights_icon + .save(&mut write_file) + .expect("Failed to save lights dmi"); +} diff --git a/tests/load_dmi.rs b/tests/load_dmi.rs deleted file mode 100644 index 2aea1b0..0000000 --- a/tests/load_dmi.rs +++ /dev/null @@ -1,11 +0,0 @@ -use dmi::icon::Icon; -use std::fs::File; -use std::path::PathBuf; - -#[test] -fn load_dmi() { - let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - path.push("tests/resources/load_test.dmi"); - let file = File::open(path.as_path()).unwrap_or_else(|_| panic!("No lights dmi: {path:?}")); - let _lights_icon = Icon::load(&file).expect("Unable to load lights dmi"); -} diff --git a/tests/resources/save_test.dmi b/tests/resources/save_test.dmi new file mode 100644 index 0000000..dd84169 Binary files /dev/null and b/tests/resources/save_test.dmi differ