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