resvg/filter/
convolve_matrix.rs

1// Copyright 2020 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use super::{f32_bound, ImageRefMut};
5use rgb::RGBA8;
6use usvg::filter::{ConvolveMatrix, EdgeMode};
7
8/// Applies a convolve matrix.
9///
10/// Input image pixels should have a **premultiplied alpha** when `preserve_alpha=false`.
11///
12/// # Allocations
13///
14/// This method will allocate a copy of the `src` image as a back buffer.
15pub fn apply(matrix: &ConvolveMatrix, src: ImageRefMut) {
16    fn bound(min: i32, val: i32, max: i32) -> i32 {
17        core::cmp::max(min, core::cmp::min(max, val))
18    }
19
20    let width_max = src.width as i32 - 1;
21    let height_max = src.height as i32 - 1;
22
23    let mut buf = vec![RGBA8::default(); src.data.len()];
24    let mut buf = ImageRefMut::new(src.width, src.height, &mut buf);
25    let mut x = 0;
26    let mut y = 0;
27    for in_p in src.data.iter() {
28        let mut new_r = 0.0;
29        let mut new_g = 0.0;
30        let mut new_b = 0.0;
31        let mut new_a = 0.0;
32        for oy in 0..matrix.matrix().rows() {
33            for ox in 0..matrix.matrix().columns() {
34                let mut tx = x as i32 - matrix.matrix().target_x() as i32 + ox as i32;
35                let mut ty = y as i32 - matrix.matrix().target_y() as i32 + oy as i32;
36
37                match matrix.edge_mode() {
38                    EdgeMode::None => {
39                        if tx < 0 || tx > width_max || ty < 0 || ty > height_max {
40                            continue;
41                        }
42                    }
43                    EdgeMode::Duplicate => {
44                        tx = bound(0, tx, width_max);
45                        ty = bound(0, ty, height_max);
46                    }
47                    EdgeMode::Wrap => {
48                        while tx < 0 {
49                            tx += src.width as i32;
50                        }
51                        tx %= src.width as i32;
52
53                        while ty < 0 {
54                            ty += src.height as i32;
55                        }
56                        ty %= src.height as i32;
57                    }
58                }
59
60                let k = matrix.matrix().get(
61                    matrix.matrix().columns() - ox - 1,
62                    matrix.matrix().rows() - oy - 1,
63                );
64
65                let p = src.pixel_at(tx as u32, ty as u32);
66                new_r += (p.r as f32) / 255.0 * k;
67                new_g += (p.g as f32) / 255.0 * k;
68                new_b += (p.b as f32) / 255.0 * k;
69
70                if !matrix.preserve_alpha() {
71                    new_a += (p.a as f32) / 255.0 * k;
72                }
73            }
74        }
75
76        if matrix.preserve_alpha() {
77            new_a = in_p.a as f32 / 255.0;
78        } else {
79            new_a = new_a / matrix.divisor().get() + matrix.bias();
80        }
81
82        let bounded_new_a = f32_bound(0.0, new_a, 1.0);
83
84        let calc = |x| {
85            let x = x / matrix.divisor().get() + matrix.bias() * new_a;
86
87            let x = if matrix.preserve_alpha() {
88                f32_bound(0.0, x, 1.0) * bounded_new_a
89            } else {
90                f32_bound(0.0, x, bounded_new_a)
91            };
92
93            (x * 255.0 + 0.5) as u8
94        };
95
96        let out_p = buf.pixel_at_mut(x, y);
97        out_p.r = calc(new_r);
98        out_p.g = calc(new_g);
99        out_p.b = calc(new_b);
100        out_p.a = (bounded_new_a * 255.0 + 0.5) as u8;
101
102        x += 1;
103        if x == src.width {
104            x = 0;
105            y += 1;
106        }
107    }
108
109    // Do not use `mem::swap` because `data` referenced via FFI.
110    src.data.copy_from_slice(buf.data);
111}