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