Skip to content

Commit

Permalink
New feature: Flood-fill (#684)
Browse files Browse the repository at this point in the history
  • Loading branch information
tkallady authored Oct 19, 2024
1 parent 54573da commit a6fe217
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 0 deletions.
72 changes: 72 additions & 0 deletions src/drawing/fill.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use crate::definitions::Image;
use image::Pixel;

/// Equivalent to bucket tool in MS-PAINT
/// Performs 4-way flood-fill based on this algorithm: <https://en.wikipedia.org/wiki/Flood_fill#Span_filling>
pub fn flood_fill<P>(image: &Image<P>, x: u32, y: u32, fill_with: P) -> Image<P>
where
P: Pixel + PartialEq,
{
let mut filled_image = image.clone();
flood_fill_mut(&mut filled_image, x, y, fill_with);
filled_image
}

#[doc=generate_mut_doc_comment!("flood_fill")]
pub fn flood_fill_mut<P>(image: &mut Image<P>, x: u32, y: u32, fill_with: P)
where
P: Pixel + PartialEq,
{
let target = image.get_pixel(x, y).clone();

let mut stack = Vec::new();

stack.push((x as i32, x as i32, y as i32, 1 as i32));
stack.push((x as i32, x as i32, y as i32 - 1, -1 as i32));

while !stack.is_empty() {
let (x1, x2, y, dy) = stack.pop().unwrap();
let mut x1 = x1;
let mut x = x1;
if inside(image, x, y, target) {
while inside(image, x - 1, y, target) {
image.put_pixel(x as u32 - 1, y as u32, fill_with);
x = x - 1;
}
if x < x1 {
stack.push((x, x1 - 1, y - dy, -dy))
}
}
while x1 <= x2 {
while inside(image, x1, y, target) {
image.put_pixel(x1 as u32, y as u32, fill_with);
x1 = x1 + 1;
}
if x1 > x {
stack.push((x, x1 - 1, y + dy, dy))
}
if x1 - 1 > x2 {
stack.push((x2 + 1, x1 - 1, y - dy, -dy))
}
x1 = x1 + 1;
while x1 < x2 && !inside(image, x1, y, target) {
x1 = x1 + 1
}
x = x1
}
}
}

/// Determines whether (x,y) is within the image bounds and if the pixel there is equal to target_color
fn inside<P>(image: &Image<P>, x: i32, y: i32, target_pixel: P) -> bool
where
P: Pixel + PartialEq,
{
if x < 0 || y < 0 {
return false;
}
let x = x as u32;
let y = y as u32;
let (width, height) = image.dimensions();
x < width && y < height && *image.get_pixel(x, y) == target_pixel
}
3 changes: 3 additions & 0 deletions src/drawing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ pub use self::rect::{
mod text;
pub use self::text::{draw_text, draw_text_mut, text_size};

mod fill;
pub use self::fill::{flood_fill, flood_fill_mut};

// Set pixel at (x, y) to color if this point lies within image bounds,
// otherwise do nothing.
fn draw_if_in_bounds<C>(canvas: &mut C, x: i32, y: i32, color: C::Pixel)
Expand Down
Binary file added tests/data/truth/flood_filled_shape.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions tests/regression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,24 @@ fn test_draw_filled_ellipse() {
compare_to_truth_image(&image, "filled_ellipse.png");
}

#[test]
fn test_draw_flood_filled_shape() {
use imageproc::drawing::{draw_hollow_ellipse_mut, flood_fill, flood_fill_mut};

let red = Rgb([255, 0, 0]);
let green = Rgb([0, 255, 0]);
let blue = Rgb([0, 0, 255]);
let mut image = RgbImage::from_pixel(200, 200, Rgb([255, 255, 255]));

draw_hollow_ellipse_mut(&mut image, (100, 100), 50, 50, red);
draw_hollow_ellipse_mut(&mut image, (50, 100), 40, 90, blue);
draw_hollow_ellipse_mut(&mut image, (100, 150), 80, 30, green);
draw_hollow_ellipse_mut(&mut image, (150, 150), 100, 60, blue);

flood_fill_mut(&mut image, 120, 120, red);
compare_to_truth_image(&image, "flood_filled_shape.png");
}

#[test]
fn test_hough_line_detection() {
use imageproc::hough::{detect_lines, draw_polar_lines, LineDetectionOptions, PolarLine};
Expand Down

0 comments on commit a6fe217

Please sign in to comment.