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