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