resvg/filter/
mod.rs

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