1use denise::{Point, Rect, Size};
27
28use crate::blend::source_over;
29use crate::canvas::{Canvas, PixelView};
30
31#[inline(always)]
34fn blend_word(dst: &mut u32, src: u32) {
35 match src >> 24 {
36 0 => {}
37 255 => *dst = src,
38 a => *dst = source_over(*dst, src, a),
39 }
40}
41
42#[inline(always)]
45fn nearest(d: i64, src_len: i64, dst_len: i64) -> i64 {
46 (2 * d + 1) * src_len / (2 * dst_len)
47}
48
49impl Canvas<'_> {
50 pub fn blit(&mut self, src: &PixelView<'_>, at: Point) {
57 let Size { width, height } = src.size();
58 let dest = Rect::new(at.x, at.y, width as i32, height as i32);
59 let Some(visible) = self.visible(dest) else {
60 return;
61 };
62 for y in visible.y..visible.bottom() {
63 let Some(srow) = src.row(y - at.y, visible.x - at.x, visible.right() - at.x) else {
64 continue;
65 };
66 let Some(drow) = self.row_span(y, visible.x, visible.right()) else {
67 continue;
68 };
69 for (d, &s) in drow.iter_mut().zip(srow) {
70 blend_word(d, s);
71 }
72 }
73 }
74
75 pub fn blit_scaled(&mut self, src: &PixelView<'_>, dest: Rect) {
84 let Size { width, height } = src.size();
88 let (sw, sh) = (width as i64, height as i64);
89 if dest.width == width as i32 && dest.height == height as i32 {
90 return self.blit(src, Point::new(dest.x, dest.y));
91 }
92 let Some(visible) = self.visible(dest) else {
93 return;
94 };
95 for y in visible.y..visible.bottom() {
96 let sy = nearest((y - dest.y) as i64, sh, dest.height as i64);
97 let Some(srow) = src.row(sy as i32, 0, width as i32) else {
98 continue;
99 };
100 let Some(drow) = self.row_span(y, visible.x, visible.right()) else {
101 continue;
102 };
103 for (i, d) in drow.iter_mut().enumerate() {
104 let dx = (visible.x - dest.x) as i64 + i as i64;
105 let sx = nearest(dx, sw, dest.width as i64);
106 blend_word(d, srow[sx as usize]);
107 }
108 }
109 }
110
111 pub fn blit_rounded(&mut self, src: &PixelView<'_>, dest: Rect, shape: Rect, radius: i32) {
128 use crate::blend::scale_premul;
129 use crate::rounded::{Scan, ceil_px, floor_px};
130
131 let Size { width, height } = src.size();
132 let (sw, sh) = (width as i64, height as i64);
133 let radius = radius.clamp(0, shape.width.min(shape.height) / 2);
134 let Some(painted) = dest.intersect(&shape) else {
135 return;
136 };
137 let Some(visible) = self.visible(painted) else {
138 return;
139 };
140 for y in visible.y..visible.bottom() {
141 let sy = nearest((y - dest.y) as i64, sh, dest.height as i64);
142 let Some(srow) = src.row(sy as i32, 0, width as i32) else {
143 continue;
144 };
145 let scan = (radius > 0).then(|| Scan::new(shape, radius, y));
148 let (solid0, solid1) = match &scan {
149 None => (visible.x, visible.right()),
150 Some(scan) => (ceil_px(scan.max_left()), floor_px(scan.min_right())),
151 };
152 let Some(drow) = self.row_span(y, visible.x, visible.right()) else {
153 continue;
154 };
155 for (i, d) in drow.iter_mut().enumerate() {
156 let x = visible.x + i as i32;
157 let coverage = if (solid0..solid1).contains(&x) {
158 255
159 } else if let Some(scan) = &scan {
160 scan.coverage(x)
161 } else {
162 255
163 };
164 if coverage == 0 {
165 continue;
166 }
167 let sx = nearest((x - dest.x) as i64, sw, dest.width as i64);
168 let s = srow[sx as usize];
169 blend_word(
170 d,
171 if coverage == 255 {
172 s
173 } else {
174 scale_premul(s, coverage)
175 },
176 );
177 }
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use crate::blend::{Paint, premultiply};
186 use crate::testing::TestCanvas;
187 use denise::Color;
188
189 fn coordinate_source(width: u32, height: u32) -> Vec<u32> {
192 (0..height)
193 .flat_map(|y| (0..width).map(move |x| 0xFF00_0000 | (x << 8) | y))
194 .collect()
195 }
196
197 #[test]
198 fn a_blit_lands_exactly_where_it_is_put() {
199 let pixels = coordinate_source(3, 2);
200 let src = PixelView::new(&pixels, Size::new(3, 2), 3).unwrap();
201 let mut t = TestCanvas::new(8, 8);
202 t.canvas().blit(&src, Point::new(2, 3));
203 for y in 0..8 {
204 for x in 0..8 {
205 let inside = (2..5).contains(&x) && (3..5).contains(&y);
206 let expected = if inside {
207 0xFF00_0000 | (((x - 2) as u32) << 8) | (y - 3) as u32
208 } else {
209 0
210 };
211 assert_eq!(t.at(x, y), expected, "at {x},{y}");
212 }
213 }
214 }
215
216 #[test]
217 fn a_blit_is_clipped_at_every_edge() {
218 let pixels = coordinate_source(4, 4);
220 let src = PixelView::new(&pixels, Size::new(4, 4), 4).unwrap();
221 for at in [
222 Point::new(-2, -2),
223 Point::new(6, -2),
224 Point::new(-2, 6),
225 Point::new(6, 6),
226 ] {
227 let mut t = TestCanvas::new(8, 8);
228 t.canvas().blit(&src, at);
229 for y in 0..8i32 {
230 for x in 0..8i32 {
231 let (sx, sy) = (x - at.x, y - at.y);
232 let inside = (0..4).contains(&sx) && (0..4).contains(&sy);
233 let expected = if inside {
234 0xFF00_0000 | ((sx as u32) << 8) | sy as u32
235 } else {
236 0
237 };
238 assert_eq!(t.at(x, y), expected, "at {x},{y} for corner {at:?}");
239 }
240 }
241 }
242 }
243
244 #[test]
245 fn a_blit_respects_the_canvas_clip() {
246 let pixels = vec![0xFFFF_FFFF; 64];
247 let src = PixelView::new(&pixels, Size::new(8, 8), 8).unwrap();
248 let mut t = TestCanvas::new(8, 8);
249 {
250 let mut c = t.canvas();
251 c.clip_to(Rect::new(2, 2, 4, 4));
252 c.blit(&src, Point::new(0, 0));
253 }
254 for y in 0..8 {
255 for x in 0..8 {
256 let inside = (2..6).contains(&x) && (2..6).contains(&y);
257 assert_eq!(t.at(x, y) != 0, inside, "at {x},{y}");
258 }
259 }
260 }
261
262 #[test]
263 fn a_blit_respects_the_stride_padding() {
264 let pixels = vec![0xFFFF_FFFF; 4];
265 let src = PixelView::new(&pixels, Size::new(2, 2), 2).unwrap();
266 let mut t = TestCanvas::with_stride(4, 4, 9);
267 t.canvas().blit(&src, Point::new(2, 0));
268 assert_eq!(t.at(3, 0), 0xFFFF_FFFF);
270 assert_eq!(t.pixels()[4], 0, "padding written");
271 assert_eq!(t.pixels()[8], 0, "padding written");
272 assert_eq!(t.at(3, 1), 0xFFFF_FFFF);
273 }
274
275 #[test]
276 fn alpha_pixels_composite_by_the_blend_rules() {
277 let color = Color::rgba(200, 100, 50, 128);
280 let paint = Paint::new(color);
281 let mut pixels = vec![u32::from_be_bytes([color.a, color.r, color.g, color.b])];
282 premultiply(&mut pixels);
283 assert_eq!(pixels[0], paint.premultiplied());
284
285 let src = PixelView::new(&pixels, Size::new(1, 1), 1).unwrap();
286 let mut t = TestCanvas::new(1, 1);
287 t.canvas().clear(Color::rgb(0, 0, 64));
288 let background = t.at(0, 0);
289 t.canvas().blit(&src, Point::new(0, 0));
290 assert_eq!(
291 t.at(0, 0),
292 source_over(background, paint.premultiplied(), paint.alpha())
293 );
294 }
295
296 #[test]
297 fn transparent_pixels_leave_the_destination_alone() {
298 let pixels = vec![0u32; 4];
299 let src = PixelView::new(&pixels, Size::new(2, 2), 2).unwrap();
300 let mut t = TestCanvas::new(2, 2);
301 t.canvas().clear(Color::rgb(10, 20, 30));
302 let before = t.pixels().to_vec();
303 t.canvas().blit(&src, Point::new(0, 0));
304 assert_eq!(t.pixels(), &before[..]);
305 }
306
307 #[test]
308 fn scaling_to_the_source_size_is_a_plain_blit() {
309 let pixels = coordinate_source(3, 3);
310 let src = PixelView::new(&pixels, Size::new(3, 3), 3).unwrap();
311 let mut plain = TestCanvas::new(8, 8);
312 plain.canvas().blit(&src, Point::new(2, 2));
313 let mut scaled = TestCanvas::new(8, 8);
314 scaled.canvas().blit_scaled(&src, Rect::new(2, 2, 3, 3));
315 assert_eq!(plain.pixels(), scaled.pixels());
316 }
317
318 #[test]
319 fn an_integer_upscale_makes_even_blocks() {
320 let pixels = coordinate_source(2, 2);
321 let src = PixelView::new(&pixels, Size::new(2, 2), 2).unwrap();
322 let mut t = TestCanvas::new(4, 4);
323 t.canvas().blit_scaled(&src, Rect::new(0, 0, 4, 4));
324 for y in 0..4 {
325 for x in 0..4 {
326 let expected = 0xFF00_0000 | (((x / 2) as u32) << 8) | (y / 2) as u32;
327 assert_eq!(t.at(x, y), expected, "at {x},{y}");
328 }
329 }
330 }
331
332 #[test]
333 fn a_downscale_samples_pixel_centres_not_corners() {
334 let pixels = coordinate_source(8, 1);
337 let src = PixelView::new(&pixels, Size::new(8, 1), 8).unwrap();
338 let mut t = TestCanvas::new(4, 1);
339 t.canvas().blit_scaled(&src, Rect::new(0, 0, 4, 1));
340 for x in 0..4 {
341 let expected = 0xFF00_0000 | (((2 * x + 1) as u32) << 8);
342 assert_eq!(t.at(x, 0), expected, "at {x}");
343 }
344 }
345
346 #[test]
347 fn a_clipped_scaled_blit_samples_as_if_unclipped() {
348 let pixels = coordinate_source(5, 5);
351 let src = PixelView::new(&pixels, Size::new(5, 5), 5).unwrap();
352 let dest = Rect::new(-3, -3, 13, 13);
353
354 let mut whole = TestCanvas::new(16, 16);
355 whole.canvas().blit_scaled(&src, Rect::new(5, 5, 13, 13));
356 let mut clipped = TestCanvas::new(8, 8);
357 clipped.canvas().blit_scaled(&src, dest);
358
359 for y in 0..8 {
360 for x in 0..8 {
361 assert_eq!(clipped.at(x, y), whole.at(x + 8, y + 8), "at {x},{y}");
362 }
363 }
364 }
365
366 #[test]
367 fn absurd_rectangles_neither_read_nor_write_out_of_bounds() {
368 let pixels = coordinate_source(4, 4);
369 let src = PixelView::new(&pixels, Size::new(4, 4), 4).unwrap();
370 let mut t = TestCanvas::new(8, 8);
371 for dest in [
372 Rect::new(-1_000_000, -1_000_000, 3_000_000, 3_000_000),
373 Rect::new(i32::MIN / 2, i32::MIN / 2, i32::MAX, i32::MAX),
374 Rect::new(0, 0, i32::MAX, 1),
375 Rect::new(4, 4, 0, 5),
376 Rect::new(4, 4, 5, 0),
377 Rect::new(100, 100, 4, 4),
378 ] {
379 t.canvas().blit_scaled(&src, dest);
380 }
381 let empty = PixelView::new(&pixels, Size::new(4, 4), 4).unwrap();
382 t.canvas()
383 .blit(&empty, Point::new(i32::MAX - 1, i32::MAX - 1));
384 t.canvas().blit(&empty, Point::new(i32::MIN, i32::MIN));
385 }
386
387 #[test]
388 fn a_rounded_blit_of_solid_white_is_a_rounded_fill() {
389 let pixels = vec![0xFFFF_FFFFu32; 32 * 32];
393 let src = PixelView::new(&pixels, Size::new(32, 32), 32).unwrap();
394 let shape = Rect::new(2, 2, 28, 28);
395
396 let mut blitted = TestCanvas::new(32, 32);
397 blitted.canvas().blit_rounded(&src, shape, shape, 8);
398 let mut filled = TestCanvas::new(32, 32);
399 filled.canvas().fill_rounded_rect(shape, 8, Color::WHITE);
400
401 assert_eq!(blitted.pixels(), filled.pixels());
402 }
403
404 #[test]
405 fn a_zero_radius_rounded_blit_is_a_scaled_blit() {
406 let pixels = coordinate_source(5, 5);
407 let src = PixelView::new(&pixels, Size::new(5, 5), 5).unwrap();
408 let dest = Rect::new(1, 1, 10, 10);
409
410 let mut rounded = TestCanvas::new(12, 12);
411 rounded.canvas().blit_rounded(&src, dest, dest, 0);
412 let mut scaled = TestCanvas::new(12, 12);
413 scaled.canvas().blit_scaled(&src, dest);
414
415 assert_eq!(rounded.pixels(), scaled.pixels());
416 }
417
418 #[test]
419 fn a_full_radius_on_a_square_is_the_avatar_circle() {
420 let pixels = vec![0xFFFF_FFFFu32; 16 * 16];
421 let src = PixelView::new(&pixels, Size::new(16, 16), 16).unwrap();
422 let shape = Rect::new(0, 0, 16, 16);
423 let mut t = TestCanvas::new(16, 16);
424 t.canvas().blit_rounded(&src, shape, shape, 999);
425 assert_eq!(t.at(0, 0), 0, "corner outside the circle");
426 assert_eq!(t.at(15, 15), 0, "corner outside the circle");
427 assert_eq!(t.at(8, 8), 0xFFFF_FFFF, "centre inside the circle");
428 assert_eq!(t.at(8, 0) >> 24, 255, "top of the circle touches the edge");
429 }
430
431 #[test]
432 fn the_shape_crops_an_overflowing_dest_the_cover_case() {
433 let pixels = coordinate_source(8, 8);
437 let src = PixelView::new(&pixels, Size::new(8, 8), 8).unwrap();
438 let dest = Rect::new(-4, -4, 16, 16);
439 let shape = Rect::new(2, 2, 4, 4);
440
441 let mut masked = TestCanvas::new(8, 8);
442 masked.canvas().blit_rounded(&src, dest, shape, 0);
443 let mut unmasked = TestCanvas::new(8, 8);
444 unmasked.canvas().blit_scaled(&src, dest);
445
446 for y in 0..8 {
447 for x in 0..8 {
448 let inside = (2..6).contains(&x) && (2..6).contains(&y);
449 let expected = if inside { unmasked.at(x, y) } else { 0 };
450 assert_eq!(masked.at(x, y), expected, "at {x},{y}");
451 }
452 }
453 }
454
455 #[test]
456 fn damage_clipping_rounds_the_image_corners_not_the_damage_rect() {
457 let pixels = vec![0xFFFF_FFFFu32; 24 * 24];
461 let src = PixelView::new(&pixels, Size::new(24, 24), 24).unwrap();
462 let shape = Rect::new(0, 0, 24, 24);
463
464 let mut whole = TestCanvas::new(24, 24);
465 whole.canvas().blit_rounded(&src, shape, shape, 8);
466
467 let mut damaged = TestCanvas::new(24, 24);
468 {
469 let mut c = damaged.canvas();
470 c.clip_to(Rect::new(0, 0, 12, 24));
471 c.blit_rounded(&src, shape, shape, 8);
472 }
473 for y in 0..24 {
474 for x in 0..12 {
475 assert_eq!(damaged.at(x, y), whole.at(x, y), "at {x},{y}");
476 }
477 for x in 12..24 {
478 assert_eq!(damaged.at(x, y), 0, "leaked past the clip at {x},{y}");
479 }
480 }
481 }
482
483 #[test]
484 fn rounded_blits_survive_absurd_rectangles() {
485 let pixels = coordinate_source(4, 4);
486 let src = PixelView::new(&pixels, Size::new(4, 4), 4).unwrap();
487 let mut t = TestCanvas::new(8, 8);
488 for (dest, shape) in [
489 (
490 Rect::new(-1_000_000, -1_000_000, 3_000_000, 3_000_000),
491 Rect::new(0, 0, 8, 8),
492 ),
493 (Rect::new(0, 0, 8, 8), Rect::new(100, 100, 4, 4)),
494 (Rect::new(0, 0, 0, 0), Rect::new(0, 0, 8, 8)),
495 (
496 Rect::new(i32::MIN / 2, i32::MIN / 2, i32::MAX, i32::MAX),
497 Rect::new(2, 2, 4, 4),
498 ),
499 ] {
500 t.canvas().blit_rounded(&src, dest, shape, 3);
501 }
502 }
503
504 #[test]
505 fn premultiply_is_exact_and_paints_agree() {
506 for (r, g, b, a) in [
507 (255, 255, 255, 255),
508 (0xAB, 0xCD, 0xEF, 0),
509 (200, 100, 50, 128),
510 ] {
511 let mut px = [u32::from_be_bytes([a, r, g, b])];
512 premultiply(&mut px);
513 assert_eq!(
514 px[0],
515 Paint::new(Color::rgba(r, g, b, a)).premultiplied(),
516 "rgba({r},{g},{b},{a})"
517 );
518 }
519 }
520}