imageproc/drawing/
bezier.rs

1use crate::definitions::Image;
2use crate::drawing::line::draw_line_segment_mut;
3use crate::drawing::Canvas;
4use image::{GenericImage, ImageBuffer};
5use std::f32;
6use std::i32;
7
8/// Draws a cubic Bézier curve on a new copy of an image.
9///
10/// Draws as much of the curve as lies within image bounds.
11#[must_use = "the function does not modify the original image"]
12pub fn draw_cubic_bezier_curve<I>(
13    image: &I,
14    start: (f32, f32),
15    end: (f32, f32),
16    control_a: (f32, f32),
17    control_b: (f32, f32),
18    color: I::Pixel,
19) -> Image<I::Pixel>
20where
21    I: GenericImage,
22{
23    let mut out = ImageBuffer::new(image.width(), image.height());
24    out.copy_from(image, 0, 0).unwrap();
25    draw_cubic_bezier_curve_mut(&mut out, start, end, control_a, control_b, color);
26    out
27}
28
29/// Draws a cubic Bézier curve on an image in place.
30///
31/// Draws as much of the curve as lies within image bounds.
32pub fn draw_cubic_bezier_curve_mut<C>(
33    canvas: &mut C,
34    start: (f32, f32),
35    end: (f32, f32),
36    control_a: (f32, f32),
37    control_b: (f32, f32),
38    color: C::Pixel,
39) where
40    C: Canvas,
41{
42    // Bezier Curve function from: https://pomax.github.io/bezierinfo/#control
43    let cubic_bezier_curve = |t: f32| {
44        let t2 = t * t;
45        let t3 = t2 * t;
46        let mt = 1.0 - t;
47        let mt2 = mt * mt;
48        let mt3 = mt2 * mt;
49        let x = (start.0 * mt3)
50            + (3.0 * control_a.0 * mt2 * t)
51            + (3.0 * control_b.0 * mt * t2)
52            + (end.0 * t3);
53        let y = (start.1 * mt3)
54            + (3.0 * control_a.1 * mt2 * t)
55            + (3.0 * control_b.1 * mt * t2)
56            + (end.1 * t3);
57        (x.round(), y.round()) // round to nearest pixel, to avoid ugly line artifacts
58    };
59
60    let distance = |point_a: (f32, f32), point_b: (f32, f32)| {
61        ((point_a.0 - point_b.0).powi(2) + (point_a.1 - point_b.1).powi(2)).sqrt()
62    };
63
64    // Approximate curve's length by adding distance between control points.
65    let curve_length_bound: f32 =
66        distance(start, control_a) + distance(control_a, control_b) + distance(control_b, end);
67
68    // Use hyperbola function to give shorter curves a bias in number of line segments.
69    let num_segments: i32 = ((curve_length_bound.powi(2) + 800.0).sqrt() / 8.0) as i32;
70
71    // Sample points along the curve and connect them with line segments.
72    let t_interval = 1f32 / (num_segments as f32);
73    let mut t1 = 0f32;
74    for i in 0..num_segments {
75        let t2 = (i as f32 + 1.0) * t_interval;
76        draw_line_segment_mut(
77            canvas,
78            cubic_bezier_curve(t1),
79            cubic_bezier_curve(t2),
80            color,
81        );
82        t1 = t2;
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use image::{GrayImage, Luma};
89
90    macro_rules! bench_cubic_bezier_curve {
91        ($name:ident, $start:expr, $end:expr, $control_a:expr, $control_b:expr) => {
92            #[bench]
93            fn $name(b: &mut test::Bencher) {
94                use super::draw_cubic_bezier_curve_mut;
95
96                let mut image = GrayImage::new(500, 500);
97                let color = Luma([50u8]);
98                b.iter(|| {
99                    draw_cubic_bezier_curve_mut(
100                        &mut image, $start, $end, $control_a, $control_b, color,
101                    );
102                    test::black_box(&image);
103                });
104            }
105        };
106    }
107
108    bench_cubic_bezier_curve!(
109        bench_draw_cubic_bezier_curve_short,
110        (100.0, 100.0),
111        (130.0, 130.0),
112        (110.0, 100.0),
113        (120.0, 130.0)
114    );
115
116    bench_cubic_bezier_curve!(
117        bench_draw_cubic_bezier_curve_long,
118        (100.0, 100.0),
119        (400.0, 400.0),
120        (500.0, 0.0),
121        (0.0, 500.0)
122    );
123}