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#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
30enum OperationType {
31 #[default]
32 Matrix,
33 Saturate,
34 HueRotate,
35 LuminanceToAlpha,
36}
37
38#[derive(Default)]
40pub struct FeColorMatrix {
41 base: Primitive,
42 params: ColorMatrix,
43}
44
45#[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 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 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 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 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 #[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 #[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 #[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 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}