From 60bce4b5f257d9078184913b4e9a878e1736aba0 Mon Sep 17 00:00:00 2001 From: Hunter Chen <0x@hunterchen.ca> Date: Thu, 29 Feb 2024 03:17:03 -0500 Subject: [PATCH 1/2] DFS should take type > instead of img --- src/img_to_line.rs | 43 +++++++++++++++++++++++++++---------------- src/main.rs | 15 ++++----------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/img_to_line.rs b/src/img_to_line.rs index 19a11ec..080d4f4 100644 --- a/src/img_to_line.rs +++ b/src/img_to_line.rs @@ -54,21 +54,19 @@ fn first_white_from(img: &DynamicImage, start: (u32, u32)) -> Option<(u32, u32)> first_col_from(img, WHITE, start) } -fn oob(x: i32, y: i32, img: &DynamicImage) -> bool { - x < 0 || y < 0 || x as u32 >= img.width() || y as u32 >= img.height() -} - fn dfs( x: i32, y: i32, - visited: &mut [bool], - img: &DynamicImage, + visited: &mut Vec>, + img: &mut Vec>, path: &mut Vec<(i32, i32)>, - col: image::Rgba, ) { - if oob(x, y, img) - || img.get_pixel(x as u32, y as u32) != col - || visited[(y * img.width() as i32 + x) as usize] + if x < 0 + || y < 0 + || x >= img.len() as i32 + || y >= img[0].len() as i32 + || visited[y as usize][x as usize] + || !img[y as usize][x as usize] { // if out of bounds or already visited or not white return; @@ -76,7 +74,7 @@ fn dfs( // TODO: add path of (x, y) -> path[-1] to path, otherwise there are disconnected lines which means weird equations path.push((x, y)); // path.push((x, y)); // add to path - visited[(y * img.width() as i32 + x) as usize] = true; // set visited + visited[y as usize][x as usize] = true; // set visited for (i, j) in [ (-1, -1), (0, -1), @@ -88,7 +86,7 @@ fn dfs( (1, 1), ] { // loop through surrounding 3x3 - dfs(x + i, y + j, visited, img, path, col); + dfs(x + i, y + j, visited, img, path); } } @@ -119,17 +117,30 @@ fn remove_start_palindrome(path: &mut Vec<(i32, i32)>) { } } +fn img_to_bool(img: &DynamicImage, col: image::Rgba) -> Vec> { + let mut bool_img = vec![]; + for y in 0..img.height() { + let mut row = vec![]; + for x in 0..img.width() { + row.push(img.get_pixel(x, y) == col); + } + bool_img.push(row); + } + bool_img +} + pub fn edges_to_lines(img: &mut DynamicImage, col: image::Rgba) -> Vec> { let mut lines = vec![]; let dims = img.dimensions(); - let mut visited = vec![false; (dims.0 * dims.1) as usize]; + let mut visited = vec![vec![false; dims.0 as usize]; dims.1 as usize]; + let mut img = img_to_bool(img, col); for x in 0..dims.0 { for y in 0..dims.1 { - if !visited[(y * dims.0 + x) as usize] && img.get_pixel(x, y) == col { + if !visited[y as usize][x as usize] && img[y as usize][x as usize] { let mut path = vec![]; - dfs(x as i32, y as i32, &mut visited, img, &mut path, col); - // somehow neither of these seem to do anything but they work in tests + dfs(x as i32, y as i32, &mut visited, &mut img, &mut path); + // somehow neither of these seem to do anything, but they work in tests // the idea is that if dfs backtracks to a point that is already in the path // from the end of the path, then it can be partially truncated remove_start_palindrome(&mut path); diff --git a/src/main.rs b/src/main.rs index ad05f7f..3c6cb8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,25 +10,18 @@ fn main() -> std::io::Result<()> { let handler = builder .spawn(|| { - let mut img = img_to_line::get_image("images/big_apple.jpg"); + let img = img_to_line::get_image("images/shapes1.png"); let now = Instant::now(); - let blurred = edge_detection::gaussian_blur_5x5(&img); - blurred.save("generated/blurred.png").unwrap(); - println!("Gaussian blur new: {:?}", now.elapsed()); - - let now = Instant::now(); - let mut edges = edge_detection::sobel_default(&img); + let mut edges = edge_detection::canny(&img, 50.0, 100.0); println!("Sobel: {:?}", now.elapsed()); edges.save("generated/edges.png").unwrap(); - let blurred_edges = edge_detection::sobel(&blurred); - blurred_edges.save("generated/blurred_edges.png").unwrap(); - let now = Instant::now(); - let mut lines = img_to_line::edges_to_lines_w(&mut edges); + let mut lines = img_to_line::edges_to_lines_b(&mut edges); lines.sort_by_key(|b| std::cmp::Reverse(b.len())); // sort by length lines.truncate(32); // only take n longest lines + img_to_line::lines_to_img(&lines); println!("Edges to lines: {:?}", now.elapsed()); let mut file = File::create("generated/equations.txt").unwrap(); From 2fca7375ede7fe7e6765faa788131aebc02d4ff4 Mon Sep 17 00:00:00 2001 From: Hunter Chen <0x@hunterchen.ca> Date: Thu, 29 Feb 2024 03:56:22 -0500 Subject: [PATCH 2/2] Update img_to_line.rs --- src/img_to_line.rs | 47 ++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/img_to_line.rs b/src/img_to_line.rs index 080d4f4..0355cd8 100644 --- a/src/img_to_line.rs +++ b/src/img_to_line.rs @@ -57,24 +57,25 @@ fn first_white_from(img: &DynamicImage, start: (u32, u32)) -> Option<(u32, u32)> fn dfs( x: i32, y: i32, - visited: &mut Vec>, + // visited: &mut Vec>, img: &mut Vec>, path: &mut Vec<(i32, i32)>, ) { if x < 0 || y < 0 - || x >= img.len() as i32 - || y >= img[0].len() as i32 - || visited[y as usize][x as usize] + || x >= img[0].len() as i32 + || y >= img.len() as i32 + // || visited[y as usize][x as usize] || !img[y as usize][x as usize] { - // if out of bounds or already visited or not white + // if out of bounds or already visited or not color match return; } // TODO: add path of (x, y) -> path[-1] to path, otherwise there are disconnected lines which means weird equations path.push((x, y)); // path.push((x, y)); // add to path - visited[y as usize][x as usize] = true; // set visited + // visited[y as usize][x as usize] = true; // set visited + img[y as usize][x as usize] = false; // set visited for (i, j) in [ (-1, -1), (0, -1), @@ -86,7 +87,7 @@ fn dfs( (1, 1), ] { // loop through surrounding 3x3 - dfs(x + i, y + j, visited, img, path); + dfs(x + i, y + j, img, path); } } @@ -102,6 +103,7 @@ fn is_palindrome(s: usize, e: usize, path: &[(i32, i32)]) -> bool { true } +// some attempts at path shortening by removing end/beginning palindromic paths fn remove_end_palindrome(path: &mut Vec<(i32, i32)>) { let longest_palindrome = (0..path.len()).rev().find(|&i| is_palindrome(0, i, path)); if let Some(i) = longest_palindrome { @@ -117,6 +119,9 @@ fn remove_start_palindrome(path: &mut Vec<(i32, i32)>) { } } +// takes in an image and returns a 2D vec of bools, true if pixel is color +// purpose is to reduce memory usage, since img contains 4 u8 per pixel +// compiler might end up optimizing this away anyway, but I think it's worth a shot fn img_to_bool(img: &DynamicImage, col: image::Rgba) -> Vec> { let mut bool_img = vec![]; for y in 0..img.height() { @@ -129,22 +134,22 @@ fn img_to_bool(img: &DynamicImage, col: image::Rgba) -> Vec> { bool_img } +// takes in an image and returns a 2D vec of points, each inner vec represents a line/path pub fn edges_to_lines(img: &mut DynamicImage, col: image::Rgba) -> Vec> { let mut lines = vec![]; let dims = img.dimensions(); - let mut visited = vec![vec![false; dims.0 as usize]; dims.1 as usize]; + // let mut visited = vec![vec![false; dims.0 as usize]; dims.1 as usize]; let mut img = img_to_bool(img, col); for x in 0..dims.0 { for y in 0..dims.1 { - if !visited[y as usize][x as usize] && img[y as usize][x as usize] { + if img[y as usize][x as usize] { let mut path = vec![]; - dfs(x as i32, y as i32, &mut visited, &mut img, &mut path); - // somehow neither of these seem to do anything, but they work in tests - // the idea is that if dfs backtracks to a point that is already in the path - // from the end of the path, then it can be partially truncated - remove_start_palindrome(&mut path); - remove_end_palindrome(&mut path); + dfs(x as i32, y as i32, &mut img, &mut path); + // the idea with these is that if dfs backtracks to a point that is already in the + // path from the end/beginning of the path, then it can be partially truncated + // remove_start_palindrome(&mut path); + // remove_end_palindrome(&mut path); if path.len() > 16 { lines.push(path); } @@ -164,6 +169,7 @@ pub fn edges_to_lines_b(img: &mut DynamicImage) -> Vec> { edges_to_lines(img, BLACK) } +// generates a random color, used in lines_to_img to visualize generated lines fn random_col() -> image::Rgba { let col1 = rand::thread_rng().gen_range(100..255); let col2 = rand::thread_rng().gen_range(100..255); @@ -171,14 +177,17 @@ fn random_col() -> image::Rgba { image::Rgba([col1, col2, col3, 255]) } -pub fn line_to_img(img: &mut DynamicImage, line: &[(i32, i32)], col: image::Rgba) { +// takes in a mutable image and a line, and colors the pixels in the line +// used in lines_to_img +fn line_to_img(img: &mut DynamicImage, line: &[(i32, i32)], col: image::Rgba) { for point in line.iter() { img.put_pixel(point.0 as u32, point.1 as u32, col); } -} // fn line_to_img() +} +// takes in a 2D vec of points where the inner vectors represent lines pub fn lines_to_img(lines: &[Vec<(i32, i32)>]) { - let (mut max_x, mut max_y) = (0, 0); + let (mut max_x, mut max_y) = (0, 0); // find max x and y to set image dimensions for line in lines.iter() { for point in line.iter() { if point.0 > max_x { @@ -189,6 +198,8 @@ pub fn lines_to_img(lines: &[Vec<(i32, i32)>]) { } } } + // + 50 for a bit of padding, might need a better system for this + // TODO? let mut img = DynamicImage::new_rgb8(max_x as u32 + 50, max_y as u32 + 50); for line in lines.iter() { let col = random_col();