1use denise::{Point, Rect};
4
5use crate::blend::Paint;
6use crate::canvas::Canvas;
7
8const FRAC_BITS: u32 = 8;
10const ONE: i64 = 1 << FRAC_BITS;
11
12#[inline]
14fn coverage(frac: i64) -> u32 {
15 (frac as u32 * 255) >> FRAC_BITS
16}
17
18impl Canvas<'_> {
19 pub fn draw_line(&mut self, a: Point, b: Point, color: impl Into<Paint>) {
31 let paint = color.into();
32 if paint.is_invisible() {
33 return;
34 }
35
36 if a.y == b.y {
37 let x0 = a.x.min(b.x);
38 let x1 = a.x.max(b.x);
39 self.fill_rect(Rect::from_edges(x0, a.y, x1 + 1, a.y + 1), paint);
40 return;
41 }
42 if a.x == b.x {
43 let y0 = a.y.min(b.y);
44 let y1 = a.y.max(b.y);
45 self.fill_rect(Rect::from_edges(a.x, y0, a.x + 1, y1 + 1), paint);
46 return;
47 }
48
49 let dx = (b.x - a.x).abs();
50 let dy = (b.y - a.y).abs();
51
52 if dx >= dy {
53 let (p, q) = if a.x <= b.x { (a, b) } else { (b, a) };
55 let run = (q.x - p.x) as i64;
56 let rise = (q.y - p.y) as i64;
57 for x in p.x..=q.x {
58 let t = (x - p.x) as i64;
59 let y_fx = ((p.y as i64) << FRAC_BITS) + ((rise * t) << FRAC_BITS) / run;
60 let y = y_fx.div_euclid(ONE) as i32;
61 let frac = y_fx.rem_euclid(ONE);
62 self.blend_at(x, y, paint, 255 - coverage(frac));
63 self.blend_at(x, y + 1, paint, coverage(frac));
64 }
65 } else {
66 let (p, q) = if a.y <= b.y { (a, b) } else { (b, a) };
67 let run = (q.y - p.y) as i64;
68 let rise = (q.x - p.x) as i64;
69 for y in p.y..=q.y {
70 let t = (y - p.y) as i64;
71 let x_fx = ((p.x as i64) << FRAC_BITS) + ((rise * t) << FRAC_BITS) / run;
72 let x = x_fx.div_euclid(ONE) as i32;
73 let frac = x_fx.rem_euclid(ONE);
74 self.blend_at(x, y, paint, 255 - coverage(frac));
75 self.blend_at(x + 1, y, paint, coverage(frac));
76 }
77 }
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use crate::testing::TestCanvas;
85 use denise::Color;
86
87 fn alpha_of(px: u32) -> u32 {
88 px & 0xFF
89 }
90
91 #[test]
92 fn horizontal_line_is_crisp() {
93 let mut t = TestCanvas::new(16, 16);
94 t.canvas()
95 .draw_line(Point::new(2, 8), Point::new(12, 8), Color::WHITE);
96 for x in 2..=12 {
97 assert_eq!(alpha_of(t.at(x, 8)), 255, "at x={x}");
98 }
99 assert_eq!(t.at(1, 8), 0);
100 assert_eq!(t.at(13, 8), 0);
101 assert_eq!(t.at(8, 7), 0, "must not bleed to the row above");
102 assert_eq!(t.at(8, 9), 0, "must not bleed to the row below");
103 }
104
105 #[test]
106 fn vertical_line_is_crisp() {
107 let mut t = TestCanvas::new(16, 16);
108 t.canvas()
109 .draw_line(Point::new(8, 2), Point::new(8, 12), Color::WHITE);
110 for y in 2..=12 {
111 assert_eq!(alpha_of(t.at(8, y)), 255, "at y={y}");
112 }
113 assert_eq!(t.at(7, 8), 0);
114 assert_eq!(t.at(9, 8), 0);
115 }
116
117 #[test]
118 fn exact_diagonal_is_crisp() {
119 let mut t = TestCanvas::new(16, 16);
120 t.canvas()
121 .draw_line(Point::new(0, 0), Point::new(15, 15), Color::WHITE);
122 for i in 0..16 {
123 assert_eq!(alpha_of(t.at(i, i)), 255, "at {i},{i}");
124 }
125 }
126
127 #[test]
128 fn shallow_diagonal_is_antialiased() {
129 let mut t = TestCanvas::new(32, 16);
130 t.canvas()
131 .draw_line(Point::new(0, 2), Point::new(31, 9), Color::WHITE);
132 let partial = (0..32).any(|x| (0..16).any(|y| (1..255).contains(&alpha_of(t.at(x, y)))));
133 assert!(partial, "a 7-in-31 slope must produce partial coverage");
134 }
135
136 #[test]
137 fn every_column_of_a_shallow_line_is_covered() {
138 let mut t = TestCanvas::new(32, 16);
141 t.canvas()
142 .draw_line(Point::new(0, 2), Point::new(31, 9), Color::WHITE);
143 for x in 0..32 {
144 let total: u32 = (0..16).map(|y| alpha_of(t.at(x, y))).sum();
145 assert!((250..=255).contains(&total), "column {x} summed to {total}");
146 }
147 }
148
149 #[test]
150 fn direction_does_not_change_the_result() {
151 let a = Point::new(3, 1);
152 let b = Point::new(28, 13);
153
154 let mut forward = TestCanvas::new(32, 16);
155 forward.canvas().draw_line(a, b, Color::WHITE);
156
157 let mut backward = TestCanvas::new(32, 16);
158 backward.canvas().draw_line(b, a, Color::WHITE);
159
160 assert_eq!(forward.pixels(), backward.pixels());
161 }
162
163 #[test]
164 fn lines_are_clipped_not_wrapped() {
165 let mut t = TestCanvas::with_stride(16, 16, 24);
166 t.canvas()
167 .draw_line(Point::new(-40, -10), Point::new(60, 30), Color::WHITE);
168 for row in t.pixels().chunks(24) {
171 assert!(row[16..].iter().all(|&p| p == 0));
172 }
173 }
174
175 #[test]
176 fn single_point_line_does_not_panic() {
177 let mut t = TestCanvas::new(8, 8);
178 t.canvas()
179 .draw_line(Point::new(4, 4), Point::new(4, 4), Color::WHITE);
180 assert_eq!(alpha_of(t.at(4, 4)), 255);
181 }
182}