diff options
Diffstat (limited to 'rsvg/src/filters/convolve_matrix.rs')
-rw-r--r-- | rsvg/src/filters/convolve_matrix.rs | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/rsvg/src/filters/convolve_matrix.rs b/rsvg/src/filters/convolve_matrix.rs new file mode 100644 index 00000000..096ad043 --- /dev/null +++ b/rsvg/src/filters/convolve_matrix.rs @@ -0,0 +1,354 @@ +use cssparser::Parser; +use markup5ever::{expanded_name, local_name, namespace_url, ns}; +use nalgebra::{DMatrix, Dyn, VecStorage}; + +use crate::document::AcquiredNodes; +use crate::drawing_ctx::DrawingCtx; +use crate::element::{set_attribute, ElementTrait}; +use crate::error::*; +use crate::node::{CascadedValues, Node}; +use crate::parsers::{NumberList, NumberOptionalNumber, Parse, ParseValue}; +use crate::properties::ColorInterpolationFilters; +use crate::rect::IRect; +use crate::session::Session; +use crate::surface_utils::{ + iterators::{PixelRectangle, Pixels}, + shared_surface::ExclusiveImageSurface, + EdgeMode, ImageSurfaceDataExt, Pixel, +}; +use crate::util::clamp; +use crate::xml::Attributes; + +use super::bounds::BoundsBuilder; +use super::context::{FilterContext, FilterOutput}; +use super::{ + FilterEffect, FilterError, FilterResolveError, Input, Primitive, PrimitiveParams, + ResolvedPrimitive, +}; + +/// The `feConvolveMatrix` filter primitive. +#[derive(Default)] +pub struct FeConvolveMatrix { + base: Primitive, + params: ConvolveMatrix, +} + +/// Resolved `feConvolveMatrix` primitive for rendering. +#[derive(Clone)] +pub struct ConvolveMatrix { + in1: Input, + order: NumberOptionalNumber<u32>, + kernel_matrix: NumberList<0, 400>, // #691: Limit list to 400 (20x20) to mitigate malicious SVGs + divisor: f64, + bias: f64, + target_x: Option<u32>, + target_y: Option<u32>, + edge_mode: EdgeMode, + kernel_unit_length: Option<(f64, f64)>, + preserve_alpha: bool, + color_interpolation_filters: ColorInterpolationFilters, +} + +impl Default for ConvolveMatrix { + /// Constructs a new `ConvolveMatrix` with empty properties. + #[inline] + fn default() -> ConvolveMatrix { + ConvolveMatrix { + in1: Default::default(), + order: NumberOptionalNumber(3, 3), + kernel_matrix: NumberList(Vec::new()), + divisor: 0.0, + bias: 0.0, + target_x: None, + target_y: None, + edge_mode: EdgeMode::Duplicate, + kernel_unit_length: None, + preserve_alpha: false, + color_interpolation_filters: Default::default(), + } + } +} + +impl ElementTrait for FeConvolveMatrix { + fn set_attributes(&mut self, attrs: &Attributes, session: &Session) { + self.params.in1 = self.base.parse_one_input(attrs, session); + + for (attr, value) in attrs.iter() { + match attr.expanded() { + expanded_name!("", "order") => { + set_attribute(&mut self.params.order, attr.parse(value), session) + } + expanded_name!("", "kernelMatrix") => { + set_attribute(&mut self.params.kernel_matrix, attr.parse(value), session) + } + expanded_name!("", "divisor") => { + set_attribute(&mut self.params.divisor, attr.parse(value), session) + } + expanded_name!("", "bias") => { + set_attribute(&mut self.params.bias, attr.parse(value), session) + } + expanded_name!("", "targetX") => { + set_attribute(&mut self.params.target_x, attr.parse(value), session) + } + expanded_name!("", "targetY") => { + set_attribute(&mut self.params.target_y, attr.parse(value), session) + } + expanded_name!("", "edgeMode") => { + set_attribute(&mut self.params.edge_mode, attr.parse(value), session) + } + expanded_name!("", "kernelUnitLength") => { + let v: Result<NumberOptionalNumber<f64>, _> = attr.parse(value); + match v { + Ok(NumberOptionalNumber(x, y)) => { + self.params.kernel_unit_length = Some((x, y)); + } + + Err(e) => { + rsvg_log!(session, "ignoring attribute with invalid value: {}", e); + } + } + } + expanded_name!("", "preserveAlpha") => { + set_attribute(&mut self.params.preserve_alpha, attr.parse(value), session); + } + + _ => (), + } + } + } +} + +impl ConvolveMatrix { + pub fn render( + &self, + bounds_builder: BoundsBuilder, + ctx: &FilterContext, + acquired_nodes: &mut AcquiredNodes<'_>, + draw_ctx: &mut DrawingCtx, + ) -> Result<FilterOutput, FilterError> { + #![allow(clippy::many_single_char_names)] + + let input_1 = ctx.get_input( + acquired_nodes, + draw_ctx, + &self.in1, + self.color_interpolation_filters, + )?; + let mut bounds: IRect = bounds_builder + .add_input(&input_1) + .compute(ctx) + .clipped + .into(); + let original_bounds = bounds; + + let target_x = match self.target_x { + Some(x) if x >= self.order.0 => { + return Err(FilterError::InvalidParameter( + "targetX must be less than orderX".to_string(), + )) + } + Some(x) => x, + None => self.order.0 / 2, + }; + + let target_y = match self.target_y { + Some(y) if y >= self.order.1 => { + return Err(FilterError::InvalidParameter( + "targetY must be less than orderY".to_string(), + )) + } + Some(y) => y, + None => self.order.1 / 2, + }; + + let mut input_surface = if self.preserve_alpha { + // preserve_alpha means we need to premultiply and unpremultiply the values. + input_1.surface().unpremultiply(bounds)? + } else { + input_1.surface().clone() + }; + + let scale = self + .kernel_unit_length + .and_then(|(x, y)| { + if x <= 0.0 || y <= 0.0 { + None + } else { + Some((x, y)) + } + }) + .map(|(dx, dy)| ctx.paffine().transform_distance(dx, dy)); + + if let Some((ox, oy)) = scale { + // Scale the input surface to match kernel_unit_length. + let (new_surface, new_bounds) = input_surface.scale(bounds, 1.0 / ox, 1.0 / oy)?; + + input_surface = new_surface; + bounds = new_bounds; + } + + let cols = self.order.0 as usize; + let rows = self.order.1 as usize; + let number_of_elements = cols * rows; + let numbers = self.kernel_matrix.0.clone(); + + if numbers.len() != number_of_elements && numbers.len() != 400 { + // "If the result of orderX * orderY is not equal to the the number of entries + // in the value list, the filter primitive acts as a pass through filter." + // + // https://drafts.fxtf.org/filter-effects/#element-attrdef-feconvolvematrix-kernelmatrix + rsvg_log!( + draw_ctx.session(), + "feConvolveMatrix got {} elements when it expected {}; ignoring it", + numbers.len(), + number_of_elements + ); + return Ok(FilterOutput { + surface: input_1.surface().clone(), + bounds: original_bounds, + }); + } + + let matrix = DMatrix::from_data(VecStorage::new(Dyn(rows), Dyn(cols), numbers)); + + let divisor = if self.divisor != 0.0 { + self.divisor + } else { + let d = matrix.iter().sum(); + + if d != 0.0 { + d + } else { + 1.0 + } + }; + + let mut surface = ExclusiveImageSurface::new( + input_surface.width(), + input_surface.height(), + input_1.surface().surface_type(), + )?; + + surface.modify(&mut |data, stride| { + for (x, y, pixel) in Pixels::within(&input_surface, bounds) { + // Compute the convolution rectangle bounds. + let kernel_bounds = IRect::new( + x as i32 - target_x as i32, + y as i32 - target_y as i32, + x as i32 - target_x as i32 + self.order.0 as i32, + y as i32 - target_y as i32 + self.order.1 as i32, + ); + + // Do the convolution. + let mut r = 0.0; + let mut g = 0.0; + let mut b = 0.0; + let mut a = 0.0; + + for (x, y, pixel) in + PixelRectangle::within(&input_surface, bounds, kernel_bounds, self.edge_mode) + { + let kernel_x = (kernel_bounds.x1 - x - 1) as usize; + let kernel_y = (kernel_bounds.y1 - y - 1) as usize; + + r += f64::from(pixel.r) / 255.0 * matrix[(kernel_y, kernel_x)]; + g += f64::from(pixel.g) / 255.0 * matrix[(kernel_y, kernel_x)]; + b += f64::from(pixel.b) / 255.0 * matrix[(kernel_y, kernel_x)]; + + if !self.preserve_alpha { + a += f64::from(pixel.a) / 255.0 * matrix[(kernel_y, kernel_x)]; + } + } + + // If preserve_alpha is true, set a to the source alpha value. + if self.preserve_alpha { + a = f64::from(pixel.a) / 255.0; + } else { + a = a / divisor + self.bias; + } + + let clamped_a = clamp(a, 0.0, 1.0); + + let compute = |x| { + let x = x / divisor + self.bias * a; + + let x = if self.preserve_alpha { + // Premultiply the output value. + clamp(x, 0.0, 1.0) * clamped_a + } else { + clamp(x, 0.0, clamped_a) + }; + + ((x * 255.0) + 0.5) as u8 + }; + + let output_pixel = Pixel { + r: compute(r), + g: compute(g), + b: compute(b), + a: ((clamped_a * 255.0) + 0.5) as u8, + }; + + data.set_pixel(stride, output_pixel, x, y); + } + }); + + let mut surface = surface.share()?; + + if let Some((ox, oy)) = scale { + // Scale the output surface back. + surface = surface.scale_to( + ctx.source_graphic().width(), + ctx.source_graphic().height(), + original_bounds, + ox, + oy, + )?; + + bounds = original_bounds; + } + + Ok(FilterOutput { surface, bounds }) + } +} + +impl FilterEffect for FeConvolveMatrix { + fn resolve( + &self, + _acquired_nodes: &mut AcquiredNodes<'_>, + node: &Node, + ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> { + let cascaded = CascadedValues::new_from_node(node); + let values = cascaded.get(); + + let mut params = self.params.clone(); + params.color_interpolation_filters = values.color_interpolation_filters(); + + Ok(vec![ResolvedPrimitive { + primitive: self.base.clone(), + params: PrimitiveParams::ConvolveMatrix(params), + }]) + } +} + +impl Parse for EdgeMode { + fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> { + Ok(parse_identifiers!( + parser, + "duplicate" => EdgeMode::Duplicate, + "wrap" => EdgeMode::Wrap, + "none" => EdgeMode::None, + )?) + } +} + +// Used for the preserveAlpha attribute +impl Parse for bool { + fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> { + Ok(parse_identifiers!( + parser, + "false" => false, + "true" => true, + )?) + } +} |