use super::super::drawable::{Drawable, Pixel};
use super::super::transform::Transform;
use crate::geometry::{Dimensions, Point, Size};
use crate::pixelcolor::PixelColor;
use crate::primitives::Primitive;
use crate::style::Style;
use crate::style::WithStyle;
#[derive(Debug, Copy, Clone)]
pub struct Circle<C: PixelColor> {
pub center: Point,
pub radius: u32,
pub style: Style<C>,
}
impl<C> Circle<C>
where
C: PixelColor,
{
pub fn new(center: Point, radius: u32) -> Self {
Circle {
center,
radius,
style: Style::default(),
}
}
}
impl<C> Primitive for Circle<C> where C: PixelColor {}
impl<C> Dimensions for Circle<C>
where
C: PixelColor,
{
fn top_left(&self) -> Point {
let radius_coord = Point::new(self.radius as i32, self.radius as i32);
self.center - radius_coord
}
fn bottom_right(&self) -> Point {
self.top_left() + self.size()
}
fn size(&self) -> Size {
Size::new(self.radius * 2, self.radius * 2)
}
}
impl<C> WithStyle<C> for Circle<C>
where
C: PixelColor,
{
fn style(mut self, style: Style<C>) -> Self {
self.style = style;
self
}
fn stroke(mut self, color: Option<C>) -> Self {
self.style.stroke_color = color;
self
}
fn stroke_width(mut self, width: u8) -> Self {
self.style.stroke_width = width;
self
}
fn fill(mut self, color: Option<C>) -> Self {
self.style.fill_color = color;
self
}
}
impl<C> IntoIterator for Circle<C>
where
C: PixelColor,
{
type Item = Pixel<C>;
type IntoIter = CircleIterator<C>;
fn into_iter(self) -> Self::IntoIter {
(&self).into_iter()
}
}
impl<'a, C> IntoIterator for &'a Circle<C>
where
C: PixelColor,
{
type Item = Pixel<C>;
type IntoIter = CircleIterator<C>;
fn into_iter(self) -> Self::IntoIter {
CircleIterator {
center: self.center,
radius: self.radius,
style: self.style,
p: Point::new(-(self.radius as i32), -(self.radius as i32)),
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct CircleIterator<C: PixelColor> {
center: Point,
radius: u32,
style: Style<C>,
p: Point,
}
impl<C> Iterator for CircleIterator<C>
where
C: PixelColor,
{
type Item = Pixel<C>;
fn next(&mut self) -> Option<Self::Item> {
if self.style.stroke_color.is_none() && self.style.fill_color.is_none() {
return None;
}
let radius = self.radius as i32 - self.style.stroke_width as i32 + 1;
let outer_radius = self.radius as i32;
let radius_sq = radius * radius;
let outer_radius_sq = outer_radius * outer_radius;
loop {
let t = self.p;
let len = t.x * t.x + t.y * t.y;
let is_border = len > radius_sq - radius && len < outer_radius_sq + radius;
let is_fill = len <= outer_radius_sq;
let item = if is_border && self.style.stroke_color.is_some() {
Some(Pixel(
self.center + t,
self.style.stroke_color.expect("Border color not defined"),
))
} else if is_fill && self.style.fill_color.is_some() {
Some(Pixel(
self.center + t,
self.style.fill_color.expect("Fill color not defined"),
))
} else {
None
};
self.p.x += 1;
if self.p.x > self.radius as i32 {
self.p.x = -(self.radius as i32);
self.p.y += 1;
}
if self.p.y > self.radius as i32 {
break None;
}
if item.is_some() {
break item;
}
}
}
}
impl<C> Drawable for Circle<C> where C: PixelColor {}
impl<C> Transform for Circle<C>
where
C: PixelColor,
{
fn translate(&self, by: Point) -> Self {
Self {
center: self.center + by,
..self.clone()
}
}
fn translate_mut(&mut self, by: Point) -> &mut Self {
self.center += by;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pixelcolor::BinaryColor;
#[test]
fn negative_dimensions() {
let circ: Circle<BinaryColor> = Circle::new(Point::new(-10, -10), 5);
assert_eq!(circ.top_left(), Point::new(-15, -15));
assert_eq!(circ.bottom_right(), Point::new(-5, -5));
assert_eq!(circ.size(), Size::new(10, 10));
}
#[test]
fn dimensions() {
let circ: Circle<BinaryColor> = Circle::new(Point::new(10, 20), 5);
assert_eq!(circ.top_left(), Point::new(5, 15));
assert_eq!(circ.bottom_right(), Point::new(15, 25));
assert_eq!(circ.size(), Size::new(10, 10));
}
#[test]
fn large_radius() {
let circ: Circle<BinaryColor> = Circle::new(Point::new(5, 5), 10);
assert_eq!(circ.top_left(), Point::new(-5, -5));
assert_eq!(circ.bottom_right(), Point::new(15, 15));
assert_eq!(circ.size(), Size::new(20, 20));
}
#[test]
fn transparent_border() {
let circ: Circle<BinaryColor> = Circle::new(Point::new(5, 5), 10)
.stroke(None)
.fill(Some(BinaryColor::On));
assert!(circ.into_iter().count() > 0);
}
#[test]
fn it_handles_negative_coordinates() {
let positive: CircleIterator<BinaryColor> = Circle::new(Point::new(10, 10), 5)
.style(Style::stroke(BinaryColor::On))
.into_iter();
let negative: CircleIterator<BinaryColor> = Circle::new(Point::new(-10, -10), 5)
.style(Style::stroke(BinaryColor::On))
.into_iter();
assert!(negative.into_iter().eq(positive
.into_iter()
.map(|Pixel(p, c)| Pixel(p - Point::new(20, 20), c))));
}
}