Line data Source code
1 : /* -*- Mode: C++; tab-width: 20; 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 : #include "DisplayItemClip.h"
7 :
8 : #include "gfxContext.h"
9 : #include "gfxUtils.h"
10 : #include "mozilla/gfx/2D.h"
11 : #include "mozilla/gfx/PathHelpers.h"
12 : #include "nsPresContext.h"
13 : #include "nsCSSRendering.h"
14 : #include "nsLayoutUtils.h"
15 : #include "nsRegion.h"
16 :
17 : using namespace mozilla::gfx;
18 :
19 : namespace mozilla {
20 :
21 : void
22 829 : DisplayItemClip::SetTo(const nsRect& aRect)
23 : {
24 829 : SetTo(aRect, nullptr);
25 829 : }
26 :
27 : void
28 949 : DisplayItemClip::SetTo(const nsRect& aRect, const nscoord* aRadii)
29 : {
30 949 : mHaveClipRect = true;
31 949 : mClipRect = aRect;
32 949 : if (aRadii) {
33 120 : mRoundedClipRects.SetLength(1);
34 120 : mRoundedClipRects[0].mRect = aRect;
35 120 : memcpy(mRoundedClipRects[0].mRadii, aRadii, sizeof(nscoord)*8);
36 : } else {
37 829 : mRoundedClipRects.Clear();
38 : }
39 949 : }
40 :
41 : void
42 0 : DisplayItemClip::SetTo(const nsRect& aRect,
43 : const nsRect& aRoundedRect,
44 : const nscoord* aRadii)
45 : {
46 0 : mHaveClipRect = true;
47 0 : mClipRect = aRect;
48 0 : mRoundedClipRects.SetLength(1);
49 0 : mRoundedClipRects[0].mRect = aRoundedRect;
50 0 : memcpy(mRoundedClipRects[0].mRadii, aRadii, sizeof(nscoord)*8);
51 0 : }
52 :
53 : bool
54 171 : DisplayItemClip::MayIntersect(const nsRect& aRect) const
55 : {
56 171 : if (!mHaveClipRect) {
57 36 : return !aRect.IsEmpty();
58 : }
59 270 : nsRect r = aRect.Intersect(mClipRect);
60 135 : if (r.IsEmpty()) {
61 0 : return false;
62 : }
63 135 : for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
64 0 : const RoundedRect& rr = mRoundedClipRects[i];
65 0 : if (!nsLayoutUtils::RoundedRectIntersectsRect(rr.mRect, rr.mRadii, r)) {
66 0 : return false;
67 : }
68 : }
69 135 : return true;
70 : }
71 :
72 : void
73 1073 : DisplayItemClip::IntersectWith(const DisplayItemClip& aOther)
74 : {
75 1073 : if (!aOther.mHaveClipRect) {
76 0 : return;
77 : }
78 1073 : if (!mHaveClipRect) {
79 268 : *this = aOther;
80 268 : return;
81 : }
82 805 : if (!mClipRect.IntersectRect(mClipRect, aOther.mClipRect)) {
83 0 : mRoundedClipRects.Clear();
84 0 : return;
85 : }
86 805 : mRoundedClipRects.AppendElements(aOther.mRoundedClipRects);
87 : }
88 :
89 : void
90 201 : DisplayItemClip::ApplyTo(gfxContext* aContext,
91 : nsPresContext* aPresContext,
92 : uint32_t aBegin, uint32_t aEnd)
93 : {
94 201 : int32_t A2D = aPresContext->AppUnitsPerDevPixel();
95 201 : ApplyRectTo(aContext, A2D);
96 201 : ApplyRoundedRectClipsTo(aContext, A2D, aBegin, aEnd);
97 201 : }
98 :
99 : void
100 201 : DisplayItemClip::ApplyRectTo(gfxContext* aContext, int32_t A2D) const
101 : {
102 201 : aContext->NewPath();
103 201 : gfxRect clip = nsLayoutUtils::RectToGfxRect(mClipRect, A2D);
104 201 : aContext->Rectangle(clip, true);
105 201 : aContext->Clip();
106 201 : }
107 :
108 : void
109 201 : DisplayItemClip::ApplyRoundedRectClipsTo(gfxContext* aContext,
110 : int32_t A2D,
111 : uint32_t aBegin, uint32_t aEnd) const
112 : {
113 201 : DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
114 :
115 201 : aEnd = std::min<uint32_t>(aEnd, mRoundedClipRects.Length());
116 :
117 222 : for (uint32_t i = aBegin; i < aEnd; ++i) {
118 : RefPtr<Path> roundedRect =
119 42 : MakeRoundedRectPath(aDrawTarget, A2D, mRoundedClipRects[i]);
120 21 : aContext->Clip(roundedRect);
121 : }
122 201 : }
123 :
124 : void
125 0 : DisplayItemClip::FillIntersectionOfRoundedRectClips(gfxContext* aContext,
126 : const Color& aColor,
127 : int32_t aAppUnitsPerDevPixel,
128 : uint32_t aBegin,
129 : uint32_t aEnd) const
130 : {
131 0 : DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
132 :
133 0 : aEnd = std::min<uint32_t>(aEnd, mRoundedClipRects.Length());
134 :
135 0 : if (aBegin >= aEnd) {
136 0 : return;
137 : }
138 :
139 : // Push clips for any rects that come BEFORE the rect at |aEnd - 1|, if any:
140 0 : ApplyRoundedRectClipsTo(aContext, aAppUnitsPerDevPixel, aBegin, aEnd - 1);
141 :
142 : // Now fill the rect at |aEnd - 1|:
143 0 : RefPtr<Path> roundedRect = MakeRoundedRectPath(aDrawTarget,
144 : aAppUnitsPerDevPixel,
145 0 : mRoundedClipRects[aEnd - 1]);
146 0 : ColorPattern color(ToDeviceColor(aColor));
147 0 : aDrawTarget.Fill(roundedRect, color);
148 :
149 : // Finally, pop any clips that we may have pushed:
150 0 : for (uint32_t i = aBegin; i < aEnd - 1; ++i) {
151 0 : aContext->PopClip();
152 : }
153 : }
154 :
155 : already_AddRefed<Path>
156 21 : DisplayItemClip::MakeRoundedRectPath(DrawTarget& aDrawTarget,
157 : int32_t A2D,
158 : const RoundedRect &aRoundRect) const
159 : {
160 21 : RectCornerRadii pixelRadii;
161 21 : nsCSSRendering::ComputePixelRadii(aRoundRect.mRadii, A2D, &pixelRadii);
162 :
163 21 : Rect rect = NSRectToSnappedRect(aRoundRect.mRect, A2D, aDrawTarget);
164 :
165 21 : return MakePathForRoundedRect(aDrawTarget, rect, pixelRadii);
166 : }
167 :
168 : nsRect
169 235 : DisplayItemClip::ApproximateIntersectInward(const nsRect& aRect) const
170 : {
171 235 : nsRect r = aRect;
172 235 : if (mHaveClipRect) {
173 174 : r.IntersectRect(r, mClipRect);
174 : }
175 294 : for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
176 294 : i < iEnd; ++i) {
177 59 : const RoundedRect &rr = mRoundedClipRects[i];
178 118 : nsRegion rgn = nsLayoutUtils::RoundedRectIntersectRect(rr.mRect, rr.mRadii, r);
179 59 : r = rgn.GetLargestRectangle();
180 : }
181 235 : return r;
182 : }
183 :
184 : // Test if (aXPoint, aYPoint) is in the ellipse with center (aXCenter, aYCenter)
185 : // and radii aXRadius, aYRadius.
186 21 : bool IsInsideEllipse(nscoord aXRadius, nscoord aXCenter, nscoord aXPoint,
187 : nscoord aYRadius, nscoord aYCenter, nscoord aYPoint)
188 : {
189 21 : float scaledX = float(aXPoint - aXCenter) / float(aXRadius);
190 21 : float scaledY = float(aYPoint - aYCenter) / float(aYRadius);
191 21 : return scaledX * scaledX + scaledY * scaledY < 1.0f;
192 : }
193 :
194 : bool
195 29 : DisplayItemClip::IsRectClippedByRoundedCorner(const nsRect& aRect) const
196 : {
197 29 : if (mRoundedClipRects.IsEmpty())
198 0 : return false;
199 :
200 58 : nsRect rect;
201 29 : rect.IntersectRect(aRect, NonRoundedIntersection());
202 37 : for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
203 37 : i < iEnd; ++i) {
204 29 : const RoundedRect &rr = mRoundedClipRects[i];
205 : // top left
206 46 : if (rect.x < rr.mRect.x + rr.mRadii[eCornerTopLeftX] &&
207 17 : rect.y < rr.mRect.y + rr.mRadii[eCornerTopLeftY]) {
208 68 : if (!IsInsideEllipse(rr.mRadii[eCornerTopLeftX],
209 17 : rr.mRect.x + rr.mRadii[eCornerTopLeftX],
210 : rect.x,
211 17 : rr.mRadii[eCornerTopLeftY],
212 17 : rr.mRect.y + rr.mRadii[eCornerTopLeftY],
213 : rect.y)) {
214 17 : return true;
215 : }
216 : }
217 : // top right
218 16 : if (rect.XMost() > rr.mRect.XMost() - rr.mRadii[eCornerTopRightX] &&
219 4 : rect.y < rr.mRect.y + rr.mRadii[eCornerTopRightY]) {
220 16 : if (!IsInsideEllipse(rr.mRadii[eCornerTopRightX],
221 4 : rr.mRect.XMost() - rr.mRadii[eCornerTopRightX],
222 : rect.XMost(),
223 4 : rr.mRadii[eCornerTopRightY],
224 4 : rr.mRect.y + rr.mRadii[eCornerTopRightY],
225 : rect.y)) {
226 4 : return true;
227 : }
228 : }
229 : // bottom left
230 8 : if (rect.x < rr.mRect.x + rr.mRadii[eCornerBottomLeftX] &&
231 0 : rect.YMost() > rr.mRect.YMost() - rr.mRadii[eCornerBottomLeftY]) {
232 0 : if (!IsInsideEllipse(rr.mRadii[eCornerBottomLeftX],
233 0 : rr.mRect.x + rr.mRadii[eCornerBottomLeftX],
234 : rect.x,
235 0 : rr.mRadii[eCornerBottomLeftY],
236 0 : rr.mRect.YMost() - rr.mRadii[eCornerBottomLeftY],
237 : rect.YMost())) {
238 0 : return true;
239 : }
240 : }
241 : // bottom right
242 8 : if (rect.XMost() > rr.mRect.XMost() - rr.mRadii[eCornerBottomRightX] &&
243 0 : rect.YMost() > rr.mRect.YMost() - rr.mRadii[eCornerBottomRightY]) {
244 0 : if (!IsInsideEllipse(rr.mRadii[eCornerBottomRightX],
245 0 : rr.mRect.XMost() - rr.mRadii[eCornerBottomRightX],
246 : rect.XMost(),
247 0 : rr.mRadii[eCornerBottomRightY],
248 0 : rr.mRect.YMost() - rr.mRadii[eCornerBottomRightY],
249 : rect.YMost())) {
250 0 : return true;
251 : }
252 : }
253 : }
254 8 : return false;
255 : }
256 :
257 : nsRect
258 2230 : DisplayItemClip::NonRoundedIntersection() const
259 : {
260 2230 : NS_ASSERTION(mHaveClipRect, "Must have a clip rect!");
261 2230 : nsRect result = mClipRect;
262 2520 : for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
263 2520 : i < iEnd; ++i) {
264 290 : result.IntersectRect(result, mRoundedClipRects[i].mRect);
265 : }
266 2230 : return result;
267 : }
268 :
269 : bool
270 773 : DisplayItemClip::IsRectAffectedByClip(const nsRect& aRect) const
271 : {
272 773 : if (mHaveClipRect && !mClipRect.Contains(aRect)) {
273 350 : return true;
274 : }
275 425 : for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
276 425 : i < iEnd; ++i) {
277 5 : const RoundedRect &rr = mRoundedClipRects[i];
278 7 : nsRegion rgn = nsLayoutUtils::RoundedRectIntersectRect(rr.mRect, rr.mRadii, aRect);
279 5 : if (!rgn.Contains(aRect)) {
280 3 : return true;
281 : }
282 : }
283 420 : return false;
284 : }
285 :
286 : bool
287 26 : DisplayItemClip::IsRectAffectedByClip(const nsIntRect& aRect,
288 : float aXScale,
289 : float aYScale,
290 : int32_t A2D) const
291 : {
292 26 : if (mHaveClipRect) {
293 24 : nsIntRect pixelClipRect = mClipRect.ScaleToNearestPixels(aXScale, aYScale, A2D);
294 24 : if (!pixelClipRect.Contains(aRect)) {
295 0 : return true;
296 : }
297 : }
298 :
299 : // Rounded rect clipping only snaps to user-space pixels, not device space.
300 26 : nsIntRect unscaled = aRect;
301 26 : unscaled.Scale(1/aXScale, 1/aYScale);
302 :
303 26 : for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
304 26 : i < iEnd; ++i) {
305 0 : const RoundedRect &rr = mRoundedClipRects[i];
306 :
307 0 : nsIntRect pixelRect = rr.mRect.ToNearestPixels(A2D);
308 :
309 0 : RectCornerRadii pixelRadii;
310 0 : nsCSSRendering::ComputePixelRadii(rr.mRadii, A2D, &pixelRadii);
311 :
312 0 : nsIntRegion rgn = nsLayoutUtils::RoundedRectIntersectIntRect(pixelRect, pixelRadii, unscaled);
313 0 : if (!rgn.Contains(unscaled)) {
314 0 : return true;
315 : }
316 : }
317 26 : return false;
318 : }
319 :
320 : nsRect
321 6752 : DisplayItemClip::ApplyNonRoundedIntersection(const nsRect& aRect) const
322 : {
323 6752 : if (!mHaveClipRect) {
324 1158 : return aRect;
325 : }
326 :
327 11188 : nsRect result = aRect.Intersect(mClipRect);
328 5934 : for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
329 5934 : i < iEnd; ++i) {
330 340 : result = result.Intersect(mRoundedClipRects[i].mRect);
331 : }
332 5594 : return result;
333 : }
334 :
335 : void
336 8 : DisplayItemClip::RemoveRoundedCorners()
337 : {
338 8 : if (mRoundedClipRects.IsEmpty())
339 0 : return;
340 :
341 8 : mClipRect = NonRoundedIntersection();
342 8 : mRoundedClipRects.Clear();
343 : }
344 :
345 : // Computes the difference between aR1 and aR2, limited to aBounds.
346 : static void
347 920 : AccumulateRectDifference(const nsRect& aR1, const nsRect& aR2, const nsRect& aBounds, nsRegion* aOut)
348 : {
349 920 : if (aR1.IsEqualInterior(aR2))
350 889 : return;
351 62 : nsRegion r;
352 31 : r.Xor(aR1, aR2);
353 31 : r.And(r, aBounds);
354 31 : aOut->Or(*aOut, r);
355 : }
356 :
357 : void
358 1109 : DisplayItemClip::AddOffsetAndComputeDifference(uint32_t aStart,
359 : const nsPoint& aOffset,
360 : const nsRect& aBounds,
361 : const DisplayItemClip& aOther,
362 : uint32_t aOtherStart,
363 : const nsRect& aOtherBounds,
364 : nsRegion* aDifference)
365 : {
366 3327 : if (mHaveClipRect != aOther.mHaveClipRect ||
367 2218 : aStart != aOtherStart ||
368 1109 : mRoundedClipRects.Length() != aOther.mRoundedClipRects.Length()) {
369 0 : aDifference->Or(*aDifference, aBounds);
370 0 : aDifference->Or(*aDifference, aOtherBounds);
371 0 : return;
372 : }
373 1109 : if (mHaveClipRect) {
374 1840 : AccumulateRectDifference(mClipRect + aOffset, aOther.mClipRect,
375 1840 : aBounds.Union(aOtherBounds),
376 920 : aDifference);
377 : }
378 1218 : for (uint32_t i = aStart; i < mRoundedClipRects.Length(); ++i) {
379 109 : if (mRoundedClipRects[i] + aOffset != aOther.mRoundedClipRects[i]) {
380 : // The corners make it tricky so we'll just add both rects here.
381 6 : aDifference->Or(*aDifference, mRoundedClipRects[i].mRect.Intersect(aBounds));
382 6 : aDifference->Or(*aDifference, aOther.mRoundedClipRects[i].mRect.Intersect(aOtherBounds));
383 : }
384 : }
385 : }
386 :
387 : uint32_t
388 1067 : DisplayItemClip::GetCommonRoundedRectCount(const DisplayItemClip& aOther,
389 : uint32_t aMax) const
390 : {
391 3201 : uint32_t end = std::min(std::min(mRoundedClipRects.Length(), size_t(aMax)),
392 4268 : aOther.mRoundedClipRects.Length());
393 1067 : uint32_t clipCount = 0;
394 1067 : for (; clipCount < end; ++clipCount) {
395 0 : if (mRoundedClipRects[clipCount] !=
396 0 : aOther.mRoundedClipRects[clipCount]) {
397 0 : return clipCount;
398 : }
399 : }
400 1067 : return clipCount;
401 : }
402 :
403 : void
404 0 : DisplayItemClip::AppendRoundedRects(nsTArray<RoundedRect>* aArray, uint32_t aCount) const
405 : {
406 0 : size_t count = std::min(mRoundedClipRects.Length(), size_t(aCount));
407 0 : aArray->AppendElements(mRoundedClipRects.Elements(), count);
408 0 : }
409 :
410 : bool
411 1252 : DisplayItemClip::ComputeRegionInClips(DisplayItemClip* aOldClip,
412 : const nsPoint& aShift,
413 : nsRegion* aCombined) const
414 : {
415 1252 : if (!mHaveClipRect || (aOldClip && !aOldClip->mHaveClipRect)) {
416 332 : return false;
417 : }
418 :
419 920 : if (aOldClip) {
420 920 : *aCombined = aOldClip->NonRoundedIntersection();
421 920 : aCombined->MoveBy(aShift);
422 920 : aCombined->Or(*aCombined, NonRoundedIntersection());
423 : } else {
424 0 : *aCombined = NonRoundedIntersection();
425 : }
426 920 : return true;
427 : }
428 :
429 : void
430 0 : DisplayItemClip::MoveBy(nsPoint aPoint)
431 : {
432 0 : if (!mHaveClipRect)
433 0 : return;
434 0 : mClipRect += aPoint;
435 0 : for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
436 0 : mRoundedClipRects[i].mRect += aPoint;
437 : }
438 : }
439 :
440 : static DisplayItemClip* gNoClip;
441 :
442 : const DisplayItemClip&
443 2084 : DisplayItemClip::NoClip()
444 : {
445 2084 : if (!gNoClip) {
446 2 : gNoClip = new DisplayItemClip();
447 : }
448 2084 : return *gNoClip;
449 : }
450 :
451 : void
452 0 : DisplayItemClip::Shutdown()
453 : {
454 0 : delete gNoClip;
455 0 : gNoClip = nullptr;
456 0 : }
457 :
458 : nsCString
459 0 : DisplayItemClip::ToString() const
460 : {
461 0 : nsAutoCString str;
462 0 : if (mHaveClipRect) {
463 0 : str.AppendPrintf("%d,%d,%d,%d", mClipRect.x, mClipRect.y,
464 0 : mClipRect.width, mClipRect.height);
465 0 : for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
466 0 : const RoundedRect& r = mRoundedClipRects[i];
467 : str.AppendPrintf(" [%d,%d,%d,%d corners %d,%d,%d,%d,%d,%d,%d,%d]",
468 0 : r.mRect.x, r.mRect.y, r.mRect.width, r.mRect.height,
469 0 : r.mRadii[0], r.mRadii[1], r.mRadii[2], r.mRadii[3],
470 0 : r.mRadii[4], r.mRadii[5], r.mRadii[6], r.mRadii[7]);
471 : }
472 : }
473 0 : return str;
474 : }
475 :
476 : } // namespace mozilla
|