Skip to content

Commit

Permalink
Use layer bbox during individual nodes rendering.
Browse files Browse the repository at this point in the history
  • Loading branch information
RazrFalcon committed Feb 11, 2024
1 parent 1961564 commit 6af096e
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 18 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ This changelog also contains important changes in dependencies.
- `usvg::ImageHrefDataResolverFn` and `usvg::ImageHrefStringResolverFn`
require `fontdb::Database` argument.
- All shared nodes are stored in `Arc` and not `Rc<RefCell>` now.
- `resvg::render_node` now includes filters bounding box. Meaning that a node with a blur filter
no longer be clipped.

### Removed
- `usvg::Tree::postprocess()` and `usvg::PostProcessingSteps`. No longer needed.
Expand Down
8 changes: 2 additions & 6 deletions crates/resvg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ mod render;
/// `transform` will be used as a root transform.
/// Can be used to position SVG inside the `pixmap`.
///
/// Text nodes should be already converted into paths using
/// [`usvg::TreeTextToPath::convert_text`].
///
/// The produced content is in the sRGB color space.
pub fn render(
tree: &usvg::Tree,
Expand Down Expand Up @@ -66,8 +63,7 @@ pub fn render(
/// `transform` will be used as a root transform.
/// Can be used to position SVG inside the `pixmap`.
///
/// Text nodes should be already converted into paths using
/// [`usvg::TreeTextToPath::convert_text`].
/// The expected pixmap size can be retrieved from `usvg::Node::abs_layer_bounding_box()`.
///
/// Returns `None` when `node` has a zero size.
///
Expand All @@ -77,7 +73,7 @@ pub fn render_node(
mut transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) -> Option<()> {
let bbox = node.abs_bounding_box().to_non_zero_rect()?;
let bbox = node.abs_layer_bounding_box()?;

let target_size = tiny_skia::IntSize::from_wh(pixmap.width(), pixmap.height()).unwrap();
let max_bbox = tiny_skia::IntRect::from_xywh(
Expand Down
9 changes: 6 additions & 3 deletions crates/resvg/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,11 @@ fn query_all_impl(parent: &usvg::Group) -> usize {
(v * 1000.0).round() / 1000.0
}

let bbox = node.abs_bounding_box();
let bbox = node
.abs_layer_bounding_box()
.map(|r| r.to_rect())
.unwrap_or(node.abs_bounding_box());

println!(
"{},{},{},{},{}",
node.id(),
Expand Down Expand Up @@ -652,8 +656,7 @@ fn render_svg(args: &Args, tree: &usvg::Tree) -> Result<tiny_skia::Pixmap, Strin
};

let bbox = node
.abs_bounding_box()
.to_non_zero_rect()
.abs_layer_bounding_box()
.ok_or_else(|| "node has zero size".to_string())?;

let size = args
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions crates/resvg/tests/extra/filter-on-empty-group.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions crates/resvg/tests/extra/filter-with-transform-on-shape.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 11 additions & 1 deletion crates/resvg/tests/integration/extra.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{render_extra, render_extra_with_scale};
use crate::{render_extra, render_extra_with_scale, render_node};

#[test]
fn group_with_only_transform() {
Expand Down Expand Up @@ -67,3 +67,13 @@ fn filter_region_precision() {
fn translate_outside_viewbox() {
assert_eq!(render_extra("extra/translate-outside-viewbox"), 0);
}

#[test]
fn render_node_filter_on_empty_group() {
assert_eq!(render_node("extra/filter-on-empty-group", "g1"), 0);
}

#[test]
fn render_node_filter_with_transform_on_shape() {
assert_eq!(render_node("extra/filter-with-transform-on-shape", "g1"), 0);
}
45 changes: 45 additions & 0 deletions crates/resvg/tests/integration/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,51 @@ pub fn render_extra(name: &str) -> usize {
render_extra_with_scale(name, 1.0)
}

pub fn render_node(name: &str, id: &str) -> usize {
let svg_path = format!("tests/{}.svg", name);
let png_path = format!("tests/{}.png", name);

let opt = usvg::Options::default();

let tree = {
let svg_data = std::fs::read(&svg_path).unwrap();
let db = GLOBAL_FONTDB.lock().unwrap();
usvg::Tree::from_data(&svg_data, &opt, &db).unwrap()
};

let node = tree.node_by_id(id).unwrap();
let size = node.abs_layer_bounding_box().unwrap().size().to_int_size();
let mut pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).unwrap();
resvg::render_node(node, tiny_skia::Transform::identity(), &mut pixmap.as_mut());

// pixmap.save_png(&format!("tests/{}.png", name)).unwrap();

let mut rgba = pixmap.take();
demultiply_alpha(rgba.as_mut_slice().as_rgba_mut());

let expected_data = load_png(&png_path);
assert_eq!(expected_data.len(), rgba.len());

let mut pixels_d = 0;
for (a, b) in expected_data
.as_slice()
.as_rgba()
.iter()
.zip(rgba.as_rgba())
{
if is_pix_diff(*a, *b) {
pixels_d += 1;
}
}

// Save diff if needed.
// if pixels_d != 0 {
// gen_diff(&name, &expected_data, rgba.as_slice()).unwrap();
// }

pixels_d
}

fn load_png(path: &str) -> Vec<u8> {
let data = std::fs::read(path).unwrap();
let mut decoder = png::Decoder::new(data.as_slice());
Expand Down
1 change: 1 addition & 0 deletions crates/usvg/src/parser/converter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@ pub(crate) fn convert_group(
stroke_bounding_box: dummy,
abs_stroke_bounding_box: dummy,
layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
abs_layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
children: Vec::new(),
};
collect_children(cache, &mut g);
Expand Down
33 changes: 25 additions & 8 deletions crates/usvg/src/tree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -908,8 +908,6 @@ impl Node {
}

/// Returns node's bounding box in object coordinates, if any.
///
/// This method is cheap since bounding boxes are already calculated.
pub fn bounding_box(&self) -> Rect {
match self {
Node::Group(ref group) => group.bounding_box(),
Expand All @@ -920,8 +918,6 @@ impl Node {
}

/// Returns node's bounding box in canvas coordinates, if any.
///
/// This method is cheap since bounding boxes are already calculated.
pub fn abs_bounding_box(&self) -> Rect {
match self {
Node::Group(ref group) => group.abs_bounding_box(),
Expand All @@ -932,8 +928,6 @@ impl Node {
}

/// Returns node's bounding box, including stroke, in object coordinates, if any.
///
/// This method is cheap since bounding boxes are already calculated.
pub fn stroke_bounding_box(&self) -> Rect {
match self {
Node::Group(ref group) => group.stroke_bounding_box(),
Expand All @@ -945,8 +939,6 @@ impl Node {
}

/// Returns node's bounding box, including stroke, in canvas coordinates, if any.
///
/// This method is cheap since bounding boxes are already calculated.
pub fn abs_stroke_bounding_box(&self) -> Rect {
match self {
Node::Group(ref group) => group.abs_stroke_bounding_box(),
Expand All @@ -957,6 +949,22 @@ impl Node {
}
}

/// Element's "layer" bounding box in canvas units, if any.
///
/// For most nodes this is just `abs_bounding_box`,
/// but for groups this is `abs_layer_bounding_box`.
///
/// See [`Group::layer_bounding_box`] for details.
pub fn abs_layer_bounding_box(&self) -> Option<NonZeroRect> {
match self {
Node::Group(ref group) => Some(group.abs_layer_bounding_box()),
// Hor/ver path without stroke can return None. This is expected.
Node::Path(ref path) => path.abs_bounding_box().to_non_zero_rect(),
Node::Image(ref image) => image.abs_bounding_box().to_non_zero_rect(),
Node::Text(ref text) => text.abs_bounding_box().to_non_zero_rect(),
}
}

/// Calls a closure for each subroot this `Node` has.
///
/// The [`Tree::root`](Tree::root) field contain only render-able SVG elements.
Expand Down Expand Up @@ -1013,6 +1021,7 @@ pub struct Group {
pub(crate) stroke_bounding_box: Rect,
pub(crate) abs_stroke_bounding_box: Rect,
pub(crate) layer_bounding_box: NonZeroRect,
pub(crate) abs_layer_bounding_box: NonZeroRect,
pub(crate) children: Vec<Node>,
}

Expand All @@ -1034,6 +1043,7 @@ impl Group {
stroke_bounding_box: dummy,
abs_stroke_bounding_box: dummy,
layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
abs_layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
children: Vec::new(),
}
}
Expand Down Expand Up @@ -1146,6 +1156,11 @@ impl Group {
self.layer_bounding_box
}

/// Element's "layer" bounding box in canvas units.
pub fn abs_layer_bounding_box(&self) -> NonZeroRect {
self.abs_layer_bounding_box
}

/// Group's children.
pub fn children(&self) -> &[Node] {
&self.children
Expand Down Expand Up @@ -1848,6 +1863,8 @@ impl Group {
self.layer_bounding_box = layer_bbox.to_non_zero_rect()?;
}

self.abs_layer_bounding_box = self.layer_bounding_box.transform(self.abs_transform)?;

Some(())
}
}

0 comments on commit 6af096e

Please sign in to comment.