From 3374dbe529b61c543c29f2ab7a75a12bfd90b355 Mon Sep 17 00:00:00 2001 From: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Date: Sat, 25 Nov 2023 12:54:24 -0800 Subject: [PATCH] Improves DMI saving (smaller files, better formatting) (#15) --- src/icon.rs | 25 ++++++++++++++++++++----- tests/dmi_ops.rs | 18 ++++++++++++++++++ tests/load_dmi.rs | 11 ----------- tests/resources/save_test.dmi | Bin 0 -> 4530 bytes 4 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 tests/dmi_ops.rs delete mode 100644 tests/load_dmi.rs create mode 100644 tests/resources/save_test.dmi 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 0000000000000000000000000000000000000000..dd84169e5ff2c48b52a3f2ca3b3c0db33bd5f68a GIT binary patch literal 4530 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 sWJ&0z(~0wd7YFwJzw&=P2)l=V-N#Q+{XPTi_@% literal 0 HcmV?d00001