M6: SVG import: a stretched image stays stretched (#1811)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Trevor Brown
2026-11-01 14:12:18 -05:00
committed by GitHub
co-authored by Claude Fable 5.1
parent 5fc2840d90
commit 83c28cf3ae
2 changed files with 33 additions and 1 deletions
+11 -1
View File
@@ -608,7 +608,17 @@ fn image(cx: &mut Ctx, img: &usvg::Image, id: &str, parent: NodeId, base: Affine
let hash = add_image(cx.doc, bytes, iw, ih);
let mut n = Node::new(cx.doc.alloc_id(), NodeKind::Rectangle, layer_name(id, "Image"), w, h);
n.transform = transform;
n.fills = vec![Paint::image(hash, ImageScaleMode::Fill)];
let mut fill = Paint::image(hash, ImageScaleMode::Fill);
// usvg sizes the box to the picture (`meet` and `slice` are in the transform), so a box of
// another shape is `preserveAspectRatio="none"`: the picture stretched edge to edge. A Fill
// would keep its shape and crop it; a crop of the whole picture stretches it.
if (w * ih - h * iw).abs() > 1e-6 * (w * ih).max(h * iw)
&& let figcraft_color::PaintKind::Image(ip) = &mut fill.kind
{
ip.scale_mode = ImageScaleMode::Crop;
ip.crop = Some(Affine::IDENTITY.as_coeffs());
}
n.fills = vec![fill];
cx.add(n, parent);
}
+22
View File
@@ -129,6 +129,28 @@ fn strokes_scale_with_their_layer() {
assert_eq!((l.width, l.stroke.weight, l.stroke.dashes.clone()), (30.0, 6.0, vec![12.0, 6.0]));
}
/// `preserveAspectRatio="none"` stretches a picture into its box edge to edge. usvg sizes the
/// box to the picture for `meet` and `slice` (the fit is in the transform), so a box of another
/// shape is a stretch: a Fill, which keeps the picture's shape and crops it, would show the
/// middle of the picture instead.
#[test]
fn an_image_stretched_into_its_box_stays_stretched() {
use figcraft_color::{ImageScaleMode, PaintKind};
let (d, f) =
import(&format!(r##"<image id="pic" x="10" y="20" width="40" height="40" preserveAspectRatio="none" href="data:image/png;base64,{PNG}"/>"##));
let n = node(&d, f, "pic");
assert_eq!((n.width, n.height), (40.0, 40.0));
let Some(PaintKind::Image(ip)) = n.fills.first().map(|p| &p.kind) else { panic!("{:?}", n.fills) };
// The 4 × 2 picture's pixels map onto the whole 40 × 40 box: 10 across, 20 down.
let m = figcraft_render::paint::image_transform(ip, 4.0, 2.0, n.width, n.height);
assert_eq!((m.sx, m.ky, m.kx, m.sy, m.tx, m.ty), (10.0, 0.0, 0.0, 20.0, 0.0, 0.0), "{ip:?}");
// With the aspect kept (the default) the box is the picture's own shape and a Fill is exact.
let (d, f) = import(&format!(r##"<image id="pic" x="10" y="20" width="40" height="40" href="data:image/png;base64,{PNG}"/>"##));
let n = node(&d, f, "pic");
assert_eq!((n.width, n.height), (40.0, 20.0));
assert!(matches!(n.fills.first().map(|p| &p.kind), Some(PaintKind::Image(i)) if i.scale_mode == ImageScaleMode::Fill), "{:?}", n.fills);
}
#[test]
fn linked_files_are_never_read() {
let dir = std::env::temp_dir().join("figcraft-svg-linked");