LCOV - code coverage report
Current view: top level - layout/painting - nsCSSRenderingGradients.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 260 513 50.7 %
Date: 2017-07-14 16:53:18 Functions: 15 20 75.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : // vim:cindent:ts=2:et:sw=2:
       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             : /* utility functions for drawing borders and backgrounds */
       8             : 
       9             : #include "nsCSSRenderingGradients.h"
      10             : 
      11             : #include "gfx2DGlue.h"
      12             : #include "mozilla/ArrayUtils.h"
      13             : #include "mozilla/DebugOnly.h"
      14             : #include "mozilla/gfx/2D.h"
      15             : #include "mozilla/gfx/Helpers.h"
      16             : #include "mozilla/MathAlgorithms.h"
      17             : 
      18             : #include "nsStyleConsts.h"
      19             : #include "nsPresContext.h"
      20             : #include "nsPoint.h"
      21             : #include "nsRect.h"
      22             : #include "nsStyleContext.h"
      23             : #include "nsCSSColorUtils.h"
      24             : #include "gfxContext.h"
      25             : #include "nsStyleStructInlines.h"
      26             : #include "nsCSSProps.h"
      27             : #include "mozilla/Telemetry.h"
      28             : #include "gfxUtils.h"
      29             : #include "gfxGradientCache.h"
      30             : 
      31             : #include "mozilla/layers/StackingContextHelper.h"
      32             : #include "mozilla/layers/WebRenderLayerManager.h"
      33             : #include "mozilla/webrender/WebRenderTypes.h"
      34             : #include "mozilla/webrender/WebRenderAPI.h"
      35             : #include "Units.h"
      36             : 
      37             : using namespace mozilla;
      38             : using namespace mozilla::gfx;
      39             : 
      40             : static gfxFloat
      41           0 : ConvertGradientValueToPixels(const nsStyleCoord& aCoord,
      42             :                              gfxFloat aFillLength,
      43             :                              int32_t aAppUnitsPerPixel)
      44             : {
      45           0 :   switch (aCoord.GetUnit()) {
      46             :     case eStyleUnit_Percent:
      47           0 :       return aCoord.GetPercentValue() * aFillLength;
      48             :     case eStyleUnit_Coord:
      49           0 :       return NSAppUnitsToFloatPixels(aCoord.GetCoordValue(), aAppUnitsPerPixel);
      50             :     case eStyleUnit_Calc: {
      51           0 :       const nsStyleCoord::Calc *calc = aCoord.GetCalcValue();
      52           0 :       return calc->mPercent * aFillLength +
      53           0 :              NSAppUnitsToFloatPixels(calc->mLength, aAppUnitsPerPixel);
      54             :     }
      55             :     default:
      56           0 :       NS_WARNING("Unexpected coord unit");
      57           0 :       return 0;
      58             :   }
      59             : }
      60             : 
      61             : // Given a box with size aBoxSize and origin (0,0), and an angle aAngle,
      62             : // and a starting point for the gradient line aStart, find the endpoint of
      63             : // the gradient line --- the intersection of the gradient line with a line
      64             : // perpendicular to aAngle that passes through the farthest corner in the
      65             : // direction aAngle.
      66             : static gfxPoint
      67          26 : ComputeGradientLineEndFromAngle(const gfxPoint& aStart,
      68             :                                 double aAngle,
      69             :                                 const gfxSize& aBoxSize)
      70             : {
      71          26 :   double dx = cos(-aAngle);
      72          26 :   double dy = sin(-aAngle);
      73             :   gfxPoint farthestCorner(dx > 0 ? aBoxSize.width : 0,
      74          26 :                           dy > 0 ? aBoxSize.height : 0);
      75          26 :   gfxPoint delta = farthestCorner - aStart;
      76          26 :   double u = delta.x*dy - delta.y*dx;
      77          26 :   return farthestCorner + gfxPoint(-u*dy, u*dx);
      78             : }
      79             : 
      80             : // Compute the start and end points of the gradient line for a linear gradient.
      81             : static void
      82          26 : ComputeLinearGradientLine(nsPresContext* aPresContext,
      83             :                           nsStyleGradient* aGradient,
      84             :                           const gfxSize& aBoxSize,
      85             :                           gfxPoint* aLineStart,
      86             :                           gfxPoint* aLineEnd)
      87             : {
      88          26 :   if (aGradient->mBgPosX.GetUnit() == eStyleUnit_None) {
      89             :     double angle;
      90          23 :     if (aGradient->mAngle.IsAngleValue()) {
      91           0 :       angle = aGradient->mAngle.GetAngleValueInRadians();
      92           0 :       if (!aGradient->mLegacySyntax) {
      93           0 :         angle = M_PI_2 - angle;
      94             :       }
      95             :     } else {
      96          23 :       angle = -M_PI_2; // defaults to vertical gradient starting from top
      97             :     }
      98          23 :     gfxPoint center(aBoxSize.width/2, aBoxSize.height/2);
      99          23 :     *aLineEnd = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
     100          23 :     *aLineStart = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineEnd;
     101           3 :   } else if (!aGradient->mLegacySyntax) {
     102           3 :     float xSign = aGradient->mBgPosX.GetPercentValue() * 2 - 1;
     103           3 :     float ySign = 1 - aGradient->mBgPosY.GetPercentValue() * 2;
     104           3 :     double angle = atan2(ySign * aBoxSize.width, xSign * aBoxSize.height);
     105           3 :     gfxPoint center(aBoxSize.width/2, aBoxSize.height/2);
     106           3 :     *aLineEnd = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
     107           3 :     *aLineStart = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineEnd;
     108             :   } else {
     109           0 :     int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
     110           0 :     *aLineStart = gfxPoint(
     111           0 :       ConvertGradientValueToPixels(aGradient->mBgPosX, aBoxSize.width,
     112             :                                    appUnitsPerPixel),
     113           0 :       ConvertGradientValueToPixels(aGradient->mBgPosY, aBoxSize.height,
     114             :                                    appUnitsPerPixel));
     115           0 :     if (aGradient->mAngle.IsAngleValue()) {
     116           0 :       MOZ_ASSERT(aGradient->mLegacySyntax);
     117           0 :       double angle = aGradient->mAngle.GetAngleValueInRadians();
     118           0 :       *aLineEnd = ComputeGradientLineEndFromAngle(*aLineStart, angle, aBoxSize);
     119             :     } else {
     120             :       // No angle, the line end is just the reflection of the start point
     121             :       // through the center of the box
     122           0 :       *aLineEnd = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineStart;
     123             :     }
     124             :   }
     125          26 : }
     126             : 
     127             : // Compute the start and end points of the gradient line for a radial gradient.
     128             : // Also returns the horizontal and vertical radii defining the circle or
     129             : // ellipse to use.
     130             : static void
     131           0 : ComputeRadialGradientLine(nsPresContext* aPresContext,
     132             :                           nsStyleGradient* aGradient,
     133             :                           const gfxSize& aBoxSize,
     134             :                           gfxPoint* aLineStart,
     135             :                           gfxPoint* aLineEnd,
     136             :                           double* aRadiusX,
     137             :                           double* aRadiusY)
     138             : {
     139           0 :   if (aGradient->mBgPosX.GetUnit() == eStyleUnit_None) {
     140             :     // Default line start point is the center of the box
     141           0 :     *aLineStart = gfxPoint(aBoxSize.width/2, aBoxSize.height/2);
     142             :   } else {
     143           0 :     int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
     144           0 :     *aLineStart = gfxPoint(
     145           0 :       ConvertGradientValueToPixels(aGradient->mBgPosX, aBoxSize.width,
     146             :                                    appUnitsPerPixel),
     147           0 :       ConvertGradientValueToPixels(aGradient->mBgPosY, aBoxSize.height,
     148             :                                    appUnitsPerPixel));
     149             :   }
     150             : 
     151             :   // Compute gradient shape: the x and y radii of an ellipse.
     152             :   double radiusX, radiusY;
     153           0 :   double leftDistance = Abs(aLineStart->x);
     154           0 :   double rightDistance = Abs(aBoxSize.width - aLineStart->x);
     155           0 :   double topDistance = Abs(aLineStart->y);
     156           0 :   double bottomDistance = Abs(aBoxSize.height - aLineStart->y);
     157           0 :   switch (aGradient->mSize) {
     158             :   case NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE:
     159           0 :     radiusX = std::min(leftDistance, rightDistance);
     160           0 :     radiusY = std::min(topDistance, bottomDistance);
     161           0 :     if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
     162           0 :       radiusX = radiusY = std::min(radiusX, radiusY);
     163             :     }
     164           0 :     break;
     165             :   case NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER: {
     166             :     // Compute x and y distances to nearest corner
     167           0 :     double offsetX = std::min(leftDistance, rightDistance);
     168           0 :     double offsetY = std::min(topDistance, bottomDistance);
     169           0 :     if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
     170           0 :       radiusX = radiusY = NS_hypot(offsetX, offsetY);
     171             :     } else {
     172             :       // maintain aspect ratio
     173           0 :       radiusX = offsetX*M_SQRT2;
     174           0 :       radiusY = offsetY*M_SQRT2;
     175             :     }
     176           0 :     break;
     177             :   }
     178             :   case NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE:
     179           0 :     radiusX = std::max(leftDistance, rightDistance);
     180           0 :     radiusY = std::max(topDistance, bottomDistance);
     181           0 :     if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
     182           0 :       radiusX = radiusY = std::max(radiusX, radiusY);
     183             :     }
     184           0 :     break;
     185             :   case NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER: {
     186             :     // Compute x and y distances to nearest corner
     187           0 :     double offsetX = std::max(leftDistance, rightDistance);
     188           0 :     double offsetY = std::max(topDistance, bottomDistance);
     189           0 :     if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
     190           0 :       radiusX = radiusY = NS_hypot(offsetX, offsetY);
     191             :     } else {
     192             :       // maintain aspect ratio
     193           0 :       radiusX = offsetX*M_SQRT2;
     194           0 :       radiusY = offsetY*M_SQRT2;
     195             :     }
     196           0 :     break;
     197             :   }
     198             :   case NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE: {
     199           0 :     int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
     200           0 :     radiusX = ConvertGradientValueToPixels(aGradient->mRadiusX,
     201           0 :                                            aBoxSize.width, appUnitsPerPixel);
     202           0 :     radiusY = ConvertGradientValueToPixels(aGradient->mRadiusY,
     203           0 :                                            aBoxSize.height, appUnitsPerPixel);
     204           0 :     break;
     205             :   }
     206             :   default:
     207           0 :     radiusX = radiusY = 0;
     208           0 :     MOZ_ASSERT(false, "unknown radial gradient sizing method");
     209             :   }
     210           0 :   *aRadiusX = radiusX;
     211           0 :   *aRadiusY = radiusY;
     212             : 
     213             :   double angle;
     214           0 :   if (aGradient->mAngle.IsAngleValue()) {
     215           0 :     angle = aGradient->mAngle.GetAngleValueInRadians();
     216             :   } else {
     217             :     // Default angle is 0deg
     218           0 :     angle = 0.0;
     219             :   }
     220             : 
     221             :   // The gradient line end point is where the gradient line intersects
     222             :   // the ellipse.
     223           0 :   *aLineEnd = *aLineStart + gfxPoint(radiusX*cos(-angle), radiusY*sin(-angle));
     224           0 : }
     225             : 
     226             : 
     227           0 : static float Interpolate(float aF1, float aF2, float aFrac)
     228             : {
     229           0 :   return aF1 + aFrac * (aF2 - aF1);
     230             : }
     231             : 
     232             : // Returns aFrac*aC2 + (1 - aFrac)*C1. The interpolation is done
     233             : // in unpremultiplied space, which is what SVG gradients and cairo
     234             : // gradients expect.
     235             : static Color
     236          28 : InterpolateColor(const Color& aC1, const Color& aC2, float aFrac)
     237             : {
     238          28 :   double other = 1 - aFrac;
     239         112 :   return Color(aC2.r*aFrac + aC1.r*other,
     240          28 :                aC2.g*aFrac + aC1.g*other,
     241          28 :                aC2.b*aFrac + aC1.b*other,
     242          84 :                aC2.a*aFrac + aC1.a*other);
     243             : }
     244             : 
     245             : static nscoord
     246          52 : FindTileStart(nscoord aDirtyCoord, nscoord aTilePos, nscoord aTileDim)
     247             : {
     248          52 :   NS_ASSERTION(aTileDim > 0, "Non-positive tile dimension");
     249          52 :   double multiples = floor(double(aDirtyCoord - aTilePos)/aTileDim);
     250          52 :   return NSToCoordRound(multiples*aTileDim + aTilePos);
     251             : }
     252             : 
     253             : static gfxFloat
     254          32 : LinearGradientStopPositionForPoint(const gfxPoint& aGradientStart,
     255             :                                    const gfxPoint& aGradientEnd,
     256             :                                    const gfxPoint& aPoint)
     257             : {
     258          32 :   gfxPoint d = aGradientEnd - aGradientStart;
     259          32 :   gfxPoint p = aPoint - aGradientStart;
     260             :   /**
     261             :    * Compute a parameter t such that a line perpendicular to the
     262             :    * d vector, passing through aGradientStart + d*t, also
     263             :    * passes through aPoint.
     264             :    *
     265             :    * t is given by
     266             :    *   (p.x - d.x*t)*d.x + (p.y - d.y*t)*d.y = 0
     267             :    *
     268             :    * Solving for t we get
     269             :    *   numerator = d.x*p.x + d.y*p.y
     270             :    *   denominator = d.x^2 + d.y^2
     271             :    *   t = numerator/denominator
     272             :    *
     273             :    * In nsCSSRendering::PaintGradient we know the length of d
     274             :    * is not zero.
     275             :    */
     276          32 :   double numerator = d.x * p.x + d.y * p.y;
     277          32 :   double denominator = d.x * d.x + d.y * d.y;
     278          32 :   return numerator / denominator;
     279             : }
     280             : 
     281             : static bool
     282           8 : RectIsBeyondLinearGradientEdge(const gfxRect& aRect,
     283             :                                const gfxMatrix& aPatternMatrix,
     284             :                                const nsTArray<ColorStop>& aStops,
     285             :                                const gfxPoint& aGradientStart,
     286             :                                const gfxPoint& aGradientEnd,
     287             :                                Color* aOutEdgeColor)
     288             : {
     289             :   gfxFloat topLeft = LinearGradientStopPositionForPoint(
     290           8 :     aGradientStart, aGradientEnd, aPatternMatrix.TransformPoint(aRect.TopLeft()));
     291             :   gfxFloat topRight = LinearGradientStopPositionForPoint(
     292           8 :     aGradientStart, aGradientEnd, aPatternMatrix.TransformPoint(aRect.TopRight()));
     293             :   gfxFloat bottomLeft = LinearGradientStopPositionForPoint(
     294           8 :     aGradientStart, aGradientEnd, aPatternMatrix.TransformPoint(aRect.BottomLeft()));
     295             :   gfxFloat bottomRight = LinearGradientStopPositionForPoint(
     296           8 :     aGradientStart, aGradientEnd, aPatternMatrix.TransformPoint(aRect.BottomRight()));
     297             : 
     298           8 :   const ColorStop& firstStop = aStops[0];
     299          10 :   if (topLeft < firstStop.mPosition && topRight < firstStop.mPosition &&
     300           2 :       bottomLeft < firstStop.mPosition && bottomRight < firstStop.mPosition) {
     301           0 :     *aOutEdgeColor = firstStop.mColor;
     302           0 :     return true;
     303             :   }
     304             : 
     305           8 :   const ColorStop& lastStop = aStops.LastElement();
     306           9 :   if (topLeft >= lastStop.mPosition && topRight >= lastStop.mPosition &&
     307           2 :       bottomLeft >= lastStop.mPosition && bottomRight >= lastStop.mPosition) {
     308           1 :     *aOutEdgeColor = lastStop.mColor;
     309           1 :     return true;
     310             :   }
     311             : 
     312           7 :   return false;
     313             : }
     314             : 
     315          26 : static void ResolveMidpoints(nsTArray<ColorStop>& stops)
     316             : {
     317          42 :   for (size_t x = 1; x < stops.Length() - 1;) {
     318          16 :     if (!stops[x].mIsMidpoint) {
     319          16 :       x++;
     320          32 :       continue;
     321             :     }
     322             : 
     323           0 :     Color color1 = stops[x-1].mColor;
     324           0 :     Color color2 = stops[x+1].mColor;
     325           0 :     float offset1 = stops[x-1].mPosition;
     326           0 :     float offset2 = stops[x+1].mPosition;
     327           0 :     float offset = stops[x].mPosition;
     328             :     // check if everything coincides. If so, ignore the midpoint.
     329           0 :     if (offset - offset1 == offset2 - offset) {
     330           0 :       stops.RemoveElementAt(x);
     331           0 :       continue;
     332             :     }
     333             : 
     334             :     // Check if we coincide with the left colorstop.
     335           0 :     if (offset1 == offset) {
     336             :       // Morph the midpoint to a regular stop with the color of the next
     337             :       // color stop.
     338           0 :       stops[x].mColor = color2;
     339           0 :       stops[x].mIsMidpoint = false;
     340           0 :       continue;
     341             :     }
     342             : 
     343             :     // Check if we coincide with the right colorstop.
     344           0 :     if (offset2 == offset) {
     345             :       // Morph the midpoint to a regular stop with the color of the previous
     346             :       // color stop.
     347           0 :       stops[x].mColor = color1;
     348           0 :       stops[x].mIsMidpoint = false;
     349           0 :       continue;
     350             :     }
     351             : 
     352           0 :     float midpoint = (offset - offset1) / (offset2 - offset1);
     353           0 :     ColorStop newStops[9];
     354           0 :     if (midpoint > .5f) {
     355           0 :       for (size_t y = 0; y < 7; y++) {
     356           0 :         newStops[y].mPosition = offset1 + (offset - offset1) * (7 + y) / 13;
     357             :       }
     358             : 
     359           0 :       newStops[7].mPosition = offset + (offset2 - offset) / 3;
     360           0 :       newStops[8].mPosition = offset + (offset2 - offset) * 2 / 3;
     361             :     } else {
     362           0 :       newStops[0].mPosition = offset1 + (offset - offset1) / 3;
     363           0 :       newStops[1].mPosition = offset1 + (offset - offset1) * 2 / 3;
     364             : 
     365           0 :       for (size_t y = 0; y < 7; y++) {
     366           0 :         newStops[y+2].mPosition = offset + (offset2 - offset) * y / 13;
     367             :       }
     368             :     }
     369             :     // calculate colors
     370             : 
     371           0 :     for (size_t y = 0; y < 9; y++) {
     372             :       // Calculate the intermediate color stops per the formula of the CSS images
     373             :       // spec. http://dev.w3.org/csswg/css-images/#color-stop-syntax
     374             :       // 9 points were chosen since it is the minimum number of stops that always
     375             :       // give the smoothest appearace regardless of midpoint position and difference
     376             :       // in luminance of the end points.
     377           0 :       float relativeOffset = (newStops[y].mPosition - offset1) / (offset2 - offset1);
     378           0 :       float multiplier = powf(relativeOffset, logf(.5f) / logf(midpoint));
     379             : 
     380           0 :       gfx::Float red = color1.r + multiplier * (color2.r - color1.r);
     381           0 :       gfx::Float green = color1.g + multiplier * (color2.g - color1.g);
     382           0 :       gfx::Float blue = color1.b + multiplier * (color2.b - color1.b);
     383           0 :       gfx::Float alpha = color1.a + multiplier * (color2.a - color1.a);
     384             : 
     385           0 :       newStops[y].mColor = Color(red, green, blue, alpha);
     386             :     }
     387             : 
     388           0 :     stops.ReplaceElementsAt(x, 1, newStops, 9);
     389           0 :     x += 9;
     390             :   }
     391          26 : }
     392             : 
     393             : static Color
     394          56 : Premultiply(const Color& aColor)
     395             : {
     396          56 :   gfx::Float a = aColor.a;
     397          56 :   return Color(aColor.r * a, aColor.g * a, aColor.b * a, a);
     398             : }
     399             : 
     400             : static Color
     401          28 : Unpremultiply(const Color& aColor)
     402             : {
     403          28 :   gfx::Float a = aColor.a;
     404             :   return (a > 0.f)
     405          84 :        ? Color(aColor.r / a, aColor.g / a, aColor.b / a, a)
     406         112 :        : aColor;
     407             : }
     408             : 
     409             : static Color
     410          19 : TransparentColor(Color aColor) {
     411          19 :   aColor.a = 0;
     412          19 :   return aColor;
     413             : }
     414             : 
     415             : // Adjusts and adds color stops in such a way that drawing the gradient with
     416             : // unpremultiplied interpolation looks nearly the same as if it were drawn with
     417             : // premultiplied interpolation.
     418             : static const float kAlphaIncrementPerGradientStep = 0.1f;
     419             : static void
     420          26 : ResolvePremultipliedAlpha(nsTArray<ColorStop>& aStops)
     421             : {
     422          76 :   for (size_t x = 1; x < aStops.Length(); x++) {
     423          50 :     const ColorStop leftStop = aStops[x - 1];
     424          50 :     const ColorStop rightStop = aStops[x];
     425             : 
     426             :     // if the left and right stop have the same alpha value, we don't need
     427             :     // to do anything
     428          50 :     if (leftStop.mColor.a == rightStop.mColor.a) {
     429          81 :       continue;
     430             :     }
     431             : 
     432             :     // Is the stop on the left 100% transparent? If so, have it adopt the color
     433             :     // of the right stop
     434          19 :     if (leftStop.mColor.a == 0) {
     435          16 :       aStops[x - 1].mColor = TransparentColor(rightStop.mColor);
     436          16 :       continue;
     437             :     }
     438             : 
     439             :     // Is the stop on the right completely transparent?
     440             :     // If so, duplicate it and assign it the color on the left.
     441           3 :     if (rightStop.mColor.a == 0) {
     442           3 :       ColorStop newStop = rightStop;
     443           3 :       newStop.mColor = TransparentColor(leftStop.mColor);
     444           3 :       aStops.InsertElementAt(x, newStop);
     445           3 :       x++;
     446           3 :       continue;
     447             :     }
     448             : 
     449             :     // Now handle cases where one or both of the stops are partially transparent.
     450           0 :     if (leftStop.mColor.a != 1.0f || rightStop.mColor.a != 1.0f) {
     451           0 :       Color premulLeftColor = Premultiply(leftStop.mColor);
     452           0 :       Color premulRightColor = Premultiply(rightStop.mColor);
     453             :       // Calculate how many extra steps. We do a step per 10% transparency.
     454           0 :       size_t stepCount = NSToIntFloor(fabsf(leftStop.mColor.a - rightStop.mColor.a) / kAlphaIncrementPerGradientStep);
     455           0 :       for (size_t y = 1; y < stepCount; y++) {
     456           0 :         float frac = static_cast<float>(y) / stepCount;
     457           0 :         ColorStop newStop(Interpolate(leftStop.mPosition, rightStop.mPosition, frac), false,
     458           0 :                           Unpremultiply(InterpolateColor(premulLeftColor, premulRightColor, frac)));
     459           0 :         aStops.InsertElementAt(x, newStop);
     460           0 :         x++;
     461             :       }
     462             :     }
     463             :   }
     464          26 : }
     465             : 
     466             : static ColorStop
     467          28 : InterpolateColorStop(const ColorStop& aFirst, const ColorStop& aSecond,
     468             :                      double aPosition, const Color& aDefault)
     469             : {
     470          28 :   MOZ_ASSERT(aFirst.mPosition <= aPosition);
     471          28 :   MOZ_ASSERT(aPosition <= aSecond.mPosition);
     472             : 
     473          28 :   double delta = aSecond.mPosition - aFirst.mPosition;
     474             : 
     475          28 :   if (delta < 1e-6) {
     476           0 :     return ColorStop(aPosition, false, aDefault);
     477             :   }
     478             : 
     479             :   return ColorStop(aPosition, false,
     480          56 :                    Unpremultiply(InterpolateColor(Premultiply(aFirst.mColor),
     481          56 :                                                   Premultiply(aSecond.mColor),
     482          56 :                                                   (aPosition - aFirst.mPosition) / delta)));
     483             : }
     484             : 
     485             : // Clamp and extend the given ColorStop array in-place to fit exactly into the
     486             : // range [0, 1].
     487             : static void
     488          18 : ClampColorStops(nsTArray<ColorStop>& aStops)
     489             : {
     490          18 :   MOZ_ASSERT(aStops.Length() > 0);
     491             : 
     492             :   // If all stops are outside the range, then get rid of everything and replace
     493             :   // with a single colour.
     494          36 :   if (aStops.Length() < 2 || aStops[0].mPosition > 1 ||
     495          18 :       aStops.LastElement().mPosition < 0) {
     496           0 :     Color c = aStops[0].mPosition > 1 ? aStops[0].mColor : aStops.LastElement().mColor;
     497           0 :     aStops.Clear();
     498           0 :     aStops.AppendElement(ColorStop(0, false, c));
     499           0 :     return;
     500             :   }
     501             : 
     502             :   // Create the 0 and 1 points if they fall in the range of |aStops|, and discard
     503             :   // all stops outside the range [0, 1].
     504             :   // XXX: If we have stops positioned at 0 or 1, we only keep the innermost of
     505             :   // those stops. This should be fine for the current user(s) of this function.
     506          34 :   for (size_t i = aStops.Length() - 1; i > 0; i--) {
     507          26 :     if (aStops[i - 1].mPosition < 1 && aStops[i].mPosition >= 1) {
     508             :       // Add a point to position 1.
     509          54 :       aStops[i] = InterpolateColorStop(aStops[i - 1], aStops[i],
     510             :                                        /* aPosition = */ 1,
     511          36 :                                        aStops[i - 1].mColor);
     512             :       // Remove all the elements whose position is greater than 1.
     513          18 :       aStops.RemoveElementsAt(i + 1, aStops.Length() - (i + 1));
     514             :     }
     515          26 :     if (aStops[i - 1].mPosition <= 0 && aStops[i].mPosition > 0) {
     516             :       // Add a point to position 0.
     517          30 :       aStops[i - 1] = InterpolateColorStop(aStops[i - 1], aStops[i],
     518             :                                            /* aPosition = */ 0,
     519          20 :                                            aStops[i].mColor);
     520             :       // Remove all of the preceding stops -- they are all negative.
     521          10 :       aStops.RemoveElementsAt(0, i - 1);
     522          10 :       break;
     523             :     }
     524             :   }
     525             : 
     526          18 :   MOZ_ASSERT(aStops[0].mPosition >= -1e6);
     527          18 :   MOZ_ASSERT(aStops.LastElement().mPosition - 1 <= 1e6);
     528             : 
     529             :   // The end points won't exist yet if they don't fall in the original range of
     530             :   // |aStops|. Create them if needed.
     531          18 :   if (aStops[0].mPosition > 0) {
     532           8 :     aStops.InsertElementAt(0, ColorStop(0, false, aStops[0].mColor));
     533             :   }
     534          18 :   if (aStops.LastElement().mPosition < 1) {
     535           0 :     aStops.AppendElement(ColorStop(1, false, aStops.LastElement().mColor));
     536             :   }
     537             : }
     538             : 
     539             : namespace mozilla {
     540             : 
     541             : nsCSSGradientRenderer
     542          26 : nsCSSGradientRenderer::Create(nsPresContext* aPresContext,
     543             :                              nsStyleGradient* aGradient,
     544             :                              const nsSize& aIntrinsicSize)
     545             : {
     546          26 :   nscoord appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
     547          26 :   gfxSize srcSize = gfxSize(gfxFloat(aIntrinsicSize.width)/appUnitsPerDevPixel,
     548          52 :                             gfxFloat(aIntrinsicSize.height)/appUnitsPerDevPixel);
     549             : 
     550             :   // Compute "gradient line" start and end relative to the intrinsic size of
     551             :   // the gradient.
     552          26 :   gfxPoint lineStart, lineEnd;
     553          26 :   double radiusX = 0, radiusY = 0; // for radial gradients only
     554          26 :   if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
     555             :     ComputeLinearGradientLine(aPresContext, aGradient, srcSize,
     556          26 :                               &lineStart, &lineEnd);
     557             :   } else {
     558             :     ComputeRadialGradientLine(aPresContext, aGradient, srcSize,
     559           0 :                               &lineStart, &lineEnd, &radiusX, &radiusY);
     560             :   }
     561             :   // Avoid sending Infs or Nans to downwind draw targets.
     562          26 :   if (!lineStart.IsFinite() || !lineEnd.IsFinite()) {
     563           0 :     lineStart = lineEnd = gfxPoint(0, 0);
     564             :   }
     565          26 :   gfxFloat lineLength = NS_hypot(lineEnd.x - lineStart.x,
     566          52 :                                   lineEnd.y - lineStart.y);
     567             : 
     568          26 :   MOZ_ASSERT(aGradient->mStops.Length() >= 2,
     569             :              "The parser should reject gradients with less than two stops");
     570             : 
     571             :   // Build color stop array and compute stop positions
     572          52 :   nsTArray<ColorStop> stops;
     573             :   // If there is a run of stops before stop i that did not have specified
     574             :   // positions, then this is the index of the first stop in that run, otherwise
     575             :   // it's -1.
     576          26 :   int32_t firstUnsetPosition = -1;
     577          94 :   for (uint32_t i = 0; i < aGradient->mStops.Length(); ++i) {
     578          68 :     const nsStyleGradientStop& stop = aGradient->mStops[i];
     579             :     double position;
     580          68 :     switch (stop.mLocation.GetUnit()) {
     581             :     case eStyleUnit_None:
     582          39 :       if (i == 0) {
     583             :         // First stop defaults to position 0.0
     584          16 :         position = 0.0;
     585          23 :       } else if (i == aGradient->mStops.Length() - 1) {
     586             :         // Last stop defaults to position 1.0
     587          23 :         position = 1.0;
     588             :       } else {
     589             :         // Other stops with no specified position get their position assigned
     590             :         // later by interpolation, see below.
     591             :         // Remeber where the run of stops with no specified position starts,
     592             :         // if it starts here.
     593           0 :         if (firstUnsetPosition < 0) {
     594           0 :           firstUnsetPosition = i;
     595             :         }
     596           0 :         stops.AppendElement(ColorStop(0, stop.mIsInterpolationHint,
     597           0 :                                       Color::FromABGR(stop.mColor)));
     598           0 :         continue;
     599             :       }
     600          39 :       break;
     601             :     case eStyleUnit_Percent:
     602           6 :       position = stop.mLocation.GetPercentValue();
     603           6 :       break;
     604             :     case eStyleUnit_Coord:
     605          46 :       position = lineLength < 1e-6 ? 0.0 :
     606          23 :           stop.mLocation.GetCoordValue() / appUnitsPerDevPixel / lineLength;
     607          23 :       break;
     608             :     case eStyleUnit_Calc:
     609             :       nsStyleCoord::Calc *calc;
     610           0 :       calc = stop.mLocation.GetCalcValue();
     611           0 :       position = calc->mPercent +
     612           0 :           ((lineLength < 1e-6) ? 0.0 :
     613           0 :           (NSAppUnitsToFloatPixels(calc->mLength, appUnitsPerDevPixel) / lineLength));
     614           0 :       break;
     615             :     default:
     616           0 :       MOZ_ASSERT(false, "Unknown stop position type");
     617             :     }
     618             : 
     619          68 :     if (i > 0) {
     620             :       // Prevent decreasing stop positions by advancing this position
     621             :       // to the previous stop position, if necessary
     622             :       double previousPosition = firstUnsetPosition > 0
     623          84 :         ? stops[firstUnsetPosition - 1].mPosition
     624          84 :         : stops[i - 1].mPosition;
     625          42 :       position = std::max(position, previousPosition);
     626             :     }
     627         136 :     stops.AppendElement(ColorStop(position, stop.mIsInterpolationHint,
     628         204 :                                   Color::FromABGR(stop.mColor)));
     629          68 :     if (firstUnsetPosition > 0) {
     630             :       // Interpolate positions for all stops that didn't have a specified position
     631           0 :       double p = stops[firstUnsetPosition - 1].mPosition;
     632           0 :       double d = (stops[i].mPosition - p)/(i - firstUnsetPosition + 1);
     633           0 :       for (uint32_t j = firstUnsetPosition; j < i; ++j) {
     634           0 :         p += d;
     635           0 :         stops[j].mPosition = p;
     636             :       }
     637           0 :       firstUnsetPosition = -1;
     638             :     }
     639             :   }
     640             : 
     641          26 :   ResolveMidpoints(stops);
     642             : 
     643          26 :   nsCSSGradientRenderer renderer;
     644          26 :   renderer.mPresContext = aPresContext;
     645          26 :   renderer.mGradient = aGradient;
     646          26 :   renderer.mStops = std::move(stops);
     647          26 :   renderer.mLineStart = lineStart;
     648          26 :   renderer.mLineEnd = lineEnd;
     649          26 :   renderer.mRadiusX = radiusX;
     650          26 :   renderer.mRadiusY = radiusY;
     651          52 :   return renderer;
     652             : }
     653             : 
     654             : void
     655          26 : nsCSSGradientRenderer::Paint(gfxContext& aContext,
     656             :                              const nsRect& aDest,
     657             :                              const nsRect& aFillArea,
     658             :                              const nsSize& aRepeatSize,
     659             :                              const CSSIntRect& aSrc,
     660             :                              const nsRect& aDirtyRect,
     661             :                              float aOpacity)
     662             : {
     663          52 :   AUTO_PROFILER_LABEL("nsCSSGradientRenderer::Paint", GRAPHICS);
     664          52 :   Telemetry::AutoTimer<Telemetry::GRADIENT_DURATION, Telemetry::Microsecond> gradientTimer;
     665             : 
     666          26 :   if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
     667           0 :     return;
     668             :   }
     669             : 
     670          26 :   nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel();
     671             : 
     672          26 :   gfxFloat lineLength = NS_hypot(mLineEnd.x - mLineStart.x,
     673          52 :                                  mLineEnd.y - mLineStart.y);
     674          26 :   bool cellContainsFill = aDest.Contains(aFillArea);
     675             : 
     676             :   // If a non-repeating linear gradient is axis-aligned and there are no gaps
     677             :   // between tiles, we can optimise away most of the work by converting to a
     678             :   // repeating linear gradient and filling the whole destination rect at once.
     679             :   bool forceRepeatToCoverTiles =
     680          52 :     mGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR &&
     681          47 :     (mLineStart.x == mLineEnd.x) != (mLineStart.y == mLineEnd.y) &&
     682          63 :     aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height &&
     683          68 :     !mGradient->mRepeating && !aSrc.IsEmpty() && !cellContainsFill;
     684             : 
     685          26 :   gfxMatrix matrix;
     686          26 :   if (forceRepeatToCoverTiles) {
     687             :     // Length of the source rectangle along the gradient axis.
     688             :     double rectLen;
     689             :     // The position of the start of the rectangle along the gradient.
     690             :     double offset;
     691             : 
     692             :     // The gradient line is "backwards". Flip the line upside down to make
     693             :     // things easier, and then rotate the matrix to turn everything back the
     694             :     // right way up.
     695          18 :     if (mLineStart.x > mLineEnd.x || mLineStart.y > mLineEnd.y) {
     696           0 :       std::swap(mLineStart, mLineEnd);
     697           0 :       matrix.PreScale(-1, -1);
     698             :     }
     699             : 
     700             :     // Fit the gradient line exactly into the source rect.
     701             :     // aSrc is relative to aIntrinsincSize.
     702             :     // srcRectDev will be relative to srcSize, so in the same coordinate space
     703             :     // as lineStart / lineEnd.
     704             :     gfxRect srcRectDev = nsLayoutUtils::RectToGfxRect(
     705          18 :       CSSPixel::ToAppUnits(aSrc), appUnitsPerDevPixel);
     706          18 :     if (mLineStart.x != mLineEnd.x) {
     707           0 :       rectLen = srcRectDev.width;
     708           0 :       offset = (srcRectDev.x - mLineStart.x) / lineLength;
     709           0 :       mLineStart.x = srcRectDev.x;
     710           0 :       mLineEnd.x = srcRectDev.XMost();
     711             :     } else {
     712          18 :       rectLen = srcRectDev.height;
     713          18 :       offset = (srcRectDev.y - mLineStart.y) / lineLength;
     714          18 :       mLineStart.y = srcRectDev.y;
     715          18 :       mLineEnd.y = srcRectDev.YMost();
     716             :     }
     717             : 
     718             :     // Adjust gradient stop positions for the new gradient line.
     719          18 :     double scale = lineLength / rectLen;
     720          62 :     for (size_t i = 0; i < mStops.Length(); i++) {
     721          44 :       mStops[i].mPosition = (mStops[i].mPosition - offset) * fabs(scale);
     722             :     }
     723             : 
     724             :     // Clamp or extrapolate gradient stops to exactly [0, 1].
     725          18 :     ClampColorStops(mStops);
     726             : 
     727          18 :     lineLength = rectLen;
     728             :   }
     729             : 
     730             :   // Eliminate negative-position stops if the gradient is radial.
     731          26 :   double firstStop = mStops[0].mPosition;
     732          26 :   if (mGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && firstStop < 0.0) {
     733           0 :     if (mGradient->mRepeating) {
     734             :       // Choose an instance of the repeated pattern that gives us all positive
     735             :       // stop-offsets.
     736           0 :       double lastStop = mStops[mStops.Length() - 1].mPosition;
     737           0 :       double stopDelta = lastStop - firstStop;
     738             :       // If all the stops are in approximately the same place then logic below
     739             :       // will kick in that makes us draw just the last stop color, so don't
     740             :       // try to do anything in that case. We certainly need to avoid
     741             :       // dividing by zero.
     742           0 :       if (stopDelta >= 1e-6) {
     743           0 :         double instanceCount = ceil(-firstStop/stopDelta);
     744             :         // Advance stops by instanceCount multiples of the period of the
     745             :         // repeating gradient.
     746           0 :         double offset = instanceCount*stopDelta;
     747           0 :         for (uint32_t i = 0; i < mStops.Length(); i++) {
     748           0 :           mStops[i].mPosition += offset;
     749             :         }
     750             :       }
     751             :     } else {
     752             :       // Move negative-position stops to position 0.0. We may also need
     753             :       // to set the color of the stop to the color the gradient should have
     754             :       // at the center of the ellipse.
     755           0 :       for (uint32_t i = 0; i < mStops.Length(); i++) {
     756           0 :         double pos = mStops[i].mPosition;
     757           0 :         if (pos < 0.0) {
     758           0 :           mStops[i].mPosition = 0.0;
     759             :           // If this is the last stop, we don't need to adjust the color,
     760             :           // it will fill the entire area.
     761           0 :           if (i < mStops.Length() - 1) {
     762           0 :             double nextPos = mStops[i + 1].mPosition;
     763             :             // If nextPos is approximately equal to pos, then we don't
     764             :             // need to adjust the color of this stop because it's
     765             :             // not going to be displayed.
     766             :             // If nextPos is negative, we don't need to adjust the color of
     767             :             // this stop since it's not going to be displayed because
     768             :             // nextPos will also be moved to 0.0.
     769           0 :             if (nextPos >= 0.0 && nextPos - pos >= 1e-6) {
     770             :               // Compute how far the new position 0.0 is along the interval
     771             :               // between pos and nextPos.
     772             :               // XXX Color interpolation (in cairo, too) should use the
     773             :               // CSS 'color-interpolation' property!
     774           0 :               float frac = float((0.0 - pos)/(nextPos - pos));
     775           0 :               mStops[i].mColor =
     776           0 :                 InterpolateColor(mStops[i].mColor, mStops[i + 1].mColor, frac);
     777             :             }
     778             :           }
     779             :         }
     780             :       }
     781             :     }
     782           0 :     firstStop = mStops[0].mPosition;
     783           0 :     MOZ_ASSERT(firstStop >= 0.0, "Failed to fix stop offsets");
     784             :   }
     785             : 
     786          26 :   if (mGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && !mGradient->mRepeating) {
     787             :     // Direct2D can only handle a particular class of radial gradients because
     788             :     // of the way the it specifies gradients. Setting firstStop to 0, when we
     789             :     // can, will help us stay on the fast path. Currently we don't do this
     790             :     // for repeating gradients but we could by adjusting the stop collection
     791             :     // to start at 0
     792           0 :     firstStop = 0;
     793             :   }
     794             : 
     795          26 :   double lastStop = mStops[mStops.Length() - 1].mPosition;
     796             :   // Cairo gradients must have stop positions in the range [0, 1]. So,
     797             :   // stop positions will be normalized below by subtracting firstStop and then
     798             :   // multiplying by stopScale.
     799             :   double stopScale;
     800          26 :   double stopOrigin = firstStop;
     801          26 :   double stopEnd = lastStop;
     802          26 :   double stopDelta = lastStop - firstStop;
     803          26 :   bool zeroRadius = mGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR &&
     804          26 :                       (mRadiusX < 1e-6 || mRadiusY < 1e-6);
     805          26 :   if (stopDelta < 1e-6 || lineLength < 1e-6 || zeroRadius) {
     806             :     // Stops are all at the same place. Map all stops to 0.0.
     807             :     // For repeating radial gradients, or for any radial gradients with
     808             :     // a zero radius, we need to fill with the last stop color, so just set
     809             :     // both radii to 0.
     810           0 :     if (mGradient->mRepeating || zeroRadius) {
     811           0 :       mRadiusX = mRadiusY = 0.0;
     812             :     }
     813           0 :     stopDelta = 0.0;
     814           0 :     lastStop = firstStop;
     815             :   }
     816             : 
     817             :   // Don't normalize non-repeating or degenerate gradients below 0..1
     818             :   // This keeps the gradient line as large as the box and doesn't
     819             :   // lets us avoiding having to get padding correct for stops
     820             :   // at 0 and 1
     821          26 :   if (!mGradient->mRepeating || stopDelta == 0.0) {
     822          26 :     stopOrigin = std::min(stopOrigin, 0.0);
     823          26 :     stopEnd = std::max(stopEnd, 1.0);
     824             :   }
     825          26 :   stopScale = 1.0/(stopEnd - stopOrigin);
     826             : 
     827             :   // Create the gradient pattern.
     828          52 :   RefPtr<gfxPattern> gradientPattern;
     829          26 :   gfxPoint gradientStart;
     830          26 :   gfxPoint gradientEnd;
     831          26 :   if (mGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
     832             :     // Compute the actual gradient line ends we need to pass to cairo after
     833             :     // stops have been normalized.
     834          26 :     gradientStart = mLineStart + (mLineEnd - mLineStart)*stopOrigin;
     835          26 :     gradientEnd = mLineStart + (mLineEnd - mLineStart)*stopEnd;
     836             : 
     837          26 :     if (stopDelta == 0.0) {
     838             :       // Stops are all at the same place. For repeating gradients, this will
     839             :       // just paint the last stop color. We don't need to do anything.
     840             :       // For non-repeating gradients, this should render as two colors, one
     841             :       // on each "side" of the gradient line segment, which is a point. All
     842             :       // our stops will be at 0.0; we just need to set the direction vector
     843             :       // correctly.
     844           0 :       gradientEnd = gradientStart + (mLineEnd - mLineStart);
     845             :     }
     846             : 
     847             :     gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y,
     848          26 :                                       gradientEnd.x, gradientEnd.y);
     849             :   } else {
     850           0 :     NS_ASSERTION(firstStop >= 0.0,
     851             :                   "Negative stops not allowed for radial gradients");
     852             : 
     853             :     // To form an ellipse, we'll stretch a circle vertically, if necessary.
     854             :     // So our radii are based on radiusX.
     855           0 :     double innerRadius = mRadiusX*stopOrigin;
     856           0 :     double outerRadius = mRadiusX*stopEnd;
     857           0 :     if (stopDelta == 0.0) {
     858             :       // Stops are all at the same place.  See above (except we now have
     859             :       // the inside vs. outside of an ellipse).
     860           0 :       outerRadius = innerRadius + 1;
     861             :     }
     862             :     gradientPattern = new gfxPattern(mLineStart.x, mLineStart.y, innerRadius,
     863           0 :                                      mLineStart.x, mLineStart.y, outerRadius);
     864           0 :     if (mRadiusX != mRadiusY) {
     865             :       // Stretch the circles into ellipses vertically by setting a transform
     866             :       // in the pattern.
     867             :       // Recall that this is the transform from user space to pattern space.
     868             :       // So to stretch the ellipse by factor of P vertically, we scale
     869             :       // user coordinates by 1/P.
     870           0 :       matrix.PreTranslate(mLineStart);
     871           0 :       matrix.PreScale(1.0, mRadiusX/mRadiusY);
     872           0 :       matrix.PreTranslate(-mLineStart);
     873             :     }
     874             :   }
     875             :   // Use a pattern transform to take account of source and dest rects
     876          52 :   matrix.PreTranslate(gfxPoint(mPresContext->CSSPixelsToDevPixels(aSrc.x),
     877          52 :                                mPresContext->CSSPixelsToDevPixels(aSrc.y)));
     878          26 :   matrix.PreScale(gfxFloat(mPresContext->CSSPixelsToAppUnits(aSrc.width))/aDest.width,
     879          52 :                   gfxFloat(mPresContext->CSSPixelsToAppUnits(aSrc.height))/aDest.height);
     880          26 :   gradientPattern->SetMatrix(matrix);
     881             : 
     882          26 :   if (stopDelta == 0.0) {
     883             :     // Non-repeating gradient with all stops in same place -> just add
     884             :     // first stop and last stop, both at position 0.
     885             :     // Repeating gradient with all stops in the same place, or radial
     886             :     // gradient with radius of 0 -> just paint the last stop color.
     887             :     // We use firstStop offset to keep |stops| with same units (will later normalize to 0).
     888           0 :     Color firstColor(mStops[0].mColor);
     889           0 :     Color lastColor(mStops.LastElement().mColor);
     890           0 :     mStops.Clear();
     891             : 
     892           0 :     if (!mGradient->mRepeating && !zeroRadius) {
     893           0 :       mStops.AppendElement(ColorStop(firstStop, false, firstColor));
     894             :     }
     895           0 :     mStops.AppendElement(ColorStop(firstStop, false, lastColor));
     896             :   }
     897             : 
     898          26 :   ResolvePremultipliedAlpha(mStops);
     899             : 
     900          26 :   bool isRepeat = mGradient->mRepeating || forceRepeatToCoverTiles;
     901             : 
     902             :   // Now set normalized color stops in pattern.
     903             :   // Offscreen gradient surface cache (not a tile):
     904             :   // On some backends (e.g. D2D), the GradientStops object holds an offscreen surface
     905             :   // which is a lookup table used to evaluate the gradient. This surface can use
     906             :   // much memory (ram and/or GPU ram) and can be expensive to create. So we cache it.
     907             :   // The cache key correlates 1:1 with the arguments for CreateGradientStops (also the implied backend type)
     908             :   // Note that GradientStop is a simple struct with a stop value (while GradientStops has the surface).
     909          52 :   nsTArray<gfx::GradientStop> rawStops(mStops.Length());
     910          26 :   rawStops.SetLength(mStops.Length());
     911         105 :   for(uint32_t i = 0; i < mStops.Length(); i++) {
     912          79 :     rawStops[i].color = mStops[i].mColor;
     913          79 :     rawStops[i].color.a *= aOpacity;
     914          79 :     rawStops[i].offset = stopScale * (mStops[i].mPosition - stopOrigin);
     915             :   }
     916             :   RefPtr<mozilla::gfx::GradientStops> gs =
     917          52 :     gfxGradientCache::GetOrCreateGradientStops(aContext.GetDrawTarget(),
     918             :                                                rawStops,
     919          52 :                                                isRepeat ? gfx::ExtendMode::REPEAT : gfx::ExtendMode::CLAMP);
     920          26 :   gradientPattern->SetColorStops(gs);
     921             : 
     922             :   // Paint gradient tiles. This isn't terribly efficient, but doing it this
     923             :   // way is simple and sure to get pixel-snapping right. We could speed things
     924             :   // up by drawing tiles into temporary surfaces and copying those to the
     925             :   // destination, but after pixel-snapping tiles may not all be the same size.
     926          52 :   nsRect dirty;
     927          26 :   if (!dirty.IntersectRect(aDirtyRect, aFillArea))
     928           0 :     return;
     929             : 
     930             :   gfxRect areaToFill =
     931          26 :     nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel);
     932          26 :   gfxRect dirtyAreaToFill = nsLayoutUtils::RectToGfxRect(dirty, appUnitsPerDevPixel);
     933          26 :   dirtyAreaToFill.RoundOut();
     934             : 
     935          26 :   gfxMatrix ctm = aContext.CurrentMatrix();
     936          26 :   bool isCTMPreservingAxisAlignedRectangles = ctm.PreservesAxisAlignedRectangles();
     937             : 
     938             :   // xStart/yStart are the top-left corner of the top-left tile.
     939          26 :   nscoord xStart = FindTileStart(dirty.x, aDest.x, aRepeatSize.width);
     940          26 :   nscoord yStart = FindTileStart(dirty.y, aDest.y, aRepeatSize.height);
     941          26 :   nscoord xEnd = forceRepeatToCoverTiles ? xStart + aDest.width : dirty.XMost();
     942          26 :   nscoord yEnd = forceRepeatToCoverTiles ? yStart + aDest.height : dirty.YMost();
     943             : 
     944             :   // x and y are the top-left corner of the tile to draw
     945          52 :   for (nscoord y = yStart; y < yEnd; y += aRepeatSize.height) {
     946          52 :     for (nscoord x = xStart; x < xEnd; x += aRepeatSize.width) {
     947             :       // The coordinates of the tile
     948             :       gfxRect tileRect = nsLayoutUtils::RectToGfxRect(
     949          52 :                       nsRect(x, y, aDest.width, aDest.height),
     950          26 :                       appUnitsPerDevPixel);
     951             :       // The actual area to fill with this tile is the intersection of this
     952             :       // tile with the overall area we're supposed to be filling
     953             :       gfxRect fillRect =
     954          26 :         forceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill);
     955             :       // Try snapping the fill rect. Snap its top-left and bottom-right
     956             :       // independently to preserve the orientation.
     957          26 :       gfxPoint snappedFillRectTopLeft = fillRect.TopLeft();
     958          26 :       gfxPoint snappedFillRectTopRight = fillRect.TopRight();
     959          26 :       gfxPoint snappedFillRectBottomRight = fillRect.BottomRight();
     960             :       // Snap three points instead of just two to ensure we choose the
     961             :       // correct orientation if there's a reflection.
     962          52 :       if (isCTMPreservingAxisAlignedRectangles &&
     963          52 :           aContext.UserToDevicePixelSnapped(snappedFillRectTopLeft, true) &&
     964          78 :           aContext.UserToDevicePixelSnapped(snappedFillRectBottomRight, true) &&
     965          26 :           aContext.UserToDevicePixelSnapped(snappedFillRectTopRight, true)) {
     966          52 :         if (snappedFillRectTopLeft.x == snappedFillRectBottomRight.x ||
     967          26 :             snappedFillRectTopLeft.y == snappedFillRectBottomRight.y) {
     968             :           // Nothing to draw; avoid scaling by zero and other weirdness that
     969             :           // could put the context in an error state.
     970           0 :           continue;
     971             :         }
     972             :         // Set the context's transform to the transform that maps fillRect to
     973             :         // snappedFillRect. The part of the gradient that was going to
     974             :         // exactly fill fillRect will fill snappedFillRect instead.
     975             :         gfxMatrix transform = gfxUtils::TransformRectToRect(fillRect,
     976             :             snappedFillRectTopLeft, snappedFillRectTopRight,
     977          26 :             snappedFillRectBottomRight);
     978          26 :         aContext.SetMatrix(transform);
     979             :       }
     980          26 :       aContext.NewPath();
     981          26 :       aContext.Rectangle(fillRect);
     982             : 
     983          26 :       gfxRect dirtyFillRect = fillRect.Intersect(dirtyAreaToFill);
     984          26 :       gfxRect fillRectRelativeToTile = dirtyFillRect - tileRect.TopLeft();
     985          26 :       Color edgeColor;
     986          34 :       if (mGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR && !isRepeat &&
     987           8 :           RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, mStops,
     988             :                                          gradientStart, gradientEnd, &edgeColor)) {
     989           1 :         edgeColor.a *= aOpacity;
     990           1 :         aContext.SetColor(edgeColor);
     991             :       } else {
     992             :         aContext.SetMatrix(
     993          25 :           aContext.CurrentMatrix().Copy().PreTranslate(tileRect.TopLeft()));
     994          25 :         aContext.SetPattern(gradientPattern);
     995             :       }
     996          26 :       aContext.Fill();
     997          26 :       aContext.SetMatrix(ctm);
     998             :     }
     999             :   }
    1000             : }
    1001             : 
    1002             : void
    1003           0 : nsCSSGradientRenderer::BuildWebRenderParameters(float aOpacity,
    1004             :                                                 WrGradientExtendMode& aMode,
    1005             :                                                 nsTArray<WrGradientStop>& aStops,
    1006             :                                                 LayoutDevicePoint& aLineStart,
    1007             :                                                 LayoutDevicePoint& aLineEnd,
    1008             :                                                 LayoutDeviceSize& aGradientRadius)
    1009             : {
    1010           0 :   aMode = mGradient->mRepeating ? WrGradientExtendMode::Repeat : WrGradientExtendMode::Clamp;
    1011             : 
    1012           0 :   aStops.SetLength(mStops.Length());
    1013           0 :   for(uint32_t i = 0; i < mStops.Length(); i++) {
    1014           0 :     aStops[i].color.r = mStops[i].mColor.r;
    1015           0 :     aStops[i].color.g = mStops[i].mColor.g;
    1016           0 :     aStops[i].color.b = mStops[i].mColor.b;
    1017           0 :     aStops[i].color.a = mStops[i].mColor.a * aOpacity;
    1018           0 :     aStops[i].offset = mStops[i].mPosition;
    1019             :   }
    1020             : 
    1021           0 :   aLineStart = LayoutDevicePoint(mLineStart.x, mLineStart.y);
    1022           0 :   aLineEnd = LayoutDevicePoint(mLineEnd.x, mLineEnd.y);
    1023           0 :   aGradientRadius = LayoutDeviceSize(mRadiusX, mRadiusY);
    1024           0 : }
    1025             : 
    1026             : void
    1027           0 : nsCSSGradientRenderer::BuildWebRenderDisplayItems(wr::DisplayListBuilder& aBuilder,
    1028             :                                                   const layers::StackingContextHelper& aSc,
    1029             :                                                   layers::WebRenderDisplayItemLayer* aLayer,
    1030             :                                                   const nsRect& aDest,
    1031             :                                                   const nsRect& aFillArea,
    1032             :                                                   const nsSize& aRepeatSize,
    1033             :                                                   const CSSIntRect& aSrc,
    1034             :                                                   float aOpacity)
    1035             : {
    1036           0 :   if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
    1037           0 :     return;
    1038             :   }
    1039             : 
    1040             :   WrGradientExtendMode extendMode;
    1041           0 :   nsTArray<WrGradientStop> stops;
    1042           0 :   LayoutDevicePoint lineStart;
    1043           0 :   LayoutDevicePoint lineEnd;
    1044           0 :   LayoutDeviceSize gradientRadius;
    1045           0 :   BuildWebRenderParameters(aOpacity, extendMode, stops, lineStart, lineEnd, gradientRadius);
    1046             : 
    1047           0 :   nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel();
    1048             : 
    1049           0 :   nsPoint firstTile = nsPoint(FindTileStart(aFillArea.x, aDest.x, aRepeatSize.width),
    1050           0 :                               FindTileStart(aFillArea.y, aDest.y, aRepeatSize.height));
    1051             : 
    1052             :   // Translate the parameters into device coordinates
    1053           0 :   LayoutDeviceRect clipBounds = LayoutDevicePixel::FromAppUnits(aFillArea, appUnitsPerDevPixel);
    1054           0 :   LayoutDeviceRect firstTileBounds = LayoutDevicePixel::FromAppUnits(nsRect(firstTile, aDest.Size()), appUnitsPerDevPixel);
    1055           0 :   LayoutDeviceSize tileRepeat = LayoutDevicePixel::FromAppUnits(aRepeatSize, appUnitsPerDevPixel);
    1056             : 
    1057             :   // Calculate the bounds of the gradient display item, which starts at the first
    1058             :   // tile and extends to the end of clip bounds
    1059           0 :   LayoutDevicePoint tileToClip = clipBounds.BottomRight() - firstTileBounds.TopLeft();
    1060           0 :   LayoutDeviceRect gradientBounds = LayoutDeviceRect(firstTileBounds.TopLeft(),
    1061           0 :                                                      LayoutDeviceSize(tileToClip.x, tileToClip.y));
    1062             : 
    1063             :   // Calculate the tile spacing, which is the repeat size minus the tile size
    1064           0 :   LayoutDeviceSize tileSpacing = tileRepeat - firstTileBounds.Size();
    1065             : 
    1066             :   // Make the rects relative to the parent stacking context
    1067           0 :   WrRect wrClipBounds = aSc.ToRelativeWrRect(clipBounds);
    1068           0 :   LayerSize layerFirstTileSize = ViewAs<LayerPixel>(firstTileBounds.Size(),
    1069           0 :       PixelCastJustification::WebRenderHasUnitResolution);
    1070           0 :   WrRect wrGradientBounds = aSc.ToRelativeWrRect(gradientBounds);
    1071             : 
    1072             :   // srcTransform is used for scaling the gradient to match aSrc
    1073           0 :   LayoutDeviceRect srcTransform = LayoutDeviceRect(mPresContext->CSSPixelsToAppUnits(aSrc.x),
    1074           0 :                                                    mPresContext->CSSPixelsToAppUnits(aSrc.y),
    1075           0 :                                                    aDest.width / ((float)mPresContext->CSSPixelsToAppUnits(aSrc.width)),
    1076           0 :                                                    aDest.height / ((float)mPresContext->CSSPixelsToAppUnits(aSrc.height)));
    1077             : 
    1078           0 :   lineStart.x = (lineStart.x - srcTransform.x) * srcTransform.width;
    1079           0 :   lineStart.y = (lineStart.y - srcTransform.y) * srcTransform.height;
    1080             : 
    1081           0 :   if (mGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
    1082           0 :     lineEnd.x = (lineEnd.x - srcTransform.x) * srcTransform.width;
    1083           0 :     lineEnd.y = (lineEnd.y - srcTransform.y) * srcTransform.height;
    1084             : 
    1085           0 :     aBuilder.PushLinearGradient(
    1086             :       wrGradientBounds,
    1087             :       wrClipBounds,
    1088           0 :       mozilla::wr::ToWrPoint(lineStart),
    1089           0 :       mozilla::wr::ToWrPoint(lineEnd),
    1090             :       stops,
    1091             :       extendMode,
    1092             :       mozilla::wr::ToWrSize(layerFirstTileSize),
    1093           0 :       mozilla::wr::ToWrSize(tileSpacing));
    1094             :   } else {
    1095           0 :     gradientRadius.width *= srcTransform.width;
    1096           0 :     gradientRadius.height *= srcTransform.height;
    1097             : 
    1098           0 :     aBuilder.PushRadialGradient(
    1099             :       wrGradientBounds,
    1100             :       wrClipBounds,
    1101           0 :       mozilla::wr::ToWrPoint(lineStart),
    1102           0 :       mozilla::wr::ToWrSize(gradientRadius),
    1103             :       stops,
    1104             :       extendMode,
    1105             :       mozilla::wr::ToWrSize(layerFirstTileSize),
    1106           0 :       mozilla::wr::ToWrSize(tileSpacing));
    1107             :   }
    1108             : }
    1109             : 
    1110             : } // namespace mozilla

Generated by: LCOV version 1.13