Skip to content

Commit

Permalink
Gradients and patterns are always in userSpaceOnUse units now.
Browse files Browse the repository at this point in the history
Closes #700
  • Loading branch information
RazrFalcon committed Feb 11, 2024
1 parent 6af096e commit e042024
Show file tree
Hide file tree
Showing 16 changed files with 288 additions and 252 deletions.
9 changes: 6 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ This changelog also contains important changes in dependencies.
- All `usvg::Tree` parsing methods require the `fontdb` argument now.
- All `defs` children like gradients, patterns, clipPaths, masks and filters are guarantee
to have a unique, non-empty ID.
- `usvg::ClipPath`, `usvg::Mask`, `usvg::Filter` are always in `userSpaceOnUse` units now.
- All `defs` children like gradients, patterns, clipPaths, masks and filters are guarantee
to have `userSpaceOnUse` units now. No `objectBoundingBox` units anymore.
- `usvg::Mask` is allowed to have no children now.
- Text nodes will not be parsed when the `text` build feature isn't enabled.
- `usvg::Tree::clip_paths`, `usvg::Tree::masks`, `usvg::Tree::filters` returns
Expand All @@ -44,8 +45,10 @@ This changelog also contains important changes in dependencies.
### Removed
- `usvg::Tree::postprocess()` and `usvg::PostProcessingSteps`. No longer needed.
- `usvg::ClipPath::units()`, `usvg::Mask::units()`, `usvg::Mask::content_units()`,
`usvg::Filter::units()`, `usvg::Filter::content_units()`.
They are always `userSpaceOnUse` now.
`usvg::Filter::units()`, `usvg::Filter::content_units()`, `usvg::LinearGradient::units()`,
`usvg::RadialGradient::units()`, `usvg::Pattern::units()`, `usvg::Pattern::content_units()`
and `usvg::Paint::units()`. They are always `userSpaceOnUse` now.
- `usvg::Units`. No longer needed.

