Bug 1185636 - Part 4 - Implement CSS border corners by splitting geometry instead of gradients with hard stops. r=mstange
authorLee Salzman <lsalzman@mozilla.com>
Fri, 31 Jul 2015 14:27:19 -0400
changeset 287519 942beac221e4b6a89ffe096da593871b4d7649b9
parent 287518 09b212388126c4b24042f8a10502ff505f4558d2
child 287520 0ba60c89c282428d1d4a86227065d778df34a838
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1185636
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1185636 - Part 4 - Implement CSS border corners by splitting geometry instead of gradients with hard stops. r=mstange
gfx/2d/Types.h
layout/base/nsCSSRenderingBorders.cpp
layout/base/nsCSSRenderingBorders.h
--- a/gfx/2d/Types.h
+++ b/gfx/2d/Types.h
@@ -261,16 +261,24 @@ public:
   }
 
   uint32_t ToARGB() const
   {
     return uint32_t(b * 255.0f) | uint32_t(g * 255.0f) << 8 |
            uint32_t(r * 255.0f) << 16 | uint32_t(a * 255.0f) << 24;
   }
 
+  bool operator==(const Color& aColor) const {
+    return r == aColor.r && g == aColor.g && b == aColor.b && a == aColor.a;
+  }
+
+  bool operator!=(const Color& aColor) const {
+    return !(*this == aColor);
+  }
+
   Float r, g, b, a;
 };
 
 struct GradientStop
 {
   bool operator<(const GradientStop& aOther) const {
     return offset < aOther.offset;
   }
--- a/layout/base/nsCSSRenderingBorders.cpp
+++ b/layout/base/nsCSSRenderingBorders.cpp
@@ -1027,257 +1027,463 @@ bool IsVisible(int aStyle)
 {
   if (aStyle != NS_STYLE_BORDER_STYLE_NONE &&
       aStyle != NS_STYLE_BORDER_STYLE_HIDDEN) {
         return true;
   }
   return false;
 }
 
-already_AddRefed<GradientStops>
-nsCSSBorderRenderer::CreateCornerGradient(mozilla::css::Corner aCorner,
-                                          nscolor aFirstColor,
-                                          nscolor aSecondColor,
-                                          DrawTarget *aDT,
-                                          Point &aPoint1,
-                                          Point &aPoint2)
+struct twoFloats
 {
-  typedef struct { Float a, b; } twoFloats;
+    Float a, b;
 
-  const twoFloats gradientCoeff[4] = { { -1, +1 },
-                                       { -1, -1 },
-                                       { +1, -1 },
-                                       { +1, +1 } };
-
-  // Sides which form the 'width' and 'height' for the calculation of the angle
-  // for our gradient.
-  const int cornerWidth[4] = { 3, 1, 1, 3 };
-  const int cornerHeight[4] = { 0, 0, 2, 2 };
-
-  Point cornerOrigin = mOuterRect.AtCorner(aCorner);
+    twoFloats operator*(const Size& aSize) const {
+      return { a * aSize.width, b * aSize.height };
+    }
 
-  Point pat1, pat2;
-  pat1.x = cornerOrigin.x +
-    mBorderWidths[cornerHeight[aCorner]] * gradientCoeff[aCorner].a;
-  pat1.y = cornerOrigin.y +
-    mBorderWidths[cornerWidth[aCorner]]  * gradientCoeff[aCorner].b;
-  pat2.x = cornerOrigin.x -
-    mBorderWidths[cornerHeight[aCorner]] * gradientCoeff[aCorner].a;
-  pat2.y = cornerOrigin.y -
-    mBorderWidths[cornerWidth[aCorner]]  * gradientCoeff[aCorner].b;
-
-  aPoint1 = Point(pat1.x, pat1.y);
-  aPoint2 = Point(pat2.x, pat2.y);
-
-  Color firstColor = Color::FromABGR(aFirstColor);
-  Color secondColor = Color::FromABGR(aSecondColor);
+    twoFloats operator*(Float aScale) const {
+      return { a * aScale, b * aScale };
+    }
 
-  nsTArray<gfx::GradientStop> rawStops(2);
-  rawStops.SetLength(2);
-  rawStops[0].color = firstColor;
-  rawStops[0].offset = 0.5;
-  rawStops[1].color = secondColor;
-  rawStops[1].offset = 0.5;
-  RefPtr<GradientStops> gs =
-    gfxGradientCache::GetGradientStops(aDT, rawStops, ExtendMode::CLAMP);
-  if (!gs) {
-    // Having two corners, both with reversed color stops is pretty common
-    // for certain border types. Let's optimize it!
-    rawStops[0].color = secondColor;
-    rawStops[1].color = firstColor;
-    Point tmp = aPoint1;
-    aPoint1 = aPoint2;
-    aPoint2 = tmp;
-    gs = gfxGradientCache::GetOrCreateGradientStops(aDT, rawStops, ExtendMode::CLAMP);
-  }
-  return gs.forget();
-}
+    twoFloats operator+(const Point& aPoint) const {
+      return { a + aPoint.x, b + aPoint.y };
+    }
 
-typedef struct { Float a, b; } twoFloats;
+    operator Point() const {
+      return Point(a, b);
+    }
+};
 
 void
 nsCSSBorderRenderer::DrawSingleWidthSolidBorder()
 {
   // Easy enough to deal with.
   Rect rect = mOuterRect;
   rect.Deflate(0.5);
 
   const twoFloats cornerAdjusts[4] = { { +0.5,  0   },
                                        {    0, +0.5 },
                                        { -0.5,  0   },
                                        {    0, -0.5 } };
   NS_FOR_CSS_SIDES(side) {
-    Point firstCorner = rect.CCWCorner(side);
-    firstCorner.x += cornerAdjusts[side].a;
-    firstCorner.y += cornerAdjusts[side].b;
-    Point secondCorner = rect.CWCorner(side);
-    secondCorner.x += cornerAdjusts[side].a;
-    secondCorner.y += cornerAdjusts[side].b;
+    Point firstCorner = rect.CCWCorner(side) + cornerAdjusts[side];
+    Point secondCorner = rect.CWCorner(side) + cornerAdjusts[side];
 
     ColorPattern color(ToDeviceColor(mBorderColors[side]));
 
     mDrawTarget->StrokeLine(firstCorner, secondCorner, color);
   }
 }
 
