embedded_graphics/primitives/common/
scanline.rs1use crate::{
2 draw_target::DrawTarget,
3 geometry::{Point, Size},
4 primitives::{Line, PointsIter, Rectangle},
5};
6use core::ops::Range;
7
8#[derive(Debug, Clone, Eq, PartialEq, Hash)]
10#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
11pub struct Scanline {
12 pub y: i32,
13 pub x: Range<i32>,
14}
15
16impl Scanline {
17 pub const fn new(y: i32, x: Range<i32>) -> Self {
19 Self { y, x }
20 }
21
22 pub const fn new_empty(y: i32) -> Self {
24 Self::new(y, 0..0)
25 }
26
27 pub fn is_empty(&self) -> bool {
29 self.x.is_empty()
30 }
31
32 fn extend(&mut self, x: i32) {
34 if self.is_empty() {
35 self.x = x..x + 1;
36 } else if x < self.x.start {
37 self.x.start = x;
38 } else if x >= self.x.end {
39 self.x.end = x + 1;
40 }
41 }
42
43 pub fn bresenham_intersection(&mut self, line: &Line) {
48 let y_range = if line.start.y <= line.end.y {
50 line.start.y..=line.end.y
51 } else {
52 line.end.y..=line.start.y
53 };
54
55 if !y_range.contains(&self.y) {
56 return;
57 }
58
59 let y = self.y;
60
61 line.points()
62 .skip_while(|p| p.y != y)
63 .take_while(|p| p.y == y)
64 .for_each(|p| {
65 self.extend(p.x);
66 });
67 }
68
69 fn touches(&self, other: &Scanline) -> bool {
73 debug_assert_eq!(
74 self.y, other.y,
75 "try_extend must be called with scanlines with equal y coordinate"
76 );
77
78 if self.is_empty() || other.is_empty() {
79 return false;
80 }
81
82 let range = self.x.start - 1..=self.x.end;
83
84 range.contains(&(other.x.start)) || range.contains(&(other.x.end - 1)) || {
85 let range = other.x.start - 1..=other.x.end;
87
88 range.contains(&(self.x.start)) || range.contains(&(self.x.end - 1))
89 }
90 }
91
92 pub fn try_extend(&mut self, other: &Scanline) -> bool {
99 debug_assert_eq!(
100 self.y, other.y,
101 "try_extend must be called with scanlines with equal y coordinate"
102 );
103
104 if self.touches(other) {
105 self.x.start = self.x.start.min(other.x.start);
106 self.x.end = self.x.end.max(other.x.end);
107
108 true
109 } else {
110 false
111 }
112 }
113
114 pub fn to_rectangle(&self) -> Rectangle {
116 let width = if !self.is_empty() {
117 (self.x.end - self.x.start) as u32
118 } else {
119 0
120 };
121
122 Rectangle::new(Point::new(self.x.start, self.y), Size::new(width, 1))
123 }
124
125 pub fn try_take(&mut self) -> Option<Self> {
130 if !self.is_empty() {
131 let ret = self.clone();
132 self.x = 0..0;
133 Some(ret)
134 } else {
135 None
136 }
137 }
138
139 pub fn draw<T>(&self, target: &mut T, color: T::Color) -> Result<(), T::Error>
141 where
142 T: DrawTarget,
143 {
144 if self.is_empty() {
145 return Ok(());
146 }
147
148 let width = (self.x.end - self.x.start) as u32;
149
150 target.fill_solid(
151 &Rectangle::new(Point::new(self.x.start, self.y), Size::new(width, 1)),
152 color,
153 )
154 }
155}
156
157impl Iterator for Scanline {
158 type Item = Point;
159
160 fn next(&mut self) -> Option<Self::Item> {
161 self.x.next().map(|x| Point::new(x, self.y))
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 fn run_touches_test(s1: i32, e1: i32, s2: i32, e2: i32, expected: bool, ident: &str) {
170 let mut l1 = Scanline::new_empty(0);
171 l1.extend(s1);
172 l1.extend(e1);
173
174 let mut l2 = Scanline::new_empty(0);
175 l2.extend(s2);
176 l2.extend(e2);
177
178 assert_eq!(l1.touches(&l2), expected, "{}", ident);
179 }
180
181 #[test]
182 fn check_touches() {
183 run_touches_test(30, 40, 5, 15, false, "Reversed");
184 run_touches_test(0, 6, 5, 10, true, "Contained");
185 run_touches_test(11, 13, 11, 14, true, "Contained 2");
186 run_touches_test(10, 15, 25, 35, false, "Separated");
187 run_touches_test(10, 10, 10, 10, true, "Zero size");
188 run_touches_test(10, 20, 10, 20, true, "Equal");
189 run_touches_test(10, 20, 20, 10, true, "Equal reversed");
190 run_touches_test(79, 82, 82, 92, true, "Overlapping lines 1");
191 run_touches_test(82, 92, 79, 82, true, "Overlapping lines 1, reversed");
192 run_touches_test(80, 83, 83, 94, true, "Overlapping lines 2");
193 run_touches_test(83, 94, 80, 83, true, "Overlapping lines 2, reversed");
194 run_touches_test(83, 94, 94, 100, true, "Adjacent");
195 }
196
197 #[test]
198 fn issue_489_filled_triangle_bug() {
199 let mut l1 = Scanline { y: 5, x: 18..20 };
200 let l2 = Scanline { y: 5, x: 11..26 };
201
202 assert_eq!(l1.touches(&l2), true, "l1 touches l2");
203
204 let result = l1.try_extend(&l2);
205
206 assert_eq!(result, true);
207 assert_eq!(l1, Scanline { y: 5, x: 11..26 });
208 }
209}