### Fixed
- Text bounding box is accounted during SVG size resolving.
Expand Down
6 changes: 1 addition & 5 deletions crates/resvg/src/clip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ fn draw_children(
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) {
// The actual value doesn't matter, because it will never be used.
// Clip path children are always filled with just a color.
let object_bbox = tiny_skia::Rect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap();

for child in parent.children() {
match child {
usvg::Node::Path(ref path) => {
Expand All @@ -50,7 +46,7 @@ fn draw_children(
max_bbox: tiny_skia::IntRect::from_xywh(0, 0, 1, 1).unwrap(),
};

crate::path::fill_path(path, mode, &ctx, object_bbox, transform, pixmap);
crate::path::fill_path(path, mode, &ctx, transform, pixmap);
}
usvg::Node::Text(ref text) => {
draw_children(text.flattened(), mode, transform, pixmap);
Expand Down
2 changes: 1 addition & 1 deletion crates/resvg/src/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,7 @@ fn apply_image(
.unwrap(),
};

crate::render::render_nodes(node, &ctx, transform, None, &mut pixmap.as_mut());
crate::render::render_nodes(node, &ctx, transform, &mut pixmap.as_mut());
}
}

Expand Down
5 changes: 2 additions & 3 deletions crates/resvg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub fn render(
let root_transform = transform.pre_concat(ts);

let ctx = render::Context { max_bbox };
render::render_nodes(tree.root(), &ctx, root_transform, None, pixmap);
render::render_nodes(tree.root(), &ctx, root_transform, pixmap);
}

/// Renders a node onto the pixmap.
Expand Down Expand Up @@ -87,8 +87,7 @@ pub fn render_node(
transform = transform.pre_translate(-bbox.x(), -bbox.y());

let ctx = render::Context { max_bbox };
// TODO: what to do with `text_bbox`?
render::render_node(node, &ctx, transform, None, pixmap);
render::render_node(node, &ctx, transform, pixmap);

Some(())
}
Expand Down
2 changes: 1 addition & 1 deletion crates/resvg/src/mask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub fn apply(
transform,
);

crate::render::render_nodes(mask.root(), ctx, transform, None, &mut mask_pixmap.as_mut());
crate::render::render_nodes(mask.root(), ctx, transform, &mut mask_pixmap.as_mut());

mask_pixmap.apply_mask(&alpha_mask);
}
Expand Down
88 changes: 18 additions & 70 deletions crates/resvg/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,31 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use crate::render::Context;
use crate::OptionLog;

pub fn render(
path: &usvg::Path,
blend_mode: tiny_skia::BlendMode,
ctx: &Context,
text_bbox: Option<tiny_skia::Rect>,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) {
if path.visibility() != usvg::Visibility::Visible {
return;
}

let mut object_bbox = path.bounding_box();
if let Some(text_bbox) = text_bbox {
object_bbox = text_bbox;
}

if path.paint_order() == usvg::PaintOrder::FillAndStroke {
fill_path(path, blend_mode, ctx, object_bbox, transform, pixmap);
stroke_path(path, blend_mode, ctx, object_bbox, transform, pixmap);
fill_path(path, blend_mode, ctx, transform, pixmap);
stroke_path(path, blend_mode, ctx, transform, pixmap);
} else {
stroke_path(path, blend_mode, ctx, object_bbox, transform, pixmap);
fill_path(path, blend_mode, ctx, object_bbox, transform, pixmap);
stroke_path(path, blend_mode, ctx, transform, pixmap);
fill_path(path, blend_mode, ctx, transform, pixmap);
}
}

pub fn fill_path(
path: &usvg::Path,
blend_mode: tiny_skia::BlendMode,
ctx: &Context,
object_bbox: tiny_skia::Rect,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) -> Option<()> {
Expand All @@ -46,8 +38,6 @@ pub fn fill_path(
return None;
}

let object_bbox = object_bbox.to_non_zero_rect();

let rule = match fill.rule() {
usvg::FillRule::NonZero => tiny_skia::FillRule::Winding,
usvg::FillRule::EvenOdd => tiny_skia::FillRule::EvenOdd,
Expand All @@ -60,13 +50,13 @@ pub fn fill_path(
paint.set_color_rgba8(c.red, c.green, c.blue, fill.opacity().to_u8());
}
usvg::Paint::LinearGradient(ref lg) => {
paint.shader = convert_linear_gradient(lg, fill.opacity(), object_bbox)?;
paint.shader = convert_linear_gradient(lg, fill.opacity())?;
}
usvg::Paint::RadialGradient(ref rg) => {
paint.shader = convert_radial_gradient(rg, fill.opacity(), object_bbox)?;
paint.shader = convert_radial_gradient(rg, fill.opacity())?;
}
usvg::Paint::Pattern(ref pattern) => {
let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform, object_bbox)?;
let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?;

pattern_pixmap = patt_pix;
paint.shader = tiny_skia::Pattern::new(
Expand All @@ -90,27 +80,24 @@ fn stroke_path(
path: &usvg::Path,
blend_mode: tiny_skia::BlendMode,
ctx: &Context,
object_bbox: tiny_skia::Rect,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) -> Option<()> {
let stroke = path.stroke()?;
let object_bbox = object_bbox.to_non_zero_rect();

let pattern_pixmap;
let mut paint = tiny_skia::Paint::default();
match stroke.paint() {
usvg::Paint::Color(c) => {
paint.set_color_rgba8(c.red, c.green, c.blue, stroke.opacity().to_u8());
}
usvg::Paint::LinearGradient(ref lg) => {
paint.shader = convert_linear_gradient(lg, stroke.opacity(), object_bbox)?;
paint.shader = convert_linear_gradient(lg, stroke.opacity())?;
}
usvg::Paint::RadialGradient(ref rg) => {
paint.shader = convert_radial_gradient(rg, stroke.opacity(), object_bbox)?;
paint.shader = convert_radial_gradient(rg, stroke.opacity())?;
}
usvg::Paint::Pattern(ref pattern) => {
let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform, object_bbox)?;
let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?;

pattern_pixmap = patt_pix;
paint.shader = tiny_skia::Pattern::new(
Expand All @@ -133,16 +120,15 @@ fn stroke_path(
fn convert_linear_gradient(
gradient: &usvg::LinearGradient,
opacity: usvg::Opacity,
object_bbox: Option<tiny_skia::NonZeroRect>,
) -> Option<tiny_skia::Shader> {
let (mode, transform, points) = convert_base_gradient(gradient, opacity, object_bbox)?;
let (mode, points) = convert_base_gradient(gradient, opacity)?;

let shader = tiny_skia::LinearGradient::new(
(gradient.x1(), gradient.y1()).into(),
(gradient.x2(), gradient.y2()).into(),
points,
mode,
transform,
gradient.transform(),
)?;

Some(shader)
Expand All @@ -151,17 +137,16 @@ fn convert_linear_gradient(
fn convert_radial_gradient(
gradient: &usvg::RadialGradient,
opacity: usvg::Opacity,
object_bbox: Option<tiny_skia::NonZeroRect>,
) -> Option<tiny_skia::Shader> {
let (mode, transform, points) = convert_base_gradient(gradient, opacity, object_bbox)?;
let (mode, points) = convert_base_gradient(gradient, opacity)?;

let shader = tiny_skia::RadialGradient::new(
(gradient.fx(), gradient.fy()).into(),
(gradient.cx(), gradient.cy()).into(),
gradient.r().get(),
points,
mode,
transform,
gradient.transform(),
)?;

Some(shader)
Expand All @@ -170,27 +155,13 @@ fn convert_radial_gradient(
fn convert_base_gradient(
gradient: &usvg::BaseGradient,
opacity: usvg::Opacity,
object_bbox: Option<tiny_skia::NonZeroRect>,
) -> Option<(
tiny_skia::SpreadMode,
tiny_skia::Transform,
Vec<tiny_skia::GradientStop>,
)> {
) -> Option<(tiny_skia::SpreadMode, Vec<tiny_skia::GradientStop>)> {
let mode = match gradient.spread_method() {
usvg::SpreadMethod::Pad => tiny_skia::SpreadMode::Pad,
usvg::SpreadMethod::Reflect => tiny_skia::SpreadMode::Reflect,
usvg::SpreadMethod::Repeat => tiny_skia::SpreadMode::Repeat,
};

let transform = if gradient.units() == usvg::Units::ObjectBoundingBox {
let bbox =
object_bbox.log_none(|| log::warn!("Gradient on zero-sized shapes is not allowed."))?;
let ts = tiny_skia::Transform::from_bbox(bbox);
ts.pre_concat(gradient.transform())
} else {
gradient.transform()
};

let mut points = Vec::with_capacity(gradient.stops().len());
for stop in gradient.stops() {
let alpha = stop.opacity() * opacity;
Expand All @@ -203,41 +174,20 @@ fn convert_base_gradient(
points.push(tiny_skia::GradientStop::new(stop.offset().get(), color))
}

Some((mode, transform, points))
Some((mode, points))
}

fn render_pattern_pixmap(
pattern: &usvg::Pattern,
ctx: &Context,
transform: tiny_skia::Transform,
object_bbox: Option<tiny_skia::NonZeroRect>,
) -> Option<(tiny_skia::Pixmap, tiny_skia::Transform)> {
let content_transform = if pattern.content_units() == usvg::Units::ObjectBoundingBox
&& pattern.view_box().is_none()
{
let bbox =
object_bbox.log_none(|| log::warn!("Pattern on zero-sized shapes is not allowed."))?;

// No need to shift patterns.
tiny_skia::Transform::from_scale(bbox.width(), bbox.height())
} else {
tiny_skia::Transform::default()
};

let rect = if pattern.units() == usvg::Units::ObjectBoundingBox {
let bbox =
object_bbox.log_none(|| log::warn!("Pattern on zero-sized shapes is not allowed."))?;

pattern.rect().bbox_transform(bbox)
} else {
pattern.rect()
};

let (sx, sy) = {
let ts2 = transform.pre_concat(pattern.transform());
ts2.get_scale()
};

let rect = pattern.rect();
let img_size = tiny_skia::IntSize::from_wh(
(rect.width() * sx).round() as u32,
(rect.height() * sy).round() as u32,
Expand All @@ -250,9 +200,7 @@ fn render_pattern_pixmap(
transform = transform.pre_concat(ts);
}

transform = transform.pre_concat(content_transform);

crate::render::render_nodes(pattern.root(), ctx, transform, None, &mut pixmap.as_mut());
crate::render::render_nodes(pattern.root(), ctx, transform, &mut pixmap.as_mut());

let mut ts = tiny_skia::Transform::default();
ts = ts.pre_concat(pattern.transform());
Expand Down
20 changes: 5 additions & 15 deletions crates/resvg/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,28 @@ pub fn render_nodes(
parent: &usvg::Group,
ctx: &Context,
transform: tiny_skia::Transform,
text_bbox: Option<tiny_skia::Rect>,
pixmap: &mut tiny_skia::PixmapMut,
) {
for node in parent.children() {
render_node(node, ctx, transform, text_bbox, pixmap);
render_node(node, ctx, transform, pixmap);
}
}

pub fn render_node(
node: &usvg::Node,
ctx: &Context,
transform: tiny_skia::Transform,
text_bbox: Option<tiny_skia::Rect>,
pixmap: &mut tiny_skia::PixmapMut,
) {
match node {
usvg::Node::Group(ref group) => {
render_group(group, ctx, transform, text_bbox, pixmap);
render_group(group, ctx, transform, pixmap);
}
usvg::Node::Path(ref path) => {
crate::path::render(
path,
tiny_skia::BlendMode::SourceOver,
ctx,
text_bbox,
transform,
pixmap,
);
Expand All @@ -45,13 +42,7 @@ pub fn render_node(
crate::image::render(image, transform, pixmap);
}
usvg::Node::Text(ref text) => {
render_group(
text.flattened(),
ctx,
transform,
Some(text.bounding_box()),
pixmap,
);
render_group(text.flattened(), ctx, transform, pixmap);
}
}
}
Expand All @@ -60,13 +51,12 @@ fn render_group(
group: &usvg::Group,
ctx: &Context,
transform: tiny_skia::Transform,
text_bbox: Option<tiny_skia::Rect>,
pixmap: &mut tiny_skia::PixmapMut,
) -> Option<()> {
let transform = transform.pre_concat(group.transform());

if !group.should_isolate() {
render_nodes(group, ctx, transform, text_bbox, pixmap);
render_nodes(group, ctx, transform, pixmap);
return Some(());
}

Expand Down Expand Up @@ -114,7 +104,7 @@ fn render_group(
let mut sub_pixmap = tiny_skia::Pixmap::new(ibbox.width(), ibbox.height())
.log_none(|| log::warn!("Failed to allocate a group layer for: {:?}.", ibbox))?;

render_nodes(group, ctx, transform, text_bbox, &mut sub_pixmap.as_mut());
render_nodes(group, ctx, transform, &mut sub_pixmap.as_mut());

if !group.filters().is_empty() {
for filter in group.filters() {
Expand Down

0 comments on commit e042024

Please sign in to comment.