From 3a15f4cbebf907d29ed8f58c3d5e2cecba480dfb Mon Sep 17 00:00:00 2001 From: Connor Slade Date: Fri, 22 Nov 2024 19:44:50 -0500 Subject: [PATCH] Create iter_mut_layers method for post processing plugins --- TODO.md | 1 + mslicer/src/plugins/anti_alias.rs | 31 ++-------- mslicer/src/plugins/elephant_foot_fixer.rs | 45 ++++---------- mslicer/src/plugins/mod.rs | 8 +-- mslicer/src/windows/slice_operation.rs | 4 +- slicer/src/format/iter.rs | 71 ++++++++++++++++++++++ slicer/src/{format.rs => format/mod.rs} | 58 ++++++++++++++++-- 7 files changed, 148 insertions(+), 70 deletions(-) create mode 100644 slicer/src/format/iter.rs rename slicer/src/{format.rs => format/mod.rs} (52%) diff --git a/TODO.md b/TODO.md index d3464b6..7c602c6 100644 --- a/TODO.md +++ b/TODO.md @@ -86,3 +86,4 @@ - [x] Save config on quit - [x] Refactor plugins to post processors - [ ] Update readme +- [ ] Make post processing async diff --git a/mslicer/src/plugins/anti_alias.rs b/mslicer/src/plugins/anti_alias.rs index 3eadfff..959322c 100644 --- a/mslicer/src/plugins/anti_alias.rs +++ b/mslicer/src/plugins/anti_alias.rs @@ -1,10 +1,8 @@ -use common::image::Image; use egui::{Context, Ui}; -use image::GrayImage; -use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; +use rayon::iter::{ParallelBridge, ParallelIterator}; +use slicer::format::FormatSliceFile; use crate::{app::App, ui::components::dragger}; -use goo_format::{File as GooFile, LayerDecoder, LayerEncoder}; use super::Plugin; @@ -28,32 +26,13 @@ impl Plugin for AntiAliasPlugin { }); } - fn post_slice(&self, _app: &App, goo: &mut GooFile) { + fn post_slice(&self, _app: &App, file: &mut FormatSliceFile) { if !self.enabled { return; } - let (width, height) = ( - goo.header.x_resolution as usize, - goo.header.y_resolution as usize, - ); - - goo.layers.par_iter_mut().for_each(|layer| { - let decoder = LayerDecoder::new(&layer.data); - let raw_image = Image::from_decoder(width, height, decoder).take(); - - let image = GrayImage::from_raw(width as u32, height as u32, raw_image).unwrap(); - let image = imageproc::filter::gaussian_blur_f32(&image, self.radius); - - let mut new_layer = LayerEncoder::new(); - let raw_image = Image::from_raw(width, height, image.into_raw()); - for run in raw_image.runs() { - new_layer.add_run(run.length, run.value) - } - - let (data, checksum) = new_layer.finish(); - layer.data = data; - layer.checksum = checksum; + file.iter_mut_layers().par_bridge().for_each(|mut layer| { + *layer = imageproc::filter::gaussian_blur_f32(&layer, self.radius); }); } } diff --git a/mslicer/src/plugins/elephant_foot_fixer.rs b/mslicer/src/plugins/elephant_foot_fixer.rs index aec1266..144abba 100644 --- a/mslicer/src/plugins/elephant_foot_fixer.rs +++ b/mslicer/src/plugins/elephant_foot_fixer.rs @@ -2,18 +2,17 @@ use std::{mem, time::Instant}; use egui::{Align, Context, Layout, Ui}; use egui_phosphor::regular::{INFO, WARNING}; -use image::{GrayImage, Luma}; +use image::Luma; use imageproc::{morphology::Mask, point::Point}; use itertools::Itertools; use rayon::iter::{ParallelBridge, ParallelIterator}; +use slicer::format::FormatSliceFile; use tracing::info; use crate::{ app::App, ui::components::{dragger, dragger_tip}, }; -use common::image::Image; -use goo_format::{File as GooFile, LayerDecoder, LayerEncoder}; use super::Plugin; @@ -56,23 +55,21 @@ impl Plugin for ElephantFootFixerPlugin { ); } - fn post_slice(&self, _app: &App, goo: &mut GooFile) { + fn post_slice(&self, _app: &App, file: &mut FormatSliceFile) { if !self.enabled { return; } - let (width, height) = ( - goo.header.x_resolution as usize, - goo.header.y_resolution as usize, - ); + let info = file.info(); + let (width, height) = (info.resolution.x as usize, info.resolution.y 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, + (self.inset_distance * (width as f32 / info.size.x)) as usize, + (self.inset_distance * (height as f32 / info.size.y)) as usize, ); info!( "Eroding {} bottom layers with radius ({}, {})", - goo.header.bottom_layers, x_radius, y_radius + info.bottom_layers, x_radius, y_radius ); let intensity = self.intensity_multiplier / 100.0; @@ -81,32 +78,16 @@ impl Plugin for ElephantFootFixerPlugin { let darken = |value: u8| (value as f32 * intensity).round() as u8; let start = Instant::now(); - goo.layers - .iter_mut() - .take(goo.header.bottom_layers as usize) + file.iter_mut_layers() + .take(info.bottom_layers as usize) .par_bridge() - .for_each(|layer| { - let decoder = LayerDecoder::new(&layer.data); - let raw_image = Image::from_decoder(width, height, decoder).take(); - let mut image = - GrayImage::from_raw(width as u32, height as u32, raw_image).unwrap(); - - let erode = imageproc::morphology::grayscale_erode(&image, &mask); - for (x, y, pixel) in image.enumerate_pixels_mut() { + .for_each(|mut layer| { + let erode = imageproc::morphology::grayscale_erode(&layer, &mask); + for (x, y, pixel) in layer.enumerate_pixels_mut() { if erode.get_pixel(x, y)[0] == 0 && pixel[0] != 0 { *pixel = Luma([darken(pixel[0])]); } } - - let mut new_layer = LayerEncoder::new(); - let raw_image = Image::from_raw(width, height, image.into_raw()); - for run in raw_image.runs() { - new_layer.add_run(run.length, run.value) - } - - let (data, checksum) = new_layer.finish(); - layer.data = data; - layer.checksum = checksum; }); info!("Eroded bottom layers in {:?}", start.elapsed()); diff --git a/mslicer/src/plugins/mod.rs b/mslicer/src/plugins/mod.rs index 8e1b308..6f15dd9 100644 --- a/mslicer/src/plugins/mod.rs +++ b/mslicer/src/plugins/mod.rs @@ -2,7 +2,6 @@ use egui::{Context, Ui}; use slicer::format::FormatSliceFile; use crate::app::App; -use goo_format::File as GooFile; pub mod anti_alias; pub mod elephant_foot_fixer; @@ -11,7 +10,7 @@ pub trait Plugin { fn name(&self) -> &'static str; fn ui(&mut self, app: &mut App, ui: &mut Ui, ctx: &Context); - fn post_slice(&self, _app: &App, _goo: &mut GooFile) {} + fn post_slice(&self, _app: &App, _goo: &mut FormatSliceFile) {} } pub struct PluginManager { @@ -19,10 +18,9 @@ pub struct PluginManager { } impl PluginManager { - pub fn post_slice(&self, app: &App, goo: &mut FormatSliceFile) { + pub fn post_slice(&self, app: &App, file: &mut FormatSliceFile) { for plugin in &self.plugins { - // TODO: FIX PLUGINS - // plugin.post_slice(app, goo); + plugin.post_slice(app, file); } } } diff --git a/mslicer/src/windows/slice_operation.rs b/mslicer/src/windows/slice_operation.rs index 7d8b8a0..ed89698 100644 --- a/mslicer/src/windows/slice_operation.rs +++ b/mslicer/src/windows/slice_operation.rs @@ -163,7 +163,7 @@ fn slice_preview(ui: &mut egui::Ui, result: &mut SliceResult) { .show_value(false), ); - let (width, height) = (info.resolution.x as u32, info.resolution.y as u32); + let (width, height) = (info.resolution.x, info.resolution.y); result.slice_preview_layer = result.slice_preview_layer.clamp(1, info.layers as usize); let new_preview = if result.last_preview_layer != result.slice_preview_layer { @@ -202,7 +202,7 @@ fn slice_preview(ui: &mut egui::Ui, result: &mut SliceResult) { let callback = Callback::new_paint_callback( rect, SlicePreviewRenderCallback { - dimensions: Vector2::new(info.resolution.x as u32, info.resolution.y as u32), + dimensions: Vector2::new(info.resolution.x, info.resolution.y), offset: result.preview_offset, scale: preview_scale, new_preview, diff --git a/slicer/src/format/iter.rs b/slicer/src/format/iter.rs new file mode 100644 index 0000000..04b3525 --- /dev/null +++ b/slicer/src/format/iter.rs @@ -0,0 +1,71 @@ +// yes, i know this is jank as hell + +use std::{ + mem, + ops::{Deref, DerefMut}, +}; + +use image::GrayImage; + +use super::FormatSliceFile; + +pub struct SliceLayerIterator<'a> { + pub(crate) file: &'a mut FormatSliceFile, + pub(crate) layer: usize, + pub(crate) layers: usize, +} + +pub struct SliceLayerElement { + image: GrayImage, + + file: *mut FormatSliceFile, + layer: usize, +} + +impl<'a> Iterator for SliceLayerIterator<'a> { + type Item = SliceLayerElement; + + fn next(&mut self) -> Option { + if self.layer >= self.layers { + return None; + } + + let image = self.file.read_layer(self.layer); + self.layer += 1; + + Some(SliceLayerElement { + image, + file: self.file as *mut _, + layer: self.layer - 1, + }) + } +} + +impl Drop for SliceLayerElement { + fn drop(&mut self) { + // SAFETY: it's not... But the idea is that each SliceLayerElement will + // only be writing to one layer each, meaning the same memory will only + // be mutably borrowed once. + // + // You could easily keep one of these objects alive after the slice + // layer iter is dropped, but don't please. + let file = unsafe { &mut *self.file }; + file.overwrite_layer(self.layer, mem::take(&mut self.image)); + } +} + +impl Deref for SliceLayerElement { + type Target = GrayImage; + + fn deref(&self) -> &Self::Target { + &self.image + } +} + +impl DerefMut for SliceLayerElement { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.image + } +} + +unsafe impl Send for SliceLayerElement {} diff --git a/slicer/src/format.rs b/slicer/src/format/mod.rs similarity index 52% rename from slicer/src/format.rs rename to slicer/src/format/mod.rs index 9f94a24..d600f84 100644 --- a/slicer/src/format.rs +++ b/slicer/src/format/mod.rs @@ -1,14 +1,18 @@ use goo_format::PreviewImage; -use image::{imageops::FilterType, RgbaImage}; -use nalgebra::Vector2; +use image::{imageops::FilterType, GrayImage, RgbaImage}; +use iter::SliceLayerIterator; +use nalgebra::{Vector2, Vector3}; use parking_lot::MappedMutexGuard; -use common::{misc::SliceResult, serde::Serializer}; +use common::{image::Image, misc::SliceResult, serde::Serializer}; + +pub mod iter; pub enum FormatSliceResult<'a> { Goo(SliceResult<'a, goo_format::LayerContent>), } +// todo: replace with trait obj? pub enum FormatSliceFile { Goo(goo_format::File), } @@ -16,9 +20,10 @@ pub enum FormatSliceFile { pub struct SliceInfo { pub layers: u32, pub resolution: Vector2, -} + pub size: Vector3, -// TODO: convert to dynamic dispatch + pub bottom_layers: u32, +} impl FormatSliceFile { pub fn from_slice_result( @@ -51,6 +56,9 @@ impl FormatSliceFile { file.header.x_resolution as u32, file.header.y_resolution as u32, ), + size: Vector3::new(file.header.x_size, file.header.y_size, file.header.x_size), + + bottom_layers: file.header.bottom_layers, }, } } @@ -72,3 +80,43 @@ impl FormatSliceFile { } } } + +impl FormatSliceFile { + pub fn iter_mut_layers(&mut self) -> SliceLayerIterator<'_> { + let layers = self.info().layers as usize; + SliceLayerIterator { + file: self, + layer: 0, + layers, + } + } + + pub fn read_layer(&self, layer: usize) -> GrayImage { + let info = self.info(); + let (width, height) = (info.resolution.x, info.resolution.y); + + let mut raw = vec![0; width as usize * height as usize]; + self.decode_layer(layer, &mut raw); + GrayImage::from_raw(width, height, raw).unwrap() + } + + pub fn overwrite_layer(&mut self, layer: usize, image: GrayImage) { + let info = self.info(); + let (width, height) = (info.resolution.x as usize, info.resolution.y as usize); + + match self { + FormatSliceFile::Goo(file) => { + let image = Image::from_raw(width, height, image.into_raw()); + let mut new_layer = goo_format::LayerEncoder::new(); + for run in image.runs() { + new_layer.add_run(run.length, run.value) + } + + let (data, checksum) = new_layer.finish(); + let layer = &mut file.layers[layer]; + layer.data = data; + layer.checksum = checksum; + } + } + } +}