1// Copyright 2018 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use std::rc::Rc;
5
6use rgb::{FromSlice, RGBA8};
7use tiny_skia::IntRect;
8use usvg::{ApproxEqUlps, ApproxZeroUlps};
9
10mod box_blur;
11mod color_matrix;
12mod component_transfer;
13mod composite;
14mod convolve_matrix;
15mod displacement_map;
16mod iir_blur;
17mod lighting;
18mod morphology;
19mod 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)]
30pub struct ImageRef<'a> {
31 data: &'a [RGBA8],
32 width: u32,
33 height: u32,
34}
35
36impl<'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.
56pub struct ImageRefMut<'a> {
57 data: &'a mut [RGBA8],
58 width: u32,
59 height: u32,
60}
61
62impl<'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)]
87pub(crate) enum Error {
88 InvalidRegion,
89 NoResults,
90}
91
92trait 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
100impl 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.
129fn 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.
139fn 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]
162const 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]
195const 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.
219fn 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.
232fn 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]
242fn 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)]
257struct 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
276impl 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
330struct FilterResult {
331 name: String,
332 image: Image,
333}
334
335pub 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
357fn 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
516fn 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
559trait PixmapToImageRef<'a> {
560 fn as_image_ref(&'a self) -> ImageRef<'a>;
561 fn as_image_ref_mut(&'a mut self) -> ImageRefMut<'a>;
562}
563
564impl<'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
574fn 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
638fn 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
661fn 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
688fn 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
724fn 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
788fn 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
813fn 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
830fn 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
854fn 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
884fn 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
898fn 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
912fn 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
928fn 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
951fn 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
981fn 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
1013fn 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
1034fn 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
1056fn 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
1093fn 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.
1112fn 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
1137fn 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