rsvg/filters/
color_matrix.rs

1use cssparser::Parser;
2use markup5ever::{expanded_name, local_name, ns, QualName};
3use nalgebra::{Matrix3, Matrix4x5, Matrix5, Vector5};
4
5use crate::document::AcquiredNodes;
6use crate::element::{set_attribute, ElementTrait};
7use crate::error::*;
8use crate::node::{CascadedValues, Node};
9use crate::parse_identifiers;
10use crate::parsers::{CommaSeparatedList, Parse, ParseValue};
11use crate::properties::ColorInterpolationFilters;
12use crate::rect::IRect;
13use crate::rsvg_log;
14use crate::session::Session;
15use crate::surface_utils::{
16    iterators::Pixels, shared_surface::ExclusiveImageSurface, ImageSurfaceDataExt, Pixel,
17};
18use crate::util::clamp;
19use crate::xml::Attributes;
20
21use super::bounds::BoundsBuilder;
22use super::context::{FilterContext, FilterOutput};
23use super::{
24    FilterEffect, FilterError, FilterResolveError, Input, InputRequirements, Primitive,
25    PrimitiveParams, ResolvedPrimitive,
26};
27
28/// Color matrix operation types.
29#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
30enum OperationType {
31    #[default]
32    Matrix,
33    Saturate,
34    HueRotate,
35    LuminanceToAlpha,
36}
37
38/// The `feColorMatrix` filter primitive.
39#[derive(Default)]
40pub struct FeColorMatrix {
41    base: Primitive,
42    params: ColorMatrix,
43}
44
45/// Resolved `feColorMatrix` primitive for rendering.
46#[derive(Clone)]
47pub struct ColorMatrix {
48    pub in1: Input,
49    pub matrix: Matrix5<f64>,
50    pub color_interpolation_filters: ColorInterpolationFilters,
51}
52
53impl Default for ColorMatrix {
54    fn default() -> ColorMatrix {
55        ColorMatrix {
56            in1: Default::default(),
57            color_interpolation_filters: Default::default(),
58
59            // nalgebra's Default for Matrix5 is all zeroes, so we actually need this :(
60            matrix: Matrix5::identity(),
61        }
62    }
63}
64
65impl ElementTrait for FeColorMatrix {
66    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
67        self.params.in1 = self.base.parse_one_input(attrs, session);
68
69        // First, determine the operation type.
70        let mut operation_type = Default::default();
71        for (attr, value) in attrs
72            .iter()
73            .filter(|(attr, _)| attr.expanded() == expanded_name!("", "type"))
74        {
75            set_attribute(&mut operation_type, attr.parse(value), session);
76        }
77
78        // Now read the matrix correspondingly.
79        //
80        // Here we cannot assume that ColorMatrix::default() has provided the correct
81        // initial value for the matrix itself, since the initial value for the matrix
82        // (i.e. the value to which it should fall back if the `values` attribute is in
83        // error) depends on the operation_type.
84        //
85        // So, for each operation_type, first initialize the proper default matrix, then
86        // try to parse the value.
87
88        use OperationType::*;
89
90        self.params.matrix = match operation_type {
91            Matrix => ColorMatrix::default_matrix(),
92            Saturate => ColorMatrix::saturate_matrix(1.0),
93            HueRotate => ColorMatrix::hue_rotate_matrix(0.0),
94            LuminanceToAlpha => ColorMatrix::luminance_to_alpha_matrix(),
95        };
96
97        for (attr, value) in attrs
98            .iter()
99            .filter(|(attr, _)| attr.expanded() == expanded_name!("", "values"))
100        {
101            match operation_type {
102                Matrix => parse_matrix(&mut self.params.matrix, attr, value, session),
103                Saturate => parse_saturate_matrix(&mut self.params.matrix, attr, value, session),
104                HueRotate => parse_hue_rotate_matrix(&mut self.params.matrix, attr, value, session),
105                LuminanceToAlpha => {
106                    parse_luminance_to_alpha_matrix(&mut self.params.matrix, attr, value, session)
107                }
108            }
109        }
110    }
111}
112
113fn parse_matrix(dest: &mut Matrix5<f64>, attr: QualName, value: &str, session: &Session) {
114    let parsed: Result<CommaSeparatedList<f64, 20, 20>, _> = attr.parse(value);
115
116    match parsed {
117        Ok(CommaSeparatedList(v)) => {
118            let matrix = Matrix4x5::from_row_slice(&v);
119            let mut matrix = matrix.fixed_resize(0.0);
120            matrix[(4, 4)] = 1.0;
121            *dest = matrix;
122        }
123
124        Err(e) => {
125            rsvg_log!(session, "element feColorMatrix with type=\"matrix\", expected a values attribute with 20 numbers: {}", e);
126        }
127    }
128}
129
130fn parse_saturate_matrix(dest: &mut Matrix5<f64>, attr: QualName, value: &str, session: &Session) {
131    let parsed: Result<f64, _> = attr.parse(value);
132
133    match parsed {
134        Ok(s) => {
135            *dest = ColorMatrix::saturate_matrix(s);
136        }
137
138        Err(e) => {
139            rsvg_log!(session, "element feColorMatrix with type=\"saturate\", expected a values attribute with 1 number: {}", e);
140        }
141    }
142}
143
144fn parse_hue_rotate_matrix(
145    dest: &mut Matrix5<f64>,
146    attr: QualName,
147    value: &str,
148    session: &Session,
149) {
150    let parsed: Result<f64, _> = attr.parse(value);
151
152    match parsed {
153        Ok(degrees) => {
154            *dest = ColorMatrix::hue_rotate_matrix(degrees.to_radians());
155        }
156
157        Err(e) => {
158            rsvg_log!(session, "element feColorMatrix with type=\"hueRotate\", expected a values attribute with 1 number: {}", e);
159        }
160    }
161}
162
163fn parse_luminance_to_alpha_matrix(
164    _dest: &mut Matrix5<f64>,
165    _attr: QualName,
166    _value: &str,
167    session: &Session,
168) {
169    // There's nothing to parse, since our caller already supplied the default value,
170    // and type="luminanceToAlpha" does not takes a `values` attribute.  So, just warn
171    // that the value is being ignored.
172
173    rsvg_log!(
174        session,
175        "ignoring \"values\" attribute for feColorMatrix with type=\"luminanceToAlpha\""
176    );
177}
178
179impl ColorMatrix {
180    pub fn render(
181        &self,
182        bounds_builder: BoundsBuilder,
183        ctx: &FilterContext,
184    ) -> Result<FilterOutput, FilterError> {
185        let input_1 = ctx.get_input(&self.in1, self.color_interpolation_filters)?;
186        let bounds: IRect = bounds_builder
187            .add_input(&input_1)
188            .compute(ctx)
189            .clipped
190            .into();
191
192        let mut surface = ExclusiveImageSurface::new(
193            ctx.source_graphic().width(),
194            ctx.source_graphic().height(),
195            input_1.surface().surface_type(),
196        )?;
197
198        surface.modify(&mut |data, stride| {
199            for (x, y, pixel) in Pixels::within(input_1.surface(), bounds) {
200                let alpha = f64::from(pixel.a) / 255f64;
201
202                let pixel_vec = if alpha == 0.0 {
203                    Vector5::new(0.0, 0.0, 0.0, 0.0, 1.0)
204                } else {
205                    Vector5::new(
206                        f64::from(pixel.r) / 255f64 / alpha,
207                        f64::from(pixel.g) / 255f64 / alpha,
208                        f64::from(pixel.b) / 255f64 / alpha,
209                        alpha,
210                        1.0,
211                    )
212                };
213                let mut new_pixel_vec = Vector5::zeros();
214                self.matrix.mul_to(&pixel_vec, &mut new_pixel_vec);
215
216                let new_alpha = clamp(new_pixel_vec[3], 0.0, 1.0);
217
218                let premultiply = |x: f64| ((clamp(x, 0.0, 1.0) * new_alpha * 255f64) + 0.5) as u8;
219
220                let output_pixel = Pixel {
221                    r: premultiply(new_pixel_vec[0]),
222                    g: premultiply(new_pixel_vec[1]),
223                    b: premultiply(new_pixel_vec[2]),
224                    a: ((new_alpha * 255f64) + 0.5) as u8,
225                };
226
227                data.set_pixel(stride, output_pixel, x, y);
228            }
229        });
230
231        Ok(FilterOutput {
232            surface: surface.share()?,
233            bounds,
234        })
235    }
236
237    /// Compute a `type="hueRotate"` matrix.
238    ///
239    /// <https://drafts.fxtf.org/filter-effects/#element-attrdef-fecolormatrix-values>
240    #[rustfmt::skip]
241    pub fn hue_rotate_matrix(radians: f64) -> Matrix5<f64> {
242        let (sin, cos) = radians.sin_cos();
243
244        let a = Matrix3::new(
245            0.213, 0.715, 0.072,
246            0.213, 0.715, 0.072,
247            0.213, 0.715, 0.072,
248        );
249
250        let b = Matrix3::new(
251             0.787, -0.715, -0.072,
252            -0.213,  0.285, -0.072,
253            -0.213, -0.715,  0.928,
254        );
255
256        let c = Matrix3::new(
257            -0.213, -0.715,  0.928,
258             0.143,  0.140, -0.283,
259            -0.787,  0.715,  0.072,
260        );
261
262        let top_left = a + b * cos + c * sin;
263
264        let mut matrix = top_left.fixed_resize(0.0);
265        matrix[(3, 3)] = 1.0;
266        matrix[(4, 4)] = 1.0;
267        matrix
268    }
269
270    /// Compute a `type="luminanceToAlpha"` matrix.
271    ///
272    /// <https://drafts.fxtf.org/filter-effects/#element-attrdef-fecolormatrix-values>
273    #[rustfmt::skip]
274    fn luminance_to_alpha_matrix() -> Matrix5<f64> {
275        Matrix5::new(
276            0.0,    0.0,    0.0,    0.0, 0.0,
277            0.0,    0.0,    0.0,    0.0, 0.0,
278            0.0,    0.0,    0.0,    0.0, 0.0,
279            0.2126, 0.7152, 0.0722, 0.0, 0.0,
280            0.0,    0.0,    0.0,    0.0, 1.0,
281        )
282    }
283
284    /// Compute a `type="saturate"` matrix.
285    ///
286    /// <https://drafts.fxtf.org/filter-effects/#element-attrdef-fecolormatrix-values>
287    #[rustfmt::skip]
288    fn saturate_matrix(s: f64) -> Matrix5<f64> {
289        Matrix5::new(
290            0.213 + 0.787 * s, 0.715 - 0.715 * s, 0.072 - 0.072 * s, 0.0, 0.0,
291            0.213 - 0.213 * s, 0.715 + 0.285 * s, 0.072 - 0.072 * s, 0.0, 0.0,
292            0.213 - 0.213 * s, 0.715 - 0.715 * s, 0.072 + 0.928 * s, 0.0, 0.0,
293            0.0,               0.0,               0.0,               1.0, 0.0,
294            0.0,               0.0,               0.0,               0.0, 1.0,
295        )
296    }
297
298    /// Default for `type="matrix"`.
299    ///
300    /// <https://drafts.fxtf.org/filter-effects/#element-attrdef-fecolormatrix-values>
301    fn default_matrix() -> Matrix5<f64> {
302        Matrix5::identity()
303    }
304
305    pub fn get_input_requirements(&self) -> InputRequirements {
306        self.in1.get_requirements()
307    }
308}
309
310impl FilterEffect for FeColorMatrix {
311    fn resolve(
312        &self,
313        _acquired_nodes: &mut AcquiredNodes<'_>,
314        node: &Node,
315    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
316        let cascaded = CascadedValues::new_from_node(node);
317        let values = cascaded.get();
318
319        let mut params = self.params.clone();
320        params.color_interpolation_filters = values.color_interpolation_filters();
321
322        Ok(vec![ResolvedPrimitive {
323            primitive: self.base.clone(),
324            params: PrimitiveParams::ColorMatrix(params),
325        }])
326    }
327}
328
329impl Parse for OperationType {
330    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
331        Ok(parse_identifiers!(
332            parser,
333            "matrix" => OperationType::Matrix,
334            "saturate" => OperationType::Saturate,
335            "hueRotate" => OperationType::HueRotate,
336            "luminanceToAlpha" => OperationType::LuminanceToAlpha,
337        )?)
338    }
339}