1use denise::{Color, Frame, PixelFormat, Rect, Size};
4
5use crate::blend::{Paint, blend_pixel, blend_span};
6
7#[derive(Clone, Copy, Debug)]
9pub struct PixelView<'a> {
10 pixels: &'a [u32],
11 size: Size,
12 stride: usize,
13}
14
15impl<'a> PixelView<'a> {
16 pub fn new(pixels: &'a [u32], size: Size, stride: u32) -> Option<Self> {
19 if size.is_empty() || stride < size.width {
20 return None;
21 }
22 let required = denise::required_words(size, stride);
25 let stride = stride as usize;
26 (pixels.len() as u64 >= required).then_some(Self {
27 pixels,
28 size,
29 stride,
30 })
31 }
32
33 pub fn from_frame(frame: &'a Frame<'_>) -> Option<Self> {
35 Self::new(frame.pixels(), frame.size(), frame.stride())
36 }
37
38 #[inline]
40 pub const fn size(&self) -> Size {
41 self.size
42 }
43
44 #[inline]
45 pub(crate) fn row(&self, y: i32, x0: i32, x1: i32) -> Option<&[u32]> {
46 if y < 0 || y >= self.size.height as i32 || x0 >= x1 || x0 < 0 {
47 return None;
48 }
49 let base = y as usize * self.stride;
50 self.pixels.get(base + x0 as usize..base + x1 as usize)
51 }
52}
53
54#[derive(Debug)]
64pub struct Canvas<'a> {
65 pixels: &'a mut [u32],
66 size: Size,
67 stride: usize,
68 format: PixelFormat,
69 clip: Rect,
70}
71
72impl<'a> Canvas<'a> {
73 pub fn new(frame: &'a mut Frame<'_>) -> Self {
75 let size = frame.size();
76 let stride = frame.stride() as usize;
77 let format = frame.format();
78 Self {
79 pixels: frame.pixels_mut(),
80 size,
81 stride,
82 format,
83 clip: Rect::from_size(size),
84 }
85 }
86
87 pub fn from_pixels(
92 pixels: &'a mut [u32],
93 size: Size,
94 stride: u32,
95 format: PixelFormat,
96 ) -> Option<Self> {
97 if size.is_empty() || stride < size.width {
98 return None;
99 }
100 let stride = stride as usize;
101 let required = stride * (size.height as usize - 1) + size.width as usize;
102 (pixels.len() >= required).then_some(Self {
103 pixels,
104 size,
105 stride,
106 format,
107 clip: Rect::from_size(size),
108 })
109 }
110
111 #[inline]
113 pub const fn size(&self) -> Size {
114 self.size
115 }
116
117 #[inline]
119 pub const fn format(&self) -> PixelFormat {
120 self.format
121 }
122
123 #[inline]
125 pub const fn clip(&self) -> Rect {
126 self.clip
127 }
128
129 pub fn clip_to(&mut self, rect: Rect) {
131 self.clip = self.clip.intersect(&rect).unwrap_or(Rect::ZERO);
132 }
133
134 pub fn with_clip(&mut self, rect: Rect) -> Canvas<'_> {
139 let clip = self.clip.intersect(&rect).unwrap_or(Rect::ZERO);
140 Canvas {
141 pixels: self.pixels,
142 size: self.size,
143 stride: self.stride,
144 format: self.format,
145 clip,
146 }
147 }
148
149 #[inline]
151 pub const fn is_clipped_out(&self) -> bool {
152 self.clip.is_empty()
153 }
154
155 #[inline]
157 pub fn visible(&self, rect: Rect) -> Option<Rect> {
158 self.clip.intersect(&rect)
159 }
160
161 #[inline]
163 pub(crate) fn row_span(&mut self, y: i32, x0: i32, x1: i32) -> Option<&mut [u32]> {
164 if y < self.clip.y || y >= self.clip.bottom() {
165 return None;
166 }
167 let x0 = x0.max(self.clip.x);
168 let x1 = x1.min(self.clip.right());
169 if x0 >= x1 {
170 return None;
171 }
172 let base = y as usize * self.stride;
173 Some(&mut self.pixels[base + x0 as usize..base + x1 as usize])
174 }
175
176 #[inline]
178 pub(crate) fn blend_at(&mut self, x: i32, y: i32, paint: Paint, coverage: u32) {
179 if coverage == 0 {
180 return;
181 }
182 if let Some(span) = self.row_span(y, x, x + 1)
183 && let Some(px) = span.first_mut()
184 {
185 blend_pixel(px, paint, coverage);
186 }
187 }
188
189 pub fn clear(&mut self, color: Color) {
194 let paint = Paint::new(Color::rgb(color.r, color.g, color.b));
195 let clip = self.clip;
196 for y in clip.y..clip.bottom() {
197 if let Some(span) = self.row_span(y, clip.x, clip.right()) {
198 blend_span(span, paint);
199 }
200 }
201 }
202
203 pub fn copy_from(&mut self, src: &PixelView<'_>, regions: &[Rect]) {
209 let bounds = Rect::from_size(Size::new(
210 self.size.width.min(src.size.width),
211 self.size.height.min(src.size.height),
212 ));
213 for region in regions {
214 let Some(r) = region.intersect(&bounds) else {
215 continue;
216 };
217 for y in r.y..r.bottom() {
218 let Some(source) = src.row(y, r.x, r.right()) else {
219 continue;
220 };
221 if let Some(dst) = self.row_span(y, r.x, r.right()) {
224 let n = dst.len().min(source.len());
225 dst[..n].copy_from_slice(&source[..n]);
226 }
227 }
228 }
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use crate::testing::TestCanvas;
236
237 #[test]
238 fn clip_only_narrows() {
239 let mut t = TestCanvas::new(16, 16);
240 let mut c = t.canvas();
241 c.clip_to(Rect::new(4, 4, 8, 8));
242 c.clip_to(Rect::new(0, 0, 16, 16));
243 assert_eq!(c.clip(), Rect::new(4, 4, 8, 8));
244 }
245
246 #[test]
247 fn disjoint_clip_is_empty_not_negative() {
248 let mut t = TestCanvas::new(16, 16);
249 {
250 let mut c = t.canvas();
251 c.clip_to(Rect::new(0, 0, 4, 4));
252 c.clip_to(Rect::new(8, 8, 4, 4));
253 assert!(c.is_clipped_out());
254 c.clear(Color::WHITE);
255 }
256 assert!(t.pixels().iter().all(|&p| p == 0));
257 }
258
259 #[test]
260 fn nested_clip_borrow_restores_the_parent() {
261 let mut t = TestCanvas::new(16, 16);
262 let mut c = t.canvas();
263 {
264 let mut child = c.with_clip(Rect::new(0, 0, 4, 4));
265 child.clear(Color::WHITE);
266 }
267 assert_eq!(c.clip(), Rect::new(0, 0, 16, 16));
268 }
269
270 #[test]
271 fn clear_respects_the_clip_and_the_stride() {
272 let mut t = TestCanvas::with_stride(10, 4, 16);
273 {
274 let mut c = t.canvas();
275 c.clip_to(Rect::new(2, 1, 4, 2));
276 c.clear(Color::WHITE);
277 }
278 for (y, row) in t.pixels().chunks(16).enumerate() {
279 for (x, &px) in row.iter().enumerate() {
280 let inside = (2..6).contains(&x) && (1..3).contains(&y);
281 assert_eq!(px == 0xFFFF_FFFF, inside, "at {x},{y}");
282 }
283 }
284 }
285
286 #[test]
287 fn copy_from_moves_only_the_listed_regions() {
288 let mut source = TestCanvas::new(8, 8);
289 source.canvas().clear(Color::from_argb8888(0xFFAA_BBCC));
290
291 let mut t = TestCanvas::new(8, 8);
292 t.canvas()
293 .copy_from(&source.view(), &[Rect::new(2, 2, 3, 3)]);
294
295 for y in 0..8 {
296 for x in 0..8 {
297 let inside = (2..5).contains(&x) && (2..5).contains(&y);
298 let expected = if inside { 0xFFAA_BBCC } else { 0 };
299 assert_eq!(t.pixels()[y * 8 + x], expected, "at {x},{y}");
300 }
301 }
302 }
303
304 #[test]
305 fn copy_from_clips_to_the_smaller_buffer() {
306 let mut source = TestCanvas::new(4, 4);
307 source.canvas().clear(Color::from_argb8888(0xFFAA_BBCC));
308
309 let mut t = TestCanvas::new(8, 8);
310 t.canvas()
311 .copy_from(&source.view(), &[Rect::new(0, 0, 8, 8)]);
312
313 assert_eq!(t.pixels()[0], 0xFFAA_BBCC);
314 assert_eq!(t.pixels()[3], 0xFFAA_BBCC);
315 assert_eq!(t.pixels()[4], 0);
316 assert_eq!(t.pixels()[4 * 8], 0);
317 }
318
319 #[test]
320 fn pixel_view_rejects_undersized_slices() {
321 let pixels = [0u32; 10];
322 assert!(PixelView::new(&pixels, Size::new(4, 4), 4).is_none());
323 assert!(PixelView::new(&pixels, Size::new(4, 2), 2).is_none());
324 }
325}