1 use std::cmp::{max, min}; 2 3 use cssparser::Parser; 4 use markup5ever::{expanded_name, local_name, namespace_url, ns}; 5 6 use crate::document::AcquiredNodes; 7 use crate::drawing_ctx::DrawingCtx; 8 use crate::element::{ElementResult, SetAttributes}; 9 use crate::error::*; 10 use crate::node::Node; 11 use crate::parsers::{NonNegative, NumberOptionalNumber, Parse, ParseValue}; 12 use crate::properties::ColorInterpolationFilters; 13 use crate::rect::IRect; 14 use crate::surface_utils::{ 15 iterators::{PixelRectangle, Pixels}, 16 shared_surface::ExclusiveImageSurface, 17 EdgeMode, ImageSurfaceDataExt, Pixel, 18 }; 19 use crate::xml::Attributes; 20 21 use super::bounds::BoundsBuilder; 22 use super::context::{FilterContext, FilterOutput}; 23 use super::{ 24 FilterEffect, FilterError, FilterResolveError, Input, Primitive, PrimitiveParams, 25 ResolvedPrimitive, 26 }; 27 28 /// Enumeration of the possible morphology operations. 29 #[derive(Clone)] 30 enum Operator { 31 Erode, 32 Dilate, 33 } 34 35 enum_default!(Operator, Operator::Erode); 36 37 /// The `feMorphology` filter primitive. 38 #[derive(Default)] 39 pub struct FeMorphology { 40 base: Primitive, 41 params: Morphology, 42 } 43 44 /// Resolved `feMorphology` primitive for rendering. 45 #[derive(Clone, Default)] 46 pub struct Morphology { 47 in1: Input, 48 operator: Operator, 49 radius: (f64, f64), 50 } 51 52 impl SetAttributes for FeMorphology { set_attributes(&mut self, attrs: &Attributes) -> ElementResult53 fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { 54 self.params.in1 = self.base.parse_one_input(attrs)?; 55 56 for (attr, value) in attrs.iter() { 57 match attr.expanded() { 58 expanded_name!("", "operator") => self.params.operator = attr.parse(value)?, 59 expanded_name!("", "radius") => { 60 let NumberOptionalNumber(NonNegative(x), NonNegative(y)) = attr.parse(value)?; 61 self.params.radius = (x, y); 62 } 63 _ => (), 64 } 65 } 66 67 Ok(()) 68 } 69 } 70 71 impl Morphology { render( &self, bounds_builder: BoundsBuilder, ctx: &FilterContext, acquired_nodes: &mut AcquiredNodes<'_>, draw_ctx: &mut DrawingCtx, ) -> Result<FilterOutput, FilterError>72 pub fn render( 73 &self, 74 bounds_builder: BoundsBuilder, 75 ctx: &FilterContext, 76 acquired_nodes: &mut AcquiredNodes<'_>, 77 draw_ctx: &mut DrawingCtx, 78 ) -> Result<FilterOutput, FilterError> { 79 // Although https://www.w3.org/TR/filter-effects/#propdef-color-interpolation-filters does not mention 80 // feMorphology as being one of the primitives that does *not* use that property, 81 // the SVG1.1 test for filters-morph-01-f.svg fails if we pass the value from the ComputedValues here (that 82 // document does not specify the color-interpolation-filters property, so it defaults to linearRGB). 83 // So, we pass Auto, which will get resolved to SRGB, and that makes that test pass. 84 // 85 // I suppose erosion/dilation doesn't care about the color space of the source image? 86 87 let input_1 = ctx.get_input( 88 acquired_nodes, 89 draw_ctx, 90 &self.in1, 91 ColorInterpolationFilters::Auto, 92 )?; 93 let bounds: IRect = bounds_builder 94 .add_input(&input_1) 95 .compute(ctx) 96 .clipped 97 .into(); 98 99 let (rx, ry) = self.radius; 100 let (rx, ry) = ctx.paffine().transform_distance(rx, ry); 101 102 // The radii can become negative here due to the transform. 103 // Additionally The radii being excessively large causes cpu hangups 104 let (rx, ry) = (rx.abs().min(10.0), ry.abs().min(10.0)); 105 106 let mut surface = ExclusiveImageSurface::new( 107 ctx.source_graphic().width(), 108 ctx.source_graphic().height(), 109 input_1.surface().surface_type(), 110 )?; 111 112 surface.modify(&mut |data, stride| { 113 for (x, y, _pixel) in Pixels::within(input_1.surface(), bounds) { 114 // Compute the kernel rectangle bounds. 115 let kernel_bounds = IRect::new( 116 (f64::from(x) - rx).floor() as i32, 117 (f64::from(y) - ry).floor() as i32, 118 (f64::from(x) + rx).ceil() as i32 + 1, 119 (f64::from(y) + ry).ceil() as i32 + 1, 120 ); 121 122 // Compute the new pixel values. 123 let initial = match self.operator { 124 Operator::Erode => u8::max_value(), 125 Operator::Dilate => u8::min_value(), 126 }; 127 128 let mut output_pixel = Pixel { 129 r: initial, 130 g: initial, 131 b: initial, 132 a: initial, 133 }; 134 135 for (_x, _y, pixel) in 136 PixelRectangle::within(input_1.surface(), bounds, kernel_bounds, EdgeMode::None) 137 { 138 let op = match self.operator { 139 Operator::Erode => min, 140 Operator::Dilate => max, 141 }; 142 143 output_pixel.r = op(output_pixel.r, pixel.r); 144 output_pixel.g = op(output_pixel.g, pixel.g); 145 output_pixel.b = op(output_pixel.b, pixel.b); 146 output_pixel.a = op(output_pixel.a, pixel.a); 147 } 148 149 data.set_pixel(stride, output_pixel, x, y); 150 } 151 }); 152 153 Ok(FilterOutput { 154 surface: surface.share()?, 155 bounds, 156 }) 157 } 158 } 159 160 impl FilterEffect for FeMorphology { resolve( &self, _acquired_nodes: &mut AcquiredNodes<'_>, _node: &Node, ) -> Result<ResolvedPrimitive, FilterResolveError>161 fn resolve( 162 &self, 163 _acquired_nodes: &mut AcquiredNodes<'_>, 164 _node: &Node, 165 ) -> Result<ResolvedPrimitive, FilterResolveError> { 166 Ok(ResolvedPrimitive { 167 primitive: self.base.clone(), 168 params: PrimitiveParams::Morphology(self.params.clone()), 169 }) 170 } 171 } 172 173 impl Parse for Operator { parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>>174 fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> { 175 Ok(parse_identifiers!( 176 parser, 177 "erode" => Operator::Erode, 178 "dilate" => Operator::Dilate, 179 )?) 180 } 181 } 182