Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
rectangle_list.hpp
Go to the documentation of this file.
1#pragma once
2
3// RectangleList — union of rectangles for clip regions and dirty tracking.
4// Supports intersection, subtraction, containment, and bounding box queries.
5
6#include <vector>
7#include <algorithm>
8#include <cmath>
9
10namespace pulp::canvas {
11
28struct Rect2D {
29 float x = 0, y = 0, width = 0, height = 0;
30
31 float right() const { return x + width; }
32 float bottom() const { return y + height; }
33 bool empty() const { return width <= 0 || height <= 0; }
34
35 bool contains(float px, float py) const {
36 return px >= x && px < right() && py >= y && py < bottom();
37 }
38
39 bool intersects(const Rect2D& other) const {
40 if (empty() || other.empty()) return false;
41 return x < other.right() && right() > other.x &&
42 y < other.bottom() && bottom() > other.y;
43 }
44
45 Rect2D intersection(const Rect2D& other) const {
46 float ix = std::max(x, other.x);
47 float iy = std::max(y, other.y);
48 float ir = std::min(right(), other.right());
49 float ib = std::min(bottom(), other.bottom());
50 if (ir <= ix || ib <= iy) return {};
51 return {ix, iy, ir - ix, ib - iy};
52 }
53
54 Rect2D enclosing_union(const Rect2D& other) const {
55 if (empty()) return other;
56 if (other.empty()) return *this;
57 float ux = std::min(x, other.x);
58 float uy = std::min(y, other.y);
59 float ur = std::max(right(), other.right());
60 float ub = std::max(bottom(), other.bottom());
61 return {ux, uy, ur - ux, ub - uy};
62 }
63
64 bool operator==(const Rect2D& o) const {
65 return x == o.x && y == o.y && width == o.width && height == o.height;
66 }
67};
68
71public:
72 RectangleList() = default;
73
75 void add(const Rect2D& rect) {
76 if (!rect.empty())
77 rects_.push_back(rect);
78 }
79
81 void clear() { rects_.clear(); }
82
84 bool empty() const { return rects_.empty(); }
85
87 int size() const { return static_cast<int>(rects_.size()); }
88
90 const Rect2D& operator[](int i) const { return rects_[static_cast<size_t>(i)]; }
91
93 bool contains(float px, float py) const {
94 for (auto& r : rects_)
95 if (r.contains(px, py)) return true;
96 return false;
97 }
98
100 bool intersects(const Rect2D& rect) const {
101 for (auto& r : rects_)
102 if (r.intersects(rect)) return true;
103 return false;
104 }
105
108 if (rects_.empty()) return {};
109 Rect2D result = rects_[0];
110 for (size_t i = 1; i < rects_.size(); ++i)
111 result = result.enclosing_union(rects_[i]);
112 return result;
113 }
114
116 float total_area() const {
117 float area = 0;
118 for (auto& r : rects_)
119 area += r.width * r.height;
120 return area;
121 }
122
124 RectangleList clipped(const Rect2D& clip) const {
125 RectangleList result;
126 for (auto& r : rects_) {
127 auto i = r.intersection(clip);
128 if (!i.empty())
129 result.add(i);
130 }
131 return result;
132 }
133
135 void subtract(const Rect2D& sub) {
136 std::vector<Rect2D> result;
137 for (auto& r : rects_) {
138 if (!r.intersects(sub)) {
139 result.push_back(r);
140 continue;
141 }
142 // Split r into up to 4 non-overlapping pieces
143 // Top piece
144 if (sub.y > r.y)
145 result.push_back({r.x, r.y, r.width, sub.y - r.y});
146 // Bottom piece
147 if (sub.bottom() < r.bottom())
148 result.push_back({r.x, sub.bottom(), r.width, r.bottom() - sub.bottom()});
149 // Left piece (in the middle band)
150 float mid_top = std::max(r.y, sub.y);
151 float mid_bot = std::min(r.bottom(), sub.bottom());
152 if (sub.x > r.x)
153 result.push_back({r.x, mid_top, sub.x - r.x, mid_bot - mid_top});
154 // Right piece
155 if (sub.right() < r.right())
156 result.push_back({sub.right(), mid_top, r.right() - sub.right(), mid_bot - mid_top});
157 }
158 rects_ = std::move(result);
159 }
160
162 auto begin() const { return rects_.begin(); }
163 auto end() const { return rects_.end(); }
164
165 // ── Antialiased region operations ──────────────────────────────────
166 //
167 // The plain `add` / `subtract` / `clipped` API above is the
168 // axis-aligned, integer-grid story — fine for dirty-tracking and
169 // clip-band scanlines. The AA variants below treat each rectangle
170 // as a half-open float region with fractional coverage at the
171 // edges, so set operations on rectangles whose bounds don't fall
172 // on the pixel grid don't round away sub-pixel area.
173 //
174 // The pure-CPU implementation here is the cross-platform contract.
175 // When `PULP_HAS_SKIA` is defined, the equivalent calls may dispatch
176 // to `SkRegion` for the integer-aligned fast path; the AA cases
177 // still walk the CPU code path because `SkRegion` is integer-only.
178 // The CPU implementations live in this header to keep RectangleList
179 // header-only.
180
186 void union_aa(const Rect2D& rect, float aa_tolerance = 1.0f / 256.0f) {
187 if (rect.empty()) return;
188 rects_.push_back(rect);
189 coalesce_aa(aa_tolerance);
190 }
191
196 void subtract_aa(const Rect2D& sub, float aa_tolerance = 1.0f / 256.0f) {
197 if (sub.empty()) return;
198 std::vector<Rect2D> out;
199 out.reserve(rects_.size());
200 for (auto& r : rects_) {
201 if (!r.intersects(sub)) {
202 out.push_back(r);
203 continue;
204 }
205 // Top
206 if (sub.y > r.y + aa_tolerance) {
207 out.push_back({r.x, r.y, r.width, sub.y - r.y});
208 }
209 // Bottom
210 if (sub.bottom() + aa_tolerance < r.bottom()) {
211 out.push_back({r.x, sub.bottom(), r.width, r.bottom() - sub.bottom()});
212 }
213 float mid_top = std::max(r.y, sub.y);
214 float mid_bot = std::min(r.bottom(), sub.bottom());
215 float mid_h = mid_bot - mid_top;
216 if (mid_h > aa_tolerance) {
217 // Left
218 if (sub.x > r.x + aa_tolerance) {
219 out.push_back({r.x, mid_top, sub.x - r.x, mid_h});
220 }
221 // Right
222 if (sub.right() + aa_tolerance < r.right()) {
223 out.push_back({sub.right(), mid_top, r.right() - sub.right(), mid_h});
224 }
225 }
226 }
227 rects_ = std::move(out);
228 }
229
233 void intersect_aa(const Rect2D& clip, float aa_tolerance = 1.0f / 256.0f) {
234 std::vector<Rect2D> out;
235 out.reserve(rects_.size());
236 for (auto& r : rects_) {
237 auto i = r.intersection(clip);
238 // Drop slivers below the AA tolerance — they would
239 // contribute < 1/256 alpha and only bloat the list.
240 if (i.empty()) continue;
241 if (i.width < aa_tolerance || i.height < aa_tolerance) continue;
242 out.push_back(i);
243 }
244 rects_ = std::move(out);
245 }
246
251 void coalesce_aa(float aa_tolerance = 1.0f / 256.0f) {
252 bool merged = true;
253 while (merged && rects_.size() >= 2) {
254 merged = false;
255 for (std::size_t i = 0; i < rects_.size() && !merged; ++i) {
256 for (std::size_t j = i + 1; j < rects_.size(); ++j) {
257 auto& a = rects_[i];
258 auto& b = rects_[j];
259 // Horizontal merge: same y/height, right edge of one
260 // == left edge of the other.
261 bool same_horiz =
262 std::abs(a.y - b.y) < aa_tolerance &&
263 std::abs(a.height - b.height) < aa_tolerance;
264 if (same_horiz) {
265 if (std::abs(a.right() - b.x) < aa_tolerance) {
266 a = {a.x, a.y, a.width + b.width, a.height};
267 rects_.erase(rects_.begin() + static_cast<long>(j));
268 merged = true;
269 break;
270 }
271 if (std::abs(b.right() - a.x) < aa_tolerance) {
272 a = {b.x, a.y, a.width + b.width, a.height};
273 rects_.erase(rects_.begin() + static_cast<long>(j));
274 merged = true;
275 break;
276 }
277 }
278 // Vertical merge.
279 bool same_vert =
280 std::abs(a.x - b.x) < aa_tolerance &&
281 std::abs(a.width - b.width) < aa_tolerance;
282 if (same_vert) {
283 if (std::abs(a.bottom() - b.y) < aa_tolerance) {
284 a = {a.x, a.y, a.width, a.height + b.height};
285 rects_.erase(rects_.begin() + static_cast<long>(j));
286 merged = true;
287 break;
288 }
289 if (std::abs(b.bottom() - a.y) < aa_tolerance) {
290 a = {a.x, b.y, a.width, a.height + b.height};
291 rects_.erase(rects_.begin() + static_cast<long>(j));
292 merged = true;
293 break;
294 }
295 }
296 }
297 }
298 }
299 }
300
306 static constexpr const char* aa_backend() {
307#if defined(PULP_HAS_SKIA) && PULP_HAS_SKIA
308 return "skia";
309#else
310 return "cpu";
311#endif
312 }
313
314private:
315 std::vector<Rect2D> rects_;
316};
317
318} // namespace pulp::canvas
void add(const Rect2D &rect)
Add a rectangle to the list (may overlap existing).
Definition rectangle_list.hpp:75
int size() const
Number of rectangles.
Definition rectangle_list.hpp:87
void coalesce_aa(float aa_tolerance=1.0f/256.0f)
Coalesce adjacent rectangles that share a full edge (within aa_tolerance).
Definition rectangle_list.hpp:251
bool empty() const
Whether the list is empty.
Definition rectangle_list.hpp:84
auto begin() const
Iterator support.
Definition rectangle_list.hpp:162
void intersect_aa(const Rect2D &clip, float aa_tolerance=1.0f/256.0f)
Antialiased intersect — keep only the intersection with clip, in-place.
Definition rectangle_list.hpp:233
void subtract_aa(const Rect2D &sub, float aa_tolerance=1.0f/256.0f)
Antialiased subtract — splits intersecting rectangles into non-overlapping pieces.
Definition rectangle_list.hpp:196
static constexpr const char * aa_backend()
Reports the implementation backend in use.
Definition rectangle_list.hpp:306
bool intersects(const Rect2D &rect) const
Whether any rectangle intersects the given rect.
Definition rectangle_list.hpp:100
Rect2D bounding_box() const
Compute the bounding box of all rectangles.
Definition rectangle_list.hpp:107
auto end() const
Definition rectangle_list.hpp:163
void clear()
Clear all rectangles.
Definition rectangle_list.hpp:81
void subtract(const Rect2D &sub)
Subtract a rectangle from all rectangles in the list.
Definition rectangle_list.hpp:135
void union_aa(const Rect2D &rect, float aa_tolerance=1.0f/256.0f)
Antialiased union — appends rect, then merges any pieces that share an edge to within aa_tolerance.
Definition rectangle_list.hpp:186
float total_area() const
Total area (may double-count overlapping regions).
Definition rectangle_list.hpp:116
RectangleList clipped(const Rect2D &clip) const
Clip this list against a rectangle (keep only the intersection).
Definition rectangle_list.hpp:124
const Rect2D & operator[](int i) const
Get a rectangle by index.
Definition rectangle_list.hpp:90
bool contains(float px, float py) const
Whether a point is contained in any rectangle.
Definition rectangle_list.hpp:93
Definition affine_transform.hpp:24
Axis-aligned rectangle in canvas coordinates.
Definition rectangle_list.hpp:28
float right() const
Definition rectangle_list.hpp:31
float y
Definition rectangle_list.hpp:29
bool intersects(const Rect2D &other) const
Definition rectangle_list.hpp:39
Rect2D enclosing_union(const Rect2D &other) const
Definition rectangle_list.hpp:54
float x
Definition rectangle_list.hpp:29
Rect2D intersection(const Rect2D &other) const
Definition rectangle_list.hpp:45
float height
Definition rectangle_list.hpp:29
float width
Definition rectangle_list.hpp:29
bool empty() const
Definition rectangle_list.hpp:33
bool contains(float px, float py) const
Definition rectangle_list.hpp:35
float bottom() const
Definition rectangle_list.hpp:32
bool operator==(const Rect2D &o) const
Definition rectangle_list.hpp:64