1use cssparser::{BasicParseError, Parser};
4use markup5ever::{expanded_name, local_name, ns};
5use std::rc::Rc;
6use std::time::Instant;
7
8use crate::bbox::BoundingBox;
9use crate::document::AcquiredNodes;
10use crate::drawing_ctx::{DrawingCtx, Viewport};
11use crate::element::{ElementTrait, set_attribute};
12use crate::error::{InternalRenderingError, ParseError};
13use crate::filter::UserSpaceFilter;
14use crate::length::*;
15use crate::node::Node;
16use crate::parse_identifiers;
17use crate::parsers::{CustomIdent, Parse, ParseValue};
18use crate::properties::ColorInterpolationFilters;
19use crate::rsvg_log;
20use crate::session::Session;
21use crate::surface_utils::{
22 EdgeMode,
23 shared_surface::{SharedImageSurface, SurfaceType},
24};
25use crate::xml::Attributes;
26
27mod bounds;
28use self::bounds::BoundsBuilder;
29
30pub mod context;
31use self::context::{FilterContext, FilterOutput, FilterResult};
32
33mod error;
34use self::error::FilterError;
35pub use self::error::FilterResolveError;
36
37pub trait FilterEffect: ElementTrait {
39 fn resolve(
40 &self,
41 acquired_nodes: &mut AcquiredNodes<'_>,
42 node: &Node,
43 ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError>;
44}
45
46pub mod blend;
47pub mod color_matrix;
48pub mod component_transfer;
49pub mod composite;
50pub mod convolve_matrix;
51pub mod displacement_map;
52pub mod drop_shadow;
53pub mod flood;
54pub mod gaussian_blur;
55pub mod image;
56pub mod lighting;
57pub mod merge;
58pub mod morphology;
59pub mod offset;
60pub mod tile;
61pub mod turbulence;
62
63pub struct FilterSpec {
68 pub name: String,
70
71 pub user_space_filter: UserSpaceFilter,
73
74 pub primitives: Vec<UserSpacePrimitive>,
76}
77
78pub struct FilterPlan {
100 session: Session,
101
102 pub viewport: Viewport,
104
105 background_image: Option<SharedImageSurface>,
107
108 stroke_paint_image: Option<SharedImageSurface>,
112
113 fill_paint_image: Option<SharedImageSurface>,
117}
118
119impl FilterPlan {
120 pub fn new(
121 session: &Session,
122 viewport: Viewport,
123 requirements: InputRequirements,
124 background_image: Option<SharedImageSurface>,
125 stroke_paint_image: Option<SharedImageSurface>,
126 fill_paint_image: Option<SharedImageSurface>,
127 ) -> Result<FilterPlan, Box<InternalRenderingError>> {
128 assert_eq!(
129 requirements.needs_background_image || requirements.needs_background_alpha,
130 background_image.is_some()
131 );
132
133 assert_eq!(
134 requirements.needs_stroke_paint_image,
135 stroke_paint_image.is_some()
136 );
137
138 assert_eq!(
139 requirements.needs_fill_paint_image,
140 fill_paint_image.is_some()
141 );
142
143 Ok(FilterPlan {
144 session: session.clone(),
145 viewport,
146 background_image,
147 stroke_paint_image,
148 fill_paint_image,
149 })
150 }
151}
152
153#[derive(Debug, Default, PartialEq)]
168pub struct InputRequirements {
169 pub needs_source_alpha: bool,
171
172 pub needs_background_image: bool,
174
175 pub needs_background_alpha: bool,
177
178 pub needs_stroke_paint_image: bool,
180
181 pub needs_fill_paint_image: bool,
183}
184
185impl InputRequirements {
186 pub fn new_from_filter_specs(specs: &[FilterSpec]) -> InputRequirements {
188 specs
189 .iter()
190 .flat_map(|spec| spec.primitives.iter())
191 .map(|primitive| primitive.params.get_input_requirements())
192 .fold(InputRequirements::default(), |a, b| a.fold(b))
193 }
194
195 #[rustfmt::skip]
196 fn fold(self, r: InputRequirements) -> InputRequirements {
197 InputRequirements {
198 needs_source_alpha: self.needs_source_alpha || r.needs_source_alpha,
199 needs_background_image: self.needs_background_image || r.needs_background_image,
200 needs_background_alpha: self.needs_background_alpha || r.needs_background_alpha,
201 needs_stroke_paint_image: self.needs_stroke_paint_image || r.needs_stroke_paint_image,
202 needs_fill_paint_image: self.needs_fill_paint_image || r.needs_fill_paint_image,
203 }
204 }
205}
206
207pub enum PrimitiveParams {
214 Blend(blend::Blend),
215 ColorMatrix(color_matrix::ColorMatrix),
216 ComponentTransfer(component_transfer::ComponentTransfer),
217 Composite(composite::Composite),
218 ConvolveMatrix(convolve_matrix::ConvolveMatrix),
219 DiffuseLighting(lighting::DiffuseLighting),
220 DisplacementMap(displacement_map::DisplacementMap),
221 Flood(flood::Flood),
222 GaussianBlur(gaussian_blur::GaussianBlur),
223 Image(image::Image),
224 Merge(merge::Merge),
225 Morphology(morphology::Morphology),
226 Offset(offset::Offset),
227 SpecularLighting(lighting::SpecularLighting),
228 Tile(tile::Tile),
229 Turbulence(turbulence::Turbulence),
230}
231
232impl PrimitiveParams {
233 #[rustfmt::skip]
235 fn name(&self) -> &'static str {
236 use PrimitiveParams::*;
237 match self {
238 Blend(..) => "feBlend",
239 ColorMatrix(..) => "feColorMatrix",
240 ComponentTransfer(..) => "feComponentTransfer",
241 Composite(..) => "feComposite",
242 ConvolveMatrix(..) => "feConvolveMatrix",
243 DiffuseLighting(..) => "feDiffuseLighting",
244 DisplacementMap(..) => "feDisplacementMap",
245 Flood(..) => "feFlood",
246 GaussianBlur(..) => "feGaussianBlur",
247 Image(..) => "feImage",
248 Merge(..) => "feMerge",
249 Morphology(..) => "feMorphology",
250 Offset(..) => "feOffset",
251 SpecularLighting(..) => "feSpecularLighting",
252 Tile(..) => "feTile",
253 Turbulence(..) => "feTurbulence",
254 }
255 }
256
257 #[rustfmt::skip]
258 fn get_input_requirements(&self) -> InputRequirements {
259 use PrimitiveParams::*;
260 match self {
261 Blend(p) => p.get_input_requirements(),
262 ColorMatrix(p) => p.get_input_requirements(),
263 ComponentTransfer(p) => p.get_input_requirements(),
264 Composite(p) => p.get_input_requirements(),
265 ConvolveMatrix(p) => p.get_input_requirements(),
266 DiffuseLighting(p) => p.get_input_requirements(),
267 DisplacementMap(p) => p.get_input_requirements(),
268 Flood(p) => p.get_input_requirements(),
269 GaussianBlur(p) => p.get_input_requirements(),
270 Image(p) => p.get_input_requirements(),
271 Merge(p) => p.get_input_requirements(),
272 Morphology(p) => p.get_input_requirements(),
273 Offset(p) => p.get_input_requirements(),
274 SpecularLighting(p) => p.get_input_requirements(),
275 Tile(p) => p.get_input_requirements(),
276 Turbulence(p) => p.get_input_requirements(),
277 }
278 }
279}
280
281#[derive(Default, Clone)]
283pub struct Primitive {
284 pub x: Option<Length<Horizontal>>,
285 pub y: Option<Length<Vertical>>,
286 pub width: Option<ULength<Horizontal>>,
287 pub height: Option<ULength<Vertical>>,
288 pub result: Option<CustomIdent>,
289}
290
291pub struct ResolvedPrimitive {
292 pub primitive: Primitive,
293 pub params: PrimitiveParams,
294}
295
296pub struct UserSpacePrimitive {
298 x: Option<f64>,
299 y: Option<f64>,
300 width: Option<f64>,
301 height: Option<f64>,
302 result: Option<CustomIdent>,
303
304 params: PrimitiveParams,
305}
306
307#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
309pub enum Input {
310 #[default]
311 Unspecified,
312 SourceGraphic,
313 SourceAlpha,
314 BackgroundImage,
315 BackgroundAlpha,
316 FillPaint,
317 StrokePaint,
318 FilterOutput(CustomIdent),
319}
320
321impl Input {
322 pub fn get_requirements(&self) -> InputRequirements {
330 use Input::*;
331
332 let mut reqs = InputRequirements::default();
333
334 match self {
335 SourceAlpha => reqs.needs_source_alpha = true,
336 BackgroundImage => reqs.needs_background_image = true,
337 BackgroundAlpha => reqs.needs_background_alpha = true,
338 FillPaint => reqs.needs_fill_paint_image = true,
339 StrokePaint => reqs.needs_stroke_paint_image = true,
340 _ => (),
341 }
342
343 reqs
344 }
345}
346
347impl Parse for Input {
348 fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
349 parser
350 .try_parse(|p| {
351 parse_identifiers!(
352 p,
353 "SourceGraphic" => Input::SourceGraphic,
354 "SourceAlpha" => Input::SourceAlpha,
355 "BackgroundImage" => Input::BackgroundImage,
356 "BackgroundAlpha" => Input::BackgroundAlpha,
357 "FillPaint" => Input::FillPaint,
358 "StrokePaint" => Input::StrokePaint,
359 )
360 })
361 .or_else(|_: BasicParseError<'_>| {
362 let ident = CustomIdent::parse(parser)?;
363 Ok(Input::FilterOutput(ident))
364 })
365 }
366}
367
368impl ResolvedPrimitive {
369 pub fn into_user_space(self, params: &NormalizeParams) -> UserSpacePrimitive {
370 let x = self.primitive.x.map(|l| l.to_user(params));
371 let y = self.primitive.y.map(|l| l.to_user(params));
372 let width = self.primitive.width.map(|l| l.to_user(params));
373 let height = self.primitive.height.map(|l| l.to_user(params));
374
375 UserSpacePrimitive {
376 x,
377 y,
378 width,
379 height,
380 result: self.primitive.result,
381 params: self.params,
382 }
383 }
384}
385
386impl UserSpacePrimitive {
387 #[inline]
389 fn get_bounds(&self, ctx: &FilterContext) -> BoundsBuilder {
390 BoundsBuilder::new(self.x, self.y, self.width, self.height, ctx.paffine())
391 }
392}
393
394impl Primitive {
395 fn parse_standard_attributes(
396 &mut self,
397 attrs: &Attributes,
398 session: &Session,
399 ) -> (Input, Input) {
400 let mut input_1 = Input::Unspecified;
401 let mut input_2 = Input::Unspecified;
402
403 for (attr, value) in attrs.iter() {
404 match attr.expanded() {
405 expanded_name!("", "x") => set_attribute(&mut self.x, attr.parse(value), session),
406 expanded_name!("", "y") => set_attribute(&mut self.y, attr.parse(value), session),
407 expanded_name!("", "width") => {
408 set_attribute(&mut self.width, attr.parse(value), session)
409 }
410 expanded_name!("", "height") => {
411 set_attribute(&mut self.height, attr.parse(value), session)
412 }
413 expanded_name!("", "result") => {
414 set_attribute(&mut self.result, attr.parse(value), session)
415 }
416 expanded_name!("", "in") => set_attribute(&mut input_1, attr.parse(value), session),
417 expanded_name!("", "in2") => {
418 set_attribute(&mut input_2, attr.parse(value), session)
419 }
420 _ => (),
421 }
422 }
423
424 (input_1, input_2)
425 }
426
427 pub fn parse_no_inputs(&mut self, attrs: &Attributes, session: &Session) {
428 let (_, _) = self.parse_standard_attributes(attrs, session);
429 }
430
431 pub fn parse_one_input(&mut self, attrs: &Attributes, session: &Session) -> Input {
432 let (input_1, _) = self.parse_standard_attributes(attrs, session);
433 input_1
434 }
435
436 pub fn parse_two_inputs(&mut self, attrs: &Attributes, session: &Session) -> (Input, Input) {
437 self.parse_standard_attributes(attrs, session)
438 }
439}
440
441pub fn render(
443 plan: Rc<FilterPlan>,
444 filter: &FilterSpec,
445 source_surface: SharedImageSurface,
446 acquired_nodes: &mut AcquiredNodes<'_>,
447 draw_ctx: &mut DrawingCtx,
448 node_bbox: &BoundingBox,
449) -> Result<SharedImageSurface, Box<InternalRenderingError>> {
450 let session = draw_ctx.session().clone();
451
452 let surface_width = source_surface.width();
453 let surface_height = source_surface.height();
454
455 FilterContext::new(&filter.user_space_filter, plan, source_surface, *node_bbox)
456 .and_then(|mut filter_ctx| {
457 rsvg_log!(
459 session,
460 "(filter \"{}\" with effects_region={:?}",
461 filter.name,
462 filter_ctx.effects_region()
463 );
464 for user_space_primitive in &filter.primitives {
465 let start = Instant::now();
466
467 match render_primitive(user_space_primitive, &filter_ctx, acquired_nodes, draw_ctx)
468 {
469 Ok(output) => {
470 let elapsed = start.elapsed();
471 rsvg_log!(
472 session,
473 "(rendered filter primitive {} in {} seconds)",
474 user_space_primitive.params.name(),
475 elapsed.as_secs() as f64 + f64::from(elapsed.subsec_nanos()) / 1e9
476 );
477
478 filter_ctx.store_result(FilterResult {
479 name: user_space_primitive.result.clone(),
480 output,
481 });
482 }
483
484 Err(err) => {
485 rsvg_log!(
486 session,
487 "(filter primitive {} returned an error: {})",
488 user_space_primitive.params.name(),
489 err
490 );
491
492 rsvg_log!(session, ")");
494
495 if let FilterError::CairoError(status) = err {
497 return Err(FilterError::CairoError(status));
498 }
499 }
500 }
501 }
502
503 rsvg_log!(session, ")");
505
506 Ok(filter_ctx.into_output()?)
507 })
508 .or_else(|err| match err {
509 FilterError::CairoError(status) => {
510 Err(Box::new(InternalRenderingError::from(status)))
512 }
513
514 _ => {
515 Ok(SharedImageSurface::empty(
517 surface_width,
518 surface_height,
519 SurfaceType::AlphaOnly,
520 )?)
521 }
522 })
523}
524
525#[rustfmt::skip]
526fn render_primitive(
527 primitive: &UserSpacePrimitive,
528 ctx: &FilterContext,
529 acquired_nodes: &mut AcquiredNodes<'_>,
530 draw_ctx: &mut DrawingCtx,
531) -> Result<FilterOutput, FilterError> {
532 use PrimitiveParams::*;
533
534 let bounds_builder = primitive.get_bounds(ctx);
535
536 match primitive.params {
541 Blend(ref p) => p.render(bounds_builder, ctx),
542 ColorMatrix(ref p) => p.render(bounds_builder, ctx),
543 ComponentTransfer(ref p) => p.render(bounds_builder, ctx),
544 Composite(ref p) => p.render(bounds_builder, ctx),
545 ConvolveMatrix(ref p) => p.render(bounds_builder, ctx),
546 DiffuseLighting(ref p) => p.render(bounds_builder, ctx),
547 DisplacementMap(ref p) => p.render(bounds_builder, ctx),
548 Flood(ref p) => p.render(bounds_builder, ctx),
549 GaussianBlur(ref p) => p.render(bounds_builder, ctx),
550 Image(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
551 Merge(ref p) => p.render(bounds_builder, ctx),
552 Morphology(ref p) => p.render(bounds_builder, ctx),
553 Offset(ref p) => p.render(bounds_builder, ctx),
554 SpecularLighting(ref p) => p.render(bounds_builder, ctx),
555 Tile(ref p) => p.render(bounds_builder, ctx),
556 Turbulence(ref p) => p.render(bounds_builder, ctx),
557 }
558}
559
560impl From<ColorInterpolationFilters> for SurfaceType {
561 fn from(c: ColorInterpolationFilters) -> Self {
562 match c {
563 ColorInterpolationFilters::LinearRgb => SurfaceType::LinearRgb,
564 _ => SurfaceType::SRgb,
565 }
566 }
567}
568
569impl Parse for EdgeMode {
570 fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
571 Ok(parse_identifiers!(
572 parser,
573 "duplicate" => EdgeMode::Duplicate,
574 "wrap" => EdgeMode::Wrap,
575 "none" => EdgeMode::None,
576 )?)
577 }
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583
584 use crate::color::{Color, RGBA};
585 use crate::document::Document;
586 use crate::dpi::Dpi;
587 use crate::node::NodeBorrow;
588 use crate::properties::Filter;
589
590 fn get_input_requirements_for_node(document: &Document, node_id: &str) -> InputRequirements {
591 let node = document.lookup_internal_node(node_id).unwrap();
592 let elt = node.borrow_element();
593 let values = elt.get_computed_values();
594
595 let session = Session::default();
596 let mut acquired_nodes = AcquiredNodes::new(&document, None::<gio::Cancellable>);
597
598 let viewport = Viewport::new(Dpi::new(96.0, 96.0), 100.0, 100.0);
599
600 let filter = values.filter();
601
602 let filter_list = match filter {
603 Filter::None => {
604 panic!("the referenced node should have a filter property that is not 'none'")
605 }
606 Filter::List(filter_list) => filter_list,
607 };
608
609 let params = NormalizeParams::new(&values, &viewport);
610
611 let filter_specs = filter_list
612 .iter()
613 .map(|filter_value| {
614 filter_value.to_filter_spec(
615 &mut acquired_nodes,
616 ¶ms,
617 Color::Rgba(RGBA::new(0, 0, 0, 1.0)),
618 &viewport,
619 &session,
620 "rect",
621 )
622 })
623 .collect::<Result<Vec<FilterSpec>, _>>()
624 .unwrap();
625
626 InputRequirements::new_from_filter_specs(&filter_specs)
627 }
628
629 fn input_requirements_with_only_source_alpha() -> InputRequirements {
630 InputRequirements {
631 needs_source_alpha: true,
632 needs_background_image: false,
633 needs_background_alpha: false,
634 needs_stroke_paint_image: false,
635 needs_fill_paint_image: false,
636 }
637 }
638
639 #[test]
640 fn detects_source_alpha() {
641 let document = Document::load_from_bytes(include_bytes!("test_input_requirements.svg"));
642
643 assert_eq!(
644 get_input_requirements_for_node(&document, "rect_1"),
645 input_requirements_with_only_source_alpha(),
646 );
647
648 assert_eq!(
649 get_input_requirements_for_node(&document, "rect_2"),
650 input_requirements_with_only_source_alpha(),
651 );
652
653 assert_eq!(
654 get_input_requirements_for_node(&document, "rect_3"),
655 InputRequirements {
656 needs_source_alpha: false,
657 needs_background_image: true,
658 needs_background_alpha: true,
659 needs_stroke_paint_image: true,
660 needs_fill_paint_image: true,
661 }
662 );
663 }
664}