Skip to main content

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(lg) => {
52            paint.shader = convert_linear_gradient(lg, fill.opacity())?;
53        }
54        usvg::Paint::RadialGradient(rg) => {
55            paint.shader = convert_radial_gradient(rg, fill.opacity())?;
56        }
57        usvg::Paint::Pattern(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(lg) => {
92            paint.shader = convert_linear_gradient(lg, stroke.opacity())?;
93        }
94        usvg::Paint::RadialGradient(rg) => {
95            paint.shader = convert_radial_gradient(rg, stroke.opacity())?;
96        }
97        usvg::Paint::Pattern(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.fr().get(),
144        (gradient.cx(), gradient.cy()).into(),
145        gradient.r().get(),
146        points,
147        mode,
148        gradient.transform(),
149    )?;
150
151    Some(shader)
152}
153
154fn convert_base_gradient(
155    gradient: &usvg::BaseGradient,
156    opacity: usvg::Opacity,
157) -> Option<(tiny_skia::SpreadMode, Vec<tiny_skia::GradientStop>)> {
158    let mode = match gradient.spread_method() {
159        usvg::SpreadMethod::Pad => tiny_skia::SpreadMode::Pad,
160        usvg::SpreadMethod::Reflect => tiny_skia::SpreadMode::Reflect,
161        usvg::SpreadMethod::Repeat => tiny_skia::SpreadMode::Repeat,
162    };
163
164    let mut points = Vec::with_capacity(gradient.stops().len());
165    for stop in gradient.stops() {
166        let alpha = stop.opacity() * opacity;
167        let color = tiny_skia::Color::from_rgba8(
168            stop.color().red,
169            stop.color().green,
170            stop.color().blue,
171            alpha.to_u8(),
172        );
173        points.push(tiny_skia::GradientStop::new(stop.offset().get(), color));
174    }
175
176    Some((mode, points))
177}
178
179fn render_pattern_pixmap(
180    pattern: &usvg::Pattern,
181    ctx: &Context,
182    transform: tiny_skia::Transform,
183) -> Option<(tiny_skia::Pixmap, tiny_skia::Transform)> {
184    let (sx, sy) = {
185        let ts2 = transform.pre_concat(pattern.transform());
186        ts2.get_scale()
187    };
188
189    let rect = pattern.rect();
190    let img_size = tiny_skia::IntSize::from_wh(
191        (rect.width() * sx).round() as u32,
192        (rect.height() * sy).round() as u32,
193    )?;
194    let mut pixmap = tiny_skia::Pixmap::new(img_size.width(), img_size.height())?;
195
196    let transform = tiny_skia::Transform::from_scale(sx, sy);
197    crate::render::render_nodes(pattern.root(), ctx, transform, &mut pixmap.as_mut());
198
199    let mut ts = tiny_skia::Transform::default();
200    ts = ts.pre_concat(pattern.transform());
201    ts = ts.pre_translate(rect.x(), rect.y());
202    ts = ts.pre_scale(1.0 / sx, 1.0 / sy);
203
204    Some((pixmap, ts))
205}