diff --git a/src/drawing/conics.rs b/src/drawing/conics.rs index ac7f2337..15dbfccd 100644 --- a/src/drawing/conics.rs +++ b/src/drawing/conics.rs @@ -146,42 +146,42 @@ where F: FnMut(i32, i32, i32, i32), { let (x0, y0) = center; - let w2 = width_radius * width_radius; - let h2 = height_radius * height_radius; + let w2 = (width_radius * width_radius) as f32; + let h2 = (height_radius * height_radius) as f32; let mut x = 0; let mut y = height_radius; - let mut px = 0; - let mut py = 2 * w2 * y; + let mut px = 0.0; + let mut py = 2.0 * w2 * y as f32; render_func(x0, y0, x, y); // Top and bottom regions. - let mut p = (h2 - (w2 * height_radius)) as f32 + (0.25 * w2 as f32); + let mut p = h2 - (w2 * height_radius as f32) + (0.25 * w2); while px < py { x += 1; - px += 2 * h2; + px += 2.0 * h2; if p < 0.0 { - p += (h2 + px) as f32; + p += h2 + px; } else { y -= 1; - py += -2 * w2; - p += (h2 + px - py) as f32; + py += -2.0 * w2; + p += h2 + px - py; } render_func(x0, y0, x, y); } // Left and right regions. - p = (h2 as f32) * (x as f32 + 0.5).powi(2) + (w2 * (y - 1).pow(2)) as f32 - (w2 * h2) as f32; + p = h2 * (x as f32 + 0.5).powi(2) + (w2 * (y - 1).pow(2) as f32) - w2 * h2; while y > 0 { y -= 1; - py += -2 * w2; + py += -2.0 * w2; if p > 0.0 { - p += (w2 - py) as f32; + p += w2 - py; } else { x += 1; - px += 2 * h2; - p += (w2 - py + px) as f32; + px += 2.0 * h2; + p += w2 - py + px; } render_func(x0, y0, x, y); @@ -310,7 +310,74 @@ where #[cfg(test)] mod tests { - use image::{GrayImage, Luma}; + use super::draw_filled_ellipse_mut; + use image::{GenericImage, GrayImage, Luma}; + + struct Ellipse { + center: (i32, i32), + width_radius: i32, + height_radius: i32, + } + + impl Ellipse { + fn normalized_distance_from_center(&self, (x, y): (i32, i32)) -> f32 { + let (cx, cy) = self.center; + let (w, h) = (self.width_radius as f32, self.height_radius as f32); + ((cx - x) as f32 / w).powi(2) + ((cy - y) as f32 / h).powi(2) + } + fn is_boundary_point(&self, (x, y): (i32, i32), boundary_eps: f32) -> bool { + assert!(boundary_eps >= 0.0); + (self.normalized_distance_from_center((x, y)) - 1.0).abs() < boundary_eps + } + fn is_inner_point(&self, (x, y): (i32, i32)) -> bool { + self.normalized_distance_from_center((x, y)) < 1.0 + } + } + + fn check_filled_ellipse( + img: &I, + ellipse: Ellipse, + inner_color: I::Pixel, + outer_color: I::Pixel, + boundary_eps: f32, + ) where + I::Pixel: core::fmt::Debug + PartialEq, + { + for x in 0..img.width() as i32 { + for y in 0..img.height() as i32 { + if ellipse.is_boundary_point((x, y), boundary_eps) { + continue; + } + let pixel = img.get_pixel(x as u32, y as u32); + if ellipse.is_inner_point((x, y)) { + assert_eq!(pixel, inner_color); + } else { + assert_eq!(pixel, outer_color); + } + } + } + } + + #[test] + fn test_draw_filled_ellipse() { + let ellipse = Ellipse { + center: (960, 540), + width_radius: 960, + height_radius: 540, + }; + let inner_color = image::Rgb([255, 0, 0]); + let outer_color = image::Rgb([0, 0, 0]); + let mut img = image::RgbImage::new(1920, 1080); + draw_filled_ellipse_mut( + &mut img, + ellipse.center, + ellipse.width_radius, + ellipse.height_radius, + inner_color, + ); + const EPS: f32 = 0.0019; + check_filled_ellipse(&img, ellipse, inner_color, outer_color, EPS); + } macro_rules! bench_hollow_ellipse { ($name:ident, $center:expr, $width_radius:expr, $height_radius:expr) => {