linebender / resvg

An SVG rendering library.
Apache License 2.0
2.85k stars 228 forks source link

Unable to draw accurate bounding boxes #724

Closed Tameflame closed 8 months ago

Tameflame commented 8 months ago

I've got an SVG (see attached image below), which has a lot of nested SVGs to emulate layers (I do this because I want to specify width/height and I can't do that with g tags), but the the following draw bboxes function fails to draw the boundary boxes correctly (see attached output below).

Here is my function:


pub fn render_svg_with_bboxes_to_png(input_path: &str, output_path: &str) {
    let zoom = 1.0;
    let mut opt = usvg::Options::default();
    // Get file's absolute directory.
    opt.resources_dir = std::fs::canonicalize(input_path)
        .ok()
        .and_then(|p| p.parent().map(|p| p.to_path_buf()));

    let mut fontdb = fontdb::Database::new();
    fontdb.load_system_fonts();

    let svg_data = std::fs::read(&input_path).unwrap();
    let tree = usvg::Tree::from_data(&svg_data, &opt, &fontdb).unwrap();

    let mut bboxes = Vec::new();
    let mut stroke_bboxes = Vec::new();
    collect_bboxes(tree.root(), &mut bboxes, &mut stroke_bboxes);

    let pixmap_size = tree.size().to_int_size().scale_by(zoom).unwrap();
    let mut pixmap =
        tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height())
            .unwrap();
    let render_ts = tiny_skia::Transform::from_scale(zoom, zoom);
    resvg::render(&tree, render_ts, &mut pixmap.as_mut());

    let mut stroke = tiny_skia::Stroke::default();
    stroke.width = 1.0 / zoom; // prevent stroke scaling as well

    let mut paint1 = tiny_skia::Paint::default();
    paint1.set_color_rgba8(255, 0, 0, 127);

    let mut paint2 = tiny_skia::Paint::default();
    paint2.set_color_rgba8(0, 200, 0, 127);

    let root_ts = tree.view_box().to_transform(tree.size());
    let bbox_ts = render_ts.pre_concat(root_ts);

    for bbox in bboxes {
        let path = tiny_skia::PathBuilder::from_rect(bbox);
        pixmap.stroke_path(&path, &paint1, &stroke, bbox_ts, None);
    }

    for bbox in stroke_bboxes {
        let path = tiny_skia::PathBuilder::from_rect(bbox);
        pixmap.stroke_path(&path, &paint2, &stroke, bbox_ts, None);
    }

    pixmap.save_png(&output_path).unwrap();
}

fn collect_bboxes(
    parent: &usvg::Group,
    bboxes: &mut Vec<usvg::Rect>,
    stroke_bboxes: &mut Vec<usvg::Rect>,
) {
    for node in parent.children() {
        if let usvg::Node::Group(ref group) = node {
            collect_bboxes(group, bboxes, stroke_bboxes);
        }

        let bbox = node.abs_bounding_box();
        bboxes.push(bbox);

        let stroke_bbox = node.abs_stroke_bounding_box();
        if bbox != stroke_bbox {
            stroke_bboxes.push(stroke_bbox);
        }
    }
}

Source svg file:

test

Output png file:

rendered_svg2-bboxes

If the solution is to change my use of nested SVG tags, please advise, I really need them to emulate layers because I want to be able to programmatically modify the SVG. If the issue can be solved by adding attributes to specific tags or something like that please let me know. Thanks

RazrFalcon commented 8 months ago

Well, the image is rendered correctly, therefore SVG was parsed correctly. That's something. I guess something is wrong with usvg API. Will take a look.

RazrFalcon commented 8 months ago

Should be fixed in the next release.

Tameflame commented 8 months ago

Awesome thanks very much!

Tameflame commented 7 months ago

Btw @RazrFalcon, if I wanted to draw the bounding box for a specific node in the tree, is there a way to do that? I basically want to use the bounding boxes as a quick way of getting the intrinsic size of a node on the tree. Can I use nodeID or something?

RazrFalcon commented 7 months ago

You can use usvg::Tree::node_by_id.

Tameflame commented 7 months ago

@RazrFalcon thanks but for some reason I can't get it to fetch even a simple node:

#[test]
pub fn find_text_node_by_id() {
    let svg = r#"
    <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
        <svg id="unique">
            <text x="10" y="10">Hello World</text>
        </svg>
    </svg>
    "#;

    let tree = usvg::Tree::from_str(
        svg,
        &usvg::Options::default(),
        &Default::default(),
    )
    .unwrap();

    println!("{:#?}", tree.node_by_id("unique"));
}

Output:

running 1 test
None
test tests::find_text_node_by_id ... ok
RazrFalcon commented 7 months ago

It's not a simple node. Nested svg elements are very complicated and in this case usvg looses the id. Will fix.

Tameflame commented 7 months ago

Awesome, thanks so much!

RazrFalcon commented 7 months ago

See #734