bhcli

A TUI for chatting on LE PHP Chats (onion)
git clone https://git.dasho.dev/n0tr1v/bhcli.git
Log | Files | Refs | README

commit 183c9a8099e32fea5d3d92c4d3e09f4df54795e5
parent c5100a462403259664507e7a912c884585fbb68a
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Thu, 30 Mar 2023 18:05:04 -0700

new algorithm to find path

Diffstat:
MCargo.lock | 7+++++++
MCargo.toml | 1+
Msrc/lechatphp/mod.rs | 140+++++++++++--------------------------------------------------------------------
3 files changed, 26 insertions(+), 122 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -108,6 +108,7 @@ name = "bhcli" version = "0.1.0" dependencies = [ "base64", + "bresenham", "chrono", "clap 4.1.14", "clipboard", @@ -190,6 +191,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] +name = "bresenham" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfc1116225f66d2ea341a26503f83a6b1205070a6f7199ce1f1550ead91f6fd7" + +[[package]] name = "bumpalo" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] base64 = "0.21.0" +bresenham = "0.1.1" chrono = "0.4.19" clap = { version = "4.1.14", features = ["derive", "env"] } clipboard = "0.5.0" diff --git a/src/lechatphp/mod.rs b/src/lechatphp/mod.rs @@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Formatter}; use std::hash::Hash; use base64::{engine::general_purpose, Engine as _}; +use bresenham::Bresenham; use image::{ColorType, DynamicImage, GenericImageView, Rgba}; use lazy_static::lazy_static; @@ -100,8 +101,8 @@ pub fn solve_b64(b64_str: &str) -> Option<String> { if img.width() > 60 { return match solve_difficulty3(&img) { Ok(answer) => Some(answer), - Err(_e) => { - // println!("{:?}", e); + Err(e) => { + println!("{:?}", e); None }, }; @@ -166,10 +167,6 @@ impl Letter { let offset = self.offset(); Point::new(offset.x + LETTER_WIDTH/2, offset.y + LETTER_HEIGHT/2 - 1) } - - fn angle(&self, other: &Self) -> f64 { - get_angle(&self.center(), &other.center()) - } } #[derive(Debug)] @@ -203,32 +200,27 @@ fn solve_difficulty3(img: &DynamicImage) -> Result<String, CaptchaErr> { .ok_or(CaptchaErr("could not find starting letter".to_owned()))?; let mut answer = String::new(); - let mut visited = HashSet::<Letter>::new(); + let mut remaining: HashSet<Letter> = letters_map.values().cloned().collect(); let mut letter = starting; + remaining.remove(&letter); answer.push(letter.character); - visited.insert(letter.clone()); - for i in 0..NB_CHARS-1 { - let top_left = Point::new(letter.offset.x-5, letter.offset.y-3); - let mut rect = Rect{p1: top_left, width: LETTER_WIDTH+5+6, height: LETTER_HEIGHT+3+2}; - let mut retry = 0; - 'retry_loop: loop { - retry += 1; - let square = img.crop_imm(rect.p1.x, rect.p1.y, rect.width, rect.height); - let red_px_pts = get_contour_red_pixels(&rect.p1, &square); - let nb_lines = if i == 0 { 1 } else { 2 }; - if red_px_pts.len() != nb_lines { - if retry < 10 { - rect.enlarge(); - continue 'retry_loop; + for _ in 0..NB_CHARS-1 { + let mut hm = HashMap::<Letter, u32>::new(); + for l in remaining.iter() { + let mut red = 0; + let p1 = letter.center(); + let p2 = l.center(); + for (x, y) in Bresenham::new((p1.x as isize, p1.y as isize), (p2.x as isize, p2.y as isize)) { + if img.get_pixel(x as u32, y as u32) == *RED_COLOR { + red += 1; } - return Err(CaptchaErr(format!("letter #{} {:?} invalid nb lines detected ({})", i+1, letter, red_px_pts.len()))); } - letter = get_next_node(&red_px_pts, &letter, &letters_map, &visited, i, nb_lines) - .ok_or(CaptchaErr(format!("letter #{} {:?} all neighbors already visited", i+1, letter)))?; - break 'retry_loop; + hm.insert(l.clone(), red); } + let (max, _) = hm.iter().max_by(|a, b| a.1.cmp(b.1)).unwrap(); + letter = max.clone(); + remaining.remove(&letter); answer.push(letter.character); - visited.insert(letter.clone()); } Ok(answer) } @@ -298,74 +290,6 @@ fn get_starting_letter(img: &DynamicImage, letters_map: &HashMap<char, Letter>) starting } -fn get_next_node(red_px_pts: &Vec<Point>, letter: &Letter, letters_map: &HashMap<char, Letter>, visited: &HashSet<Letter>, i: u32, nb_lines: usize) -> Option<Letter> { - if i == NB_CHARS-2 { - for (_, l) in letters_map.iter() { - if !visited.contains(&l) { - return Some(l.clone()); - } - } - return None; - } - for idx in 0..nb_lines { - let fst_red_pt = red_px_pts.get(idx).unwrap(); - let angle = get_angle(fst_red_pt, &letter.center()); - let neighbor = get_letter_in_direction(&letter, angle, &letters_map).unwrap(); - if !visited.contains(&neighbor) { - return Some(neighbor); - } - } - None -} - -fn get_letter_in_direction(letter: &Letter, angle: f64, letters_map: &HashMap<char, Letter>) -> Option<Letter> { - let mut min_angle = f64::MAX; - let mut out: Option<Letter> = None; - // Visit every other letters - for (_, other_letter) in letters_map.iter() { - if other_letter == letter { - continue; - } - // Find the angle between the two letters - let t = other_letter.angle(letter); - let angle_diff = (angle - t).abs(); - if angle_diff < min_angle { - // Keep track of the letter with the smaller angle difference - min_angle = angle_diff; - out = Some(other_letter.clone()); - } - } - out -} - -// Find the angle in radians between two points -// Ensure the angle is always positive [0, 2*PI] -fn get_angle(p1: &Point, p2: &Point) -> f64 { - let delta_y = p1.y as f64 - p2.y as f64; - let delta_x = p1.x as f64 - p2.x as f64; - let mut angle = delta_y.atan2(delta_x); - if angle < 0.0 { - angle += 2.0 * std::f64::consts::PI; - } - angle -} - -#[derive(Debug)] -struct Rect { - p1: Point, - width: u32, - height: u32, -} - -impl Rect { - fn enlarge(&mut self) { - self.p1.x = self.p1.x.checked_sub(1).unwrap_or(0); - self.p1.y = self.p1.y.checked_sub(1).unwrap_or(0); - self.width += 2; - self.height += 2; - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct Point { x: u32, @@ -376,34 +300,6 @@ impl Point { fn new(x: u32, y: u32) -> Self { Self{x, y} } - - fn add(&self, other: Self) -> Self { - Self{x: self.x + other.x, y: self.y + other.y} - } -} - -fn get_contour_red_pixels(top_left_pt: &Point, img: &DynamicImage) -> Vec<Point> { - let (img_width, img_height) = img.dimensions(); - let mut out = vec![]; - let mut add_px = |x, y| { - if let Some(px_color) = get_pixel_in_bound(img, x, y) { - if px_color == *RED_COLOR { - out.push(top_left_pt.add(Point::new(x, y))); - } - } - }; - // process points on x axis, top/bottom lines - for x in 0..img_width { - add_px(x, 0); - add_px(x, img_height-1); - } - // Process points on y axis, left/right columns, excluding the first line and last line - // We exclude first/last lines to avoid processing the corners twice - for y in 1..img_height-1 { - add_px(0, y); - add_px(img_width-1, y); - } - out } fn get_pixel_in_bound(img: &DynamicImage, x: u32, y: u32) -> Option<Rgba<u8>> {