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}
113