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 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)]
30
pub 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

            
44
impl BoundsBuilder {
45
    /// Constructs a new `BoundsBuilder`.
46
    #[inline]
47
424
    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
424
        Self {
56
            x,
57
            y,
58
            width,
59
            height,
60
            transform,
61
424
            inverse: transform.invert().unwrap(),
62
            standard_input_was_referenced: false,
63
424
            rect: None,
64
        }
65
424
    }
66

            
67
    /// Adds a filter primitive input to the bounding box.
68
    #[inline]
69
374
    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
374
        if self.standard_input_was_referenced {
73
19
            return self;
74
        }
75

            
76
355
        match *input {
77
240
            FilterInput::StandardInput(_) => {
78
240
                self.standard_input_was_referenced = true;
79
            }
80
115
            FilterInput::PrimitiveOutput(ref output) => {
81
115
                let input_rect = self.inverse.transform_rect(&Rect::from(output.bounds));
82
153
                self.rect = Some(self.rect.map_or(input_rect, |r| input_rect.union(&r)));
83
115
            }
84
        }
85

            
86
355
        self
87
374
    }
88

            
89
    /// Returns the final exact bounds, both with and without clipping to the effects region.
90
424
    pub fn compute(self, ctx: &FilterContext) -> Bounds {
91
424
        let effects_region = ctx.effects_region();
92

            
93
        // The default value is the filter effects region converted into
94
        // the ptimitive coordinate system.
95
424
        let mut rect = match self.rect {
96
77
            Some(r) if !self.standard_input_was_referenced => r,
97
364
            _ => 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
424
        if self.x.is_some() || self.y.is_some() || self.width.is_some() || self.height.is_some() {
103
424
            if let Some(x) = self.x {
104
21
                let w = rect.width();
105
21
                rect.x0 = x;
106
21
                rect.x1 = rect.x0 + w;
107
            }
108
424
            if let Some(y) = self.y {
109
23
                let h = rect.height();
110
23
                rect.y0 = y;
111
23
                rect.y1 = rect.y0 + h;
112
            }
113
424
            if let Some(width) = self.width {
114
22
                rect.x1 = rect.x0 + width;
115
            }
116
424
            if let Some(height) = self.height {
117
21
                rect.y1 = rect.y0 + height;
118
            }
119
        }
120

            
121
        // Convert into the surface coordinate system.
122
424
        let unclipped = self.transform.transform_rect(&rect);
123

            
124
424
        let clipped = unclipped.intersection(&effects_region).unwrap_or_default();
125

            
126
424
        Bounds {
127
424
            x: self.x,
128
424
            y: self.y,
129
424
            width: self.width,
130
424
            height: self.height,
131
            clipped,
132
            unclipped,
133
        }
134
424
    }
135
}