andrew/
line.rs

1use std::cmp::{max, min};
2
3use Canvas;
4use Drawable;
5use Endian;
6
7/// A drawable object that represents a line
8pub struct Line {
9    /// The first point of the line
10    pub pt1: (usize, usize),
11    /// The second point of the line
12    pub pt2: (usize, usize),
13    /// The color of the line
14    pub color: [u8; 4],
15    /// Decides whether the line will be antialiased
16    pub antialiased: bool,
17}
18
19impl Line {
20    /// Creates a new Line object
21    pub fn new(
22        pt1: (usize, usize),
23        pt2: (usize, usize),
24        color: [u8; 4],
25        antialiased: bool,
26    ) -> Line {
27        Line {
28            pt1,
29            pt2,
30            color,
31            antialiased,
32        }
33    }
34}
35
36impl Drawable for Line {
37    fn draw(&self, canvas: &mut Canvas) {
38        if !self.antialiased {
39            if self.pt1.0 == self.pt2.0 && self.pt1.0 < canvas.width {
40                let min_y = min(self.pt1.1, self.pt2.1);
41                let max_y = min(max(self.pt1.1, self.pt2.1), canvas.height - 1);
42                for y in min_y..=max_y {
43                    canvas.draw_point(self.pt1.0, y, self.color)
44                }
45            } else if self.pt1.1 == self.pt2.1 && self.pt1.1 < canvas.height {
46                let min_x = min(self.pt1.0, self.pt2.0);
47                let max_x = min(max(self.pt1.0, self.pt2.0), canvas.width - 1);
48                for x in min_x..=max_x {
49                    canvas.draw_point(x, self.pt1.1, self.color)
50                }
51            } else {
52                // Angled line without antialias
53                for (x, y) in bresenham(
54                    self.pt1.0 as isize,
55                    self.pt1.1 as isize,
56                    self.pt2.0 as isize,
57                    self.pt2.1 as isize,
58                ) {
59                    if x < canvas.width && y < canvas.height {
60                        canvas.draw_point(x, y, self.color)
61                    }
62                }
63            }
64        } else {
65            // Angled line with antialias
66            for (x, y, coverage) in xiaolin_wu(
67                self.pt1.0 as f32,
68                self.pt1.1 as f32,
69                self.pt2.0 as f32,
70                self.pt2.1 as f32,
71            ) {
72                if x < canvas.width && y < canvas.height {
73                    let mut color = self.color;
74                    let base = canvas.stride * y + canvas.pixel_size * x;
75                    if coverage != 1.0 {
76                        if canvas.endianness == Endian::Little {
77                            color[1] = (canvas.buffer[base + 2] as f32 * (1.0 - coverage)
78                                + color[1] as f32 * coverage)
79                                as u8;
80                            color[2] = (canvas.buffer[base + 1] as f32 * (1.0 - coverage)
81                                + color[2] as f32 * coverage)
82                                as u8;
83                            color[3] = (canvas.buffer[base] as f32 * (1.0 - coverage)
84                                + color[3] as f32 * coverage)
85                                as u8;
86                        } else {
87                            color[1] = (canvas.buffer[base + 1] as f32 * (1.0 - coverage)
88                                + color[1] as f32 * coverage)
89                                as u8;
90                            color[2] = (canvas.buffer[base + 2] as f32 * (1.0 - coverage)
91                                + color[2] as f32 * coverage)
92                                as u8;
93                            color[3] = (canvas.buffer[base + 3] as f32 * (1.0 - coverage)
94                                + color[3] as f32 * coverage)
95                                as u8;
96                        }
97                    }
98                    canvas.draw_point(x as usize, y as usize, color)
99                }
100            }
101        }
102    }
103}
104
105fn bresenham(mut x0: isize, mut y0: isize, x1: isize, y1: isize) -> Vec<(usize, usize)> {
106    let mut points: Vec<(usize, usize)> = Vec::new();
107    let dx = (x1 - x0).abs();
108    let sx = if x0 < x1 { 1 } else { -1 };
109    let dy = -((y1 - y0).abs());
110    let sy = if y0 < y1 { 1 } else { -1 };
111    let mut err = dx + dy;
112
113    loop {
114        points.push((x0 as usize, y0 as usize));
115        if x0 == x1 && y0 == y1 {
116            break;
117        };
118        let e2 = 2 * err;
119        if e2 >= dy {
120            err += dy;
121            x0 += sx;
122        }
123        if e2 <= dx {
124            err += dx;
125            y0 += sy;
126        }
127    }
128    points
129}
130
131fn xiaolin_wu(mut x0: f32, mut y0: f32, mut x1: f32, mut y1: f32) -> Vec<(usize, usize, f32)> {
132    let mut points: Vec<(usize, usize, f32)> = Vec::new();
133    let steep = (y1 - y0).abs() > (x1 - x0).abs();
134    if steep {
135        std::mem::swap(&mut x0, &mut y0);
136        std::mem::swap(&mut x1, &mut y1);
137    }
138    if x0 > x1 {
139        std::mem::swap(&mut x0, &mut x1);
140        std::mem::swap(&mut y0, &mut y1);
141    }
142    let dx = x1 - x0;
143    let dy = y1 - y0;
144    let gradient = if dx == 0.0 {
145        1.0
146    } else {
147        dy as f32 / dx as f32
148    };
149
150    let mut intery = y0 + gradient;
151    points.push((x0 as usize, y0 as usize, 1.0));
152    points.push((x1 as usize, y1 as usize, 1.0));
153    if steep {
154        for x in x0 as usize + 1..=x1 as usize - 1 {
155            points.push((intery as usize, x, 1.0 - intery.fract()));
156            points.push((intery as usize + 1, x, intery.fract()));
157            intery = intery + gradient;
158        }
159    } else {
160        for x in x0 as usize + 1..=x1 as usize - 1 {
161            points.push((x, intery as usize, 1.0 - intery.fract()));
162            points.push((x, intery as usize + 1, intery.fract()));
163            intery = intery + gradient;
164        }
165    }
166    points
167}