+// Intersect a ray from the inner corner to the outer corner
+// with the border radius, yielding the intersection point.
+static Point
+IntersectBorderRadius(const Point& aCenter, const Size& aRadius,
+                      const Point& aInnerCorner,
+                      const Point& aCornerDirection)
+{
+  Point toCorner = aCornerDirection;
+  // transform to-corner ray to unit-circle space
+  toCorner.x /= aRadius.width;
+  toCorner.y /= aRadius.height;
+  // normalize to-corner ray
+  Float cornerDist = toCorner.Length();
+  if (cornerDist < 1.0e-6f) {
+    return aInnerCorner;
+  }
+  toCorner = toCorner / cornerDist;
+  // ray from inner corner to border radius center
+  Point toCenter = aCenter - aInnerCorner;
+  // transform to-center ray to unit-circle space
+  toCenter.x /= aRadius.width;
+  toCenter.y /= aRadius.height;
+  // compute offset of intersection with border radius unit circle
+  Float offset = toCenter.DotProduct(toCorner);
+  // compute discriminant to check for intersections
+  Float discrim = 1.0f - toCenter.DotProduct(toCenter) + offset * offset;
+  // choose farthest intersection
+  offset += sqrtf(std::max(discrim, 0.0f));
+  // transform to-corner ray back out of unit-circle space
+  toCorner.x *= aRadius.width;
+  toCorner.y *= aRadius.height;
+  return aInnerCorner + toCorner * offset;
+}
+
+// Calculate the split point and split angle for a border radius with
+// differing sides.
+static inline void
+SplitBorderRadius(const Point& aCenter, const Size& aRadius,
+                  const Point& aOuterCorner, const Point& aInnerCorner,
+                  const twoFloats& aCornerMults, Float aStartAngle,
+                  Point& aSplit, Float& aSplitAngle)
+{
+  Point cornerDir = aOuterCorner - aInnerCorner;
+  if (cornerDir.x == cornerDir.y && aRadius.IsSquare()) {
+    // optimize 45-degree intersection with circle since we can assume
+    // the circle center lies along the intersection edge
+    aSplit = aCenter - aCornerMults * (aRadius * Float(1.0f / M_SQRT2));
+    aSplitAngle = aStartAngle + 0.5f * M_PI / 2.0f;
+  } else {
+    aSplit = IntersectBorderRadius(aCenter, aRadius, aInnerCorner, cornerDir);
+    aSplitAngle =
+      atan2f((aSplit.y - aCenter.y) / aRadius.height,
+             (aSplit.x - aCenter.x) / aRadius.width);
+  }
+}
+
+// Compute the size of the skirt needed, given the color alphas
+// of each corner side and the slope between them.
+static void
+ComputeCornerSkirtSize(Float aAlpha1, Float aAlpha2,
+                       Float aSlopeY, Float aSlopeX,
+                       Float& aSizeResult, Float& aSlopeResult)
+{
+  // If either side is (almost) invisible or there is no diagonal edge,
+  // then don't try to render a skirt.
+  if (aAlpha1 < 0.01f || aAlpha2 < 0.01f) {
+    return;
+  }
+  aSlopeX = fabs(aSlopeX);
+  aSlopeY = fabs(aSlopeY);
+  if (aSlopeX < 1.0e-6f || aSlopeY < 1.0e-6f) {
+    return;
+  }
+
+  // If first and second color don't match, we need to split the corner in
+  // half. The diagonal edges created may not have full pixel coverage given
+  // anti-aliasing, so we need to compute a small subpixel skirt edge. This
+  // assumes each half has half coverage to start with, and that coverage
+  // increases as the skirt is pushed over, with the end result that we want
+  // to roughly preserve the alpha value along this edge.
+  // Given slope m, alphas a and A, use quadratic formula to solve for S in:
+  //   a*(1 - 0.5*(1-S)*(1-mS))*(1 - 0.5*A) + 0.5*A = A
+  // yielding:
+  //   S = ((1+m) - sqrt((1+m)*(1+m) + 4*m*(1 - A/(a*(1-0.5*A))))) / (2*m)
+  // and substitute k = (1+m)/(2*m):
+  //   S = k - sqrt(k*k + (1 - A/(a*(1-0.5*A)))/m)
+  Float slope = aSlopeY / aSlopeX;
+  Float slopeScale = (1.0f + slope) / (2.0f * slope);
+  Float discrim =
+    slopeScale*slopeScale +
+      (1 - aAlpha2 / (aAlpha1 * (1.0f - 0.49f * aAlpha2))) / slope;
+  if (discrim >= 0) {
+    aSizeResult = slopeScale - sqrtf(discrim);
+    aSlopeResult = slope;
+  }
+}
+
+// Draws a border radius with possibly different sides.
+// A skirt is drawn underneath the corner intersection to hide possible
+// seams when anti-aliased drawing is used.
+// As an optimization, this tries to combine the drawing of the side itself
+// with the drawing of the border radius where possible.
+static void
+DrawBorderRadius(DrawTarget* aDrawTarget,
+                 const Point& aSideStart, Float aSideWidth,
+                 mozilla::css::Corner c,
+                 const Point& aOuterCorner, const Point& aInnerCorner,
+                 const twoFloats& aCornerMultPrev, const twoFloats& aCornerMultNext,
+                 const Size& aCornerDims,
+                 const Size& aOuterRadius, const Size& aInnerRadius,
+                 const Color& aFirstColor, const Color& aSecondColor,
+                 Float aSkirtSize, Float aSkirtSlope)
+{
+  // Connect edge to outer arc start point
+  Point outerCornerStart = aOuterCorner + aCornerMultPrev * aCornerDims;
+  // Connect edge to outer arc end point
+  Point outerCornerEnd = aOuterCorner + aCornerMultNext * aCornerDims;
+  // Connect edge to inner arc start point
+  Point innerCornerStart =
+    outerCornerStart +
+      aCornerMultNext * (aCornerDims - aInnerRadius);
+  // Connect edge to inner arc end point
+  Point innerCornerEnd =
+    outerCornerEnd +
+      aCornerMultPrev * (aCornerDims - aInnerRadius);
+
+  // Outer arc start point
+  Point outerArcStart = aOuterCorner + aCornerMultPrev * aOuterRadius;
+  // Outer arc end point
+  Point outerArcEnd = aOuterCorner + aCornerMultNext * aOuterRadius;
+  // Inner arc start point
+  Point innerArcStart = aInnerCorner + aCornerMultPrev * aInnerRadius;
+  // Inner arc end point
+  Point innerArcEnd = aInnerCorner + aCornerMultNext * aInnerRadius;
+
+  // Outer radius center
+  Point outerCenter = aOuterCorner + (aCornerMultPrev + aCornerMultNext) * aOuterRadius;
+  // Inner radius center
+  Point innerCenter = aInnerCorner + (aCornerMultPrev + aCornerMultNext) * aInnerRadius;
+
+  RefPtr<PathBuilder> builder;
+  RefPtr<Path> path;
+
+  if (aFirstColor.a > 0) {
+    builder = aDrawTarget->CreatePathBuilder();
+    // Combine stroke with corner if color matches.
+    if (aSideWidth > 0) {
+      builder->MoveTo(aSideStart + aCornerMultNext * aSideWidth);
+      builder->LineTo(aSideStart);
+      builder->LineTo(outerCornerStart);
+    } else {
+      builder->MoveTo(outerCornerStart);
+    }
+  }
+
+  if (aFirstColor != aSecondColor) {
+    // Start and end angles of corner quadrant
+    Float startAngle = (c * M_PI) / 2.0f - M_PI,
+          endAngle = startAngle + M_PI / 2.0f,
+          outerSplitAngle, innerSplitAngle;
+    Point outerSplit, innerSplit;
+
+    // Outer half-way point
+    SplitBorderRadius(outerCenter, aOuterRadius, aOuterCorner, aInnerCorner,
+                      aCornerMultPrev + aCornerMultNext, startAngle,
+                      outerSplit, outerSplitAngle);
+    // Inner half-way point
+    if (aInnerRadius.IsEmpty()) {
+      innerSplit = aInnerCorner;
+      innerSplitAngle = endAngle;
+    } else {
+      SplitBorderRadius(innerCenter, aInnerRadius, aOuterCorner, aInnerCorner,
+                        aCornerMultPrev + aCornerMultNext, startAngle,
+                        innerSplit, innerSplitAngle);
+    }
+
+    // Draw first half with first color
+    if (aFirstColor.a > 0) {
+      AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius,
+                       outerArcStart, outerSplit, startAngle, outerSplitAngle);
+      // Draw skirt as part of first half
+      if (aSkirtSize > 0) {
+        builder->LineTo(outerSplit + aCornerMultNext * aSkirtSize);
+        builder->LineTo(innerSplit - aCornerMultPrev * (aSkirtSize * aSkirtSlope));
+      }
+      AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius,
+                       innerSplit, innerArcStart, innerSplitAngle, startAngle);
+      if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) {
+        builder->LineTo(innerCornerStart);
+      }
+      builder->Close();
+      path = builder->Finish();
+      aDrawTarget->Fill(path, ColorPattern(aFirstColor));
+    }
+
+    // Draw second half with second color
+    if (aSecondColor.a > 0) {
+      builder = aDrawTarget->CreatePathBuilder();
+      builder->MoveTo(outerCornerEnd);
+      if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) {
+        builder->LineTo(innerCornerEnd);
+      }
+      AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius,
+                       innerArcEnd, innerSplit, endAngle, innerSplitAngle);
+      AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius,
+                       outerSplit, outerArcEnd, outerSplitAngle, endAngle);
+      builder->Close();
+      path = builder->Finish();
+      aDrawTarget->Fill(path, ColorPattern(aSecondColor));
+    }
+  } else if (aFirstColor.a > 0) {
+    // Draw corner with single color
+    AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius,
+                     outerArcStart, outerArcEnd);
+    builder->LineTo(outerCornerEnd);
+    if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) {
+      builder->LineTo(innerCornerEnd);
+    }
+    AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius,
+                     innerArcEnd, innerArcStart, -kKappaFactor);
+    if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) {
+      builder->LineTo(innerCornerStart);
+    }
+    builder->Close();
+    path = builder->Finish();
+    aDrawTarget->Fill(path, ColorPattern(aFirstColor));
+  }
+}
+
+// Draw a corner with possibly different sides.
+// A skirt is drawn underneath the corner intersection to hide possible
+// seams when anti-aliased drawing is used.
+// As an optimization, this tries to combine the drawing of the side itself
+// with the drawing of the corner where possible.
+static void
+DrawCorner(DrawTarget* aDrawTarget,
+           const Point& aSideStart, Float aSideWidth,
+           mozilla::css::Corner c,
+           const Point& aOuterCorner, const Point& aInnerCorner,
+           const twoFloats& aCornerMultPrev, const twoFloats& aCornerMultNext,
+           const Size& aCornerDims,
+           const Color& aFirstColor, const Color& aSecondColor,
+           Float aSkirtSize, Float aSkirtSlope)
+{
+  // Corner box start point
+  Point cornerStart = aOuterCorner + aCornerMultPrev * aCornerDims;
+  // Corner box end point
+  Point cornerEnd = aOuterCorner + aCornerMultNext * aCornerDims;
+
+  RefPtr<PathBuilder> builder;
+  RefPtr<Path> path;
+
+  if (aFirstColor.a > 0) {
+    builder = aDrawTarget->CreatePathBuilder();
+    // Combine stroke with corner if color matches.
+    if (aSideWidth > 0) {
+      builder->MoveTo(aSideStart + aCornerMultNext * aSideWidth);
+      builder->LineTo(aSideStart);
+    } else {
+      builder->MoveTo(cornerStart);
+    }
+  }
+
+  if (aFirstColor != aSecondColor) {
+    // Draw first half with first color
+    if (aFirstColor.a > 0) {
+      builder->LineTo(aOuterCorner);
+      // Draw skirt as part of first half
+      if (aSkirtSize > 0) {
+        builder->LineTo(aOuterCorner + aCornerMultNext * aSkirtSize);
+        builder->LineTo(aInnerCorner - aCornerMultPrev * (aSkirtSize * aSkirtSlope));
+      }
+      builder->LineTo(aInnerCorner);
+      builder->Close();
+      path = builder->Finish();
+      aDrawTarget->Fill(path, ColorPattern(aFirstColor));
+    }
+
+    // Draw second half with second color
+    if (aSecondColor.a > 0) {
+      builder = aDrawTarget->CreatePathBuilder();
+      builder->MoveTo(cornerEnd);
+      builder->LineTo(aInnerCorner);
+      builder->LineTo(aOuterCorner);
+      builder->Close();
+      path = builder->Finish();
+      aDrawTarget->Fill(path, ColorPattern(aSecondColor));
+    }
+  } else if (aFirstColor.a > 0) {
+    // Draw corner with single color
+    builder->LineTo(aOuterCorner);
+    builder->LineTo(cornerEnd);
+    builder->LineTo(aInnerCorner);
+    builder->Close();
+    path = builder->Finish();
+    aDrawTarget->Fill(path, ColorPattern(aFirstColor));
+  }
+}
+
 void
 nsCSSBorderRenderer::DrawNoCompositeColorSolidBorder()
 {
-  const Float alpha = 0.55191497064665766025f;
-
   const twoFloats cornerMults[4] = { { -1,  0 },
                                      {  0, -1 },
                                      { +1,  0 },
                                      {  0, +1 } };
 
   const twoFloats centerAdjusts[4] = { { 0, +0.5 },
                                        { -0.5, 0 },
                                        { 0, -0.5 },
                                        { +0.5, 0 } };
 
-  Point pc, pci, p0, p1, p2, p3, pd, p3i;
-
   RectCornerRadii innerRadii;
   ComputeInnerRadii(mBorderRadii, mBorderWidths, &innerRadii);
 
   Rect strokeRect = mOuterRect;
   strokeRect.Deflate(Margin(mBorderWidths[0] / 2.0, mBorderWidths[1] / 2.0,
                             mBorderWidths[2] / 2.0, mBorderWidths[3] / 2.0));
 
-  ColorPattern colorPat(Color(0, 0, 0, 0));
-  LinearGradientPattern gradPat(Point(), Point(), nullptr);
+  NS_FOR_CSS_SIDES(i) {
+    // We now draw the current side and the CW corner following it.
+    // The CCW corner of this side was already drawn in the previous iteration.
+    // The side will either be drawn as an explicit stroke or combined
+    // with the drawing of the CW corner.
+    // If the next side does not have a matching color, then we split the
+    // corner into two halves, one of each side's color and draw both.
+    // Thus, the CCW corner of the next side will end up drawn here.
 
-  NS_FOR_CSS_CORNERS(i) {
-      // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
+    // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
     mozilla::css::Corner c = mozilla::css::Corner((i+1) % 4);
     mozilla::css::Corner prevCorner = mozilla::css::Corner(i);
 
     // i+2 and i+3 respectively.  These are used to index into the corner
     // multiplier table, and were deduced by calculating out the long form
     // of each corner and finding a pattern in the signs and values.
     int i1 = (i+1) % 4;
     int i2 = (i+2) % 4;
     int i3 = (i+3) % 4;
 
-    pc = mOuterRect.AtCorner(c);
-    pci = mInnerRect.AtCorner(c);
-
-    nscolor firstColor, secondColor;
-    if (IsVisible(mBorderStyles[i]) && IsVisible(mBorderStyles[i1])) {
-      firstColor = mBorderColors[i];
-      secondColor = mBorderColors[i1];
-    } else if (IsVisible(mBorderStyles[i])) {
-      firstColor = mBorderColors[i];
-      secondColor = mBorderColors[i];
+    Float sideWidth = 0.0f;
+    Color firstColor, secondColor;
+    if (IsVisible(mBorderStyles[i]) && mBorderWidths[i]) {
+      // draw the side since it is visible
+      sideWidth = mBorderWidths[i];
+      firstColor = ToDeviceColor(mBorderColors[i]);
+      // if the next side is visible, use its color for corner
+      secondColor =
+        IsVisible(mBorderStyles[i1]) && mBorderWidths[i1] ?
+          ToDeviceColor(mBorderColors[i1]) :
+          firstColor;
+    } else if (IsVisible(mBorderStyles[i1]) && mBorderWidths[i1]) {
+      // assign next side's color to both corner sides
+      firstColor = ToDeviceColor(mBorderColors[i1]);
+      secondColor = firstColor;
     } else {
-      firstColor = mBorderColors[i1];
-      secondColor = mBorderColors[i1];
+      // neither side is visible, so nothing to do
+      continue;
     }
 
-    RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
-
-    Point strokeStart, strokeEnd;
-
-    strokeStart.x = mOuterRect.AtCorner(prevCorner).x +
-      mBorderCornerDimensions[prevCorner].width * cornerMults[i2].a;
-    strokeStart.y = mOuterRect.AtCorner(prevCorner).y +
-      mBorderCornerDimensions[prevCorner].height * cornerMults[i2].b;
-
-    strokeEnd.x = pc.x + mBorderCornerDimensions[c].width * cornerMults[i].a;
-    strokeEnd.y = pc.y + mBorderCornerDimensions[c].height * cornerMults[i].b;
-
-    strokeStart.x += centerAdjusts[i].a * mBorderWidths[i];
-    strokeStart.y += centerAdjusts[i].b * mBorderWidths[i];
-    strokeEnd.x += centerAdjusts[i].a * mBorderWidths[i];
-    strokeEnd.y += centerAdjusts[i].b * mBorderWidths[i];
+    Point outerCorner = mOuterRect.AtCorner(c);
+    Point innerCorner = mInnerRect.AtCorner(c);
 
-    builder->MoveTo(strokeStart);
-    builder->LineTo(strokeEnd);
-    RefPtr<Path> path = builder->Finish();
-    mDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(mBorderColors[i])),
-                        StrokeOptions(mBorderWidths[i]));
-    builder = nullptr;
-    path = nullptr;
-
-    Pattern *pattern;
-
-    if (firstColor != secondColor) {
-      gradPat.mStops = CreateCornerGradient(c, firstColor, secondColor,
-                                            mDrawTarget, gradPat.mBegin,
-                                            gradPat.mEnd);
-      pattern = &gradPat;
-    } else {
-      colorPat.mColor = ToDeviceColor(firstColor);
-      pattern = &colorPat;
+    // start and end points of border side stroke between corners
+    Point sideStart =
+      mOuterRect.AtCorner(prevCorner) +
+        cornerMults[i2] * mBorderCornerDimensions[prevCorner];
+    Point sideEnd = outerCorner + cornerMults[i] * mBorderCornerDimensions[c];
+    // if the side is inverted, don't draw it
+    if (-(sideEnd - sideStart).DotProduct(cornerMults[i]) <= 0) {
+      sideWidth = 0.0f;
     }
 
-    builder = mDrawTarget->CreatePathBuilder();
-
-    if (mBorderRadii[c].width > 0 && mBorderRadii[c].height > 0) {
-      p0.x = pc.x + cornerMults[i].a * mBorderRadii[c].width;
-      p0.y = pc.y + cornerMults[i].b * mBorderRadii[c].height;
-
-      p3.x = pc.x + cornerMults[i3].a * mBorderRadii[c].width;
-      p3.y = pc.y + cornerMults[i3].b * mBorderRadii[c].height;
-
-      p1.x = p0.x + alpha * cornerMults[i2].a * mBorderRadii[c].width;
-      p1.y = p0.y + alpha * cornerMults[i2].b * mBorderRadii[c].height;
-
-      p2.x = p3.x - alpha * cornerMults[i3].a * mBorderRadii[c].width;
-      p2.y = p3.y - alpha * cornerMults[i3].b * mBorderRadii[c].height;
-
-      Point cornerStart;
-      cornerStart.x = pc.x + cornerMults[i].a * mBorderCornerDimensions[c].width;
-      cornerStart.y = pc.y + cornerMults[i].b * mBorderCornerDimensions[c].height;
-
-      builder->MoveTo(cornerStart);
-      builder->LineTo(p0);
-
-      builder->BezierTo(p1, p2, p3);
-
-      Point outerCornerEnd;
-      outerCornerEnd.x = pc.x + cornerMults[i3].a * mBorderCornerDimensions[c].width;
-      outerCornerEnd.y = pc.y + cornerMults[i3].b * mBorderCornerDimensions[c].height;
-
-      builder->LineTo(outerCornerEnd);
+    Float skirtSize = 0.0f, skirtSlope = 0.0f;
+    // the sides don't match, so compute a skirt
+    if (firstColor != secondColor) {
+      Point cornerDir = outerCorner - innerCorner;
+      ComputeCornerSkirtSize(firstColor.a, secondColor.a,
+                             cornerDir.DotProduct(cornerMults[i]),
+                             cornerDir.DotProduct(cornerMults[i3]),
+                             skirtSize, skirtSlope);
+    }
 
-      p0.x = pci.x + cornerMults[i].a * innerRadii[c].width;
-      p0.y = pci.y + cornerMults[i].b * innerRadii[c].height;
-
-      p3i.x = pci.x + cornerMults[i3].a * innerRadii[c].width;
-      p3i.y = pci.y + cornerMults[i3].b * innerRadii[c].height;
-
-      p1.x = p0.x + alpha * cornerMults[i2].a * innerRadii[c].width;
-      p1.y = p0.y + alpha * cornerMults[i2].b * innerRadii[c].height;
-
-      p2.x = p3i.x - alpha * cornerMults[i3].a * innerRadii[c].width;
-      p2.y = p3i.y - alpha * cornerMults[i3].b * innerRadii[c].height;
-      builder->LineTo(p3i);
-      builder->BezierTo(p2, p1, p0);
-      builder->Close();
-      path = builder->Finish();
-      mDrawTarget->Fill(path, *pattern);
-    } else {
-      Point c1, c2, c3, c4;
-
-      c1.x = pc.x + cornerMults[i].a * mBorderCornerDimensions[c].width;
-      c1.y = pc.y + cornerMults[i].b * mBorderCornerDimensions[c].height;
-      c2 = pc;
-      c3.x = pc.x + cornerMults[i3].a * mBorderCornerDimensions[c].width;
-      c3.y = pc.y + cornerMults[i3].b * mBorderCornerDimensions[c].height;
-
-      builder->MoveTo(c1);
-      builder->LineTo(c2);
-      builder->LineTo(c3);
-      builder->LineTo(pci);
-      builder->Close();
-
-      path = builder->Finish();
-
-      mDrawTarget->Fill(path, *pattern);
+    if (!mBorderRadii[c].IsEmpty()) {
+      // the corner has a border radius
+      DrawBorderRadius(mDrawTarget,
+                       sideStart, sideWidth,
+                       c, outerCorner, innerCorner,
+                       cornerMults[i], cornerMults[i3],
+                       mBorderCornerDimensions[c],
+                       mBorderRadii[c], innerRadii[c],
+                       firstColor, secondColor, skirtSize, skirtSlope);
+    } else if (!mBorderCornerDimensions[c].IsEmpty()) {
+      // a corner with no border radius
+      DrawCorner(mDrawTarget,
+                 sideStart, sideWidth,
+                 c, outerCorner, innerCorner,
+                 cornerMults[i], cornerMults[i3],
+                 mBorderCornerDimensions[c],
+                 firstColor, secondColor, skirtSize, skirtSlope);
+    } else if (sideWidth > 0 && firstColor.a > 0) {
+      // if there is no corner, then stroke the border side separately
+      mDrawTarget->StrokeLine(sideStart + centerAdjusts[i] * sideWidth,
+                              sideEnd + centerAdjusts[i] * sideWidth,
+                              ColorPattern(firstColor),
+                              StrokeOptions(sideWidth));
     }
   }
 }
 
 void
 nsCSSBorderRenderer::DrawRectangularCompositeColors()
 {
   nsBorderColors *currentColors[4];
@@ -1289,33 +1495,27 @@ nsCSSBorderRenderer::DrawRectangularComp
                                         {    0, +0.5 },
                                         { -0.5,  0   },
                                         {    0, -0.5 } };
 
   for (int i = 0; i < mBorderWidths[0]; i++) {
     NS_FOR_CSS_SIDES(side) {
       int sideNext = (side + 1) % 4;
 
-      Point firstCorner = rect.CCWCorner(side);
-      firstCorner.x += cornerAdjusts[side].a;
-      firstCorner.y += cornerAdjusts[side].b;
-      Point secondCorner = rect.CWCorner(side);
-      secondCorner.x -= cornerAdjusts[side].a;
-      secondCorner.y -= cornerAdjusts[side].b;
+      Point firstCorner = rect.CCWCorner(side) + cornerAdjusts[side];
+      Point secondCorner = rect.CWCorner(side) - cornerAdjusts[side];
 
       Color currentColor = Color::FromABGR(
         currentColors[side] ? currentColors[side]->mColor
                             : mBorderColors[side]);
 
       mDrawTarget->StrokeLine(firstCorner, secondCorner,
                               ColorPattern(ToDeviceColor(currentColor)));
 
-      Point cornerTopLeft = rect.CWCorner(side);
-      cornerTopLeft.x -= 0.5;
-      cornerTopLeft.y -= 0.5;
+      Point cornerTopLeft = rect.CWCorner(side) - Point(0.5, 0.5);
       Color nextColor = Color::FromABGR(
         currentColors[sideNext] ? currentColors[sideNext]->mColor
                                 : mBorderColors[sideNext]);
 
       Color cornerColor((currentColor.r + nextColor.r) / 2.f,
                         (currentColor.g + nextColor.g) / 2.f,
                         (currentColor.b + nextColor.b) / 2.f,
                         (currentColor.a + nextColor.a) / 2.f);
--- a/layout/base/nsCSSRenderingBorders.h
+++ b/layout/base/nsCSSRenderingBorders.h
@@ -206,22 +206,16 @@ private:
   // Analyze if all border sides have the same width.
   bool AllBordersSameWidth();
 
   // Analyze if all borders are 'solid' this also considers hidden or 'none'
   // borders because they can be considered 'solid' borders of 0 width and
   // with no color effect.
   bool AllBordersSolid(bool *aHasCompositeColors);
 
-  // Azure variant of CreateCornerGradient.
-  already_AddRefed<mozilla::gfx::GradientStops>
-  CreateCornerGradient(mozilla::css::Corner aCorner, nscolor aFirstColor,
-                       nscolor aSecondColor, mozilla::gfx::DrawTarget *aDT,
-                       mozilla::gfx::Point &aPoint1, mozilla::gfx::Point &aPoint2);
-
   // Draw a solid color border that is uniformly the same width.
   void DrawSingleWidthSolidBorder();
 
   // Draw any border which is solid on all sides and does not use
   // CompositeColors.
   void DrawNoCompositeColorSolidBorder();
 
   // Draw a solid border that has no border radius (i.e. is rectangular) and