1
//! Filter primitive subregion computation.
2
use crate::rect::Rect;
3
use crate::transform::Transform;
4

            
5
use super::context::{FilterContext, FilterInput};
6

            
7
/// A helper type for filter primitive subregion computation.
8
pub struct BoundsBuilder {
9
    /// Filter primitive properties.
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
pub struct Bounds {
30
    /// Primitive's subregion, clipped to the filter effects region.
31
    pub clipped: Rect,
32

            
33
    /// Primitive's subregion, unclipped.
34
    pub unclipped: Rect,
35
}
36

            
37
impl BoundsBuilder {
38
    /// Constructs a new `BoundsBuilder`.
39
    #[inline]
40
422
    pub fn new(
41
        x: Option<f64>,
42
        y: Option<f64>,
43
        width: Option<f64>,
44
        height: Option<f64>,
45
        transform: Transform,
46
    ) -> Self {
47
        // We panic if transform is not invertible. This is checked in the caller.
48
422
        Self {
49
            x,
50
            y,
51
            width,
52
            height,
53
422
            transform,
54
422
            inverse: transform.invert().unwrap(),
55
            standard_input_was_referenced: false,
56
422
            rect: None,
57
        }
58
422
    }
59

            
60
    /// Adds a filter primitive input to the bounding box.
61
    #[inline]
62
374
    pub fn add_input(mut self, input: &FilterInput) -> Self {
63
        // If a standard input was referenced, the default value is the filter effects region
64
        // regardless of other referenced inputs. This means we can skip computing the bounds.
65
374
        if self.standard_input_was_referenced {
66
19
            return self;
67
        }
68

            
69
355
        match *input {
70
240
            FilterInput::StandardInput(_) => {
71
240
                self.standard_input_was_referenced = true;
72
            }
73
115
            FilterInput::PrimitiveOutput(ref output) => {
74
115
                let input_rect = self.inverse.transform_rect(&Rect::from(output.bounds));
75
153
                self.rect = Some(self.rect.map_or(input_rect, |r| input_rect.union(&r)));
76
115
            }
77
        }
78

            
79
355
        self
80
374
    }
81

            
82
    /// Returns the final exact bounds, both with and without clipping to the effects region.
83
422
    pub fn compute(self, ctx: &FilterContext) -> Bounds {
84
422
        let effects_region = ctx.effects_region();
85

            
86
        // The default value is the filter effects region converted into
87
        // the ptimitive coordinate system.
88
422
        let mut rect = match self.rect {
89
77
            Some(r) if !self.standard_input_was_referenced => r,
90
362
            _ => self.inverse.transform_rect(&effects_region),
91
        };
92

            
93
        // If any of the properties were specified, we need to respect them.
94
        // These replacements are possible because of the primitive coordinate system.
95
422
        if self.x.is_some() || self.y.is_some() || self.width.is_some() || self.height.is_some() {
96
422
            if let Some(x) = self.x {
97
21
                let w = rect.width();
98
21
                rect.x0 = x;
99
21
                rect.x1 = rect.x0 + w;
100
            }
101
422
            if let Some(y) = self.y {
102
23
                let h = rect.height();
103
23
                rect.y0 = y;
104
23
                rect.y1 = rect.y0 + h;
105
            }
106
422
            if let Some(width) = self.width {
107
22
                rect.x1 = rect.x0 + width;
108
            }
109
422
            if let Some(height) = self.height {
110
21
                rect.y1 = rect.y0 + height;
111
            }
112
        }
113

            
114
        // Convert into the surface coordinate system.
115
422
        let unclipped = self.transform.transform_rect(&rect);
116

            
117
422
        let clipped = unclipped.intersection(&effects_region).unwrap_or_default();
118

            
119
422
        Bounds { clipped, unclipped }
120
422
    }
121
}