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 "nsLayoutUtils.h"
23 #include "nsStyleConsts.h"
24 #include "nsPresContext.h"
27 #include "nsCSSColorUtils.h"
28 #include "gfxContext.h"
29 #include "nsStyleStructInlines.h"
30 #include "nsCSSProps.h"
32 #include "gfxGradientCache.h"
34 #include "mozilla/layers/StackingContextHelper.h"
35 #include "mozilla/layers/WebRenderLayerManager.h"
36 #include "mozilla/webrender/WebRenderTypes.h"
37 #include "mozilla/webrender/WebRenderAPI.h"
40 using namespace mozilla
;
41 using namespace mozilla::gfx
;
43 static CSSPoint
ResolvePosition(const Position
& aPos
, const CSSSize
& aSize
) {
44 CSSCoord h
= aPos
.horizontal
.ResolveToCSSPixels(aSize
.width
);
45 CSSCoord v
= aPos
.vertical
.ResolveToCSSPixels(aSize
.height
);
46 return CSSPoint(h
, v
);
49 // Given a box with size aBoxSize and origin (0,0), and an angle aAngle,
50 // and a starting point for the gradient line aStart, find the endpoint of
51 // the gradient line --- the intersection of the gradient line with a line
52 // perpendicular to aAngle that passes through the farthest corner in the
54 static CSSPoint
ComputeGradientLineEndFromAngle(const CSSPoint
& aStart
,
56 const CSSSize
& aBoxSize
) {
57 double dx
= cos(-aAngle
);
58 double dy
= sin(-aAngle
);
59 CSSPoint
farthestCorner(dx
> 0 ? aBoxSize
.width
: 0,
60 dy
> 0 ? aBoxSize
.height
: 0);
61 CSSPoint delta
= farthestCorner
- aStart
;
62 double u
= delta
.x
* dy
- delta
.y
* dx
;
63 return farthestCorner
+ CSSPoint(-u
* dy
, u
* dx
);
66 // Compute the start and end points of the gradient line for a linear gradient.
67 static std::tuple
<CSSPoint
, CSSPoint
> ComputeLinearGradientLine(
68 nsPresContext
* aPresContext
, const StyleGradient
& aGradient
,
69 const CSSSize
& aBoxSize
) {
70 using X
= StyleHorizontalPositionKeyword
;
71 using Y
= StyleVerticalPositionKeyword
;
73 const StyleLineDirection
& direction
= aGradient
.AsLinear().direction
;
75 aGradient
.AsLinear().compat_mode
== StyleGradientCompatMode::Modern
;
77 CSSPoint
center(aBoxSize
.width
/ 2, aBoxSize
.height
/ 2);
78 switch (direction
.tag
) {
79 case StyleLineDirection::Tag::Angle
: {
80 double angle
= direction
.AsAngle().ToRadians();
82 angle
= M_PI_2
- angle
;
84 CSSPoint end
= ComputeGradientLineEndFromAngle(center
, angle
, aBoxSize
);
85 CSSPoint start
= CSSPoint(aBoxSize
.width
, aBoxSize
.height
) - end
;
88 case StyleLineDirection::Tag::Vertical
: {
89 CSSPoint
start(center
.x
, 0);
90 CSSPoint
end(center
.x
, aBoxSize
.height
);
91 if (isModern
== (direction
.AsVertical() == Y::Top
)) {
92 std::swap(start
.y
, end
.y
);
96 case StyleLineDirection::Tag::Horizontal
: {
97 CSSPoint
start(0, center
.y
);
98 CSSPoint
end(aBoxSize
.width
, center
.y
);
99 if (isModern
== (direction
.AsHorizontal() == X::Left
)) {
100 std::swap(start
.x
, end
.x
);
104 case StyleLineDirection::Tag::Corner
: {
105 const auto& corner
= direction
.AsCorner();
106 const X
& h
= corner
._0
;
107 const Y
& v
= corner
._1
;
110 float xSign
= h
== X::Right
? 1.0 : -1.0;
111 float ySign
= v
== Y::Top
? 1.0 : -1.0;
112 double angle
= atan2(ySign
* aBoxSize
.width
, xSign
* aBoxSize
.height
);
113 CSSPoint end
= ComputeGradientLineEndFromAngle(center
, angle
, aBoxSize
);
114 CSSPoint start
= CSSPoint(aBoxSize
.width
, aBoxSize
.height
) - end
;
118 CSSCoord startX
= h
== X::Left
? 0.0 : aBoxSize
.width
;
119 CSSCoord startY
= v
== Y::Top
? 0.0 : aBoxSize
.height
;
121 CSSPoint
start(startX
, startY
);
122 CSSPoint end
= CSSPoint(aBoxSize
.width
, aBoxSize
.height
) - start
;
128 MOZ_ASSERT_UNREACHABLE("Unknown line direction");
129 return {CSSPoint(), CSSPoint()};
132 using EndingShape
= StyleGenericEndingShape
<Length
, LengthPercentage
>;
133 using RadialGradientRadii
=
134 Variant
<StyleShapeExtent
, std::pair
<CSSCoord
, CSSCoord
>>;
136 static RadialGradientRadii
ComputeRadialGradientRadii(const EndingShape
& aShape
,
137 const CSSSize
& aSize
) {
138 if (aShape
.IsCircle()) {
139 auto& circle
= aShape
.AsCircle();
140 if (circle
.IsExtent()) {
141 return RadialGradientRadii(circle
.AsExtent());
143 CSSCoord radius
= circle
.AsRadius().ToCSSPixels();
144 return RadialGradientRadii(std::make_pair(radius
, radius
));
146 auto& ellipse
= aShape
.AsEllipse();
147 if (ellipse
.IsExtent()) {
148 return RadialGradientRadii(ellipse
.AsExtent());
151 auto& radii
= ellipse
.AsRadii();
152 return RadialGradientRadii(
153 std::make_pair(radii
._0
.ResolveToCSSPixels(aSize
.width
),
154 radii
._1
.ResolveToCSSPixels(aSize
.height
)));
157 // Compute the start and end points of the gradient line for a radial gradient.
158 // Also returns the horizontal and vertical radii defining the circle or
160 static std::tuple
<CSSPoint
, CSSPoint
, CSSCoord
, CSSCoord
>
161 ComputeRadialGradientLine(const StyleGradient
& aGradient
,
162 const CSSSize
& aBoxSize
) {
163 const auto& radial
= aGradient
.AsRadial();
164 const EndingShape
& endingShape
= radial
.shape
;
165 const Position
& position
= radial
.position
;
166 CSSPoint start
= ResolvePosition(position
, aBoxSize
);
168 // Compute gradient shape: the x and y radii of an ellipse.
169 CSSCoord radiusX
, radiusY
;
170 CSSCoord leftDistance
= Abs(start
.x
);
171 CSSCoord rightDistance
= Abs(aBoxSize
.width
- start
.x
);
172 CSSCoord topDistance
= Abs(start
.y
);
173 CSSCoord bottomDistance
= Abs(aBoxSize
.height
- start
.y
);
175 auto radii
= ComputeRadialGradientRadii(endingShape
, aBoxSize
);
176 if (radii
.is
<StyleShapeExtent
>()) {
177 switch (radii
.as
<StyleShapeExtent
>()) {
178 case StyleShapeExtent::ClosestSide
:
179 radiusX
= std::min(leftDistance
, rightDistance
);
180 radiusY
= std::min(topDistance
, bottomDistance
);
181 if (endingShape
.IsCircle()) {
182 radiusX
= radiusY
= std::min(radiusX
, radiusY
);
185 case StyleShapeExtent::ClosestCorner
: {
186 // Compute x and y distances to nearest corner
187 CSSCoord offsetX
= std::min(leftDistance
, rightDistance
);
188 CSSCoord offsetY
= std::min(topDistance
, bottomDistance
);
189 if (endingShape
.IsCircle()) {
190 radiusX
= radiusY
= NS_hypot(offsetX
, offsetY
);
192 // maintain aspect ratio
193 radiusX
= offsetX
* M_SQRT2
;
194 radiusY
= offsetY
* M_SQRT2
;
198 case StyleShapeExtent::FarthestSide
:
199 radiusX
= std::max(leftDistance
, rightDistance
);
200 radiusY
= std::max(topDistance
, bottomDistance
);
201 if (endingShape
.IsCircle()) {
202 radiusX
= radiusY
= std::max(radiusX
, radiusY
);
205 case StyleShapeExtent::FarthestCorner
: {
206 // Compute x and y distances to nearest corner
207 CSSCoord offsetX
= std::max(leftDistance
, rightDistance
);
208 CSSCoord offsetY
= std::max(topDistance
, bottomDistance
);
209 if (endingShape
.IsCircle()) {
210 radiusX
= radiusY
= NS_hypot(offsetX
, offsetY
);
212 // maintain aspect ratio
213 radiusX
= offsetX
* M_SQRT2
;
214 radiusY
= offsetY
* M_SQRT2
;
219 MOZ_ASSERT_UNREACHABLE("Unknown shape extent keyword?");
220 radiusX
= radiusY
= 0;
223 auto pair
= radii
.as
<std::pair
<CSSCoord
, CSSCoord
>>();
224 radiusX
= pair
.first
;
225 radiusY
= pair
.second
;
228 // The gradient line end point is where the gradient line intersects
230 CSSPoint end
= start
+ CSSPoint(radiusX
, 0);
231 return {start
, end
, radiusX
, radiusY
};
234 // Compute the center and the start angle of the conic gradient.
235 static std::tuple
<CSSPoint
, float> ComputeConicGradientProperties(
236 const StyleGradient
& aGradient
, const CSSSize
& aBoxSize
) {
237 const auto& conic
= aGradient
.AsConic();
238 const Position
& position
= conic
.position
;
239 float angle
= static_cast<float>(conic
.angle
.ToRadians());
240 CSSPoint center
= ResolvePosition(position
, aBoxSize
);
242 return {center
, angle
};
245 static float Interpolate(float aF1
, float aF2
, float aFrac
) {
246 return aF1
+ aFrac
* (aF2
- aF1
);
249 static StyleAbsoluteColor
Interpolate(const StyleAbsoluteColor
& aLeft
,
250 const StyleAbsoluteColor
& aRight
,
252 // NOTE: This has to match the interpolation method that WebRender uses which
253 // right now is sRGB. In the future we should implement interpolation in more
254 // gradient color-spaces.
255 static constexpr auto kMethod
= StyleColorInterpolationMethod
{
256 StyleColorSpace::Srgb
,
257 StyleHueInterpolationMethod::Shorter
,
259 return Servo_InterpolateColor(kMethod
, &aRight
, &aLeft
, aFrac
);
262 static nscoord
FindTileStart(nscoord aDirtyCoord
, nscoord aTilePos
,
264 NS_ASSERTION(aTileDim
> 0, "Non-positive tile dimension");
265 double multiples
= floor(double(aDirtyCoord
- aTilePos
) / aTileDim
);
266 return NSToCoordRound(multiples
* aTileDim
+ aTilePos
);
269 static gfxFloat
LinearGradientStopPositionForPoint(
270 const gfxPoint
& aGradientStart
, const gfxPoint
& aGradientEnd
,
271 const gfxPoint
& aPoint
) {
272 gfxPoint d
= aGradientEnd
- aGradientStart
;
273 gfxPoint p
= aPoint
- aGradientStart
;
275 * Compute a parameter t such that a line perpendicular to the
276 * d vector, passing through aGradientStart + d*t, also
277 * passes through aPoint.
280 * (p.x - d.x*t)*d.x + (p.y - d.y*t)*d.y = 0
282 * Solving for t we get
283 * numerator = d.x*p.x + d.y*p.y
284 * denominator = d.x^2 + d.y^2
285 * t = numerator/denominator
287 * In nsCSSRendering::PaintGradient we know the length of d
290 double numerator
= d
.x
.value
* p
.x
.value
+ d
.y
.value
* p
.y
.value
;
291 double denominator
= d
.x
.value
* d
.x
.value
+ d
.y
.value
* d
.y
.value
;
292 return numerator
/ denominator
;
295 static bool RectIsBeyondLinearGradientEdge(const gfxRect
& aRect
,
296 const gfxMatrix
& aPatternMatrix
,
297 const nsTArray
<ColorStop
>& aStops
,
298 const gfxPoint
& aGradientStart
,
299 const gfxPoint
& aGradientEnd
,
300 StyleAbsoluteColor
* aOutEdgeColor
) {
301 gfxFloat topLeft
= LinearGradientStopPositionForPoint(
302 aGradientStart
, aGradientEnd
,
303 aPatternMatrix
.TransformPoint(aRect
.TopLeft()));
304 gfxFloat topRight
= LinearGradientStopPositionForPoint(
305 aGradientStart
, aGradientEnd
,
306 aPatternMatrix
.TransformPoint(aRect
.TopRight()));
307 gfxFloat bottomLeft
= LinearGradientStopPositionForPoint(
308 aGradientStart
, aGradientEnd
,
309 aPatternMatrix
.TransformPoint(aRect
.BottomLeft()));
310 gfxFloat bottomRight
= LinearGradientStopPositionForPoint(
311 aGradientStart
, aGradientEnd
,
312 aPatternMatrix
.TransformPoint(aRect
.BottomRight()));
314 const ColorStop
& firstStop
= aStops
[0];
315 if (topLeft
< firstStop
.mPosition
&& topRight
< firstStop
.mPosition
&&
316 bottomLeft
< firstStop
.mPosition
&& bottomRight
< firstStop
.mPosition
) {
317 *aOutEdgeColor
= firstStop
.mColor
;
321 const ColorStop
& lastStop
= aStops
.LastElement();
322 if (topLeft
>= lastStop
.mPosition
&& topRight
>= lastStop
.mPosition
&&
323 bottomLeft
>= lastStop
.mPosition
&& bottomRight
>= lastStop
.mPosition
) {
324 *aOutEdgeColor
= lastStop
.mColor
;
331 static void ResolveMidpoints(nsTArray
<ColorStop
>& stops
) {
332 for (size_t x
= 1; x
< stops
.Length() - 1;) {
333 if (!stops
[x
].mIsMidpoint
) {
338 const auto& color1
= stops
[x
- 1].mColor
;
339 const auto& color2
= stops
[x
+ 1].mColor
;
340 float offset1
= stops
[x
- 1].mPosition
;
341 float offset2
= stops
[x
+ 1].mPosition
;
342 float offset
= stops
[x
].mPosition
;
343 // check if everything coincides. If so, ignore the midpoint.
344 if (offset
- offset1
== offset2
- offset
) {
345 stops
.RemoveElementAt(x
);
349 // Check if we coincide with the left colorstop.
350 if (offset1
== offset
) {
351 // Morph the midpoint to a regular stop with the color of the next
353 stops
[x
].mColor
= color2
;
354 stops
[x
].mIsMidpoint
= false;
358 // Check if we coincide with the right colorstop.
359 if (offset2
== offset
) {
360 // Morph the midpoint to a regular stop with the color of the previous
362 stops
[x
].mColor
= color1
;
363 stops
[x
].mIsMidpoint
= false;
367 float midpoint
= (offset
- offset1
) / (offset2
- offset1
);
368 ColorStop newStops
[9];
369 if (midpoint
> .5f
) {
370 for (size_t y
= 0; y
< 7; y
++) {
371 newStops
[y
].mPosition
= offset1
+ (offset
- offset1
) * (7 + y
) / 13;
374 newStops
[7].mPosition
= offset
+ (offset2
- offset
) / 3;
375 newStops
[8].mPosition
= offset
+ (offset2
- offset
) * 2 / 3;
377 newStops
[0].mPosition
= offset1
+ (offset
- offset1
) / 3;
378 newStops
[1].mPosition
= offset1
+ (offset
- offset1
) * 2 / 3;
380 for (size_t y
= 0; y
< 7; y
++) {
381 newStops
[y
+ 2].mPosition
= offset
+ (offset2
- offset
) * y
/ 13;
386 for (auto& newStop
: newStops
) {
387 // Calculate the intermediate color stops per the formula of the CSS
388 // images spec. http://dev.w3.org/csswg/css-images/#color-stop-syntax 9
389 // points were chosen since it is the minimum number of stops that always
390 // give the smoothest appearace regardless of midpoint position and
391 // difference in luminance of the end points.
392 const float relativeOffset
=
393 (newStop
.mPosition
- offset1
) / (offset2
- offset1
);
394 const float multiplier
= powf(relativeOffset
, logf(.5f
) / logf(midpoint
));
396 auto srgb1
= color1
.ToColorSpace(StyleColorSpace::Srgb
);
397 auto srgb2
= color2
.ToColorSpace(StyleColorSpace::Srgb
);
400 srgb1
.components
._0
+
401 multiplier
* (srgb2
.components
._0
- srgb1
.components
._0
);
403 srgb1
.components
._1
+
404 multiplier
* (srgb2
.components
._1
- srgb1
.components
._1
);
406 srgb1
.components
._2
+
407 multiplier
* (srgb2
.components
._2
- srgb1
.components
._2
);
409 srgb1
.alpha
+ multiplier
* (srgb2
.alpha
- srgb1
.alpha
);
411 newStop
.mColor
= StyleAbsoluteColor::SrgbLegacy(red
, green
, blue
, alpha
);
414 stops
.ReplaceElementsAt(x
, 1, newStops
, 9);
419 static StyleAbsoluteColor
TransparentColor(const StyleAbsoluteColor
& aColor
) {
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 ResolvePremultipliedAlpha(nsTArray
<ColorStop
>& aStops
) {
430 for (size_t x
= 1; x
< aStops
.Length(); x
++) {
431 const ColorStop leftStop
= aStops
[x
- 1];
432 const ColorStop rightStop
= aStops
[x
];
434 // if the left and right stop have the same alpha value, we don't need
435 // to do anything. Hardstops should be instant, and also should never
436 // require dealing with interpolation.
437 if (leftStop
.mColor
.alpha
== rightStop
.mColor
.alpha
||
438 leftStop
.mPosition
== rightStop
.mPosition
) {
442 // Is the stop on the left 100% transparent? If so, have it adopt the color
444 if (leftStop
.mColor
.alpha
== 0) {
445 aStops
[x
- 1].mColor
= TransparentColor(rightStop
.mColor
);
449 // Is the stop on the right completely transparent?
450 // If so, duplicate it and assign it the color on the left.
451 if (rightStop
.mColor
.alpha
== 0) {
452 ColorStop newStop
= rightStop
;
453 newStop
.mColor
= TransparentColor(leftStop
.mColor
);
454 aStops
.InsertElementAt(x
, newStop
);
459 // Now handle cases where one or both of the stops are partially
461 if (leftStop
.mColor
.alpha
!= 1.0f
|| rightStop
.mColor
.alpha
!= 1.0f
) {
462 // Calculate how many extra steps. We do a step per 10% transparency.
464 NSToIntFloor(fabsf(leftStop
.mColor
.alpha
- rightStop
.mColor
.alpha
) /
465 kAlphaIncrementPerGradientStep
);
466 for (size_t y
= 1; y
< stepCount
; y
++) {
467 float frac
= static_cast<float>(y
) / stepCount
;
469 Interpolate(leftStop
.mPosition
, rightStop
.mPosition
, frac
), false,
470 Interpolate(leftStop
.mColor
, rightStop
.mColor
, frac
));
471 aStops
.InsertElementAt(x
, newStop
);
478 static ColorStop
InterpolateColorStop(const ColorStop
& aFirst
,
479 const ColorStop
& aSecond
,
481 const StyleAbsoluteColor
& aDefault
) {
482 MOZ_ASSERT(aFirst
.mPosition
<= aPosition
);
483 MOZ_ASSERT(aPosition
<= aSecond
.mPosition
);
485 double delta
= aSecond
.mPosition
- aFirst
.mPosition
;
487 return ColorStop(aPosition
, false, aDefault
);
490 return ColorStop(aPosition
, false,
491 Interpolate(aFirst
.mColor
, aSecond
.mColor
,
492 (aPosition
- aFirst
.mPosition
) / delta
));
495 // Clamp and extend the given ColorStop array in-place to fit exactly into the
497 static void ClampColorStops(nsTArray
<ColorStop
>& aStops
) {
498 MOZ_ASSERT(aStops
.Length() > 0);
500 // If all stops are outside the range, then get rid of everything and replace
501 // with a single colour.
502 if (aStops
.Length() < 2 || aStops
[0].mPosition
> 1 ||
503 aStops
.LastElement().mPosition
< 0) {
504 const auto c
= aStops
[0].mPosition
> 1 ? aStops
[0].mColor
505 : aStops
.LastElement().mColor
;
507 aStops
.AppendElement(ColorStop(0, false, c
));
511 // Create the 0 and 1 points if they fall in the range of |aStops|, and
512 // discard all stops outside the range [0, 1].
513 // XXX: If we have stops positioned at 0 or 1, we only keep the innermost of
514 // those stops. This should be fine for the current user(s) of this function.
515 for (size_t i
= aStops
.Length() - 1; i
> 0; i
--) {
516 if (aStops
[i
- 1].mPosition
< 1 && aStops
[i
].mPosition
>= 1) {
517 // Add a point to position 1.
519 InterpolateColorStop(aStops
[i
- 1], aStops
[i
],
520 /* aPosition = */ 1, aStops
[i
- 1].mColor
);
521 // Remove all the elements whose position is greater than 1.
522 aStops
.RemoveLastElements(aStops
.Length() - (i
+ 1));
524 if (aStops
[i
- 1].mPosition
<= 0 && aStops
[i
].mPosition
> 0) {
525 // Add a point to position 0.
527 InterpolateColorStop(aStops
[i
- 1], aStops
[i
],
528 /* aPosition = */ 0, aStops
[i
].mColor
);
529 // Remove all of the preceding stops -- they are all negative.
530 aStops
.RemoveElementsAt(0, i
- 1);
535 MOZ_ASSERT(aStops
[0].mPosition
>= -1e6
);
536 MOZ_ASSERT(aStops
.LastElement().mPosition
- 1 <= 1e6
);
538 // The end points won't exist yet if they don't fall in the original range of
539 // |aStops|. Create them if needed.
540 if (aStops
[0].mPosition
> 0) {
541 aStops
.InsertElementAt(0, ColorStop(0, false, aStops
[0].mColor
));
543 if (aStops
.LastElement().mPosition
< 1) {
544 aStops
.AppendElement(ColorStop(1, false, aStops
.LastElement().mColor
));
550 template <typename T
>
551 static StyleAbsoluteColor
GetSpecifiedColor(
552 const StyleGenericGradientItem
<StyleColor
, T
>& aItem
,
553 const ComputedStyle
& aStyle
) {
554 if (aItem
.IsInterpolationHint()) {
555 return StyleAbsoluteColor::TRANSPARENT_BLACK
;
557 const StyleColor
& c
= aItem
.IsSimpleColorStop()
558 ? aItem
.AsSimpleColorStop()
559 : aItem
.AsComplexColorStop().color
;
561 return c
.ResolveColor(aStyle
.StyleText()->mColor
);
564 static Maybe
<double> GetSpecifiedGradientPosition(
565 const StyleGenericGradientItem
<StyleColor
, StyleLengthPercentage
>& aItem
,
566 CSSCoord aLineLength
) {
567 if (aItem
.IsSimpleColorStop()) {
571 const LengthPercentage
& pos
= aItem
.IsComplexColorStop()
572 ? aItem
.AsComplexColorStop().position
573 : aItem
.AsInterpolationHint();
575 if (pos
.ConvertsToPercentage()) {
576 return Some(pos
.ToPercentage());
579 if (aLineLength
< 1e-6) {
582 return Some(pos
.ResolveToCSSPixels(aLineLength
) / aLineLength
);
585 // aLineLength argument is unused for conic-gradients.
586 static Maybe
<double> GetSpecifiedGradientPosition(
587 const StyleGenericGradientItem
<StyleColor
, StyleAngleOrPercentage
>& aItem
,
588 CSSCoord aLineLength
) {
589 if (aItem
.IsSimpleColorStop()) {
593 const StyleAngleOrPercentage
& pos
= aItem
.IsComplexColorStop()
594 ? aItem
.AsComplexColorStop().position
595 : aItem
.AsInterpolationHint();
597 if (pos
.IsPercentage()) {
598 return Some(pos
.AsPercentage()._0
);
601 return Some(pos
.AsAngle().ToRadians() / (2 * M_PI
));
604 template <typename T
>
605 static nsTArray
<ColorStop
> ComputeColorStopsForItems(
606 ComputedStyle
* aComputedStyle
,
607 Span
<const StyleGenericGradientItem
<StyleColor
, T
>> aItems
,
608 CSSCoord aLineLength
) {
609 MOZ_ASSERT(aItems
.Length() >= 2,
610 "The parser should reject gradients with less than two stops");
612 nsTArray
<ColorStop
> stops(aItems
.Length());
614 // If there is a run of stops before stop i that did not have specified
615 // positions, then this is the index of the first stop in that run.
616 Maybe
<size_t> firstUnsetPosition
;
617 for (size_t i
= 0; i
< aItems
.Length(); ++i
) {
618 const auto& stop
= aItems
[i
];
621 Maybe
<double> specifiedPosition
=
622 GetSpecifiedGradientPosition(stop
, aLineLength
);
624 if (specifiedPosition
) {
625 position
= *specifiedPosition
;
627 // First stop defaults to position 0.0
629 } else if (i
== aItems
.Length() - 1) {
630 // Last stop defaults to position 1.0
633 // Other stops with no specified position get their position assigned
634 // later by interpolation, see below.
635 // Remember where the run of stops with no specified position starts,
636 // if it starts here.
637 if (firstUnsetPosition
.isNothing()) {
638 firstUnsetPosition
.emplace(i
);
640 MOZ_ASSERT(!stop
.IsInterpolationHint(),
641 "Interpolation hints always specify position");
642 auto color
= GetSpecifiedColor(stop
, *aComputedStyle
);
643 stops
.AppendElement(ColorStop(0, false, color
));
648 // Prevent decreasing stop positions by advancing this position
649 // to the previous stop position, if necessary
650 double previousPosition
= firstUnsetPosition
651 ? stops
[*firstUnsetPosition
- 1].mPosition
652 : stops
[i
- 1].mPosition
;
653 position
= std::max(position
, previousPosition
);
655 auto stopColor
= GetSpecifiedColor(stop
, *aComputedStyle
);
657 ColorStop(position
, stop
.IsInterpolationHint(), stopColor
));
658 if (firstUnsetPosition
) {
659 // Interpolate positions for all stops that didn't have a specified
661 double p
= stops
[*firstUnsetPosition
- 1].mPosition
;
662 double d
= (stops
[i
].mPosition
- p
) / (i
- *firstUnsetPosition
+ 1);
663 for (size_t j
= *firstUnsetPosition
; j
< i
; ++j
) {
665 stops
[j
].mPosition
= p
;
667 firstUnsetPosition
.reset();
674 static nsTArray
<ColorStop
> ComputeColorStops(ComputedStyle
* aComputedStyle
,
675 const StyleGradient
& aGradient
,
676 CSSCoord aLineLength
) {
677 if (aGradient
.IsLinear()) {
678 return ComputeColorStopsForItems(
679 aComputedStyle
, aGradient
.AsLinear().items
.AsSpan(), aLineLength
);
681 if (aGradient
.IsRadial()) {
682 return ComputeColorStopsForItems(
683 aComputedStyle
, aGradient
.AsRadial().items
.AsSpan(), aLineLength
);
685 return ComputeColorStopsForItems(
686 aComputedStyle
, aGradient
.AsConic().items
.AsSpan(), aLineLength
);
689 nsCSSGradientRenderer
nsCSSGradientRenderer::Create(
690 nsPresContext
* aPresContext
, ComputedStyle
* aComputedStyle
,
691 const StyleGradient
& aGradient
, const nsSize
& aIntrinsicSize
) {
692 auto srcSize
= CSSSize::FromAppUnits(aIntrinsicSize
);
694 // Compute "gradient line" start and end relative to the intrinsic size of
696 CSSPoint lineStart
, lineEnd
, center
; // center is for conic gradients only
697 CSSCoord radiusX
= 0, radiusY
= 0; // for radial gradients only
698 float angle
= 0.0; // for conic gradients only
699 if (aGradient
.IsLinear()) {
700 std::tie(lineStart
, lineEnd
) =
701 ComputeLinearGradientLine(aPresContext
, aGradient
, srcSize
);
702 } else if (aGradient
.IsRadial()) {
703 std::tie(lineStart
, lineEnd
, radiusX
, radiusY
) =
704 ComputeRadialGradientLine(aGradient
, srcSize
);
706 MOZ_ASSERT(aGradient
.IsConic());
707 std::tie(center
, angle
) =
708 ComputeConicGradientProperties(aGradient
, srcSize
);
710 // Avoid sending Infs or Nans to downwind draw targets.
711 if (!lineStart
.IsFinite() || !lineEnd
.IsFinite()) {
712 lineStart
= lineEnd
= CSSPoint(0, 0);
714 if (!center
.IsFinite()) {
715 center
= CSSPoint(0, 0);
717 CSSCoord lineLength
=
718 NS_hypot(lineEnd
.x
- lineStart
.x
, lineEnd
.y
- lineStart
.y
);
720 // Build color stop array and compute stop positions
721 nsTArray
<ColorStop
> stops
=
722 ComputeColorStops(aComputedStyle
, aGradient
, lineLength
);
724 ResolveMidpoints(stops
);
726 nsCSSGradientRenderer renderer
;
727 renderer
.mPresContext
= aPresContext
;
728 renderer
.mGradient
= &aGradient
;
729 renderer
.mStops
= std::move(stops
);
730 renderer
.mLineStart
= {
731 aPresContext
->CSSPixelsToDevPixels(lineStart
.x
),
732 aPresContext
->CSSPixelsToDevPixels(lineStart
.y
),
734 renderer
.mLineEnd
= {
735 aPresContext
->CSSPixelsToDevPixels(lineEnd
.x
),
736 aPresContext
->CSSPixelsToDevPixels(lineEnd
.y
),
738 renderer
.mRadiusX
= aPresContext
->CSSPixelsToDevPixels(radiusX
);
739 renderer
.mRadiusY
= aPresContext
->CSSPixelsToDevPixels(radiusY
);
741 aPresContext
->CSSPixelsToDevPixels(center
.x
),
742 aPresContext
->CSSPixelsToDevPixels(center
.y
),
744 renderer
.mAngle
= angle
;
748 void nsCSSGradientRenderer::Paint(gfxContext
& aContext
, const nsRect
& aDest
,
749 const nsRect
& aFillArea
,
750 const nsSize
& aRepeatSize
,
751 const CSSIntRect
& aSrc
,
752 const nsRect
& aDirtyRect
, float aOpacity
) {
753 AUTO_PROFILER_LABEL("nsCSSGradientRenderer::Paint", GRAPHICS
);
755 if (aDest
.IsEmpty() || aFillArea
.IsEmpty()) {
759 nscoord appUnitsPerDevPixel
= mPresContext
->AppUnitsPerDevPixel();
761 gfxFloat lineLength
=
762 NS_hypot(mLineEnd
.x
- mLineStart
.x
, mLineEnd
.y
- mLineStart
.y
);
763 bool cellContainsFill
= aDest
.Contains(aFillArea
);
765 // If a non-repeating linear gradient is axis-aligned and there are no gaps
766 // between tiles, we can optimise away most of the work by converting to a
767 // repeating linear gradient and filling the whole destination rect at once.
768 bool forceRepeatToCoverTiles
=
769 mGradient
->IsLinear() &&
770 (mLineStart
.x
== mLineEnd
.x
) != (mLineStart
.y
== mLineEnd
.y
) &&
771 aRepeatSize
.width
== aDest
.width
&& aRepeatSize
.height
== aDest
.height
&&
772 !(mGradient
->Repeating()) && !aSrc
.IsEmpty() && !cellContainsFill
;
775 if (forceRepeatToCoverTiles
) {
776 // Length of the source rectangle along the gradient axis.
778 // The position of the start of the rectangle along the gradient.
781 // The gradient line is "backwards". Flip the line upside down to make
782 // things easier, and then rotate the matrix to turn everything back the
784 if (mLineStart
.x
> mLineEnd
.x
|| mLineStart
.y
> mLineEnd
.y
) {
785 std::swap(mLineStart
, mLineEnd
);
786 matrix
.PreScale(-1, -1);
789 // Fit the gradient line exactly into the source rect.
790 // aSrc is relative to aIntrinsincSize.
791 // srcRectDev will be relative to srcSize, so in the same coordinate space
792 // as lineStart / lineEnd.
793 gfxRect srcRectDev
= nsLayoutUtils::RectToGfxRect(
794 CSSPixel::ToAppUnits(aSrc
), appUnitsPerDevPixel
);
795 if (mLineStart
.x
!= mLineEnd
.x
) {
796 rectLen
= srcRectDev
.width
;
797 offset
= (srcRectDev
.x
- mLineStart
.x
) / lineLength
;
798 mLineStart
.x
= srcRectDev
.x
;
799 mLineEnd
.x
= srcRectDev
.XMost();
801 rectLen
= srcRectDev
.height
;
802 offset
= (srcRectDev
.y
- mLineStart
.y
) / lineLength
;
803 mLineStart
.y
= srcRectDev
.y
;
804 mLineEnd
.y
= srcRectDev
.YMost();
807 // Adjust gradient stop positions for the new gradient line.
808 double scale
= lineLength
/ rectLen
;
809 for (size_t i
= 0; i
< mStops
.Length(); i
++) {
810 mStops
[i
].mPosition
= (mStops
[i
].mPosition
- offset
) * fabs(scale
);
813 // Clamp or extrapolate gradient stops to exactly [0, 1].
814 ClampColorStops(mStops
);
816 lineLength
= rectLen
;
819 // Eliminate negative-position stops if the gradient is radial.
820 double firstStop
= mStops
[0].mPosition
;
821 if (mGradient
->IsRadial() && firstStop
< 0.0) {
822 if (mGradient
->AsRadial().flags
& StyleGradientFlags::REPEATING
) {
823 // Choose an instance of the repeated pattern that gives us all positive
825 double lastStop
= mStops
[mStops
.Length() - 1].mPosition
;
826 double stopDelta
= lastStop
- firstStop
;
827 // If all the stops are in approximately the same place then logic below
828 // will kick in that makes us draw just the last stop color, so don't
829 // try to do anything in that case. We certainly need to avoid
831 if (stopDelta
>= 1e-6) {
832 double instanceCount
= ceil(-firstStop
/ stopDelta
);
833 // Advance stops by instanceCount multiples of the period of the
834 // repeating gradient.
835 double offset
= instanceCount
* stopDelta
;
836 for (uint32_t i
= 0; i
< mStops
.Length(); i
++) {
837 mStops
[i
].mPosition
+= offset
;
841 // Move negative-position stops to position 0.0. We may also need
842 // to set the color of the stop to the color the gradient should have
843 // at the center of the ellipse.
844 for (uint32_t i
= 0; i
< mStops
.Length(); i
++) {
845 double pos
= mStops
[i
].mPosition
;
847 mStops
[i
].mPosition
= 0.0;
848 // If this is the last stop, we don't need to adjust the color,
849 // it will fill the entire area.
850 if (i
< mStops
.Length() - 1) {
851 double nextPos
= mStops
[i
+ 1].mPosition
;
852 // If nextPos is approximately equal to pos, then we don't
853 // need to adjust the color of this stop because it's
854 // not going to be displayed.
855 // If nextPos is negative, we don't need to adjust the color of
856 // this stop since it's not going to be displayed because
857 // nextPos will also be moved to 0.0.
858 if (nextPos
>= 0.0 && nextPos
- pos
>= 1e-6) {
859 // Compute how far the new position 0.0 is along the interval
860 // between pos and nextPos.
861 // XXX Color interpolation (in cairo, too) should use the
862 // CSS 'color-interpolation' property!
863 float frac
= float((0.0 - pos
) / (nextPos
- pos
));
865 Interpolate(mStops
[i
].mColor
, mStops
[i
+ 1].mColor
, frac
);
871 firstStop
= mStops
[0].mPosition
;
872 MOZ_ASSERT(firstStop
>= 0.0, "Failed to fix stop offsets");
875 if (mGradient
->IsRadial() &&
876 !(mGradient
->AsRadial().flags
& StyleGradientFlags::REPEATING
)) {
877 // Direct2D can only handle a particular class of radial gradients because
878 // of the way the it specifies gradients. Setting firstStop to 0, when we
879 // can, will help us stay on the fast path. Currently we don't do this
880 // for repeating gradients but we could by adjusting the stop collection
885 double lastStop
= mStops
[mStops
.Length() - 1].mPosition
;
886 // Cairo gradients must have stop positions in the range [0, 1]. So,
887 // stop positions will be normalized below by subtracting firstStop and then
888 // multiplying by stopScale.
890 double stopOrigin
= firstStop
;
891 double stopEnd
= lastStop
;
892 double stopDelta
= lastStop
- firstStop
;
894 mGradient
->IsRadial() && (mRadiusX
< 1e-6 || mRadiusY
< 1e-6);
895 if (stopDelta
< 1e-6 || (!mGradient
->IsConic() && lineLength
< 1e-6) ||
897 // Stops are all at the same place. Map all stops to 0.0.
898 // For repeating radial gradients, or for any radial gradients with
899 // a zero radius, we need to fill with the last stop color, so just set
901 if (mGradient
->Repeating() || zeroRadius
) {
902 mRadiusX
= mRadiusY
= 0.0;
907 // Don't normalize non-repeating or degenerate gradients below 0..1
908 // This keeps the gradient line as large as the box and doesn't
909 // lets us avoiding having to get padding correct for stops
911 if (!mGradient
->Repeating() || stopDelta
== 0.0) {
912 stopOrigin
= std::min(stopOrigin
, 0.0);
913 stopEnd
= std::max(stopEnd
, 1.0);
915 stopScale
= 1.0 / (stopEnd
- stopOrigin
);
917 // Create the gradient pattern.
918 RefPtr
<gfxPattern
> gradientPattern
;
919 gfxPoint gradientStart
;
920 gfxPoint gradientEnd
;
921 if (mGradient
->IsLinear()) {
922 // Compute the actual gradient line ends we need to pass to cairo after
923 // stops have been normalized.
924 gradientStart
= mLineStart
+ (mLineEnd
- mLineStart
) * stopOrigin
;
925 gradientEnd
= mLineStart
+ (mLineEnd
- mLineStart
) * stopEnd
;
927 if (stopDelta
== 0.0) {
928 // Stops are all at the same place. For repeating gradients, this will
929 // just paint the last stop color. We don't need to do anything.
930 // For non-repeating gradients, this should render as two colors, one
931 // on each "side" of the gradient line segment, which is a point. All
932 // our stops will be at 0.0; we just need to set the direction vector
934 gradientEnd
= gradientStart
+ (mLineEnd
- mLineStart
);
937 gradientPattern
= new gfxPattern(gradientStart
.x
, gradientStart
.y
,
938 gradientEnd
.x
, gradientEnd
.y
);
939 } else if (mGradient
->IsRadial()) {
940 NS_ASSERTION(firstStop
>= 0.0,
941 "Negative stops not allowed for radial gradients");
943 // To form an ellipse, we'll stretch a circle vertically, if necessary.
944 // So our radii are based on radiusX.
945 double innerRadius
= mRadiusX
* stopOrigin
;
946 double outerRadius
= mRadiusX
* stopEnd
;
947 if (stopDelta
== 0.0) {
948 // Stops are all at the same place. See above (except we now have
949 // the inside vs. outside of an ellipse).
950 outerRadius
= innerRadius
+ 1;
952 gradientPattern
= new gfxPattern(mLineStart
.x
, mLineStart
.y
, innerRadius
,
953 mLineStart
.x
, mLineStart
.y
, outerRadius
);
954 if (mRadiusX
!= mRadiusY
) {
955 // Stretch the circles into ellipses vertically by setting a transform
957 // Recall that this is the transform from user space to pattern space.
958 // So to stretch the ellipse by factor of P vertically, we scale
959 // user coordinates by 1/P.
960 matrix
.PreTranslate(mLineStart
);
961 matrix
.PreScale(1.0, mRadiusX
/ mRadiusY
);
962 matrix
.PreTranslate(-mLineStart
);
966 new gfxPattern(mCenter
.x
, mCenter
.y
, mAngle
, stopOrigin
, stopEnd
);
968 // Use a pattern transform to take account of source and dest rects
969 matrix
.PreTranslate(gfxPoint(mPresContext
->CSSPixelsToDevPixels(aSrc
.x
),
970 mPresContext
->CSSPixelsToDevPixels(aSrc
.y
)));
972 gfxFloat(nsPresContext::CSSPixelsToAppUnits(aSrc
.width
)) / aDest
.width
,
973 gfxFloat(nsPresContext::CSSPixelsToAppUnits(aSrc
.height
)) / aDest
.height
);
974 gradientPattern
->SetMatrix(matrix
);
976 if (stopDelta
== 0.0) {
977 // Non-repeating gradient with all stops in same place -> just add
978 // first stop and last stop, both at position 0.
979 // Repeating gradient with all stops in the same place, or radial
980 // gradient with radius of 0 -> just paint the last stop color.
981 // We use firstStop offset to keep |stops| with same units (will later
983 auto firstColor(mStops
[0].mColor
);
984 auto lastColor(mStops
.LastElement().mColor
);
987 if (!mGradient
->Repeating() && !zeroRadius
) {
988 mStops
.AppendElement(ColorStop(firstStop
, false, firstColor
));
990 mStops
.AppendElement(ColorStop(firstStop
, false, lastColor
));
993 ResolvePremultipliedAlpha(mStops
);
995 bool isRepeat
= mGradient
->Repeating() || forceRepeatToCoverTiles
;
997 // Now set normalized color stops in pattern.
998 // Offscreen gradient surface cache (not a tile):
999 // On some backends (e.g. D2D), the GradientStops object holds an offscreen
1000 // surface which is a lookup table used to evaluate the gradient. This surface
1001 // can use much memory (ram and/or GPU ram) and can be expensive to create. So
1002 // we cache it. The cache key correlates 1:1 with the arguments for
1003 // CreateGradientStops (also the implied backend type) Note that GradientStop
1004 // is a simple struct with a stop value (while GradientStops has the surface).
1005 nsTArray
<gfx::GradientStop
> rawStops(mStops
.Length());
1006 rawStops
.SetLength(mStops
.Length());
1007 for (uint32_t i
= 0; i
< mStops
.Length(); i
++) {
1008 rawStops
[i
].color
= ToDeviceColor(mStops
[i
].mColor
);
1009 rawStops
[i
].color
.a
*= aOpacity
;
1010 rawStops
[i
].offset
= stopScale
* (mStops
[i
].mPosition
- stopOrigin
);
1012 RefPtr
<mozilla::gfx::GradientStops
> gs
=
1013 gfxGradientCache::GetOrCreateGradientStops(
1014 aContext
.GetDrawTarget(), rawStops
,
1015 isRepeat
? gfx::ExtendMode::REPEAT
: gfx::ExtendMode::CLAMP
);
1016 gradientPattern
->SetColorStops(gs
);
1018 // Paint gradient tiles. This isn't terribly efficient, but doing it this
1019 // way is simple and sure to get pixel-snapping right. We could speed things
1020 // up by drawing tiles into temporary surfaces and copying those to the
1021 // destination, but after pixel-snapping tiles may not all be the same size.
1023 if (!dirty
.IntersectRect(aDirtyRect
, aFillArea
)) return;
1025 gfxRect areaToFill
=
1026 nsLayoutUtils::RectToGfxRect(aFillArea
, appUnitsPerDevPixel
);
1027 gfxRect dirtyAreaToFill
=
1028 nsLayoutUtils::RectToGfxRect(dirty
, appUnitsPerDevPixel
);
1029 dirtyAreaToFill
.RoundOut();
1031 Matrix ctm
= aContext
.CurrentMatrix();
1032 bool isCTMPreservingAxisAlignedRectangles
=
1033 ctm
.PreservesAxisAlignedRectangles();
1035 // xStart/yStart are the top-left corner of the top-left tile.
1036 nscoord xStart
= FindTileStart(dirty
.x
, aDest
.x
, aRepeatSize
.width
);
1037 nscoord yStart
= FindTileStart(dirty
.y
, aDest
.y
, aRepeatSize
.height
);
1038 nscoord xEnd
= forceRepeatToCoverTiles
? xStart
+ aDest
.width
: dirty
.XMost();
1040 forceRepeatToCoverTiles
? yStart
+ aDest
.height
: dirty
.YMost();
1042 if (TryPaintTilesWithExtendMode(aContext
, gradientPattern
, xStart
, yStart
,
1043 dirtyAreaToFill
, aDest
, aRepeatSize
,
1044 forceRepeatToCoverTiles
)) {
1048 // x and y are the top-left corner of the tile to draw
1049 for (nscoord y
= yStart
; y
< yEnd
; y
+= aRepeatSize
.height
) {
1050 for (nscoord x
= xStart
; x
< xEnd
; x
+= aRepeatSize
.width
) {
1051 // The coordinates of the tile
1052 gfxRect tileRect
= nsLayoutUtils::RectToGfxRect(
1053 nsRect(x
, y
, aDest
.width
, aDest
.height
), appUnitsPerDevPixel
);
1054 // The actual area to fill with this tile is the intersection of this
1055 // tile with the overall area we're supposed to be filling
1057 forceRepeatToCoverTiles
? areaToFill
: tileRect
.Intersect(areaToFill
);
1058 // Try snapping the fill rect. Snap its top-left and bottom-right
1059 // independently to preserve the orientation.
1060 gfxPoint snappedFillRectTopLeft
= fillRect
.TopLeft();
1061 gfxPoint snappedFillRectTopRight
= fillRect
.TopRight();
1062 gfxPoint snappedFillRectBottomRight
= fillRect
.BottomRight();
1063 // Snap three points instead of just two to ensure we choose the
1064 // correct orientation if there's a reflection.
1065 if (isCTMPreservingAxisAlignedRectangles
&&
1066 aContext
.UserToDevicePixelSnapped(snappedFillRectTopLeft
, true) &&
1067 aContext
.UserToDevicePixelSnapped(snappedFillRectBottomRight
, true) &&
1068 aContext
.UserToDevicePixelSnapped(snappedFillRectTopRight
, true)) {
1069 if (snappedFillRectTopLeft
.x
== snappedFillRectBottomRight
.x
||
1070 snappedFillRectTopLeft
.y
== snappedFillRectBottomRight
.y
) {
1071 // Nothing to draw; avoid scaling by zero and other weirdness that
1072 // could put the context in an error state.
1075 // Set the context's transform to the transform that maps fillRect to
1076 // snappedFillRect. The part of the gradient that was going to
1077 // exactly fill fillRect will fill snappedFillRect instead.
1078 gfxMatrix transform
= gfxUtils::TransformRectToRect(
1079 fillRect
, snappedFillRectTopLeft
, snappedFillRectTopRight
,
1080 snappedFillRectBottomRight
);
1081 aContext
.SetMatrixDouble(transform
);
1084 aContext
.Rectangle(fillRect
);
1086 gfxRect dirtyFillRect
= fillRect
.Intersect(dirtyAreaToFill
);
1087 gfxRect fillRectRelativeToTile
= dirtyFillRect
- tileRect
.TopLeft();
1088 auto edgeColor
= StyleAbsoluteColor::TRANSPARENT_BLACK
;
1089 if (mGradient
->IsLinear() && !isRepeat
&&
1090 RectIsBeyondLinearGradientEdge(fillRectRelativeToTile
, matrix
, mStops
,
1091 gradientStart
, gradientEnd
,
1093 edgeColor
.alpha
*= aOpacity
;
1094 aContext
.SetColor(ToSRGBColor(edgeColor
));
1096 aContext
.SetMatrixDouble(
1097 aContext
.CurrentMatrixDouble().Copy().PreTranslate(
1098 tileRect
.TopLeft()));
1099 aContext
.SetPattern(gradientPattern
);
1102 aContext
.SetMatrix(ctm
);
1107 bool nsCSSGradientRenderer::TryPaintTilesWithExtendMode(
1108 gfxContext
& aContext
, gfxPattern
* aGradientPattern
, nscoord aXStart
,
1109 nscoord aYStart
, const gfxRect
& aDirtyAreaToFill
, const nsRect
& aDest
,
1110 const nsSize
& aRepeatSize
, bool aForceRepeatToCoverTiles
) {
1111 // If we have forced a non-repeating gradient to repeat to cover tiles,
1112 // then it will be faster to just paint it once using that optimization
1113 if (aForceRepeatToCoverTiles
) {
1117 nscoord appUnitsPerDevPixel
= mPresContext
->AppUnitsPerDevPixel();
1119 // We can only use this fast path if we don't have to worry about pixel
1120 // snapping, and there is no spacing between tiles. We could handle spacing
1121 // by increasing the size of tileSurface and leaving it transparent, but I'm
1122 // not sure it's worth it
1123 bool canUseExtendModeForTiling
= (aXStart
% appUnitsPerDevPixel
== 0) &&
1124 (aYStart
% appUnitsPerDevPixel
== 0) &&
1125 (aDest
.width
% appUnitsPerDevPixel
== 0) &&
1126 (aDest
.height
% appUnitsPerDevPixel
== 0) &&
1127 (aRepeatSize
.width
== aDest
.width
) &&
1128 (aRepeatSize
.height
== aDest
.height
);
1130 if (!canUseExtendModeForTiling
) {
1135 NSAppUnitsToIntPixels(aDest
.width
, appUnitsPerDevPixel
),
1136 NSAppUnitsToIntPixels(aDest
.height
, appUnitsPerDevPixel
),
1139 // Check whether this is a reasonable surface size and doesn't overflow
1140 // before doing calculations with the tile size
1141 if (!Factory::ReasonableSurfaceSize(tileSize
)) {
1145 // We only want to do this when there are enough tiles to justify the
1146 // overhead of painting to an offscreen surface. The heuristic here
1147 // is when we will be painting at least 16 tiles or more, this is kind
1149 bool shouldUseExtendModeForTiling
=
1150 aDirtyAreaToFill
.Area() > (tileSize
.width
* tileSize
.height
) * 16.0;
1152 if (!shouldUseExtendModeForTiling
) {
1156 // Draw the gradient pattern into a surface for our single tile
1157 RefPtr
<gfx::SourceSurface
> tileSurface
;
1159 RefPtr
<gfx::DrawTarget
> tileTarget
=
1160 aContext
.GetDrawTarget()->CreateSimilarDrawTarget(
1161 tileSize
, gfx::SurfaceFormat::B8G8R8A8
);
1162 if (!tileTarget
|| !tileTarget
->IsValid()) {
1167 gfxContext
tileContext(tileTarget
);
1169 tileContext
.SetPattern(aGradientPattern
);
1170 tileContext
.Paint();
1173 tileSurface
= tileTarget
->Snapshot();
1174 tileTarget
= nullptr;
1177 // Draw the gradient using tileSurface as a repeating pattern masked by
1179 Matrix tileTransform
= Matrix::Translation(
1180 NSAppUnitsToFloatPixels(aXStart
, appUnitsPerDevPixel
),
1181 NSAppUnitsToFloatPixels(aYStart
, appUnitsPerDevPixel
));
1184 aContext
.Rectangle(aDirtyAreaToFill
);
1185 aContext
.Fill(SurfacePattern(tileSurface
, ExtendMode::REPEAT
, tileTransform
));
1190 void nsCSSGradientRenderer::BuildWebRenderParameters(
1191 float aOpacity
, wr::ExtendMode
& aMode
, nsTArray
<wr::GradientStop
>& aStops
,
1192 LayoutDevicePoint
& aLineStart
, LayoutDevicePoint
& aLineEnd
,
1193 LayoutDeviceSize
& aGradientRadius
, LayoutDevicePoint
& aGradientCenter
,
1194 float& aGradientAngle
) {
1196 mGradient
->Repeating() ? wr::ExtendMode::Repeat
: wr::ExtendMode::Clamp
;
1198 aStops
.SetLength(mStops
.Length());
1199 for (uint32_t i
= 0; i
< mStops
.Length(); i
++) {
1200 aStops
[i
].color
= wr::ToColorF(ToDeviceColor(mStops
[i
].mColor
));
1201 aStops
[i
].color
.a
*= aOpacity
;
1202 aStops
[i
].offset
= mStops
[i
].mPosition
;
1205 aLineStart
= LayoutDevicePoint(mLineStart
.x
, mLineStart
.y
);
1206 aLineEnd
= LayoutDevicePoint(mLineEnd
.x
, mLineEnd
.y
);
1207 aGradientRadius
= LayoutDeviceSize(mRadiusX
, mRadiusY
);
1208 aGradientCenter
= LayoutDevicePoint(mCenter
.x
, mCenter
.y
);
1209 aGradientAngle
= mAngle
;
1212 void nsCSSGradientRenderer::BuildWebRenderDisplayItems(
1213 wr::DisplayListBuilder
& aBuilder
, const layers::StackingContextHelper
& aSc
,
1214 const nsRect
& aDest
, const nsRect
& aFillArea
, const nsSize
& aRepeatSize
,
1215 const CSSIntRect
& aSrc
, bool aIsBackfaceVisible
, float aOpacity
) {
1216 if (aDest
.IsEmpty() || aFillArea
.IsEmpty()) {
1220 wr::ExtendMode extendMode
;
1221 nsTArray
<wr::GradientStop
> stops
;
1222 LayoutDevicePoint lineStart
;
1223 LayoutDevicePoint lineEnd
;
1224 LayoutDeviceSize gradientRadius
;
1225 LayoutDevicePoint gradientCenter
;
1226 float gradientAngle
;
1227 BuildWebRenderParameters(aOpacity
, extendMode
, stops
, lineStart
, lineEnd
,
1228 gradientRadius
, gradientCenter
, gradientAngle
);
1230 nscoord appUnitsPerDevPixel
= mPresContext
->AppUnitsPerDevPixel();
1233 nsPoint(FindTileStart(aFillArea
.x
, aDest
.x
, aRepeatSize
.width
),
1234 FindTileStart(aFillArea
.y
, aDest
.y
, aRepeatSize
.height
));
1236 // Translate the parameters into device coordinates
1237 LayoutDeviceRect clipBounds
=
1238 LayoutDevicePixel::FromAppUnits(aFillArea
, appUnitsPerDevPixel
);
1239 LayoutDeviceRect firstTileBounds
= LayoutDevicePixel::FromAppUnits(
1240 nsRect(firstTile
, aDest
.Size()), appUnitsPerDevPixel
);
1241 LayoutDeviceSize tileRepeat
=
1242 LayoutDevicePixel::FromAppUnits(aRepeatSize
, appUnitsPerDevPixel
);
1244 // Calculate the bounds of the gradient display item, which starts at the
1245 // first tile and extends to the end of clip bounds
1246 LayoutDevicePoint tileToClip
=
1247 clipBounds
.BottomRight() - firstTileBounds
.TopLeft();
1248 LayoutDeviceRect gradientBounds
= LayoutDeviceRect(
1249 firstTileBounds
.TopLeft(), LayoutDeviceSize(tileToClip
.x
, tileToClip
.y
));
1251 // Calculate the tile spacing, which is the repeat size minus the tile size
1252 LayoutDeviceSize tileSpacing
= tileRepeat
- firstTileBounds
.Size();
1254 // srcTransform is used for scaling the gradient to match aSrc
1255 LayoutDeviceRect srcTransform
= LayoutDeviceRect(
1256 nsPresContext::CSSPixelsToAppUnits(aSrc
.x
),
1257 nsPresContext::CSSPixelsToAppUnits(aSrc
.y
),
1258 aDest
.width
/ ((float)nsPresContext::CSSPixelsToAppUnits(aSrc
.width
)),
1259 aDest
.height
/ ((float)nsPresContext::CSSPixelsToAppUnits(aSrc
.height
)));
1261 lineStart
.x
= (lineStart
.x
- srcTransform
.x
) * srcTransform
.width
;
1262 lineStart
.y
= (lineStart
.y
- srcTransform
.y
) * srcTransform
.height
;
1264 gradientCenter
.x
= (gradientCenter
.x
- srcTransform
.x
) * srcTransform
.width
;
1265 gradientCenter
.y
= (gradientCenter
.y
- srcTransform
.y
) * srcTransform
.height
;
1267 if (mGradient
->IsLinear()) {
1268 lineEnd
.x
= (lineEnd
.x
- srcTransform
.x
) * srcTransform
.width
;
1269 lineEnd
.y
= (lineEnd
.y
- srcTransform
.y
) * srcTransform
.height
;
1271 aBuilder
.PushLinearGradient(
1272 mozilla::wr::ToLayoutRect(gradientBounds
),
1273 mozilla::wr::ToLayoutRect(clipBounds
), aIsBackfaceVisible
,
1274 mozilla::wr::ToLayoutPoint(lineStart
),
1275 mozilla::wr::ToLayoutPoint(lineEnd
), stops
, extendMode
,
1276 mozilla::wr::ToLayoutSize(firstTileBounds
.Size()),
1277 mozilla::wr::ToLayoutSize(tileSpacing
));
1278 } else if (mGradient
->IsRadial()) {
1279 gradientRadius
.width
*= srcTransform
.width
;
1280 gradientRadius
.height
*= srcTransform
.height
;
1282 aBuilder
.PushRadialGradient(
1283 mozilla::wr::ToLayoutRect(gradientBounds
),
1284 mozilla::wr::ToLayoutRect(clipBounds
), aIsBackfaceVisible
,
1285 mozilla::wr::ToLayoutPoint(lineStart
),
1286 mozilla::wr::ToLayoutSize(gradientRadius
), stops
, extendMode
,
1287 mozilla::wr::ToLayoutSize(firstTileBounds
.Size()),
1288 mozilla::wr::ToLayoutSize(tileSpacing
));
1290 MOZ_ASSERT(mGradient
->IsConic());
1291 aBuilder
.PushConicGradient(
1292 mozilla::wr::ToLayoutRect(gradientBounds
),
1293 mozilla::wr::ToLayoutRect(clipBounds
), aIsBackfaceVisible
,
1294 mozilla::wr::ToLayoutPoint(gradientCenter
), gradientAngle
, stops
,
1295 extendMode
, mozilla::wr::ToLayoutSize(firstTileBounds
.Size()),
1296 mozilla::wr::ToLayoutSize(tileSpacing
));
1300 } // namespace mozilla