use crate::render::Context;
pub fn render(
path: &usvg::Path,
blend_mode: tiny_skia::BlendMode,
ctx: &Context,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) {
if !path.is_visible() {
return;
}
if path.paint_order() == usvg::PaintOrder::FillAndStroke {
fill_path(path, blend_mode, ctx, transform, pixmap);
stroke_path(path, blend_mode, ctx, transform, pixmap);
} else {
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,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) -> Option<()> {
let fill = path.fill()?;
if path.data().bounds().width() == 0.0 || path.data().bounds().height() == 0.0 {
return None;
}
let rule = match fill.rule() {
usvg::FillRule::NonZero => tiny_skia::FillRule::Winding,
usvg::FillRule::EvenOdd => tiny_skia::FillRule::EvenOdd,
};
let pattern_pixmap;
let mut paint = tiny_skia::Paint::default();
match fill.paint() {
usvg::Paint::Color(c) => {
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())?;
}
usvg::Paint::RadialGradient(ref rg) => {
paint.shader = convert_radial_gradient(rg, fill.opacity())?;
}
usvg::Paint::Pattern(ref pattern) => {
let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?;
pattern_pixmap = patt_pix;
paint.shader = tiny_skia::Pattern::new(
pattern_pixmap.as_ref(),
tiny_skia::SpreadMode::Repeat,
tiny_skia::FilterQuality::Bicubic,
fill.opacity().get(),
patt_ts,
)
}
}
paint.anti_alias = path.rendering_mode().use_shape_antialiasing();
paint.blend_mode = blend_mode;
pixmap.fill_path(path.data(), &paint, rule, transform, None);
Some(())
}
fn stroke_path(
path: &usvg::Path,
blend_mode: tiny_skia::BlendMode,
ctx: &Context,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) -> Option<()> {
let stroke = path.stroke()?;
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())?;
}
usvg::Paint::RadialGradient(ref rg) => {
paint.shader = convert_radial_gradient(rg, stroke.opacity())?;
}
usvg::Paint::Pattern(ref pattern) => {
let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?;
pattern_pixmap = patt_pix;
paint.shader = tiny_skia::Pattern::new(
pattern_pixmap.as_ref(),
tiny_skia::SpreadMode::Repeat,
tiny_skia::FilterQuality::Bicubic,
stroke.opacity().get(),
patt_ts,
)
}
}
paint.anti_alias = path.rendering_mode().use_shape_antialiasing();
paint.blend_mode = blend_mode;
pixmap.stroke_path(path.data(), &paint, &stroke.to_tiny_skia(), transform, None);
Some(())
}
fn convert_linear_gradient(
gradient: &usvg::LinearGradient,
opacity: usvg::Opacity,
) -> Option<tiny_skia::Shader> {
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,
gradient.transform(),
)?;
Some(shader)
}
fn convert_radial_gradient(
gradient: &usvg::RadialGradient,
opacity: usvg::Opacity,
) -> Option<tiny_skia::Shader> {
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,
gradient.transform(),
)?;
Some(shader)
}
fn convert_base_gradient(
gradient: &usvg::BaseGradient,
opacity: usvg::Opacity,
) -> 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 mut points = Vec::with_capacity(gradient.stops().len());
for stop in gradient.stops() {
let alpha = stop.opacity() * opacity;
let color = tiny_skia::Color::from_rgba8(
stop.color().red,
stop.color().green,
stop.color().blue,
alpha.to_u8(),
);
points.push(tiny_skia::GradientStop::new(stop.offset().get(), color))
}
Some((mode, points))
}
fn render_pattern_pixmap(
pattern: &usvg::Pattern,
ctx: &Context,
transform: tiny_skia::Transform,
) -> Option<(tiny_skia::Pixmap, tiny_skia::Transform)> {
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,
)?;
let mut pixmap = tiny_skia::Pixmap::new(img_size.width(), img_size.height())?;
let transform = tiny_skia::Transform::from_scale(sx, sy);
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());
ts = ts.pre_translate(rect.x(), rect.y());
ts = ts.pre_scale(1.0 / sx, 1.0 / sy);
Some((pixmap, ts))
}