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