resvg/
path.rs

1// Copyright 2019 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use crate::render::Context;
5
6pub fn render(
7    path: &usvg::Path,
8    blend_mode: tiny_skia::BlendMode,
9    ctx: &Context,
10    transform: tiny_skia::Transform,
11    pixmap: &mut tiny_skia::PixmapMut,
12) {
13    if !path.is_visible() {
14        return;
15    }
16
17    if path.paint_order() == usvg::PaintOrder::FillAndStroke {
18        fill_path(path, blend_mode, ctx, transform, pixmap);
19        stroke_path(path, blend_mode, ctx, transform, pixmap);
20    } else {
21        stroke_path(path, blend_mode, ctx, transform, pixmap);
22        fill_path(path, blend_mode, ctx, transform, pixmap);
23    }
24}
25
26pub fn fill_path(
27    path: &usvg::Path,
28    blend_mode: tiny_skia::BlendMode,
29    ctx: &Context,
30    transform: tiny_skia::Transform,
31    pixmap: &mut tiny_skia::PixmapMut,
32) -> Option<()> {
33    let fill = path.fill()?;
34
35    // Horizontal and vertical lines cannot be filled. Skip.
36    if path.data().bounds().width() == 0.0 || path.data().bounds().height() == 0.0 {
37        return None;
38    }
39
40    let rule = match fill.rule() {
41        usvg::FillRule::NonZero => tiny_skia::FillRule::Winding,
42        usvg::FillRule::EvenOdd => tiny_skia::FillRule::EvenOdd,
43    };
44
45    let pattern_pixmap;
46    let mut paint = tiny_skia::Paint::default();
47    match fill.paint() {
48        usvg::Paint::Color(c) => {
49            paint.set_color_rgba8(c.red, c.green, c.blue, fill.opacity().to_u8());
50        }
51        usvg::Paint::LinearGradient(ref lg) => {
52            paint.shader = convert_linear_gradient(lg, fill.opacity())?;
53        }
54        usvg::Paint::RadialGradient(ref rg) => {
55            paint.shader = convert_radial_gradient(rg, fill.opacity())?;
56        }
57        usvg::Paint::Pattern(ref pattern) => {
58            let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?;
59
60            pattern_pixmap = patt_pix;
61            paint.shader = tiny_skia::Pattern::new(
62                pattern_pixmap.as_ref(),
63                tiny_skia::SpreadMode::Repeat,
64                tiny_skia::FilterQuality::Bicubic,
65                fill.opacity().get(),
66                patt_ts,
67            )
68        }
69    }
70    paint.anti_alias = path.rendering_mode().use_shape_antialiasing();
71    paint.blend_mode = blend_mode;
72
73    pixmap.fill_path(path.data(), &paint, rule, transform, None);
74    Some(())
75}
76
77fn stroke_path(
78    path: &usvg::Path,
79    blend_mode: tiny_skia::BlendMode,
80    ctx: &Context,
81    transform: tiny_skia::Transform,
82    pixmap: &mut tiny_skia::PixmapMut,
83) -> Option<()> {
84    let stroke = path.stroke()?;
85    let pattern_pixmap;
86    let mut paint = tiny_skia::Paint::default();
87    match stroke.paint() {
88        usvg::Paint::Color(c) => {
89            paint.set_color_rgba8(c.red, c.green, c.blue, stroke.opacity().to_u8());
90        }
91        usvg::Paint::LinearGradient(ref lg) => {
92            paint.shader = convert_linear_gradient(lg, stroke.opacity())?;
93        }
94        usvg::Paint::RadialGradient(ref rg) => {
95            paint.shader = convert_radial_gradient(rg, stroke.opacity())?;
96        }
97        usvg::Paint::Pattern(ref pattern) => {
98            let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?;
99
100            pattern_pixmap = patt_pix;
101            paint.shader = tiny_skia::Pattern::new(
102                pattern_pixmap.as_ref(),
103                tiny_skia::SpreadMode::Repeat,
104                tiny_skia::FilterQuality::Bicubic,
105                stroke.opacity().get(),
106                patt_ts,
107            )
108        }
109    }
110    paint.anti_alias = path.rendering_mode().use_shape_antialiasing();
111    paint.blend_mode = blend_mode;
112
113    pixmap.stroke_path(path.data(), &paint, &stroke.to_tiny_skia(), transform, None);
114
115    Some(())
116}
117
118fn convert_linear_gradient(
119    gradient: &usvg::LinearGradient,
120    opacity: usvg::Opacity,
121) -> Option<tiny_skia::Shader> {
122    let (mode, points) = convert_base_gradient(gradient, opacity)?;
123
124    let shader = tiny_skia::LinearGradient::new(
125        (gradient.x1(), gradient.y1()).into(),
126        (gradient.x2(), gradient.y2()).into(),
127        points,
128        mode,
129        gradient.transform(),
130    )?;
131
132    Some(shader)
133}
134
135fn convert_radial_gradient(
136    gradient: &usvg::RadialGradient,
137    opacity: usvg::Opacity,
138) -> Option<tiny_skia::Shader> {
139    let (mode, points) = convert_base_gradient(gradient, opacity)?;
140
141    let shader = tiny_skia::RadialGradient::new(
142        (gradient.fx(), gradient.fy()).into(),
143        (gradient.cx(), gradient.cy()).into(),
144        gradient.r().get(),
145        points,
146        mode,
147        gradient.transform(),
148    )?;
149
150    Some(shader)
151}
152
153fn convert_base_gradient(
154    gradient: &usvg::BaseGradient,
155    opacity: usvg::Opacity,
156) -> Option<(tiny_skia::SpreadMode, Vec<tiny_skia::GradientStop>)> {
157    let mode = match gradient.spread_method() {
158        usvg::SpreadMethod::Pad => tiny_skia::SpreadMode::Pad,
159        usvg::SpreadMethod::Reflect => tiny_skia::SpreadMode::Reflect,
160        usvg::SpreadMethod::Repeat => tiny_skia::SpreadMode::Repeat,
161    };
162
163    let mut points = Vec::with_capacity(gradient.stops().len());
164    for stop in gradient.stops() {
165        let alpha = stop.opacity() * opacity;
166        let color = tiny_skia::Color::from_rgba8(
167            stop.color().red,
168            stop.color().green,
169            stop.color().blue,
170            alpha.to_u8(),
171        );
172        points.push(tiny_skia::GradientStop::new(stop.offset().get(), color))
173    }
174
175    Some((mode, points))
176}
177
178fn render_pattern_pixmap(
179    pattern: &usvg::Pattern,
180    ctx: &Context,
181    transform: tiny_skia::Transform,
182) -> Option<(tiny_skia::Pixmap, tiny_skia::Transform)> {
183    let (sx, sy) = {
184        let ts2 = transform.pre_concat(pattern.transform());
185        ts2.get_scale()
186    };
187
188    let rect = pattern.rect();
189    let img_size = tiny_skia::IntSize::from_wh(
190        (rect.width() * sx).round() as u32,
191        (rect.height() * sy).round() as u32,
192    )?;
193    let mut pixmap = tiny_skia::Pixmap::new(img_size.width(), img_size.height())?;
194
195    let transform = tiny_skia::Transform::from_scale(sx, sy);
196    crate::render::render_nodes(pattern.root(), ctx, transform, &mut pixmap.as_mut());
197
198    let mut ts = tiny_skia::Transform::default();
199    ts = ts.pre_concat(pattern.transform());
200    ts = ts.pre_translate(rect.x(), rect.y());
201    ts = ts.pre_scale(1.0 / sx, 1.0 / sy);
202
203    Some((pixmap, ts))
204}