mod bresenham;
pub(in crate::primitives) mod intersection_params;
mod points;
mod styled;
mod thick_points;
use crate::{
geometry::{Dimensions, Point},
primitives::{
common::StrokeOffset,
line::thick_points::{ParallelLineType, ParallelsIterator},
PointsIter, Primitive, Rectangle,
},
transform::Transform,
SaturatingCast,
};
pub use points::Points;
pub use styled::StyledPixels;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub struct Line {
pub start: Point,
pub end: Point,
}
impl Primitive for Line {}
impl PointsIter for Line {
type Iter = Points;
fn points(&self) -> Self::Iter {
Points::new(self)
}
}
impl Dimensions for Line {
fn bounding_box(&self) -> Rectangle {
Rectangle::with_corners(self.start, self.end)
}
}
impl Line {
pub const fn new(start: Point, end: Point) -> Self {
Self { start, end }
}
fn perpendicular(&self) -> Self {
let delta = self.end - self.start;
let delta = Point::new(delta.y, -delta.x);
Line::new(self.start, self.start + delta)
}
pub(in crate::primitives) fn extents(
&self,
thickness: u32,
stroke_offset: StrokeOffset,
) -> (Line, Line) {
let mut it = ParallelsIterator::new(self, thickness.saturating_cast(), stroke_offset);
let reduce =
it.parallel_parameters.position_step.major + it.parallel_parameters.position_step.minor;
let mut left = (self.start, ParallelLineType::Normal);
let mut right = (self.start, ParallelLineType::Normal);
match stroke_offset {
StrokeOffset::None => loop {
if let Some((bresenham, reduce)) = it.next() {
right = (bresenham.point, reduce);
} else {
break;
}
if let Some((bresenham, reduce)) = it.next() {
left = (bresenham.point, reduce);
} else {
break;
}
},
StrokeOffset::Left => {
if let Some((bresenham, reduce)) = it.last() {
left = (bresenham.point, reduce);
}
}
StrokeOffset::Right => {
if let Some((bresenham, reduce)) = it.last() {
right = (bresenham.point, reduce);
}
}
};
let left_start = left.0;
let right_start = right.0;
let delta = self.end - self.start;
let left_line = Line::new(
left_start,
left_start + delta
- match left.1 {
ParallelLineType::Normal => Point::zero(),
ParallelLineType::Extra => reduce,
},
);
let right_line = Line::new(
right_start,
right_start + delta
- match right.1 {
ParallelLineType::Normal => Point::zero(),
ParallelLineType::Extra => reduce,
},
);
(left_line, right_line)
}
pub fn midpoint(&self) -> Point {
self.start + (self.end - self.start) / 2
}
pub fn delta(&self) -> Point {
self.end - self.start
}
}
impl Transform for Line {
fn translate(&self, by: Point) -> Self {
Self {
start: self.start + by,
end: self.end + by,
}
}
fn translate_mut(&mut self, by: Point) -> &mut Self {
self.start += by;
self.end += by;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
geometry::Size, iterator::IntoPixels, mock_display::MockDisplay, pixelcolor::BinaryColor,
primitives::PrimitiveStyle, Drawable, Pixel,
};
use arrayvec::ArrayVec;
#[test]
fn bounding_box() {
let start = Point::new(10, 10);
let end = Point::new(19, 29);
let line: Line = Line::new(start, end);
let backwards_line: Line = Line::new(end, start);
assert_eq!(
line.bounding_box(),
Rectangle::new(start, Size::new(10, 20))
);
assert_eq!(
backwards_line.bounding_box(),
Rectangle::new(start, Size::new(10, 20))
);
}
#[test]
fn no_stroke_width_no_line() {
let start = Point::new(2, 3);
let end = Point::new(3, 2);
let line =
Line::new(start, end).into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 0));
assert!(line.into_pixels().eq(core::iter::empty()));
}
#[test]
fn thick_line_octant_1() {
let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
Line::new(Point::new(2, 2), Point::new(20, 8))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 5))
.draw(&mut display)
.unwrap();
display.assert_pattern(&[
" # ",
" ##### ",
" ######## ",
" ########### ",
" ############### ",
" ############### ",
" ############### ",
" ########### ",
" ######## ",
" ##### ",
" # ",
]);
}
#[test]
fn thick_line_2px() {
let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
Line::new(Point::new(2, 2), Point::new(10, 2))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 2))
.draw(&mut display)
.unwrap();
Line::new(Point::new(2, 5), Point::new(2, 10))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::Off, 2))
.draw(&mut display)
.unwrap();
display.assert_pattern(&[
" ",
" ######### ",
" ######### ",
" ",
" ",
" .. ",
" .. ",
" .. ",
" .. ",
" .. ",
" .. ",
]);
}
#[test]
fn diagonal() {
let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
Line::new(Point::new(3, 2), Point::new(10, 9))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 7))
.draw(&mut display)
.unwrap();
display.assert_pattern(&[
" # ",
" ### ",
" ##### ",
" ####### ",
" ######### ",
" ######### ",
" ######### ",
" ######### ",
" ####### ",
" ##### ",
" ### ",
" # ",
]);
}
#[test]
fn thick_line_3px() {
let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
Line::new(Point::new(2, 2), Point::new(10, 2))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 3))
.draw(&mut display)
.unwrap();
Line::new(Point::new(2, 5), Point::new(2, 10))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::Off, 3))
.draw(&mut display)
.unwrap();
display.assert_pattern(&[
" ",
" ######### ",
" ######### ",
" ######### ",
" ",
" ... ",
" ... ",
" ... ",
" ... ",
" ... ",
" ... ",
]);
}
#[test]
fn thick_line_0px() {
let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
Line::new(Point::new(2, 2), Point::new(2, 2))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 3))
.draw(&mut display)
.unwrap();
display.assert_pattern(&[
" ",
" #",
" #",
" #",
]);
}
#[test]
fn event_width_offset() {
let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
Line::new(Point::new(2, 3), Point::new(10, 3))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 4))
.draw(&mut display)
.unwrap();
Line::new(Point::new(2, 9), Point::new(10, 8))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 4))
.draw(&mut display)
.unwrap();
display.assert_pattern(&[
" ",
" ######### ",
" ######### ",
" ######### ",
" ######### ",
" ",
" #### ",
" ######### ",
" ######### ",
" ######### ",
" ##### ",
]);
}
#[test]
fn points_iter() {
let line = Line::new(Point::new(10, 10), Point::new(20, 30));
let styled_points: ArrayVec<[_; 32]> = line
.clone()
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
.into_pixels()
.map(|Pixel(p, _)| p)
.collect();
let points: ArrayVec<[_; 32]> = line.points().collect();
assert_eq!(points, styled_points);
}
#[test]
fn perpendicular() {
assert_eq!(
Line::new(Point::zero(), Point::new(10, 0)).perpendicular(),
Line::new(Point::zero(), Point::new(0, -10))
);
assert_eq!(
Line::new(Point::new(10, 20), Point::new(20, 10)).perpendicular(),
Line::new(Point::new(10, 20), Point::new(0, 10))
);
assert_eq!(
Line::new(Point::zero(), Point::new(0, -10)).perpendicular(),
Line::new(Point::zero(), Point::new(-10, 0))
);
}
#[test]
fn extents_zero_thickness() {
let line = Line::new(Point::new(10, 20), Point::new(20, 10));
let (l, r) = line.extents(0, StrokeOffset::None);
assert_eq!(l, line);
assert_eq!(r, line);
}
}