1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "DisplayItemClip.h"
9 #include "gfxContext.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/gfx/PathHelpers.h"
13 #include "mozilla/layers/StackingContextHelper.h"
14 #include "mozilla/StaticPtr.h"
15 #include "mozilla/webrender/WebRenderTypes.h"
16 #include "nsPresContext.h"
17 #include "nsCSSRendering.h"
18 #include "nsLayoutUtils.h"
21 using namespace mozilla::gfx
;
25 void DisplayItemClip::SetTo(const nsRect
& aRect
) { SetTo(aRect
, nullptr); }
27 void DisplayItemClip::SetTo(const nsRect
& aRect
, const nscoord
* aRadii
) {
31 mRoundedClipRects
.SetLength(1);
32 mRoundedClipRects
[0].mRect
= aRect
;
33 memcpy(mRoundedClipRects
[0].mRadii
, aRadii
, sizeof(nscoord
) * 8);
35 mRoundedClipRects
.Clear();
39 void DisplayItemClip::SetTo(const nsRect
& aRect
, const nsRect
& aRoundedRect
,
40 const nscoord
* aRadii
) {
43 mRoundedClipRects
.SetLength(1);
44 mRoundedClipRects
[0].mRect
= aRoundedRect
;
45 memcpy(mRoundedClipRects
[0].mRadii
, aRadii
, sizeof(nscoord
) * 8);
48 bool DisplayItemClip::MayIntersect(const nsRect
& aRect
) const {
50 return !aRect
.IsEmpty();
52 nsRect r
= aRect
.Intersect(mClipRect
);
56 for (uint32_t i
= 0; i
< mRoundedClipRects
.Length(); ++i
) {
57 const RoundedRect
& rr
= mRoundedClipRects
[i
];
58 if (!nsLayoutUtils::RoundedRectIntersectsRect(rr
.mRect
, rr
.mRadii
, r
)) {
65 void DisplayItemClip::IntersectWith(const DisplayItemClip
& aOther
) {
66 if (!aOther
.mHaveClipRect
) {
73 if (!mClipRect
.IntersectRect(mClipRect
, aOther
.mClipRect
)) {
74 mRoundedClipRects
.Clear();
77 mRoundedClipRects
.AppendElements(aOther
.mRoundedClipRects
);
80 void DisplayItemClip::ApplyTo(gfxContext
* aContext
, int32_t A2D
) const {
81 ApplyRectTo(aContext
, A2D
);
82 ApplyRoundedRectClipsTo(aContext
, A2D
, 0, mRoundedClipRects
.Length());
85 void DisplayItemClip::ApplyRectTo(gfxContext
* aContext
, int32_t A2D
) const {
87 gfxRect clip
= nsLayoutUtils::RectToGfxRect(mClipRect
, A2D
);
88 aContext
->SnappedRectangle(clip
);
92 void DisplayItemClip::ApplyRoundedRectClipsTo(gfxContext
* aContext
, int32_t A2D
,
94 uint32_t aEnd
) const {
95 DrawTarget
& aDrawTarget
= *aContext
->GetDrawTarget();
97 aEnd
= std::min
<uint32_t>(aEnd
, mRoundedClipRects
.Length());
99 for (uint32_t i
= aBegin
; i
< aEnd
; ++i
) {
100 RefPtr
<Path
> roundedRect
=
101 MakeRoundedRectPath(aDrawTarget
, A2D
, mRoundedClipRects
[i
]);
102 aContext
->Clip(roundedRect
);
106 void DisplayItemClip::FillIntersectionOfRoundedRectClips(
107 gfxContext
* aContext
, const DeviceColor
& aColor
,
108 int32_t aAppUnitsPerDevPixel
) const {
109 DrawTarget
& aDrawTarget
= *aContext
->GetDrawTarget();
111 uint32_t end
= mRoundedClipRects
.Length();
116 // Push clips for any rects that come BEFORE the rect at |aEnd - 1|, if any:
117 ApplyRoundedRectClipsTo(aContext
, aAppUnitsPerDevPixel
, 0, end
- 1);
119 // Now fill the rect at |aEnd - 1|:
120 RefPtr
<Path
> roundedRect
= MakeRoundedRectPath(
121 aDrawTarget
, aAppUnitsPerDevPixel
, mRoundedClipRects
[end
- 1]);
122 aDrawTarget
.Fill(roundedRect
, ColorPattern(aColor
));
124 // Finally, pop any clips that we may have pushed:
125 for (uint32_t i
= 0; i
< end
- 1; ++i
) {
130 already_AddRefed
<Path
> DisplayItemClip::MakeRoundedRectPath(
131 DrawTarget
& aDrawTarget
, int32_t A2D
, const RoundedRect
& aRoundRect
) const {
132 RectCornerRadii pixelRadii
;
133 nsCSSRendering::ComputePixelRadii(aRoundRect
.mRadii
, A2D
, &pixelRadii
);
135 Rect rect
= NSRectToSnappedRect(aRoundRect
.mRect
, A2D
, aDrawTarget
);
137 return MakePathForRoundedRect(aDrawTarget
, rect
, pixelRadii
);
140 nsRect
DisplayItemClip::ApproximateIntersectInward(const nsRect
& aRect
) const {
143 r
.IntersectRect(r
, mClipRect
);
145 for (uint32_t i
= 0, iEnd
= mRoundedClipRects
.Length(); i
< iEnd
; ++i
) {
146 const RoundedRect
& rr
= mRoundedClipRects
[i
];
148 nsLayoutUtils::RoundedRectIntersectRect(rr
.mRect
, rr
.mRadii
, r
);
149 r
= rgn
.GetLargestRectangle();
154 // Test if (aXPoint, aYPoint) is in the ellipse with center (aXCenter, aYCenter)
155 // and radii aXRadius, aYRadius.
156 static bool IsInsideEllipse(nscoord aXRadius
, nscoord aXCenter
, nscoord aXPoint
,
157 nscoord aYRadius
, nscoord aYCenter
,
159 float scaledX
= float(aXPoint
- aXCenter
) / float(aXRadius
);
160 float scaledY
= float(aYPoint
- aYCenter
) / float(aYRadius
);
161 return scaledX
* scaledX
+ scaledY
* scaledY
< 1.0f
;
164 bool DisplayItemClip::IsRectClippedByRoundedCorner(const nsRect
& aRect
) const {
165 if (mRoundedClipRects
.IsEmpty()) return false;
168 rect
.IntersectRect(aRect
, NonRoundedIntersection());
169 for (uint32_t i
= 0, iEnd
= mRoundedClipRects
.Length(); i
< iEnd
; ++i
) {
170 const RoundedRect
& rr
= mRoundedClipRects
[i
];
172 if (rect
.x
< rr
.mRect
.x
+ rr
.mRadii
[eCornerTopLeftX
] &&
173 rect
.y
< rr
.mRect
.y
+ rr
.mRadii
[eCornerTopLeftY
]) {
174 if (!IsInsideEllipse(rr
.mRadii
[eCornerTopLeftX
],
175 rr
.mRect
.x
+ rr
.mRadii
[eCornerTopLeftX
], rect
.x
,
176 rr
.mRadii
[eCornerTopLeftY
],
177 rr
.mRect
.y
+ rr
.mRadii
[eCornerTopLeftY
], rect
.y
)) {
182 if (rect
.XMost() > rr
.mRect
.XMost() - rr
.mRadii
[eCornerTopRightX
] &&
183 rect
.y
< rr
.mRect
.y
+ rr
.mRadii
[eCornerTopRightY
]) {
184 if (!IsInsideEllipse(rr
.mRadii
[eCornerTopRightX
],
185 rr
.mRect
.XMost() - rr
.mRadii
[eCornerTopRightX
],
186 rect
.XMost(), rr
.mRadii
[eCornerTopRightY
],
187 rr
.mRect
.y
+ rr
.mRadii
[eCornerTopRightY
], rect
.y
)) {
192 if (rect
.x
< rr
.mRect
.x
+ rr
.mRadii
[eCornerBottomLeftX
] &&
193 rect
.YMost() > rr
.mRect
.YMost() - rr
.mRadii
[eCornerBottomLeftY
]) {
194 if (!IsInsideEllipse(rr
.mRadii
[eCornerBottomLeftX
],
195 rr
.mRect
.x
+ rr
.mRadii
[eCornerBottomLeftX
], rect
.x
,
196 rr
.mRadii
[eCornerBottomLeftY
],
197 rr
.mRect
.YMost() - rr
.mRadii
[eCornerBottomLeftY
],
203 if (rect
.XMost() > rr
.mRect
.XMost() - rr
.mRadii
[eCornerBottomRightX
] &&
204 rect
.YMost() > rr
.mRect
.YMost() - rr
.mRadii
[eCornerBottomRightY
]) {
205 if (!IsInsideEllipse(rr
.mRadii
[eCornerBottomRightX
],
206 rr
.mRect
.XMost() - rr
.mRadii
[eCornerBottomRightX
],
207 rect
.XMost(), rr
.mRadii
[eCornerBottomRightY
],
208 rr
.mRect
.YMost() - rr
.mRadii
[eCornerBottomRightY
],
217 nsRect
DisplayItemClip::NonRoundedIntersection() const {
218 NS_ASSERTION(mHaveClipRect
, "Must have a clip rect!");
219 nsRect result
= mClipRect
;
220 for (uint32_t i
= 0, iEnd
= mRoundedClipRects
.Length(); i
< iEnd
; ++i
) {
221 result
.IntersectRect(result
, mRoundedClipRects
[i
].mRect
);
226 bool DisplayItemClip::IsRectAffectedByClip(const nsRect
& aRect
) const {
227 if (mHaveClipRect
&& !mClipRect
.Contains(aRect
)) {
230 for (uint32_t i
= 0, iEnd
= mRoundedClipRects
.Length(); i
< iEnd
; ++i
) {
231 const RoundedRect
& rr
= mRoundedClipRects
[i
];
233 nsLayoutUtils::RoundedRectIntersectRect(rr
.mRect
, rr
.mRadii
, aRect
);
234 if (!rgn
.Contains(aRect
)) {
241 bool DisplayItemClip::IsRectAffectedByClip(const nsIntRect
& aRect
,
242 float aXScale
, float aYScale
,
245 nsIntRect pixelClipRect
=
246 mClipRect
.ScaleToNearestPixels(aXScale
, aYScale
, A2D
);
247 if (!pixelClipRect
.Contains(aRect
)) {
252 // Rounded rect clipping only snaps to user-space pixels, not device space.
253 nsIntRect unscaled
= aRect
;
254 unscaled
.Scale(1 / aXScale
, 1 / aYScale
);
256 for (uint32_t i
= 0, iEnd
= mRoundedClipRects
.Length(); i
< iEnd
; ++i
) {
257 const RoundedRect
& rr
= mRoundedClipRects
[i
];
259 nsIntRect pixelRect
= rr
.mRect
.ToNearestPixels(A2D
);
261 RectCornerRadii pixelRadii
;
262 nsCSSRendering::ComputePixelRadii(rr
.mRadii
, A2D
, &pixelRadii
);
264 nsIntRegion rgn
= nsLayoutUtils::RoundedRectIntersectIntRect(
265 pixelRect
, pixelRadii
, unscaled
);
266 if (!rgn
.Contains(unscaled
)) {
273 nsRect
DisplayItemClip::ApplyNonRoundedIntersection(const nsRect
& aRect
) const {
274 if (!mHaveClipRect
) {
278 nsRect result
= aRect
.Intersect(mClipRect
);
279 for (uint32_t i
= 0, iEnd
= mRoundedClipRects
.Length(); i
< iEnd
; ++i
) {
280 result
= result
.Intersect(mRoundedClipRects
[i
].mRect
);
285 void DisplayItemClip::RemoveRoundedCorners() {
286 if (mRoundedClipRects
.IsEmpty()) return;
288 mClipRect
= NonRoundedIntersection();
289 mRoundedClipRects
.Clear();
292 // Computes the difference between aR1 and aR2, limited to aBounds.
293 static void AccumulateRectDifference(const nsRect
& aR1
, const nsRect
& aR2
,
294 const nsRect
& aBounds
, nsRegion
* aOut
) {
295 if (aR1
.IsEqualInterior(aR2
)) return;
302 static void AccumulateRoundedRectDifference(
303 const DisplayItemClip::RoundedRect
& aR1
,
304 const DisplayItemClip::RoundedRect
& aR2
, const nsRect
& aBounds
,
305 const nsRect
& aOtherBounds
, nsRegion
* aOut
) {
306 const nsRect
& rect1
= aR1
.mRect
;
307 const nsRect
& rect2
= aR2
.mRect
;
309 // If the two rectangles are totally disjoint, just add them both - otherwise
310 // we'd end up adding one big enclosing rect
311 if (!rect1
.Intersects(rect2
) ||
312 memcmp(aR1
.mRadii
, aR2
.mRadii
, sizeof(aR1
.mRadii
))) {
313 aOut
->Or(*aOut
, rect1
.Intersect(aBounds
));
314 aOut
->Or(*aOut
, rect2
.Intersect(aOtherBounds
));
318 nscoord lowestBottom
= std::max(rect1
.YMost(), rect2
.YMost());
319 nscoord highestTop
= std::min(rect1
.Y(), rect2
.Y());
320 nscoord maxRight
= std::max(rect1
.XMost(), rect2
.XMost());
321 nscoord minLeft
= std::min(rect1
.X(), rect2
.X());
323 // At this point, we know that the radii haven't changed, and that the bounds
324 // are different in some way. To explain how this works, consider the case
325 // where the rounded rect has just been translated along the X direction.
326 // | ______________________ _ _ _ _ _ _ |
329 // | | aR1 | | aR2 | |
331 // | \ __________\___________ / _ _ _ _ _ / |
333 // The invalidation region will be as if we lopped off the left rounded part
334 // of aR2, and the right rounded part of aR1, and XOR'd them:
335 // | ______________________ _ _ _ _ _ _ |
336 // | -/-----------/- -\-----------\- |
337 // | |-------------- --|------------ |
338 // | |-----aR1---|-- --|-----aR2---| |
339 // | |-------------- --|------------ |
340 // | -\ __________\-__________-/ _ _ _ _ _ /- |
342 // The logic below just implements this idea, but generalized to both the
343 // X and Y dimensions. The "(...)Adjusted(...)" values represent the lopped
345 nscoord highestAdjustedBottom
= std::min(
346 rect1
.YMost() - aR1
.mRadii
[eCornerBottomLeftY
],
347 std::min(rect1
.YMost() - aR1
.mRadii
[eCornerBottomRightY
],
348 std::min(rect2
.YMost() - aR2
.mRadii
[eCornerBottomLeftY
],
349 rect2
.YMost() - aR2
.mRadii
[eCornerBottomRightY
])));
350 nscoord lowestAdjustedTop
=
351 std::max(rect1
.Y() + aR1
.mRadii
[eCornerTopLeftY
],
352 std::max(rect1
.Y() + aR1
.mRadii
[eCornerTopRightY
],
353 std::max(rect2
.Y() + aR2
.mRadii
[eCornerTopLeftY
],
354 rect2
.Y() + aR2
.mRadii
[eCornerTopRightY
])));
356 nscoord minAdjustedRight
= std::min(
357 rect1
.XMost() - aR1
.mRadii
[eCornerTopRightX
],
358 std::min(rect1
.XMost() - aR1
.mRadii
[eCornerBottomRightX
],
359 std::min(rect2
.XMost() - aR2
.mRadii
[eCornerTopRightX
],
360 rect2
.XMost() - aR2
.mRadii
[eCornerBottomRightX
])));
361 nscoord maxAdjustedLeft
=
362 std::max(rect1
.X() + aR1
.mRadii
[eCornerTopLeftX
],
363 std::max(rect1
.X() + aR1
.mRadii
[eCornerBottomLeftX
],
364 std::max(rect2
.X() + aR2
.mRadii
[eCornerTopLeftX
],
365 rect2
.X() + aR2
.mRadii
[eCornerBottomLeftX
])));
367 // We only want to add an invalidation rect if the bounds have changed. If we
368 // always added all of the 4 rects below, we would always be invalidating a
369 // border around the rects, even in cases where we just translated along the X
372 // First, or with the Y delta rects, wide along the X axis
373 if (rect1
.Y() != rect2
.Y()) {
374 r
.Or(r
, nsRect(minLeft
, highestTop
, maxRight
- minLeft
,
375 lowestAdjustedTop
- highestTop
));
377 if (rect1
.YMost() != rect2
.YMost()) {
378 r
.Or(r
, nsRect(minLeft
, highestAdjustedBottom
, maxRight
- minLeft
,
379 lowestBottom
- highestAdjustedBottom
));
381 // Then, or with the X delta rects, wide along the Y axis
382 if (rect1
.X() != rect2
.X()) {
383 r
.Or(r
, nsRect(minLeft
, highestTop
, maxAdjustedLeft
- minLeft
,
384 lowestBottom
- highestTop
));
386 if (rect1
.XMost() != rect2
.XMost()) {
387 r
.Or(r
, nsRect(minAdjustedRight
, highestTop
, maxRight
- minAdjustedRight
,
388 lowestBottom
- highestTop
));
391 r
.And(r
, aBounds
.Union(aOtherBounds
));
395 void DisplayItemClip::AddOffsetAndComputeDifference(
396 const nsPoint
& aOffset
, const nsRect
& aBounds
,
397 const DisplayItemClip
& aOther
, const nsRect
& aOtherBounds
,
398 nsRegion
* aDifference
) {
399 if (mHaveClipRect
!= aOther
.mHaveClipRect
||
400 mRoundedClipRects
.Length() != aOther
.mRoundedClipRects
.Length()) {
401 aDifference
->Or(*aDifference
, aBounds
);
402 aDifference
->Or(*aDifference
, aOtherBounds
);
406 AccumulateRectDifference(mClipRect
+ aOffset
, aOther
.mClipRect
,
407 aBounds
.Union(aOtherBounds
), aDifference
);
409 for (uint32_t i
= 0; i
< mRoundedClipRects
.Length(); ++i
) {
410 if (mRoundedClipRects
[i
] + aOffset
!= aOther
.mRoundedClipRects
[i
]) {
411 AccumulateRoundedRectDifference(mRoundedClipRects
[i
] + aOffset
,
412 aOther
.mRoundedClipRects
[i
], aBounds
,
413 aOtherBounds
, aDifference
);
418 void DisplayItemClip::AppendRoundedRects(nsTArray
<RoundedRect
>* aArray
) const {
419 aArray
->AppendElements(mRoundedClipRects
.Elements(),
420 mRoundedClipRects
.Length());
423 bool DisplayItemClip::ComputeRegionInClips(const DisplayItemClip
* aOldClip
,
424 const nsPoint
& aShift
,
425 nsRegion
* aCombined
) const {
426 if (!mHaveClipRect
|| (aOldClip
&& !aOldClip
->mHaveClipRect
)) {
431 *aCombined
= aOldClip
->NonRoundedIntersection();
432 aCombined
->MoveBy(aShift
);
433 aCombined
->Or(*aCombined
, NonRoundedIntersection());
435 *aCombined
= NonRoundedIntersection();
440 void DisplayItemClip::MoveBy(const nsPoint
& aPoint
) {
441 if (!mHaveClipRect
) {
445 for (uint32_t i
= 0; i
< mRoundedClipRects
.Length(); ++i
) {
446 mRoundedClipRects
[i
].mRect
+= aPoint
;
450 static StaticAutoPtr
<DisplayItemClip
> gNoClip
;
452 const DisplayItemClip
& DisplayItemClip::NoClip() {
454 gNoClip
= new DisplayItemClip();
459 void DisplayItemClip::Shutdown() { gNoClip
= nullptr; }
461 nsCString
DisplayItemClip::ToString() const {
464 str
.AppendPrintf("%d,%d,%d,%d", mClipRect
.x
, mClipRect
.y
, mClipRect
.width
,
466 for (uint32_t i
= 0; i
< mRoundedClipRects
.Length(); ++i
) {
467 const RoundedRect
& r
= mRoundedClipRects
[i
];
468 str
.AppendPrintf(" [%d,%d,%d,%d corners %d,%d,%d,%d,%d,%d,%d,%d]",
469 r
.mRect
.x
, r
.mRect
.y
, r
.mRect
.width
, r
.mRect
.height
,
470 r
.mRadii
[0], r
.mRadii
[1], r
.mRadii
[2], r
.mRadii
[3],
471 r
.mRadii
[4], r
.mRadii
[5], r
.mRadii
[6], r
.mRadii
[7]);
474 return std::move(str
);
477 void DisplayItemClip::ToComplexClipRegions(
478 int32_t aAppUnitsPerDevPixel
,
479 nsTArray
<wr::ComplexClipRegion
>& aOutArray
) const {
480 for (const auto& clipRect
: mRoundedClipRects
) {
481 aOutArray
.AppendElement(wr::ToComplexClipRegion(
482 clipRect
.mRect
, clipRect
.mRadii
, aAppUnitsPerDevPixel
));
486 } // namespace mozilla