| 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 | |