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