use super::{Drawable, hspan, vspan};
use crate::image::ImageViewMut;
use crate::{CoordinateI32, Size};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rect<P> {
pub top_left: CoordinateI32,
pub size: Size,
pub color: P,
pub fill: bool,
}
impl<P: Copy> Drawable<P> for Rect<P> {
fn draw_into(&self, image: &mut impl ImageViewMut<Pixel = P>) {
if self.size.width == 0 || self.size.height == 0 {
return;
}
let (x0, y0) = (i64::from(self.top_left.x), i64::from(self.top_left.y));
let width = i64::try_from(self.size.width).unwrap_or(i64::MAX);
let height = i64::try_from(self.size.height).unwrap_or(i64::MAX);
let x1 = x0.saturating_add(width - 1);
let y1 = y0.saturating_add(height - 1);
if self.fill {
let lo = y0.max(0);
let hi = y1.min(image.size().height as i64 - 1);
for y in lo..=hi {
hspan(image, x0, x1, y, self.color);
}
} else {
hspan(image, x0, x1, y0, self.color);
if y1 > y0 {
hspan(image, x0, x1, y1, self.color);
}
if y1 > y0 + 1 {
vspan(image, x0, y0 + 1, y1 - 1, self.color);
if x1 > x0 {
vspan(image, x1, y0 + 1, y1 - 1, self.color);
}
}
}
}
}
pub fn draw_rect<P: Copy>(
image: &mut impl ImageViewMut<Pixel = P>,
top_left: impl Into<CoordinateI32>,
size: Size,
color: P,
fill: bool,
) {
Rect {
top_left: top_left.into(),
size,
color,
fill,
}
.draw_into(image);
}
#[cfg(test)]
mod tests {
use super::super::tests::inked;
use super::*;
use crate::image::Image;
use crate::pixel::Mono8;
fn ink() -> Mono8 {
Mono8::new(255)
}
#[test]
fn outline_touches_exactly_the_border() {
let mut image: Image<Mono8> = Image::zero(8, 8);
draw_rect(&mut image, (1, 2), Size::new(5, 4), ink(), false);
let drawn = inked(&image);
assert_eq!(drawn.len(), 14);
for (x, y) in drawn {
let inside = (1..=5).contains(&x) && (2..=5).contains(&y);
let on_border = x == 1 || x == 5 || y == 2 || y == 5;
assert!(inside && on_border, "({x}, {y}) not on the border");
}
}
#[test]
fn fill_covers_the_whole_region() {
let mut image: Image<Mono8> = Image::zero(8, 8);
draw_rect(&mut image, (1, 2), Size::new(5, 4), ink(), true);
let drawn = inked(&image);
assert_eq!(drawn.len(), 20);
for x in 1..=5 {
for y in 2..=5 {
assert!(drawn.contains(&(x, y)), "({x}, {y}) not filled");
}
}
}
#[test]
fn degenerate_sizes() {
let mut image: Image<Mono8> = Image::zero(6, 6);
draw_rect(&mut image, (2, 2), Size::new(0, 3), ink(), false);
draw_rect(&mut image, (2, 2), Size::new(3, 0), ink(), true);
assert!(inked(&image).is_empty());
draw_rect(&mut image, (4, 4), Size::new(1, 1), ink(), false);
assert_eq!(inked(&image), vec![(4, 4)]);
let mut image: Image<Mono8> = Image::zero(6, 6);
draw_rect(&mut image, (3, 1), Size::new(1, 4), ink(), false);
assert_eq!(inked(&image), vec![(3, 1), (3, 2), (3, 3), (3, 4)]);
}
#[test]
fn clips_across_every_edge() {
let mut image: Image<Mono8> = Image::zero(4, 4);
draw_rect(&mut image, (-2, -2), Size::new(8, 8), ink(), false);
assert!(inked(&image).is_empty());
draw_rect(&mut image, (-2, -2), Size::new(8, 8), ink(), true);
assert_eq!(inked(&image).len(), 16);
let mut image: Image<Mono8> = Image::zero(4, 4);
draw_rect(&mut image, (2, 2), Size::new(5, 5), ink(), false);
assert_eq!(inked(&image), vec![(2, 2), (3, 2), (2, 3)]);
}
#[test]
fn a_pathological_size_clips_instead_of_wrapping() {
let mut image: Image<Mono8> = Image::zero(6, 6);
draw_rect(&mut image, (2, 2), Size::new(usize::MAX, 2), ink(), true);
let drawn = inked(&image);
assert_eq!(drawn.len(), 8, "{drawn:?}");
assert!(
drawn.contains(&(2, 2)) && drawn.contains(&(5, 3)),
"{drawn:?}"
);
assert!(
!drawn.contains(&(0, 2)) && !drawn.contains(&(1, 2)),
"{drawn:?}"
);
let mut image: Image<Mono8> = Image::zero(6, 6);
draw_rect(
&mut image,
(1, 1),
Size::new(usize::MAX, usize::MAX),
ink(),
false,
);
let drawn = inked(&image);
assert!(
drawn.contains(&(5, 1)) && drawn.contains(&(1, 5)),
"{drawn:?}"
);
assert!(!drawn.contains(&(0, 0)), "{drawn:?}");
}
}