diff --git a/src/drawing/fill.rs b/src/drawing/fill.rs new file mode 100644 index 00000000..f7cb458f --- /dev/null +++ b/src/drawing/fill.rs @@ -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: +pub fn flood_fill

(image: &Image

, x: u32, y: u32, fill_with: P) -> Image

+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

(image: &mut Image

, 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

(image: &Image

, 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 +} diff --git a/src/drawing/mod.rs b/src/drawing/mod.rs index 4883011f..a56aa0fa 100644 --- a/src/drawing/mod.rs +++ b/src/drawing/mod.rs @@ -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(canvas: &mut C, x: i32, y: i32, color: C::Pixel) diff --git a/tests/data/truth/flood_filled_shape.png b/tests/data/truth/flood_filled_shape.png new file mode 100644 index 00000000..e850eaa8 Binary files /dev/null and b/tests/data/truth/flood_filled_shape.png differ diff --git a/tests/regression.rs b/tests/regression.rs index a8e478df..0710de04 100644 --- a/tests/regression.rs +++ b/tests/regression.rs @@ -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};