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