Skip to main content

denise_render/
rect.rs

1//! Axis-aligned rectangles.
2
3use denise::Rect;
4
5use crate::blend::{Paint, blend_span};
6use crate::canvas::Canvas;
7use crate::painter::Painter;
8
9impl Canvas<'_> {
10    /// Fills `rect`, compositing with the destination if the paint has alpha.
11    pub fn fill_rect(&mut self, rect: Rect, color: impl Into<Paint>) {
12        let paint = color.into();
13        if paint.is_invisible() {
14            return;
15        }
16        let Some(r) = self.visible(rect) else {
17            return;
18        };
19        for y in r.y..r.bottom() {
20            if let Some(span) = self.row_span(y, r.x, r.right()) {
21                blend_span(span, paint);
22            }
23        }
24    }
25
26    /// Fills every rectangle. Overlapping rectangles composite twice.
27    pub fn fill_rects(&mut self, rects: &[Rect], color: impl Into<Paint>) {
28        Painter::fill_rects(self, rects, color.into());
29    }
30
31    /// Draws a border of `thickness` pixels *inside* `rect`.
32    ///
33    /// The four bands are cut so they do not overlap. Drawing them as four
34    /// full-length rectangles would composite the corners twice, which is invisible
35    /// at full alpha and obvious at anything less.
36    pub fn stroke_rect(&mut self, rect: Rect, thickness: i32, color: impl Into<Paint>) {
37        Painter::stroke_rect(self, rect, thickness, color.into());
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44    use crate::testing::TestCanvas;
45    use denise::Color;
46
47    #[test]
48    fn fill_clips_to_the_canvas() {
49        let mut t = TestCanvas::new(8, 8);
50        t.canvas().fill_rect(Rect::new(-4, -4, 8, 8), Color::WHITE);
51        assert_eq!(t.at(0, 0), 0xFFFF_FFFF);
52        assert_eq!(t.at(3, 3), 0xFFFF_FFFF);
53        assert_eq!(t.at(4, 0), 0);
54        assert_eq!(t.at(0, 4), 0);
55    }
56
57    #[test]
58    fn fill_never_touches_stride_padding() {
59        let mut t = TestCanvas::with_stride(10, 4, 16);
60        t.canvas().fill_rect(Rect::new(0, 0, 10, 4), Color::WHITE);
61        for row in t.pixels().chunks(16) {
62            assert!(row[..10].iter().all(|&p| p == 0xFFFF_FFFF));
63            assert!(row[10..].iter().all(|&p| p == 0));
64        }
65    }
66
67    #[test]
68    fn wholly_offscreen_fill_is_a_noop() {
69        let mut t = TestCanvas::new(8, 8);
70        t.canvas()
71            .fill_rect(Rect::new(100, 100, 8, 8), Color::WHITE);
72        assert!(t.pixels().iter().all(|&p| p == 0));
73    }
74
75    #[test]
76    fn half_alpha_twice_is_not_full_alpha() {
77        let mut t = TestCanvas::new(4, 4);
78        t.canvas()
79            .fill_rect(Rect::new(0, 0, 4, 4), Color::rgba(255, 255, 255, 128));
80        let once = t.at(0, 0);
81        t.canvas()
82            .fill_rect(Rect::new(0, 0, 4, 4), Color::rgba(255, 255, 255, 128));
83        let twice = t.at(0, 0);
84        assert!(twice > once, "second coat must lighten further");
85        assert!(twice < 0xFFFF_FFFF, "two half coats must not reach opaque");
86    }
87
88    #[test]
89    fn stroke_corners_are_not_composited_twice() {
90        let mut t = TestCanvas::new(16, 16);
91        t.canvas()
92            .stroke_rect(Rect::new(0, 0, 16, 16), 2, Color::rgba(255, 255, 255, 128));
93        // A corner pixel and an edge pixel get exactly one coat each.
94        assert_eq!(t.at(0, 0), t.at(8, 0));
95        assert_eq!(t.at(0, 0), t.at(0, 8));
96    }
97
98    #[test]
99    fn stroke_leaves_the_interior_alone() {
100        let mut t = TestCanvas::new(16, 16);
101        t.canvas()
102            .stroke_rect(Rect::new(2, 2, 12, 12), 3, Color::WHITE);
103        assert_eq!(t.at(2, 2), 0xFFFF_FFFF);
104        assert_eq!(t.at(4, 4), 0xFFFF_FFFF);
105        assert_eq!(t.at(5, 5), 0, "interior must be untouched");
106        assert_eq!(t.at(1, 1), 0, "outside must be untouched");
107    }
108
109    #[test]
110    fn stroke_thicker_than_the_rect_becomes_a_fill() {
111        let mut t = TestCanvas::new(8, 8);
112        t.canvas()
113            .stroke_rect(Rect::new(1, 1, 4, 4), 9, Color::WHITE);
114        assert_eq!(t.at(2, 2), 0xFFFF_FFFF);
115        assert_eq!(t.at(0, 0), 0);
116    }
117}