imageproc/drawing/
conics.rs

1use crate::definitions::Image;
2use crate::drawing::draw_if_in_bounds;
3use crate::drawing::line::draw_line_segment_mut;
4use crate::drawing::Canvas;
5use image::{GenericImage, ImageBuffer};
6use std::f32;
7use std::i32;
8
9/// Draws the outline of an ellipse on a new copy of an image.
10///
11/// Draws as much of an ellipse as lies inside the image bounds.
12///
13/// Uses the [Midpoint Ellipse Drawing Algorithm](https://web.archive.org/web/20160128020853/http://tutsheap.com/c/mid-point-ellipse-drawing-algorithm/).
14/// (Modified from Bresenham's algorithm)
15///
16/// The ellipse is axis-aligned and satisfies the following equation:
17///
18/// (`x^2 / width_radius^2) + (y^2 / height_radius^2) = 1`
19#[must_use = "the function does not modify the original image"]
20pub fn draw_hollow_ellipse<I>(
21    image: &I,
22    center: (i32, i32),
23    width_radius: i32,
24    height_radius: i32,
25    color: I::Pixel,
26) -> Image<I::Pixel>
27where
28    I: GenericImage,
29{
30    let mut out = ImageBuffer::new(image.width(), image.height());
31    out.copy_from(image, 0, 0).unwrap();
32    draw_hollow_ellipse_mut(&mut out, center, width_radius, height_radius, color);
33    out
34}
35
36/// Draws the outline of an ellipse on an image in place.
37///
38/// Draws as much of an ellipse as lies inside the image bounds.
39///
40/// Uses the [Midpoint Ellipse Drawing Algorithm](https://web.archive.org/web/20160128020853/http://tutsheap.com/c/mid-point-ellipse-drawing-algorithm/).
41/// (Modified from Bresenham's algorithm)
42///
43/// The ellipse is axis-aligned and satisfies the following equation:
44///
45/// `(x^2 / width_radius^2) + (y^2 / height_radius^2) = 1`
46pub fn draw_hollow_ellipse_mut<C>(
47    canvas: &mut C,
48    center: (i32, i32),
49    width_radius: i32,
50    height_radius: i32,
51    color: C::Pixel,
52) where
53    C: Canvas,
54{
55    // Circle drawing algorithm is faster, so use it if the given ellipse is actually a circle.
56    if width_radius == height_radius {
57        draw_hollow_circle_mut(canvas, center, width_radius, color);
58        return;
59    }
60
61    let draw_quad_pixels = |x0: i32, y0: i32, x: i32, y: i32| {
62        draw_if_in_bounds(canvas, x0 + x, y0 + y, color);
63        draw_if_in_bounds(canvas, x0 - x, y0 + y, color);
64        draw_if_in_bounds(canvas, x0 + x, y0 - y, color);
65        draw_if_in_bounds(canvas, x0 - x, y0 - y, color);
66    };
67
68    draw_ellipse(draw_quad_pixels, center, width_radius, height_radius);
69}
70
71/// Draws an ellipse and its contents on a new copy of the image.
72///
73/// Draw as much of the ellipse and its contents as lies inside the image bounds.
74///
75/// Uses the [Midpoint Ellipse Drawing Algorithm](https://web.archive.org/web/20160128020853/http://tutsheap.com/c/mid-point-ellipse-drawing-algorithm/).
76/// (Modified from Bresenham's algorithm)
77///
78/// The ellipse is axis-aligned and satisfies the following equation:
79///
80/// `(x^2 / width_radius^2) + (y^2 / height_radius^2) <= 1`
81#[must_use = "the function does not modify the original image"]
82pub fn draw_filled_ellipse<I>(
83    image: &I,
84    center: (i32, i32),
85    width_radius: i32,
86    height_radius: i32,
87    color: I::Pixel,
88) -> Image<I::Pixel>
89where
90    I: GenericImage,
91{
92    let mut out = ImageBuffer::new(image.width(), image.height());
93    out.copy_from(image, 0, 0).unwrap();
94    draw_filled_ellipse_mut(&mut out, center, width_radius, height_radius, color);
95    out
96}
97
98/// Draws an ellipse and its contents on an image in place.
99///
100/// Draw as much of the ellipse and its contents as lies inside the image bounds.
101///
102/// Uses the [Midpoint Ellipse Drawing Algorithm](https://web.archive.org/web/20160128020853/http://tutsheap.com/c/mid-point-ellipse-drawing-algorithm/).
103/// (Modified from Bresenham's algorithm)
104///
105/// The ellipse is axis-aligned and satisfies the following equation:
106///
107/// `(x^2 / width_radius^2) + (y^2 / height_radius^2) <= 1`
108pub fn draw_filled_ellipse_mut<C>(
109    canvas: &mut C,
110    center: (i32, i32),
111    width_radius: i32,
112    height_radius: i32,
113    color: C::Pixel,
114) where
115    C: Canvas,
116{
117    // Circle drawing algorithm is faster, so use it if the given ellipse is actually a circle.
118    if width_radius == height_radius {
119        draw_filled_circle_mut(canvas, center, width_radius, color);
120        return;
121    }
122
123    let draw_line_pairs = |x0: i32, y0: i32, x: i32, y: i32| {
124        draw_line_segment_mut(
125            canvas,
126            ((x0 - x) as f32, (y0 + y) as f32),
127            ((x0 + x) as f32, (y0 + y) as f32),
128            color,
129        );
130        draw_line_segment_mut(
131            canvas,
132            ((x0 - x) as f32, (y0 - y) as f32),
133            ((x0 + x) as f32, (y0 - y) as f32),
134            color,
135        );
136    };
137
138    draw_ellipse(draw_line_pairs, center, width_radius, height_radius);
139}
140
141// Implements the Midpoint Ellipse Drawing Algorithm https://web.archive.org/web/20160128020853/http://tutsheap.com/c/mid-point-ellipse-drawing-algorithm/). (Modified from Bresenham's algorithm)
142//
143// Takes a function that determines how to render the points on the ellipse.
144fn draw_ellipse<F>(mut render_func: F, center: (i32, i32), width_radius: i32, height_radius: i32)
145where
146    F: FnMut(i32, i32, i32, i32),
147{
148    let (x0, y0) = center;
149    let w2 = width_radius * width_radius;
150    let h2 = height_radius * height_radius;
151    let mut x = 0;
152    let mut y = height_radius;
153    let mut px = 0;
154    let mut py = 2 * w2 * y;
155
156    render_func(x0, y0, x, y);
157
158    // Top and bottom regions.
159    let mut p = (h2 - (w2 * height_radius)) as f32 + (0.25 * w2 as f32);
160    while px < py {
161        x += 1;
162        px += 2 * h2;
163        if p < 0.0 {
164            p += (h2 + px) as f32;
165        } else {
166            y -= 1;
167            py += -2 * w2;
168            p += (h2 + px - py) as f32;
169        }
170
171        render_func(x0, y0, x, y);
172    }
173
174    // Left and right regions.
175    p = (h2 as f32) * (x as f32 + 0.5).powi(2) + (w2 * (y - 1).pow(2)) as f32 - (w2 * h2) as f32;
176    while y > 0 {
177        y -= 1;
178        py += -2 * w2;
179        if p > 0.0 {
180            p += (w2 - py) as f32;
181        } else {
182            x += 1;
183            px += 2 * h2;
184            p += (w2 - py + px) as f32;
185        }
186
187        render_func(x0, y0, x, y);
188    }
189}
190
191/// Draws the outline of a circle on a new copy of an image.
192///
193/// Draw as much of the circle as lies inside the image bounds.
194#[must_use = "the function does not modify the original image"]
195pub fn draw_hollow_circle<I>(
196    image: &I,
197    center: (i32, i32),
198    radius: i32,
199    color: I::Pixel,
200) -> Image<I::Pixel>
201where
202    I: GenericImage,
203{
204    let mut out = ImageBuffer::new(image.width(), image.height());
205    out.copy_from(image, 0, 0).unwrap();
206    draw_hollow_circle_mut(&mut out, center, radius, color);
207    out
208}
209
210/// Draws the outline of a circle on an image in place.
211///
212/// Draw as much of the circle as lies inside the image bounds.
213pub fn draw_hollow_circle_mut<C>(canvas: &mut C, center: (i32, i32), radius: i32, color: C::Pixel)
214where
215    C: Canvas,
216{
217    let mut x = 0i32;
218    let mut y = radius;
219    let mut p = 1 - radius;
220    let x0 = center.0;
221    let y0 = center.1;
222
223    while x <= y {
224        draw_if_in_bounds(canvas, x0 + x, y0 + y, color);
225        draw_if_in_bounds(canvas, x0 + y, y0 + x, color);
226        draw_if_in_bounds(canvas, x0 - y, y0 + x, color);
227        draw_if_in_bounds(canvas, x0 - x, y0 + y, color);
228        draw_if_in_bounds(canvas, x0 - x, y0 - y, color);
229        draw_if_in_bounds(canvas, x0 - y, y0 - x, color);
230        draw_if_in_bounds(canvas, x0 + y, y0 - x, color);
231        draw_if_in_bounds(canvas, x0 + x, y0 - y, color);
232
233        x += 1;
234        if p < 0 {
235            p += 2 * x + 1;
236        } else {
237            y -= 1;
238            p += 2 * (x - y) + 1;
239        }
240    }
241}
242
243/// Draws a circle and its contents on an image in place.
244///
245/// Draws as much of a circle and its contents as lies inside the image bounds.
246pub fn draw_filled_circle_mut<C>(canvas: &mut C, center: (i32, i32), radius: i32, color: C::Pixel)
247where
248    C: Canvas,
249{
250    let mut x = 0i32;
251    let mut y = radius;
252    let mut p = 1 - radius;
253    let x0 = center.0;
254    let y0 = center.1;
255
256    while x <= y {
257        draw_line_segment_mut(
258            canvas,
259            ((x0 - x) as f32, (y0 + y) as f32),
260            ((x0 + x) as f32, (y0 + y) as f32),
261            color,
262        );
263        draw_line_segment_mut(
264            canvas,
265            ((x0 - y) as f32, (y0 + x) as f32),
266            ((x0 + y) as f32, (y0 + x) as f32),
267            color,
268        );
269        draw_line_segment_mut(
270            canvas,
271            ((x0 - x) as f32, (y0 - y) as f32),
272            ((x0 + x) as f32, (y0 - y) as f32),
273            color,
274        );
275        draw_line_segment_mut(
276            canvas,
277            ((x0 - y) as f32, (y0 - x) as f32),
278            ((x0 + y) as f32, (y0 - x) as f32),
279            color,
280        );
281
282        x += 1;
283        if p < 0 {
284            p += 2 * x + 1;
285        } else {
286            y -= 1;
287            p += 2 * (x - y) + 1;
288        }
289    }
290}
291
292/// Draws a circle and its contents on a new copy of the image.
293///
294/// Draws as much of a circle and its contents as lies inside the image bounds.
295#[must_use = "the function does not modify the original image"]
296pub fn draw_filled_circle<I>(
297    image: &I,
298    center: (i32, i32),
299    radius: i32,
300    color: I::Pixel,
301) -> Image<I::Pixel>
302where
303    I: GenericImage,
304{
305    let mut out = ImageBuffer::new(image.width(), image.height());
306    out.copy_from(image, 0, 0).unwrap();
307    draw_filled_circle_mut(&mut out, center, radius, color);
308    out
309}
310
311#[cfg(test)]
312mod tests {
313    use image::{GrayImage, Luma};
314
315    macro_rules! bench_hollow_ellipse {
316        ($name:ident, $center:expr, $width_radius:expr, $height_radius:expr) => {
317            #[bench]
318            fn $name(b: &mut test::Bencher) {
319                use super::draw_hollow_ellipse_mut;
320
321                let mut image = GrayImage::new(500, 500);
322                let color = Luma([50u8]);
323                b.iter(|| {
324                    draw_hollow_ellipse_mut(
325                        &mut image,
326                        $center,
327                        $width_radius,
328                        $height_radius,
329                        color,
330                    );
331                    test::black_box(&image);
332                });
333            }
334        };
335    }
336
337    bench_hollow_ellipse!(bench_bench_hollow_ellipse_circle, (200, 200), 80, 80);
338    bench_hollow_ellipse!(bench_bench_hollow_ellipse_vertical, (200, 200), 40, 100);
339    bench_hollow_ellipse!(bench_bench_hollow_ellipse_horizontal, (200, 200), 100, 40);
340
341    macro_rules! bench_filled_ellipse {
342        ($name:ident, $center:expr, $width_radius:expr, $height_radius:expr) => {
343            #[bench]
344            fn $name(b: &mut test::Bencher) {
345                use super::draw_filled_ellipse_mut;
346
347                let mut image = GrayImage::new(500, 500);
348                let color = Luma([50u8]);
349                b.iter(|| {
350                    draw_filled_ellipse_mut(
351                        &mut image,
352                        $center,
353                        $width_radius,
354                        $height_radius,
355                        color,
356                    );
357                    test::black_box(&image);
358                });
359            }
360        };
361    }
362
363    bench_filled_ellipse!(bench_bench_filled_ellipse_circle, (200, 200), 80, 80);
364    bench_filled_ellipse!(bench_bench_filled_ellipse_vertical, (200, 200), 40, 100);
365    bench_filled_ellipse!(bench_bench_filled_ellipse_horizontal, (200, 200), 100, 40);
366}