Create iter_mut_layers method for post processing plugins
This commit is contained in:
1
TODO.md
1
TODO.md
@@ -86,3 +86,4 @@
|
||||
- [x] Save config on quit
|
||||
- [x] Refactor plugins to post processors
|
||||
- [ ] Update readme
|
||||
- [ ] Make post processing async
|
||||
|
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -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());
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
71
slicer/src/format/iter.rs
Normal 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 {}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user