1pub use denise::PixelView;
4
5use denise::{Color, Frame, PixelFormat, Rect, Size};
6
7use crate::blend::{Paint, blend_pixel, blend_span};
8
9#[derive(Debug)]
19pub struct Canvas<'a> {
20 pixels: &'a mut [u32],
21 size: Size,
22 stride: usize,
23 format: PixelFormat,
24 clip: Rect,
25}
26
27impl<'a> Canvas<'a> {
28 pub fn new(frame: &'a mut Frame<'_>) -> Self {
30 let size = frame.size();
31 let stride = frame.stride() as usize;
32 let format = frame.format();
33 Self {
34 pixels: frame.pixels_mut(),
35 size,
36 stride,
37 format,
38 clip: Rect::from_size(size),
39 }
40 }
41
42 pub fn from_pixels(
47 pixels: &'a mut [u32],
48 size: Size,
49 stride: u32,
50 format: PixelFormat,
51 ) -> Option<Self> {
52 if size.is_empty() || stride < size.width {
53 return None;
54 }
55 let stride = stride as usize;
56 let required = stride * (size.height as usize - 1) + size.width as usize;
57 (pixels.len() >= required).then_some(Self {
58 pixels,
59 size,
60 stride,
61 format,
62 clip: Rect::from_size(size),
63 })
64 }
65
66 #[inline]
71 pub fn pen(&mut self) -> crate::Pen<'_> {
72 crate::Pen::new(self)
73 }
74
75 #[inline]
77 pub const fn size(&self) -> Size {
78 self.size
79 }
80
81 #[inline]
83 pub const fn format(&self) -> PixelFormat {
84 self.format
85 }
86
87 #[inline]
89 pub const fn clip(&self) -> Rect {
90 self.clip
91 }
92
93 pub fn clip_to(&mut self, rect: Rect) {
95 self.clip = self.clip.intersect(&rect).unwrap_or(Rect::ZERO);
96 }
97
98 #[inline]
105 pub(crate) fn restore_clip(&mut self, rect: Rect) {
106 self.clip = rect;
107 }
108
109 pub fn with_clip(&mut self, rect: Rect) -> Canvas<'_> {
114 let clip = self.clip.intersect(&rect).unwrap_or(Rect::ZERO);
115 Canvas {
116 pixels: self.pixels,
117 size: self.size,
118 stride: self.stride,
119 format: self.format,
120 clip,
121 }
122 }
123
124 #[inline]
126 pub const fn is_clipped_out(&self) -> bool {
127 self.clip.is_empty()
128 }
129
130 #[inline]
132 pub fn visible(&self, rect: Rect) -> Option<Rect> {
133 self.clip.intersect(&rect)
134 }
135
136 #[inline]
138 pub(crate) fn row_span(&mut self, y: i32, x0: i32, x1: i32) -> Option<&mut [u32]> {
139 if y < self.clip.y || y >= self.clip.bottom() {
140 return None;
141 }
142 let x0 = x0.max(self.clip.x);
143 let x1 = x1.min(self.clip.right());
144 if x0 >= x1 {
145 return None;
146 }
147 let base = y as usize * self.stride;
148 Some(&mut self.pixels[base + x0 as usize..base + x1 as usize])
149 }
150
151 #[inline]
153 pub(crate) fn blend_at(&mut self, x: i32, y: i32, paint: Paint, coverage: u32) {
154 if coverage == 0 {
155 return;
156 }
157 if let Some(span) = self.row_span(y, x, x + 1)
158 && let Some(px) = span.first_mut()
159 {
160 blend_pixel(px, paint, coverage);
161 }
162 }
163
164 pub fn scroll_rows(&mut self, rect: Rect, dy: i32) -> bool {
173 let Some(rect) = rect
174 .intersect(&self.clip)
175 .and_then(|r| r.intersect(&Rect::from_size(self.size)))
176 else {
177 return false;
178 };
179 let shift = dy.unsigned_abs() as usize;
180 let (width, height) = (rect.width as usize, rect.height as usize);
181 if dy == 0 || shift >= height || width == 0 {
182 return false;
183 }
184 let stride = self.stride;
185 let (left, top) = (rect.x as usize, rect.y as usize);
186 if (top + height - 1) * stride + left + width > self.pixels.len() {
190 return false;
191 }
192 if dy > 0 {
193 for row in 0..height - shift {
195 let from = (top + row + shift) * stride + left;
196 self.pixels
197 .copy_within(from..from + width, (top + row) * stride + left);
198 }
199 } else {
200 for row in (shift..height).rev() {
201 let from = (top + row - shift) * stride + left;
202 self.pixels
203 .copy_within(from..from + width, (top + row) * stride + left);
204 }
205 }
206 true
207 }
208
209 pub fn clear(&mut self, color: Color) {
214 let paint = Paint::new(Color::rgb(color.r, color.g, color.b));
215 let clip = self.clip;
216 for y in clip.y..clip.bottom() {
217 if let Some(span) = self.row_span(y, clip.x, clip.right()) {
218 blend_span(span, paint);
219 }
220 }
221 }
222
223 pub fn copy_from(&mut self, src: &PixelView<'_>, regions: &[Rect]) {
229 let bounds = Rect::from_size(Size::new(
230 self.size.width.min(src.size().width),
231 self.size.height.min(src.size().height),
232 ));
233 for region in regions {
234 let Some(r) = region.intersect(&bounds) else {
235 continue;
236 };
237 for y in r.y..r.bottom() {
238 let Some(source) = src.row(y, r.x, r.right()) else {
239 continue;
240 };
241 if let Some(dst) = self.row_span(y, r.x, r.right()) {
244 let n = dst.len().min(source.len());
245 dst[..n].copy_from_slice(&source[..n]);
246 }
247 }
248 }
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255 use crate::testing::TestCanvas;
256
257 #[test]
260 fn scrolled_rows_move_within_the_rectangle_and_the_clip() {
261 let mut t = TestCanvas::with_stride(8, 8, 10);
262 {
263 let mut c = t.canvas();
264 for y in 0..8 {
265 c.fill_rect(Rect::new(0, y, 8, 1), Color::rgb(y as u8, 0, 0));
266 }
267 assert!(c.scroll_rows(Rect::new(2, 1, 4, 6), 2));
269 }
270 let row = |t: &TestCanvas, x: usize, y: usize| (t.pixels()[y * 10 + x] >> 16) & 0xFF;
271 assert_eq!(row(&t, 3, 1), 3);
272 assert_eq!(row(&t, 3, 4), 6);
273 assert_eq!(row(&t, 3, 5), 5);
275 assert_eq!(row(&t, 3, 6), 6);
276 assert_eq!(row(&t, 0, 1), 1);
278 assert_eq!(row(&t, 3, 0), 0);
279 assert_eq!(row(&t, 3, 7), 7);
280
281 {
285 let mut c = t.canvas();
286 c.clip_to(Rect::new(0, 0, 4, 8));
287 assert!(c.scroll_rows(Rect::new(0, 0, 8, 8), -1));
288 }
289 assert_eq!(row(&t, 1, 2), 1);
290 assert_eq!(row(&t, 1, 0), 0);
291 assert_eq!(row(&t, 6, 2), 2);
292
293 let mut c = t.canvas();
295 assert!(!c.scroll_rows(Rect::new(0, 0, 8, 8), 8));
296 assert!(!c.scroll_rows(Rect::new(0, 0, 8, 8), 0));
297 assert!(!c.scroll_rows(Rect::new(20, 0, 8, 8), 1));
298 }
299
300 #[test]
301 fn clip_only_narrows() {
302 let mut t = TestCanvas::new(16, 16);
303 let mut c = t.canvas();
304 c.clip_to(Rect::new(4, 4, 8, 8));
305 c.clip_to(Rect::new(0, 0, 16, 16));
306 assert_eq!(c.clip(), Rect::new(4, 4, 8, 8));
307 }
308
309 #[test]
310 fn disjoint_clip_is_empty_not_negative() {
311 let mut t = TestCanvas::new(16, 16);
312 {
313 let mut c = t.canvas();
314 c.clip_to(Rect::new(0, 0, 4, 4));
315 c.clip_to(Rect::new(8, 8, 4, 4));
316 assert!(c.is_clipped_out());
317 c.clear(Color::WHITE);
318 }
319 assert!(t.pixels().iter().all(|&p| p == 0));
320 }
321
322 #[test]
323 fn nested_clip_borrow_restores_the_parent() {
324 let mut t = TestCanvas::new(16, 16);
325 let mut c = t.canvas();
326 {
327 let mut child = c.with_clip(Rect::new(0, 0, 4, 4));
328 child.clear(Color::WHITE);
329 }
330 assert_eq!(c.clip(), Rect::new(0, 0, 16, 16));
331 }
332
333 #[test]
334 fn clear_respects_the_clip_and_the_stride() {
335 let mut t = TestCanvas::with_stride(10, 4, 16);
336 {
337 let mut c = t.canvas();
338 c.clip_to(Rect::new(2, 1, 4, 2));
339 c.clear(Color::WHITE);
340 }
341 for (y, row) in t.pixels().chunks(16).enumerate() {
342 for (x, &px) in row.iter().enumerate() {
343 let inside = (2..6).contains(&x) && (1..3).contains(&y);
344 assert_eq!(px == 0xFFFF_FFFF, inside, "at {x},{y}");
345 }
346 }
347 }
348
349 #[test]
350 fn copy_from_moves_only_the_listed_regions() {
351 let mut source = TestCanvas::new(8, 8);
352 source.canvas().clear(Color::from_argb8888(0xFFAA_BBCC));
353
354 let mut t = TestCanvas::new(8, 8);
355 t.canvas()
356 .copy_from(&source.view(), &[Rect::new(2, 2, 3, 3)]);
357
358 for y in 0..8 {
359 for x in 0..8 {
360 let inside = (2..5).contains(&x) && (2..5).contains(&y);
361 let expected = if inside { 0xFFAA_BBCC } else { 0 };
362 assert_eq!(t.pixels()[y * 8 + x], expected, "at {x},{y}");
363 }
364 }
365 }
366
367 #[test]
368 fn copy_from_clips_to_the_smaller_buffer() {
369 let mut source = TestCanvas::new(4, 4);
370 source.canvas().clear(Color::from_argb8888(0xFFAA_BBCC));
371
372 let mut t = TestCanvas::new(8, 8);
373 t.canvas()
374 .copy_from(&source.view(), &[Rect::new(0, 0, 8, 8)]);
375
376 assert_eq!(t.pixels()[0], 0xFFAA_BBCC);
377 assert_eq!(t.pixels()[3], 0xFFAA_BBCC);
378 assert_eq!(t.pixels()[4], 0);
379 assert_eq!(t.pixels()[4 * 8], 0);
380 }
381
382 #[test]
383 fn pixel_view_rejects_undersized_slices() {
384 let pixels = [0u32; 10];
385 assert!(PixelView::new(&pixels, Size::new(4, 4), 4).is_none());
386 assert!(PixelView::new(&pixels, Size::new(4, 2), 2).is_none());
387 }
388}