Skip to content

Commit

Permalink
Add usvg::Node::filters_bounding_box
Browse files Browse the repository at this point in the history
  • Loading branch information
RazrFalcon committed Dec 23, 2023
1 parent 76ca66d commit 39e592a
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 50 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ This changelog also contains important changes in dependencies.
- `usvg::Node::abs_stroke_bounding_box` which returns a precalculated node's bounding box,
including stroke, in canvas coordinates.
- (c-api) `resvg_get_node_stroke_bbox`
- `usvg::Node::filters_bounding_box`.
- `usvg::Node::abs_filters_bounding_box`.

### Changed
- `usvg` no longer uses `rctree` for the nodes tree implementation.
Expand Down
51 changes: 7 additions & 44 deletions crates/resvg/src/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,23 +98,17 @@ pub struct Filter {
pub primitives: Vec<Primitive>,
}

pub fn convert(
ufilters: &[usvg::filter::SharedFilter],
object_bbox: Option<tiny_skia::Rect>,
) -> (Vec<Filter>, Option<tiny_skia::Rect>) {
let object_bbox = object_bbox.and_then(|bbox| bbox.to_non_zero_rect());
pub fn convert(ugroup: &usvg::Group) -> (Vec<Filter>, Option<tiny_skia::Rect>) {
let object_bbox = ugroup.bounding_box.and_then(|bbox| bbox.to_non_zero_rect());

let region = match calc_filters_region(ufilters, object_bbox) {
let region = match ugroup.filters_bounding_box() {
Some(v) => v,
None => return (Vec::new(), None),
};

let mut filters = Vec::new();
for ufilter in ufilters {
let filter = match convert_filter(&ufilter.borrow(), object_bbox, region) {
Some(v) => v,
None => return (Vec::new(), None),
};
for ufilter in &ugroup.filters {
let filter = convert_filter(&ufilter.borrow(), object_bbox, region);
filters.push(filter);
}

Expand All @@ -125,7 +119,7 @@ fn convert_filter(
ufilter: &usvg::filter::Filter,
object_bbox: Option<tiny_skia::NonZeroRect>,
region: tiny_skia::NonZeroRect,
) -> Option<Filter> {
) -> Filter {
let mut primitives = Vec::with_capacity(ufilter.primitives.len());
for uprimitive in &ufilter.primitives {
let subregion = match calc_subregion(ufilter, uprimitive, object_bbox, region) {
Expand All @@ -146,7 +140,7 @@ fn convert_filter(
}
}

Some(Filter { region, primitives })
Filter { region, primitives }
}

fn convert_primitive(
Expand Down Expand Up @@ -639,37 +633,6 @@ fn apply_inner(
}
}

// TODO: merge with mask region logic
fn calc_region(
filter: &usvg::filter::Filter,
object_bbox: Option<tiny_skia::NonZeroRect>,
) -> Option<tiny_skia::NonZeroRect> {
if filter.units == usvg::Units::ObjectBoundingBox {
Some(filter.rect.bbox_transform(object_bbox?))
} else {
Some(filter.rect)
}
}

pub fn calc_filters_region(
filters: &[usvg::filter::SharedFilter],
object_bbox: Option<tiny_skia::NonZeroRect>,
) -> Option<tiny_skia::NonZeroRect> {
let mut global_region = usvg::BBox::default();

for filter in filters {
if let Some(region) = calc_region(&filter.borrow(), object_bbox) {
global_region = global_region.expand(usvg::BBox::from(region));
}
}

if !global_region.is_default() {
global_region.to_non_zero_rect()
} else {
None
}
}

fn calc_subregion(
filter: &usvg::filter::Filter,
primitive: &usvg::filter::Primitive,
Expand Down
4 changes: 2 additions & 2 deletions crates/resvg/src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ fn convert_group(
None => return convert_empty_group(ugroup, children),
};

let (filters, filter_bbox) = crate::filter::convert(&ugroup.filters, ugroup.bounding_box);
let (filters, filter_bbox) = crate::filter::convert(ugroup);

// TODO: figure out a nicer solution
// Ignore groups with filters but invalid filter bboxes.
Expand Down Expand Up @@ -223,7 +223,7 @@ fn convert_empty_group(ugroup: &usvg::Group, children: &mut Vec<Node>) -> Option
return None;
}

let (filters, layer_bbox) = crate::filter::convert(&ugroup.filters, None);
let (filters, layer_bbox) = crate::filter::convert(ugroup);
let layer_bbox = layer_bbox?;

let group = Group {
Expand Down
44 changes: 40 additions & 4 deletions crates/usvg-tree/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,7 @@ pub struct Mask {
/// `mask` in SVG.
pub mask: Option<SharedMask>,

/// Clip path children.
/// Mask children.
pub root: Group,
}

Expand Down Expand Up @@ -971,6 +971,44 @@ impl Group {
self.bounding_box?.transform(self.abs_transform)
}

/// Calculates a node's filter bounding box.
///
/// Filters with `objectBoundingBox` and missing or zero `bounding_box` would be ignored.
///
/// Note that a filter region can act like a clipping rectangle,
/// therefore this function can produce a bounding box smaller than `bounding_box`.
///
/// Returns `None` when then group has no filters.
///
/// This function is very fast, that's why we do not store this bbox as a `Group` field.
pub fn filters_bounding_box(&self) -> Option<NonZeroRect> {
let object_bbox = self.bounding_box.and_then(|bbox| bbox.to_non_zero_rect());

let mut full_region = BBox::default();

for filter in &self.filters {
let mut region = filter.borrow().rect;

if filter.borrow().units == Units::ObjectBoundingBox {
if let Some(object_bbox) = object_bbox {
region = region.bbox_transform(object_bbox);
} else {
// Skip filters with `objectBoundingBox` on nodes without a bbox.
continue;
}
}

full_region = full_region.expand(BBox::from(region));
}

full_region.to_non_zero_rect()
}

/// Calculates a node's filter bounding box in canvas coordinates.
pub fn abs_filters_bounding_box(&self) -> Option<NonZeroRect> {
self.filters_bounding_box()?.transform(self.abs_transform)
}

fn subroots(&self, f: &mut dyn FnMut(&Group)) {
if let Some(ref clip) = self.clip_path {
f(&clip.borrow().root);
Expand Down Expand Up @@ -1270,11 +1308,9 @@ pub struct Tree {
}

impl Tree {
// TODO: remove
/// Returns renderable node by ID.
/// Returns a renderable node by ID.
///
/// If an empty ID is provided, than this method will always return `None`.
/// Even if tree has nodes with empty ID.
pub fn node_by_id(&self, id: &str) -> Option<&Node> {
if id.is_empty() {
return None;
Expand Down

0 comments on commit 39e592a

Please sign in to comment.