use super::{Drawable, put};
use crate::CoordinateI32;
use crate::image::ImageViewMut;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Line<P> {
pub from: CoordinateI32,
pub to: CoordinateI32,
pub color: P,
}
impl<P: Copy> Drawable<P> for Line<P> {
fn draw_into(&self, image: &mut impl ImageViewMut<Pixel = P>) {
segment(image, self.from, self.to, self.color);
}
}
pub fn draw_line<P: Copy>(
image: &mut impl ImageViewMut<Pixel = P>,
from: impl Into<CoordinateI32>,
to: impl Into<CoordinateI32>,
color: P,
) {
Line {
from: from.into(),
to: to.into(),
color,
}
.draw_into(image);
}
pub(super) fn segment<P: Copy>(
image: &mut impl ImageViewMut<Pixel = P>,
from: CoordinateI32,
to: CoordinateI32,
color: P,
) {
let size = image.size();
let (w, h) = (size.width as i64, size.height as i64);
let (x0, y0) = (i64::from(from.x), i64::from(from.y));
let (x1, y1) = (i64::from(to.x), i64::from(to.y));
if x0.max(x1) < 0 || x0.min(x1) >= w || y0.max(y1) < 0 || y0.min(y1) >= h {
return;
}
let a = (x1 - x0).abs();
let b = (y1 - y0).abs();
let sx = if x0 < x1 { 1 } else { -1 };
let sy = if y0 < y1 { 1 } else { -1 };
let steps = a.max(b);
let (m, n) = (a.max(b), a.min(b));
let x_is_major = a >= b;
let (major0, s_major, major_len) = if x_is_major { (x0, sx, w) } else { (y0, sy, h) };
let (t_major_lo, t_major_hi) = if s_major == 1 {
(-major0, major_len - 1 - major0)
} else {
(major0 - (major_len - 1), major0)
};
let (minor0, s_minor, minor_len) = if x_is_major { (y0, sy, h) } else { (x0, sx, w) };
let (t_minor_lo, t_minor_hi) = if n == 0 {
(0, steps)
} else {
let (k_lo, k_hi) = if s_minor == 1 {
(-minor0, minor_len - 1 - minor0)
} else {
(minor0 - (minor_len - 1), minor0)
};
let (k_lo, k_hi) = (k_lo.max(0), k_hi.min(n));
if k_lo > k_hi {
return;
}
let ceil_div = |p: i128, q: i128| ((p + q - 1) / q) as i64;
let lo = if k_lo <= 0 {
0
} else {
ceil_div((m as i128) * (2 * k_lo as i128 - 1), 2 * n as i128)
};
let hi = if k_hi >= n {
steps
} else {
ceil_div((m as i128) * (2 * k_hi as i128 + 1), 2 * n as i128) - 1
};
(lo, hi)
};
let t_lo = t_major_lo.max(t_minor_lo).max(0);
let t_hi = t_major_hi.min(t_minor_hi).min(steps);
if t_lo > t_hi {
return;
}
let minor_at = |t: i64| -> i64 {
if n == 0 || t == 0 {
0
} else {
(((2 * n as i128 * t as i128 - m as i128).div_euclid(2 * m as i128)) as i64 + 1).max(0)
}
};
let k = minor_at(t_lo);
let mut err = ((a - b) as i128
+ if x_is_major {
m as i128 * k as i128 - n as i128 * t_lo as i128
} else {
n as i128 * t_lo as i128 - m as i128 * k as i128
}) as i64;
let (mut x, mut y) = if x_is_major {
(x0 + sx * t_lo, y0 + sy * k)
} else {
(x0 + sx * k, y0 + sy * t_lo)
};
let dx = a;
let dy = -b;
for _ in t_lo..=t_hi {
put(image, x, y, color);
if x == x1 && y == y1 {
return;
}
let e2 = 2 * err;
if e2 >= dy {
err += dy;
x += sx;
}
if e2 <= dx {
err += dx;
y += sy;
}
}
}
#[cfg(test)]
mod tests {
use super::super::tests::inked;
use super::*;
use crate::image::Image;
use crate::pixel::Mono8;
fn ink() -> Mono8 {
Mono8::new(255)
}
#[test]
fn horizontal_vertical_and_point() {
let mut image: Image<Mono8> = Image::zero(6, 6);
draw_line(&mut image, (1, 2), (4, 2), ink());
assert_eq!(inked(&image), vec![(1, 2), (2, 2), (3, 2), (4, 2)]);
let mut image: Image<Mono8> = Image::zero(6, 6);
draw_line(&mut image, (3, 4), (3, 1), ink());
assert_eq!(inked(&image), vec![(3, 1), (3, 2), (3, 3), (3, 4)]);
let mut image: Image<Mono8> = Image::zero(6, 6);
draw_line(&mut image, (2, 5), (2, 5), ink());
assert_eq!(inked(&image), vec![(2, 5)]);
}
#[test]
fn perfect_diagonals_in_all_four_directions() {
for (from, to) in [
((0, 0), (5, 5)),
((5, 5), (0, 0)),
((0, 5), (5, 0)),
((5, 0), (0, 5)),
] {
let mut image: Image<Mono8> = Image::zero(6, 6);
draw_line(&mut image, from, to, ink());
let drawn = inked(&image);
assert_eq!(drawn.len(), 6, "{from:?} -> {to:?}: {drawn:?}");
for (x, y) in drawn {
let on_main = x == y;
let on_anti = x + y == 5;
assert!(on_main || on_anti, "({x}, {y}) off both diagonals");
}
}
}
#[test]
fn shallow_slope_is_one_pixel_per_column() {
let mut image: Image<Mono8> = Image::zero(10, 4);
draw_line(&mut image, (0, 0), (9, 3), ink());
let drawn = inked(&image);
assert_eq!(drawn.len(), 10);
let mut columns: Vec<usize> = drawn.iter().map(|&(x, _)| x).collect();
columns.sort_unstable();
assert_eq!(columns, (0..10).collect::<Vec<_>>());
let mut ys: Vec<usize> = (0..10)
.map(|x| drawn.iter().find(|&&(px, _)| px == x).unwrap().1)
.collect();
let sorted = ys.clone();
ys.sort_unstable();
assert_eq!(ys, sorted);
}
#[test]
fn steep_slope_is_one_pixel_per_row() {
let mut image: Image<Mono8> = Image::zero(4, 10);
draw_line(&mut image, (0, 0), (3, 9), ink());
let drawn = inked(&image);
assert_eq!(drawn.len(), 10);
let rows: Vec<usize> = drawn.iter().map(|&(_, y)| y).collect();
assert_eq!(rows, (0..10).collect::<Vec<_>>());
}
#[test]
fn clips_a_partially_visible_line() {
let mut image: Image<Mono8> = Image::zero(4, 4);
draw_line(&mut image, (-3, 1), (7, 1), ink());
assert_eq!(inked(&image), vec![(0, 1), (1, 1), (2, 1), (3, 1)]);
}
#[test]
fn fully_outside_draws_nothing() {
let mut image: Image<Mono8> = Image::zero(4, 4);
draw_line(&mut image, (-5, -5), (-1, -2), ink());
draw_line(&mut image, (4, 0), (9, 3), ink());
draw_line(&mut image, (0, 4), (3, 9), ink());
draw_line(&mut image, (i32::MIN, i32::MIN), (-1, i32::MAX), ink());
assert!(inked(&image).is_empty());
}
#[test]
fn extreme_endpoints_do_not_overflow() {
let mut image: Image<Mono8> = Image::zero(4, 4);
draw_line(&mut image, (-2, -2), (5, 5), ink());
assert_eq!(inked(&image), vec![(0, 0), (1, 1), (2, 2), (3, 3)]);
}
fn reference_segment(image: &mut Image<Mono8>, from: (i32, i32), to: (i32, i32), color: Mono8) {
use crate::image::ImageView;
let (mut x, mut y) = (i64::from(from.0), i64::from(from.1));
let (x1, y1) = (i64::from(to.0), i64::from(to.1));
let dx = (x1 - x).abs();
let dy = -(y1 - y).abs();
let sx = if x < x1 { 1 } else { -1 };
let sy = if y < y1 { 1 } else { -1 };
let mut err = dx + dy;
loop {
if x >= 0 && y >= 0 && x < image.width() as i64 && y < image.height() as i64 {
*image.pixel_at_mut(x as usize, y as usize) = color;
}
if x == x1 && y == y1 {
return;
}
let e2 = 2 * err;
if e2 >= dy {
err += dy;
x += sx;
}
if e2 <= dx {
err += dx;
y += sy;
}
}
}
#[test]
fn clipping_matches_the_unclipped_walk_exactly() {
let coords: Vec<(i32, i32)> = (-6..=9)
.flat_map(|x| (-6..=9).map(move |y| (x, y)))
.collect();
for &from in &coords {
for &to in &coords {
let mut clipped: Image<Mono8> = Image::zero(5, 4);
draw_line(&mut clipped, from, to, ink());
let mut reference: Image<Mono8> = Image::zero(5, 4);
reference_segment(&mut reference, from, to, ink());
assert_eq!(
inked(&clipped),
inked(&reference),
"{from:?} -> {to:?} diverged from the unclipped walk"
);
}
}
}
#[test]
fn far_off_image_endpoints_cost_only_the_visible_span() {
let mut image: Image<Mono8> = Image::zero(4, 4);
draw_line(&mut image, (i32::MIN, 0), (i32::MAX, 0), ink());
assert_eq!(inked(&image), vec![(0, 0), (1, 0), (2, 0), (3, 0)]);
let mut image: Image<Mono8> = Image::zero(4, 4);
draw_line(
&mut image,
(-10_000_000, -10_000_000),
(10_000_000, 10_000_000),
ink(),
);
assert_eq!(inked(&image), vec![(0, 0), (1, 1), (2, 2), (3, 3)]);
let mut image: Image<Mono8> = Image::zero(4, 4);
draw_line(&mut image, (1, i32::MIN), (2, i32::MAX), ink());
assert_eq!(inked(&image).len(), 4);
let mut image: Image<Mono8> = Image::zero(4, 4);
draw_line(&mut image, (i32::MIN, -20), (0, 20), ink());
assert!(inked(&image).is_empty());
}
}