diff --git a/Cargo.toml b/Cargo.toml index 7bfa3f1..8491fda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fimg" -version = "0.4.10" +version = "0.4.11" authors = ["bend-n "] license = "MIT" edition = "2021" diff --git a/src/affine.rs b/src/affine.rs index 893be81..f4c311e 100644 --- a/src/affine.rs +++ b/src/affine.rs @@ -188,9 +188,7 @@ unsafe fn transpose_non_power_of_two, const CHANNELS: usize> Image { + /// Draws a circle, using the [Bresenham's circle](https://en.wikipedia.org/wiki/Midpoint_circle_algorithm) algorithm. + /// ``` + /// # use fimg::Image; + /// let mut i = Image::alloc(50, 50); + /// i.border_circle((25, 25), 20, [255]); + /// # assert_eq!(i.buffer(), include_bytes!("../../tdata/circle.imgbuf")); + /// ``` + pub fn border_circle(&mut self, (xc, yc): (i32, i32), radius: i32, c: [u8; CHANNELS]) { + let mut x = 0; + let mut y = radius; + let mut p = 1 - radius; + /// bounds the pixels + macro_rules! bound { + ($($x:expr,$y:expr);+;) => { + $(if $x >= 0 && $x < self.width() as i32 && $y >= 0 && $y < self.height() as i32 { + // SAFETY: ^ + unsafe { self.set_pixel($x as u32, $y as u32, c) }; + })+ + }; + } + while x <= y { + bound! { + xc + x, yc + y; + xc + y, yc + x; + xc - y, yc + x; + xc - x, yc + y; + xc - x, yc - y; + xc - y, yc - x; + xc + y, yc - x; + xc + x, yc - y; + }; + x += 1; + if p < 0 { + p += 2 * x + 1; + } else { + y -= 1; + p += 2 * (x - y) + 1; + } + } + } + + /// Draw a filled circle. + /// ``` + /// # use fimg::Image; + /// let mut i = Image::alloc(50, 50); + /// i.circle((25, 25), 20, [255]); + /// # assert_eq!(i.buffer(), include_bytes!("../../tdata/circle2.imgbuf")); + /// ``` + pub fn circle(&mut self, (xc, yc): (i32, i32), radius: i32, c: [u8; CHANNELS]) { + for x in -radius..radius { + let h = ((radius * radius - x * x) as f32).sqrt().round() as i32; + for y in -h..h { + let x = x + xc; + let y = y + yc; + if x >= 0 && x < self.width() as i32 && y >= 0 && y < self.height() as i32 { + // SAFETY: ^ + unsafe { self.set_pixel(x as u32, y as u32, c) }; + } + } + } + } +} diff --git a/src/drawing/mod.rs b/src/drawing/mod.rs index 87b4b88..ebc4e99 100644 --- a/src/drawing/mod.rs +++ b/src/drawing/mod.rs @@ -1,5 +1,6 @@ -//! contains drawing operations, like {line, box, triangle, polygon} drawing +//! contains drawing operations, like {line, box, triangle, polygon, circle} drawing mod r#box; +mod circle; mod line; mod poly; mod tri; diff --git a/src/drawing/poly.rs b/src/drawing/poly.rs index e0dd017..3847a56 100644 --- a/src/drawing/poly.rs +++ b/src/drawing/poly.rs @@ -86,6 +86,7 @@ impl, const CHANNELS: usize> Image { } /// Draws a regular convex polygon with a specified number of sides, a radius, and a rotation (radians). + /// Prefer [`Image::circle`] over `poly(.., 600, ..)`. /// Calls into [`Image::tri`] and [`Image::quad`]. /// ``` /// # use fimg::Image; @@ -113,7 +114,7 @@ impl, const CHANNELS: usize> Image { self.tri( add(trans(space + rotation)), add(trans(rotation)), - add(trans(space * 2.0 + rotation)), + add(trans(space.mul_add(2.0, rotation))), c, ); } @@ -122,9 +123,9 @@ impl, const CHANNELS: usize> Image { for i in (0..sides - 1).step_by(2).map(|i| i as f32) { self.quad( r((x, y)), - r(add(trans(space * i + rotation))), - r(add(trans(space * (i + 1.) + rotation))), - r(add(trans(space * (i + 2.) + rotation))), + r(add(trans(space.mul_add(i, rotation)))), + r(add(trans(space.mul_add(i + 1., rotation)))), + r(add(trans(space.mul_add(i + 2., rotation)))), c, ); } @@ -134,8 +135,8 @@ impl, const CHANNELS: usize> Image { // the missing piece self.tri( (x, y), - add(trans(space * i + rotation)), - add(trans(space * (i + 1.) + rotation)), + add(trans(space.mul_add(i, rotation))), + add(trans(space.mul_add(i + 1., rotation))), c, ); } diff --git a/tdata/circle.imgbuf b/tdata/circle.imgbuf new file mode 100644 index 0000000..3235779 Binary files /dev/null and b/tdata/circle.imgbuf differ diff --git a/tdata/circle2.imgbuf b/tdata/circle2.imgbuf new file mode 100644 index 0000000..26d22fd Binary files /dev/null and b/tdata/circle2.imgbuf differ