Skip to content

Commit

Permalink
Add triangle primitive (#86)
Browse files Browse the repository at this point in the history
* triagles draft

* ignore vscode settings

* triangle draft

* Always draw lines from start to end

* fix missing points in triangle

* update fill and stroke simulator

* fix line tests

* fix test and triangle line error

* fix bug where end point was missing

* triangle example

* fix warnings

* add comments

* format code

* move size change to stroke example

* update readme

* cleanup

* don't draw lines with length zero

* fix format
  • Loading branch information
weichweich authored and jamwaffles committed May 5, 2019
1 parent 29d58c9 commit f2e105d
Show file tree
Hide file tree
Showing 8 changed files with 671 additions and 115 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

.vscode/
/target/
**/*.rs.bk
Cargo.lock
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ following:
* Lines
* Rectangles (and squares)
* Circles
* Triangles
* Text with [multiple bitmap fonts](src/fonts)

A core goal is to do the above without using any buffers; the crate should work without a
Expand Down
196 changes: 88 additions & 108 deletions embedded-graphics/src/primitives/line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,121 +90,85 @@ where

impl<'a, C: PixelColor> IntoIterator for &'a Line<C> {
type Item = Pixel<C>;
type IntoIter = LineIterator<'a, C>;
type IntoIter = LineIterator<C>;

fn into_iter(self) -> Self::IntoIter {
let x0 = self.start[0].max(0);
let y0 = self.start[1].max(0);
let x1 = self.end[0].max(0);
let y1 = self.end[1].max(0);

// Find out if our line is steep or shallow
let is_steep = (y1 - y0).abs() > (x1 - x0).abs();

// Determine if endpoints should be switched
// based on the "quick" direction
let (x0, y0, x1, y1) = if is_steep {
if y0 > y1 {
(x1, y1, x0, y0)
} else {
(x0, y0, x1, y1)
}
} else {
if x0 > x1 {
(x1, y1, x0, y0)
} else {
(x0, y0, x1, y1)
}
};

// Setup our pre-calculated values
let (dquick, mut dslow) = if is_steep {
(y1 - y0, x1 - x0)
} else {
(x1 - x0, y1 - y0)
};
let mut delta = self.end - self.start;
if delta[0] < 0 {
delta = Coord::new(-delta[0], delta[1]);
}
if delta[1] > 0 {
delta = Coord::new(delta[0], -delta[1]);
}

// Determine how we should increment the slow direction
let increment = if dslow < 0 {
dslow = -dslow;
-1
} else {
1
let direction = match (self.start[0] >= self.end[0], self.start[1] >= self.end[1]) {
(false, false) => Coord::new(1, 1),
(false, true) => Coord::new(1, -1),
(true, false) => Coord::new(-1, 1),
(true, true) => Coord::new(-1, -1),
};

// Compute the default error
let error = 2 * dslow - dquick;

// Set our inital quick & slow
let (quick, slow, end) = if is_steep { (y0, x0, y1) } else { (x0, y0, x1) };

LineIterator {
line: self,

is_steep,
dquick,
dslow,
increment,
error,

quick,
slow,
end,
style: self.style,

start: self.start,
end: self.end,
delta,
direction,
err: delta[0] + delta[1],
stop: self.start == self.end, // if line length is zero, draw nothing
}
}
}

/// Pixel iterator for each pixel in the line
#[derive(Debug)]
pub struct LineIterator<'a, C: 'a>
#[derive(Debug, Clone, Copy)]
pub struct LineIterator<C>
where
C: PixelColor,
{
line: &'a Line<C>,

dquick: i32,
dslow: i32,
increment: i32,
error: i32,
is_steep: bool,

quick: i32,
slow: i32,
end: i32,
style: Style<C>,

start: Coord,
end: Coord,
delta: Coord,
/// in which quadrant is the line drawn (upper-left=(-1, -1), lower-right=(1, 1), ...)
direction: Coord,
err: i32,
stop: bool,
}

// [Bresenham's line algorithm](https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm)
impl<'a, C: PixelColor> Iterator for LineIterator<'a, C> {
impl<C: PixelColor> Iterator for LineIterator<C> {
type Item = Pixel<C>;

fn next(&mut self) -> Option<Self::Item> {
if self.quick > self.end {
return None;
}
// return none if stroke color is none
self.style.stroke_color?;

// Get the next point
// let &Line { ref color, .. } = self.line;
let coord = if self.is_steep {
Coord::new(self.slow, self.quick)
} else {
Coord::new(self.quick, self.slow)
};
while !self.stop {
let p_coord = self.start;

// Update error and increment slow direction
if self.error > 0 {
self.slow = self.slow + self.increment;
self.error -= 2 * self.dquick;
if self.start == self.end {
self.stop = true;
}
let err_double = 2 * self.err;
if err_double > self.delta[1] {
self.err += self.delta[1];
self.start += Coord::new(self.direction[0], 0);
}
if err_double < self.delta[0] {
self.err += self.delta[0];
self.start += Coord::new(0, self.direction[1]);
}
if p_coord[0] >= 0 && p_coord[1] >= 0 {
return Some(Pixel(
p_coord.to_unsigned(),
self.style.stroke_color.unwrap(),
));
}
}
self.error += 2 * self.dslow;

// Increment fast direction
self.quick += 1;

// Return if there is a stroke on the line
self.line
.style
.stroke_color
.map(|color| Pixel(coord.to_unsigned(), color))
None
}
}

Expand Down Expand Up @@ -235,7 +199,7 @@ where
Self {
start: self.start + by,
end: self.end + by,
..self.clone()
..*self
}
}

Expand Down Expand Up @@ -274,9 +238,16 @@ mod tests {

fn test_expected_line(start: Coord, end: Coord, expected: &[(u32, u32)]) {
let line = Line::new(start, end).with_style(Style::with_stroke(PixelColorU8(1)));
for (idx, Pixel(coord, _)) in line.into_iter().enumerate() {
assert_eq!(coord, UnsignedCoord::new(expected[idx].0, expected[idx].1));
let mut expected_iter = expected.iter();
for Pixel(coord, _) in line.into_iter() {
match expected_iter.next() {
Some(point) => assert_eq!(coord, UnsignedCoord::new(point.0, point.1)),
// expected runs out of points before line does
None => unreachable!(),
}
}
// check that expected has no points left
assert!(expected_iter.next().is_none())
}

#[test]
Expand All @@ -296,6 +267,22 @@ mod tests {
assert_eq!(backwards_line.size(), UnsignedCoord::new(10, 10));
}

#[test]
fn draws_no_dot() {
let start = Coord::new(10, 10);
let end = Coord::new(10, 10);
let expected = [];
test_expected_line(start, end, &expected);
}

#[test]
fn draws_short_correctly() {
let start = Coord::new(2, 3);
let end = Coord::new(3, 2);
let expected = [(2, 3), (3, 2)];
test_expected_line(start, end, &expected);
}

#[test]
fn draws_octant_1_correctly() {
let start = Coord::new(10, 10);
Expand Down Expand Up @@ -324,54 +311,47 @@ mod tests {
fn draws_octant_4_correctly() {
let start = Coord::new(10, 10);
let end = Coord::new(5, 13);
let expected = [(5, 13), (6, 12), (7, 12), (8, 11), (9, 11), (10, 10)];
let expected = [(10, 10), (9, 11), (8, 11), (7, 12), (6, 12), (5, 13)];
test_expected_line(start, end, &expected);
}

#[test]
fn draws_octant_5_correctly() {
let start = Coord::new(10, 10);
let end = Coord::new(5, 7);
let expected = [(5, 7), (6, 8), (7, 8), (8, 9), (9, 9), (10, 10)];
let expected = [(10, 10), (9, 9), (8, 9), (7, 8), (6, 8), (5, 7)];
test_expected_line(start, end, &expected);
}

#[test]
fn draws_octant_6_correctly() {
let start = Coord::new(10, 10);
let end = Coord::new(7, 5);
let expected = [(7, 5), (8, 6), (8, 7), (9, 8), (9, 9), (10, 10)];
let expected = [(10, 10), (9, 9), (9, 8), (8, 7), (8, 6), (7, 5)];
test_expected_line(start, end, &expected);
}

#[test]
fn draws_octant_7_correctly() {
let start = Coord::new(10, 10);
let end = Coord::new(13, 5);
let expected = [(13, 5), (12, 6), (12, 7), (11, 8), (11, 9), (10, 10)];
let expected = [(10, 10), (11, 9), (11, 8), (12, 7), (12, 6), (13, 5)];
test_expected_line(start, end, &expected);
}

#[test]
fn draws_octant_8_correctly() {
let start = Coord::new(10, 10);
let end = Coord::new(15, 7);
let expected = [
(10, 10).into(),
(11, 9).into(),
(12, 9).into(),
(13, 8).into(),
(14, 8).into(),
(15, 7).into(),
];
let expected = [(10, 10), (11, 9), (12, 9), (13, 8), (14, 8), (15, 7)];
test_expected_line(start, end, &expected);
}

#[test]
fn it_truncates_lines_out_of_bounds() {
let start = Coord::new(-2, -2);
let end = Coord::new(2, 2);
let expected = [(0, 0).into(), (1, 1).into(), (2, 2).into()];
let expected = [(0, 0), (1, 1), (2, 2)];
test_expected_line(start, end, &expected);
}
}
2 changes: 2 additions & 0 deletions embedded-graphics/src/primitives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ use crate::drawable::Dimensions;
pub mod circle;
pub mod line;
pub mod rect;
pub mod triangle;

/// Primitive trait
pub trait Primitive: Dimensions {}

pub use self::circle::Circle;
pub use self::line::Line;
pub use self::rect::Rect;
pub use self::triangle::Triangle;
Loading

0 comments on commit f2e105d

Please sign in to comment.