1use denise::Rect;
4
5use crate::blend::{Paint, blend_span};
6use crate::canvas::Canvas;
7
8impl Canvas<'_> {
9 pub fn fill_rect(&mut self, rect: Rect, color: impl Into<Paint>) {
11 let paint = color.into();
12 if paint.is_invisible() {
13 return;
14 }
15 let Some(r) = self.visible(rect) else {
16 return;
17 };
18 for y in r.y..r.bottom() {
19 if let Some(span) = self.row_span(y, r.x, r.right()) {
20 blend_span(span, paint);
21 }
22 }
23 }
24
25 pub fn fill_rects(&mut self, rects: &[Rect], color: impl Into<Paint>) {
27 let paint = color.into();
28 for rect in rects {
29 self.fill_rect(*rect, paint);
30 }
31 }
32
33 pub fn stroke_rect(&mut self, rect: Rect, thickness: i32, color: impl Into<Paint>) {
39 let paint = color.into();
40 let t = thickness.max(0);
41 if t == 0 || rect.is_empty() || paint.is_invisible() {
42 return;
43 }
44 if t * 2 >= rect.width.min(rect.height) {
45 self.fill_rect(rect, paint);
46 return;
47 }
48
49 let inner_height = rect.height - 2 * t;
50 self.fill_rect(Rect::new(rect.x, rect.y, rect.width, t), paint);
51 self.fill_rect(Rect::new(rect.x, rect.bottom() - t, rect.width, t), paint);
52 self.fill_rect(Rect::new(rect.x, rect.y + t, t, inner_height), paint);
53 self.fill_rect(
54 Rect::new(rect.right() - t, rect.y + t, t, inner_height),
55 paint,
56 );
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63 use crate::testing::TestCanvas;
64 use denise::Color;
65
66 #[test]
67 fn fill_clips_to_the_canvas() {
68 let mut t = TestCanvas::new(8, 8);
69 t.canvas().fill_rect(Rect::new(-4, -4, 8, 8), Color::WHITE);
70 assert_eq!(t.at(0, 0), 0xFFFF_FFFF);
71 assert_eq!(t.at(3, 3), 0xFFFF_FFFF);
72 assert_eq!(t.at(4, 0), 0);
73 assert_eq!(t.at(0, 4), 0);
74 }
75
76 #[test]
77 fn fill_never_touches_stride_padding() {
78 let mut t = TestCanvas::with_stride(10, 4, 16);
79 t.canvas().fill_rect(Rect::new(0, 0, 10, 4), Color::WHITE);
80 for row in t.pixels().chunks(16) {
81 assert!(row[..10].iter().all(|&p| p == 0xFFFF_FFFF));
82 assert!(row[10..].iter().all(|&p| p == 0));
83 }
84 }
85
86 #[test]
87 fn wholly_offscreen_fill_is_a_noop() {
88 let mut t = TestCanvas::new(8, 8);
89 t.canvas()
90 .fill_rect(Rect::new(100, 100, 8, 8), Color::WHITE);
91 assert!(t.pixels().iter().all(|&p| p == 0));
92 }
93
94 #[test]
95 fn half_alpha_twice_is_not_full_alpha() {
96 let mut t = TestCanvas::new(4, 4);
97 t.canvas()
98 .fill_rect(Rect::new(0, 0, 4, 4), Color::rgba(255, 255, 255, 128));
99 let once = t.at(0, 0);
100 t.canvas()
101 .fill_rect(Rect::new(0, 0, 4, 4), Color::rgba(255, 255, 255, 128));
102 let twice = t.at(0, 0);
103 assert!(twice > once, "second coat must lighten further");
104 assert!(twice < 0xFFFF_FFFF, "two half coats must not reach opaque");
105 }
106
107 #[test]
108 fn stroke_corners_are_not_composited_twice() {
109 let mut t = TestCanvas::new(16, 16);
110 t.canvas()
111 .stroke_rect(Rect::new(0, 0, 16, 16), 2, Color::rgba(255, 255, 255, 128));
112 assert_eq!(t.at(0, 0), t.at(8, 0));
114 assert_eq!(t.at(0, 0), t.at(0, 8));
115 }
116
117 #[test]
118 fn stroke_leaves_the_interior_alone() {
119 let mut t = TestCanvas::new(16, 16);
120 t.canvas()
121 .stroke_rect(Rect::new(2, 2, 12, 12), 3, Color::WHITE);
122 assert_eq!(t.at(2, 2), 0xFFFF_FFFF);
123 assert_eq!(t.at(4, 4), 0xFFFF_FFFF);
124 assert_eq!(t.at(5, 5), 0, "interior must be untouched");
125 assert_eq!(t.at(1, 1), 0, "outside must be untouched");
126 }
127
128 #[test]
129 fn stroke_thicker_than_the_rect_becomes_a_fill() {
130 let mut t = TestCanvas::new(8, 8);
131 t.canvas()
132 .stroke_rect(Rect::new(1, 1, 4, 4), 9, Color::WHITE);
133 assert_eq!(t.at(2, 2), 0xFFFF_FFFF);
134 assert_eq!(t.at(0, 0), 0);
135 }
136}