rsvg/filters/
bounds.rs

1//! Filter primitive subregion computation.
2use crate::rect::Rect;
3use crate::transform::Transform;
4
5use super::context::{FilterContext, FilterInput};
6
7/// A helper type for filter primitive subregion computation.
8pub struct BoundsBuilder {
9    /// Filter primitive attributes.
10    x: Option<f64>,
11    y: Option<f64>,
12    width: Option<f64>,
13    height: Option<f64>,
14
15    /// The transform to use when generating the rect
16    transform: Transform,
17
18    /// The inverse transform used when adding rects
19    inverse: Transform,
20
21    /// Whether one of the input nodes is standard input.
22    standard_input_was_referenced: bool,
23
24    /// The current bounding rectangle.
25    rect: Option<Rect>,
26}
27
28/// A filter primitive's subregion.
29#[derive(Debug)]
30pub struct Bounds {
31    /// Filter primitive attributes.
32    pub x: Option<f64>,
33    pub y: Option<f64>,
34    pub width: Option<f64>,
35    pub height: Option<f64>,
36
37    /// Primitive's subregion, clipped to the filter effects region.
38    pub clipped: Rect,
39
40    /// Primitive's subregion, unclipped.
41    pub unclipped: Rect,
42}
43
44impl BoundsBuilder {
45    /// Constructs a new `BoundsBuilder`.
46    #[inline]
47    pub fn new(
48        x: Option<f64>,
49        y: Option<f64>,
50        width: Option<f64>,
51        height: Option<f64>,
52        transform: Transform,
53    ) -> Self {
54        // We panic if transform is not invertible. This is checked in the caller.
55        Self {
56            x,
57            y,
58            width,
59            height,
60            transform,
61            inverse: transform.invert().unwrap(),
62            standard_input_was_referenced: false,
63            rect: None,
64        }
65    }
66
67    /// Adds a filter primitive input to the bounding box.
68    #[inline]
69    pub fn add_input(mut self, input: &FilterInput) -> Self {
70        // If a standard input was referenced, the default value is the filter effects region
71        // regardless of other referenced inputs. This means we can skip computing the bounds.
72        if self.standard_input_was_referenced {
73            return self;
74        }
75
76        match *input {
77            FilterInput::StandardInput(_) => {
78                self.standard_input_was_referenced = true;
79            }
80            FilterInput::PrimitiveOutput(ref output) => {
81                let input_rect = self.inverse.transform_rect(&Rect::from(output.bounds));
82                self.rect = Some(self.rect.map_or(input_rect, |r| input_rect.union(&r)));
83            }
84        }
85
86        self
87    }
88
89    /// Returns the final exact bounds, both with and without clipping to the effects region.
90    pub fn compute(self, ctx: &FilterContext) -> Bounds {
91        let effects_region = ctx.effects_region();
92
93        // The default value is the filter effects region converted into
94        // the ptimitive coordinate system.
95        let mut rect = match self.rect {
96            Some(r) if !self.standard_input_was_referenced => r,
97            _ => self.inverse.transform_rect(&effects_region),
98        };
99
100        // If any of the properties were specified, we need to respect them.
101        // These replacements are possible because of the primitive coordinate system.
102        if self.x.is_some() || self.y.is_some() || self.width.is_some() || self.height.is_some() {
103            if let Some(x) = self.x {
104                let w = rect.width();
105                rect.x0 = x;
106                rect.x1 = rect.x0 + w;
107            }
108            if let Some(y) = self.y {
109                let h = rect.height();
110                rect.y0 = y;
111                rect.y1 = rect.y0 + h;
112            }
113            if let Some(width) = self.width {
114                rect.x1 = rect.x0 + width;
115            }
116            if let Some(height) = self.height {
117                rect.y1 = rect.y0 + height;
118            }
119        }
120
121        // Convert into the surface coordinate system.
122        let unclipped = self.transform.transform_rect(&rect);
123
124        let clipped = unclipped.intersection(&effects_region).unwrap_or_default();
125
126        Bounds {
127            x: self.x,
128            y: self.y,
129            width: self.width,
130            height: self.height,
131            clipped,
132            unclipped,
133        }
134    }
135}