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