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 <tuple>
12 
13 #include "gfx2DGlue.h"
14 #include "mozilla/ArrayUtils.h"
15 #include "mozilla/ComputedStyle.h"
16 #include "mozilla/DebugOnly.h"
17 #include "mozilla/gfx/2D.h"
18 #include "mozilla/gfx/Helpers.h"
19 #include "mozilla/MathAlgorithms.h"
20 #include "mozilla/ProfilerLabels.h"
21 
22 #include "nsStyleConsts.h"
23 #include "nsPresContext.h"
24 #include "nsPoint.h"
25 #include "nsRect.h"
26 #include "nsCSSColorUtils.h"
27 #include "gfxContext.h"
28 #include "nsStyleStructInlines.h"
29 #include "nsCSSProps.h"
30 #include "gfxUtils.h"
31 #include "gfxGradientCache.h"
32 
33 #include "mozilla/layers/StackingContextHelper.h"
34 #include "mozilla/layers/WebRenderLayerManager.h"
35 #include "mozilla/webrender/WebRenderTypes.h"
36 #include "mozilla/webrender/WebRenderAPI.h"
37 #include "Units.h"
38 
39 using namespace mozilla;
40 using namespace mozilla::gfx;
41 
ResolvePosition(const Position & aPos,const CSSSize & aSize)42 static CSSPoint ResolvePosition(const Position& aPos, const CSSSize& aSize) {
43   CSSCoord h = aPos.horizontal.ResolveToCSSPixels(aSize.width);
44   CSSCoord v = aPos.vertical.ResolveToCSSPixels(aSize.height);
45   return CSSPoint(h, v);
46 }
47 
48 // Given a box with size aBoxSize and origin (0,0), and an angle aAngle,
49 // and a starting point for the gradient line aStart, find the endpoint of
50 // the gradient line --- the intersection of the gradient line with a line
51 // perpendicular to aAngle that passes through the farthest corner in the
52 // direction aAngle.
ComputeGradientLineEndFromAngle(const CSSPoint & aStart,double aAngle,const CSSSize & aBoxSize)53 static CSSPoint ComputeGradientLineEndFromAngle(const CSSPoint& aStart,
54                                                 double aAngle,
55                                                 const CSSSize& aBoxSize) {
56   double dx = cos(-aAngle);
57   double dy = sin(-aAngle);
58   CSSPoint farthestCorner(dx > 0 ? aBoxSize.width : 0,
59                           dy > 0 ? aBoxSize.height : 0);
60   CSSPoint delta = farthestCorner - aStart;
61   double u = delta.x * dy - delta.y * dx;
62   return farthestCorner + CSSPoint(-u * dy, u * dx);
63 }
64 
65 // Compute the start and end points of the gradient line for a linear gradient.
ComputeLinearGradientLine(nsPresContext * aPresContext,const StyleGradient & aGradient,const CSSSize & aBoxSize)66 static std::tuple<CSSPoint, CSSPoint> ComputeLinearGradientLine(
67     nsPresContext* aPresContext, const StyleGradient& aGradient,
68     const CSSSize& aBoxSize) {
69   using X = StyleHorizontalPositionKeyword;
70   using Y = StyleVerticalPositionKeyword;
71 
72   const StyleLineDirection& direction = aGradient.AsLinear().direction;
73   const bool isModern =
74       aGradient.AsLinear().compat_mode == StyleGradientCompatMode::Modern;
75 
76   CSSPoint center(aBoxSize.width / 2, aBoxSize.height / 2);
77   switch (direction.tag) {
78     case StyleLineDirection::Tag::Angle: {
79       double angle = direction.AsAngle().ToRadians();
80       if (isModern) {
81         angle = M_PI_2 - angle;
82       }
83       CSSPoint end = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
84       CSSPoint start = CSSPoint(aBoxSize.width, aBoxSize.height) - end;
85       return {start, end};
86     }
87     case StyleLineDirection::Tag::Vertical: {
88       CSSPoint start(center.x, 0);
89       CSSPoint end(center.x, aBoxSize.height);
90       if (isModern == (direction.AsVertical() == Y::Top)) {
91         std::swap(start.y, end.y);
92       }
93       return {start, end};
94     }
95     case StyleLineDirection::Tag::Horizontal: {
96       CSSPoint start(0, center.y);
97       CSSPoint end(aBoxSize.width, center.y);
98       if (isModern == (direction.AsHorizontal() == X::Left)) {
99         std::swap(start.x, end.x);
100       }
101       return {start, end};
102     }
103     case StyleLineDirection::Tag::Corner: {
104       const auto& corner = direction.AsCorner();
105       const X& h = corner._0;
106       const Y& v = corner._1;
107 
108       if (isModern) {
109         float xSign = h == X::Right ? 1.0 : -1.0;
110         float ySign = v == Y::Top ? 1.0 : -1.0;
111         double angle = atan2(ySign * aBoxSize.width, xSign * aBoxSize.height);
112         CSSPoint end = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
113         CSSPoint start = CSSPoint(aBoxSize.width, aBoxSize.height) - end;
114         return {start, end};
115       }
116 
117       CSSCoord startX = h == X::Left ? 0.0 : aBoxSize.width;
118       CSSCoord startY = v == Y::Top ? 0.0 : aBoxSize.height;
119 
120       CSSPoint start(startX, startY);
121       CSSPoint end = CSSPoint(aBoxSize.width, aBoxSize.height) - start;
122       return {start, end};
123     }
124     default:
125       break;
126   }
127   MOZ_ASSERT_UNREACHABLE("Unknown line direction");
128   return {CSSPoint(), CSSPoint()};
129 }
130 
131 using EndingShape = StyleGenericEndingShape<Length, LengthPercentage>;
132 using RadialGradientRadii =
133     Variant<StyleShapeExtent, std::pair<CSSCoord, CSSCoord>>;
134 
ComputeRadialGradientRadii(const EndingShape & aShape,const CSSSize & aSize)135 static RadialGradientRadii ComputeRadialGradientRadii(const EndingShape& aShape,
136                                                       const CSSSize& aSize) {
137   if (aShape.IsCircle()) {
138     auto& circle = aShape.AsCircle();
139     if (circle.IsExtent()) {
140       return RadialGradientRadii(circle.AsExtent());
141     }
142     CSSCoord radius = circle.AsRadius().ToCSSPixels();
143     return RadialGradientRadii(std::make_pair(radius, radius));
144   }
145   auto& ellipse = aShape.AsEllipse();
146   if (ellipse.IsExtent()) {
147     return RadialGradientRadii(ellipse.AsExtent());
148   }
149 
150   auto& radii = ellipse.AsRadii();
151   return RadialGradientRadii(
152       std::make_pair(radii._0.ResolveToCSSPixels(aSize.width),
153                      radii._1.ResolveToCSSPixels(aSize.height)));
154 }
155 
156 // Compute the start and end points of the gradient line for a radial gradient.
157 // Also returns the horizontal and vertical radii defining the circle or
158 // ellipse to use.
159 static std::tuple<CSSPoint, CSSPoint, CSSCoord, CSSCoord>
ComputeRadialGradientLine(const StyleGradient & aGradient,const CSSSize & aBoxSize)160 ComputeRadialGradientLine(const StyleGradient& aGradient,
161                           const CSSSize& aBoxSize) {
162   const auto& radial = aGradient.AsRadial();
163   const EndingShape& endingShape = radial.shape;
164   const Position& position = radial.position;
165   CSSPoint start = ResolvePosition(position, aBoxSize);
166 
167   // Compute gradient shape: the x and y radii of an ellipse.
168   CSSCoord radiusX, radiusY;
169   CSSCoord leftDistance = Abs(start.x);
170   CSSCoord rightDistance = Abs(aBoxSize.width - start.x);
171   CSSCoord topDistance = Abs(start.y);
172   CSSCoord bottomDistance = Abs(aBoxSize.height - start.y);
173 
174   auto radii = ComputeRadialGradientRadii(endingShape, aBoxSize);
175   if (radii.is<StyleShapeExtent>()) {
176     switch (radii.as<StyleShapeExtent>()) {
177       case StyleShapeExtent::ClosestSide:
178         radiusX = std::min(leftDistance, rightDistance);
179         radiusY = std::min(topDistance, bottomDistance);
180         if (endingShape.IsCircle()) {
181           radiusX = radiusY = std::min(radiusX, radiusY);
182         }
183         break;
184       case StyleShapeExtent::ClosestCorner: {
185         // Compute x and y distances to nearest corner
186         CSSCoord offsetX = std::min(leftDistance, rightDistance);
187         CSSCoord offsetY = std::min(topDistance, bottomDistance);
188         if (endingShape.IsCircle()) {
189           radiusX = radiusY = NS_hypot(offsetX, offsetY);
190         } else {
191           // maintain aspect ratio
192           radiusX = offsetX * M_SQRT2;
193           radiusY = offsetY * M_SQRT2;
194         }
195         break;
196       }
197       case StyleShapeExtent::FarthestSide:
198         radiusX = std::max(leftDistance, rightDistance);
199         radiusY = std::max(topDistance, bottomDistance);
200         if (endingShape.IsCircle()) {
201           radiusX = radiusY = std::max(radiusX, radiusY);
202         }
203         break;
204       case StyleShapeExtent::FarthestCorner: {
205         // Compute x and y distances to nearest corner
206         CSSCoord offsetX = std::max(leftDistance, rightDistance);
207         CSSCoord offsetY = std::max(topDistance, bottomDistance);
208         if (endingShape.IsCircle()) {
209           radiusX = radiusY = NS_hypot(offsetX, offsetY);
210         } else {
211           // maintain aspect ratio
212           radiusX = offsetX * M_SQRT2;
213           radiusY = offsetY * M_SQRT2;
214         }
215         break;
216       }
217       default:
218         MOZ_ASSERT_UNREACHABLE("Unknown shape extent keyword?");
219         radiusX = radiusY = 0;
220     }
221   } else {
222     auto pair = radii.as<std::pair<CSSCoord, CSSCoord>>();
223     radiusX = pair.first;
224     radiusY = pair.second;
225   }
226 
227   // The gradient line end point is where the gradient line intersects
228   // the ellipse.
229   CSSPoint end = start + CSSPoint(radiusX, 0);
230   return {start, end, radiusX, radiusY};
231 }
232 
233 // Compute the center and the start angle of the conic gradient.
ComputeConicGradientProperties(const StyleGradient & aGradient,const CSSSize & aBoxSize)234 static std::tuple<CSSPoint, float> ComputeConicGradientProperties(
235     const StyleGradient& aGradient, const CSSSize& aBoxSize) {
236   const auto& conic = aGradient.AsConic();
237   const Position& position = conic.position;
238   float angle = static_cast<float>(conic.angle.ToRadians());
239   CSSPoint center = ResolvePosition(position, aBoxSize);
240 
241   return {center, angle};
242 }
243 
Interpolate(float aF1,float aF2,float aFrac)244 static float Interpolate(float aF1, float aF2, float aFrac) {
245   return aF1 + aFrac * (aF2 - aF1);
246 }
247 
FindTileStart(nscoord aDirtyCoord,nscoord aTilePos,nscoord aTileDim)248 static nscoord FindTileStart(nscoord aDirtyCoord, nscoord aTilePos,
249                              nscoord aTileDim) {
250   NS_ASSERTION(aTileDim > 0, "Non-positive tile dimension");
251   double multiples = floor(double(aDirtyCoord - aTilePos) / aTileDim);
252   return NSToCoordRound(multiples * aTileDim + aTilePos);
253 }
254 
LinearGradientStopPositionForPoint(const gfxPoint & aGradientStart,const gfxPoint & aGradientEnd,const gfxPoint & aPoint)255 static gfxFloat LinearGradientStopPositionForPoint(
256     const gfxPoint& aGradientStart, const gfxPoint& aGradientEnd,
257     const gfxPoint& aPoint) {
258   gfxPoint d = aGradientEnd - aGradientStart;
259   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   double numerator = d.x * p.x + d.y * p.y;
277   double denominator = d.x * d.x + d.y * d.y;
278   return numerator / denominator;
279 }
280 
RectIsBeyondLinearGradientEdge(const gfxRect & aRect,const gfxMatrix & aPatternMatrix,const nsTArray<ColorStop> & aStops,const gfxPoint & aGradientStart,const gfxPoint & aGradientEnd,sRGBColor * aOutEdgeColor)281 static bool RectIsBeyondLinearGradientEdge(const gfxRect& aRect,
282                                            const gfxMatrix& aPatternMatrix,
283                                            const nsTArray<ColorStop>& aStops,
284                                            const gfxPoint& aGradientStart,
285                                            const gfxPoint& aGradientEnd,
286                                            sRGBColor* aOutEdgeColor) {
287   gfxFloat topLeft = LinearGradientStopPositionForPoint(
288       aGradientStart, aGradientEnd,
289       aPatternMatrix.TransformPoint(aRect.TopLeft()));
290   gfxFloat topRight = LinearGradientStopPositionForPoint(
291       aGradientStart, aGradientEnd,
292       aPatternMatrix.TransformPoint(aRect.TopRight()));
293   gfxFloat bottomLeft = LinearGradientStopPositionForPoint(
294       aGradientStart, aGradientEnd,
295       aPatternMatrix.TransformPoint(aRect.BottomLeft()));
296   gfxFloat bottomRight = LinearGradientStopPositionForPoint(
297       aGradientStart, aGradientEnd,
298       aPatternMatrix.TransformPoint(aRect.BottomRight()));
299 
300   const ColorStop& firstStop = aStops[0];
301   if (topLeft < firstStop.mPosition && topRight < firstStop.mPosition &&
302       bottomLeft < firstStop.mPosition && bottomRight < firstStop.mPosition) {
303     *aOutEdgeColor = firstStop.mColor;
304     return true;
305   }
306 
307   const ColorStop& lastStop = aStops.LastElement();
308   if (topLeft >= lastStop.mPosition && topRight >= lastStop.mPosition &&
309       bottomLeft >= lastStop.mPosition && bottomRight >= lastStop.mPosition) {
310     *aOutEdgeColor = lastStop.mColor;
311     return true;
312   }
313 
314   return false;
315 }
316 
ResolveMidpoints(nsTArray<ColorStop> & stops)317 static void ResolveMidpoints(nsTArray<ColorStop>& stops) {
318   for (size_t x = 1; x < stops.Length() - 1;) {
319     if (!stops[x].mIsMidpoint) {
320       x++;
321       continue;
322     }
323 
324     sRGBColor color1 = stops[x - 1].mColor;
325     sRGBColor color2 = stops[x + 1].mColor;
326     float offset1 = stops[x - 1].mPosition;
327     float offset2 = stops[x + 1].mPosition;
328     float offset = stops[x].mPosition;
329     // check if everything coincides. If so, ignore the midpoint.
330     if (offset - offset1 == offset2 - offset) {
331       stops.RemoveElementAt(x);
332       continue;
333     }
334 
335     // Check if we coincide with the left colorstop.
336     if (offset1 == offset) {
337       // Morph the midpoint to a regular stop with the color of the next
338       // color stop.
339       stops[x].mColor = color2;
340       stops[x].mIsMidpoint = false;
341       continue;
342     }
343 
344     // Check if we coincide with the right colorstop.
345     if (offset2 == offset) {
346       // Morph the midpoint to a regular stop with the color of the previous
347       // color stop.
348       stops[x].mColor = color1;
349       stops[x].mIsMidpoint = false;
350       continue;
351     }
352 
353     float midpoint = (offset - offset1) / (offset2 - offset1);
354     ColorStop newStops[9];
355     if (midpoint > .5f) {
356       for (size_t y = 0; y < 7; y++) {
357         newStops[y].mPosition = offset1 + (offset - offset1) * (7 + y) / 13;
358       }
359 
360       newStops[7].mPosition = offset + (offset2 - offset) / 3;
361       newStops[8].mPosition = offset + (offset2 - offset) * 2 / 3;
362     } else {
363       newStops[0].mPosition = offset1 + (offset - offset1) / 3;
364       newStops[1].mPosition = offset1 + (offset - offset1) * 2 / 3;
365 
366       for (size_t y = 0; y < 7; y++) {
367         newStops[y + 2].mPosition = offset + (offset2 - offset) * y / 13;
368       }
369     }
370     // calculate colors
371 
372     for (auto& newStop : newStops) {
373       // Calculate the intermediate color stops per the formula of the CSS
374       // images spec. http://dev.w3.org/csswg/css-images/#color-stop-syntax 9
375       // points were chosen since it is the minimum number of stops that always
376       // give the smoothest appearace regardless of midpoint position and
377       // difference in luminance of the end points.
378       float relativeOffset =
379           (newStop.mPosition - offset1) / (offset2 - offset1);
380       float multiplier = powf(relativeOffset, logf(.5f) / logf(midpoint));
381 
382       gfx::Float red = color1.r + multiplier * (color2.r - color1.r);
383       gfx::Float green = color1.g + multiplier * (color2.g - color1.g);
384       gfx::Float blue = color1.b + multiplier * (color2.b - color1.b);
385       gfx::Float alpha = color1.a + multiplier * (color2.a - color1.a);
386 
387       newStop.mColor = sRGBColor(red, green, blue, alpha);
388     }
389 
390     stops.ReplaceElementsAt(x, 1, newStops, 9);
391     x += 9;
392   }
393 }
394 
TransparentColor(sRGBColor aColor)395 static sRGBColor TransparentColor(sRGBColor aColor) {
396   aColor.a = 0;
397   return aColor;
398 }
399 
400 // Adjusts and adds color stops in such a way that drawing the gradient with
401 // unpremultiplied interpolation looks nearly the same as if it were drawn with
402 // premultiplied interpolation.
403 static const float kAlphaIncrementPerGradientStep = 0.1f;
ResolvePremultipliedAlpha(nsTArray<ColorStop> & aStops)404 static void ResolvePremultipliedAlpha(nsTArray<ColorStop>& aStops) {
405   for (size_t x = 1; x < aStops.Length(); x++) {
406     const ColorStop leftStop = aStops[x - 1];
407     const ColorStop rightStop = aStops[x];
408 
409     // if the left and right stop have the same alpha value, we don't need
410     // to do anything. Hardstops should be instant, and also should never
411     // require dealing with interpolation.
412     if (leftStop.mColor.a == rightStop.mColor.a ||
413         leftStop.mPosition == rightStop.mPosition) {
414       continue;
415     }
416 
417     // Is the stop on the left 100% transparent? If so, have it adopt the color
418     // of the right stop
419     if (leftStop.mColor.a == 0) {
420       aStops[x - 1].mColor = TransparentColor(rightStop.mColor);
421       continue;
422     }
423 
424     // Is the stop on the right completely transparent?
425     // If so, duplicate it and assign it the color on the left.
426     if (rightStop.mColor.a == 0) {
427       ColorStop newStop = rightStop;
428       newStop.mColor = TransparentColor(leftStop.mColor);
429       aStops.InsertElementAt(x, newStop);
430       x++;
431       continue;
432     }
433 
434     // Now handle cases where one or both of the stops are partially
435     // transparent.
436     if (leftStop.mColor.a != 1.0f || rightStop.mColor.a != 1.0f) {
437       sRGBColor premulLeftColor = leftStop.mColor.Premultiplied();
438       sRGBColor premulRightColor = rightStop.mColor.Premultiplied();
439       // Calculate how many extra steps. We do a step per 10% transparency.
440       size_t stepCount =
441           NSToIntFloor(fabsf(leftStop.mColor.a - rightStop.mColor.a) /
442                        kAlphaIncrementPerGradientStep);
443       for (size_t y = 1; y < stepCount; y++) {
444         float frac = static_cast<float>(y) / stepCount;
445         ColorStop newStop(
446             Interpolate(leftStop.mPosition, rightStop.mPosition, frac), false,
447             sRGBColor::InterpolatePremultiplied(premulLeftColor,
448                                                 premulRightColor, frac)
449                 .Unpremultiplied());
450         aStops.InsertElementAt(x, newStop);
451         x++;
452       }
453     }
454   }
455 }
456 
InterpolateColorStop(const ColorStop & aFirst,const ColorStop & aSecond,double aPosition,const sRGBColor & aDefault)457 static ColorStop InterpolateColorStop(const ColorStop& aFirst,
458                                       const ColorStop& aSecond,
459                                       double aPosition,
460                                       const sRGBColor& aDefault) {
461   MOZ_ASSERT(aFirst.mPosition <= aPosition);
462   MOZ_ASSERT(aPosition <= aSecond.mPosition);
463 
464   double delta = aSecond.mPosition - aFirst.mPosition;
465   if (delta < 1e-6) {
466     return ColorStop(aPosition, false, aDefault);
467   }
468 
469   return ColorStop(
470       aPosition, false,
471       sRGBColor::Interpolate(aFirst.mColor, aSecond.mColor,
472                              (aPosition - aFirst.mPosition) / delta));
473 }
474 
475 // Clamp and extend the given ColorStop array in-place to fit exactly into the
476 // range [0, 1].
ClampColorStops(nsTArray<ColorStop> & aStops)477 static void ClampColorStops(nsTArray<ColorStop>& aStops) {
478   MOZ_ASSERT(aStops.Length() > 0);
479 
480   // If all stops are outside the range, then get rid of everything and replace
481   // with a single colour.
482   if (aStops.Length() < 2 || aStops[0].mPosition > 1 ||
483       aStops.LastElement().mPosition < 0) {
484     sRGBColor c = aStops[0].mPosition > 1 ? aStops[0].mColor
485                                           : aStops.LastElement().mColor;
486     aStops.Clear();
487     aStops.AppendElement(ColorStop(0, false, c));
488     return;
489   }
490 
491   // Create the 0 and 1 points if they fall in the range of |aStops|, and
492   // discard all stops outside the range [0, 1].
493   // XXX: If we have stops positioned at 0 or 1, we only keep the innermost of
494   // those stops. This should be fine for the current user(s) of this function.
495   for (size_t i = aStops.Length() - 1; i > 0; i--) {
496     if (aStops[i - 1].mPosition < 1 && aStops[i].mPosition >= 1) {
497       // Add a point to position 1.
498       aStops[i] =
499           InterpolateColorStop(aStops[i - 1], aStops[i],
500                                /* aPosition = */ 1, aStops[i - 1].mColor);
501       // Remove all the elements whose position is greater than 1.
502       aStops.RemoveLastElements(aStops.Length() - (i + 1));
503     }
504     if (aStops[i - 1].mPosition <= 0 && aStops[i].mPosition > 0) {
505       // Add a point to position 0.
506       aStops[i - 1] =
507           InterpolateColorStop(aStops[i - 1], aStops[i],
508                                /* aPosition = */ 0, aStops[i].mColor);
509       // Remove all of the preceding stops -- they are all negative.
510       aStops.RemoveElementsAt(0, i - 1);
511       break;
512     }
513   }
514 
515   MOZ_ASSERT(aStops[0].mPosition >= -1e6);
516   MOZ_ASSERT(aStops.LastElement().mPosition - 1 <= 1e6);
517 
518   // The end points won't exist yet if they don't fall in the original range of
519   // |aStops|. Create them if needed.
520   if (aStops[0].mPosition > 0) {
521     aStops.InsertElementAt(0, ColorStop(0, false, aStops[0].mColor));
522   }
523   if (aStops.LastElement().mPosition < 1) {
524     aStops.AppendElement(ColorStop(1, false, aStops.LastElement().mColor));
525   }
526 }
527 
528 namespace mozilla {
529 
530 template <typename T>
GetSpecifiedColor(const StyleGenericGradientItem<StyleColor,T> & aItem,const ComputedStyle & aStyle)531 static sRGBColor GetSpecifiedColor(
532     const StyleGenericGradientItem<StyleColor, T>& aItem,
533     const ComputedStyle& aStyle) {
534   if (aItem.IsInterpolationHint()) {
535     return sRGBColor();
536   }
537   const StyleColor& color = aItem.IsSimpleColorStop()
538                                 ? aItem.AsSimpleColorStop()
539                                 : aItem.AsComplexColorStop().color;
540   return sRGBColor::FromABGR(color.CalcColor(aStyle));
541 }
542 
GetSpecifiedGradientPosition(const StyleGenericGradientItem<StyleColor,StyleLengthPercentage> & aItem,CSSCoord aLineLength)543 static Maybe<double> GetSpecifiedGradientPosition(
544     const StyleGenericGradientItem<StyleColor, StyleLengthPercentage>& aItem,
545     CSSCoord aLineLength) {
546   if (aItem.IsSimpleColorStop()) {
547     return Nothing();
548   }
549 
550   const LengthPercentage& pos = aItem.IsComplexColorStop()
551                                     ? aItem.AsComplexColorStop().position
552                                     : aItem.AsInterpolationHint();
553 
554   if (pos.ConvertsToPercentage()) {
555     return Some(pos.ToPercentage());
556   }
557 
558   if (aLineLength < 1e-6) {
559     return Some(0.0);
560   }
561   return Some(pos.ResolveToCSSPixels(aLineLength) / aLineLength);
562 }
563 
564 // aLineLength argument is unused for conic-gradients.
GetSpecifiedGradientPosition(const StyleGenericGradientItem<StyleColor,StyleAngleOrPercentage> & aItem,CSSCoord aLineLength)565 static Maybe<double> GetSpecifiedGradientPosition(
566     const StyleGenericGradientItem<StyleColor, StyleAngleOrPercentage>& aItem,
567     CSSCoord aLineLength) {
568   if (aItem.IsSimpleColorStop()) {
569     return Nothing();
570   }
571 
572   const StyleAngleOrPercentage& pos = aItem.IsComplexColorStop()
573                                           ? aItem.AsComplexColorStop().position
574                                           : aItem.AsInterpolationHint();
575 
576   if (pos.IsPercentage()) {
577     return Some(pos.AsPercentage()._0);
578   }
579 
580   return Some(pos.AsAngle().ToRadians() / (2 * M_PI));
581 }
582 
583 template <typename T>
ComputeColorStopsForItems(ComputedStyle * aComputedStyle,Span<const StyleGenericGradientItem<StyleColor,T>> aItems,CSSCoord aLineLength)584 static nsTArray<ColorStop> ComputeColorStopsForItems(
585     ComputedStyle* aComputedStyle,
586     Span<const StyleGenericGradientItem<StyleColor, T>> aItems,
587     CSSCoord aLineLength) {
588   MOZ_ASSERT(aItems.Length() >= 2,
589              "The parser should reject gradients with less than two stops");
590 
591   nsTArray<ColorStop> stops(aItems.Length());
592 
593   // If there is a run of stops before stop i that did not have specified
594   // positions, then this is the index of the first stop in that run.
595   Maybe<size_t> firstUnsetPosition;
596   for (size_t i = 0; i < aItems.Length(); ++i) {
597     const auto& stop = aItems[i];
598     double position;
599 
600     Maybe<double> specifiedPosition =
601         GetSpecifiedGradientPosition(stop, aLineLength);
602 
603     if (specifiedPosition) {
604       position = *specifiedPosition;
605     } else if (i == 0) {
606       // First stop defaults to position 0.0
607       position = 0.0;
608     } else if (i == aItems.Length() - 1) {
609       // Last stop defaults to position 1.0
610       position = 1.0;
611     } else {
612       // Other stops with no specified position get their position assigned
613       // later by interpolation, see below.
614       // Remember where the run of stops with no specified position starts,
615       // if it starts here.
616       if (firstUnsetPosition.isNothing()) {
617         firstUnsetPosition.emplace(i);
618       }
619       MOZ_ASSERT(!stop.IsInterpolationHint(),
620                  "Interpolation hints always specify position");
621       auto color = GetSpecifiedColor(stop, *aComputedStyle);
622       stops.AppendElement(ColorStop(0, false, color));
623       continue;
624     }
625 
626     if (i > 0) {
627       // Prevent decreasing stop positions by advancing this position
628       // to the previous stop position, if necessary
629       double previousPosition = firstUnsetPosition
630                                     ? stops[*firstUnsetPosition - 1].mPosition
631                                     : stops[i - 1].mPosition;
632       position = std::max(position, previousPosition);
633     }
634     auto stopColor = GetSpecifiedColor(stop, *aComputedStyle);
635     stops.AppendElement(
636         ColorStop(position, stop.IsInterpolationHint(), stopColor));
637     if (firstUnsetPosition) {
638       // Interpolate positions for all stops that didn't have a specified
639       // position
640       double p = stops[*firstUnsetPosition - 1].mPosition;
641       double d = (stops[i].mPosition - p) / (i - *firstUnsetPosition + 1);
642       for (size_t j = *firstUnsetPosition; j < i; ++j) {
643         p += d;
644         stops[j].mPosition = p;
645       }
646       firstUnsetPosition.reset();
647     }
648   }
649 
650   return stops;
651 }
652 
ComputeColorStops(ComputedStyle * aComputedStyle,const StyleGradient & aGradient,CSSCoord aLineLength)653 static nsTArray<ColorStop> ComputeColorStops(ComputedStyle* aComputedStyle,
654                                              const StyleGradient& aGradient,
655                                              CSSCoord aLineLength) {
656   if (aGradient.IsLinear()) {
657     return ComputeColorStopsForItems(
658         aComputedStyle, aGradient.AsLinear().items.AsSpan(), aLineLength);
659   }
660   if (aGradient.IsRadial()) {
661     return ComputeColorStopsForItems(
662         aComputedStyle, aGradient.AsRadial().items.AsSpan(), aLineLength);
663   }
664   return ComputeColorStopsForItems(
665       aComputedStyle, aGradient.AsConic().items.AsSpan(), aLineLength);
666 }
667 
Create(nsPresContext * aPresContext,ComputedStyle * aComputedStyle,const StyleGradient & aGradient,const nsSize & aIntrinsicSize)668 nsCSSGradientRenderer nsCSSGradientRenderer::Create(
669     nsPresContext* aPresContext, ComputedStyle* aComputedStyle,
670     const StyleGradient& aGradient, const nsSize& aIntrinsicSize) {
671   auto srcSize = CSSSize::FromAppUnits(aIntrinsicSize);
672 
673   // Compute "gradient line" start and end relative to the intrinsic size of
674   // the gradient.
675   CSSPoint lineStart, lineEnd, center;  // center is for conic gradients only
676   CSSCoord radiusX = 0, radiusY = 0;    // for radial gradients only
677   float angle = 0.0;                    // for conic gradients only
678   if (aGradient.IsLinear()) {
679     std::tie(lineStart, lineEnd) =
680         ComputeLinearGradientLine(aPresContext, aGradient, srcSize);
681   } else if (aGradient.IsRadial()) {
682     std::tie(lineStart, lineEnd, radiusX, radiusY) =
683         ComputeRadialGradientLine(aGradient, srcSize);
684   } else {
685     MOZ_ASSERT(aGradient.IsConic());
686     std::tie(center, angle) =
687         ComputeConicGradientProperties(aGradient, srcSize);
688   }
689   // Avoid sending Infs or Nans to downwind draw targets.
690   if (!lineStart.IsFinite() || !lineEnd.IsFinite()) {
691     lineStart = lineEnd = CSSPoint(0, 0);
692   }
693   if (!center.IsFinite()) {
694     center = CSSPoint(0, 0);
695   }
696   CSSCoord lineLength =
697       NS_hypot(lineEnd.x - lineStart.x, lineEnd.y - lineStart.y);
698 
699   // Build color stop array and compute stop positions
700   nsTArray<ColorStop> stops =
701       ComputeColorStops(aComputedStyle, aGradient, lineLength);
702 
703   ResolveMidpoints(stops);
704 
705   nsCSSGradientRenderer renderer;
706   renderer.mPresContext = aPresContext;
707   renderer.mGradient = &aGradient;
708   renderer.mStops = std::move(stops);
709   renderer.mLineStart = {
710       aPresContext->CSSPixelsToDevPixels(lineStart.x),
711       aPresContext->CSSPixelsToDevPixels(lineStart.y),
712   };
713   renderer.mLineEnd = {
714       aPresContext->CSSPixelsToDevPixels(lineEnd.x),
715       aPresContext->CSSPixelsToDevPixels(lineEnd.y),
716   };
717   renderer.mRadiusX = aPresContext->CSSPixelsToDevPixels(radiusX);
718   renderer.mRadiusY = aPresContext->CSSPixelsToDevPixels(radiusY);
719   renderer.mCenter = {
720       aPresContext->CSSPixelsToDevPixels(center.x),
721       aPresContext->CSSPixelsToDevPixels(center.y),
722   };
723   renderer.mAngle = angle;
724   return renderer;
725 }
726 
Paint(gfxContext & aContext,const nsRect & aDest,const nsRect & aFillArea,const nsSize & aRepeatSize,const CSSIntRect & aSrc,const nsRect & aDirtyRect,float aOpacity)727 void nsCSSGradientRenderer::Paint(gfxContext& aContext, const nsRect& aDest,
728                                   const nsRect& aFillArea,
729                                   const nsSize& aRepeatSize,
730                                   const CSSIntRect& aSrc,
731                                   const nsRect& aDirtyRect, float aOpacity) {
732   AUTO_PROFILER_LABEL("nsCSSGradientRenderer::Paint", GRAPHICS);
733 
734   if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
735     return;
736   }
737 
738   nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel();
739 
740   gfxFloat lineLength =
741       NS_hypot(mLineEnd.x - mLineStart.x, mLineEnd.y - mLineStart.y);
742   bool cellContainsFill = aDest.Contains(aFillArea);
743 
744   // If a non-repeating linear gradient is axis-aligned and there are no gaps
745   // between tiles, we can optimise away most of the work by converting to a
746   // repeating linear gradient and filling the whole destination rect at once.
747   bool forceRepeatToCoverTiles =
748       mGradient->IsLinear() &&
749       (mLineStart.x == mLineEnd.x) != (mLineStart.y == mLineEnd.y) &&
750       aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height &&
751       !mGradient->AsLinear().repeating && !aSrc.IsEmpty() && !cellContainsFill;
752 
753   gfxMatrix matrix;
754   if (forceRepeatToCoverTiles) {
755     // Length of the source rectangle along the gradient axis.
756     double rectLen;
757     // The position of the start of the rectangle along the gradient.
758     double offset;
759 
760     // The gradient line is "backwards". Flip the line upside down to make
761     // things easier, and then rotate the matrix to turn everything back the
762     // right way up.
763     if (mLineStart.x > mLineEnd.x || mLineStart.y > mLineEnd.y) {
764       std::swap(mLineStart, mLineEnd);
765       matrix.PreScale(-1, -1);
766     }
767 
768     // Fit the gradient line exactly into the source rect.
769     // aSrc is relative to aIntrinsincSize.
770     // srcRectDev will be relative to srcSize, so in the same coordinate space
771     // as lineStart / lineEnd.
772     gfxRect srcRectDev = nsLayoutUtils::RectToGfxRect(
773         CSSPixel::ToAppUnits(aSrc), appUnitsPerDevPixel);
774     if (mLineStart.x != mLineEnd.x) {
775       rectLen = srcRectDev.width;
776       offset = (srcRectDev.x - mLineStart.x) / lineLength;
777       mLineStart.x = srcRectDev.x;
778       mLineEnd.x = srcRectDev.XMost();
779     } else {
780       rectLen = srcRectDev.height;
781       offset = (srcRectDev.y - mLineStart.y) / lineLength;
782       mLineStart.y = srcRectDev.y;
783       mLineEnd.y = srcRectDev.YMost();
784     }
785 
786     // Adjust gradient stop positions for the new gradient line.
787     double scale = lineLength / rectLen;
788     for (size_t i = 0; i < mStops.Length(); i++) {
789       mStops[i].mPosition = (mStops[i].mPosition - offset) * fabs(scale);
790     }
791 
792     // Clamp or extrapolate gradient stops to exactly [0, 1].
793     ClampColorStops(mStops);
794 
795     lineLength = rectLen;
796   }
797 
798   // Eliminate negative-position stops if the gradient is radial.
799   double firstStop = mStops[0].mPosition;
800   if (mGradient->IsRadial() && firstStop < 0.0) {
801     if (mGradient->AsRadial().repeating) {
802       // Choose an instance of the repeated pattern that gives us all positive
803       // stop-offsets.
804       double lastStop = mStops[mStops.Length() - 1].mPosition;
805       double stopDelta = lastStop - firstStop;
806       // If all the stops are in approximately the same place then logic below
807       // will kick in that makes us draw just the last stop color, so don't
808       // try to do anything in that case. We certainly need to avoid
809       // dividing by zero.
810       if (stopDelta >= 1e-6) {
811         double instanceCount = ceil(-firstStop / stopDelta);
812         // Advance stops by instanceCount multiples of the period of the
813         // repeating gradient.
814         double offset = instanceCount * stopDelta;
815         for (uint32_t i = 0; i < mStops.Length(); i++) {
816           mStops[i].mPosition += offset;
817         }
818       }
819     } else {
820       // Move negative-position stops to position 0.0. We may also need
821       // to set the color of the stop to the color the gradient should have
822       // at the center of the ellipse.
823       for (uint32_t i = 0; i < mStops.Length(); i++) {
824         double pos = mStops[i].mPosition;
825         if (pos < 0.0) {
826           mStops[i].mPosition = 0.0;
827           // If this is the last stop, we don't need to adjust the color,
828           // it will fill the entire area.
829           if (i < mStops.Length() - 1) {
830             double nextPos = mStops[i + 1].mPosition;
831             // If nextPos is approximately equal to pos, then we don't
832             // need to adjust the color of this stop because it's
833             // not going to be displayed.
834             // If nextPos is negative, we don't need to adjust the color of
835             // this stop since it's not going to be displayed because
836             // nextPos will also be moved to 0.0.
837             if (nextPos >= 0.0 && nextPos - pos >= 1e-6) {
838               // Compute how far the new position 0.0 is along the interval
839               // between pos and nextPos.
840               // XXX Color interpolation (in cairo, too) should use the
841               // CSS 'color-interpolation' property!
842               float frac = float((0.0 - pos) / (nextPos - pos));
843               mStops[i].mColor = sRGBColor::InterpolatePremultiplied(
844                   mStops[i].mColor, mStops[i + 1].mColor, frac);
845             }
846           }
847         }
848       }
849     }
850     firstStop = mStops[0].mPosition;
851     MOZ_ASSERT(firstStop >= 0.0, "Failed to fix stop offsets");
852   }
853 
854   if (mGradient->IsRadial() && !mGradient->AsRadial().repeating) {
855     // Direct2D can only handle a particular class of radial gradients because
856     // of the way the it specifies gradients. Setting firstStop to 0, when we
857     // can, will help us stay on the fast path. Currently we don't do this
858     // for repeating gradients but we could by adjusting the stop collection
859     // to start at 0
860     firstStop = 0;
861   }
862 
863   double lastStop = mStops[mStops.Length() - 1].mPosition;
864   // Cairo gradients must have stop positions in the range [0, 1]. So,
865   // stop positions will be normalized below by subtracting firstStop and then
866   // multiplying by stopScale.
867   double stopScale;
868   double stopOrigin = firstStop;
869   double stopEnd = lastStop;
870   double stopDelta = lastStop - firstStop;
871   bool zeroRadius =
872       mGradient->IsRadial() && (mRadiusX < 1e-6 || mRadiusY < 1e-6);
873   if (stopDelta < 1e-6 || (!mGradient->IsConic() && lineLength < 1e-6) ||
874       zeroRadius) {
875     // Stops are all at the same place. Map all stops to 0.0.
876     // For repeating radial gradients, or for any radial gradients with
877     // a zero radius, we need to fill with the last stop color, so just set
878     // both radii to 0.
879     if (mGradient->Repeating() || zeroRadius) {
880       mRadiusX = mRadiusY = 0.0;
881     }
882     stopDelta = 0.0;
883   }
884 
885   // Don't normalize non-repeating or degenerate gradients below 0..1
886   // This keeps the gradient line as large as the box and doesn't
887   // lets us avoiding having to get padding correct for stops
888   // at 0 and 1
889   if (!mGradient->Repeating() || stopDelta == 0.0) {
890     stopOrigin = std::min(stopOrigin, 0.0);
891     stopEnd = std::max(stopEnd, 1.0);
892   }
893   stopScale = 1.0 / (stopEnd - stopOrigin);
894 
895   // Create the gradient pattern.
896   RefPtr<gfxPattern> gradientPattern;
897   gfxPoint gradientStart;
898   gfxPoint gradientEnd;
899   if (mGradient->IsLinear()) {
900     // Compute the actual gradient line ends we need to pass to cairo after
901     // stops have been normalized.
902     gradientStart = mLineStart + (mLineEnd - mLineStart) * stopOrigin;
903     gradientEnd = mLineStart + (mLineEnd - mLineStart) * stopEnd;
904 
905     if (stopDelta == 0.0) {
906       // Stops are all at the same place. For repeating gradients, this will
907       // just paint the last stop color. We don't need to do anything.
908       // For non-repeating gradients, this should render as two colors, one
909       // on each "side" of the gradient line segment, which is a point. All
910       // our stops will be at 0.0; we just need to set the direction vector
911       // correctly.
912       gradientEnd = gradientStart + (mLineEnd - mLineStart);
913     }
914 
915     gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y,
916                                      gradientEnd.x, gradientEnd.y);
917   } else if (mGradient->IsRadial()) {
918     NS_ASSERTION(firstStop >= 0.0,
919                  "Negative stops not allowed for radial gradients");
920 
921     // To form an ellipse, we'll stretch a circle vertically, if necessary.
922     // So our radii are based on radiusX.
923     double innerRadius = mRadiusX * stopOrigin;
924     double outerRadius = mRadiusX * stopEnd;
925     if (stopDelta == 0.0) {
926       // Stops are all at the same place.  See above (except we now have
927       // the inside vs. outside of an ellipse).
928       outerRadius = innerRadius + 1;
929     }
930     gradientPattern = new gfxPattern(mLineStart.x, mLineStart.y, innerRadius,
931                                      mLineStart.x, mLineStart.y, outerRadius);
932     if (mRadiusX != mRadiusY) {
933       // Stretch the circles into ellipses vertically by setting a transform
934       // in the pattern.
935       // Recall that this is the transform from user space to pattern space.
936       // So to stretch the ellipse by factor of P vertically, we scale
937       // user coordinates by 1/P.
938       matrix.PreTranslate(mLineStart);
939       matrix.PreScale(1.0, mRadiusX / mRadiusY);
940       matrix.PreTranslate(-mLineStart);
941     }
942   } else {
943     gradientPattern =
944         new gfxPattern(mCenter.x, mCenter.y, mAngle, stopOrigin, stopEnd);
945   }
946   // Use a pattern transform to take account of source and dest rects
947   matrix.PreTranslate(gfxPoint(mPresContext->CSSPixelsToDevPixels(aSrc.x),
948                                mPresContext->CSSPixelsToDevPixels(aSrc.y)));
949   matrix.PreScale(
950       gfxFloat(nsPresContext::CSSPixelsToAppUnits(aSrc.width)) / aDest.width,
951       gfxFloat(nsPresContext::CSSPixelsToAppUnits(aSrc.height)) / aDest.height);
952   gradientPattern->SetMatrix(matrix);
953 
954   if (stopDelta == 0.0) {
955     // Non-repeating gradient with all stops in same place -> just add
956     // first stop and last stop, both at position 0.
957     // Repeating gradient with all stops in the same place, or radial
958     // gradient with radius of 0 -> just paint the last stop color.
959     // We use firstStop offset to keep |stops| with same units (will later
960     // normalize to 0).
961     sRGBColor firstColor(mStops[0].mColor);
962     sRGBColor lastColor(mStops.LastElement().mColor);
963     mStops.Clear();
964 
965     if (!mGradient->Repeating() && !zeroRadius) {
966       mStops.AppendElement(ColorStop(firstStop, false, firstColor));
967     }
968     mStops.AppendElement(ColorStop(firstStop, false, lastColor));
969   }
970 
971   ResolvePremultipliedAlpha(mStops);
972 
973   bool isRepeat = mGradient->Repeating() || forceRepeatToCoverTiles;
974 
975   // Now set normalized color stops in pattern.
976   // Offscreen gradient surface cache (not a tile):
977   // On some backends (e.g. D2D), the GradientStops object holds an offscreen
978   // surface which is a lookup table used to evaluate the gradient. This surface
979   // can use much memory (ram and/or GPU ram) and can be expensive to create. So
980   // we cache it. The cache key correlates 1:1 with the arguments for
981   // CreateGradientStops (also the implied backend type) Note that GradientStop
982   // is a simple struct with a stop value (while GradientStops has the surface).
983   nsTArray<gfx::GradientStop> rawStops(mStops.Length());
984   rawStops.SetLength(mStops.Length());
985   for (uint32_t i = 0; i < mStops.Length(); i++) {
986     rawStops[i].color = ToDeviceColor(mStops[i].mColor);
987     rawStops[i].color.a *= aOpacity;
988     rawStops[i].offset = stopScale * (mStops[i].mPosition - stopOrigin);
989   }
990   RefPtr<mozilla::gfx::GradientStops> gs =
991       gfxGradientCache::GetOrCreateGradientStops(
992           aContext.GetDrawTarget(), rawStops,
993           isRepeat ? gfx::ExtendMode::REPEAT : gfx::ExtendMode::CLAMP);
994   gradientPattern->SetColorStops(gs);
995 
996   // Paint gradient tiles. This isn't terribly efficient, but doing it this
997   // way is simple and sure to get pixel-snapping right. We could speed things
998   // up by drawing tiles into temporary surfaces and copying those to the
999   // destination, but after pixel-snapping tiles may not all be the same size.
1000   nsRect dirty;
1001   if (!dirty.IntersectRect(aDirtyRect, aFillArea)) return;
1002 
1003   gfxRect areaToFill =
1004       nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel);
1005   gfxRect dirtyAreaToFill =
1006       nsLayoutUtils::RectToGfxRect(dirty, appUnitsPerDevPixel);
1007   dirtyAreaToFill.RoundOut();
1008 
1009   Matrix ctm = aContext.CurrentMatrix();
1010   bool isCTMPreservingAxisAlignedRectangles =
1011       ctm.PreservesAxisAlignedRectangles();
1012 
1013   // xStart/yStart are the top-left corner of the top-left tile.
1014   nscoord xStart = FindTileStart(dirty.x, aDest.x, aRepeatSize.width);
1015   nscoord yStart = FindTileStart(dirty.y, aDest.y, aRepeatSize.height);
1016   nscoord xEnd = forceRepeatToCoverTiles ? xStart + aDest.width : dirty.XMost();
1017   nscoord yEnd =
1018       forceRepeatToCoverTiles ? yStart + aDest.height : dirty.YMost();
1019 
1020   if (TryPaintTilesWithExtendMode(aContext, gradientPattern, xStart, yStart,
1021                                   dirtyAreaToFill, aDest, aRepeatSize,
1022                                   forceRepeatToCoverTiles)) {
1023     return;
1024   }
1025 
1026   // x and y are the top-left corner of the tile to draw
1027   for (nscoord y = yStart; y < yEnd; y += aRepeatSize.height) {
1028     for (nscoord x = xStart; x < xEnd; x += aRepeatSize.width) {
1029       // The coordinates of the tile
1030       gfxRect tileRect = nsLayoutUtils::RectToGfxRect(
1031           nsRect(x, y, aDest.width, aDest.height), appUnitsPerDevPixel);
1032       // The actual area to fill with this tile is the intersection of this
1033       // tile with the overall area we're supposed to be filling
1034       gfxRect fillRect =
1035           forceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill);
1036       // Try snapping the fill rect. Snap its top-left and bottom-right
1037       // independently to preserve the orientation.
1038       gfxPoint snappedFillRectTopLeft = fillRect.TopLeft();
1039       gfxPoint snappedFillRectTopRight = fillRect.TopRight();
1040       gfxPoint snappedFillRectBottomRight = fillRect.BottomRight();
1041       // Snap three points instead of just two to ensure we choose the
1042       // correct orientation if there's a reflection.
1043       if (isCTMPreservingAxisAlignedRectangles &&
1044           aContext.UserToDevicePixelSnapped(snappedFillRectTopLeft, true) &&
1045           aContext.UserToDevicePixelSnapped(snappedFillRectBottomRight, true) &&
1046           aContext.UserToDevicePixelSnapped(snappedFillRectTopRight, true)) {
1047         if (snappedFillRectTopLeft.x == snappedFillRectBottomRight.x ||
1048             snappedFillRectTopLeft.y == snappedFillRectBottomRight.y) {
1049           // Nothing to draw; avoid scaling by zero and other weirdness that
1050           // could put the context in an error state.
1051           continue;
1052         }
1053         // Set the context's transform to the transform that maps fillRect to
1054         // snappedFillRect. The part of the gradient that was going to
1055         // exactly fill fillRect will fill snappedFillRect instead.
1056         gfxMatrix transform = gfxUtils::TransformRectToRect(
1057             fillRect, snappedFillRectTopLeft, snappedFillRectTopRight,
1058             snappedFillRectBottomRight);
1059         aContext.SetMatrixDouble(transform);
1060       }
1061       aContext.NewPath();
1062       aContext.Rectangle(fillRect);
1063 
1064       gfxRect dirtyFillRect = fillRect.Intersect(dirtyAreaToFill);
1065       gfxRect fillRectRelativeToTile = dirtyFillRect - tileRect.TopLeft();
1066       sRGBColor edgeColor;
1067       if (mGradient->IsLinear() && !isRepeat &&
1068           RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, mStops,
1069                                          gradientStart, gradientEnd,
1070                                          &edgeColor)) {
1071         edgeColor.a *= aOpacity;
1072         aContext.SetColor(edgeColor);
1073       } else {
1074         aContext.SetMatrixDouble(
1075             aContext.CurrentMatrixDouble().Copy().PreTranslate(
1076                 tileRect.TopLeft()));
1077         aContext.SetPattern(gradientPattern);
1078       }
1079       aContext.Fill();
1080       aContext.SetMatrix(ctm);
1081     }
1082   }
1083 }
1084 
TryPaintTilesWithExtendMode(gfxContext & aContext,gfxPattern * aGradientPattern,nscoord aXStart,nscoord aYStart,const gfxRect & aDirtyAreaToFill,const nsRect & aDest,const nsSize & aRepeatSize,bool aForceRepeatToCoverTiles)1085 bool nsCSSGradientRenderer::TryPaintTilesWithExtendMode(
1086     gfxContext& aContext, gfxPattern* aGradientPattern, nscoord aXStart,
1087     nscoord aYStart, const gfxRect& aDirtyAreaToFill, const nsRect& aDest,
1088     const nsSize& aRepeatSize, bool aForceRepeatToCoverTiles) {
1089   // If we have forced a non-repeating gradient to repeat to cover tiles,
1090   // then it will be faster to just paint it once using that optimization
1091   if (aForceRepeatToCoverTiles) {
1092     return false;
1093   }
1094 
1095   nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel();
1096 
1097   // We can only use this fast path if we don't have to worry about pixel
1098   // snapping, and there is no spacing between tiles. We could handle spacing
1099   // by increasing the size of tileSurface and leaving it transparent, but I'm
1100   // not sure it's worth it
1101   bool canUseExtendModeForTiling = (aXStart % appUnitsPerDevPixel == 0) &&
1102                                    (aYStart % appUnitsPerDevPixel == 0) &&
1103                                    (aDest.width % appUnitsPerDevPixel == 0) &&
1104                                    (aDest.height % appUnitsPerDevPixel == 0) &&
1105                                    (aRepeatSize.width == aDest.width) &&
1106                                    (aRepeatSize.height == aDest.height);
1107 
1108   if (!canUseExtendModeForTiling) {
1109     return false;
1110   }
1111 
1112   IntSize tileSize{
1113       NSAppUnitsToIntPixels(aDest.width, appUnitsPerDevPixel),
1114       NSAppUnitsToIntPixels(aDest.height, appUnitsPerDevPixel),
1115   };
1116 
1117   // Check whether this is a reasonable surface size and doesn't overflow
1118   // before doing calculations with the tile size
1119   if (!Factory::ReasonableSurfaceSize(tileSize)) {
1120     return false;
1121   }
1122 
1123   // We only want to do this when there are enough tiles to justify the
1124   // overhead of painting to an offscreen surface. The heuristic here
1125   // is when we will be painting at least 16 tiles or more, this is kind
1126   // of arbitrary
1127   bool shouldUseExtendModeForTiling =
1128       aDirtyAreaToFill.Area() > (tileSize.width * tileSize.height) * 16.0;
1129 
1130   if (!shouldUseExtendModeForTiling) {
1131     return false;
1132   }
1133 
1134   // Draw the gradient pattern into a surface for our single tile
1135   RefPtr<gfx::SourceSurface> tileSurface;
1136   {
1137     RefPtr<gfx::DrawTarget> tileTarget =
1138         aContext.GetDrawTarget()->CreateSimilarDrawTarget(
1139             tileSize, gfx::SurfaceFormat::B8G8R8A8);
1140     if (!tileTarget || !tileTarget->IsValid()) {
1141       return false;
1142     }
1143 
1144     RefPtr<gfxContext> tileContext = gfxContext::CreateOrNull(tileTarget);
1145 
1146     tileContext->SetPattern(aGradientPattern);
1147     tileContext->Paint();
1148 
1149     tileContext = nullptr;
1150     tileSurface = tileTarget->Snapshot();
1151     tileTarget = nullptr;
1152   }
1153 
1154   // Draw the gradient using tileSurface as a repeating pattern masked by
1155   // the dirtyRect
1156   Matrix tileTransform = Matrix::Translation(
1157       NSAppUnitsToFloatPixels(aXStart, appUnitsPerDevPixel),
1158       NSAppUnitsToFloatPixels(aYStart, appUnitsPerDevPixel));
1159 
1160   aContext.NewPath();
1161   aContext.Rectangle(aDirtyAreaToFill);
1162   aContext.Fill(SurfacePattern(tileSurface, ExtendMode::REPEAT, tileTransform));
1163 
1164   return true;
1165 }
1166 
BuildWebRenderParameters(float aOpacity,wr::ExtendMode & aMode,nsTArray<wr::GradientStop> & aStops,LayoutDevicePoint & aLineStart,LayoutDevicePoint & aLineEnd,LayoutDeviceSize & aGradientRadius,LayoutDevicePoint & aGradientCenter,float & aGradientAngle)1167 void nsCSSGradientRenderer::BuildWebRenderParameters(
1168     float aOpacity, wr::ExtendMode& aMode, nsTArray<wr::GradientStop>& aStops,
1169     LayoutDevicePoint& aLineStart, LayoutDevicePoint& aLineEnd,
1170     LayoutDeviceSize& aGradientRadius, LayoutDevicePoint& aGradientCenter,
1171     float& aGradientAngle) {
1172   aMode =
1173       mGradient->Repeating() ? wr::ExtendMode::Repeat : wr::ExtendMode::Clamp;
1174 
1175   aStops.SetLength(mStops.Length());
1176   for (uint32_t i = 0; i < mStops.Length(); i++) {
1177     aStops[i].color = wr::ToColorF(ToDeviceColor(mStops[i].mColor));
1178     aStops[i].color.a *= aOpacity;
1179     aStops[i].offset = mStops[i].mPosition;
1180   }
1181 
1182   aLineStart = LayoutDevicePoint(mLineStart.x, mLineStart.y);
1183   aLineEnd = LayoutDevicePoint(mLineEnd.x, mLineEnd.y);
1184   aGradientRadius = LayoutDeviceSize(mRadiusX, mRadiusY);
1185   aGradientCenter = LayoutDevicePoint(mCenter.x, mCenter.y);
1186   aGradientAngle = mAngle;
1187 }
1188 
BuildWebRenderDisplayItems(wr::DisplayListBuilder & aBuilder,const layers::StackingContextHelper & aSc,const nsRect & aDest,const nsRect & aFillArea,const nsSize & aRepeatSize,const CSSIntRect & aSrc,bool aIsBackfaceVisible,float aOpacity)1189 void nsCSSGradientRenderer::BuildWebRenderDisplayItems(
1190     wr::DisplayListBuilder& aBuilder, const layers::StackingContextHelper& aSc,
1191     const nsRect& aDest, const nsRect& aFillArea, const nsSize& aRepeatSize,
1192     const CSSIntRect& aSrc, bool aIsBackfaceVisible, float aOpacity) {
1193   if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
1194     return;
1195   }
1196 
1197   wr::ExtendMode extendMode;
1198   nsTArray<wr::GradientStop> stops;
1199   LayoutDevicePoint lineStart;
1200   LayoutDevicePoint lineEnd;
1201   LayoutDeviceSize gradientRadius;
1202   LayoutDevicePoint gradientCenter;
1203   float gradientAngle;
1204   BuildWebRenderParameters(aOpacity, extendMode, stops, lineStart, lineEnd,
1205                            gradientRadius, gradientCenter, gradientAngle);
1206 
1207   nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel();
1208 
1209   nsPoint firstTile =
1210       nsPoint(FindTileStart(aFillArea.x, aDest.x, aRepeatSize.width),
1211               FindTileStart(aFillArea.y, aDest.y, aRepeatSize.height));
1212 
1213   // Translate the parameters into device coordinates
1214   LayoutDeviceRect clipBounds =
1215       LayoutDevicePixel::FromAppUnits(aFillArea, appUnitsPerDevPixel);
1216   LayoutDeviceRect firstTileBounds = LayoutDevicePixel::FromAppUnits(
1217       nsRect(firstTile, aDest.Size()), appUnitsPerDevPixel);
1218   LayoutDeviceSize tileRepeat =
1219       LayoutDevicePixel::FromAppUnits(aRepeatSize, appUnitsPerDevPixel);
1220 
1221   // Calculate the bounds of the gradient display item, which starts at the
1222   // first tile and extends to the end of clip bounds
1223   LayoutDevicePoint tileToClip =
1224       clipBounds.BottomRight() - firstTileBounds.TopLeft();
1225   LayoutDeviceRect gradientBounds = LayoutDeviceRect(
1226       firstTileBounds.TopLeft(), LayoutDeviceSize(tileToClip.x, tileToClip.y));
1227 
1228   // Calculate the tile spacing, which is the repeat size minus the tile size
1229   LayoutDeviceSize tileSpacing = tileRepeat - firstTileBounds.Size();
1230 
1231   // srcTransform is used for scaling the gradient to match aSrc
1232   LayoutDeviceRect srcTransform = LayoutDeviceRect(
1233       nsPresContext::CSSPixelsToAppUnits(aSrc.x),
1234       nsPresContext::CSSPixelsToAppUnits(aSrc.y),
1235       aDest.width / ((float)nsPresContext::CSSPixelsToAppUnits(aSrc.width)),
1236       aDest.height / ((float)nsPresContext::CSSPixelsToAppUnits(aSrc.height)));
1237 
1238   lineStart.x = (lineStart.x - srcTransform.x) * srcTransform.width;
1239   lineStart.y = (lineStart.y - srcTransform.y) * srcTransform.height;
1240 
1241   gradientCenter.x = (gradientCenter.x - srcTransform.x) * srcTransform.width;
1242   gradientCenter.y = (gradientCenter.y - srcTransform.y) * srcTransform.height;
1243 
1244   if (mGradient->IsLinear()) {
1245     lineEnd.x = (lineEnd.x - srcTransform.x) * srcTransform.width;
1246     lineEnd.y = (lineEnd.y - srcTransform.y) * srcTransform.height;
1247 
1248     aBuilder.PushLinearGradient(
1249         mozilla::wr::ToLayoutRect(gradientBounds),
1250         mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible,
1251         mozilla::wr::ToLayoutPoint(lineStart),
1252         mozilla::wr::ToLayoutPoint(lineEnd), stops, extendMode,
1253         mozilla::wr::ToLayoutSize(firstTileBounds.Size()),
1254         mozilla::wr::ToLayoutSize(tileSpacing));
1255   } else if (mGradient->IsRadial()) {
1256     gradientRadius.width *= srcTransform.width;
1257     gradientRadius.height *= srcTransform.height;
1258 
1259     aBuilder.PushRadialGradient(
1260         mozilla::wr::ToLayoutRect(gradientBounds),
1261         mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible,
1262         mozilla::wr::ToLayoutPoint(lineStart),
1263         mozilla::wr::ToLayoutSize(gradientRadius), stops, extendMode,
1264         mozilla::wr::ToLayoutSize(firstTileBounds.Size()),
1265         mozilla::wr::ToLayoutSize(tileSpacing));
1266   } else {
1267     MOZ_ASSERT(mGradient->IsConic());
1268     aBuilder.PushConicGradient(
1269         mozilla::wr::ToLayoutRect(gradientBounds),
1270         mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible,
1271         mozilla::wr::ToLayoutPoint(gradientCenter), gradientAngle, stops,
1272         extendMode, mozilla::wr::ToLayoutSize(firstTileBounds.Size()),
1273         mozilla::wr::ToLayoutSize(tileSpacing));
1274   }
1275 }
1276 
1277 }  // namespace mozilla
1278