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::{set_attribute, ElementTrait};
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 {
220 d
221 } else {
222 1.0
223 }
224 };
225
226 let mut surface = ExclusiveImageSurface::new(
227 input_surface.width(),
228 input_surface.height(),
229 input_1.surface().surface_type(),
230 )?;
231
232 surface.modify(&mut |data, stride| {
233 for (x, y, pixel) in Pixels::within(&input_surface, bounds) {
234 let kernel_bounds = IRect::new(
236 x as i32 - target_x as i32,
237 y as i32 - target_y as i32,
238 x as i32 - target_x as i32 + self.order.0 as i32,
239 y as i32 - target_y as i32 + self.order.1 as i32,
240 );
241
242 let mut r = 0.0;
244 let mut g = 0.0;
245 let mut b = 0.0;
246 let mut a = 0.0;
247
248 for (x, y, pixel) in
249 PixelRectangle::within(&input_surface, bounds, kernel_bounds, self.edge_mode)
250 {
251 let kernel_x = (kernel_bounds.x1 - x - 1) as usize;
252 let kernel_y = (kernel_bounds.y1 - y - 1) as usize;
253
254 r += f64::from(pixel.r) / 255.0 * matrix[(kernel_y, kernel_x)];
255 g += f64::from(pixel.g) / 255.0 * matrix[(kernel_y, kernel_x)];
256 b += f64::from(pixel.b) / 255.0 * matrix[(kernel_y, kernel_x)];
257
258 if !self.preserve_alpha {
259 a += f64::from(pixel.a) / 255.0 * matrix[(kernel_y, kernel_x)];
260 }
261 }
262
263 if self.preserve_alpha {
265 a = f64::from(pixel.a) / 255.0;
266 } else {
267 a = a / divisor + self.bias;
268 }
269
270 let clamped_a = clamp(a, 0.0, 1.0);
271
272 let compute = |x| {
273 let x = x / divisor + self.bias * a;
274
275 let x = if self.preserve_alpha {
276 clamp(x, 0.0, 1.0) * clamped_a
278 } else {
279 clamp(x, 0.0, clamped_a)
280 };
281
282 ((x * 255.0) + 0.5) as u8
283 };
284
285 let output_pixel = Pixel {
286 r: compute(r),
287 g: compute(g),
288 b: compute(b),
289 a: ((clamped_a * 255.0) + 0.5) as u8,
290 };
291
292 data.set_pixel(stride, output_pixel, x, y);
293 }
294 });
295
296 let mut surface = surface.share()?;
297
298 if let Some((ox, oy)) = scale {
299 surface = surface.scale_to(
301 ctx.source_graphic().width(),
302 ctx.source_graphic().height(),
303 original_bounds,
304 ox,
305 oy,
306 )?;
307
308 bounds = original_bounds;
309 }
310
311 Ok(FilterOutput { surface, bounds })
312 }
313
314 pub fn get_input_requirements(&self) -> InputRequirements {
315 self.in1.get_requirements()
316 }
317}
318
319impl FilterEffect for FeConvolveMatrix {
320 fn resolve(
321 &self,
322 _acquired_nodes: &mut AcquiredNodes<'_>,
323 node: &Node,
324 ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
325 let cascaded = CascadedValues::new_from_node(node);
326 let values = cascaded.get();
327
328 let mut params = self.params.clone();
329 params.color_interpolation_filters = values.color_interpolation_filters();
330
331 Ok(vec![ResolvedPrimitive {
332 primitive: self.base.clone(),
333 params: PrimitiveParams::ConvolveMatrix(params),
334 }])
335 }
336}
337
338impl Parse for bool {
340 fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
341 Ok(parse_identifiers!(
342 parser,
343 "false" => false,
344 "true" => true,
345 )?)
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn kernel_unit_length_expects_positive_numbers() {
355 assert!(KernelUnitLength::parse_str("-1").is_err());
356 assert!(KernelUnitLength::parse_str("1 -1").is_err());
357 assert!(KernelUnitLength::parse_str("-1 -1").is_err());
358 }
359
360 #[test]
361 fn kernel_unit_length_catches_errors() {
362 assert!(KernelUnitLength::parse_str("foo").is_err());
363 }
364}