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