tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

DottedCornerFinder.cpp (16963B)


      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/. */
      6 
      7 #include "DottedCornerFinder.h"
      8 
      9 #include <utility>
     10 
     11 #include "BorderCache.h"
     12 #include "BorderConsts.h"
     13 #include "nsTHashMap.h"
     14 
     15 namespace mozilla {
     16 
     17 using namespace gfx;
     18 
     19 static inline Float Square(Float x) { return x * x; }
     20 
     21 static Point PointRotateCCW90(const Point& aP) { return Point(aP.y, -aP.x); }
     22 
     23 struct BestOverlap {
     24  Float overlap;
     25  size_t count;
     26 
     27  BestOverlap() : overlap(0.0f), count(0) {}
     28 
     29  BestOverlap(Float aOverlap, size_t aCount)
     30      : overlap(aOverlap), count(aCount) {}
     31 };
     32 
     33 static const size_t DottedCornerCacheSize = 256;
     34 MOZ_RUNINIT nsTHashMap<FourFloatsHashKey, BestOverlap> DottedCornerCache;
     35 
     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.");
     63 
     64  mInnerWidth = fabs(mInnerBezier.mPoints[0].x - mInnerBezier.mPoints[3].x);
     65  mInnerHeight = fabs(mInnerBezier.mPoints[0].y - mInnerBezier.mPoints[3].y);
     66 
     67  DetermineType(aBorderRadiusX, aBorderRadiusY);
     68 
     69  Reset();
     70 }
     71 
     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;
     76 }
     77 
     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);
     84 
     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;
     96  }
     97 
     98  // See the comment at mType declaration for each condition.
     99 
    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;
    109 
    110        mCenterCurveR = centerCurveWidth;
    111      } else {
    112        mType = SINGLE_CURVE_AND_RADIUS;
    113        borderLength =
    114            GetQuarterEllipticArcLength(centerCurveWidth, centerCurveHeight);
    115      }
    116 
    117      Float diameter = mR0 * 2.0f;
    118      size_t count = round(borderLength / diameter);
    119      if (count % 2) {
    120        count++;
    121      }
    122      mCount = count / 2 - 1;
    123      if (mCount > 0) {
    124        mBestOverlap = 1.0f - borderLength / (diameter * count);
    125      }
    126    } else {
    127      mType = SINGLE_CURVE;
    128    }
    129  }
    130 
    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]);
    137    }
    138  }
    139 
    140  if (minR == 0.0f) {
    141    mHasZeroBorderWidth = true;
    142  }
    143 
    144  if ((mType == SINGLE_CURVE || mType == OTHER) && !mHasZeroBorderWidth) {
    145    FindBestOverlap(minR, minBorderRadius, maxBorderRadius);
    146  }
    147 }
    148 
    149 bool DottedCornerFinder::HasMore(void) const {
    150  if (mHasZeroBorderWidth) {
    151    return mI < mMaxCount && mHasMore;
    152  }
    153 
    154  return mI < mCount;
    155 }
    156 
    157 DottedCornerFinder::Result DottedCornerFinder::Next(void) {
    158  mI++;
    159 
    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;
    170    }
    171 
    172    Point C(mCenterCurveOrigin.x + mCenterCurveR * cos(phi),
    173            mCenterCurveOrigin.y + mCenterCurveR * sin(phi));
    174    return DottedCornerFinder::Result(C, mR0);
    175  }
    176 
    177  // Find unfilled and filled circles.
    178  (void)FindNext(mBestOverlap);
    179  if (mHasMore) {
    180    (void)FindNext(mBestOverlap);
    181  }
    182 
    183  return Result(mLastC, mLastR);
    184 }
    185 
    186 void DottedCornerFinder::Reset(void) {
    187  mLastC = mC0;
    188  mLastR = mR0;
    189  mLastT = 0.0f;
    190  mHasMore = true;
    191 }
    192 
    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.
    198 
    199  NS_ASSERTION(mType == OTHER, "Wrong mType");
    200 
    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;
    207 
    208    Point Near = FindBezierNearestPoint(mOuterBezier, C, t);
    209    Float distSquare = (C - Near).LengthSquare();
    210 
    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;
    217    }
    218  }
    219 }
    220 
    221 Float DottedCornerFinder::FindNext(Float overlap) {
    222  Float lower = mLastT;
    223  Float upper = 1.0f;
    224  Float t;
    225 
    226  Point C = mLastC;
    227  Float r = 0.0f;
    228 
    229  Float factor = (1.0f - overlap);
    230 
    231  Float circlesDist = 0.0f;
    232  Float expectedDist = 0.0f;
    233 
    234  const Float DIST_MARGIN = 0.1f;
    235  if (mType == SINGLE_CURVE_AND_RADIUS) {
    236    r = mR0;
    237 
    238    expectedDist = (r + mLastR) * factor;
    239 
    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);
    244 
    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;
    253      }
    254    }
    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);
    260 
    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;
    269      }
    270 
    271      Point normal = PointRotateCCW90(Diff / DiffLength) * (-mNormalSign);
    272      r = CalculateDistanceToEllipticArc(C, normal, mInnerCurveOrigin,
    273                                         mInnerWidth, mInnerHeight);
    274 
    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;
    284      }
    285    }
    286  } else {
    287    Float distSquareMax = Square(mMaxR * 3.0f);
    288    Float circlesDistSquare = 0.0f;
    289 
    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;
    298      }
    299 
    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;
    308      }
    309 
    310      Point normal = PointRotateCCW90(Diff / DiffLength) * mNormalSign;
    311      FindPointAndRadius(C, r, innerTangent, normal, t);
    312 
    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;
    322      }
    323    }
    324 
    325    circlesDist = sqrt(circlesDistSquare);
    326  }
    327 
    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;
    336    }
    337  }
    338 
    339  mLastT = t;
    340  mLastC = C;
    341  mLastR = r;
    342 
    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;
    348    }
    349  }
    350 
    351  if (expectedDist == 0.0f) {
    352    return 0.0f;
    353  }
    354 
    355  return 1.0f - circlesDist * factor / expectedDist;
    356 }
    357 
    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.
    362 
    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;
    369  }
    370 
    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;
    377 
    378  const Float OVERLAP_MARGIN = 0.1f;
    379  for (size_t j = 0; j < MAX_LOOP; j++) {
    380    Reset();
    381 
    382    size_t count;
    383    Float actualOverlap;
    384    if (!GetCountAndLastOverlap(overlap, &count, &actualOverlap)) {
    385      if (j == 0) {
    386        mCount = mMaxCount;
    387        break;
    388      }
    389    }
    390 
    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.
    395        //
    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        //   +-------+---+-------+---|-------+
    414        //
    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;
    444      }
    445 
    446      // targetCount should be 2n, as we're searching C_1 to C_n.
    447      //
    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
    459      //
    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;
    475      }
    476 
    477      mCount = targetCount / 2 - 1;
    478    }
    479 
    480    if (count == targetCount) {
    481      mBestOverlap = overlap;
    482 
    483      if (fabs(actualOverlap - overlap) < OVERLAP_MARGIN) {
    484        break;
    485      }
    486 
    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;
    493        }
    494      }
    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;
    503        }
    504      }
    505    }
    506 
    507    overlap = (upper + lower) / 2.0f;
    508  }
    509 
    510  if (DottedCornerCache.Count() > DottedCornerCacheSize) {
    511    DottedCornerCache.Clear();
    512  }
    513  DottedCornerCache.InsertOrUpdate(key, BestOverlap(mBestOverlap, mCount));
    514 }
    515 
    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.
    520 
    521  Reset();
    522 
    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;
    533    }
    534  }
    535 
    536  return false;
    537 }
    538 
    539 }  // namespace mozilla