1// Copyright 2018 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use crate::OptionLog;
5
6pub struct Context {
7 pub max_bbox: tiny_skia::IntRect,
8}
9
10pub fn render_nodes(
11 parent: &usvg::Group,
12 ctx: &Context,
13 transform: tiny_skia::Transform,
14 pixmap: &mut tiny_skia::PixmapMut,
15) {
16 for node: &Node in parent.children() {
17 render_node(node, ctx, transform, pixmap);
18 }
19}
20
21pub fn render_node(
22 node: &usvg::Node,
23 ctx: &Context,
24 transform: tiny_skia::Transform,
25 pixmap: &mut tiny_skia::PixmapMut,
26) {
27 match node {
28 usvg::Node::Group(ref group: &Box) => {
29 render_group(group, ctx, transform, pixmap);
30 }
31 usvg::Node::Path(ref path: &Box) => {
32 crate::path::render(
33 path,
34 tiny_skia::BlendMode::SourceOver,
35 ctx,
36 transform,
37 pixmap,
38 );
39 }
40 usvg::Node::Image(ref image: &Box) => {
41 crate::image::render(image, transform, pixmap);
42 }
43 usvg::Node::Text(ref text: &Box) => {
44 render_group(group:text.flattened(), ctx, transform, pixmap);
45 }
46 }
47}
48
49fn render_group(
50 group: &usvg::Group,
51 ctx: &Context,
52 transform: tiny_skia::Transform,
53 pixmap: &mut tiny_skia::PixmapMut,
54) -> Option<()> {
55 let transform = transform.pre_concat(group.transform());
56
57 if !group.should_isolate() {
58 render_nodes(group, ctx, transform, pixmap);
59 return Some(());
60 }
61
62 let bbox = group.layer_bounding_box().transform(transform)?;
63
64 let mut ibbox = if group.filters().is_empty() {
65 // Convert group bbox into an integer one, expanding each side outwards by 2px
66 // to make sure that anti-aliased pixels would not be clipped.
67 tiny_skia::IntRect::from_xywh(
68 bbox.x().floor() as i32 - 2,
69 bbox.y().floor() as i32 - 2,
70 bbox.width().ceil() as u32 + 4,
71 bbox.height().ceil() as u32 + 4,
72 )?
73 } else {
74 // The bounding box for groups with filters is special and should not be expanded by 2px,
75 // because it's already acting as a clipping region.
76 let bbox = bbox.to_int_rect();
77 // Make sure our filter region is not bigger than 4x the canvas size.
78 // This is required mainly to prevent huge filter regions that would tank the performance.
79 // It should not affect the final result in any way.
80 crate::geom::fit_to_rect(bbox, ctx.max_bbox)?
81 };
82
83 // Make sure our layer is not bigger than 4x the canvas size.
84 // This is required to prevent huge layers.
85 if group.filters().is_empty() {
86 ibbox = crate::geom::fit_to_rect(ibbox, ctx.max_bbox)?;
87 }
88
89 let shift_ts = {
90 // Original shift.
91 let mut dx = bbox.x();
92 let mut dy = bbox.y();
93
94 // Account for subpixel positioned layers.
95 dx -= bbox.x() - ibbox.x() as f32;
96 dy -= bbox.y() - ibbox.y() as f32;
97
98 tiny_skia::Transform::from_translate(-dx, -dy)
99 };
100
101 let transform = shift_ts.pre_concat(transform);
102
103 let mut sub_pixmap = tiny_skia::Pixmap::new(ibbox.width(), ibbox.height())
104 .log_none(|| log::warn!("Failed to allocate a group layer for: {:?}.", ibbox))?;
105
106 render_nodes(group, ctx, transform, &mut sub_pixmap.as_mut());
107
108 if !group.filters().is_empty() {
109 for filter in group.filters() {
110 crate::filter::apply(filter, transform, &mut sub_pixmap);
111 }
112 }
113
114 if let Some(clip_path) = group.clip_path() {
115 crate::clip::apply(clip_path, transform, &mut sub_pixmap);
116 }
117
118 if let Some(mask) = group.mask() {
119 crate::mask::apply(mask, ctx, transform, &mut sub_pixmap);
120 }
121
122 let paint = tiny_skia::PixmapPaint {
123 opacity: group.opacity().get(),
124 blend_mode: convert_blend_mode(group.blend_mode()),
125 quality: tiny_skia::FilterQuality::Nearest,
126 };
127
128 pixmap.draw_pixmap(
129 ibbox.x(),
130 ibbox.y(),
131 sub_pixmap.as_ref(),
132 &paint,
133 tiny_skia::Transform::identity(),
134 None,
135 );
136
137 Some(())
138}
139
140pub fn convert_blend_mode(mode: usvg::BlendMode) -> tiny_skia::BlendMode {
141 match mode {
142 usvg::BlendMode::Normal => tiny_skia::BlendMode::SourceOver,
143 usvg::BlendMode::Multiply => tiny_skia::BlendMode::Multiply,
144 usvg::BlendMode::Screen => tiny_skia::BlendMode::Screen,
145 usvg::BlendMode::Overlay => tiny_skia::BlendMode::Overlay,
146 usvg::BlendMode::Darken => tiny_skia::BlendMode::Darken,
147 usvg::BlendMode::Lighten => tiny_skia::BlendMode::Lighten,
148 usvg::BlendMode::ColorDodge => tiny_skia::BlendMode::ColorDodge,
149 usvg::BlendMode::ColorBurn => tiny_skia::BlendMode::ColorBurn,
150 usvg::BlendMode::HardLight => tiny_skia::BlendMode::HardLight,
151 usvg::BlendMode::SoftLight => tiny_skia::BlendMode::SoftLight,
152 usvg::BlendMode::Difference => tiny_skia::BlendMode::Difference,
153 usvg::BlendMode::Exclusion => tiny_skia::BlendMode::Exclusion,
154 usvg::BlendMode::Hue => tiny_skia::BlendMode::Hue,
155 usvg::BlendMode::Saturation => tiny_skia::BlendMode::Saturation,
156 usvg::BlendMode::Color => tiny_skia::BlendMode::Color,
157 usvg::BlendMode::Luminosity => tiny_skia::BlendMode::Luminosity,
158 }
159}
160