linebender / resvg

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

Root transform matrix not applied #717

Closed 50U10FCA7 closed 9 months ago

50U10FCA7 commented 9 months ago

Description

Transform matrix of a root element is not applied since 0.38 version. It's possible to pass it manually to transform argument of resvg::render, but the reason of this change is not clear for me.

Examples

Original file:

test

Crop and scale in 0.37 ver:

use std::{cell, fs};

use resvg::{
    tiny_skia,
    usvg::{self, TreeParsing as _},
};

fn root_transform(tree: &mut usvg::Tree) -> cell::RefMut<'_, tiny_skia::Transform> {
    cell::RefMut::map(tree.root.borrow_mut(), |r| match r {
        usvg::NodeKind::Group(g) => &mut g.transform,
        _ => panic!("Expected `Tree::root` to be a `Group`"),
    })
}

fn crop(tree: &mut usvg::Tree, angle: f32, rect: usvg::NonZeroRect) {
    let x_center = tree.view_box.rect.x() + tree.view_box.rect.width() / 2.0;
    let y_center = tree.view_box.rect.y() + tree.view_box.rect.height() / 2.0;

    let transform = root_transform(tree)
        .post_rotate_at(angle, x_center, y_center)
        .post_translate(-rect.x(), -rect.y());
    *root_transform(tree) = transform;

    // PANIC: Unwrapping is OK here, because `rect` size is guaranteed to be
    //        non-zero and positive.
    let size = usvg::Size::from_wh(rect.width(), rect.height()).unwrap();
    tree.view_box.rect = size.to_non_zero_rect(0.0, 0.0);
    tree.size = size;
}

fn scale(tree: &mut usvg::Tree, mut size: usvg::Size, aspect_ratio: usvg::AspectRatio) {
    assert!(size.width() > tree.size.width() && size.height() > tree.size.height());

    // `scale::Mode::Contain` should not retain the provided `size`.
    if matches!(
        aspect_ratio,
        usvg::AspectRatio {
            defer: false,
            align: usvg::Align::XMidYMid,
            slice: false,
        }
    ) {
        let scale_factor =
            (size.width() / tree.size.width()).min(size.height() / tree.size.height());

        // PANIC: Unwrapping is OK here, because `size` is always smaller
        //        after `scale_factor` is applied.
        size = usvg::Size::from_wh(
            tree.size.width() * scale_factor,
            tree.size.height() * scale_factor,
        )
        .unwrap();
    }

    let transform = usvg::utils::view_box_to_transform(tree.view_box.rect, aspect_ratio, size)
        .pre_concat(*root_transform(tree));
    *root_transform(tree) = transform;

    tree.view_box.rect = size.to_non_zero_rect(0.0, 0.0);
    tree.size = size;
}

fn main() {
    let data = fs::read("test.svg").unwrap();
    let mut tree = usvg::Tree::from_data(&data, &usvg::Options::default()).unwrap();

    // Apply some operations to the tree.
    let rect = tiny_skia::NonZeroRect::from_xywh(
        tree.size.width() / 4.0,
        0.0,
        tree.size.width() / 2.0,
        tree.size.height(),
    )
    .unwrap();
    crop(&mut tree, 0.0, rect);
    scale(
        &mut tree,
        usvg::Size::from_wh(512.0, 512.0).unwrap(),
        usvg::AspectRatio::default(),
    );

    let renderer = resvg::Tree::from_usvg(&tree);

    let size = renderer.size.to_int_size();
    let mut pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).unwrap();

    renderer.render(tiny_skia::Transform::default(), &mut pixmap.as_mut());

    pixmap.save_png("test.png").unwrap();
}

The result:

test

Crop and scale in 0.39 ver:

use std::fs;

use resvg::{tiny_skia, usvg};

fn crop(tree: &mut usvg::Tree, angle: f32, rect: usvg::NonZeroRect) {
    let x_center = tree.view_box.rect.x() + tree.view_box.rect.width() / 2.0;
    let y_center = tree.view_box.rect.y() + tree.view_box.rect.height() / 2.0;

    tree.root.transform = tree
        .root
        .transform
        .post_rotate_at(angle, x_center, y_center)
        .post_translate(-rect.x(), -rect.y());

    // PANIC: Unwrapping is OK here, because `rect` size is guaranteed to be
    //        non-zero and positive.
    let size = usvg::Size::from_wh(rect.width(), rect.height()).unwrap();
    tree.view_box.rect = size.to_non_zero_rect(0.0, 0.0);
    tree.size = size;
}

fn scale(tree: &mut usvg::Tree, mut size: usvg::Size, aspect_ratio: usvg::AspectRatio) {
    assert!(size.width() > tree.size.width() && size.height() > tree.size.height());

    // `scale::Mode::Contain` should not retain the provided `size`.
    if matches!(
        aspect_ratio,
        usvg::AspectRatio {
            defer: false,
            align: usvg::Align::XMidYMid,
            slice: false,
        }
    ) {
        let scale_factor =
            (size.width() / tree.size.width()).min(size.height() / tree.size.height());

        // PANIC: Unwrapping is OK here, because `size` is always smaller
        //        after `scale_factor` is applied.
        size = usvg::Size::from_wh(
            tree.size.width() * scale_factor,
            tree.size.height() * scale_factor,
        )
        .unwrap();
    }

    tree.root.transform = tree
        .root
        .transform
        .post_concat(usvg::utils::view_box_to_transform(
            tree.view_box.rect,
            aspect_ratio,
            size,
        ));
    tree.view_box.rect = size.to_non_zero_rect(0.0, 0.0);
    tree.size = size;
}

fn main() {
    let data = fs::read("test.svg").unwrap();
    let mut tree = usvg::Tree::from_data(&data, &usvg::Options::default()).unwrap();

    // Apply some operations to the tree.
    let rect = tiny_skia::NonZeroRect::from_xywh(
        tree.size.width() / 4.0,
        0.0,
        tree.size.width() / 2.0,
        tree.size.height(),
    )
    .unwrap();
    crop(&mut tree, 0.0, rect);
    scale(
        &mut tree,
        usvg::Size::from_wh(512.0, 512.0).unwrap(),
        usvg::AspectRatio::default(),
    );

    tree.postprocess(usvg::PostProcessingSteps::default());

    let size = tree.size.to_int_size();
    let mut pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).unwrap();

    resvg::render(&tree, tiny_skia::Transform::default(), &mut pixmap.as_mut());

    pixmap.save_png("test.png").unwrap();
}

The result: test

RazrFalcon commented 9 months ago

tree.root.transform

This transform is ignored. Transform on root elements of subroots do not impact rendering. Weird API, I know. mask.root.transform would not work either, and so on.

You have to pass a transform to resvg::render. The master branch of resvg would not allow you to modify the tree anyway.

50U10FCA7 commented 9 months ago

@RazrFalcon Thanks for reply.

You have to pass a transform to resvg::render. The master branch of resvg would not allow you to modify the tree anyway.

Is it still possible to reassemble the tree in the current master? At least size and view_box should be able to change to render cropped/scaled images as mentioned in examples.

RazrFalcon commented 9 months ago

No, you can't modify the tree anymore. But a crop still can be implemented by passing a smaller pixmap.

50U10FCA7 commented 9 months ago

So, because such behavior is correct and expected, closing this issue as solved.