From 34a3f3d2a0f7ad5b1c4699c4cdde08e70c89275d Mon Sep 17 00:00:00 2001 From: Connor Slade Date: Wed, 28 Aug 2024 21:35:07 -0400 Subject: [PATCH] Erosion elephant foot fixer impl --- common/src/image.rs | 45 ++++++---- mslicer/src/plugins/elephant_foot_fixer.rs | 99 +++++++++++++++++----- mslicer/src/ui/components.rs | 20 ++++- 3 files changed, 124 insertions(+), 40 deletions(-) diff --git a/common/src/image.rs b/common/src/image.rs index d4db692..886ed0e 100644 --- a/common/src/image.rs +++ b/common/src/image.rs @@ -3,8 +3,9 @@ use nalgebra::Vector2; use crate::misc::Run; /// A fast grayscale image buffer +#[derive(Clone)] pub struct Image { - size: Vector2, + pub size: Vector2, data: Vec, idx: usize, } @@ -26,6 +27,12 @@ impl Image { } } + pub fn from_decoder(width: usize, height: usize, decoder: impl Iterator) -> Self { + let mut image = Self::blank(width, height); + decoder.for_each(|run| image.add_run(run.length as usize, run.value)); + image + } + pub fn add_run(&mut self, length: usize, value: u8) { self.data[self.idx..self.idx + length].fill(value); self.idx += length; @@ -41,23 +48,6 @@ impl Image { self.data[idx] = val; } - pub fn blur(&mut self, sigma: f32) { - let sigma = sigma as usize; - for x in 0..self.size.x { - for y in 0..self.size.y { - let mut sum = 0; - for xp in x.saturating_sub(sigma)..(x + sigma).min(self.size.x) { - for yp in y.saturating_sub(sigma)..(y + sigma).min(self.size.y) { - sum += self.get_pixel(xp, yp); - } - } - - let avg = sum as f32 / (sigma * sigma) as f32; - self.set_pixel(x, y, avg as u8); - } - } - } - pub fn runs(&self) -> ImageRuns { ImageRuns { inner: &self.data, @@ -76,6 +66,25 @@ impl Image { } } +impl Image { + pub fn blur(&mut self, sigma: f32) { + let sigma = sigma as usize; + for x in 0..self.size.x { + for y in 0..self.size.y { + let mut sum = 0; + for xp in x.saturating_sub(sigma)..(x + sigma).min(self.size.x) { + for yp in y.saturating_sub(sigma)..(y + sigma).min(self.size.y) { + sum += self.get_pixel(xp, yp); + } + } + + let avg = sum as f32 / (sigma * sigma) as f32; + self.set_pixel(x, y, avg as u8); + } + } + } +} + impl<'a> Iterator for ImageRuns<'a> { type Item = Run; diff --git a/mslicer/src/plugins/elephant_foot_fixer.rs b/mslicer/src/plugins/elephant_foot_fixer.rs index b9c9706..5efa95e 100644 --- a/mslicer/src/plugins/elephant_foot_fixer.rs +++ b/mslicer/src/plugins/elephant_foot_fixer.rs @@ -1,14 +1,16 @@ +use common::image::Image; use egui::{Context, Ui}; +use tracing::info; -use crate::{app::App, ui::components::dragger}; -use goo_format::File as GooFile; +use crate::{app::App, ui::components::dragger_tip}; +use goo_format::{File as GooFile, LayerDecoder, LayerEncoder}; use super::Plugin; pub struct ElephantFootFixerPlugin { enabled: bool, - rest_time: f32, - rest_layers: u32, + inset_distance: f32, + intensity_multiplier: f32, } impl Plugin for ElephantFootFixerPlugin { @@ -17,34 +19,64 @@ impl Plugin for ElephantFootFixerPlugin { } fn ui(&mut self, _app: &mut App, ui: &mut Ui, _ctx: &Context) { - ui.label("Fixes the 'Elephant Foot' effect by adding rest times before and after each bottom layer."); + ui.label("Fixes the 'Elephant Foot' effect by exposing the edges of the bottom layers at a lower intensity. You may have to make a few test prints to find the right settings for your printer and resin."); ui.checkbox(&mut self.enabled, "Enabled"); ui.add_space(8.0); - ui.label("How long to wait before and after each exposure with the build plate in place."); - dragger(ui, "Rest Time", &mut self.rest_time, |x| { - x.speed(0.1).suffix("s") - }); + dragger_tip( + ui, + "Inset Distance", + "The distance in from the edges that will have a reduced intensity.", + &mut self.inset_distance, + |x| x.speed(0.1).suffix("mm"), + ); ui.add_space(8.0); - ui.label("How many layers to apply the rest time to, after the bottom layers."); - dragger(ui, "Rest Layers", &mut self.rest_layers, |x| { - x.speed(1).suffix(" layers") - }); + dragger_tip( + ui, + "Intensity", + "This percent will be multiplied by the pixel values of the edge pixels.", + &mut self.intensity_multiplier, + |x| x.clamp_range(0.0..=100.0).speed(1).suffix("%"), + ); } fn post_slice(&self, _app: &App, goo: &mut GooFile) { - goo.header.advance_mode = true; - goo.header.exposure_delay_mode = true; + if !self.enabled { + return; + } + + let (width, height) = ( + goo.header.x_resolution as usize, + goo.header.y_resolution as usize, + ); + + let (x_radius, y_radius) = ( + (self.inset_distance * (width as f32 / goo.header.x_size)) as usize, + (self.inset_distance * (height as f32 / goo.header.y_size)) as usize, + ); + info!( + "Eroding bottom layers with radius ({}, {})", + x_radius, y_radius + ); - // All bottom layers should have rest time added for layer in goo .layers .iter_mut() - .take((goo.header.bottom_layers + self.rest_layers) as usize) + .take(goo.header.bottom_layers as usize) { - layer.before_lift_time = self.rest_time; - layer.after_retract_time = self.rest_time; + let decoder = LayerDecoder::new(&layer.data); + let image = Image::from_decoder(width, height, decoder); + let image = erode(image, self.intensity_multiplier / 100.0, x_radius, y_radius); + + let mut new_layer = LayerEncoder::new(); + for run in image.runs() { + new_layer.add_run(run.length, run.value) + } + + let (data, checksum) = new_layer.finish(); + layer.data = data; + layer.checksum = checksum; } } } @@ -52,7 +84,32 @@ impl Plugin for ElephantFootFixerPlugin { pub fn get_plugin() -> Box { Box::new(ElephantFootFixerPlugin { enabled: false, - rest_time: 20.0, - rest_layers: 20, + inset_distance: 2.0, + intensity_multiplier: 30.0, }) } + +pub fn erode(image: Image, intensity: f32, x_radius: usize, y_radius: usize) -> Image { + let mut new_layer = image.clone(); + + for x in 0..image.size.x { + 'outer: for y in 0..image.size.y { + let pixel = image.get_pixel(x, y); + if pixel == 0 { + continue; + } + + // if there are any black pixels in the radius, multiply the pixel by the intensity multiplier + for xp in x.saturating_sub(x_radius)..(x + x_radius).min(image.size.x) { + for yp in y.saturating_sub(y_radius)..(y + y_radius).min(image.size.y) { + if image.get_pixel(xp, yp) == 0 { + new_layer.set_pixel(x, y, (pixel as f32 * intensity) as u8); + continue 'outer; + } + } + } + } + } + + new_layer +} diff --git a/mslicer/src/ui/components.rs b/mslicer/src/ui/components.rs index 7ffd931..a39d018 100644 --- a/mslicer/src/ui/components.rs +++ b/mslicer/src/ui/components.rs @@ -1,4 +1,5 @@ -use egui::{emath::Numeric, DragValue, Ui}; +use egui::{emath::Numeric, Align, DragValue, Layout, Ui}; +use egui_phosphor::regular::INFO; pub fn dragger( ui: &mut Ui, @@ -12,6 +13,23 @@ pub fn dragger( }); } +pub fn dragger_tip( + ui: &mut Ui, + label: &str, + tip: &str, + value: &mut Num, + func: fn(DragValue) -> DragValue, +) { + ui.horizontal(|ui| { + ui.add(func(DragValue::new(value))); + ui.label(label); + ui.with_layout(Layout::right_to_left(Align::Min), |ui| { + ui.label(INFO).on_hover_text(tip); + ui.add_space(ui.available_width()); + }) + }); +} + pub fn vec2_dragger( ui: &mut Ui, val: &mut [Num; 2],