use std::{cmp::Ordering, marker::PhantomData};
#[cfg(test)]
use image::{Rgb, RgbImage};
use crate::reader::binarize::BinaryImage;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Hash)]
pub struct Point {
pub x: i32,
pub y: i32,
}
impl Point {
pub fn dist_sq(&self, other: &Point) -> u32 {
let dx = other.x - self.x;
let dy = other.y - self.y;
(dx * dx + dy * dy) as _
}
#[cfg(test)]
pub fn highlight(&self, img: &mut RgbImage, color: Rgb<u8>) {
let (w, h) = img.dimensions();
for i in [-1, 0, 1] {
for j in [-1, 0, 1] {
let nx = ((self.x - i) as u32).min(w - 1);
let ny = ((self.y - j) as u32).min(h - 1);
img.put_pixel(nx, ny, color);
}
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
pub struct Slope {
pub dx: i32,
pub dy: i32,
}
impl Slope {
pub fn new(start: &Point, end: &Point) -> Self {
let dx = end.x - start.x;
let dy = end.y - start.y;
Self { dx, dy }
}
pub fn cross(&self, other: &Self) -> i32 {
self.dx * other.dy - self.dy * other.dx
}
}
pub trait Axis {
fn bound_check(img: &BinaryImage, pt: &Point) -> bool;
fn shift(pt: &mut Point, dist: &(i32, i32)); fn shift_cross(pt: &mut Point, dist: &(i32, i32)); fn delta(m: &Slope) -> i32; fn delta_cross(m: &Slope) -> i32; fn is_aligned(a: &Point, b: &Point) -> bool; }
pub struct X;
impl Axis for X {
fn bound_check(img: &BinaryImage, pt: &Point) -> bool {
0 <= pt.x && pt.x < img.w as i32
}
fn shift(pt: &mut Point, dist: &(i32, i32)) {
pt.x += dist.0;
}
fn shift_cross(pt: &mut Point, dist: &(i32, i32)) {
pt.y += dist.1;
}
fn delta(m: &Slope) -> i32 {
m.dx
}
fn delta_cross(m: &Slope) -> i32 {
m.dy
}
fn is_aligned(a: &Point, b: &Point) -> bool {
a.x == b.x
}
}
pub struct Y;
impl Axis for Y {
fn bound_check(img: &BinaryImage, pt: &Point) -> bool {
0 <= pt.y && pt.y < img.h as i32
}
fn shift(pt: &mut Point, dist: &(i32, i32)) {
pt.y += dist.1;
}
fn shift_cross(pt: &mut Point, dist: &(i32, i32)) {
pt.x += dist.0;
}
fn delta(m: &Slope) -> i32 {
m.dy
}
fn delta_cross(m: &Slope) -> i32 {
m.dx
}
fn is_aligned(a: &Point, b: &Point) -> bool {
a.y == b.y
}
}
#[derive(Debug, Clone)]
pub struct BresenhamLine<A: Axis> {
cur: Point, end: Point,
m: Slope,
inc: (i32, i32), err: i32,
phantom: PhantomData<A>,
}
impl<A: Axis> BresenhamLine<A> {
pub fn new(from: &Point, to: &Point) -> Self {
let cur = *from;
let end = *to;
let dx = (to.x - from.x).abs();
let dy = (to.y - from.y).abs();
let m = Slope { dx: 2 * dx, dy: 2 * dy };
let xi = match to.x.cmp(&from.x) {
Ordering::Greater => 1,
Ordering::Equal => 0,
Ordering::Less => -1,
};
let yi = match to.y.cmp(&from.y) {
Ordering::Greater => 1,
Ordering::Equal => 0,
Ordering::Less => -1,
};
let inc = (xi, yi);
let err = if dy < dx { 2 * dy - dx } else { 2 * dx - dy };
let phantom = PhantomData;
Self { cur, end, m, inc, err, phantom }
}
}
impl<A: Axis> Iterator for BresenhamLine<A> {
type Item = Point;
fn next(&mut self) -> Option<Self::Item> {
if A::is_aligned(&self.cur, &self.end) {
return None;
}
let res = Some(self.cur);
if self.err > 0 {
A::shift_cross(&mut self.cur, &self.inc);
self.err -= A::delta(&self.m);
}
A::shift(&mut self.cur, &self.inc);
self.err += A::delta_cross(&self.m);
res
}
}