Backed out changeset 2960ea3e50ca (bug 1881157) for causing crashtest assertion failu...
[gecko.git] / layout / painting / DottedCornerFinder.cpp
blob2e8408d80b08db0735c0c4e957badc1050141914
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 "DottedCornerFinder.h"
9 #include <utility>
11 #include "BorderCache.h"
12 #include "BorderConsts.h"
13 #include "nsTHashMap.h"
15 namespace mozilla {
17 using namespace gfx;
19 static inline Float Square(Float x) { return x * x; }
21 static Point PointRotateCCW90(const Point& aP) { return Point(aP.y, -aP.x); }
23 struct BestOverlap {
24 Float overlap;
25 size_t count;
27 BestOverlap() : overlap(0.0f), count(0) {}
29 BestOverlap(Float aOverlap, size_t aCount)
30 : overlap(aOverlap), count(aCount) {}
33 static const size_t DottedCornerCacheSize = 256;
34 nsTHashMap<FourFloatsHashKey, BestOverlap> DottedCornerCache;
36 DottedCornerFinder::DottedCornerFinder(const Bezier& aOuterBezier,
37 const Bezier& aInnerBezier,
38 Corner aCorner, Float aBorderRadiusX,
39 Float aBorderRadiusY, const Point& aC0,
40 Float aR0, const Point& aCn, Float aRn,
41 const Size& aCornerDim)
42 : mOuterBezier(aOuterBezier),
43 mInnerBezier(aInnerBezier),
44 mCorner(aCorner),
45 mNormalSign((aCorner == C_TL || aCorner == C_BR) ? -1.0f : 1.0f),
46 mC0(aC0),
47 mCn(aCn),
48 mR0(aR0),
49 mRn(aRn),
50 mMaxR(std::max(aR0, aRn)),
51 mCenterCurveOrigin(mC0.x, mCn.y),
52 mCenterCurveR(0.0),
53 mInnerCurveOrigin(mInnerBezier.mPoints[0].x, mInnerBezier.mPoints[3].y),
54 mBestOverlap(0.0f),
55 mHasZeroBorderWidth(false),
56 mHasMore(true),
57 mMaxCount(aCornerDim.width + aCornerDim.height),
58 mType(OTHER),
59 mI(0),
60 mCount(0) {
61 NS_ASSERTION(mR0 > 0.0f || mRn > 0.0f,
62 "At least one side should have non-zero radius.");
64 mInnerWidth = fabs(mInnerBezier.mPoints[0].x - mInnerBezier.mPoints[3].x);
65 mInnerHeight = fabs(mInnerBezier.mPoints[0].y - mInnerBezier.mPoints[3].y);
67 DetermineType(aBorderRadiusX, aBorderRadiusY);
69 Reset();
72 static bool IsSingleCurve(Float aMinR, Float aMaxR, Float aMinBorderRadius,
73 Float aMaxBorderRadius) {
74 return aMinR > 0.0f && aMinBorderRadius > aMaxR * 4.0f &&
75 aMinBorderRadius / aMaxBorderRadius > 0.5f;
78 void DottedCornerFinder::DetermineType(Float aBorderRadiusX,
79 Float aBorderRadiusY) {
80 // Calculate parameters for the center curve before swap.
81 Float centerCurveWidth = fabs(mC0.x - mCn.x);
82 Float centerCurveHeight = fabs(mC0.y - mCn.y);
83 Point cornerPoint(mCn.x, mC0.y);
85 bool swapped = false;
86 if (mR0 < mRn) {
87 // Always draw from wider side to thinner side.
88 std::swap(mC0, mCn);
89 std::swap(mR0, mRn);
90 std::swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]);
91 std::swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]);
92 std::swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]);
93 std::swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]);
94 mNormalSign = -mNormalSign;
95 swapped = true;
98 // See the comment at mType declaration for each condition.
100 Float minR = std::min(mR0, mRn);
101 Float minBorderRadius = std::min(aBorderRadiusX, aBorderRadiusY);
102 Float maxBorderRadius = std::max(aBorderRadiusX, aBorderRadiusY);
103 if (IsSingleCurve(minR, mMaxR, minBorderRadius, maxBorderRadius)) {
104 if (mR0 == mRn) {
105 Float borderLength;
106 if (minBorderRadius == maxBorderRadius) {
107 mType = PERFECT;
108 borderLength = M_PI * centerCurveHeight / 2.0f;
110 mCenterCurveR = centerCurveWidth;
111 } else {
112 mType = SINGLE_CURVE_AND_RADIUS;
113 borderLength =
114 GetQuarterEllipticArcLength(centerCurveWidth, centerCurveHeight);
117 Float diameter = mR0 * 2.0f;
118 size_t count = round(borderLength / diameter);
119 if (count % 2) {
120 count++;
122 mCount = count / 2 - 1;
123 if (mCount > 0) {
124 mBestOverlap = 1.0f - borderLength / (diameter * count);
126 } else {
127 mType = SINGLE_CURVE;
131 if (mType == SINGLE_CURVE_AND_RADIUS || mType == SINGLE_CURVE) {
132 Size cornerSize(centerCurveWidth, centerCurveHeight);
133 GetBezierPointsForCorner(&mCenterBezier, mCorner, cornerPoint, cornerSize);
134 if (swapped) {
135 std::swap(mCenterBezier.mPoints[0], mCenterBezier.mPoints[3]);
136 std::swap(mCenterBezier.mPoints[1], mCenterBezier.mPoints[2]);
140 if (minR == 0.0f) {
141 mHasZeroBorderWidth = true;
144 if ((mType == SINGLE_CURVE || mType == OTHER) && !mHasZeroBorderWidth) {
145 FindBestOverlap(minR, minBorderRadius, maxBorderRadius);
149 bool DottedCornerFinder::HasMore(void) const {
150 if (mHasZeroBorderWidth) {
151 return mI < mMaxCount && mHasMore;
154 return mI < mCount;
157 DottedCornerFinder::Result DottedCornerFinder::Next(void) {
158 mI++;
160 if (mType == PERFECT) {
161 Float phi = mI * 4.0f * mR0 * (1 - mBestOverlap) / mCenterCurveR;
162 if (mCorner == C_TL) {
163 phi = -M_PI / 2.0f - phi;
164 } else if (mCorner == C_TR) {
165 phi = -M_PI / 2.0f + phi;
166 } else if (mCorner == C_BR) {
167 phi = M_PI / 2.0f - phi;
168 } else {
169 phi = M_PI / 2.0f + phi;
172 Point C(mCenterCurveOrigin.x + mCenterCurveR * cos(phi),
173 mCenterCurveOrigin.y + mCenterCurveR * sin(phi));
174 return DottedCornerFinder::Result(C, mR0);
177 // Find unfilled and filled circles.
178 (void)FindNext(mBestOverlap);
179 if (mHasMore) {
180 (void)FindNext(mBestOverlap);
183 return Result(mLastC, mLastR);
186 void DottedCornerFinder::Reset(void) {
187 mLastC = mC0;
188 mLastR = mR0;
189 mLastT = 0.0f;
190 mHasMore = true;
193 void DottedCornerFinder::FindPointAndRadius(Point& C, Float& r,
194 const Point& innerTangent,
195 const Point& normal, Float t) {
196 // Find radius for the given tangent point on the inner curve such that the
197 // circle is also tangent to the outer curve.
199 NS_ASSERTION(mType == OTHER, "Wrong mType");
201 Float lower = 0.0f;
202 Float upper = mMaxR;
203 const Float DIST_MARGIN = 0.1f;
204 for (size_t i = 0; i < MAX_LOOP; i++) {
205 r = (upper + lower) / 2.0f;
206 C = innerTangent + normal * r;
208 Point Near = FindBezierNearestPoint(mOuterBezier, C, t);
209 Float distSquare = (C - Near).LengthSquare();
211 if (distSquare > Square(r + DIST_MARGIN)) {
212 lower = r;
213 } else if (distSquare < Square(r - DIST_MARGIN)) {
214 upper = r;
215 } else {
216 break;
221 Float DottedCornerFinder::FindNext(Float overlap) {
222 Float lower = mLastT;
223 Float upper = 1.0f;
224 Float t;
226 Point C = mLastC;
227 Float r = 0.0f;
229 Float factor = (1.0f - overlap);
231 Float circlesDist = 0.0f;
232 Float expectedDist = 0.0f;
234 const Float DIST_MARGIN = 0.1f;
235 if (mType == SINGLE_CURVE_AND_RADIUS) {
236 r = mR0;
238 expectedDist = (r + mLastR) * factor;
240 // Find C_i on the center curve.
241 for (size_t i = 0; i < MAX_LOOP; i++) {
242 t = (upper + lower) / 2.0f;
243 C = GetBezierPoint(mCenterBezier, t);
245 // Check overlap along arc.
246 circlesDist = GetBezierLength(mCenterBezier, mLastT, t);
247 if (circlesDist < expectedDist - DIST_MARGIN) {
248 lower = t;
249 } else if (circlesDist > expectedDist + DIST_MARGIN) {
250 upper = t;
251 } else {
252 break;
255 } else if (mType == SINGLE_CURVE) {
256 // Find C_i on the center curve, and calculate r_i.
257 for (size_t i = 0; i < MAX_LOOP; i++) {
258 t = (upper + lower) / 2.0f;
259 C = GetBezierPoint(mCenterBezier, t);
261 Point Diff = GetBezierDifferential(mCenterBezier, t);
262 Float DiffLength = Diff.Length();
263 if (DiffLength == 0.0f) {
264 // Basically this shouldn't happen.
265 // If differential is 0, we cannot calculate tangent circle,
266 // skip this point.
267 t = (t + upper) / 2.0f;
268 continue;
271 Point normal = PointRotateCCW90(Diff / DiffLength) * (-mNormalSign);
272 r = CalculateDistanceToEllipticArc(C, normal, mInnerCurveOrigin,
273 mInnerWidth, mInnerHeight);
275 // Check overlap along arc.
276 circlesDist = GetBezierLength(mCenterBezier, mLastT, t);
277 expectedDist = (r + mLastR) * factor;
278 if (circlesDist < expectedDist - DIST_MARGIN) {
279 lower = t;
280 } else if (circlesDist > expectedDist + DIST_MARGIN) {
281 upper = t;
282 } else {
283 break;
286 } else {
287 Float distSquareMax = Square(mMaxR * 3.0f);
288 Float circlesDistSquare = 0.0f;
290 // Find C_i and r_i.
291 for (size_t i = 0; i < MAX_LOOP; i++) {
292 t = (upper + lower) / 2.0f;
293 Point innerTangent = GetBezierPoint(mInnerBezier, t);
294 if ((innerTangent - mLastC).LengthSquare() > distSquareMax) {
295 // It's clear that this tangent point is too far, skip it.
296 upper = t;
297 continue;
300 Point Diff = GetBezierDifferential(mInnerBezier, t);
301 Float DiffLength = Diff.Length();
302 if (DiffLength == 0.0f) {
303 // Basically this shouldn't happen.
304 // If differential is 0, we cannot calculate tangent circle,
305 // skip this point.
306 t = (t + upper) / 2.0f;
307 continue;
310 Point normal = PointRotateCCW90(Diff / DiffLength) * mNormalSign;
311 FindPointAndRadius(C, r, innerTangent, normal, t);
313 // Check overlap with direct distance.
314 circlesDistSquare = (C - mLastC).LengthSquare();
315 expectedDist = (r + mLastR) * factor;
316 if (circlesDistSquare < Square(expectedDist - DIST_MARGIN)) {
317 lower = t;
318 } else if (circlesDistSquare > Square(expectedDist + DIST_MARGIN)) {
319 upper = t;
320 } else {
321 break;
325 circlesDist = sqrt(circlesDistSquare);
328 if (mHasZeroBorderWidth) {
329 // When calculating circle around r=0, it may result in wrong radius that
330 // is bigger than previous circle. Detect it and stop calculating.
331 const Float R_MARGIN = 0.1f;
332 if (mLastR < R_MARGIN && r > mLastR) {
333 mHasMore = false;
334 mLastR = 0.0f;
335 return 0.0f;
339 mLastT = t;
340 mLastC = C;
341 mLastR = r;
343 if (mHasZeroBorderWidth) {
344 const Float T_MARGIN = 0.001f;
345 if (mLastT >= 1.0f - T_MARGIN ||
346 (mLastC - mCn).LengthSquare() < Square(mLastR)) {
347 mHasMore = false;
351 if (expectedDist == 0.0f) {
352 return 0.0f;
355 return 1.0f - circlesDist * factor / expectedDist;
358 void DottedCornerFinder::FindBestOverlap(Float aMinR, Float aMinBorderRadius,
359 Float aMaxBorderRadius) {
360 // If overlap is not calculateable, find it with binary search,
361 // such that there exists i that C_i == C_n with the given overlap.
363 FourFloats key(aMinR, mMaxR, aMinBorderRadius, aMaxBorderRadius);
364 BestOverlap best;
365 if (DottedCornerCache.Get(key, &best)) {
366 mCount = best.count;
367 mBestOverlap = best.overlap;
368 return;
371 Float lower = 0.0f;
372 Float upper = 0.5f;
373 // Start from lower bound to find the minimum number of circles.
374 Float overlap = 0.0f;
375 mBestOverlap = overlap;
376 size_t targetCount = 0;
378 const Float OVERLAP_MARGIN = 0.1f;
379 for (size_t j = 0; j < MAX_LOOP; j++) {
380 Reset();
382 size_t count;
383 Float actualOverlap;
384 if (!GetCountAndLastOverlap(overlap, &count, &actualOverlap)) {
385 if (j == 0) {
386 mCount = mMaxCount;
387 break;
391 if (j == 0) {
392 if (count < 3 || (count == 3 && actualOverlap > 0.5f)) {
393 // |count == 3 && actualOverlap > 0.5f| means there could be
394 // a circle but it is too near from both ends.
396 // if actualOverlap == 0.0
397 // 1 2 3
398 // +-------+-------+-------+-------+
399 // | ##### | ***** | ##### | ##### |
400 // |#######|*******|#######|#######|
401 // |###+###|***+***|###+###|###+###|
402 // |# C_0 #|* C_1 *|# C_2 #|# C_n #|
403 // | ##### | ***** | ##### | ##### |
404 // +-------+-------+-------+-------+
405 // |
406 // V
407 // +-------+---+-------+---+-------+
408 // | ##### | | ##### | | ##### |
409 // |#######| |#######| |#######|
410 // |###+###| |###+###| |###+###| Find the best overlap to place
411 // |# C_0 #| |# C_1 #| |# C_n #| C_1 at the middle of them
412 // | ##### | | ##### | | ##### |
413 // +-------+---+-------+---|-------+
415 // if actualOverlap == 0.5
416 // 1 2 3
417 // +-------+-------+-------+---+
418 // | ##### | ***** | ##### |## |
419 // |#######|*******|##### C_n #|
420 // |###+###|***+***|###+###+###|
421 // |# C_0 #|* C_1 *|# C_2 #|###|
422 // | ##### | ***** | ##### |## |
423 // +-------+-------+-------+---+
424 // |
425 // V
426 // +-------+-+-------+-+-------+
427 // | ##### | | ##### | | ##### |
428 // |#######| |#######| |#######|
429 // |###+###| |###+###| |###+###| Even if we place C_1 at the middle
430 // |# C_0 #| |# C_1 #| |# C_n #| of them, it's too near from them
431 // | ##### | | ##### | | ##### |
432 // +-------+-+-------+-|-------+
433 // |
434 // V
435 // +-------+-----------+-------+
436 // | ##### | | ##### |
437 // |#######| |#######|
438 // |###+###| |###+###| Do not draw any circle
439 // |# C_0 #| |# C_n #|
440 // | ##### | | ##### |
441 // +-------+-----------+-------+
442 mCount = 0;
443 break;
446 // targetCount should be 2n, as we're searching C_1 to C_n.
448 // targetCount = 4
449 // mCount = 1
450 // 1 2 3 4
451 // +-------+-------+-------+-------+-------+
452 // | ##### | ***** | ##### | ***** | ##### |
453 // |#######|*******|#######|*******|#######|
454 // |###+###|***+***|###+###|***+***|###+###|
455 // |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_n #|
456 // | ##### | ***** | ##### | ***** | ##### |
457 // +-------+-------+-------+-------+-------+
458 // 1
460 // targetCount = 6
461 // mCount = 2
462 // 1 2 3 4 5 6
463 // +-------+-------+-------+-------+-------+-------+-------+
464 // | ##### | ***** | ##### | ***** | ##### | ***** | ##### |
465 // |#######|*******|#######|*******|#######|*******|#######|
466 // |###+###|***+***|###+###|***+***|###+###|***+***|###+###|
467 // |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_4 #|* C_5 *|# C_n #|
468 // | ##### | ***** | ##### | ***** | ##### | ***** | ##### |
469 // +-------+-------+-------+-------+-------+-------+-------+
470 // 1 2
471 if (count % 2) {
472 targetCount = count + 1;
473 } else {
474 targetCount = count;
477 mCount = targetCount / 2 - 1;
480 if (count == targetCount) {
481 mBestOverlap = overlap;
483 if (fabs(actualOverlap - overlap) < OVERLAP_MARGIN) {
484 break;
487 // We started from upper bound, no need to update range when j == 0.
488 if (j > 0) {
489 if (actualOverlap > overlap) {
490 lower = overlap;
491 } else {
492 upper = overlap;
495 } else {
496 // |j == 0 && count != targetCount| means that |targetCount = count + 1|,
497 // and we started from upper bound, no need to update range when j == 0.
498 if (j > 0) {
499 if (count > targetCount) {
500 upper = overlap;
501 } else {
502 lower = overlap;
507 overlap = (upper + lower) / 2.0f;
510 if (DottedCornerCache.Count() > DottedCornerCacheSize) {
511 DottedCornerCache.Clear();
513 DottedCornerCache.InsertOrUpdate(key, BestOverlap(mBestOverlap, mCount));
516 bool DottedCornerFinder::GetCountAndLastOverlap(Float aOverlap, size_t* aCount,
517 Float* aActualOverlap) {
518 // Return the number of circles and the last circles' overlap for the
519 // given overlap.
521 Reset();
523 const Float T_MARGIN = 0.001f;
524 const Float DIST_MARGIN = 0.1f;
525 const Float DIST_MARGIN_SQUARE = Square(DIST_MARGIN);
526 for (size_t i = 0; i < mMaxCount; i++) {
527 Float actualOverlap = FindNext(aOverlap);
528 if (mLastT >= 1.0f - T_MARGIN ||
529 (mLastC - mCn).LengthSquare() < DIST_MARGIN_SQUARE) {
530 *aCount = i + 1;
531 *aActualOverlap = actualOverlap;
532 return true;
536 return false;
539 } // namespace mozilla