resvg/filter/
mod.rs

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 in data {
131        let a = 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 in data {
141        let a = 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 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 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 = apply_inner(filter, ts, source);
341    let result = result.and_then(|image| apply_to_canvas(image, source));
342
343    // Clear on error.
344    if result.is_err() {
345        source.fill(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, std_dy, use_box_blur) =
645        match resolve_std_dev(fe.std_dev_x().get(), fe.std_dev_y().get(), ts) {
646            Some(v) => v,
647            None => return Ok(input),
648        };
649
650    let mut pixmap = input.into_color_space(cs)?.take()?;
651
652    if use_box_blur {
653        box_blur::apply(std_dx, std_dy, pixmap.as_image_ref_mut());
654    } else {
655        iir_blur::apply(std_dx, std_dy, pixmap.as_image_ref_mut());
656    }
657
658    Ok(Image::from_image(pixmap, 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, dy) = match scale_coordinates(fe.dx(), fe.dy(), ts) {
667        Some(v) => 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 = tiny_skia::Pixmap::try_create(input.width(), input.height())?;
676    pixmap.draw_pixmap(
677        dx as i32,
678        dy as i32,
679        input.as_ref().as_ref(),
680        &tiny_skia::PixmapPaint::default(),
681        tiny_skia::Transform::identity(),
682        None,
683    );
684
685    Ok(Image::from_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 = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
796
797    for input in fe.inputs() {
798        let input = get_input(input, region, source, results)?;
799        let input = input.into_color_space(cs)?;
800        pixmap.draw_pixmap(
801            0,
802            0,
803            input.as_ref().as_ref(),
804            &tiny_skia::PixmapPaint::default(),
805            tiny_skia::Transform::identity(),
806            None,
807        );
808    }
809
810    Ok(Image::from_image(pixmap, cs))
811}
812
813fn apply_flood(fe: &usvg::filter::Flood, region: IntRect) -> Result<Image, Error> {
814    let c = fe.color();
815
816    let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
817    pixmap.fill(tiny_skia::Color::from_rgba8(
818        c.red,
819        c.green,
820        c.blue,
821        fe.opacity().to_u8(),
822    ));
823
824    Ok(Image::from_image(
825        pixmap,
826        usvg::filter::ColorInterpolation::SRGB,
827    ))
828}
829
830fn apply_tile(input: Image, region: IntRect) -> Result<Image, Error> {
831    let subregion = input.region.translate(-region.x(), -region.y()).unwrap();
832
833    let tile_pixmap = input.image.copy_region(subregion)?;
834    let mut paint = tiny_skia::Paint::default();
835    paint.shader = tiny_skia::Pattern::new(
836        tile_pixmap.as_ref(),
837        tiny_skia::SpreadMode::Repeat,
838        tiny_skia::FilterQuality::Bicubic,
839        1.0,
840        tiny_skia::Transform::from_translate(subregion.x() as f32, subregion.y() as f32),
841    );
842
843    let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
844    let rect = tiny_skia::Rect::from_xywh(0.0, 0.0, region.width() as f32, region.height() as f32)
845        .unwrap();
846    pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);
847
848    Ok(Image::from_image(
849        pixmap,
850        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 = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
861
862    let (sx, sy) = ts.get_scale();
863    let transform = tiny_skia::Transform::from_row(
864        sx,
865        0.0,
866        0.0,
867        sy,
868        subregion.x() as f32,
869        subregion.y() as f32,
870    );
871
872    let ctx = crate::render::Context {
873        max_bbox: tiny_skia::IntRect::from_xywh(0, 0, region.width(), region.height()).unwrap(),
874    };
875
876    crate::render::render_nodes(fe.root(), &ctx, transform, &mut pixmap.as_mut());
877
878    Ok(Image::from_image(
879        pixmap,
880        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 = input.into_color_space(cs)?.take()?;
890
891    demultiply_alpha(pixmap.data_mut().as_rgba_mut());
892    component_transfer::apply(fe, pixmap.as_image_ref_mut());
893    multiply_alpha(pixmap.data_mut().as_rgba_mut());
894
895    Ok(Image::from_image(pixmap, 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 = input.into_color_space(cs)?.take()?;
904
905    demultiply_alpha(pixmap.data_mut().as_rgba_mut());
906    color_matrix::apply(fe.kind(), pixmap.as_image_ref_mut());
907    multiply_alpha(pixmap.data_mut().as_rgba_mut());
908
909    Ok(Image::from_image(pixmap, 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 = input.into_color_space(cs)?.take()?;
918
919    if fe.preserve_alpha() {
920        demultiply_alpha(pixmap.data_mut().as_rgba_mut());
921    }
922
923    convolve_matrix::apply(fe, pixmap.as_image_ref_mut());
924
925    Ok(Image::from_image(pixmap, 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 = input.into_color_space(cs)?.take()?;
935
936    let (rx, ry) = match scale_coordinates(fe.radius_x().get(), fe.radius_y().get(), ts) {
937        Some(v) => v,
938        None => return Ok(Image::from_image(pixmap, cs)),
939    };
940
941    if !(rx > 0.0 && ry > 0.0) {
942        pixmap.clear();
943        return Ok(Image::from_image(pixmap, cs));
944    }
945
946    morphology::apply(fe.operator(), rx, ry, pixmap.as_image_ref_mut());
947
948    Ok(Image::from_image(pixmap, 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 = input1.into_color_space(cs)?.take()?;
960    let pixmap2 = input2.into_color_space(cs)?.take()?;
961
962    let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
963
964    let (sx, sy) = match scale_coordinates(fe.scale(), fe.scale(), ts) {
965        Some(v) => v,
966        None => return Ok(Image::from_image(pixmap1, cs)),
967    };
968
969    displacement_map::apply(
970        fe,
971        sx,
972        sy,
973        pixmap1.as_image_ref(),
974        pixmap2.as_image_ref(),
975        pixmap.as_image_ref_mut(),
976    );
977
978    Ok(Image::from_image(pixmap, 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 = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
1021
1022    let light_source = transform_light_source(fe.light_source(), region, ts);
1023
1024    lighting::diffuse_lighting(
1025        fe,
1026        light_source,
1027        input.as_ref().as_image_ref(),
1028        pixmap.as_image_ref_mut(),
1029    );
1030
1031    Ok(Image::from_image(pixmap, 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 = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
1042
1043    let light_source = transform_light_source(fe.light_source(), region, ts);
1044
1045    lighting::specular_lighting(
1046        fe,
1047        light_source,
1048        input.as_ref().as_image_ref(),
1049        pixmap.as_image_ref_mut(),
1050    );
1051
1052    Ok(Image::from_image(pixmap, 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 = input.into_color_space(usvg::filter::ColorInterpolation::SRGB)?;
1095
1096    pixmap.fill(tiny_skia::Color::TRANSPARENT);
1097    pixmap.draw_pixmap(
1098        0,
1099        0,
1100        input.as_ref().as_ref(),
1101        &tiny_skia::PixmapPaint::default(),
1102        tiny_skia::Transform::identity(),
1103        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, mut std_dy) = scale_coordinates(std_dx, 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, 4) && std_dy.approx_eq_ulps(&0.0, 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 = 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, sy) = ts.get_scale();
1139    Some((x * sx, y * sy))
1140}