Use imageproc erode

This commit is contained in:
Connor Slade
2024-08-30 21:33:37 -04:00
parent f605fadbde
commit 2637915517
8 changed files with 178 additions and 73 deletions

56
Cargo.lock generated
View File

@@ -139,7 +139,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cc836ad7a40dc9d8049574e2a29979f5dc77deeea4d7ebcd29773452f0e9694"
dependencies = [
"approx 0.3.2",
"libm",
"libm 0.1.4",
"num-complex 0.2.4",
"num-traits",
]
@@ -2044,8 +2044,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
@@ -2399,6 +2401,24 @@ dependencies = [
"thiserror",
]
[[package]]
name = "imageproc"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2393fb7808960751a52e8a154f67e7dd3f8a2ef9bd80d1553078a7b4e8ed3f0d"
dependencies = [
"ab_glyph",
"approx 0.5.1",
"getrandom",
"image 0.25.1",
"itertools 0.12.1",
"nalgebra 0.32.6",
"num 0.4.3",
"rand 0.8.5",
"rand_distr",
"rayon",
]
[[package]]
name = "imgref"
version = "1.10.1"
@@ -2633,6 +2653,12 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
[[package]]
name = "libm"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "libredox"
version = "0.0.2"
@@ -2844,6 +2870,7 @@ dependencies = [
"encase",
"goo_format",
"image 0.25.1",
"imageproc",
"nalgebra 0.32.6",
"notify-rust",
"open",
@@ -3052,6 +3079,20 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint 0.4.5",
"num-complex 0.4.6",
"num-integer",
"num-iter",
"num-rational 0.4.2",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.2.6"
@@ -3159,6 +3200,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg 1.3.0",
"libm 0.2.8",
]
[[package]]
@@ -3544,7 +3586,7 @@ dependencies = [
"fool",
"itertools 0.8.2",
"nalgebra 0.17.3",
"num",
"num 0.2.1",
"smallvec 0.6.14",
"typenum",
]
@@ -3841,6 +3883,16 @@ dependencies = [
"getrandom",
]
[[package]]
name = "rand_distr"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
dependencies = [
"num-traits",
"rand 0.8.5",
]
[[package]]
name = "rand_hc"
version = "0.1.0"

View File

@@ -21,6 +21,7 @@ egui-phosphor = { git = "https://github.com/connorslade/egui-phosphor" }
egui-wgpu = "0.27.2"
encase = { version = "0.8.0", features = ["nalgebra"] }
image = "0.25.1"
imageproc = "0.25.0"
md5 = "0.7.0"
nalgebra = "0.32.6"
notify-rust = "4.11.0"

View File

@@ -33,6 +33,14 @@ impl Image {
image
}
pub fn from_raw(width: usize, height: usize, data: Vec<u8>) -> Self {
Self {
size: Vector2::new(width, height),
data,
idx: 0,
}
}
pub fn add_run(&mut self, length: usize, value: u8) {
self.data[self.idx..self.idx + length].fill(value);
self.idx += length;

View File

@@ -3,3 +3,4 @@ pub mod image;
pub mod misc;
pub mod oklab;
pub mod serde;
pub mod rle_image;

88
common/src/rle_image.rs Normal file
View File

@@ -0,0 +1,88 @@
use crate::misc::Run;
pub struct RleImage {
runs: Vec<ImageRun>,
width: usize,
height: usize,
}
pub struct RleImageIterator<'a> {
image: &'a RleImage,
run_index: usize,
last_idx: usize,
}
pub struct ImageRun {
color: u8,
row: usize,
start: usize,
end: usize,
}
impl RleImage {
pub fn from_decoder(width: usize, height: usize, decoder: impl Iterator<Item = Run>) -> Self {
let mut runs = Vec::new();
let mut pixel = 0;
for run in decoder {
let length = run.length as usize;
if run.value != 0 {
let (y, x) = (pixel / width, pixel % width);
runs.push(ImageRun {
color: run.value,
row: y,
start: x,
end: x + length,
});
}
pixel += length;
}
Self {
runs,
width,
height,
}
}
pub fn to_runs(&self) -> RleImageIterator {
RleImageIterator {
image: self,
run_index: 0,
last_idx: 0,
}
}
}
impl<'a> Iterator for RleImageIterator<'a> {
type Item = Run;
fn next(&mut self) -> Option<Self::Item> {
if self.run_index >= self.image.runs.len() {
return None;
}
let run = &self.image.runs[self.run_index];
let row_offset = run.row * self.image.width;
let start_idx = row_offset + run.start;
if self.last_idx < start_idx {
let length = start_idx - self.last_idx;
self.last_idx = start_idx;
return Some(Run {
length: length as u64,
value: 0,
});
}
let length = run.end - run.start;
self.last_idx = row_offset + run.end;
self.run_index += 1;
Some(Run {
length: length as u64,
value: run.color,
})
}
}

