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