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