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