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: SpreadMode, points: Vec) = convert_base_gradient(gradient, opacity)?;
123
124 let shader: Shader<'static> = tiny_skia::LinearGradient::new(
125 (gradient.x1(), gradient.y1()).into(),
126 (gradient.x2(), gradient.y2()).into(),
127 stops: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: SpreadMode, points: Vec) = convert_base_gradient(gradient, opacity)?;
140
141 let shader: Shader<'static> = tiny_skia::RadialGradient::new(
142 (gradient.fx(), gradient.fy()).into(),
143 (gradient.cx(), gradient.cy()).into(),
144 radius:gradient.r().get(),
145 stops: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: SpreadMode = 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 = Vec::with_capacity(gradient.stops().len());
164 for stop: &Stop in gradient.stops() {
165 let alpha: NormalizedF32 = stop.opacity() * opacity;
166 let color: Color = tiny_skia::Color::from_rgba8(
167 r:stop.color().red,
168 g:stop.color().green,
169 b:stop.color().blue,
170 a:alpha.to_u8(),
171 );
172 points.push(tiny_skia::GradientStop::new(position: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: f32, sy: f32) = {
184 let ts2: Transform = transform.pre_concat(pattern.transform());
185 ts2.get_scale()
186 };
187
188 let rect: NonZeroRect = pattern.rect();
189 let img_size: IntSize = tiny_skia::IntSize::from_wh(
190 (rect.width() * sx).round() as u32,
191 (rect.height() * sy).round() as u32,
192 )?;
193 let mut pixmap: Pixmap = tiny_skia::Pixmap::new(img_size.width(), img_size.height())?;
194
195 let transform: Transform = tiny_skia::Transform::from_scale(sx, sy);
196 crate::render::render_nodes(parent:pattern.root(), ctx, transform, &mut pixmap.as_mut());
197
198 let mut ts: Transform = tiny_skia::Transform::default();
199 ts = ts.pre_concat(pattern.transform());
200 ts = ts.pre_translate(tx:rect.x(), ty:rect.y());
201 ts = ts.pre_scale(sx:1.0 / sx, sy:1.0 / sy);
202
203 Some((pixmap, ts))
204}
205