diff --git a/src/drawing/line.rs b/src/drawing/line.rs new file mode 100644 index 0000000..c48f923 --- /dev/null +++ b/src/drawing/line.rs @@ -0,0 +1,174 @@ +//! adds a `line` function to Image +#![allow(clippy::missing_docs_in_private_items)] +use crate::Image; +use std::iter::Iterator; + +/// taken from https://github.com/mbr/bresenham-rs/ +pub struct Bresenham { + x: i32, + y: i32, + dx: i32, + dy: i32, + x1: i32, + diff: i32, + octant: Octant, +} + +#[derive(Copy, Clone)] +struct Octant(u8); + +impl Octant { + #[inline] + const fn from_points(start: (i32, i32), end: (i32, i32)) -> Octant { + let mut dx = end.0 - start.0; + let mut dy = end.1 - start.1; + + let mut octant = 0; + + if dy < 0 { + dx = -dx; + dy = -dy; + octant += 4; + } + + if dx < 0 { + let tmp = dx; + dx = dy; + dy = -tmp; + octant += 2 + } + + if dx < dy { + octant += 1 + } + + Octant(octant) + } + + #[inline] + const fn to_octant0(self, p: (i32, i32)) -> (i32, i32) { + match self.0 { + 0 => (p.0, p.1), + 1 => (p.1, p.0), + 2 => (p.1, -p.0), + 3 => (-p.0, p.1), + 4 => (-p.0, -p.1), + 5 => (-p.1, -p.0), + 6 => (-p.1, p.0), + 7 => (p.0, -p.1), + _ => unreachable!(), + } + } + + #[inline] + #[allow(clippy::wrong_self_convention)] + fn from_octant0(self, p: (i32, i32)) -> (i32, i32) { + match self.0 { + 0 => (p.0, p.1), + 1 => (p.1, p.0), + 2 => (-p.1, p.0), + 3 => (-p.0, p.1), + 4 => (-p.0, -p.1), + 5 => (-p.1, -p.0), + 6 => (p.1, -p.0), + 7 => (p.0, -p.1), + _ => unreachable!(), + } + } +} + +impl Bresenham { + /// Creates a new iterator. Yields intermediate points between `start` + /// and `end`. Includes `start` and `end`. + #[inline] + pub const fn new(start: (i32, i32), end: (i32, i32)) -> Bresenham { + let octant = Octant::from_points(start, end); + + let start = octant.to_octant0(start); + let end = octant.to_octant0(end); + + let dx = end.0 - start.0; + let dy = end.1 - start.1; + + Bresenham { + x: start.0, + y: start.1, + dy, + dx, + x1: end.0, + diff: dy - dx, + octant, + } + } +} + +impl Iterator for Bresenham { + type Item = (i32, i32); + + #[inline] + fn next(&mut self) -> Option { + if self.x > self.x1 { + return None; + } + + let p = (self.x, self.y); + + if self.diff >= 0 { + self.y += 1; + self.diff -= self.dx; + } + + self.diff += self.dy; + + // loop inc + self.x += 1; + + Some(self.octant.from_octant0(p)) + } +} + +impl Image<&mut [u8], CHANNELS> { + /// Draw a line from point a to point b + /// + /// Points not in bounds will not be included. + /// + /// Uses [bresenshams](https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm) line algorithm. + pub fn line(&mut self, a: (i32, i32), b: (i32, i32), color: [u8; CHANNELS]) { + for (x, y) in Bresenham::new(a, b).map(|(x, y)| (x as u32, y as u32)) { + if x < self.width() && y < self.height() { + // SAFETY: bound are checked ^ + unsafe { self.set_pixel(x, y, color) }; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bresenham() { + macro_rules! test_bresenham { + ($a:expr, $b:expr => [$(($x:expr, $y:expr)),+]) => {{ + let mut bi = Bresenham::new($a, $b); + $(assert_eq!(bi.next(), Some(($x, $y)));)+ + assert_eq!(bi.next(), None); + }} + } + test_bresenham!((6, 4), (0, 1) => [(6, 4), (5, 4), (4, 3), (3, 3), (2, 2), (1, 2), (0, 1)]); + test_bresenham!((2, 3), (2, 6) => [(2, 3), (2, 4), (2, 5), (2, 6)]); + test_bresenham!((2, 3), (5, 3) => [(2, 3), (3, 3), (4, 3), (5, 3)]); + test_bresenham!((0, 1), (6, 4) => [(0, 1), (1, 1), (2, 2), (3, 2), (4, 3), (5, 3), (6, 4)]); + } + + #[test] + fn line() { + let mut a = Image::build(5, 5).alloc(); + a.as_mut().line((0, 1), (6, 4), [255]); + assert_eq!( + a.buffer, + b"\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00" + ) + } +} diff --git a/src/drawing/mod.rs b/src/drawing/mod.rs new file mode 100644 index 0000000..18bbc5d --- /dev/null +++ b/src/drawing/mod.rs @@ -0,0 +1,2 @@ +//! contains drawing operations, like line drawing +mod line; diff --git a/src/lib.rs b/src/lib.rs index 2bbfa9e..e72ea42 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ use std::{num::NonZeroU32, slice::SliceIndex}; mod affine; pub mod builder; +mod drawing; mod overlay; pub use overlay::{Overlay, OverlayAt};