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};
6
7#[must_use = "the function does not modify the original image"]
18pub fn draw_hollow_ellipse<I>(
19    image: &I,
20    center: (i32, i32),
21    width_radius: i32,
22    height_radius: i32,
23    color: I::Pixel,
24) -> Image<I::Pixel>
25where
26    I: GenericImage,
27{
28    let mut out = ImageBuffer::new(image.width(), image.height());
29    out.copy_from(image, 0, 0).unwrap();
30    draw_hollow_ellipse_mut(&mut out, center, width_radius, height_radius, color);
31    out
32}
33
34pub fn draw_hollow_ellipse_mut<C>(
45    canvas: &mut C,
46    center: (i32, i32),
47    width_radius: i32,
48    height_radius: i32,
49    color: C::Pixel,
50) where
51    C: Canvas,
52{
53    if width_radius == height_radius {
55        draw_hollow_circle_mut(canvas, center, width_radius, color);
56        return;
57    }
58
59    let draw_quad_pixels = |x0: i32, y0: i32, x: i32, y: i32| {
60        draw_if_in_bounds(canvas, x0 + x, y0 + y, color);
61        draw_if_in_bounds(canvas, x0 - x, y0 + y, color);
62        draw_if_in_bounds(canvas, x0 + x, y0 - y, color);
63        draw_if_in_bounds(canvas, x0 - x, y0 - y, color);
64    };
65
66    draw_ellipse(draw_quad_pixels, center, width_radius, height_radius);
67}
68
69#[must_use = "the function does not modify the original image"]
80pub fn draw_filled_ellipse<I>(
81    image: &I,
82    center: (i32, i32),
83    width_radius: i32,
84    height_radius: i32,
85    color: I::Pixel,
86) -> Image<I::Pixel>
87where
88    I: GenericImage,
89{
90    let mut out = ImageBuffer::new(image.width(), image.height());
91    out.copy_from(image, 0, 0).unwrap();
92    draw_filled_ellipse_mut(&mut out, center, width_radius, height_radius, color);
93    out
94}
95
96pub fn draw_filled_ellipse_mut<C>(
107    canvas: &mut C,
108    center: (i32, i32),
109    width_radius: i32,
110    height_radius: i32,
111    color: C::Pixel,
112) where
113    C: Canvas,
114{
115    if width_radius == height_radius {
117        draw_filled_circle_mut(canvas, center, width_radius, color);
118        return;
119    }
120
121    let draw_line_pairs = |x0: i32, y0: i32, x: i32, y: i32| {
122        draw_line_segment_mut(
123            canvas,
124            ((x0 - x) as f32, (y0 + y) as f32),
125            ((x0 + x) as f32, (y0 + y) as f32),
126            color,
127        );
128        draw_line_segment_mut(
129            canvas,
130            ((x0 - x) as f32, (y0 - y) as f32),
131            ((x0 + x) as f32, (y0 - y) as f32),
132            color,
133        );
134    };
135
136    draw_ellipse(draw_line_pairs, center, width_radius, height_radius);
137}
138
139fn draw_ellipse<F>(mut render_func: F, center: (i32, i32), width_radius: i32, height_radius: i32)
143where
144    F: FnMut(i32, i32, i32, i32),
145{
146    let (x0, y0) = center;
147    let w2 = (width_radius * width_radius) as f32;
148    let h2 = (height_radius * height_radius) as f32;
149    let mut x = 0;
150    let mut y = height_radius;
151    let mut px = 0.0;
152    let mut py = 2.0 * w2 * y as f32;
153
154    render_func(x0, y0, x, y);
155
156    let mut p = h2 - (w2 * height_radius as f32) + (0.25 * w2);
158    while px < py {
159        x += 1;
160        px += 2.0 * h2;
161        if p < 0.0 {
162            p += h2 + px;
163        } else {
164            y -= 1;
165            py += -2.0 * w2;
166            p += h2 + px - py;
167        }
168
169        render_func(x0, y0, x, y);
170    }
171
172    p = h2 * (x as f32 + 0.5).powi(2) + (w2 * (y - 1).pow(2) as f32) - w2 * h2;
174    while y > 0 {
175        y -= 1;
176        py += -2.0 * w2;
177        if p > 0.0 {
178            p += w2 - py;
179        } else {
180            x += 1;
181            px += 2.0 * h2;
182            p += w2 - py + px;
183        }
184
185        render_func(x0, y0, x, y);
186    }
187}
188
189#[must_use = "the function does not modify the original image"]
193pub fn draw_hollow_circle<I>(
194    image: &I,
195    center: (i32, i32),
196    radius: i32,
197    color: I::Pixel,
198) -> Image<I::Pixel>
199where
200    I: GenericImage,
201{
202    let mut out = ImageBuffer::new(image.width(), image.height());
203    out.copy_from(image, 0, 0).unwrap();
204    draw_hollow_circle_mut(&mut out, center, radius, color);
205    out
206}
207
208pub fn draw_hollow_circle_mut<C>(canvas: &mut C, center: (i32, i32), radius: i32, color: C::Pixel)
212where
213    C: Canvas,
214{
215    let mut x = 0i32;
216    let mut y = radius;
217    let mut p = 1 - radius;
218    let x0 = center.0;
219    let y0 = center.1;
220
221    while x <= y {
222        draw_if_in_bounds(canvas, x0 + x, y0 + y, color);
223        draw_if_in_bounds(canvas, x0 + y, y0 + x, color);
224        draw_if_in_bounds(canvas, x0 - y, y0 + x, color);
225        draw_if_in_bounds(canvas, x0 - x, y0 + y, color);
226        draw_if_in_bounds(canvas, x0 - x, y0 - y, color);
227        draw_if_in_bounds(canvas, x0 - y, y0 - x, color);
228        draw_if_in_bounds(canvas, x0 + y, y0 - x, color);
229        draw_if_in_bounds(canvas, x0 + x, y0 - y, color);
230
231        x += 1;
232        if p < 0 {
233            p += 2 * x + 1;
234        } else {
235            y -= 1;
236            p += 2 * (x - y) + 1;
237        }
238    }
239}
240
241pub fn draw_filled_circle_mut<C>(canvas: &mut C, center: (i32, i32), radius: i32, color: C::Pixel)
245where
246    C: Canvas,
247{
248    let mut x = 0i32;
249    let mut y = radius;
250    let mut p = 1 - radius;
251    let x0 = center.0;
252    let y0 = center.1;
253
254    while x <= y {
255        draw_line_segment_mut(
256            canvas,
257            ((x0 - x) as f32, (y0 + y) as f32),
258            ((x0 + x) as f32, (y0 + y) as f32),
259            color,
260        );
261        draw_line_segment_mut(
262            canvas,
263            ((x0 - y) as f32, (y0 + x) as f32),
264            ((x0 + y) as f32, (y0 + x) as f32),
265            color,
266        );
267        draw_line_segment_mut(
268            canvas,
269            ((x0 - x) as f32, (y0 - y) as f32),
270            ((x0 + x) as f32, (y0 - y) as f32),
271            color,
272        );
273        draw_line_segment_mut(
274            canvas,
275            ((x0 - y) as f32, (y0 - x) as f32),
276            ((x0 + y) as f32, (y0 - x) as f32),
277            color,
278        );
279
280        x += 1;
281        if p < 0 {
282            p += 2 * x + 1;
283        } else {
284            y -= 1;
285            p += 2 * (x - y) + 1;
286        }
287    }
288}
289
290#[must_use = "the function does not modify the original image"]
294pub fn draw_filled_circle<I>(
295    image: &I,
296    center: (i32, i32),
297    radius: i32,
298    color: I::Pixel,
299) -> Image<I::Pixel>
300where
301    I: GenericImage,
302{
303    let mut out = ImageBuffer::new(image.width(), image.height());
304    out.copy_from(image, 0, 0).unwrap();
305    draw_filled_circle_mut(&mut out, center, radius, color);
306    out
307}
308
309#[cfg(test)]
310mod tests {
311    use super::draw_filled_ellipse_mut;
312    use image::GenericImage;
313
314    struct Ellipse {
315        center: (i32, i32),
316        width_radius: i32,
317        height_radius: i32,
318    }
319
320    impl Ellipse {
321        fn normalized_distance_from_center(&self, (x, y): (i32, i32)) -> f32 {
322            let (cx, cy) = self.center;
323            let (w, h) = (self.width_radius as f32, self.height_radius as f32);
324            ((cx - x) as f32 / w).powi(2) + ((cy - y) as f32 / h).powi(2)
325        }
326        fn is_boundary_point(&self, (x, y): (i32, i32), boundary_eps: f32) -> bool {
327            assert!(boundary_eps >= 0.0);
328            (self.normalized_distance_from_center((x, y)) - 1.0).abs() < boundary_eps
329        }
330        fn is_inner_point(&self, (x, y): (i32, i32)) -> bool {
331            self.normalized_distance_from_center((x, y)) < 1.0
332        }
333    }
334
335    fn check_filled_ellipse<I: GenericImage>(
336        img: &I,
337        ellipse: Ellipse,
338        inner_color: I::Pixel,
339        outer_color: I::Pixel,
340        boundary_eps: f32,
341    ) where
342        I::Pixel: core::fmt::Debug + PartialEq,
343    {
344        for x in 0..img.width() as i32 {
345            for y in 0..img.height() as i32 {
346                if ellipse.is_boundary_point((x, y), boundary_eps) {
347                    continue;
348                }
349                let pixel = img.get_pixel(x as u32, y as u32);
350                if ellipse.is_inner_point((x, y)) {
351                    assert_eq!(pixel, inner_color);
352                } else {
353                    assert_eq!(pixel, outer_color);
354                }
355            }
356        }
357    }
358
359    #[cfg_attr(miri, ignore = "slow [>1480s]")]
360    #[test]
361    fn test_draw_filled_ellipse() {
362        let ellipse = Ellipse {
363            center: (960, 540),
364            width_radius: 960,
365            height_radius: 540,
366        };
367        let inner_color = image::Rgb([255, 0, 0]);
368        let outer_color = image::Rgb([0, 0, 0]);
369        let mut img = image::RgbImage::new(1920, 1080);
370        draw_filled_ellipse_mut(
371            &mut img,
372            ellipse.center,
373            ellipse.width_radius,
374            ellipse.height_radius,
375            inner_color,
376        );
377        const EPS: f32 = 0.0019;
378        check_filled_ellipse(&img, ellipse, inner_color, outer_color, EPS);
379    }
380}
381
382#[cfg(not(miri))]
383#[cfg(test)]
384mod benches {
385    use image::{GrayImage, Luma};
386
387    macro_rules! bench_hollow_ellipse {
388        ($name:ident, $center:expr, $width_radius:expr, $height_radius:expr) => {
389            #[bench]
390            fn $name(b: &mut test::Bencher) {
391                use super::draw_hollow_ellipse_mut;
392
393                let mut image = GrayImage::new(500, 500);
394                let color = Luma([50u8]);
395                b.iter(|| {
396                    draw_hollow_ellipse_mut(
397                        &mut image,
398                        $center,
399                        $width_radius,
400                        $height_radius,
401                        color,
402                    );
403                    test::black_box(&image);
404                });
405            }
406        };
407    }
408
409    bench_hollow_ellipse!(bench_bench_hollow_ellipse_circle, (200, 200), 80, 80);
410    bench_hollow_ellipse!(bench_bench_hollow_ellipse_vertical, (200, 200), 40, 100);
411    bench_hollow_ellipse!(bench_bench_hollow_ellipse_horizontal, (200, 200), 100, 40);
412
413    macro_rules! bench_filled_ellipse {
414        ($name:ident, $center:expr, $width_radius:expr, $height_radius:expr) => {
415            #[bench]
416            fn $name(b: &mut test::Bencher) {
417                use super::draw_filled_ellipse_mut;
418
419                let mut image = GrayImage::new(500, 500);
420                let color = Luma([50u8]);
421                b.iter(|| {
422                    draw_filled_ellipse_mut(
423                        &mut image,
424                        $center,
425                        $width_radius,
426                        $height_radius,
427                        color,
428                    );
429                    test::black_box(&image);
430                });
431            }
432        };
433    }
434
435    bench_filled_ellipse!(bench_bench_filled_ellipse_circle, (200, 200), 80, 80);
436    bench_filled_ellipse!(bench_bench_filled_ellipse_vertical, (200, 200), 40, 100);
437    bench_filled_ellipse!(bench_bench_filled_ellipse_horizontal, (200, 200), 100, 40);
438}