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 std::rc::Rc; |
6 | |
7 | use rgb::{FromSlice, RGBA8}; |
8 | use tiny_skia::IntRect; |
9 | use usvg::{ApproxEqUlps, ApproxZeroUlps}; |
10 | |
11 | mod box_blur; |
12 | mod color_matrix; |
13 | mod component_transfer; |
14 | mod composite; |
15 | mod convolve_matrix; |
16 | mod displacement_map; |
17 | mod iir_blur; |
18 | mod lighting; |
19 | mod morphology; |
20 | mod turbulence; |
21 | |
22 | // TODO: apply single primitive filters in-place |
23 | |
24 | /// An image reference. |
25 | /// |
26 | /// Image pixels should be stored in RGBA order. |
27 | /// |
28 | /// Some filters will require premultipled channels, some not. |
29 | /// See specific filter documentation for details. |
30 | #[derive (Clone, Copy)] |
31 | pub struct ImageRef<'a> { |
32 | data: &'a [RGBA8], |
33 | width: u32, |
34 | height: u32, |
35 | } |
36 | |
37 | impl<'a> ImageRef<'a> { |
38 | /// Creates a new image reference. |
39 | /// |
40 | /// Doesn't clone the provided data. |
41 | #[inline ] |
42 | pub fn new(width: u32, height: u32, data: &'a [RGBA8]) -> Self { |
43 | ImageRef { |
44 | data, |
45 | width, |
46 | height, |
47 | } |
48 | } |
49 | |
50 | #[inline ] |
51 | fn alpha_at(&self, x: u32, y: u32) -> i16 { |
52 | self.data[(self.width * y + x) as usize].a as i16 |
53 | } |
54 | } |
55 | |
56 | /// A mutable `ImageRef` variant. |
57 | pub struct ImageRefMut<'a> { |
58 | data: &'a mut [RGBA8], |
59 | width: u32, |
60 | height: u32, |
61 | } |
62 | |
63 | impl<'a> ImageRefMut<'a> { |
64 | /// Creates a new mutable image reference. |
65 | /// |
66 | /// Doesn't clone the provided data. |
67 | #[inline ] |
68 | pub fn new(width: u32, height: u32, data: &'a mut [RGBA8]) -> Self { |
69 | ImageRefMut { |
70 | data, |
71 | width, |
72 | height, |
73 | } |
74 | } |
75 | |
76 | #[inline ] |
77 | fn pixel_at(&self, x: u32, y: u32) -> RGBA8 { |
78 | self.data[(self.width * y + x) as usize] |
79 | } |
80 | |
81 | #[inline ] |
82 | fn pixel_at_mut(&mut self, x: u32, y: u32) -> &mut RGBA8 { |
83 | &mut self.data[(self.width * y + x) as usize] |
84 | } |
85 | } |
86 | |
87 | #[derive (Debug)] |
88 | pub(crate) enum Error { |
89 | InvalidRegion, |
90 | NoResults, |
91 | } |
92 | |
93 | trait PixmapExt: Sized { |
94 | fn try_create(width: u32, height: u32) -> Result<tiny_skia::Pixmap, Error>; |
95 | fn copy_region(&self, region: IntRect) -> Result<tiny_skia::Pixmap, Error>; |
96 | fn clear(&mut self); |
97 | fn into_srgb(&mut self); |
98 | fn into_linear_rgb(&mut self); |
99 | } |
100 | |
101 | impl PixmapExt for tiny_skia::Pixmap { |
102 | fn try_create(width: u32, height: u32) -> Result<tiny_skia::Pixmap, Error> { |
103 | tiny_skia::Pixmap::new(width, height).ok_or(Error::InvalidRegion) |
104 | } |
105 | |
106 | fn copy_region(&self, region: IntRect) -> Result<tiny_skia::Pixmap, Error> { |
107 | let rect = IntRect::from_xywh(region.x(), region.y(), region.width(), region.height()) |
108 | .ok_or(Error::InvalidRegion)?; |
109 | self.clone_rect(rect).ok_or(Error::InvalidRegion) |
110 | } |
111 | |
112 | fn clear(&mut self) { |
113 | self.fill(tiny_skia::Color::TRANSPARENT); |
114 | } |
115 | |
116 | fn into_srgb(&mut self) { |
117 | demultiply_alpha(self.data_mut().as_rgba_mut()); |
118 | from_linear_rgb(self.data_mut().as_rgba_mut()); |
119 | multiply_alpha(self.data_mut().as_rgba_mut()); |
120 | } |
121 | |
122 | fn into_linear_rgb(&mut self) { |
123 | demultiply_alpha(self.data_mut().as_rgba_mut()); |
124 | into_linear_rgb(self.data_mut().as_rgba_mut()); |
125 | multiply_alpha(self.data_mut().as_rgba_mut()); |
126 | } |
127 | } |
128 | |
129 | /// Multiplies provided pixels alpha. |
130 | fn multiply_alpha(data: &mut [RGBA8]) { |
131 | for p: &mut RGBA in data { |
132 | let a: f32 = p.a as f32 / 255.0; |
133 | p.b = (p.b as f32 * a + 0.5) as u8; |
134 | p.g = (p.g as f32 * a + 0.5) as u8; |
135 | p.r = (p.r as f32 * a + 0.5) as u8; |
136 | } |
137 | } |
138 | |
139 | /// Demultiplies provided pixels alpha. |
140 | fn demultiply_alpha(data: &mut [RGBA8]) { |
141 | for p: &mut RGBA in data { |
142 | let a: f32 = p.a as f32 / 255.0; |
143 | p.b = (p.b as f32 / a + 0.5) as u8; |
144 | p.g = (p.g as f32 / a + 0.5) as u8; |
145 | p.r = (p.r as f32 / a + 0.5) as u8; |
146 | } |
147 | } |
148 | |
149 | /// Precomputed sRGB to LinearRGB table. |
150 | /// |
151 | /// Since we are storing the result in `u8`, there is no need to compute those |
152 | /// values each time. Mainly because it's very expensive. |
153 | /// |
154 | /// ```text |
155 | /// if (C_srgb <= 0.04045) |
156 | /// C_lin = C_srgb / 12.92; |
157 | /// else |
158 | /// C_lin = pow((C_srgb + 0.055) / 1.055, 2.4); |
159 | /// ``` |
160 | /// |
161 | /// Thanks to librsvg for the idea. |
162 | #[rustfmt::skip] |
163 | const SRGB_TO_LINEAR_RGB_TABLE: &[u8; 256] = &[ |
164 | 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
165 | 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, |
166 | 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, |
167 | 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, |
168 | 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19, 19, 20, |
169 | 20, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29, |
170 | 30, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 37, 38, 39, 40, 41, |
171 | 41, 42, 43, 44, 45, 45, 46, 47, 48, 49, 50, 51, 51, 52, 53, 54, |
172 | 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, |
173 | 71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, |
174 | 90, 91, 92, 93, 95, 96, 97, 99, 100, 101, 103, 104, 105, 107, 108, 109, |
175 | 111, 112, 114, 115, 116, 118, 119, 121, 122, 124, 125, 127, 128, 130, 131, 133, |
176 | 134, 136, 138, 139, 141, 142, 144, 146, 147, 149, 151, 152, 154, 156, 157, 159, |
177 | 161, 163, 164, 166, 168, 170, 171, 173, 175, 177, 179, 181, 183, 184, 186, 188, |
178 | 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, |
179 | 222, 224, 226, 229, 231, 233, 235, 237, 239, 242, 244, 246, 248, 250, 253, 255, |
180 | ]; |
181 | |
182 | /// Precomputed LinearRGB to sRGB table. |
183 | /// |
184 | /// Since we are storing the result in `u8`, there is no need to compute those |
185 | /// values each time. Mainly because it's very expensive. |
186 | /// |
187 | /// ```text |
188 | /// if (C_lin <= 0.0031308) |
189 | /// C_srgb = C_lin * 12.92; |
190 | /// else |
191 | /// C_srgb = 1.055 * pow(C_lin, 1.0 / 2.4) - 0.055; |
192 | /// ``` |
193 | /// |
194 | /// Thanks to librsvg for the idea. |
195 | #[rustfmt::skip] |
196 | const LINEAR_RGB_TO_SRGB_TABLE: &[u8; 256] = &[ |
197 | 0, 13, 22, 28, 34, 38, 42, 46, 50, 53, 56, 59, 61, 64, 66, 69, |
198 | 71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90, 92, 93, 95, 96, 98, |
199 | 99, 101, 102, 104, 105, 106, 108, 109, 110, 112, 113, 114, 115, 117, 118, 119, |
200 | 120, 121, 122, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, |
201 | 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 148, 149, 150, 151, |
202 | 152, 153, 154, 155, 155, 156, 157, 158, 159, 159, 160, 161, 162, 163, 163, 164, |
203 | 165, 166, 167, 167, 168, 169, 170, 170, 171, 172, 173, 173, 174, 175, 175, 176, |
204 | 177, 178, 178, 179, 180, 180, 181, 182, 182, 183, 184, 185, 185, 186, 187, 187, |
205 | 188, 189, 189, 190, 190, 191, 192, 192, 193, 194, 194, 195, 196, 196, 197, 197, |
206 | 198, 199, 199, 200, 200, 201, 202, 202, 203, 203, 204, 205, 205, 206, 206, 207, |
207 | 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215, 215, 216, |
208 | 216, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 223, 224, 224, |
209 | 225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233, |
210 | 233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238, 238, 239, 239, 240, 240, |
211 | 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 246, 246, 246, 247, 247, 248, |
212 | 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255, 255, |
213 | ]; |
214 | |
215 | /// Converts input pixel from sRGB into LinearRGB. |
216 | /// |
217 | /// Provided pixels should have an **unpremultiplied alpha**. |
218 | /// |
219 | /// RGB channels order of the input image doesn't matter, but alpha channel must be the last one. |
220 | fn into_linear_rgb(data: &mut [RGBA8]) { |
221 | for p: &mut RGBA in data { |
222 | p.r = SRGB_TO_LINEAR_RGB_TABLE[p.r as usize]; |
223 | p.g = SRGB_TO_LINEAR_RGB_TABLE[p.g as usize]; |
224 | p.b = SRGB_TO_LINEAR_RGB_TABLE[p.b as usize]; |
225 | } |
226 | } |
227 | |
228 | /// Converts input pixel from LinearRGB into sRGB. |
229 | /// |
230 | /// Provided pixels should have an **unpremultiplied alpha**. |
231 | /// |
232 | /// RGB channels order of the input image doesn't matter, but alpha channel must be the last one. |
233 | fn from_linear_rgb(data: &mut [RGBA8]) { |
234 | for p: &mut RGBA in data { |
235 | p.r = LINEAR_RGB_TO_SRGB_TABLE[p.r as usize]; |
236 | p.g = LINEAR_RGB_TO_SRGB_TABLE[p.g as usize]; |
237 | p.b = LINEAR_RGB_TO_SRGB_TABLE[p.b as usize]; |
238 | } |
239 | } |
240 | |
241 | // TODO: https://github.com/rust-lang/rust/issues/44095 |
242 | #[inline ] |
243 | fn f32_bound(min: f32, val: f32, max: f32) -> f32 { |
244 | debug_assert!(min.is_finite()); |
245 | debug_assert!(val.is_finite()); |
246 | debug_assert!(max.is_finite()); |
247 | |
248 | if val > max { |
249 | max |
250 | } else if val < min { |
251 | min |
252 | } else { |
253 | val |
254 | } |
255 | } |
256 | |
257 | #[derive (Clone)] |
258 | struct Image { |
259 | /// Filter primitive result. |
260 | /// |
261 | /// All images have the same size which is equal to the current filter region. |
262 | image: Rc<tiny_skia::Pixmap>, |
263 | |
264 | /// Image's region that has actual data. |
265 | /// |
266 | /// Region is in global coordinates and not in `image` one. |
267 | /// |
268 | /// Image's content outside this region will be transparent/cleared. |
269 | /// |
270 | /// Currently used only for `feTile`. |
271 | region: IntRect, |
272 | |
273 | /// The current color space. |
274 | color_space: usvg::filter::ColorInterpolation, |
275 | } |
276 | |
277 | impl Image { |
278 | fn from_image(image: tiny_skia::Pixmap, color_space: usvg::filter::ColorInterpolation) -> Self { |
279 | let (w, h) = (image.width(), image.height()); |
280 | Image { |
281 | image: Rc::new(image), |
282 | region: IntRect::from_xywh(0, 0, w, h).unwrap(), |
283 | color_space, |
284 | } |
285 | } |
286 | |
287 | fn into_color_space( |
288 | self, |
289 | color_space: usvg::filter::ColorInterpolation, |
290 | ) -> Result<Self, Error> { |
291 | if color_space != self.color_space { |
292 | let region = self.region; |
293 | |
294 | let mut image = self.take()?; |
295 | |
296 | match color_space { |
297 | usvg::filter::ColorInterpolation::SRGB => image.into_srgb(), |
298 | usvg::filter::ColorInterpolation::LinearRGB => image.into_linear_rgb(), |
299 | } |
300 | |
301 | Ok(Image { |
302 | image: Rc::new(image), |
303 | region, |
304 | color_space, |
305 | }) |
306 | } else { |
307 | Ok(self) |
308 | } |
309 | } |
310 | |
311 | fn take(self) -> Result<tiny_skia::Pixmap, Error> { |
312 | match Rc::try_unwrap(self.image) { |
313 | Ok(v) => Ok(v), |
314 | Err(v) => Ok((*v).clone()), |
315 | } |
316 | } |
317 | |
318 | fn width(&self) -> u32 { |
319 | self.image.width() |
320 | } |
321 | |
322 | fn height(&self) -> u32 { |
323 | self.image.height() |
324 | } |
325 | |
326 | fn as_ref(&self) -> &tiny_skia::Pixmap { |
327 | &self.image |
328 | } |
329 | } |
330 | |
331 | struct FilterResult { |
332 | name: String, |
333 | image: Image, |
334 | } |
335 | |
336 | pub fn apply( |
337 | filter: &usvg::filter::Filter, |
338 | ts: tiny_skia::Transform, |
339 | source: &mut tiny_skia::Pixmap, |
340 | ) { |
341 | let result: Result = apply_inner(filter, ts, source); |
342 | let result: Result<(), Error> = result.and_then(|image: Image| apply_to_canvas(input:image, pixmap:source)); |
343 | |
344 | // Clear on error. |
345 | if result.is_err() { |
346 | source.fill(color:tiny_skia::Color::TRANSPARENT); |
347 | } |
348 | |
349 | match result { |
350 | Ok(_) => {} |
351 | Err(Error::InvalidRegion) => { |
352 | log::warn!("Filter has an invalid region." ); |
353 | } |
354 | Err(Error::NoResults) => {} |
355 | } |
356 | } |
357 | |
358 | fn apply_inner( |
359 | filter: &usvg::filter::Filter, |
360 | ts: usvg::Transform, |
361 | source: &mut tiny_skia::Pixmap, |
362 | ) -> Result<Image, Error> { |
363 | let region = filter |
364 | .rect() |
365 | .transform(ts) |
366 | .map(|r| r.to_int_rect()) |
367 | .ok_or(Error::InvalidRegion)?; |
368 | |
369 | let mut results: Vec<FilterResult> = Vec::new(); |
370 | |
371 | for primitive in filter.primitives() { |
372 | let mut subregion = primitive |
373 | .rect() |
374 | .transform(ts) |
375 | .map(|r| r.to_int_rect()) |
376 | .ok_or(Error::InvalidRegion)?; |
377 | |
378 | // `feOffset` inherits its region from the input. |
379 | if let usvg::filter::Kind::Offset(ref fe) = primitive.kind() { |
380 | if let usvg::filter::Input::Reference(ref name) = fe.input() { |
381 | if let Some(res) = results.iter().rev().find(|v| v.name == *name) { |
382 | subregion = res.image.region; |
383 | } |
384 | } |
385 | } |
386 | |
387 | let cs = primitive.color_interpolation(); |
388 | |
389 | let mut result = match primitive.kind() { |
390 | usvg::filter::Kind::Blend(ref fe) => { |
391 | let input1 = get_input(fe.input1(), region, source, &results)?; |
392 | let input2 = get_input(fe.input2(), region, source, &results)?; |
393 | apply_blend(fe, cs, region, input1, input2) |
394 | } |
395 | usvg::filter::Kind::DropShadow(ref fe) => { |
396 | let input = get_input(fe.input(), region, source, &results)?; |
397 | apply_drop_shadow(fe, cs, ts, input) |
398 | } |
399 | usvg::filter::Kind::Flood(ref fe) => apply_flood(fe, region), |
400 | usvg::filter::Kind::GaussianBlur(ref fe) => { |
401 | let input = get_input(fe.input(), region, source, &results)?; |
402 | apply_blur(fe, cs, ts, input) |
403 | } |
404 | usvg::filter::Kind::Offset(ref fe) => { |
405 | let input = get_input(fe.input(), region, source, &results)?; |
406 | apply_offset(fe, ts, input) |
407 | } |
408 | usvg::filter::Kind::Composite(ref fe) => { |
409 | let input1 = get_input(fe.input1(), region, source, &results)?; |
410 | let input2 = get_input(fe.input2(), region, source, &results)?; |
411 | apply_composite(fe, cs, region, input1, input2) |
412 | } |
413 | usvg::filter::Kind::Merge(ref fe) => apply_merge(fe, cs, region, source, &results), |
414 | usvg::filter::Kind::Tile(ref fe) => { |
415 | let input = get_input(fe.input(), region, source, &results)?; |
416 | apply_tile(input, region) |
417 | } |
418 | usvg::filter::Kind::Image(ref fe) => apply_image(fe, region, subregion, ts), |
419 | usvg::filter::Kind::ComponentTransfer(ref fe) => { |
420 | let input = get_input(fe.input(), region, source, &results)?; |
421 | apply_component_transfer(fe, cs, input) |
422 | } |
423 | usvg::filter::Kind::ColorMatrix(ref fe) => { |
424 | let input = get_input(fe.input(), region, source, &results)?; |
425 | apply_color_matrix(fe, cs, input) |
426 | } |
427 | usvg::filter::Kind::ConvolveMatrix(ref fe) => { |
428 | let input = get_input(fe.input(), region, source, &results)?; |
429 | apply_convolve_matrix(fe, cs, input) |
430 | } |
431 | usvg::filter::Kind::Morphology(ref fe) => { |
432 | let input = get_input(fe.input(), region, source, &results)?; |
433 | apply_morphology(fe, cs, ts, input) |
434 | } |
435 | usvg::filter::Kind::DisplacementMap(ref fe) => { |
436 | let input1 = get_input(fe.input1(), region, source, &results)?; |
437 | let input2 = get_input(fe.input2(), region, source, &results)?; |
438 | apply_displacement_map(fe, region, cs, ts, input1, input2) |
439 | } |
440 | usvg::filter::Kind::Turbulence(ref fe) => apply_turbulence(fe, region, cs, ts), |
441 | usvg::filter::Kind::DiffuseLighting(ref fe) => { |
442 | let input = get_input(fe.input(), region, source, &results)?; |
443 | apply_diffuse_lighting(fe, region, cs, ts, input) |
444 | } |
445 | usvg::filter::Kind::SpecularLighting(ref fe) => { |
446 | let input = get_input(fe.input(), region, source, &results)?; |
447 | apply_specular_lighting(fe, region, cs, ts, input) |
448 | } |
449 | }?; |
450 | |
451 | if region != subregion { |
452 | // Clip result. |
453 | |
454 | // TODO: explain |
455 | let subregion2 = if let usvg::filter::Kind::Offset(..) = primitive.kind() { |
456 | // We do not support clipping on feOffset. |
457 | region.translate_to(0, 0) |
458 | } else { |
459 | subregion.translate(-region.x(), -region.y()) |
460 | } |
461 | .unwrap(); |
462 | |
463 | let color_space = result.color_space; |
464 | |
465 | let pixmap = { |
466 | // This is cropping by clearing the pixels outside the region. |
467 | let mut paint = tiny_skia::Paint::default(); |
468 | paint.set_color(tiny_skia::Color::BLACK); |
469 | paint.blend_mode = tiny_skia::BlendMode::Clear; |
470 | |
471 | let mut pixmap = result.take()?; |
472 | let w = pixmap.width() as f32; |
473 | let h = pixmap.height() as f32; |
474 | |
475 | if let Some(rect) = tiny_skia::Rect::from_xywh(0.0, 0.0, w, subregion2.y() as f32) { |
476 | pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None); |
477 | } |
478 | |
479 | if let Some(rect) = tiny_skia::Rect::from_xywh(0.0, 0.0, subregion2.x() as f32, h) { |
480 | pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None); |
481 | } |
482 | |
483 | if let Some(rect) = tiny_skia::Rect::from_xywh(subregion2.right() as f32, 0.0, w, h) |
484 | { |
485 | pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None); |
486 | } |
487 | |
488 | if let Some(rect) = |
489 | tiny_skia::Rect::from_xywh(0.0, subregion2.bottom() as f32, w, h) |
490 | { |
491 | pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None); |
492 | } |
493 | |
494 | pixmap |
495 | }; |
496 | |
497 | result = Image { |
498 | image: Rc::new(pixmap), |
499 | region: subregion, |
500 | color_space, |
501 | }; |
502 | } |
503 | |
504 | results.push(FilterResult { |
505 | name: primitive.result().to_string(), |
506 | image: result, |
507 | }); |
508 | } |
509 | |
510 | if let Some(res) = results.pop() { |
511 | Ok(res.image) |
512 | } else { |
513 | Err(Error::NoResults) |
514 | } |
515 | } |
516 | |
517 | fn get_input( |
518 | input: &usvg::filter::Input, |
519 | region: IntRect, |
520 | source: &tiny_skia::Pixmap, |
521 | results: &[FilterResult], |
522 | ) -> Result<Image, Error> { |
523 | match input { |
524 | usvg::filter::Input::SourceGraphic => { |
525 | let image = source.clone(); |
526 | |
527 | Ok(Image { |
528 | image: Rc::new(image), |
529 | region, |
530 | color_space: usvg::filter::ColorInterpolation::SRGB, |
531 | }) |
532 | } |
533 | usvg::filter::Input::SourceAlpha => { |
534 | let mut image = source.clone(); |
535 | // Set RGB to black. Keep alpha as is. |
536 | for p in image.data_mut().as_rgba_mut() { |
537 | p.r = 0; |
538 | p.g = 0; |
539 | p.b = 0; |
540 | } |
541 | |
542 | Ok(Image { |
543 | image: Rc::new(image), |
544 | region, |
545 | color_space: usvg::filter::ColorInterpolation::SRGB, |
546 | }) |
547 | } |
548 | usvg::filter::Input::Reference(ref name) => { |
549 | if let Some(v) = results.iter().rev().find(|v| v.name == *name) { |
550 | Ok(v.image.clone()) |
551 | } else { |
552 | // Technically unreachable. |
553 | log::warn!("Unknown filter primitive reference ' {}'." , name); |
554 | get_input(&usvg::filter::Input::SourceGraphic, region, source, results) |
555 | } |
556 | } |
557 | } |
558 | } |
559 | |
560 | trait PixmapToImageRef<'a> { |
561 | fn as_image_ref(&'a self) -> ImageRef<'a>; |
562 | fn as_image_ref_mut(&'a mut self) -> ImageRefMut<'a>; |
563 | } |
564 | |
565 | impl<'a> PixmapToImageRef<'a> for tiny_skia::Pixmap { |
566 | fn as_image_ref(&'a self) -> ImageRef<'a> { |
567 | ImageRef::new(self.width(), self.height(), self.data().as_rgba()) |
568 | } |
569 | |
570 | fn as_image_ref_mut(&'a mut self) -> ImageRefMut<'a> { |
571 | ImageRefMut::new(self.width(), self.height(), self.data_mut().as_rgba_mut()) |
572 | } |
573 | } |
574 | |
575 | fn apply_drop_shadow( |
576 | fe: &usvg::filter::DropShadow, |
577 | cs: usvg::filter::ColorInterpolation, |
578 | ts: usvg::Transform, |
579 | input: Image, |
580 | ) -> Result<Image, Error> { |
581 | let (dx, dy) = match scale_coordinates(fe.dx(), fe.dy(), ts) { |
582 | Some(v) => v, |
583 | None => return Ok(input), |
584 | }; |
585 | |
586 | let mut pixmap = tiny_skia::Pixmap::try_create(input.width(), input.height())?; |
587 | let input_pixmap = input.into_color_space(cs)?.take()?; |
588 | let mut shadow_pixmap = input_pixmap.clone(); |
589 | |
590 | if let Some((std_dx, std_dy, use_box_blur)) = |
591 | resolve_std_dev(fe.std_dev_x().get(), fe.std_dev_y().get(), ts) |
592 | { |
593 | if use_box_blur { |
594 | box_blur::apply(std_dx, std_dy, shadow_pixmap.as_image_ref_mut()); |
595 | } else { |
596 | iir_blur::apply(std_dx, std_dy, shadow_pixmap.as_image_ref_mut()); |
597 | } |
598 | } |
599 | |
600 | // flood |
601 | let color = tiny_skia::Color::from_rgba8( |
602 | fe.color().red, |
603 | fe.color().green, |
604 | fe.color().blue, |
605 | fe.opacity().to_u8(), |
606 | ); |
607 | for p in shadow_pixmap.pixels_mut() { |
608 | let mut color = color; |
609 | color.apply_opacity(p.alpha() as f32 / 255.0); |
610 | *p = color.premultiply().to_color_u8(); |
611 | } |
612 | |
613 | match cs { |
614 | usvg::filter::ColorInterpolation::SRGB => shadow_pixmap.into_srgb(), |
615 | usvg::filter::ColorInterpolation::LinearRGB => shadow_pixmap.into_linear_rgb(), |
616 | } |
617 | |
618 | pixmap.draw_pixmap( |
619 | dx as i32, |
620 | dy as i32, |
621 | shadow_pixmap.as_ref(), |
622 | &tiny_skia::PixmapPaint::default(), |
623 | tiny_skia::Transform::identity(), |
624 | None, |
625 | ); |
626 | |
627 | pixmap.draw_pixmap( |
628 | 0, |
629 | 0, |
630 | input_pixmap.as_ref(), |
631 | &tiny_skia::PixmapPaint::default(), |
632 | tiny_skia::Transform::identity(), |
633 | None, |
634 | ); |
635 | |
636 | Ok(Image::from_image(pixmap, cs)) |
637 | } |
638 | |
639 | fn apply_blur( |
640 | fe: &usvg::filter::GaussianBlur, |
641 | cs: usvg::filter::ColorInterpolation, |
642 | ts: usvg::Transform, |
643 | input: Image, |
644 | ) -> Result<Image, Error> { |
645 | let (std_dx: f64, std_dy: f64, use_box_blur: bool) = |
646 | match resolve_std_dev(std_dx:fe.std_dev_x().get(), std_dy:fe.std_dev_y().get(), ts) { |
647 | Some(v: (f64, f64, bool)) => v, |
648 | None => return Ok(input), |
649 | }; |
650 | |
651 | let mut pixmap: Pixmap = input.into_color_space(cs)?.take()?; |
652 | |
653 | if use_box_blur { |
654 | box_blur::apply(sigma_x:std_dx, sigma_y:std_dy, src:pixmap.as_image_ref_mut()); |
655 | } else { |
656 | iir_blur::apply(sigma_x:std_dx, sigma_y:std_dy, src:pixmap.as_image_ref_mut()); |
657 | } |
658 | |
659 | Ok(Image::from_image(image:pixmap, color_space:cs)) |
660 | } |
661 | |
662 | fn apply_offset( |
663 | fe: &usvg::filter::Offset, |
664 | ts: usvg::Transform, |
665 | input: Image, |
666 | ) -> Result<Image, Error> { |
667 | let (dx: f32, dy: f32) = match scale_coordinates(x:fe.dx(), y:fe.dy(), ts) { |
668 | Some(v: (f32, f32)) => v, |
669 | None => return Ok(input), |
670 | }; |
671 | |
672 | if dx.approx_zero_ulps(4) && dy.approx_zero_ulps(4) { |
673 | return Ok(input); |
674 | } |
675 | |
676 | let mut pixmap: Pixmap = tiny_skia::Pixmap::try_create(input.width(), input.height())?; |
677 | pixmap.draw_pixmap( |
678 | x:dx as i32, |
679 | y:dy as i32, |
680 | pixmap:input.as_ref().as_ref(), |
681 | &tiny_skia::PixmapPaint::default(), |
682 | tiny_skia::Transform::identity(), |
683 | mask:None, |
684 | ); |
685 | |
686 | Ok(Image::from_image(image:pixmap, input.color_space)) |
687 | } |
688 | |
689 | fn apply_blend( |
690 | fe: &usvg::filter::Blend, |
691 | cs: usvg::filter::ColorInterpolation, |
692 | region: IntRect, |
693 | input1: Image, |
694 | input2: Image, |
695 | ) -> Result<Image, Error> { |
696 | let input1 = input1.into_color_space(cs)?; |
697 | let input2 = input2.into_color_space(cs)?; |
698 | |
699 | let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; |
700 | |
701 | pixmap.draw_pixmap( |
702 | 0, |
703 | 0, |
704 | input2.as_ref().as_ref(), |
705 | &tiny_skia::PixmapPaint::default(), |
706 | tiny_skia::Transform::identity(), |
707 | None, |
708 | ); |
709 | |
710 | pixmap.draw_pixmap( |
711 | 0, |
712 | 0, |
713 | input1.as_ref().as_ref(), |
714 | &tiny_skia::PixmapPaint { |
715 | blend_mode: crate::render::convert_blend_mode(fe.mode()), |
716 | ..tiny_skia::PixmapPaint::default() |
717 | }, |
718 | tiny_skia::Transform::identity(), |
719 | None, |
720 | ); |
721 | |
722 | Ok(Image::from_image(pixmap, cs)) |
723 | } |
724 | |
725 | fn apply_composite( |
726 | fe: &usvg::filter::Composite, |
727 | cs: usvg::filter::ColorInterpolation, |
728 | region: IntRect, |
729 | input1: Image, |
730 | input2: Image, |
731 | ) -> Result<Image, Error> { |
732 | use usvg::filter::CompositeOperator as Operator; |
733 | |
734 | let input1 = input1.into_color_space(cs)?; |
735 | let input2 = input2.into_color_space(cs)?; |
736 | |
737 | let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; |
738 | |
739 | if let Operator::Arithmetic { k1, k2, k3, k4 } = fe.operator() { |
740 | let pixmap1 = input1.take()?; |
741 | let pixmap2 = input2.take()?; |
742 | |
743 | composite::arithmetic( |
744 | k1, |
745 | k2, |
746 | k3, |
747 | k4, |
748 | pixmap1.as_image_ref(), |
749 | pixmap2.as_image_ref(), |
750 | pixmap.as_image_ref_mut(), |
751 | ); |
752 | |
753 | return Ok(Image::from_image(pixmap, cs)); |
754 | } |
755 | |
756 | pixmap.draw_pixmap( |
757 | 0, |
758 | 0, |
759 | input2.as_ref().as_ref(), |
760 | &tiny_skia::PixmapPaint::default(), |
761 | tiny_skia::Transform::identity(), |
762 | None, |
763 | ); |
764 | |
765 | let blend_mode = match fe.operator() { |
766 | Operator::Over => tiny_skia::BlendMode::SourceOver, |
767 | Operator::In => tiny_skia::BlendMode::SourceIn, |
768 | Operator::Out => tiny_skia::BlendMode::SourceOut, |
769 | Operator::Atop => tiny_skia::BlendMode::SourceAtop, |
770 | Operator::Xor => tiny_skia::BlendMode::Xor, |
771 | Operator::Arithmetic { .. } => tiny_skia::BlendMode::SourceOver, |
772 | }; |
773 | |
774 | pixmap.draw_pixmap( |
775 | 0, |
776 | 0, |
777 | input1.as_ref().as_ref(), |
778 | &tiny_skia::PixmapPaint { |
779 | blend_mode, |
780 | ..tiny_skia::PixmapPaint::default() |
781 | }, |
782 | tiny_skia::Transform::identity(), |
783 | None, |
784 | ); |
785 | |
786 | Ok(Image::from_image(pixmap, cs)) |
787 | } |
788 | |
789 | fn apply_merge( |
790 | fe: &usvg::filter::Merge, |
791 | cs: usvg::filter::ColorInterpolation, |
792 | region: IntRect, |
793 | source: &tiny_skia::Pixmap, |
794 | results: &[FilterResult], |
795 | ) -> Result<Image, Error> { |
796 | let mut pixmap: Pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; |
797 | |
798 | for input: &Input in fe.inputs() { |
799 | let input: Image = get_input(input, region, source, results)?; |
800 | let input: Image = input.into_color_space(cs)?; |
801 | pixmap.draw_pixmap( |
802 | x:0, |
803 | y:0, |
804 | pixmap:input.as_ref().as_ref(), |
805 | &tiny_skia::PixmapPaint::default(), |
806 | tiny_skia::Transform::identity(), |
807 | mask:None, |
808 | ); |
809 | } |
810 | |
811 | Ok(Image::from_image(image:pixmap, color_space:cs)) |
812 | } |
813 | |
814 | fn apply_flood(fe: &usvg::filter::Flood, region: IntRect) -> Result<Image, Error> { |
815 | let c: Color = fe.color(); |
816 | |
817 | let mut pixmap: Pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; |
818 | pixmap.fill(tiny_skia::Color::from_rgba8( |
819 | r:c.red, |
820 | g:c.green, |
821 | b:c.blue, |
822 | a:fe.opacity().to_u8(), |
823 | )); |
824 | |
825 | Ok(Image::from_image( |
826 | image:pixmap, |
827 | color_space:usvg::filter::ColorInterpolation::SRGB, |
828 | )) |
829 | } |
830 | |
831 | fn apply_tile(input: Image, region: IntRect) -> Result<Image, Error> { |
832 | let subregion: IntRect = input.region.translate(-region.x(), -region.y()).unwrap(); |
833 | |
834 | let tile_pixmap: Pixmap = input.image.copy_region(subregion)?; |
835 | let mut paint: Paint<'_> = tiny_skia::Paint::default(); |
836 | paint.shader = tiny_skia::Pattern::new( |
837 | tile_pixmap.as_ref(), |
838 | tiny_skia::SpreadMode::Repeat, |
839 | quality:tiny_skia::FilterQuality::Bicubic, |
840 | opacity:1.0, |
841 | tiny_skia::Transform::from_translate(tx:subregion.x() as f32, ty:subregion.y() as f32), |
842 | ); |
843 | |
844 | let mut pixmap: Pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; |
845 | let rect: Rect = tiny_skiaOption::Rect::from_xywh(x:0.0, y:0.0, w:region.width() as f32, h:region.height() as f32) |
846 | .unwrap(); |
847 | pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), mask:None); |
848 | |
849 | Ok(Image::from_image( |
850 | image:pixmap, |
851 | color_space:usvg::filter::ColorInterpolation::SRGB, |
852 | )) |
853 | } |
854 | |
855 | fn apply_image( |
856 | fe: &usvg::filter::Image, |
857 | region: IntRect, |
858 | subregion: IntRect, |
859 | ts: usvg::Transform, |
860 | ) -> Result<Image, Error> { |
861 | let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; |
862 | |
863 | match fe.data() { |
864 | usvg::filter::ImageKind::Image(ref kind) => { |
865 | let dx = (subregion.x() - region.x()) as f32; |
866 | let dy = (subregion.y() - region.y()) as f32; |
867 | let transform = tiny_skia::Transform::from_translate(dx, dy); |
868 | |
869 | let view_box = usvg::ViewBox { |
870 | rect: subregion |
871 | .translate_to(0, 0) |
872 | .unwrap() |
873 | .to_rect() |
874 | .to_non_zero_rect() |
875 | .unwrap(), |
876 | aspect: fe.aspect(), |
877 | }; |
878 | |
879 | crate::image::render_inner( |
880 | kind, |
881 | view_box, |
882 | transform, |
883 | fe.rendering_mode(), |
884 | &mut pixmap.as_mut(), |
885 | ); |
886 | } |
887 | usvg::filter::ImageKind::Use(ref node) => { |
888 | let (sx, sy) = ts.get_scale(); |
889 | |
890 | let transform = tiny_skia::Transform::from_row( |
891 | sx, |
892 | 0.0, |
893 | 0.0, |
894 | sy, |
895 | subregion.x() as f32, |
896 | subregion.y() as f32, |
897 | ); |
898 | |
899 | let ctx = crate::render::Context { |
900 | max_bbox: tiny_skia::IntRect::from_xywh(0, 0, region.width(), region.height()) |
901 | .unwrap(), |
902 | }; |
903 | |
904 | crate::render::render_nodes(node, &ctx, transform, &mut pixmap.as_mut()); |
905 | } |
906 | } |
907 | |
908 | Ok(Image::from_image( |
909 | pixmap, |
910 | usvg::filter::ColorInterpolation::SRGB, |
911 | )) |
912 | } |
913 | |
914 | fn apply_component_transfer( |
915 | fe: &usvg::filter::ComponentTransfer, |
916 | cs: usvg::filter::ColorInterpolation, |
917 | input: Image, |
918 | ) -> Result<Image, Error> { |
919 | let mut pixmap: Pixmap = input.into_color_space(cs)?.take()?; |
920 | |
921 | demultiply_alpha(data:pixmap.data_mut().as_rgba_mut()); |
922 | component_transfer::apply(fe, src:pixmap.as_image_ref_mut()); |
923 | multiply_alpha(data:pixmap.data_mut().as_rgba_mut()); |
924 | |
925 | Ok(Image::from_image(image:pixmap, color_space:cs)) |
926 | } |
927 | |
928 | fn apply_color_matrix( |
929 | fe: &usvg::filter::ColorMatrix, |
930 | cs: usvg::filter::ColorInterpolation, |
931 | input: Image, |
932 | ) -> Result<Image, Error> { |
933 | let mut pixmap: Pixmap = input.into_color_space(cs)?.take()?; |
934 | |
935 | demultiply_alpha(data:pixmap.data_mut().as_rgba_mut()); |
936 | color_matrix::apply(matrix:fe.kind(), src:pixmap.as_image_ref_mut()); |
937 | multiply_alpha(data:pixmap.data_mut().as_rgba_mut()); |
938 | |
939 | Ok(Image::from_image(image:pixmap, color_space:cs)) |
940 | } |
941 | |
942 | fn apply_convolve_matrix( |
943 | fe: &usvg::filter::ConvolveMatrix, |
944 | cs: usvg::filter::ColorInterpolation, |
945 | input: Image, |
946 | ) -> Result<Image, Error> { |
947 | let mut pixmap: Pixmap = input.into_color_space(cs)?.take()?; |
948 | |
949 | if fe.preserve_alpha() { |
950 | demultiply_alpha(data:pixmap.data_mut().as_rgba_mut()); |
951 | } |
952 | |
953 | convolve_matrix::apply(matrix:fe, src:pixmap.as_image_ref_mut()); |
954 | |
955 | Ok(Image::from_image(image:pixmap, color_space:cs)) |
956 | } |
957 | |
958 | fn apply_morphology( |
959 | fe: &usvg::filter::Morphology, |
960 | cs: usvg::filter::ColorInterpolation, |
961 | ts: usvg::Transform, |
962 | input: Image, |
963 | ) -> Result<Image, Error> { |
964 | let mut pixmap: Pixmap = input.into_color_space(cs)?.take()?; |
965 | |
966 | let (rx: f32, ry: f32) = match scale_coordinates(x:fe.radius_x().get(), y:fe.radius_y().get(), ts) { |
967 | Some(v: (f32, f32)) => v, |
968 | None => return Ok(Image::from_image(image:pixmap, color_space:cs)), |
969 | }; |
970 | |
971 | if !(rx > 0.0 && ry > 0.0) { |
972 | pixmap.clear(); |
973 | return Ok(Image::from_image(image:pixmap, color_space:cs)); |
974 | } |
975 | |
976 | morphology::apply(fe.operator(), rx, ry, src:pixmap.as_image_ref_mut()); |
977 | |
978 | Ok(Image::from_image(image:pixmap, color_space:cs)) |
979 | } |
980 | |
981 | fn apply_displacement_map( |
982 | fe: &usvg::filter::DisplacementMap, |
983 | region: IntRect, |
984 | cs: usvg::filter::ColorInterpolation, |
985 | ts: usvg::Transform, |
986 | input1: Image, |
987 | input2: Image, |
988 | ) -> Result<Image, Error> { |
989 | let pixmap1: Pixmap = input1.into_color_space(cs)?.take()?; |
990 | let pixmap2: Pixmap = input2.into_color_space(cs)?.take()?; |
991 | |
992 | let mut pixmap: Pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; |
993 | |
994 | let (sx: f32, sy: f32) = match scale_coordinates(x:fe.scale(), y:fe.scale(), ts) { |
995 | Some(v: (f32, f32)) => v, |
996 | None => return Ok(Image::from_image(image:pixmap1, color_space:cs)), |
997 | }; |
998 | |
999 | displacement_map::apply( |
1000 | fe, |
1001 | sx, |
1002 | sy, |
1003 | src:pixmap1.as_image_ref(), |
1004 | map:pixmap2.as_image_ref(), |
1005 | dest:pixmap.as_image_ref_mut(), |
1006 | ); |
1007 | |
1008 | Ok(Image::from_image(image:pixmap, color_space:cs)) |
1009 | } |
1010 | |
1011 | fn apply_turbulence( |
1012 | fe: &usvg::filter::Turbulence, |
1013 | region: IntRect, |
1014 | cs: usvg::filter::ColorInterpolation, |
1015 | ts: usvg::Transform, |
1016 | ) -> Result<Image, Error> { |
1017 | let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; |
1018 | |
1019 | let (sx, sy) = ts.get_scale(); |
1020 | if sx.approx_zero_ulps(4) || sy.approx_zero_ulps(4) { |
1021 | return Ok(Image::from_image(pixmap, cs)); |
1022 | } |
1023 | |
1024 | turbulence::apply( |
1025 | region.x() as f64 - ts.tx as f64, |
1026 | region.y() as f64 - ts.ty as f64, |
1027 | sx as f64, |
1028 | sy as f64, |
1029 | fe.base_frequency_x().get() as f64, |
1030 | fe.base_frequency_y().get() as f64, |
1031 | fe.num_octaves(), |
1032 | fe.seed(), |
1033 | fe.stitch_tiles(), |
1034 | fe.kind() == usvg::filter::TurbulenceKind::FractalNoise, |
1035 | pixmap.as_image_ref_mut(), |
1036 | ); |
1037 | |
1038 | multiply_alpha(pixmap.data_mut().as_rgba_mut()); |
1039 | |
1040 | Ok(Image::from_image(pixmap, cs)) |
1041 | } |
1042 | |
1043 | fn apply_diffuse_lighting( |
1044 | fe: &usvg::filter::DiffuseLighting, |
1045 | region: IntRect, |
1046 | cs: usvg::filter::ColorInterpolation, |
1047 | ts: usvg::Transform, |
1048 | input: Image, |
1049 | ) -> Result<Image, Error> { |
1050 | let mut pixmap: Pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; |
1051 | |
1052 | let light_source: LightSource = transform_light_source(fe.light_source(), region, ts); |
1053 | |
1054 | lighting::diffuse_lighting( |
1055 | fe, |
1056 | light_source, |
1057 | src:input.as_ref().as_image_ref(), |
1058 | dest:pixmap.as_image_ref_mut(), |
1059 | ); |
1060 | |
1061 | Ok(Image::from_image(image:pixmap, color_space:cs)) |
1062 | } |
1063 | |
1064 | fn apply_specular_lighting( |
1065 | fe: &usvg::filter::SpecularLighting, |
1066 | region: IntRect, |
1067 | cs: usvg::filter::ColorInterpolation, |
1068 | ts: usvg::Transform, |
1069 | input: Image, |
1070 | ) -> Result<Image, Error> { |
1071 | let mut pixmap: Pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; |
1072 | |
1073 | let light_source: LightSource = transform_light_source(fe.light_source(), region, ts); |
1074 | |
1075 | lighting::specular_lighting( |
1076 | fe, |
1077 | light_source, |
1078 | src:input.as_ref().as_image_ref(), |
1079 | dest:pixmap.as_image_ref_mut(), |
1080 | ); |
1081 | |
1082 | Ok(Image::from_image(image:pixmap, color_space:cs)) |
1083 | } |
1084 | |
1085 | // TODO: do not modify LightSource |
1086 | fn transform_light_source( |
1087 | mut source: usvg::filter::LightSource, |
1088 | region: IntRect, |
1089 | ts: usvg::Transform, |
1090 | ) -> usvg::filter::LightSource { |
1091 | use std::f32::consts::SQRT_2; |
1092 | use usvg::filter::LightSource; |
1093 | |
1094 | match source { |
1095 | LightSource::DistantLight(..) => {} |
1096 | LightSource::PointLight(ref mut light) => { |
1097 | let mut point = tiny_skia::Point::from_xy(light.x, light.y); |
1098 | ts.map_point(&mut point); |
1099 | light.x = point.x - region.x() as f32; |
1100 | light.y = point.y - region.y() as f32; |
1101 | light.z = light.z * (ts.sx * ts.sx + ts.sy * ts.sy).sqrt() / SQRT_2; |
1102 | } |
1103 | LightSource::SpotLight(ref mut light) => { |
1104 | let sz = (ts.sx * ts.sx + ts.sy * ts.sy).sqrt() / SQRT_2; |
1105 | |
1106 | let mut point = tiny_skia::Point::from_xy(light.x, light.y); |
1107 | ts.map_point(&mut point); |
1108 | light.x = point.x - region.x() as f32; |
1109 | light.y = point.y - region.x() as f32; |
1110 | light.z *= sz; |
1111 | |
1112 | let mut point = tiny_skia::Point::from_xy(light.points_at_x, light.points_at_y); |
1113 | ts.map_point(&mut point); |
1114 | light.points_at_x = point.x - region.x() as f32; |
1115 | light.points_at_y = point.y - region.x() as f32; |
1116 | light.points_at_z *= sz; |
1117 | } |
1118 | } |
1119 | |
1120 | source |
1121 | } |
1122 | |
1123 | fn apply_to_canvas(input: Image, pixmap: &mut tiny_skia::Pixmap) -> Result<(), Error> { |
1124 | let input: Image = input.into_color_space(usvg::filter::ColorInterpolation::SRGB)?; |
1125 | |
1126 | pixmap.fill(color:tiny_skia::Color::TRANSPARENT); |
1127 | pixmap.draw_pixmap( |
1128 | x:0, |
1129 | y:0, |
1130 | pixmap:input.as_ref().as_ref(), |
1131 | &tiny_skia::PixmapPaint::default(), |
1132 | tiny_skia::Transform::identity(), |
1133 | mask:None, |
1134 | ); |
1135 | |
1136 | Ok(()) |
1137 | } |
1138 | |
1139 | /// Calculates Gaussian blur sigmas for the current world transform. |
1140 | /// |
1141 | /// If the last flag is set, then a box blur should be used. Or IIR otherwise. |
1142 | fn resolve_std_dev(std_dx: f32, std_dy: f32, ts: usvg::Transform) -> Option<(f64, f64, bool)> { |
1143 | let (mut std_dx: f32, mut std_dy: f32) = scale_coordinates(x:std_dx, y:std_dy, ts)?; |
1144 | |
1145 | // 'A negative value or a value of zero disables the effect of the given filter primitive |
1146 | // (i.e., the result is the filter input image).' |
1147 | if std_dx.approx_eq_ulps(&0.0, ulps:4) && std_dy.approx_eq_ulps(&0.0, ulps:4) { |
1148 | return None; |
1149 | } |
1150 | |
1151 | // Ignore tiny sigmas. In case of IIR blur it can lead to a transparent image. |
1152 | if std_dx < 0.05 { |
1153 | std_dx = 0.0; |
1154 | } |
1155 | |
1156 | if std_dy < 0.05 { |
1157 | std_dy = 0.0; |
1158 | } |
1159 | |
1160 | const BLUR_SIGMA_THRESHOLD: f32 = 2.0; |
1161 | // Check that the current feGaussianBlur filter can be applied using a box blur. |
1162 | let box_blur: bool = std_dx >= BLUR_SIGMA_THRESHOLD || std_dy >= BLUR_SIGMA_THRESHOLD; |
1163 | |
1164 | Some((std_dx as f64, std_dy as f64, box_blur)) |
1165 | } |
1166 | |
1167 | fn scale_coordinates(x: f32, y: f32, ts: usvg::Transform) -> Option<(f32, f32)> { |
1168 | let (sx: f32, sy: f32) = ts.get_scale(); |
1169 | Some((x * sx, y * sy)) |
1170 | } |
1171 | |