rsvg/filters/
color_matrix.rs

1use cssparser::Parser;
2use markup5ever::{QualName, expanded_name, local_name, ns};
3use nalgebra::{Matrix3, Matrix4x5, Matrix5, Vector5};
4
5use crate::document::AcquiredNodes;
6use crate::element::{ElementTrait, set_attribute};
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    ImageSurfaceDataExt, Pixel, iterators::Pixels, shared_surface::ExclusiveImageSurface,
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!(
126                session,
127                "element feColorMatrix with type=\"matrix\", expected a values attribute with 20 numbers: {}",
128                e
129            );
130        }
131    }
132}
133
134fn parse_saturate_matrix(dest: &mut Matrix5<f64>, attr: QualName, value: &str, session: &Session) {
135    let parsed: Result<f64, _> = attr.parse(value);
136
137    match parsed {
138        Ok(s) => {
139            *dest = ColorMatrix::saturate_matrix(s);
140        }
141
142        Err(e) => {
143            rsvg_log!(
144                session,
145                "element feColorMatrix with type=\"saturate\", expected a values attribute with 1 number: {}",
146                e
147            );
148        }
149    }
150}
151
152fn parse_hue_rotate_matrix(
153    dest: &mut Matrix5<f64>,
154    attr: QualName,
155    value: &str,
156    session: &Session,
157) {
158    let parsed: Result<f64, _> = attr.parse(value);
159
160    match parsed {
161        Ok(degrees) => {
162            *dest = ColorMatrix::hue_rotate_matrix(degrees.to_radians());
163        }
164
165        Err(e) => {
166            rsvg_log!(
167                session,
168                "element feColorMatrix with type=\"hueRotate\", expected a values attribute with 1 number: {}",
169                e
170            );
171        }
172    }
173}
174
175fn parse_luminance_to_alpha_matrix(
176    _dest: &mut Matrix5<f64>,
177    _attr: QualName,
178    _value: &str,
179    session: &Session,
180) {
181    // There's nothing to parse, since our caller already supplied the default value,
182    // and type="luminanceToAlpha" does not takes a `values` attribute.  So, just warn
183    // that the value is being ignored.
184
185    rsvg_log!(
186        session,
187        "ignoring \"values\" attribute for feColorMatrix with type=\"luminanceToAlpha\""
188    );
189}
190
191impl ColorMatrix {
192    pub fn render(
193        &self,
194        bounds_builder: BoundsBuilder,
195        ctx: &FilterContext,
196    ) -> Result<FilterOutput, FilterError> {
197        let input_1 = ctx.get_input(&self.in1, self.color_interpolation_filters)?;
198        let bounds: IRect = bounds_builder
199            .add_input(&input_1)
200            .compute(ctx)
201            .clipped
202            .into();
203
204        let mut surface = ExclusiveImageSurface::new(
205            ctx.source_graphic().width(),
206            ctx.source_graphic().height(),
207            input_1.surface().surface_type(),
208        )?;
209
210        surface.modify(&mut |data, stride| {
211            for (x, y, pixel) in Pixels::within(input_1.surface(), bounds) {
212                let alpha = f64::from(pixel.a) / 255f64;
213
214                let pixel_vec = if alpha == 0.0 {
215                    Vector5::new(0.0, 0.0, 0.0, 0.0, 1.0)
216                } else {
217                    Vector5::new(
218                        f64::from(pixel.r) / 255f64 / alpha,
219                        f64::from(pixel.g) / 255f64 / alpha,
220                        f64::from(pixel.b) / 255f64 / alpha,
221                        alpha,
222                        1.0,
223                    )
224                };
225                let mut new_pixel_vec = Vector5::zeros();
226                self.matrix.mul_to(&pixel_vec, &mut new_pixel_vec);
227
228                let new_alpha = clamp(new_pixel_vec[3], 0.0, 1.0);
229
230                let premultiply = |x: f64| ((clamp(x, 0.0, 1.0) * new_alpha * 255f64) + 0.5) as u8;
231
232                let output_pixel = Pixel {
233                    r: premultiply(new_pixel_vec[0]),
234                    g: premultiply(new_pixel_vec[1]),
235                    b: premultiply(new_pixel_vec[2]),
236                    a: ((new_alpha * 255f64) + 0.5) as u8,
237                };
238
239                data.set_pixel(stride, output_pixel, x, y);
240            }
241        });
242
243        Ok(FilterOutput {
244            surface: surface.share()?,
245            bounds,
246        })
247    }
248
249    /// Compute a `type="hueRotate"` matrix.
250    ///
251    /// <https://drafts.fxtf.org/filter-effects/#element-attrdef-fecolormatrix-values>
252    #[rustfmt::skip]
253    pub fn hue_rotate_matrix(radians: f64) -> Matrix5<f64> {
254        let (sin, cos) = radians.sin_cos();
255
256        let a = Matrix3::new(
257            0.213, 0.715, 0.072,
258            0.213, 0.715, 0.072,
259            0.213, 0.715, 0.072,
260        );
261
262        let b = Matrix3::new(
263             0.787, -0.715, -0.072,
264            -0.213,  0.285, -0.072,
265            -0.213, -0.715,  0.928,
266        );
267
268        let c = Matrix3::new(
269            -0.213, -0.715,  0.928,
270             0.143,  0.140, -0.283,
271            -0.787,  0.715,  0.072,
272        );
273
274        let top_left = a + b * cos + c * sin;
275
276        let mut matrix = top_left.fixed_resize(0.0);
277        matrix[(3, 3)] = 1.0;
278        matrix[(4, 4)] = 1.0;
279        matrix
280    }
281
282    /// Compute a `type="luminanceToAlpha"` matrix.
283    ///
284    /// <https://drafts.fxtf.org/filter-effects/#element-attrdef-fecolormatrix-values>
285    #[rustfmt::skip]
286    fn luminance_to_alpha_matrix() -> Matrix5<f64> {
287        Matrix5::new(
288            0.0,    0.0,    0.0,    0.0, 0.0,
289            0.0,    0.0,    0.0,    0.0, 0.0,
290            0.0,    0.0,    0.0,    0.0, 0.0,
291            0.2126, 0.7152, 0.0722, 0.0, 0.0,
292            0.0,    0.0,    0.0,    0.0, 1.0,
293        )
294    }
295
296    /// Compute a `type="saturate"` matrix.
297    ///
298    /// <https://drafts.fxtf.org/filter-effects/#element-attrdef-fecolormatrix-values>
299    #[rustfmt::skip]
300    fn saturate_matrix(s: f64) -> Matrix5<f64> {
301        Matrix5::new(
302            0.213 + 0.787 * s, 0.715 - 0.715 * s, 0.072 - 0.072 * s, 0.0, 0.0,
303            0.213 - 0.213 * s, 0.715 + 0.285 * s, 0.072 - 0.072 * s, 0.0, 0.0,
304            0.213 - 0.213 * s, 0.715 - 0.715 * s, 0.072 + 0.928 * s, 0.0, 0.0,
305            0.0,               0.0,               0.0,               1.0, 0.0,
306            0.0,               0.0,               0.0,               0.0, 1.0,
307        )
308    }
309
310    /// Default for `type="matrix"`.
311    ///
312    /// <https://drafts.fxtf.org/filter-effects/#element-attrdef-fecolormatrix-values>
313    fn default_matrix() -> Matrix5<f64> {
314        Matrix5::identity()
315    }
316
317    pub fn get_input_requirements(&self) -> InputRequirements {
318        self.in1.get_requirements()
319    }
320}
321
322impl FilterEffect for FeColorMatrix {
323    fn resolve(
324        &self,
325        _acquired_nodes: &mut AcquiredNodes<'_>,
326        node: &Node,
327    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
328        let cascaded = CascadedValues::new_from_node(node);
329        let values = cascaded.get();
330
331        let mut params = self.params.clone();
332        params.color_interpolation_filters = values.color_interpolation_filters();
333
334        Ok(vec![ResolvedPrimitive {
335            primitive: self.base.clone(),
336            params: PrimitiveParams::ColorMatrix(params),
337        }])
338    }
339}
340
341impl Parse for OperationType {
342    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
343        Ok(parse_identifiers!(
344            parser,
345            "matrix" => OperationType::Matrix,
346            "saturate" => OperationType::Saturate,
347            "hueRotate" => OperationType::HueRotate,
348            "luminanceToAlpha" => OperationType::LuminanceToAlpha,
349        )?)
350    }
351}