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] Save config on quit
|
||||||
- [x] Refactor plugins to post processors
|
- [x] Refactor plugins to post processors
|
||||||
- [ ] Update readme
|
- [ ] Update readme
|
||||||
|
- [ ] Make post processing async
|
||||||
|
@@ -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;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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());
|
||||||
|
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
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 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user