dcc_lsystem/image.rs
1use std::f32::consts::FRAC_PI_2;
2
3use image::{ImageBuffer, Rgb};
4use imageproc::drawing::{draw_filled_circle_mut, draw_polygon_mut};
5use imageproc::point::Point;
6
7/// Modified every pixel of `buffer` to be the provided color.
8///
9/// # Example
10/// ```rust,no_run
11/// use image::{ImageBuffer, Rgb};
12/// use dcc_lsystem::image::fill_mut;
13///
14/// let mut buffer : ImageBuffer<Rgb<u8>, Vec<u8>> = unimplemented!();
15///
16/// // Make our image entirely black.
17/// fill_mut(&mut buffer, Rgb([0u8,0u8,0u8]));
18/// ```
19pub fn fill_mut(buffer: &mut ImageBuffer<Rgb<u8>, Vec<u8>>, color: Rgb<u8>) {
20 for pixel in buffer.pixels_mut() {
21 *pixel = color;
22 }
23}
24
25/// Draws a line to `buffer` between `(x1,y1)` and `(x2,y2)`.
26pub fn draw_line_mut(
27 buffer: &mut ImageBuffer<Rgb<u8>, Vec<u8>>,
28 x1: u32,
29 y1: u32,
30 x2: u32,
31 y2: u32,
32 thickness: f32,
33 color: Rgb<u8>,
34) {
35 assert!(thickness > 0.0);
36
37 // A thick line is really just a rectangle. It seems that
38 // imageproc::drawing::draw_filled_rect() only allows drawing axis-aligned
39 // rectangles, so we'd need to think a little harder to use that method here.
40 //
41 // Instead, we compute the four vertices of our rectangle and use the
42 // the draw_polygon_mut function from imageproc. This works fairly well,
43 // though there are some noticeable issues where two lines connect. As a workaround,
44 // we also draw a circle at the start and endpoint of our line - this covers up most
45 // of the badness, and gives a reasonable looking end result.
46
47 // Compute the angle between the first and second points
48 let angle = {
49 if x1 == x2 {
50 FRAC_PI_2
51 } else {
52 ((i64::from(y2) - i64::from(y1)) as f64 / (i64::from(x2) - i64::from(x1)) as f64).atan()
53 as f32
54 }
55 };
56
57 // Compute the angle between the line perpendicular to P1 -> P2
58 // and the x-axis.
59 let perpendicular_angle = angle + FRAC_PI_2;
60
61 // We get the vertices of our rectangle by extending out in the perpendicular
62 // direction from our starting point.
63 let p1 = Point::new(
64 (x1 as f32 + thickness * perpendicular_angle.cos()) as i32,
65 (y1 as f32 + thickness * perpendicular_angle.sin()) as i32,
66 );
67 let p2 = Point::new(
68 (x1 as f32 - thickness * perpendicular_angle.cos()) as i32,
69 (y1 as f32 - thickness * perpendicular_angle.sin()) as i32,
70 );
71 let p3 = Point::new(
72 (x2 as f32 + thickness * perpendicular_angle.cos()) as i32,
73 (y2 as f32 + thickness * perpendicular_angle.sin()) as i32,
74 );
75 let p4 = Point::new(
76 (x2 as f32 - thickness * perpendicular_angle.cos()) as i32,
77 (y2 as f32 - thickness * perpendicular_angle.sin()) as i32,
78 );
79
80 // Now we just draw the line
81 draw_polygon_mut(buffer, &[p1, p3, p4, p2], color);
82 draw_filled_circle_mut(
83 buffer,
84 (x1 as i32, y1 as i32),
85 (thickness / 1.5) as i32,
86 color,
87 );
88 draw_filled_circle_mut(
89 buffer,
90 (x2 as i32, y2 as i32),
91 (thickness / 1.5) as i32,
92 color,
93 );
94}