1 | // Copyright 2019 the Resvg Authors |
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
3 | |
4 | use crate::render::Context; |
5 | |
6 | pub 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 | |
26 | pub 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 | |
77 | fn 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 | |
118 | fn 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 | |
135 | fn 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 | |
153 | fn 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 | |
178 | fn 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 | |