1use cssparser::Parser;
2use markup5ever::{expanded_name, local_name, ns};
3use nalgebra::{DMatrix, Dyn, VecStorage};
4
5use crate::bench_only::{
6 EdgeMode, ExclusiveImageSurface, ImageSurfaceDataExt, Pixel, PixelRectangle, Pixels,
7};
8use crate::document::AcquiredNodes;
9use crate::element::{ElementTrait, set_attribute};
10use crate::error::*;
11use crate::node::{CascadedValues, Node};
12use crate::parse_identifiers;
13use crate::parsers::{CommaSeparatedList, NumberOptionalNumber, Parse, ParseValue};
14use crate::properties::ColorInterpolationFilters;
15use crate::rect::IRect;
16use crate::rsvg_log;
17use crate::session::Session;
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(Default)]
30pub struct FeConvolveMatrix {
31 base: Primitive,
32 params: ConvolveMatrix,
33}
34
35#[derive(Clone)]
37pub struct ConvolveMatrix {
38 in1: Input,
39 order: NumberOptionalNumber<u32>,
40 kernel_matrix: CommaSeparatedList<f64, 0, 400>, divisor: f64,
42 bias: f64,
43 target_x: Option<u32>,
44 target_y: Option<u32>,
45 edge_mode: EdgeMode,
46 kernel_unit_length: KernelUnitLength,
47 preserve_alpha: bool,
48 color_interpolation_filters: ColorInterpolationFilters,
49}
50
51#[derive(Clone, Default)]
52pub struct KernelUnitLength(pub Option<(f64, f64)>);
53
54impl Parse for KernelUnitLength {
55 fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
56 let loc = parser.current_source_location();
57 let v: Result<NumberOptionalNumber<f64>, _> = NumberOptionalNumber::parse(parser);
58 match v {
59 Ok(NumberOptionalNumber(x, y)) if x > 0.0 && y > 0.0 => {
60 Ok(KernelUnitLength(Some((x, y))))
61 } Ok(_) => {
63 Err(loc.new_custom_error(ValueErrorKind::value_error("expected positive values")))
64 }
65 Err(e) => Err(e),
66 }
67 }
68}
69
70impl Default for ConvolveMatrix {
71 #[inline]
73 fn default() -> ConvolveMatrix {
74 ConvolveMatrix {
75 in1: Default::default(),
76 order: NumberOptionalNumber(3, 3),
77 kernel_matrix: CommaSeparatedList(Vec::new()),
78 divisor: 0.0,
79 bias: 0.0,
80 target_x: None,
81 target_y: None,
82 edge_mode: EdgeMode::Duplicate,
85 kernel_unit_length: KernelUnitLength::default(),
86 preserve_alpha: false,
87 color_interpolation_filters: Default::default(),
88 }
89 }
90}
91
92impl ElementTrait for FeConvolveMatrix {
93 fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
94 self.params.in1 = self.base.parse_one_input(attrs, session);
95
96 for (attr, value) in attrs.iter() {
97 match attr.expanded() {
98 expanded_name!("", "order") => {
99 set_attribute(&mut self.params.order, attr.parse(value), session)
100 }
101 expanded_name!("", "kernelMatrix") => {
102 set_attribute(&mut self.params.kernel_matrix, attr.parse(value), session)
103 }
104 expanded_name!("", "divisor") => {
105 set_attribute(&mut self.params.divisor, attr.parse(value), session)
106 }
107 expanded_name!("", "bias") => {
108 set_attribute(&mut self.params.bias, attr.parse(value), session)
109 }
110 expanded_name!("", "targetX") => {
111 set_attribute(&mut self.params.target_x, attr.parse(value), session)
112 }
113 expanded_name!("", "targetY") => {
114 set_attribute(&mut self.params.target_y, attr.parse(value), session)
115 }
116 expanded_name!("", "edgeMode") => {
117 set_attribute(&mut self.params.edge_mode, attr.parse(value), session)
118 }
119 expanded_name!("", "kernelUnitLength") => set_attribute(
120 &mut self.params.kernel_unit_length,
121 attr.parse(value),
122 session,
123 ),
124 expanded_name!("", "preserveAlpha") => {
125 set_attribute(&mut self.params.preserve_alpha, attr.parse(value), session);
126 }
127
128 _ => (),
129 }
130 }
131 }
132}
133
134impl ConvolveMatrix {
135 pub fn render(
136 &self,
137 bounds_builder: BoundsBuilder,
138 ctx: &FilterContext,
139 ) -> Result<FilterOutput, FilterError> {
140 #![allow(clippy::many_single_char_names)]
141
142 let input_1 = ctx.get_input(&self.in1, self.color_interpolation_filters)?;
143 let mut bounds: IRect = bounds_builder
144 .add_input(&input_1)
145 .compute(ctx)
146 .clipped
147 .into();
148 let original_bounds = bounds;
149
150 let target_x = match self.target_x {
151 Some(x) if x >= self.order.0 => {
152 return Err(FilterError::InvalidParameter(
153 "targetX must be less than orderX".to_string(),
154 ));
155 }
156 Some(x) => x,
157 None => self.order.0 / 2,
158 };
159
160 let target_y = match self.target_y {
161 Some(y) if y >= self.order.1 => {
162 return Err(FilterError::InvalidParameter(
163 "targetY must be less than orderY".to_string(),
164 ));
165 }
166 Some(y) => y,
167 None => self.order.1 / 2,
168 };
169
170 let mut input_surface = if self.preserve_alpha {
171 input_1.surface().unpremultiply(bounds)?
173 } else {
174 input_1.surface().clone()
175 };
176
177 let scale = self
178 .kernel_unit_length
179 .0
180 .map(|(dx, dy)| ctx.paffine().transform_distance(dx, dy));
181
182 if let Some((ox, oy)) = scale {
183 let (new_surface, new_bounds) = input_surface.scale(bounds, 1.0 / ox, 1.0 / oy)?;
185
186 input_surface = new_surface;
187 bounds = new_bounds;
188 }
189
190 let cols = self.order.0 as usize;
191 let rows = self.order.1 as usize;
192 let number_of_elements = cols * rows;
193 let numbers = self.kernel_matrix.0.clone();
194
195 if numbers.len() != number_of_elements && numbers.len() != 400 {
196 rsvg_log!(
201 ctx.session(),
202 "feConvolveMatrix got {} elements when it expected {}; ignoring it",
203 numbers.len(),
204 number_of_elements
205 );
206 return Ok(FilterOutput {
207 surface: input_1.surface().clone(),
208 bounds: original_bounds,
209 });
210 }
211
212 let matrix = DMatrix::from_data(VecStorage::new(Dyn(rows), Dyn(cols), numbers));
213
214 let divisor = if self.divisor != 0.0 {
215 self.divisor
216 } else {
217 let d = matrix.iter().sum();
218
219 if d != 0.0 { d } else { 1.0 }
220 };
221
222 let mut surface = ExclusiveImageSurface::new(
223 input_surface.width(),
224 input_surface.height(),
225 input_1.surface().surface_type(),
226 )?;
227
228 surface.modify(&mut |data, stride| {
229 for (x, y, pixel) in Pixels::within(&input_surface, bounds) {
230 let kernel_bounds = IRect::new(
232 x as i32 - target_x as i32,
233 y as i32 - target_y as i32,
234 x as i32 - target_x as i32 + self.order.0 as i32,
235 y as i32 - target_y as i32 + self.order.1 as i32,
236 );
237
238 let mut r = 0.0;
240 let mut g = 0.0;
241 let mut b = 0.0;
242 let mut a = 0.0;
243
244 for (x, y, pixel) in
245 PixelRectangle::within(&input_surface, bounds, kernel_bounds, self.edge_mode)
246 {
247 let kernel_x = (kernel_bounds.x1 - x - 1) as usize;
248 let kernel_y = (kernel_bounds.y1 - y - 1) as usize;
249
250 r += f64::from(pixel.r) / 255.0 * matrix[(kernel_y, kernel_x)];
251 g += f64::from(pixel.g) / 255.0 * matrix[(kernel_y, kernel_x)];
252 b += f64::from(pixel.b) / 255.0 * matrix[(kernel_y, kernel_x)];
253
254 if !self.preserve_alpha {
255 a += f64::from(pixel.a) / 255.0 * matrix[(kernel_y, kernel_x)];
256 }
257 }
258
259 if self.preserve_alpha {
261 a = f64::from(pixel.a) / 255.0;
262 } else {
263 a = a / divisor + self.bias;
264 }
265
266 let clamped_a = clamp(a, 0.0, 1.0);
267
268 let compute = |x| {
269 let x = x / divisor + self.bias * a;
270
271 let x = if self.preserve_alpha {
272 clamp(x, 0.0, 1.0) * clamped_a
274 } else {
275 clamp(x, 0.0, clamped_a)
276 };
277
278 ((x * 255.0) + 0.5) as u8
279 };
280
281 let output_pixel = Pixel {
282 r: compute(r),
283 g: compute(g),
284 b: compute(b),
285 a: ((clamped_a * 255.0) + 0.5) as u8,
286 };
287
288 data.set_pixel(stride, output_pixel, x, y);
289 }
290 });
291
292 let mut surface = surface.share()?;
293
294 if let Some((ox, oy)) = scale {
295 surface = surface.scale_to(
297 ctx.source_graphic().width(),
298 ctx.source_graphic().height(),
299 original_bounds,
300 ox,
301 oy,
302 )?;
303
304 bounds = original_bounds;
305 }
306
307 Ok(FilterOutput { surface, bounds })
308 }
309
310 pub fn get_input_requirements(&self) -> InputRequirements {
311 self.in1.get_requirements()
312 }
313}
314
315impl FilterEffect for FeConvolveMatrix {
316 fn resolve(
317 &self,
318 _acquired_nodes: &mut AcquiredNodes<'_>,
319 node: &Node,
320 ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
321 let cascaded = CascadedValues::new_from_node(node);
322 let values = cascaded.get();
323
324 let mut params = self.params.clone();
325 params.color_interpolation_filters = values.color_interpolation_filters();
326
327 Ok(vec![ResolvedPrimitive {
328 primitive: self.base.clone(),
329 params: PrimitiveParams::ConvolveMatrix(params),
330 }])
331 }
332}
333
334impl Parse for bool {
336 fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
337 Ok(parse_identifiers!(
338 parser,
339 "false" => false,
340 "true" => true,
341 )?)
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348
349 #[test]
350 fn kernel_unit_length_expects_positive_numbers() {
351 assert!(KernelUnitLength::parse_str("-1").is_err());
352 assert!(KernelUnitLength::parse_str("1 -1").is_err());
353 assert!(KernelUnitLength::parse_str("-1 -1").is_err());
354 }
355
356 #[test]
357 fn kernel_unit_length_catches_errors() {
358 assert!(KernelUnitLength::parse_str("foo").is_err());
359 }
360}