diff --git a/src/main.rs b/src/main.rs index a2bdb78..9ce4bf0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,7 @@ -use std::{collections::HashMap, env}; +use std::{ + collections::{HashMap, HashSet}, + env, +}; use hsl::HSL; use image::{GenericImage, GenericImageView, Pixel, Rgb, Rgba}; @@ -11,56 +14,78 @@ fn main() { let in_path = args.next().expect("i need a PATH. now!"); let start_x = args .next() - .and_then(|n| n.parse::().ok()) + .and_then(|n| n.parse::().ok()) .expect("start x"); let start_y = args .next() - .and_then(|n| n.parse::().ok()) + .and_then(|n| n.parse::().ok()) .expect("start y"); let out_path = args.next().expect("out path"); let mut img = image::open(in_path).expect("i wanna open an image"); - // let's just hsl-rotate floodfill first - let mut covered = HashMap::new(); - covered.insert((start_x, start_y), HSL::from_rgb(&[255, 0, 0])); + // get length of path, find covered + // jk we can't precompute covered + let mut covered: HashSet<(u32, u32)> = HashSet::new(); + let mut front = HashSet::new(); + front.insert((start_x, start_y)); + let mut step: u32 = 0; - // TODO: hold front and covered. duh - // scale based on longest path (ez) loop { - let mut front = HashMap::new(); - for ((x, y), hsl) in &covered { - for (dx, dy) in (-1..=1).cartesian_product(-1..=1) { - let (x, y) = (x + dx, y + dy); - let Ok(x_idx) = x.try_into() else { - break; - }; - let Ok(y_idx) = y.try_into() else { - break; - }; - - if img.in_bounds(x_idx, y_idx) - && !is_dark(img.get_pixel(x_idx, y_idx).to_rgb()) - && !covered.contains_key(&(x, y)) + let mut new_front = HashSet::new(); + for (x, y) in &front { + for (x, y) in adjacent(*x, *y) { + if img.in_bounds(x, y) + && !is_dark(img.get_pixel(x, y).to_rgb()) + && !covered.contains(&(x, y)) { - let mut color = *hsl; - color.h += 1.; - if color.h >= 360. { - color.h = 0.; - } - let (r, g, b) = color.to_rgb(); - - img.put_pixel(x_idx, y_idx, Rgba([r, g, b, 255])); - front.insert((x, y), color); + new_front.insert((x, y)); } } } - if front.is_empty() { + if new_front.is_empty() { break; } - covered.extend(front); + covered.extend(new_front.iter()); + front = new_front; + step += 1; + } + + // color :D + let change_per_step = 360. / f64::from(step).max(1.); + + let mut covered: HashSet<(u32, u32)> = HashSet::new(); + let mut front = HashMap::new(); + front.insert((start_x, start_y), HSL::from_rgb(&[255, 0, 0])); + loop { + let mut new_front = HashMap::new(); + for ((x, y), hsl) in &front { + for (x, y) in adjacent(*x, *y) { + if img.in_bounds(x, y) + && !is_dark(img.get_pixel(x, y).to_rgb()) + && !covered.contains(&(x, y)) + { + let mut color = *hsl; + color.h += change_per_step; + if color.h >= 360. { + color.h = 0.; + } + + let (r, g, b) = color.to_rgb(); + new_front.insert((x, y), color); + img.put_pixel(x, y, Rgba([r, g, b, 255])); + } + } + } + + if new_front.is_empty() { + break; + } + + covered.extend(new_front.keys()); + front = new_front; } img.save(out_path).expect("tried to save :("); @@ -70,3 +95,15 @@ fn is_dark(pixel: Rgb) -> bool { let Rgb([r, g, b]) = pixel; r < 127 && g < 127 && b < 127 } + +fn adjacent(x: u32, y: u32) -> impl Iterator { + (-1..=1) + .cartesian_product(-1..=1) + .filter_map(move |(dx, dy): (i32, i32)| { + if (dx == -1 && x == 0) || (dy == -1 && y == 0) { + None + } else { + Some(((x as i32 + dx) as u32, (y as i32 + dy) as u32)) + } + }) +}