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/webrender/WebRenderTypes.h"
15 #include "nsPresContext.h"
16 #include "nsCSSRendering.h"
17 #include "nsLayoutUtils.h"
20 using namespace mozilla::gfx
;
24 void DisplayItemClip::SetTo(const nsRect
& aRect
) { SetTo(aRect
, nullptr); }
26 void DisplayItemClip::SetTo(const nsRect
& aRect
, const nscoord
* aRadii
) {
30 mRoundedClipRects
.SetLength(1);
31 mRoundedClipRects
[0].mRect
= aRect
;
32 memcpy(mRoundedClipRects
[0].mRadii
, aRadii
, sizeof(nscoord
) * 8);
34 mRoundedClipRects
.Clear();
38 void DisplayItemClip::SetTo(const nsRect
& aRect
, const nsRect
& aRoundedRect
,
39 const nscoord
* aRadii
) {
42 mRoundedClipRects
.SetLength(1);
43 mRoundedClipRects
[0].mRect
= aRoundedRect
;
44 memcpy(mRoundedClipRects
[0].mRadii
, aRadii
, sizeof(nscoord
) * 8);
47 bool DisplayItemClip::MayIntersect(const nsRect
& aRect
) const {
49 return !aRect
.IsEmpty();
51 nsRect r
= aRect
.Intersect(mClipRect
);
55 for (uint32_t i
= 0; i
< mRoundedClipRects
.Length(); ++i
) {
56 const RoundedRect
& rr
= mRoundedClipRects
[i
];
57 if (!nsLayoutUtils::RoundedRectIntersectsRect(rr
.mRect
, rr
.mRadii
, r
)) {
64 void DisplayItemClip::IntersectWith(const DisplayItemClip
& aOther
) {
65 if (!aOther
.mHaveClipRect
) {
72 if (!mClipRect
.IntersectRect(mClipRect
, aOther
.mClipRect
)) {
73 mRoundedClipRects
.Clear();
76 mRoundedClipRects
.AppendElements(aOther
.mRoundedClipRects
);
79 void DisplayItemClip::ApplyTo(gfxContext
* aContext
, int32_t A2D
) const {
80 ApplyRectTo(aContext
, A2D
);
81 ApplyRoundedRectClipsTo(aContext
, A2D
, 0, mRoundedClipRects
.Length());
84 void DisplayItemClip::ApplyRectTo(gfxContext
* aContext
, int32_t A2D
) const {
86 gfxRect clip
= nsLayoutUtils::RectToGfxRect(mClipRect
, A2D
);
87 aContext
->SnappedRectangle(clip
);
91 void DisplayItemClip::ApplyRoundedRectClipsTo(gfxContext
* aContext
, int32_t A2D
,
93 uint32_t aEnd
) const {
94 DrawTarget
& aDrawTarget
= *aContext
->GetDrawTarget();
96 aEnd
= std::min
<uint32_t>(aEnd
, mRoundedClipRects
.Length());
98 for (uint32_t i
= aBegin
; i
< aEnd
; ++i
) {
99 RefPtr
<Path
> roundedRect
=
100 MakeRoundedRectPath(aDrawTarget
, A2D
, mRoundedClipRects
[i
]);
101 aContext
->Clip(roundedRect
);
105 void DisplayItemClip::FillIntersectionOfRoundedRectClips(
106 gfxContext
* aContext
, const DeviceColor
& aColor
,
107 int32_t aAppUnitsPerDevPixel
) const {
108 DrawTarget
& aDrawTarget
= *aContext
->GetDrawTarget();
110 uint32_t end
= mRoundedClipRects
.Length();
115 // Push clips for any rects that come BEFORE the rect at |aEnd - 1|, if any:
116 ApplyRoundedRectClipsTo(aContext
, aAppUnitsPerDevPixel
, 0, end
- 1);
118 // Now fill the rect at |aEnd - 1|:
119 RefPtr
<Path
> roundedRect
= MakeRoundedRectPath(
120 aDrawTarget
, aAppUnitsPerDevPixel
, mRoundedClipRects
[end
- 1]);
121 aDrawTarget
.Fill(roundedRect
, ColorPattern(aColor
));
123 // Finally, pop any clips that we may have pushed:
124 for (uint32_t i
= 0; i
< end
- 1; ++i
) {
129 already_AddRefed
<Path
> DisplayItemClip::MakeRoundedRectPath(
130 DrawTarget
& aDrawTarget
, int32_t A2D
, const RoundedRect
& aRoundRect
) const {
131 RectCornerRadii pixelRadii
;
132 nsCSSRendering::ComputePixelRadii(aRoundRect
.mRadii
, A2D
, &pixelRadii
);
134 Rect rect
= NSRectToSnappedRect(aRoundRect
.mRect
, A2D
, aDrawTarget
);
136 return MakePathForRoundedRect(aDrawTarget
, rect
, pixelRadii
);
139 nsRect
DisplayItemClip::ApproximateIntersectInward(const nsRect
& aRect
) const {
142 r
.IntersectRect(r
, mClipRect
);
144 for (uint32_t i
= 0, iEnd
= mRoundedClipRects
.Length(); i
< iEnd
; ++i
) {
145 const RoundedRect
& rr
= mRoundedClipRects
[i
];
147 nsLayoutUtils::RoundedRectIntersectRect(rr
.mRect
, rr
.mRadii
, r
);
148 r
= rgn
.GetLargestRectangle();
153 // Test if (aXPoint, aYPoint) is in the ellipse with center (aXCenter, aYCenter)
154 // and radii aXRadius, aYRadius.
155 static bool IsInsideEllipse(nscoord aXRadius
, nscoord aXCenter
, nscoord aXPoint
,
156 nscoord aYRadius
, nscoord aYCenter
,
158 float scaledX
= float(aXPoint
- aXCenter
) / float(aXRadius
);
159 float scaledY
= float(aYPoint
- aYCenter
) / float(aYRadius
);
160 return scaledX
* scaledX
+ scaledY
* scaledY
< 1.0f
;
163 bool DisplayItemClip::IsRectClippedByRoundedCorner(const nsRect
& aRect
) const {
164 if (mRoundedClipRects
.IsEmpty()) return false;
167 rect
.IntersectRect(aRect
, NonRoundedIntersection());
168 for (uint32_t i
= 0, iEnd
= mRoundedClipRects
.Length(); i
< iEnd
; ++i
) {
169 const RoundedRect
& rr
= mRoundedClipRects
[i
];
171 if (rect
.x
< rr
.mRect
.x
+ rr
.mRadii
[eCornerTopLeftX
] &&
172 rect
.y
< rr
.mRect
.y
+ rr
.mRadii
[eCornerTopLeftY
]) {
173 if (!IsInsideEllipse(rr
.mRadii
[eCornerTopLeftX
],
174 rr
.mRect
.x
+ rr
.mRadii
[eCornerTopLeftX
], rect
.x
,
175 rr
.mRadii
[eCornerTopLeftY
],
176 rr
.mRect
.y
+ rr
.mRadii
[eCornerTopLeftY
], rect
.y
)) {
181 if (rect
.XMost() > rr
.mRect
.XMost() - rr
.mRadii
[eCornerTopRightX
] &&
182 rect
.y
< rr
.mRect
.y
+ rr
.mRadii
[eCornerTopRightY
]) {
183 if (!IsInsideEllipse(rr
.mRadii
[eCornerTopRightX
],
184 rr
.mRect
.XMost() - rr
.mRadii
[eCornerTopRightX
],
185 rect
.XMost(), rr
.mRadii
[eCornerTopRightY
],
186 rr
.mRect
.y
+ rr
.mRadii
[eCornerTopRightY
], rect
.y
)) {
191 if (rect
.x
< rr
.mRect
.x
+ rr
.mRadii
[eCornerBottomLeftX
] &&
192 rect
.YMost() > rr
.mRect
.YMost() - rr
.mRadii
[eCornerBottomLeftY
]) {
193 if (!IsInsideEllipse(rr
.mRadii
[eCornerBottomLeftX
],
194 rr
.mRect
.x
+ rr
.mRadii
[eCornerBottomLeftX
], rect
.x
,
195 rr
.mRadii
[eCornerBottomLeftY
],
196 rr
.mRect
.YMost() - rr
.mRadii
[eCornerBottomLeftY
],
202 if (rect
.XMost() > rr
.mRect
.XMost() - rr
.mRadii
[eCornerBottomRightX
] &&
203 rect
.YMost() > rr
.mRect
.YMost() - rr
.mRadii
[eCornerBottomRightY
]) {
204 if (!IsInsideEllipse(rr
.mRadii
[eCornerBottomRightX
],
205 rr
.mRect
.XMost() - rr
.mRadii
[eCornerBottomRightX
],
206 rect
.XMost(), rr
.mRadii
[eCornerBottomRightY
],
207 rr
.mRect
.YMost() - rr
.mRadii
[eCornerBottomRightY
],
216 nsRect
DisplayItemClip::NonRoundedIntersection() const {
217 NS_ASSERTION(mHaveClipRect
, "Must have a clip rect!");
218 nsRect result
= mClipRect
;
219 for (uint32_t i
= 0, iEnd
= mRoundedClipRects
.Length(); i
< iEnd
; ++i
) {
220 result
.IntersectRect(result
, mRoundedClipRects
[i
].mRect
);
225 bool DisplayItemClip::IsRectAffectedByClip(const nsRect
& aRect
) const {
226 if (mHaveClipRect
&& !mClipRect
.Contains(aRect
)) {
229 for (uint32_t i
= 0, iEnd
= mRoundedClipRects
.Length(); i
< iEnd
; ++i
) {
230 const RoundedRect
& rr
= mRoundedClipRects
[i
];
232 nsLayoutUtils::RoundedRectIntersectRect(rr
.mRect
, rr
.mRadii
, aRect
);
233 if (!rgn
.Contains(aRect
)) {
240 bool DisplayItemClip::IsRectAffectedByClip(const nsIntRect
& aRect
,
241 float aXScale
, float aYScale
,
244 nsIntRect pixelClipRect
=
245 mClipRect
.ScaleToNearestPixels(aXScale
, aYScale
, A2D
);
246 if (!pixelClipRect
.Contains(aRect
)) {
251 // Rounded rect clipping only snaps to user-space pixels, not device space.
252 nsIntRect unscaled
= aRect
;
253 unscaled
.Scale(1 / aXScale
, 1 / aYScale
);
255 for (uint32_t i
= 0, iEnd
= mRoundedClipRects
.Length(); i
< iEnd
; ++i
) {
256 const RoundedRect
& rr
= mRoundedClipRects
[i
];
258 nsIntRect pixelRect
= rr
.mRect
.ToNearestPixels(A2D
);
260 RectCornerRadii pixelRadii
;
261 nsCSSRendering::ComputePixelRadii(rr
.mRadii
, A2D
, &pixelRadii
);
263 nsIntRegion rgn
= nsLayoutUtils::RoundedRectIntersectIntRect(
264 pixelRect
, pixelRadii
, unscaled
);
265 if (!rgn
.Contains(unscaled
)) {
272 nsRect
DisplayItemClip::ApplyNonRoundedIntersection(const nsRect
& aRect
) const {
273 if (!mHaveClipRect
) {
277 nsRect result
= aRect
.Intersect(mClipRect
);
278 for (uint32_t i
= 0, iEnd
= mRoundedClipRects
.Length(); i
< iEnd
; ++i
) {
279 result
= result
.Intersect(mRoundedClipRects
[i
].mRect
);
284 void DisplayItemClip::RemoveRoundedCorners() {
285 if (mRoundedClipRects
.IsEmpty()) return;
287 mClipRect
= NonRoundedIntersection();
288 mRoundedClipRects
.Clear();
291 // Computes the difference between aR1 and aR2, limited to aBounds.
292 static void AccumulateRectDifference(const nsRect
& aR1
, const nsRect
& aR2
,
293 const nsRect
& aBounds
, nsRegion
* aOut
) {
294 if (aR1
.IsEqualInterior(aR2
)) return;
301 static void AccumulateRoundedRectDifference(
302 const DisplayItemClip::RoundedRect
& aR1
,
303 const DisplayItemClip::RoundedRect
& aR2
, const nsRect
& aBounds
,
304 const nsRect
& aOtherBounds
, nsRegion
* aOut
) {
305 const nsRect
& rect1
= aR1
.mRect
;
306 const nsRect
& rect2
= aR2
.mRect
;
308 // If the two rectangles are totally disjoint, just add them both - otherwise
309 // we'd end up adding one big enclosing rect
310 if (!rect1
.Intersects(rect2
) ||
311 memcmp(aR1
.mRadii
, aR2
.mRadii
, sizeof(aR1
.mRadii
))) {
312 aOut
->Or(*aOut
, rect1
.Intersect(aBounds
));
313 aOut
->Or(*aOut
, rect2
.Intersect(aOtherBounds
));
317 nscoord lowestBottom
= std::max(rect1
.YMost(), rect2
.YMost());
318 nscoord highestTop
= std::min(rect1
.Y(), rect2
.Y());
319 nscoord maxRight
= std::max(rect1
.XMost(), rect2
.XMost());
320 nscoord minLeft
= std::min(rect1
.X(), rect2
.X());
322 // At this point, we know that the radii haven't changed, and that the bounds
323 // are different in some way. To explain how this works, consider the case
324 // where the rounded rect has just been translated along the X direction.
325 // | ______________________ _ _ _ _ _ _ |
328 // | | aR1 | | aR2 | |
330 // | \ __________\___________ / _ _ _ _ _ / |
332 // The invalidation region will be as if we lopped off the left rounded part
333 // of aR2, and the right rounded part of aR1, and XOR'd them:
334 // | ______________________ _ _ _ _ _ _ |
335 // | -/-----------/- -\-----------\- |
336 // | |-------------- --|------------ |
337 // | |-----aR1---|-- --|-----aR2---| |
338 // | |-------------- --|------------ |
339 // | -\ __________\-__________-/ _ _ _ _ _ /- |
341 // The logic below just implements this idea, but generalized to both the
342 // X and Y dimensions. The "(...)Adjusted(...)" values represent the lopped
344 nscoord highestAdjustedBottom
= std::min(
345 rect1
.YMost() - aR1
.mRadii
[eCornerBottomLeftY
],
346 std::min(rect1
.YMost() - aR1
.mRadii
[eCornerBottomRightY
],
347 std::min(rect2
.YMost() - aR2
.mRadii
[eCornerBottomLeftY
],
348 rect2
.YMost() - aR2
.mRadii
[eCornerBottomRightY
])));
349 nscoord lowestAdjustedTop
=
350 std::max(rect1
.Y() + aR1
.mRadii
[eCornerTopLeftY
],
351 std::max(rect1
.Y() + aR1
.mRadii
[eCornerTopRightY
],
352 std::max(rect2
.Y() + aR2
.mRadii
[eCornerTopLeftY
],
353 rect2
.Y() + aR2
.mRadii
[eCornerTopRightY
])));
355 nscoord minAdjustedRight
= std::min(
356 rect1
.XMost() - aR1
.mRadii
[eCornerTopRightX
],
357 std::min(rect1
.XMost() - aR1
.mRadii
[eCornerBottomRightX
],
358 std::min(rect2
.XMost() - aR2
.mRadii
[eCornerTopRightX
],
359 rect2
.XMost() - aR2
.mRadii
[eCornerBottomRightX
])));
360 nscoord maxAdjustedLeft
=
361 std::max(rect1
.X() + aR1
.mRadii
[eCornerTopLeftX
],
362 std::max(rect1
.X() + aR1
.mRadii
[eCornerBottomLeftX
],
363 std::max(rect2
.X() + aR2
.mRadii
[eCornerTopLeftX
],
364 rect2
.X() + aR2
.mRadii
[eCornerBottomLeftX
])));
366 // We only want to add an invalidation rect if the bounds have changed. If we
367 // always added all of the 4 rects below, we would always be invalidating a
368 // border around the rects, even in cases where we just translated along the X
371 // First, or with the Y delta rects, wide along the X axis
372 if (rect1
.Y() != rect2
.Y()) {
373 r
.Or(r
, nsRect(minLeft
, highestTop
, maxRight
- minLeft
,
374 lowestAdjustedTop
- highestTop
));
376 if (rect1
.YMost() != rect2
.YMost()) {
377 r
.Or(r
, nsRect(minLeft
, highestAdjustedBottom
, maxRight
- minLeft
,
378 lowestBottom
- highestAdjustedBottom
));
380 // Then, or with the X delta rects, wide along the Y axis
381 if (rect1
.X() != rect2
.X()) {
382 r
.Or(r
, nsRect(minLeft
, highestTop
, maxAdjustedLeft
- minLeft
,
383 lowestBottom
- highestTop
));
385 if (rect1
.XMost() != rect2
.XMost()) {
386 r
.Or(r
, nsRect(minAdjustedRight
, highestTop
, maxRight
- minAdjustedRight
,
387 lowestBottom
- highestTop
));
390 r
.And(r
, aBounds
.Union(aOtherBounds
));
394 void DisplayItemClip::AddOffsetAndComputeDifference(
395 const nsPoint
& aOffset
, const nsRect
& aBounds
,
396 const DisplayItemClip
& aOther
, const nsRect
& aOtherBounds
,
397 nsRegion
* aDifference
) {
398 if (mHaveClipRect
!= aOther
.mHaveClipRect
||
399 mRoundedClipRects
.Length() != aOther
.mRoundedClipRects
.Length()) {
400 aDifference
->Or(*aDifference
, aBounds
);
401 aDifference
->Or(*aDifference
, aOtherBounds
);
405 AccumulateRectDifference(mClipRect
+ aOffset
, aOther
.mClipRect
,
406 aBounds
.Union(aOtherBounds
), aDifference
);
408 for (uint32_t i
= 0; i
< mRoundedClipRects
.Length(); ++i
) {
409 if (mRoundedClipRects
[i
] + aOffset
!= aOther
.mRoundedClipRects
[i
]) {
410 AccumulateRoundedRectDifference(mRoundedClipRects
[i
] + aOffset
,
411 aOther
.mRoundedClipRects
[i
], aBounds
,
412 aOtherBounds
, aDifference
);
417 void DisplayItemClip::AppendRoundedRects(nsTArray
<RoundedRect
>* aArray
) const {
418 aArray
->AppendElements(mRoundedClipRects
.Elements(),
419 mRoundedClipRects
.Length());
422 bool DisplayItemClip::ComputeRegionInClips(const DisplayItemClip
* aOldClip
,
423 const nsPoint
& aShift
,
424 nsRegion
* aCombined
) const {
425 if (!mHaveClipRect
|| (aOldClip
&& !aOldClip
->mHaveClipRect
)) {
430 *aCombined
= aOldClip
->NonRoundedIntersection();
431 aCombined
->MoveBy(aShift
);
432 aCombined
->Or(*aCombined
, NonRoundedIntersection());
434 *aCombined
= NonRoundedIntersection();
439 void DisplayItemClip::MoveBy(const nsPoint
& aPoint
) {
440 if (!mHaveClipRect
) {
444 for (uint32_t i
= 0; i
< mRoundedClipRects
.Length(); ++i
) {
445 mRoundedClipRects
[i
].mRect
+= aPoint
;
449 static DisplayItemClip
* gNoClip
;
451 const DisplayItemClip
& DisplayItemClip::NoClip() {
453 gNoClip
= new DisplayItemClip();
458 void DisplayItemClip::Shutdown() {
463 nsCString
DisplayItemClip::ToString() const {
466 str
.AppendPrintf("%d,%d,%d,%d", mClipRect
.x
, mClipRect
.y
, mClipRect
.width
,
468 for (uint32_t i
= 0; i
< mRoundedClipRects
.Length(); ++i
) {
469 const RoundedRect
& r
= mRoundedClipRects
[i
];
470 str
.AppendPrintf(" [%d,%d,%d,%d corners %d,%d,%d,%d,%d,%d,%d,%d]",
471 r
.mRect
.x
, r
.mRect
.y
, r
.mRect
.width
, r
.mRect
.height
,
472 r
.mRadii
[0], r
.mRadii
[1], r
.mRadii
[2], r
.mRadii
[3],
473 r
.mRadii
[4], r
.mRadii
[5], r
.mRadii
[6], r
.mRadii
[7]);
476 return std::move(str
);
479 void DisplayItemClip::ToComplexClipRegions(
480 int32_t aAppUnitsPerDevPixel
,
481 nsTArray
<wr::ComplexClipRegion
>& aOutArray
) const {
482 for (const auto& clipRect
: mRoundedClipRects
) {
483 aOutArray
.AppendElement(wr::ToComplexClipRegion(
484 clipRect
.mRect
, clipRect
.mRadii
, aAppUnitsPerDevPixel
));
488 } // namespace mozilla