|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
|
2 * This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #ifndef MOZILLA_GFX_BASERECT_H_ |
|
7 #define MOZILLA_GFX_BASERECT_H_ |
|
8 |
|
9 #include <algorithm> |
|
10 #include <cmath> |
|
11 |
|
12 #include "mozilla/Assertions.h" |
|
13 #include "mozilla/FloatingPoint.h" |
|
14 #include "mozilla/TypeTraits.h" |
|
15 |
|
16 namespace mozilla { |
|
17 namespace gfx { |
|
18 |
|
19 /** |
|
20 * Rectangles have two interpretations: a set of (zero-size) points, |
|
21 * and a rectangular area of the plane. Most rectangle operations behave |
|
22 * the same no matter what interpretation is being used, but some operations |
|
23 * differ: |
|
24 * -- Equality tests behave differently. When a rectangle represents an area, |
|
25 * all zero-width and zero-height rectangles are equal to each other since they |
|
26 * represent the empty area. But when a rectangle represents a set of |
|
27 * mathematical points, zero-width and zero-height rectangles can be unequal. |
|
28 * -- The union operation can behave differently. When rectangles represent |
|
29 * areas, taking the union of a zero-width or zero-height rectangle with |
|
30 * another rectangle can just ignore the empty rectangle. But when rectangles |
|
31 * represent sets of mathematical points, we may need to extend the latter |
|
32 * rectangle to include the points of a zero-width or zero-height rectangle. |
|
33 * |
|
34 * To ensure that these interpretations are explicitly disambiguated, we |
|
35 * deny access to the == and != operators and require use of IsEqualEdges and |
|
36 * IsEqualInterior instead. Similarly we provide separate Union and UnionEdges |
|
37 * methods. |
|
38 * |
|
39 * Do not use this class directly. Subclass it, pass that subclass as the |
|
40 * Sub parameter, and only use that subclass. |
|
41 */ |
|
42 template <class T, class Sub, class Point, class SizeT, class MarginT> |
|
43 struct BaseRect { |
|
44 T x, y, width, height; |
|
45 |
|
46 // Constructors |
|
47 BaseRect() : x(0), y(0), width(0), height(0) {} |
|
48 BaseRect(const Point& aOrigin, const SizeT &aSize) : |
|
49 x(aOrigin.x), y(aOrigin.y), width(aSize.width), height(aSize.height) |
|
50 { |
|
51 } |
|
52 BaseRect(T aX, T aY, T aWidth, T aHeight) : |
|
53 x(aX), y(aY), width(aWidth), height(aHeight) |
|
54 { |
|
55 } |
|
56 |
|
57 // Emptiness. An empty rect is one that has no area, i.e. its height or width |
|
58 // is <= 0 |
|
59 bool IsEmpty() const { return height <= 0 || width <= 0; } |
|
60 void SetEmpty() { width = height = 0; } |
|
61 |
|
62 // "Finite" means not inf and not NaN |
|
63 bool IsFinite() const |
|
64 { |
|
65 typedef typename mozilla::Conditional<mozilla::IsSame<T, float>::value, float, double>::Type FloatType; |
|
66 return (mozilla::IsFinite(FloatType(x)) && |
|
67 mozilla::IsFinite(FloatType(y)) && |
|
68 mozilla::IsFinite(FloatType(width)) && |
|
69 mozilla::IsFinite(FloatType(height))); |
|
70 } |
|
71 |
|
72 // Returns true if this rectangle contains the interior of aRect. Always |
|
73 // returns true if aRect is empty, and always returns false is aRect is |
|
74 // nonempty but this rect is empty. |
|
75 bool Contains(const Sub& aRect) const |
|
76 { |
|
77 return aRect.IsEmpty() || |
|
78 (x <= aRect.x && aRect.XMost() <= XMost() && |
|
79 y <= aRect.y && aRect.YMost() <= YMost()); |
|
80 } |
|
81 // Returns true if this rectangle contains the rectangle (aX,aY,1,1). |
|
82 bool Contains(T aX, T aY) const |
|
83 { |
|
84 return x <= aX && aX + 1 <= XMost() && |
|
85 y <= aY && aY + 1 <= YMost(); |
|
86 } |
|
87 // Returns true if this rectangle contains the rectangle (aPoint.x,aPoint.y,1,1). |
|
88 bool Contains(const Point& aPoint) const { return Contains(aPoint.x, aPoint.y); } |
|
89 |
|
90 // Intersection. Returns TRUE if the receiver's area has non-empty |
|
91 // intersection with aRect's area, and FALSE otherwise. |
|
92 // Always returns false if aRect is empty or 'this' is empty. |
|
93 bool Intersects(const Sub& aRect) const |
|
94 { |
|
95 return x < aRect.XMost() && aRect.x < XMost() && |
|
96 y < aRect.YMost() && aRect.y < YMost(); |
|
97 } |
|
98 // Returns the rectangle containing the intersection of the points |
|
99 // (including edges) of *this and aRect. If there are no points in that |
|
100 // intersection, returns an empty rectangle with x/y set to the std::max of the x/y |
|
101 // of *this and aRect. |
|
102 Sub Intersect(const Sub& aRect) const |
|
103 { |
|
104 Sub result; |
|
105 result.x = std::max<T>(x, aRect.x); |
|
106 result.y = std::max<T>(y, aRect.y); |
|
107 result.width = std::min<T>(XMost(), aRect.XMost()) - result.x; |
|
108 result.height = std::min<T>(YMost(), aRect.YMost()) - result.y; |
|
109 if (result.width < 0 || result.height < 0) { |
|
110 result.SizeTo(0, 0); |
|
111 } |
|
112 return result; |
|
113 } |
|
114 // Sets *this to be the rectangle containing the intersection of the points |
|
115 // (including edges) of *this and aRect. If there are no points in that |
|
116 // intersection, sets *this to be an empty rectangle with x/y set to the std::max |
|
117 // of the x/y of *this and aRect. |
|
118 // |
|
119 // 'this' can be the same object as either aRect1 or aRect2 |
|
120 bool IntersectRect(const Sub& aRect1, const Sub& aRect2) |
|
121 { |
|
122 *static_cast<Sub*>(this) = aRect1.Intersect(aRect2); |
|
123 return !IsEmpty(); |
|
124 } |
|
125 |
|
126 // Returns the smallest rectangle that contains both the area of both |
|
127 // this and aRect2. |
|
128 // Thus, empty input rectangles are ignored. |
|
129 // If both rectangles are empty, returns this. |
|
130 Sub Union(const Sub& aRect) const |
|
131 { |
|
132 if (IsEmpty()) { |
|
133 return aRect; |
|
134 } else if (aRect.IsEmpty()) { |
|
135 return *static_cast<const Sub*>(this); |
|
136 } else { |
|
137 return UnionEdges(aRect); |
|
138 } |
|
139 } |
|
140 // Returns the smallest rectangle that contains both the points (including |
|
141 // edges) of both aRect1 and aRect2. |
|
142 // Thus, empty input rectangles are allowed to affect the result. |
|
143 Sub UnionEdges(const Sub& aRect) const |
|
144 { |
|
145 Sub result; |
|
146 result.x = std::min(x, aRect.x); |
|
147 result.y = std::min(y, aRect.y); |
|
148 result.width = std::max(XMost(), aRect.XMost()) - result.x; |
|
149 result.height = std::max(YMost(), aRect.YMost()) - result.y; |
|
150 return result; |
|
151 } |
|
152 // Computes the smallest rectangle that contains both the area of both |
|
153 // aRect1 and aRect2, and fills 'this' with the result. |
|
154 // Thus, empty input rectangles are ignored. |
|
155 // If both rectangles are empty, sets 'this' to aRect2. |
|
156 // |
|
157 // 'this' can be the same object as either aRect1 or aRect2 |
|
158 void UnionRect(const Sub& aRect1, const Sub& aRect2) |
|
159 { |
|
160 *static_cast<Sub*>(this) = aRect1.Union(aRect2); |
|
161 } |
|
162 |
|
163 // Computes the smallest rectangle that contains both the points (including |
|
164 // edges) of both aRect1 and aRect2. |
|
165 // Thus, empty input rectangles are allowed to affect the result. |
|
166 // |
|
167 // 'this' can be the same object as either aRect1 or aRect2 |
|
168 void UnionRectEdges(const Sub& aRect1, const Sub& aRect2) |
|
169 { |
|
170 *static_cast<Sub*>(this) = aRect1.UnionEdges(aRect2); |
|
171 } |
|
172 |
|
173 void SetRect(T aX, T aY, T aWidth, T aHeight) |
|
174 { |
|
175 x = aX; y = aY; width = aWidth; height = aHeight; |
|
176 } |
|
177 void SetRect(const Point& aPt, const SizeT& aSize) |
|
178 { |
|
179 SetRect(aPt.x, aPt.y, aSize.width, aSize.height); |
|
180 } |
|
181 void MoveTo(T aX, T aY) { x = aX; y = aY; } |
|
182 void MoveTo(const Point& aPoint) { x = aPoint.x; y = aPoint.y; } |
|
183 void MoveBy(T aDx, T aDy) { x += aDx; y += aDy; } |
|
184 void MoveBy(const Point& aPoint) { x += aPoint.x; y += aPoint.y; } |
|
185 void SizeTo(T aWidth, T aHeight) { width = aWidth; height = aHeight; } |
|
186 void SizeTo(const SizeT& aSize) { width = aSize.width; height = aSize.height; } |
|
187 |
|
188 void Inflate(T aD) { Inflate(aD, aD); } |
|
189 void Inflate(T aDx, T aDy) |
|
190 { |
|
191 x -= aDx; |
|
192 y -= aDy; |
|
193 width += 2 * aDx; |
|
194 height += 2 * aDy; |
|
195 } |
|
196 void Inflate(const MarginT& aMargin) |
|
197 { |
|
198 x -= aMargin.left; |
|
199 y -= aMargin.top; |
|
200 width += aMargin.LeftRight(); |
|
201 height += aMargin.TopBottom(); |
|
202 } |
|
203 void Inflate(const SizeT& aSize) { Inflate(aSize.width, aSize.height); } |
|
204 |
|
205 void Deflate(T aD) { Deflate(aD, aD); } |
|
206 void Deflate(T aDx, T aDy) |
|
207 { |
|
208 x += aDx; |
|
209 y += aDy; |
|
210 width = std::max(T(0), width - 2 * aDx); |
|
211 height = std::max(T(0), height - 2 * aDy); |
|
212 } |
|
213 void Deflate(const MarginT& aMargin) |
|
214 { |
|
215 x += aMargin.left; |
|
216 y += aMargin.top; |
|
217 width = std::max(T(0), width - aMargin.LeftRight()); |
|
218 height = std::max(T(0), height - aMargin.TopBottom()); |
|
219 } |
|
220 void Deflate(const SizeT& aSize) { Deflate(aSize.width, aSize.height); } |
|
221 |
|
222 // Return true if the rectangles contain the same set of points, including |
|
223 // points on the edges. |
|
224 // Use when we care about the exact x/y/width/height values being |
|
225 // equal (i.e. we care about differences in empty rectangles). |
|
226 bool IsEqualEdges(const Sub& aRect) const |
|
227 { |
|
228 return x == aRect.x && y == aRect.y && |
|
229 width == aRect.width && height == aRect.height; |
|
230 } |
|
231 // Return true if the rectangles contain the same area of the plane. |
|
232 // Use when we do not care about differences in empty rectangles. |
|
233 bool IsEqualInterior(const Sub& aRect) const |
|
234 { |
|
235 return IsEqualEdges(aRect) || (IsEmpty() && aRect.IsEmpty()); |
|
236 } |
|
237 |
|
238 Sub operator+(const Point& aPoint) const |
|
239 { |
|
240 return Sub(x + aPoint.x, y + aPoint.y, width, height); |
|
241 } |
|
242 Sub operator-(const Point& aPoint) const |
|
243 { |
|
244 return Sub(x - aPoint.x, y - aPoint.y, width, height); |
|
245 } |
|
246 Sub& operator+=(const Point& aPoint) |
|
247 { |
|
248 MoveBy(aPoint); |
|
249 return *static_cast<Sub*>(this); |
|
250 } |
|
251 Sub& operator-=(const Point& aPoint) |
|
252 { |
|
253 MoveBy(-aPoint); |
|
254 return *static_cast<Sub*>(this); |
|
255 } |
|
256 |
|
257 // Find difference as a Margin |
|
258 MarginT operator-(const Sub& aRect) const |
|
259 { |
|
260 return MarginT(aRect.y - y, |
|
261 XMost() - aRect.XMost(), |
|
262 YMost() - aRect.YMost(), |
|
263 aRect.x - x); |
|
264 } |
|
265 |
|
266 // Helpers for accessing the vertices |
|
267 Point TopLeft() const { return Point(x, y); } |
|
268 Point TopRight() const { return Point(XMost(), y); } |
|
269 Point BottomLeft() const { return Point(x, YMost()); } |
|
270 Point BottomRight() const { return Point(XMost(), YMost()); } |
|
271 Point Center() const { return Point(x, y) + Point(width, height)/2; } |
|
272 SizeT Size() const { return SizeT(width, height); } |
|
273 |
|
274 // Helper methods for computing the extents |
|
275 T X() const { return x; } |
|
276 T Y() const { return y; } |
|
277 T Width() const { return width; } |
|
278 T Height() const { return height; } |
|
279 T XMost() const { return x + width; } |
|
280 T YMost() const { return y + height; } |
|
281 |
|
282 // Moves one edge of the rect without moving the opposite edge. |
|
283 void SetLeftEdge(T aX) { |
|
284 MOZ_ASSERT(aX <= XMost()); |
|
285 width = XMost() - aX; |
|
286 x = aX; |
|
287 } |
|
288 void SetRightEdge(T aXMost) { |
|
289 MOZ_ASSERT(aXMost >= x); |
|
290 width = aXMost - x; |
|
291 } |
|
292 void SetTopEdge(T aY) { |
|
293 MOZ_ASSERT(aY <= YMost()); |
|
294 height = YMost() - aY; |
|
295 y = aY; |
|
296 } |
|
297 void SetBottomEdge(T aYMost) { |
|
298 MOZ_ASSERT(aYMost >= y); |
|
299 height = aYMost - y; |
|
300 } |
|
301 |
|
302 // Round the rectangle edges to integer coordinates, such that the rounded |
|
303 // rectangle has the same set of pixel centers as the original rectangle. |
|
304 // Edges at offset 0.5 round up. |
|
305 // Suitable for most places where integral device coordinates |
|
306 // are needed, but note that any translation should be applied first to |
|
307 // avoid pixel rounding errors. |
|
308 // Note that this is *not* rounding to nearest integer if the values are negative. |
|
309 // They are always rounding as floor(n + 0.5). |
|
310 // See https://bugzilla.mozilla.org/show_bug.cgi?id=410748#c14 |
|
311 // If you need similar method which is using NS_round(), you should create |
|
312 // new |RoundAwayFromZero()| method. |
|
313 void Round() |
|
314 { |
|
315 T x0 = static_cast<T>(floor(T(X()) + 0.5)); |
|
316 T y0 = static_cast<T>(floor(T(Y()) + 0.5)); |
|
317 T x1 = static_cast<T>(floor(T(XMost()) + 0.5)); |
|
318 T y1 = static_cast<T>(floor(T(YMost()) + 0.5)); |
|
319 |
|
320 x = x0; |
|
321 y = y0; |
|
322 |
|
323 width = x1 - x0; |
|
324 height = y1 - y0; |
|
325 } |
|
326 |
|
327 // Snap the rectangle edges to integer coordinates, such that the |
|
328 // original rectangle contains the resulting rectangle. |
|
329 void RoundIn() |
|
330 { |
|
331 T x0 = static_cast<T>(ceil(T(X()))); |
|
332 T y0 = static_cast<T>(ceil(T(Y()))); |
|
333 T x1 = static_cast<T>(floor(T(XMost()))); |
|
334 T y1 = static_cast<T>(floor(T(YMost()))); |
|
335 |
|
336 x = x0; |
|
337 y = y0; |
|
338 |
|
339 width = x1 - x0; |
|
340 height = y1 - y0; |
|
341 } |
|
342 |
|
343 // Snap the rectangle edges to integer coordinates, such that the |
|
344 // resulting rectangle contains the original rectangle. |
|
345 void RoundOut() |
|
346 { |
|
347 T x0 = static_cast<T>(floor(T(X()))); |
|
348 T y0 = static_cast<T>(floor(T(Y()))); |
|
349 T x1 = static_cast<T>(ceil(T(XMost()))); |
|
350 T y1 = static_cast<T>(ceil(T(YMost()))); |
|
351 |
|
352 x = x0; |
|
353 y = y0; |
|
354 |
|
355 width = x1 - x0; |
|
356 height = y1 - y0; |
|
357 } |
|
358 |
|
359 // Scale 'this' by aScale without doing any rounding. |
|
360 void Scale(T aScale) { Scale(aScale, aScale); } |
|
361 // Scale 'this' by aXScale and aYScale, without doing any rounding. |
|
362 void Scale(T aXScale, T aYScale) |
|
363 { |
|
364 T right = XMost() * aXScale; |
|
365 T bottom = YMost() * aYScale; |
|
366 x = x * aXScale; |
|
367 y = y * aYScale; |
|
368 width = right - x; |
|
369 height = bottom - y; |
|
370 } |
|
371 // Scale 'this' by aScale, converting coordinates to integers so that the result is |
|
372 // the smallest integer-coordinate rectangle containing the unrounded result. |
|
373 // Note: this can turn an empty rectangle into a non-empty rectangle |
|
374 void ScaleRoundOut(double aScale) { ScaleRoundOut(aScale, aScale); } |
|
375 // Scale 'this' by aXScale and aYScale, converting coordinates to integers so |
|
376 // that the result is the smallest integer-coordinate rectangle containing the |
|
377 // unrounded result. |
|
378 // Note: this can turn an empty rectangle into a non-empty rectangle |
|
379 void ScaleRoundOut(double aXScale, double aYScale) |
|
380 { |
|
381 T right = static_cast<T>(ceil(double(XMost()) * aXScale)); |
|
382 T bottom = static_cast<T>(ceil(double(YMost()) * aYScale)); |
|
383 x = static_cast<T>(floor(double(x) * aXScale)); |
|
384 y = static_cast<T>(floor(double(y) * aYScale)); |
|
385 width = right - x; |
|
386 height = bottom - y; |
|
387 } |
|
388 // Scale 'this' by aScale, converting coordinates to integers so that the result is |
|
389 // the largest integer-coordinate rectangle contained by the unrounded result. |
|
390 void ScaleRoundIn(double aScale) { ScaleRoundIn(aScale, aScale); } |
|
391 // Scale 'this' by aXScale and aYScale, converting coordinates to integers so |
|
392 // that the result is the largest integer-coordinate rectangle contained by the |
|
393 // unrounded result. |
|
394 void ScaleRoundIn(double aXScale, double aYScale) |
|
395 { |
|
396 T right = static_cast<T>(floor(double(XMost()) * aXScale)); |
|
397 T bottom = static_cast<T>(floor(double(YMost()) * aYScale)); |
|
398 x = static_cast<T>(ceil(double(x) * aXScale)); |
|
399 y = static_cast<T>(ceil(double(y) * aYScale)); |
|
400 width = std::max<T>(0, right - x); |
|
401 height = std::max<T>(0, bottom - y); |
|
402 } |
|
403 // Scale 'this' by 1/aScale, converting coordinates to integers so that the result is |
|
404 // the smallest integer-coordinate rectangle containing the unrounded result. |
|
405 // Note: this can turn an empty rectangle into a non-empty rectangle |
|
406 void ScaleInverseRoundOut(double aScale) { ScaleInverseRoundOut(aScale, aScale); } |
|
407 // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers so |
|
408 // that the result is the smallest integer-coordinate rectangle containing the |
|
409 // unrounded result. |
|
410 // Note: this can turn an empty rectangle into a non-empty rectangle |
|
411 void ScaleInverseRoundOut(double aXScale, double aYScale) |
|
412 { |
|
413 T right = static_cast<T>(ceil(double(XMost()) / aXScale)); |
|
414 T bottom = static_cast<T>(ceil(double(YMost()) / aYScale)); |
|
415 x = static_cast<T>(floor(double(x) / aXScale)); |
|
416 y = static_cast<T>(floor(double(y) / aYScale)); |
|
417 width = right - x; |
|
418 height = bottom - y; |
|
419 } |
|
420 // Scale 'this' by 1/aScale, converting coordinates to integers so that the result is |
|
421 // the largest integer-coordinate rectangle contained by the unrounded result. |
|
422 void ScaleInverseRoundIn(double aScale) { ScaleInverseRoundIn(aScale, aScale); } |
|
423 // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers so |
|
424 // that the result is the largest integer-coordinate rectangle contained by the |
|
425 // unrounded result. |
|
426 void ScaleInverseRoundIn(double aXScale, double aYScale) |
|
427 { |
|
428 T right = static_cast<T>(floor(double(XMost()) / aXScale)); |
|
429 T bottom = static_cast<T>(floor(double(YMost()) / aYScale)); |
|
430 x = static_cast<T>(ceil(double(x) / aXScale)); |
|
431 y = static_cast<T>(ceil(double(y) / aYScale)); |
|
432 width = std::max<T>(0, right - x); |
|
433 height = std::max<T>(0, bottom - y); |
|
434 } |
|
435 |
|
436 /** |
|
437 * Clamp aPoint to this rectangle. It is allowed to end up on any |
|
438 * edge of the rectangle. |
|
439 */ |
|
440 Point ClampPoint(const Point& aPoint) const |
|
441 { |
|
442 return Point(std::max(x, std::min(XMost(), aPoint.x)), |
|
443 std::max(y, std::min(YMost(), aPoint.y))); |
|
444 } |
|
445 |
|
446 /** |
|
447 * Clamp this rectangle to be inside aRect. The function returns a copy of |
|
448 * this rect after it is forced inside the bounds of aRect. It will attempt to |
|
449 * retain the size but will shrink the dimensions that don't fit. |
|
450 */ |
|
451 Sub ForceInside(const Sub& aRect) const |
|
452 { |
|
453 Sub rect(std::max(aRect.x, x), |
|
454 std::max(aRect.y, y), |
|
455 std::min(aRect.width, width), |
|
456 std::min(aRect.height, height)); |
|
457 rect.x = std::min(rect.XMost(), aRect.XMost()) - rect.width; |
|
458 rect.y = std::min(rect.YMost(), aRect.YMost()) - rect.height; |
|
459 return rect; |
|
460 } |
|
461 |
|
462 private: |
|
463 // Do not use the default operator== or operator!= ! |
|
464 // Use IsEqualEdges or IsEqualInterior explicitly. |
|
465 bool operator==(const Sub& aRect) const { return false; } |
|
466 bool operator!=(const Sub& aRect) const { return false; } |
|
467 }; |
|
468 |
|
469 } |
|
470 } |
|
471 |
|
472 #endif /* MOZILLA_GFX_BASERECT_H_ */ |