View File

@@ -18,6 +18,7 @@ egui-wgpu.workspace = true
egui.workspace = true
encase.workspace = true
image.workspace = true
imageproc.workspace = true
nalgebra.workspace = true
notify-rust.workspace = true
open.workspace = true

View File

@@ -1,7 +1,14 @@
use common::image::Image;
use std::{
collections::{HashSet, VecDeque},
time::Instant,
};
use common::{image::Image, rle_image::RleImage};
use egui::{Context, Ui};
use image::{GrayImage, Luma};
use imageproc::{distance_transform::Norm, morphology::Mask};
use rayon::iter::{ParallelBridge, ParallelIterator};
use tracing::info;
use tracing::{info, trace};
use crate::{app::App, ui::components::dragger_tip};
use goo_format::{File as GooFile, LayerDecoder, LayerEncoder};
@@ -60,19 +67,29 @@ impl Plugin for ElephantFootFixerPlugin {
x_radius, y_radius
);
let intensity = self.intensity_multiplier / 100.0;
let darken = |value: u8| (value as f32 * intensity).round() as u8;
goo.layers
.iter_mut()
.take(goo.header.bottom_layers as usize)
.par_bridge()
.for_each(|layer| {
let decoder = LayerDecoder::new(&layer.data);
let mut image = Image::from_decoder(width, height, decoder);
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 intensity = self.intensity_multiplier / 100.0;
apply(&mut image, intensity, x_radius, y_radius);
let erode = imageproc::morphology::grayscale_erode(&image, &Mask::square(10));
for (x, y, pixel) in image.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();
for run in image.runs() {
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)
}
@@ -90,67 +107,3 @@ pub fn get_plugin() -> Box<dyn Plugin> {
intensity_multiplier: 30.0,
})
}
fn apply(image: &mut Image, intensity: f32, x_radius: usize, y_radius: usize) {
let mut x_distances = vec![u16::MAX; image.size.x * image.size.y];
let mut y_distances = vec![u16::MAX; image.size.x * image.size.y];
#[inline(always)]
fn update_distance(
image: &Image,
distances: &mut [u16],
distance: &mut u16,
x: usize,
y: usize,
) {
*distance += 1;
let pixel = image.get_pixel(x, y);
(pixel == 0).then(|| *distance = 0);
let idx = y * image.size.x + x;
let old = distances[idx];
(*distance < old).then(|| distances[idx] = *distance);
}
for x in 0..image.size.x {
let mut distance = 0;
for y in 0..image.size.y {
update_distance(&image, &mut y_distances, &mut distance, x, y);
}
}
for y in 0..image.size.y {
let mut distance = 0;
for x in 0..image.size.x {
update_distance(&image, &mut x_distances, &mut distance, x, y);
}
}
for x in (0..image.size.x).rev() {
let mut distance = 0;
for y in (0..image.size.y).rev() {
update_distance(&image, &mut y_distances, &mut distance, x, y);
}
}
for y in (0..image.size.y).rev() {
let mut distance = 0;
for x in (0..image.size.x).rev() {
update_distance(&image, &mut x_distances, &mut distance, x, y);
}
}
for x in 0..image.size.x {
for y in 0..image.size.y {
let pixel = image.get_pixel(x, y);
let x_distance = x_distances[y * image.size.x + x];
let y_distance = y_distances[y * image.size.x + x];
if x_distance < x_radius as u16 || y_distance < y_radius as u16 {
image.set_pixel(x, y, (pixel as f32 * intensity).round() as u8);
}
}
}
}

View File

@@ -33,6 +33,7 @@ fn main() -> Result<()> {
..Default::default()
},
first_layers: 10,
transition_layers: 10,
};
let file = File::open(FILE_PATH)?;