Erosion elephant foot fixer impl
This commit is contained in:
@@ -3,8 +3,9 @@ use nalgebra::Vector2;
|
||||
use crate::misc::Run;
|
||||
|
||||
/// A fast grayscale image buffer
|
||||
#[derive(Clone)]
|
||||
pub struct Image {
|
||||
size: Vector2<usize>,
|
||||
pub size: Vector2<usize>,
|
||||
data: Vec<u8>,
|
||||
idx: usize,
|
||||
}
|
||||
@@ -26,6 +27,12 @@ impl Image {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_decoder(width: usize, height: usize, decoder: impl Iterator<Item = Run>) -> 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;
|
||||
|
||||
|
@@ -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<dyn Plugin> {
|
||||
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
|
||||
}
|
||||
|
@@ -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<Num: Numeric>(
|
||||
ui: &mut Ui,
|
||||
@@ -12,6 +13,23 @@ pub fn dragger<Num: Numeric>(
|
||||
});
|
||||
}
|
||||
|
||||
pub fn dragger_tip<Num: Numeric>(
|
||||
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<Num: Numeric>(
|
||||
ui: &mut Ui,
|
||||
val: &mut [Num; 2],
|
||||
|
Reference in New Issue
Block a user