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