From b12ba7eb35db813dbd8bd651002129db2ce301dd Mon Sep 17 00:00:00 2001 From: Dan Costinas Date: Mon, 1 Dec 2025 20:06:04 +0200 Subject: [PATCH 1/3] Check bounds when adding to stack in hysteresis --- src/edges.rs | 7 +++++++ src/filter/mod.rs | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/edges.rs b/src/edges.rs index 38ce3d9e..572235c1 100644 --- a/src/edges.rs +++ b/src/edges.rs @@ -142,6 +142,13 @@ fn hysteresis(input: &Image>, low_thresh: f32, high_thresh: f32) -> Im let out_neighbor = *out.get_pixel(neighbor_idx.0, neighbor_idx.1); if in_neighbor[0] >= low_thresh && out_neighbor[0] == 0 { out.put_pixel(neighbor_idx.0, neighbor_idx.1, max_brightness); + if neighbor_idx.0 == 0 + || neighbor_idx.0 >= input.width() - 1 + || neighbor_idx.1 == 0 + || neighbor_idx.1 >= input.height() - 1 + { + continue; + } edges.push((neighbor_idx.0, neighbor_idx.1)); } } diff --git a/src/filter/mod.rs b/src/filter/mod.rs index dda1fcdd..b6c99d31 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -810,6 +810,20 @@ mod tests { let image2 = gaussian_blur_f32(&image, 6f32); assert_pixels_eq_within!(image2, image, 1e-6); } + + #[test] + fn test_canny_zero_threshold_panic() { + let image = image::GrayImage::new(10, 10); + _ = crate::edges::canny(&image, 0.0, 0.0); + } + + #[test] + fn test_canny_one_border_pixel_panic() { + let mut image = image::GrayImage::new(10, 10); + *image.get_pixel_mut(9, 0) = Luma([255u8]); + _ = crate::edges::canny(&image, 0.0, 100.0); + } + } #[cfg(not(miri))] From 734b329828a65dd0dbd7c1b48dda7ee472a55f18 Mon Sep 17 00:00:00 2001 From: Dan Costinas Date: Mon, 1 Dec 2025 20:39:07 +0200 Subject: [PATCH 2/3] Check all neighbors for canny --- src/edges.rs | 2 ++ tests/data/truth/zebra_canny.png | Bin 3373 -> 3346 bytes 2 files changed, 2 insertions(+) diff --git a/src/edges.rs b/src/edges.rs index 572235c1..19f25602 100644 --- a/src/edges.rs +++ b/src/edges.rs @@ -129,9 +129,11 @@ fn hysteresis(input: &Image>, low_thresh: f32, high_thresh: f32) -> Im // Track neighbors until no neighbor is >= low_thresh. while let Some((nx, ny)) = edges.pop() { let neighbor_indices = [ + (nx + 1, ny - 1), (nx + 1, ny), (nx + 1, ny + 1), (nx, ny + 1), + (nx, ny - 1), (nx - 1, ny - 1), (nx - 1, ny), (nx - 1, ny + 1), diff --git a/tests/data/truth/zebra_canny.png b/tests/data/truth/zebra_canny.png index 48577dde973ca23f8ec8d2da5f01718c73f3e9cc..f4a8c4a0361bbaf09050a3db8297adca049569ab 100644 GIT binary patch literal 3346 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 Date: Mon, 1 Dec 2025 20:44:37 +0200 Subject: [PATCH 3/3] fix fmt --- src/filter/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/filter/mod.rs b/src/filter/mod.rs index b6c99d31..39ea71b2 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -823,7 +823,6 @@ mod tests { *image.get_pixel_mut(9, 0) = Luma([255u8]); _ = crate::edges::canny(&image, 0.0, 100.0); } - } #[cfg(not(miri))]