use crate::{
geometry::{Dimensions, Point, Size},
primitives::{circle, ContainsPoint, OffsetOutline, PointsIter, Primitive, Rectangle},
transform::Transform,
};
mod points;
mod styled;
pub use points::Points;
pub use styled::StyledPixelsIterator;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct Ellipse {
pub top_left: Point,
pub size: Size,
}
impl Ellipse {
pub const fn new(top_left: Point, size: Size) -> Self {
Ellipse { top_left, size }
}
pub const fn with_center(center: Point, size: Size) -> Self {
let top_left = Rectangle::with_center(center, size).top_left;
Ellipse { top_left, size }
}
pub fn center(&self) -> Point {
self.bounding_box().center()
}
fn center_2x(&self) -> Point {
center_2x(self.top_left, self.size)
}
}
impl OffsetOutline for Ellipse {
fn offset(&self, offset: i32) -> Self {
let size = if offset >= 0 {
self.size.saturating_add(Size::new_equal(2 * offset as u32))
} else {
self.size
.saturating_sub(Size::new_equal(2 * (-offset) as u32))
};
Self::with_center(self.center(), size)
}
}
pub(in crate::primitives) fn center_2x(top_left: Point, size: Size) -> Point {
let radius = size.saturating_sub(Size::new(1, 1));
top_left * 2 + radius
}
impl Primitive for Ellipse {}
impl PointsIter for Ellipse {
type Iter = Points;
fn points(&self) -> Self::Iter {
Points::new(self)
}
}
impl ContainsPoint for Ellipse {
fn contains(&self, point: Point) -> bool {
let ellipse_contains = EllipseContains::new(self.size);
ellipse_contains.contains(point * 2 - self.center_2x())
}
}
impl Dimensions for Ellipse {
fn bounding_box(&self) -> Rectangle {
Rectangle::new(self.top_left, self.size)
}
}
impl Transform for Ellipse {
fn translate(&self, by: Point) -> Self {
Self {
top_left: self.top_left + by,
..*self
}
}
fn translate_mut(&mut self, by: Point) -> &mut Self {
self.top_left += by;
self
}
}
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Debug)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub(in crate::primitives) struct EllipseContains {
a: u32,
b: u32,
threshold: u32,
}
impl EllipseContains {
pub const fn new(size: Size) -> Self {
let Size { width, height } = size;
let a = width.pow(2);
let b = height.pow(2);
let threshold = if width == height {
circle::diameter_to_threshold(width)
} else {
b * a
};
Self { a, b, threshold }
}
pub const fn contains(&self, point: Point) -> bool {
let x = point.x.pow(2) as u32;
let y = point.y.pow(2) as u32;
if self.a == self.b {
x + y < self.threshold
} else {
self.b * x + self.a * y < self.threshold
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
geometry::{Point, Size},
mock_display::MockDisplay,
pixelcolor::BinaryColor,
primitives::ContainsPoint,
};
#[test]
fn contains() {
let ellipse = Ellipse::new(Point::zero(), Size::new(40, 20));
let display = MockDisplay::from_points(
ellipse
.bounding_box()
.points()
.filter(|p| ellipse.contains(*p)),
BinaryColor::On,
);
let expected = MockDisplay::from_points(ellipse.points(), BinaryColor::On);
display.assert_eq(&expected);
}
#[test]
fn translate() {
let moved = Ellipse::new(Point::new(4, 6), Size::new(5, 8)).translate(Point::new(3, 5));
assert_eq!(moved, Ellipse::new(Point::new(7, 11), Size::new(5, 8)));
}
#[test]
fn offset() {
let center = Point::new(5, 6);
let ellipse = Ellipse::with_center(center, Size::new(3, 4));
assert_eq!(ellipse.offset(0), ellipse);
assert_eq!(
ellipse.offset(1),
Ellipse::with_center(center, Size::new(5, 6))
);
assert_eq!(
ellipse.offset(2),
Ellipse::with_center(center, Size::new(7, 8))
);
assert_eq!(
ellipse.offset(-1),
Ellipse::with_center(center, Size::new(1, 2))
);
assert_eq!(
ellipse.offset(-2),
Ellipse::with_center(center, Size::new(0, 0))
);
assert_eq!(
ellipse.offset(-3),
Ellipse::with_center(center, Size::new(0, 0))
);
}
}