Create iter_mut_layers method for post processing plugins

This commit is contained in:
Connor Slade
2024-11-22 19:44:50 -05:00
parent a0f5ee5303
commit 3a15f4cbeb
7 changed files with 148 additions and 70 deletions

View File

@@ -86,3 +86,4 @@
- [x] Save config on quit
- [x] Refactor plugins to post processors
- [ ] Update readme
- [ ] Make post processing async

View File

@@ -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);
});
}
}

View File

@@ -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());

View File

@@ -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);
}
}
}

View File

@@ -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,

71
slicer/src/format/iter.rs Normal file
View File

@@ -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<Self::Item> {
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 {}

View File

@@ -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<u32>,
}
pub size: Vector3<f32>,
// 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;
}
}
}
}