Skip to main content

resvg/filter/
color_matrix.rs

1// Copyright 2020 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use super::{ImageRefMut, f32_bound};
5use rgb::RGBA8;
6use usvg::filter::ColorMatrixKind as ColorMatrix;
7
8/// Applies a color matrix filter.
9///
10/// Input image pixels should have an **unpremultiplied alpha**.
11pub fn apply(matrix: &ColorMatrix, src: ImageRefMut) {
12    match matrix {
13        ColorMatrix::Matrix(m) => {
14            for pixel in src.data {
15                let (r, g, b, a) = to_normalized_components(*pixel);
16
17                let new_r = r * m[0] + g * m[1] + b * m[2] + a * m[3] + m[4];
18                let new_g = r * m[5] + g * m[6] + b * m[7] + a * m[8] + m[9];
19                let new_b = r * m[10] + g * m[11] + b * m[12] + a * m[13] + m[14];
20                let new_a = r * m[15] + g * m[16] + b * m[17] + a * m[18] + m[19];
21
22                pixel.r = from_normalized(new_r);
23                pixel.g = from_normalized(new_g);
24                pixel.b = from_normalized(new_b);
25                pixel.a = from_normalized(new_a);
26            }
27        }
28        ColorMatrix::Saturate(v) => {
29            let v = v.get().max(0.0);
30            let m = [
31                0.213 + 0.787 * v,
32                0.715 - 0.715 * v,
33                0.072 - 0.072 * v,
34                0.213 - 0.213 * v,
35                0.715 + 0.285 * v,
36                0.072 - 0.072 * v,
37                0.213 - 0.213 * v,
38                0.715 - 0.715 * v,
39                0.072 + 0.928 * v,
40            ];
41
42            for pixel in src.data {
43                let (r, g, b, _) = to_normalized_components(*pixel);
44
45                let new_r = r * m[0] + g * m[1] + b * m[2];
46                let new_g = r * m[3] + g * m[4] + b * m[5];
47                let new_b = r * m[6] + g * m[7] + b * m[8];
48
49                pixel.r = from_normalized(new_r);
50                pixel.g = from_normalized(new_g);
51                pixel.b = from_normalized(new_b);
52            }
53        }
54        ColorMatrix::HueRotate(angle) => {
55            let angle = angle.to_radians();
56            let a1 = angle.cos();
57            let a2 = angle.sin();
58            let m = [
59                0.213 + 0.787 * a1 - 0.213 * a2,
60                0.715 - 0.715 * a1 - 0.715 * a2,
61                0.072 - 0.072 * a1 + 0.928 * a2,
62                0.213 - 0.213 * a1 + 0.143 * a2,
63                0.715 + 0.285 * a1 + 0.140 * a2,
64                0.072 - 0.072 * a1 - 0.283 * a2,
65                0.213 - 0.213 * a1 - 0.787 * a2,
66                0.715 - 0.715 * a1 + 0.715 * a2,
67                0.072 + 0.928 * a1 + 0.072 * a2,
68            ];
69
70            for pixel in src.data {
71                let (r, g, b, _) = to_normalized_components(*pixel);
72
73                let new_r = r * m[0] + g * m[1] + b * m[2];
74                let new_g = r * m[3] + g * m[4] + b * m[5];
75                let new_b = r * m[6] + g * m[7] + b * m[8];
76
77                pixel.r = from_normalized(new_r);
78                pixel.g = from_normalized(new_g);
79                pixel.b = from_normalized(new_b);
80            }
81        }
82        ColorMatrix::LuminanceToAlpha => {
83            for pixel in src.data {
84                let (r, g, b, _) = to_normalized_components(*pixel);
85
86                let new_a = r * 0.2125 + g * 0.7154 + b * 0.0721;
87
88                pixel.r = 0;
89                pixel.g = 0;
90                pixel.b = 0;
91                pixel.a = from_normalized(new_a);
92            }
93        }
94    }
95}
96
97#[inline]
98fn to_normalized_components(pixel: RGBA8) -> (f32, f32, f32, f32) {
99    (
100        pixel.r as f32 / 255.0,
101        pixel.g as f32 / 255.0,
102        pixel.b as f32 / 255.0,
103        pixel.a as f32 / 255.0,
104    )
105}
106
107#[inline]
108fn from_normalized(c: f32) -> u8 {
109    (f32_bound(0.0, c, 1.0) * 255.0) as u8
110}