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] Save config on quit
- [x] Refactor plugins to post processors - [x] Refactor plugins to post processors
- [ ] Update readme - [ ] Update readme
- [ ] Make post processing async

View File

@@ -1,10 +1,8 @@
use common::image::Image;
use egui::{Context, Ui}; use egui::{Context, Ui};
use image::GrayImage; use rayon::iter::{ParallelBridge, ParallelIterator};
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; use slicer::format::FormatSliceFile;
use crate::{app::App, ui::components::dragger}; use crate::{app::App, ui::components::dragger};
use goo_format::{File as GooFile, LayerDecoder, LayerEncoder};
use super::Plugin; 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 { if !self.enabled {
return; return;
} }
let (width, height) = ( file.iter_mut_layers().par_bridge().for_each(|mut layer| {
goo.header.x_resolution as usize, *layer = imageproc::filter::gaussian_blur_f32(&layer, self.radius);
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;
}); });
} }
} }

View File

@@ -2,18 +2,17 @@ use std::{mem, time::Instant};
use egui::{Align, Context, Layout, Ui}; use egui::{Align, Context, Layout, Ui};
use egui_phosphor::regular::{INFO, WARNING}; use egui_phosphor::regular::{INFO, WARNING};
use image::{GrayImage, Luma}; use image::Luma;
use imageproc::{morphology::Mask, point::Point}; use imageproc::{morphology::Mask, point::Point};
use itertools::Itertools; use itertools::Itertools;
use rayon::iter::{ParallelBridge, ParallelIterator}; use rayon::iter::{ParallelBridge, ParallelIterator};
use slicer::format::FormatSliceFile;
use tracing::info; use tracing::info;
use crate::{ use crate::{
app::App, app::App,
ui::components::{dragger, dragger_tip}, ui::components::{dragger, dragger_tip},
}; };
use common::image::Image;
use goo_format::{File as GooFile, LayerDecoder, LayerEncoder};
use super::Plugin; 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 { if !self.enabled {
return; return;
} }
let (width, height) = ( let info = file.info();
goo.header.x_resolution as usize, let (width, height) = (info.resolution.x as usize, info.resolution.y as usize);
goo.header.y_resolution as usize,
);
let (x_radius, y_radius) = ( let (x_radius, y_radius) = (
(self.inset_distance * (width as f32 / goo.header.x_size)) as usize, (self.inset_distance * (width as f32 / info.size.x)) as usize,
(self.inset_distance * (height as f32 / goo.header.y_size)) as usize, (self.inset_distance * (height as f32 / info.size.y)) as usize,
); );
info!( info!(
"Eroding {} bottom layers with radius ({}, {})", "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; 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 darken = |value: u8| (value as f32 * intensity).round() as u8;
let start = Instant::now(); let start = Instant::now();
goo.layers file.iter_mut_layers()
.iter_mut() .take(info.bottom_layers as usize)
.take(goo.header.bottom_layers as usize)
.par_bridge() .par_bridge()
.for_each(|layer| { .for_each(|mut layer| {
let decoder = LayerDecoder::new(&layer.data); let erode = imageproc::morphology::grayscale_erode(&layer, &mask);
let raw_image = Image::from_decoder(width, height, decoder).take(); for (x, y, pixel) in layer.enumerate_pixels_mut() {
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() {
if erode.get_pixel(x, y)[0] == 0 && pixel[0] != 0 { if erode.get_pixel(x, y)[0] == 0 && pixel[0] != 0 {
*pixel = Luma([darken(pixel[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()); info!("Eroded bottom layers in {:?}", start.elapsed());

View File

@@ -2,7 +2,6 @@ use egui::{Context, Ui};
use slicer::format::FormatSliceFile; use slicer::format::FormatSliceFile;
use crate::app::App; use crate::app::App;
use goo_format::File as GooFile;
pub mod anti_alias; pub mod anti_alias;
pub mod elephant_foot_fixer; pub mod elephant_foot_fixer;
@@ -11,7 +10,7 @@ pub trait Plugin {
fn name(&self) -> &'static str; fn name(&self) -> &'static str;
fn ui(&mut self, app: &mut App, ui: &mut Ui, ctx: &Context); 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 { pub struct PluginManager {
@@ -19,10 +18,9 @@ pub struct PluginManager {
} }
impl 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 { for plugin in &self.plugins {
// TODO: FIX PLUGINS plugin.post_slice(app, file);
// plugin.post_slice(app, goo);
} }
} }
} }

View File

@@ -163,7 +163,7 @@ fn slice_preview(ui: &mut egui::Ui, result: &mut SliceResult) {
.show_value(false), .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); 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 { 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( let callback = Callback::new_paint_callback(
rect, rect,
SlicePreviewRenderCallback { 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, offset: result.preview_offset,
scale: preview_scale, scale: preview_scale,
new_preview, 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 goo_format::PreviewImage;
use image::{imageops::FilterType, RgbaImage}; use image::{imageops::FilterType, GrayImage, RgbaImage};
use nalgebra::Vector2; use iter::SliceLayerIterator;
use nalgebra::{Vector2, Vector3};
use parking_lot::MappedMutexGuard; 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> { pub enum FormatSliceResult<'a> {
Goo(SliceResult<'a, goo_format::LayerContent>), Goo(SliceResult<'a, goo_format::LayerContent>),
} }
// todo: replace with trait obj?
pub enum FormatSliceFile { pub enum FormatSliceFile {
Goo(goo_format::File), Goo(goo_format::File),
} }
@@ -16,9 +20,10 @@ pub enum FormatSliceFile {
pub struct SliceInfo { pub struct SliceInfo {
pub layers: u32, pub layers: u32,
pub resolution: Vector2<u32>, pub resolution: Vector2<u32>,
} pub size: Vector3<f32>,
// TODO: convert to dynamic dispatch pub bottom_layers: u32,
}
impl FormatSliceFile { impl FormatSliceFile {
pub fn from_slice_result( pub fn from_slice_result(
@@ -51,6 +56,9 @@ impl FormatSliceFile {
file.header.x_resolution as u32, file.header.x_resolution as u32,
file.header.y_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;
}
}
}
}