Part of #3602: a node thumbnail reports the size it is drawn at

render_node_thumbnail_with draws into a context of size.clamp(1,
MAX_CONTEXT_SIDE) pixels but reported width and height as the requested
size, so a size of 0 gave a 0x0 image holding 4 bytes. The view was
also built from the requested size, and the fit scale (size - 2) /
extent was 0 or negative at sizes 1 and 2, so the art drew nothing or
mirrored.

The thumbnail now reports the side it draws, builds the view from it,
and drops the 1-pixel margin when the side is too small to keep one.

Test: node_thumbnail_reports_the_size_it_draws.
This commit is contained in:
dotCooCoo
2026-10-31 23:29:33 +07:00
committed by bflatastic
parent 403d8bc215
commit 4c32032d34
2 changed files with 20 additions and 3 deletions
+4 -3
View File
@@ -347,9 +347,10 @@ impl Renderer {
self.check_plugins();
self.check_symbols(doc);
let b = brush_fx::cull_bounds(&doc.symbol_boxes(), n)?;
let s = (size as f64 - 2.0) / b.width().max(b.height()).max(1e-6);
let view = Affine::translate((size as f64 / 2.0, size as f64 / 2.0)) * Affine::scale(s) * Affine::translate(-b.center().to_vec2());
let w = size.clamp(1, MAX_CONTEXT_SIDE) as u16;
// A 1-pixel margin on each side, unless the thumbnail is too small to keep one.
let s = (w as f64 - 2.0).max(1.0) / b.width().max(b.height()).max(1e-6);
let view = Affine::translate((w as f64 / 2.0, w as f64 / 2.0)) * Affine::scale(s) * Affine::translate(-b.center().to_vec2());
let mut ctx = single_threaded_context(w, w);
if let Some(bg) = background {
ctx.set_paint(peniko::Color::from_rgba8(bg[0], bg[1], bg[2], bg[3]));
@@ -364,6 +365,6 @@ impl Renderer {
self.draw_forced_arc(&mut ctx, &frame, &Arc::new(n.clone()));
let pm = self.finish(&mut ctx, vello_cpu::RasterizerSettings::default());
self.end_frame();
Some(Rendered { width: size, height: size, pixels: pm.data_as_u8_slice().to_vec() })
Some(Rendered { width: w as u32, height: w as u32, pixels: pm.data_as_u8_slice().to_vec() })
}
}
+16
View File
@@ -869,6 +869,22 @@ fn node_thumbnail_applies_the_root_mask() {
assert_eq!(img.pixel(75, 50), [255, 255, 255, 255]);
}
/// #3602: a thumbnail reports the size it was drawn at, and one too small for its 1-pixel margin
/// still draws the art.
#[test]
fn node_thumbnail_reports_the_size_it_draws() {
let mut d = Document::new(100.0, 100.0);
let node = rect_node(&mut d, Rect::new(10.0, 10.0, 90.0, 90.0), Paint::solid(Color::rgb(1.0, 0.0, 0.0)), Paint::None, 0.0);
let mut r = Renderer::new();
for size in 0..4 {
let img = r.render_node_thumbnail(&d, &node, size, None).unwrap();
assert_eq!(img.pixels.len(), img.width as usize * img.height as usize * 4, "size {size}");
assert_eq!(img.width, size.max(1), "size {size}");
assert_eq!(img.height, size.max(1), "size {size}");
assert!(img.pixels.chunks(4).any(|p| p[3] > 0), "size {size}: the art is drawn");
}
}
#[test]
fn opacity_mask_with_undecodable_image_leaves_out_object() {
use vectorcraft_doc::{Effect, ImageBlob, ImageObject, NodeKind, OpacityMask};