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