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