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 #include "mozilla/StaticPrefs_layout.h"
42 using namespace mozilla
;
43 using namespace mozilla::gfx
;
45 static CSSPoint
ResolvePosition(const Position
& aPos
, const CSSSize
& aSize
) {
46 CSSCoord h
= aPos
.horizontal
.ResolveToCSSPixels(aSize
.width
);
47 CSSCoord v
= aPos
.vertical
.ResolveToCSSPixels(aSize
.height
);
48 return CSSPoint(h
, v
);
51 // Given a box with size aBoxSize and origin (0,0), and an angle aAngle,
52 // and a starting point for the gradient line aStart, find the endpoint of
53 // the gradient line --- the intersection of the gradient line with a line
54 // perpendicular to aAngle that passes through the farthest corner in the
56 static CSSPoint
ComputeGradientLineEndFromAngle(const CSSPoint
& aStart
,
58 const CSSSize
& aBoxSize
) {
59 double dx
= cos(-aAngle
);
60 double dy
= sin(-aAngle
);
61 CSSPoint
farthestCorner(dx
> 0 ? aBoxSize
.width
: 0,
62 dy
> 0 ? aBoxSize
.height
: 0);
63 CSSPoint delta
= farthestCorner
- aStart
;
64 double u
= delta
.x
* dy
- delta
.y
* dx
;
65 return farthestCorner
+ CSSPoint(-u
* dy
, u
* dx
);
68 // Compute the start and end points of the gradient line for a linear gradient.
69 static std::tuple
<CSSPoint
, CSSPoint
> ComputeLinearGradientLine(
70 nsPresContext
* aPresContext
, const StyleGradient
& aGradient
,
71 const CSSSize
& aBoxSize
) {
72 using X
= StyleHorizontalPositionKeyword
;
73 using Y
= StyleVerticalPositionKeyword
;
75 const StyleLineDirection
& direction
= aGradient
.AsLinear().direction
;
77 aGradient
.AsLinear().compat_mode
== StyleGradientCompatMode::Modern
;
79 CSSPoint
center(aBoxSize
.width
/ 2, aBoxSize
.height
/ 2);
80 switch (direction
.tag
) {
81 case StyleLineDirection::Tag::Angle
: {
82 double angle
= direction
.AsAngle().ToRadians();
84 angle
= M_PI_2
- angle
;
86 CSSPoint end
= ComputeGradientLineEndFromAngle(center
, angle
, aBoxSize
);
87 CSSPoint start
= CSSPoint(aBoxSize
.width
, aBoxSize
.height
) - end
;
90 case StyleLineDirection::Tag::Vertical
: {
91 CSSPoint
start(center
.x
, 0);
92 CSSPoint
end(center
.x
, aBoxSize
.height
);
93 if (isModern
== (direction
.AsVertical() == Y::Top
)) {
94 std::swap(start
.y
, end
.y
);
98 case StyleLineDirection::Tag::Horizontal
: {
99 CSSPoint
start(0, center
.y
);
100 CSSPoint
end(aBoxSize
.width
, center
.y
);
101 if (isModern
== (direction
.AsHorizontal() == X::Left
)) {
102 std::swap(start
.x
, end
.x
);
106 case StyleLineDirection::Tag::Corner
: {
107 const auto& corner
= direction
.AsCorner();
108 const X
& h
= corner
._0
;
109 const Y
& v
= corner
._1
;
112 float xSign
= h
== X::Right
? 1.0 : -1.0;
113 float ySign
= v
== Y::Top
? 1.0 : -1.0;
114 double angle
= atan2(ySign
* aBoxSize
.width
, xSign
* aBoxSize
.height
);
115 CSSPoint end
= ComputeGradientLineEndFromAngle(center
, angle
, aBoxSize
);
116 CSSPoint start
= CSSPoint(aBoxSize
.width
, aBoxSize
.height
) - end
;
120 CSSCoord startX
= h
== X::Left
? 0.0 : aBoxSize
.width
;
121 CSSCoord startY
= v
== Y::Top
? 0.0 : aBoxSize
.height
;
123 CSSPoint
start(startX
, startY
);
124 CSSPoint end
= CSSPoint(aBoxSize
.width
, aBoxSize
.height
) - start
;
130 MOZ_ASSERT_UNREACHABLE("Unknown line direction");
131 return {CSSPoint(), CSSPoint()};
134 using EndingShape
= StyleGenericEndingShape
<Length
, LengthPercentage
>;
135 using RadialGradientRadii
=
136 Variant
<StyleShapeExtent
, std::pair
<CSSCoord
, CSSCoord
>>;
138 static RadialGradientRadii
ComputeRadialGradientRadii(const EndingShape
& aShape
,
139 const CSSSize
& aSize
) {
140 if (aShape
.IsCircle()) {
141 auto& circle
= aShape
.AsCircle();
142 if (circle
.IsExtent()) {
143 return RadialGradientRadii(circle
.AsExtent());
145 CSSCoord radius
= circle
.AsRadius().ToCSSPixels();
146 return RadialGradientRadii(std::make_pair(radius
, radius
));
148 auto& ellipse
= aShape
.AsEllipse();
149 if (ellipse
.IsExtent()) {
150 return RadialGradientRadii(ellipse
.AsExtent());
153 auto& radii
= ellipse
.AsRadii();
154 return RadialGradientRadii(
155 std::make_pair(radii
._0
.ResolveToCSSPixels(aSize
.width
),
156 radii
._1
.ResolveToCSSPixels(aSize
.height
)));
159 // Compute the start and end points of the gradient line for a radial gradient.
160 // Also returns the horizontal and vertical radii defining the circle or
162 static std::tuple
<CSSPoint
, CSSPoint
, CSSCoord
, CSSCoord
>
163 ComputeRadialGradientLine(const StyleGradient
& aGradient
,
164 const CSSSize
& aBoxSize
) {
165 const auto& radial
= aGradient
.AsRadial();
166 const EndingShape
& endingShape
= radial
.shape
;
167 const Position
& position
= radial
.position
;
168 CSSPoint start
= ResolvePosition(position
, aBoxSize
);
170 // Compute gradient shape: the x and y radii of an ellipse.
171 CSSCoord radiusX
, radiusY
;
172 CSSCoord leftDistance
= Abs(start
.x
);
173 CSSCoord rightDistance
= Abs(aBoxSize
.width
- start
.x
);
174 CSSCoord topDistance
= Abs(start
.y
);
175 CSSCoord bottomDistance
= Abs(aBoxSize
.height
- start
.y
);
177 auto radii
= ComputeRadialGradientRadii(endingShape
, aBoxSize
);
178 if (radii
.is
<StyleShapeExtent
>()) {
179 switch (radii
.as
<StyleShapeExtent
>()) {
180 case StyleShapeExtent::ClosestSide
:
181 radiusX
= std::min(leftDistance
, rightDistance
);
182 radiusY
= std::min(topDistance
, bottomDistance
);
183 if (endingShape
.IsCircle()) {
184 radiusX
= radiusY
= std::min(radiusX
, radiusY
);
187 case StyleShapeExtent::ClosestCorner
: {
188 // Compute x and y distances to nearest corner
189 CSSCoord offsetX
= std::min(leftDistance
, rightDistance
);
190 CSSCoord offsetY
= std::min(topDistance
, bottomDistance
);
191 if (endingShape
.IsCircle()) {
192 radiusX
= radiusY
= NS_hypot(offsetX
, offsetY
);
194 // maintain aspect ratio
195 radiusX
= offsetX
* M_SQRT2
;
196 radiusY
= offsetY
* M_SQRT2
;
200 case StyleShapeExtent::FarthestSide
:
201 radiusX
= std::max(leftDistance
, rightDistance
);
202 radiusY
= std::max(topDistance
, bottomDistance
);
203 if (endingShape
.IsCircle()) {
204 radiusX
= radiusY
= std::max(radiusX
, radiusY
);
207 case StyleShapeExtent::FarthestCorner
: {
208 // Compute x and y distances to nearest corner
209 CSSCoord offsetX
= std::max(leftDistance
, rightDistance
);
210 CSSCoord offsetY
= std::max(topDistance
, bottomDistance
);
211 if (endingShape
.IsCircle()) {
212 radiusX
= radiusY
= NS_hypot(offsetX
, offsetY
);
214 // maintain aspect ratio
215 radiusX
= offsetX
* M_SQRT2
;
216 radiusY
= offsetY
* M_SQRT2
;
221 MOZ_ASSERT_UNREACHABLE("Unknown shape extent keyword?");
222 radiusX
= radiusY
= 0;
225 auto pair
= radii
.as
<std::pair
<CSSCoord
, CSSCoord
>>();
226 radiusX
= pair
.first
;
227 radiusY
= pair
.second
;
230 // The gradient line end point is where the gradient line intersects
232 CSSPoint end
= start
+ CSSPoint(radiusX
, 0);
233 return {start
, end
, radiusX
, radiusY
};
236 // Compute the center and the start angle of the conic gradient.
237 static std::tuple
<CSSPoint
, float> ComputeConicGradientProperties(
238 const StyleGradient
& aGradient
, const CSSSize
& aBoxSize
) {
239 const auto& conic
= aGradient
.AsConic();
240 const Position
& position
= conic
.position
;
241 float angle
= static_cast<float>(conic
.angle
.ToRadians());
242 CSSPoint center
= ResolvePosition(position
, aBoxSize
);
244 return {center
, angle
};
247 static float Interpolate(float aF1
, float aF2
, float aFrac
) {
248 return aF1
+ aFrac
* (aF2
- aF1
);
251 static StyleAbsoluteColor
Interpolate(const StyleAbsoluteColor
& aLeft
,
252 const StyleAbsoluteColor
& aRight
,
254 // NOTE: This has to match the interpolation method that WebRender uses which
255 // right now is sRGB. In the future we should implement interpolation in more
256 // gradient color-spaces.
257 static constexpr auto kMethod
= StyleColorInterpolationMethod
{
258 StyleColorSpace::Srgb
,
259 StyleHueInterpolationMethod::Shorter
,
261 return Servo_InterpolateColor(kMethod
, &aRight
, &aLeft
, aFrac
);
264 static nscoord
FindTileStart(nscoord aDirtyCoord
, nscoord aTilePos
,
266 NS_ASSERTION(aTileDim
> 0, "Non-positive tile dimension");
267 double multiples
= floor(double(aDirtyCoord
- aTilePos
) / aTileDim
);
268 return NSToCoordRound(multiples
* aTileDim
+ aTilePos
);
271 static gfxFloat
LinearGradientStopPositionForPoint(
272 const gfxPoint
& aGradientStart
, const gfxPoint
& aGradientEnd
,
273 const gfxPoint
& aPoint
) {
274 gfxPoint d
= aGradientEnd
- aGradientStart
;
275 gfxPoint p
= aPoint
- aGradientStart
;
277 * Compute a parameter t such that a line perpendicular to the
278 * d vector, passing through aGradientStart + d*t, also
279 * passes through aPoint.
282 * (p.x - d.x*t)*d.x + (p.y - d.y*t)*d.y = 0
284 * Solving for t we get
285 * numerator = d.x*p.x + d.y*p.y
286 * denominator = d.x^2 + d.y^2
287 * t = numerator/denominator
289 * In nsCSSRendering::PaintGradient we know the length of d
292 double numerator
= d
.x
.value
* p
.x
.value
+ d
.y
.value
* p
.y
.value
;
293 double denominator
= d
.x
.value
* d
.x
.value
+ d
.y
.value
* d
.y
.value
;
294 return numerator
/ denominator
;
297 static bool RectIsBeyondLinearGradientEdge(const gfxRect
& aRect
,
298 const gfxMatrix
& aPatternMatrix
,
299 const nsTArray
<ColorStop
>& aStops
,
300 const gfxPoint
& aGradientStart
,
301 const gfxPoint
& aGradientEnd
,
302 StyleAbsoluteColor
* aOutEdgeColor
) {
303 gfxFloat topLeft
= LinearGradientStopPositionForPoint(
304 aGradientStart
, aGradientEnd
,
305 aPatternMatrix
.TransformPoint(aRect
.TopLeft()));
306 gfxFloat topRight
= LinearGradientStopPositionForPoint(
307 aGradientStart
, aGradientEnd
,
308 aPatternMatrix
.TransformPoint(aRect
.TopRight()));
309 gfxFloat bottomLeft
= LinearGradientStopPositionForPoint(
310 aGradientStart
, aGradientEnd
,
311 aPatternMatrix
.TransformPoint(aRect
.BottomLeft()));
312 gfxFloat bottomRight
= LinearGradientStopPositionForPoint(
313 aGradientStart
, aGradientEnd
,
314 aPatternMatrix
.TransformPoint(aRect
.BottomRight()));
316 const ColorStop
& firstStop
= aStops
[0];
317 if (topLeft
< firstStop
.mPosition
&& topRight
< firstStop
.mPosition
&&
318 bottomLeft
< firstStop
.mPosition
&& bottomRight
< firstStop
.mPosition
) {
319 *aOutEdgeColor
= firstStop
.mColor
;
323 const ColorStop
& lastStop
= aStops
.LastElement();
324 if (topLeft
>= lastStop
.mPosition
&& topRight
>= lastStop
.mPosition
&&
325 bottomLeft
>= lastStop
.mPosition
&& bottomRight
>= lastStop
.mPosition
) {
326 *aOutEdgeColor
= lastStop
.mColor
;
333 static void ResolveMidpoints(nsTArray
<ColorStop
>& stops
) {
334 for (size_t x
= 1; x
< stops
.Length() - 1;) {
335 if (!stops
[x
].mIsMidpoint
) {
340 const auto& color1
= stops
[x
- 1].mColor
;
341 const auto& color2
= stops
[x
+ 1].mColor
;
342 float offset1
= stops
[x
- 1].mPosition
;
343 float offset2
= stops
[x
+ 1].mPosition
;
344 float offset
= stops
[x
].mPosition
;
345 // check if everything coincides. If so, ignore the midpoint.
346 if (offset
- offset1
== offset2
- offset
) {
347 stops
.RemoveElementAt(x
);
351 // Check if we coincide with the left colorstop.
352 if (offset1
== offset
) {
353 // Morph the midpoint to a regular stop with the color of the next
355 stops
[x
].mColor
= color2
;
356 stops
[x
].mIsMidpoint
= false;
360 // Check if we coincide with the right colorstop.
361 if (offset2
== offset
) {
362 // Morph the midpoint to a regular stop with the color of the previous
364 stops
[x
].mColor
= color1
;
365 stops
[x
].mIsMidpoint
= false;
369 float midpoint
= (offset
- offset1
) / (offset2
- offset1
);
370 ColorStop newStops
[9];
371 if (midpoint
> .5f
) {
372 for (size_t y
= 0; y
< 7; y
++) {
373 newStops
[y
].mPosition
= offset1
+ (offset
- offset1
) * (7 + y
) / 13;
376 newStops
[7].mPosition
= offset
+ (offset2
- offset
) / 3;
377 newStops
[8].mPosition
= offset
+ (offset2
- offset
) * 2 / 3;
379 newStops
[0].mPosition
= offset1
+ (offset
- offset1
) / 3;
380 newStops
[1].mPosition
= offset1
+ (offset
- offset1
) * 2 / 3;
382 for (size_t y
= 0; y
< 7; y
++) {
383 newStops
[y
+ 2].mPosition
= offset
+ (offset2
- offset
) * y
/ 13;
388 for (auto& newStop
: newStops
) {
389 // Calculate the intermediate color stops per the formula of the CSS
390 // images spec. http://dev.w3.org/csswg/css-images/#color-stop-syntax 9
391 // points were chosen since it is the minimum number of stops that always
392 // give the smoothest appearace regardless of midpoint position and
393 // difference in luminance of the end points.
394 const float relativeOffset
=
395 (newStop
.mPosition
- offset1
) / (offset2
- offset1
);
396 const float multiplier
= powf(relativeOffset
, logf(.5f
) / logf(midpoint
));
398 auto srgb1
= color1
.ToColorSpace(StyleColorSpace::Srgb
);
399 auto srgb2
= color2
.ToColorSpace(StyleColorSpace::Srgb
);
402 srgb1
.components
._0
+
403 multiplier
* (srgb2
.components
._0
- srgb1
.components
._0
);
405 srgb1
.components
._1
+
406 multiplier
* (srgb2
.components
._1
- srgb1
.components
._1
);
408 srgb1
.components
._2
+
409 multiplier
* (srgb2
.components
._2
- srgb1
.components
._2
);
411 srgb1
.alpha
+ multiplier
* (srgb2
.alpha
- srgb1
.alpha
);
413 newStop
.mColor
= StyleAbsoluteColor::SrgbLegacy(red
, green
, blue
, alpha
);
416 stops
.ReplaceElementsAt(x
, 1, newStops
, 9);
421 static StyleAbsoluteColor
TransparentColor(const StyleAbsoluteColor
& aColor
) {
427 // Adjusts and adds color stops in such a way that drawing the gradient with
428 // unpremultiplied interpolation looks nearly the same as if it were drawn with
429 // premultiplied interpolation.
430 static const float kAlphaIncrementPerGradientStep
= 0.1f
;
431 static void ResolvePremultipliedAlpha(nsTArray
<ColorStop
>& aStops
) {
432 for (size_t x
= 1; x
< aStops
.Length(); x
++) {
433 const ColorStop leftStop
= aStops
[x
- 1];
434 const ColorStop rightStop
= aStops
[x
];
436 // if the left and right stop have the same alpha value, we don't need
437 // to do anything. Hardstops should be instant, and also should never
438 // require dealing with interpolation.
439 if (leftStop
.mColor
.alpha
== rightStop
.mColor
.alpha
||
440 leftStop
.mPosition
== rightStop
.mPosition
) {
444 // Is the stop on the left 100% transparent? If so, have it adopt the color
446 if (leftStop
.mColor
.alpha
== 0) {
447 aStops
[x
- 1].mColor
= TransparentColor(rightStop
.mColor
);
451 // Is the stop on the right completely transparent?
452 // If so, duplicate it and assign it the color on the left.
453 if (rightStop
.mColor
.alpha
== 0) {
454 ColorStop newStop
= rightStop
;
455 newStop
.mColor
= TransparentColor(leftStop
.mColor
);
456 aStops
.InsertElementAt(x
, newStop
);
461 // Now handle cases where one or both of the stops are partially
463 if (leftStop
.mColor
.alpha
!= 1.0f
|| rightStop
.mColor
.alpha
!= 1.0f
) {
464 // Calculate how many extra steps. We do a step per 10% transparency.
466 NSToIntFloor(fabsf(leftStop
.mColor
.alpha
- rightStop
.mColor
.alpha
) /
467 kAlphaIncrementPerGradientStep
);
468 for (size_t y
= 1; y
< stepCount
; y
++) {
469 float frac
= static_cast<float>(y
) / stepCount
;
471 Interpolate(leftStop
.mPosition
, rightStop
.mPosition
, frac
), false,
472 Interpolate(leftStop
.mColor
, rightStop
.mColor
, frac
));
473 aStops
.InsertElementAt(x
, newStop
);
480 static ColorStop
InterpolateColorStop(const ColorStop
& aFirst
,
481 const ColorStop
& aSecond
,
483 const StyleAbsoluteColor
& aDefault
) {
484 MOZ_ASSERT(aFirst
.mPosition
<= aPosition
);
485 MOZ_ASSERT(aPosition
<= aSecond
.mPosition
);
487 double delta
= aSecond
.mPosition
- aFirst
.mPosition
;
489 return ColorStop(aPosition
, false, aDefault
);
492 return ColorStop(aPosition
, false,
493 Interpolate(aFirst
.mColor
, aSecond
.mColor
,
494 (aPosition
- aFirst
.mPosition
) / delta
));
497 // Clamp and extend the given ColorStop array in-place to fit exactly into the
499 static void ClampColorStops(nsTArray
<ColorStop
>& aStops
) {
500 MOZ_ASSERT(aStops
.Length() > 0);
502 // If all stops are outside the range, then get rid of everything and replace
503 // with a single colour.
504 if (aStops
.Length() < 2 || aStops
[0].mPosition
> 1 ||
505 aStops
.LastElement().mPosition
< 0) {
506 const auto c
= aStops
[0].mPosition
> 1 ? aStops
[0].mColor
507 : aStops
.LastElement().mColor
;
509 aStops
.AppendElement(ColorStop(0, false, c
));
513 // Create the 0 and 1 points if they fall in the range of |aStops|, and
514 // discard all stops outside the range [0, 1].
515 // XXX: If we have stops positioned at 0 or 1, we only keep the innermost of
516 // those stops. This should be fine for the current user(s) of this function.
517 for (size_t i
= aStops
.Length() - 1; i
> 0; i
--) {
518 if (aStops
[i
- 1].mPosition
< 1 && aStops
[i
].mPosition
>= 1) {
519 // Add a point to position 1.
521 InterpolateColorStop(aStops
[i
- 1], aStops
[i
],
522 /* aPosition = */ 1, aStops
[i
- 1].mColor
);
523 // Remove all the elements whose position is greater than 1.
524 aStops
.RemoveLastElements(aStops
.Length() - (i
+ 1));
526 if (aStops
[i
- 1].mPosition
<= 0 && aStops
[i
].mPosition
> 0) {
527 // Add a point to position 0.
529 InterpolateColorStop(aStops
[i
- 1], aStops
[i
],
530 /* aPosition = */ 0, aStops
[i
].mColor
);
531 // Remove all of the preceding stops -- they are all negative.
532 aStops
.RemoveElementsAt(0, i
- 1);
537 MOZ_ASSERT(aStops
[0].mPosition
>= -1e6
);
538 MOZ_ASSERT(aStops
.LastElement().mPosition
- 1 <= 1e6
);
540 // The end points won't exist yet if they don't fall in the original range of
541 // |aStops|. Create them if needed.
542 if (aStops
[0].mPosition
> 0) {
543 aStops
.InsertElementAt(0, ColorStop(0, false, aStops
[0].mColor
));
545 if (aStops
.LastElement().mPosition
< 1) {
546 aStops
.AppendElement(ColorStop(1, false, aStops
.LastElement().mColor
));
552 template <typename T
>
553 static StyleAbsoluteColor
GetSpecifiedColor(
554 const StyleGenericGradientItem
<StyleColor
, T
>& aItem
,
555 const ComputedStyle
& aStyle
) {
556 if (aItem
.IsInterpolationHint()) {
557 return StyleAbsoluteColor::TRANSPARENT_BLACK
;
559 const StyleColor
& c
= aItem
.IsSimpleColorStop()
560 ? aItem
.AsSimpleColorStop()
561 : aItem
.AsComplexColorStop().color
;
563 return c
.ResolveColor(aStyle
.StyleText()->mColor
);
566 static Maybe
<double> GetSpecifiedGradientPosition(
567 const StyleGenericGradientItem
<StyleColor
, StyleLengthPercentage
>& aItem
,
568 CSSCoord aLineLength
) {
569 if (aItem
.IsSimpleColorStop()) {
573 const LengthPercentage
& pos
= aItem
.IsComplexColorStop()
574 ? aItem
.AsComplexColorStop().position
575 : aItem
.AsInterpolationHint();
577 if (pos
.ConvertsToPercentage()) {
578 return Some(pos
.ToPercentage());
581 if (aLineLength
< 1e-6) {
584 return Some(pos
.ResolveToCSSPixels(aLineLength
) / aLineLength
);
587 // aLineLength argument is unused for conic-gradients.
588 static Maybe
<double> GetSpecifiedGradientPosition(
589 const StyleGenericGradientItem
<StyleColor
, StyleAngleOrPercentage
>& aItem
,
590 CSSCoord aLineLength
) {
591 if (aItem
.IsSimpleColorStop()) {
595 const StyleAngleOrPercentage
& pos
= aItem
.IsComplexColorStop()
596 ? aItem
.AsComplexColorStop().position
597 : aItem
.AsInterpolationHint();
599 if (pos
.IsPercentage()) {
600 return Some(pos
.AsPercentage()._0
);
603 return Some(pos
.AsAngle().ToRadians() / (2 * M_PI
));
606 template <typename T
>
607 static nsTArray
<ColorStop
> ComputeColorStopsForItems(
608 ComputedStyle
* aComputedStyle
,
609 Span
<const StyleGenericGradientItem
<StyleColor
, T
>> aItems
,
610 CSSCoord aLineLength
) {
611 MOZ_ASSERT(aItems
.Length() >= 2,
612 "The parser should reject gradients with less than two stops");
614 nsTArray
<ColorStop
> stops(aItems
.Length());
616 // If there is a run of stops before stop i that did not have specified
617 // positions, then this is the index of the first stop in that run.
618 Maybe
<size_t> firstUnsetPosition
;
619 for (size_t i
= 0; i
< aItems
.Length(); ++i
) {
620 const auto& stop
= aItems
[i
];
623 Maybe
<double> specifiedPosition
=
624 GetSpecifiedGradientPosition(stop
, aLineLength
);
626 if (specifiedPosition
) {
627 position
= *specifiedPosition
;
629 // First stop defaults to position 0.0
631 } else if (i
== aItems
.Length() - 1) {
632 // Last stop defaults to position 1.0
635 // Other stops with no specified position get their position assigned
636 // later by interpolation, see below.
637 // Remember where the run of stops with no specified position starts,
638 // if it starts here.
639 if (firstUnsetPosition
.isNothing()) {
640 firstUnsetPosition
.emplace(i
);
642 MOZ_ASSERT(!stop
.IsInterpolationHint(),
643 "Interpolation hints always specify position");
644 auto color
= GetSpecifiedColor(stop
, *aComputedStyle
);
645 stops
.AppendElement(ColorStop(0, false, color
));
650 // Prevent decreasing stop positions by advancing this position
651 // to the previous stop position, if necessary
652 double previousPosition
= firstUnsetPosition
653 ? stops
[*firstUnsetPosition
- 1].mPosition
654 : stops
[i
- 1].mPosition
;
655 position
= std::max(position
, previousPosition
);
657 auto stopColor
= GetSpecifiedColor(stop
, *aComputedStyle
);
659 ColorStop(position
, stop
.IsInterpolationHint(), stopColor
));
660 if (firstUnsetPosition
) {
661 // Interpolate positions for all stops that didn't have a specified
663 double p
= stops
[*firstUnsetPosition
- 1].mPosition
;
664 double d
= (stops
[i
].mPosition
- p
) / (i
- *firstUnsetPosition
+ 1);
665 for (size_t j
= *firstUnsetPosition
; j
< i
; ++j
) {
667 stops
[j
].mPosition
= p
;
669 firstUnsetPosition
.reset();
676 static nsTArray
<ColorStop
> ComputeColorStops(ComputedStyle
* aComputedStyle
,
677 const StyleGradient
& aGradient
,
678 CSSCoord aLineLength
) {
679 if (aGradient
.IsLinear()) {
680 return ComputeColorStopsForItems(
681 aComputedStyle
, aGradient
.AsLinear().items
.AsSpan(), aLineLength
);
683 if (aGradient
.IsRadial()) {
684 return ComputeColorStopsForItems(
685 aComputedStyle
, aGradient
.AsRadial().items
.AsSpan(), aLineLength
);
687 return ComputeColorStopsForItems(
688 aComputedStyle
, aGradient
.AsConic().items
.AsSpan(), aLineLength
);
691 nsCSSGradientRenderer
nsCSSGradientRenderer::Create(
692 nsPresContext
* aPresContext
, ComputedStyle
* aComputedStyle
,
693 const StyleGradient
& aGradient
, const nsSize
& aIntrinsicSize
) {
694 auto srcSize
= CSSSize::FromAppUnits(aIntrinsicSize
);
696 // Compute "gradient line" start and end relative to the intrinsic size of
698 CSSPoint lineStart
, lineEnd
, center
; // center is for conic gradients only
699 CSSCoord radiusX
= 0, radiusY
= 0; // for radial gradients only
700 float angle
= 0.0; // for conic gradients only
701 if (aGradient
.IsLinear()) {
702 std::tie(lineStart
, lineEnd
) =
703 ComputeLinearGradientLine(aPresContext
, aGradient
, srcSize
);
704 } else if (aGradient
.IsRadial()) {
705 std::tie(lineStart
, lineEnd
, radiusX
, radiusY
) =
706 ComputeRadialGradientLine(aGradient
, srcSize
);
708 MOZ_ASSERT(aGradient
.IsConic());
709 std::tie(center
, angle
) =
710 ComputeConicGradientProperties(aGradient
, srcSize
);
712 // Avoid sending Infs or Nans to downwind draw targets.
713 if (!lineStart
.IsFinite() || !lineEnd
.IsFinite()) {
714 lineStart
= lineEnd
= CSSPoint(0, 0);
716 if (!center
.IsFinite()) {
717 center
= CSSPoint(0, 0);
719 CSSCoord lineLength
=
720 NS_hypot(lineEnd
.x
- lineStart
.x
, lineEnd
.y
- lineStart
.y
);
722 // Build color stop array and compute stop positions
723 nsTArray
<ColorStop
> stops
=
724 ComputeColorStops(aComputedStyle
, aGradient
, lineLength
);
726 ResolveMidpoints(stops
);
728 nsCSSGradientRenderer renderer
;
729 renderer
.mPresContext
= aPresContext
;
730 renderer
.mGradient
= &aGradient
;
731 renderer
.mStops
= std::move(stops
);
732 renderer
.mLineStart
= {
733 aPresContext
->CSSPixelsToDevPixels(lineStart
.x
),
734 aPresContext
->CSSPixelsToDevPixels(lineStart
.y
),
736 renderer
.mLineEnd
= {
737 aPresContext
->CSSPixelsToDevPixels(lineEnd
.x
),
738 aPresContext
->CSSPixelsToDevPixels(lineEnd
.y
),
740 renderer
.mRadiusX
= aPresContext
->CSSPixelsToDevPixels(radiusX
);
741 renderer
.mRadiusY
= aPresContext
->CSSPixelsToDevPixels(radiusY
);
743 aPresContext
->CSSPixelsToDevPixels(center
.x
),
744 aPresContext
->CSSPixelsToDevPixels(center
.y
),
746 renderer
.mAngle
= angle
;
750 void nsCSSGradientRenderer::Paint(gfxContext
& aContext
, const nsRect
& aDest
,
751 const nsRect
& aFillArea
,
752 const nsSize
& aRepeatSize
,
753 const CSSIntRect
& aSrc
,
754 const nsRect
& aDirtyRect
, float aOpacity
) {
755 AUTO_PROFILER_LABEL("nsCSSGradientRenderer::Paint", GRAPHICS
);
757 if (aDest
.IsEmpty() || aFillArea
.IsEmpty()) {
761 nscoord appUnitsPerDevPixel
= mPresContext
->AppUnitsPerDevPixel();
763 gfxFloat lineLength
=
764 NS_hypot(mLineEnd
.x
- mLineStart
.x
, mLineEnd
.y
- mLineStart
.y
);
765 bool cellContainsFill
= aDest
.Contains(aFillArea
);
767 // If a non-repeating linear gradient is axis-aligned and there are no gaps
768 // between tiles, we can optimise away most of the work by converting to a
769 // repeating linear gradient and filling the whole destination rect at once.
770 bool forceRepeatToCoverTiles
=
771 mGradient
->IsLinear() &&
772 (mLineStart
.x
== mLineEnd
.x
) != (mLineStart
.y
== mLineEnd
.y
) &&
773 aRepeatSize
.width
== aDest
.width
&& aRepeatSize
.height
== aDest
.height
&&
774 !(mGradient
->Repeating()) && !aSrc
.IsEmpty() && !cellContainsFill
;
777 if (forceRepeatToCoverTiles
) {
778 // Length of the source rectangle along the gradient axis.
780 // The position of the start of the rectangle along the gradient.
783 // The gradient line is "backwards". Flip the line upside down to make
784 // things easier, and then rotate the matrix to turn everything back the
786 if (mLineStart
.x
> mLineEnd
.x
|| mLineStart
.y
> mLineEnd
.y
) {
787 std::swap(mLineStart
, mLineEnd
);
788 matrix
.PreScale(-1, -1);
791 // Fit the gradient line exactly into the source rect.
792 // aSrc is relative to aIntrinsincSize.
793 // srcRectDev will be relative to srcSize, so in the same coordinate space
794 // as lineStart / lineEnd.
795 gfxRect srcRectDev
= nsLayoutUtils::RectToGfxRect(
796 CSSPixel::ToAppUnits(aSrc
), appUnitsPerDevPixel
);
797 if (mLineStart
.x
!= mLineEnd
.x
) {
798 rectLen
= srcRectDev
.width
;
799 offset
= (srcRectDev
.x
- mLineStart
.x
) / lineLength
;
800 mLineStart
.x
= srcRectDev
.x
;
801 mLineEnd
.x
= srcRectDev
.XMost();
803 rectLen
= srcRectDev
.height
;
804 offset
= (srcRectDev
.y
- mLineStart
.y
) / lineLength
;
805 mLineStart
.y
= srcRectDev
.y
;
806 mLineEnd
.y
= srcRectDev
.YMost();
809 // Adjust gradient stop positions for the new gradient line.
810 double scale
= lineLength
/ rectLen
;
811 for (size_t i
= 0; i
< mStops
.Length(); i
++) {
812 mStops
[i
].mPosition
= (mStops
[i
].mPosition
- offset
) * fabs(scale
);
815 // Clamp or extrapolate gradient stops to exactly [0, 1].
816 ClampColorStops(mStops
);
818 lineLength
= rectLen
;
821 // Eliminate negative-position stops if the gradient is radial.
822 double firstStop
= mStops
[0].mPosition
;
823 if (mGradient
->IsRadial() && firstStop
< 0.0) {
824 if (mGradient
->AsRadial().flags
& StyleGradientFlags::REPEATING
) {
825 // Choose an instance of the repeated pattern that gives us all positive
827 double lastStop
= mStops
[mStops
.Length() - 1].mPosition
;
828 double stopDelta
= lastStop
- firstStop
;
829 // If all the stops are in approximately the same place then logic below
830 // will kick in that makes us draw just the last stop color, so don't
831 // try to do anything in that case. We certainly need to avoid
833 if (stopDelta
>= 1e-6) {
834 double instanceCount
= ceil(-firstStop
/ stopDelta
);
835 // Advance stops by instanceCount multiples of the period of the
836 // repeating gradient.
837 double offset
= instanceCount
* stopDelta
;
838 for (uint32_t i
= 0; i
< mStops
.Length(); i
++) {
839 mStops
[i
].mPosition
+= offset
;
843 // Move negative-position stops to position 0.0. We may also need
844 // to set the color of the stop to the color the gradient should have
845 // at the center of the ellipse.
846 for (uint32_t i
= 0; i
< mStops
.Length(); i
++) {
847 double pos
= mStops
[i
].mPosition
;
849 mStops
[i
].mPosition
= 0.0;
850 // If this is the last stop, we don't need to adjust the color,
851 // it will fill the entire area.
852 if (i
< mStops
.Length() - 1) {
853 double nextPos
= mStops
[i
+ 1].mPosition
;
854 // If nextPos is approximately equal to pos, then we don't
855 // need to adjust the color of this stop because it's
856 // not going to be displayed.
857 // If nextPos is negative, we don't need to adjust the color of
858 // this stop since it's not going to be displayed because
859 // nextPos will also be moved to 0.0.
860 if (nextPos
>= 0.0 && nextPos
- pos
>= 1e-6) {
861 // Compute how far the new position 0.0 is along the interval
862 // between pos and nextPos.
863 // XXX Color interpolation (in cairo, too) should use the
864 // CSS 'color-interpolation' property!
865 float frac
= float((0.0 - pos
) / (nextPos
- pos
));
867 Interpolate(mStops
[i
].mColor
, mStops
[i
+ 1].mColor
, frac
);
873 firstStop
= mStops
[0].mPosition
;
874 MOZ_ASSERT(firstStop
>= 0.0, "Failed to fix stop offsets");
877 if (mGradient
->IsRadial() &&
878 !(mGradient
->AsRadial().flags
& StyleGradientFlags::REPEATING
)) {
879 // Direct2D can only handle a particular class of radial gradients because
880 // of the way the it specifies gradients. Setting firstStop to 0, when we
881 // can, will help us stay on the fast path. Currently we don't do this
882 // for repeating gradients but we could by adjusting the stop collection
887 double lastStop
= mStops
[mStops
.Length() - 1].mPosition
;
888 // Cairo gradients must have stop positions in the range [0, 1]. So,
889 // stop positions will be normalized below by subtracting firstStop and then
890 // multiplying by stopScale.
892 double stopOrigin
= firstStop
;
893 double stopEnd
= lastStop
;
894 double stopDelta
= lastStop
- firstStop
;
896 mGradient
->IsRadial() && (mRadiusX
< 1e-6 || mRadiusY
< 1e-6);
897 if (stopDelta
< 1e-6 || (!mGradient
->IsConic() && lineLength
< 1e-6) ||
899 // Stops are all at the same place. Map all stops to 0.0.
900 // For repeating radial gradients, or for any radial gradients with
901 // a zero radius, we need to fill with the last stop color, so just set
903 if (mGradient
->Repeating() || zeroRadius
) {
904 mRadiusX
= mRadiusY
= 0.0;
909 // Don't normalize non-repeating or degenerate gradients below 0..1
910 // This keeps the gradient line as large as the box and doesn't
911 // lets us avoiding having to get padding correct for stops
913 if (!mGradient
->Repeating() || stopDelta
== 0.0) {
914 stopOrigin
= std::min(stopOrigin
, 0.0);
915 stopEnd
= std::max(stopEnd
, 1.0);
917 stopScale
= 1.0 / (stopEnd
- stopOrigin
);
919 // Create the gradient pattern.
920 RefPtr
<gfxPattern
> gradientPattern
;
921 gfxPoint gradientStart
;
922 gfxPoint gradientEnd
;
923 if (mGradient
->IsLinear()) {
924 // Compute the actual gradient line ends we need to pass to cairo after
925 // stops have been normalized.
926 gradientStart
= mLineStart
+ (mLineEnd
- mLineStart
) * stopOrigin
;
927 gradientEnd
= mLineStart
+ (mLineEnd
- mLineStart
) * stopEnd
;
929 if (stopDelta
== 0.0) {
930 // Stops are all at the same place. For repeating gradients, this will
931 // just paint the last stop color. We don't need to do anything.
932 // For non-repeating gradients, this should render as two colors, one
933 // on each "side" of the gradient line segment, which is a point. All
934 // our stops will be at 0.0; we just need to set the direction vector
936 gradientEnd
= gradientStart
+ (mLineEnd
- mLineStart
);
939 gradientPattern
= new gfxPattern(gradientStart
.x
, gradientStart
.y
,
940 gradientEnd
.x
, gradientEnd
.y
);
941 } else if (mGradient
->IsRadial()) {
942 NS_ASSERTION(firstStop
>= 0.0,
943 "Negative stops not allowed for radial gradients");
945 // To form an ellipse, we'll stretch a circle vertically, if necessary.
946 // So our radii are based on radiusX.
947 double innerRadius
= mRadiusX
* stopOrigin
;
948 double outerRadius
= mRadiusX
* stopEnd
;
949 if (stopDelta
== 0.0) {
950 // Stops are all at the same place. See above (except we now have
951 // the inside vs. outside of an ellipse).
952 outerRadius
= innerRadius
+ 1;
954 gradientPattern
= new gfxPattern(mLineStart
.x
, mLineStart
.y
, innerRadius
,
955 mLineStart
.x
, mLineStart
.y
, outerRadius
);
956 if (mRadiusX
!= mRadiusY
) {
957 // Stretch the circles into ellipses vertically by setting a transform
959 // Recall that this is the transform from user space to pattern space.
960 // So to stretch the ellipse by factor of P vertically, we scale
961 // user coordinates by 1/P.
962 matrix
.PreTranslate(mLineStart
);
963 matrix
.PreScale(1.0, mRadiusX
/ mRadiusY
);
964 matrix
.PreTranslate(-mLineStart
);
968 new gfxPattern(mCenter
.x
, mCenter
.y
, mAngle
, stopOrigin
, stopEnd
);
970 // Use a pattern transform to take account of source and dest rects
971 matrix
.PreTranslate(gfxPoint(mPresContext
->CSSPixelsToDevPixels(aSrc
.x
),
972 mPresContext
->CSSPixelsToDevPixels(aSrc
.y
)));
974 gfxFloat(nsPresContext::CSSPixelsToAppUnits(aSrc
.width
)) / aDest
.width
,
975 gfxFloat(nsPresContext::CSSPixelsToAppUnits(aSrc
.height
)) / aDest
.height
);
976 gradientPattern
->SetMatrix(matrix
);
978 if (stopDelta
== 0.0) {
979 // Non-repeating gradient with all stops in same place -> just add
980 // first stop and last stop, both at position 0.
981 // Repeating gradient with all stops in the same place, or radial
982 // gradient with radius of 0 -> just paint the last stop color.
983 // We use firstStop offset to keep |stops| with same units (will later
985 auto firstColor(mStops
[0].mColor
);
986 auto lastColor(mStops
.LastElement().mColor
);
989 if (!mGradient
->Repeating() && !zeroRadius
) {
990 mStops
.AppendElement(ColorStop(firstStop
, false, firstColor
));
992 mStops
.AppendElement(ColorStop(firstStop
, false, lastColor
));
995 ResolvePremultipliedAlpha(mStops
);
997 bool isRepeat
= mGradient
->Repeating() || forceRepeatToCoverTiles
;
999 // Now set normalized color stops in pattern.
1000 // Offscreen gradient surface cache (not a tile):
1001 // On some backends (e.g. D2D), the GradientStops object holds an offscreen
1002 // surface which is a lookup table used to evaluate the gradient. This surface
1003 // can use much memory (ram and/or GPU ram) and can be expensive to create. So
1004 // we cache it. The cache key correlates 1:1 with the arguments for
1005 // CreateGradientStops (also the implied backend type) Note that GradientStop
1006 // is a simple struct with a stop value (while GradientStops has the surface).
1007 nsTArray
<gfx::GradientStop
> rawStops(mStops
.Length());
1008 rawStops
.SetLength(mStops
.Length());
1009 for (uint32_t i
= 0; i
< mStops
.Length(); i
++) {
1010 rawStops
[i
].color
= ToDeviceColor(mStops
[i
].mColor
);
1011 rawStops
[i
].color
.a
*= aOpacity
;
1012 rawStops
[i
].offset
= stopScale
* (mStops
[i
].mPosition
- stopOrigin
);
1014 RefPtr
<mozilla::gfx::GradientStops
> gs
=
1015 gfxGradientCache::GetOrCreateGradientStops(
1016 aContext
.GetDrawTarget(), rawStops
,
1017 isRepeat
? gfx::ExtendMode::REPEAT
: gfx::ExtendMode::CLAMP
);
1018 gradientPattern
->SetColorStops(gs
);
1020 // Paint gradient tiles. This isn't terribly efficient, but doing it this
1021 // way is simple and sure to get pixel-snapping right. We could speed things
1022 // up by drawing tiles into temporary surfaces and copying those to the
1023 // destination, but after pixel-snapping tiles may not all be the same size.
1025 if (!dirty
.IntersectRect(aDirtyRect
, aFillArea
)) return;
1027 gfxRect areaToFill
=
1028 nsLayoutUtils::RectToGfxRect(aFillArea
, appUnitsPerDevPixel
);
1029 gfxRect dirtyAreaToFill
=
1030 nsLayoutUtils::RectToGfxRect(dirty
, appUnitsPerDevPixel
);
1031 dirtyAreaToFill
.RoundOut();
1033 Matrix ctm
= aContext
.CurrentMatrix();
1034 bool isCTMPreservingAxisAlignedRectangles
=
1035 ctm
.PreservesAxisAlignedRectangles();
1037 // xStart/yStart are the top-left corner of the top-left tile.
1038 nscoord xStart
= FindTileStart(dirty
.x
, aDest
.x
, aRepeatSize
.width
);
1039 nscoord yStart
= FindTileStart(dirty
.y
, aDest
.y
, aRepeatSize
.height
);
1040 nscoord xEnd
= forceRepeatToCoverTiles
? xStart
+ aDest
.width
: dirty
.XMost();
1042 forceRepeatToCoverTiles
? yStart
+ aDest
.height
: dirty
.YMost();
1044 if (TryPaintTilesWithExtendMode(aContext
, gradientPattern
, xStart
, yStart
,
1045 dirtyAreaToFill
, aDest
, aRepeatSize
,
1046 forceRepeatToCoverTiles
)) {
1050 // x and y are the top-left corner of the tile to draw
1051 for (nscoord y
= yStart
; y
< yEnd
; y
+= aRepeatSize
.height
) {
1052 for (nscoord x
= xStart
; x
< xEnd
; x
+= aRepeatSize
.width
) {
1053 // The coordinates of the tile
1054 gfxRect tileRect
= nsLayoutUtils::RectToGfxRect(
1055 nsRect(x
, y
, aDest
.width
, aDest
.height
), appUnitsPerDevPixel
);
1056 // The actual area to fill with this tile is the intersection of this
1057 // tile with the overall area we're supposed to be filling
1059 forceRepeatToCoverTiles
? areaToFill
: tileRect
.Intersect(areaToFill
);
1060 // Try snapping the fill rect. Snap its top-left and bottom-right
1061 // independently to preserve the orientation.
1062 gfxPoint snappedFillRectTopLeft
= fillRect
.TopLeft();
1063 gfxPoint snappedFillRectTopRight
= fillRect
.TopRight();
1064 gfxPoint snappedFillRectBottomRight
= fillRect
.BottomRight();
1065 // Snap three points instead of just two to ensure we choose the
1066 // correct orientation if there's a reflection.
1067 if (isCTMPreservingAxisAlignedRectangles
&&
1068 aContext
.UserToDevicePixelSnapped(snappedFillRectTopLeft
, true) &&
1069 aContext
.UserToDevicePixelSnapped(snappedFillRectBottomRight
, true) &&
1070 aContext
.UserToDevicePixelSnapped(snappedFillRectTopRight
, true)) {
1071 if (snappedFillRectTopLeft
.x
== snappedFillRectBottomRight
.x
||
1072 snappedFillRectTopLeft
.y
== snappedFillRectBottomRight
.y
) {
1073 // Nothing to draw; avoid scaling by zero and other weirdness that
1074 // could put the context in an error state.
1077 // Set the context's transform to the transform that maps fillRect to
1078 // snappedFillRect. The part of the gradient that was going to
1079 // exactly fill fillRect will fill snappedFillRect instead.
1080 gfxMatrix transform
= gfxUtils::TransformRectToRect(
1081 fillRect
, snappedFillRectTopLeft
, snappedFillRectTopRight
,
1082 snappedFillRectBottomRight
);
1083 aContext
.SetMatrixDouble(transform
);
1086 aContext
.Rectangle(fillRect
);
1088 gfxRect dirtyFillRect
= fillRect
.Intersect(dirtyAreaToFill
);
1089 gfxRect fillRectRelativeToTile
= dirtyFillRect
- tileRect
.TopLeft();
1090 auto edgeColor
= StyleAbsoluteColor::TRANSPARENT_BLACK
;
1091 if (mGradient
->IsLinear() && !isRepeat
&&
1092 RectIsBeyondLinearGradientEdge(fillRectRelativeToTile
, matrix
, mStops
,
1093 gradientStart
, gradientEnd
,
1095 edgeColor
.alpha
*= aOpacity
;
1096 aContext
.SetColor(ToSRGBColor(edgeColor
));
1098 aContext
.SetMatrixDouble(
1099 aContext
.CurrentMatrixDouble().Copy().PreTranslate(
1100 tileRect
.TopLeft()));
1101 aContext
.SetPattern(gradientPattern
);
1104 aContext
.SetMatrix(ctm
);
1109 bool nsCSSGradientRenderer::TryPaintTilesWithExtendMode(
1110 gfxContext
& aContext
, gfxPattern
* aGradientPattern
, nscoord aXStart
,
1111 nscoord aYStart
, const gfxRect
& aDirtyAreaToFill
, const nsRect
& aDest
,
1112 const nsSize
& aRepeatSize
, bool aForceRepeatToCoverTiles
) {
1113 // If we have forced a non-repeating gradient to repeat to cover tiles,
1114 // then it will be faster to just paint it once using that optimization
1115 if (aForceRepeatToCoverTiles
) {
1119 nscoord appUnitsPerDevPixel
= mPresContext
->AppUnitsPerDevPixel();
1121 // We can only use this fast path if we don't have to worry about pixel
1122 // snapping, and there is no spacing between tiles. We could handle spacing
1123 // by increasing the size of tileSurface and leaving it transparent, but I'm
1124 // not sure it's worth it
1125 bool canUseExtendModeForTiling
= (aXStart
% appUnitsPerDevPixel
== 0) &&
1126 (aYStart
% appUnitsPerDevPixel
== 0) &&
1127 (aDest
.width
% appUnitsPerDevPixel
== 0) &&
1128 (aDest
.height
% appUnitsPerDevPixel
== 0) &&
1129 (aRepeatSize
.width
== aDest
.width
) &&
1130 (aRepeatSize
.height
== aDest
.height
);
1132 if (!canUseExtendModeForTiling
) {
1137 NSAppUnitsToIntPixels(aDest
.width
, appUnitsPerDevPixel
),
1138 NSAppUnitsToIntPixels(aDest
.height
, appUnitsPerDevPixel
),
1141 // Check whether this is a reasonable surface size and doesn't overflow
1142 // before doing calculations with the tile size
1143 if (!Factory::ReasonableSurfaceSize(tileSize
)) {
1147 // We only want to do this when there are enough tiles to justify the
1148 // overhead of painting to an offscreen surface. The heuristic here
1149 // is when we will be painting at least 16 tiles or more, this is kind
1151 bool shouldUseExtendModeForTiling
=
1152 aDirtyAreaToFill
.Area() > (tileSize
.width
* tileSize
.height
) * 16.0;
1154 if (!shouldUseExtendModeForTiling
) {
1158 // Draw the gradient pattern into a surface for our single tile
1159 RefPtr
<gfx::SourceSurface
> tileSurface
;
1161 RefPtr
<gfx::DrawTarget
> tileTarget
=
1162 aContext
.GetDrawTarget()->CreateSimilarDrawTarget(
1163 tileSize
, gfx::SurfaceFormat::B8G8R8A8
);
1164 if (!tileTarget
|| !tileTarget
->IsValid()) {
1169 gfxContext
tileContext(tileTarget
);
1171 tileContext
.SetPattern(aGradientPattern
);
1172 tileContext
.Paint();
1175 tileSurface
= tileTarget
->Snapshot();
1176 tileTarget
= nullptr;
1179 // Draw the gradient using tileSurface as a repeating pattern masked by
1181 Matrix tileTransform
= Matrix::Translation(
1182 NSAppUnitsToFloatPixels(aXStart
, appUnitsPerDevPixel
),
1183 NSAppUnitsToFloatPixels(aYStart
, appUnitsPerDevPixel
));
1186 aContext
.Rectangle(aDirtyAreaToFill
);
1187 aContext
.Fill(SurfacePattern(tileSurface
, ExtendMode::REPEAT
, tileTransform
));
1192 class MOZ_STACK_CLASS WrColorStopInterpolator
1193 : public ColorStopInterpolator
<WrColorStopInterpolator
> {
1195 WrColorStopInterpolator(
1196 const nsTArray
<ColorStop
>& aStops
,
1197 const StyleColorInterpolationMethod
& aStyleColorInterpolationMethod
,
1198 float aOpacity
, nsTArray
<wr::GradientStop
>& aResult
)
1199 : ColorStopInterpolator(aStops
, aStyleColorInterpolationMethod
),
1204 void CreateStops() {
1205 mResult
.SetLengthAndRetainStorage(0);
1206 // we always emit at least two stops (start and end) for each input stop,
1207 // which avoids ambiguity with incomplete oklch/lch/hsv/hsb color stops for
1208 // the last stop pair, where the last color stop can't be interpreted on its
1209 // own because it actually depends on the previous stop.
1210 mResult
.SetLength(mStops
.Length() * 2 + kFullRangeExtraStops
);
1212 ColorStopInterpolator::CreateStops();
1213 mResult
.SetLength(mOutputStop
);
1216 void CreateStop(float aPosition
, DeviceColor aColor
) {
1217 if (mOutputStop
< mResult
.Capacity()) {
1218 mResult
[mOutputStop
].color
= wr::ToColorF(aColor
);
1219 mResult
[mOutputStop
].color
.a
*= mOpacity
;
1220 mResult
[mOutputStop
].offset
= aPosition
;
1226 nsTArray
<wr::GradientStop
>& mResult
;
1228 uint32_t mOutputStop
;
1231 void nsCSSGradientRenderer::BuildWebRenderParameters(
1232 float aOpacity
, wr::ExtendMode
& aMode
, nsTArray
<wr::GradientStop
>& aStops
,
1233 LayoutDevicePoint
& aLineStart
, LayoutDevicePoint
& aLineEnd
,
1234 LayoutDeviceSize
& aGradientRadius
, LayoutDevicePoint
& aGradientCenter
,
1235 float& aGradientAngle
) {
1237 mGradient
->Repeating() ? wr::ExtendMode::Repeat
: wr::ExtendMode::Clamp
;
1239 // If the interpolation space is not sRGB, or if color management is active,
1240 // we need to add additional stops so that the sRGB interpolation in WebRender
1241 // still closely approximates the correct curves. We prefer avoiding this if
1242 // the gradient is simple because WebRender has fast rendering of linear
1243 // gradients with 2 stops (which represent >99% of all gradients on the web).
1245 // WebRender doesn't have easy access to StyleAbsoluteColor and CMS display
1246 // color correction, so we just expand the gradient stop table significantly
1247 // so that gamma and hue interpolation errors become imperceptible.
1249 // This always turns into 128 pairs of stops inside WebRender as an
1250 // implementation detail, so the number of stops we generate here should have
1251 // very little impact on performance as the texture upload is always the same,
1252 // except for the special linear gradient 2-stop case, and it is gpucache so
1253 // if it does not change it is not re-uploaded.
1255 // Color management bugs that this addresses:
1256 // * https://bugzilla.mozilla.org/show_bug.cgi?id=939387
1257 // * https://bugzilla.mozilla.org/show_bug.cgi?id=1248178
1258 StyleColorInterpolationMethod styleColorInterpolationMethod
=
1259 mGradient
->ColorInterpolationMethod();
1260 if (mStops
.Length() >= 2 &&
1261 (styleColorInterpolationMethod
.space
!= StyleColorSpace::Srgb
||
1262 gfxPlatform::GetCMSMode() == CMSMode::All
)) {
1263 WrColorStopInterpolator
interpolator(mStops
, styleColorInterpolationMethod
,
1265 interpolator
.CreateStops();
1267 aStops
.SetLength(mStops
.Length());
1268 for (uint32_t i
= 0; i
< mStops
.Length(); i
++) {
1269 aStops
[i
].color
= wr::ToColorF(ToDeviceColor(mStops
[i
].mColor
));
1270 aStops
[i
].color
.a
*= aOpacity
;
1271 aStops
[i
].offset
= (float)mStops
[i
].mPosition
;
1275 aLineStart
= LayoutDevicePoint(mLineStart
.x
, mLineStart
.y
);
1276 aLineEnd
= LayoutDevicePoint(mLineEnd
.x
, mLineEnd
.y
);
1277 aGradientRadius
= LayoutDeviceSize(mRadiusX
, mRadiusY
);
1278 aGradientCenter
= LayoutDevicePoint(mCenter
.x
, mCenter
.y
);
1279 aGradientAngle
= mAngle
;
1282 void nsCSSGradientRenderer::BuildWebRenderDisplayItems(
1283 wr::DisplayListBuilder
& aBuilder
, const layers::StackingContextHelper
& aSc
,
1284 const nsRect
& aDest
, const nsRect
& aFillArea
, const nsSize
& aRepeatSize
,
1285 const CSSIntRect
& aSrc
, bool aIsBackfaceVisible
, float aOpacity
) {
1286 if (aDest
.IsEmpty() || aFillArea
.IsEmpty()) {
1290 wr::ExtendMode extendMode
;
1291 nsTArray
<wr::GradientStop
> stops
;
1292 LayoutDevicePoint lineStart
;
1293 LayoutDevicePoint lineEnd
;
1294 LayoutDeviceSize gradientRadius
;
1295 LayoutDevicePoint gradientCenter
;
1296 float gradientAngle
;
1297 BuildWebRenderParameters(aOpacity
, extendMode
, stops
, lineStart
, lineEnd
,
1298 gradientRadius
, gradientCenter
, gradientAngle
);
1300 nscoord appUnitsPerDevPixel
= mPresContext
->AppUnitsPerDevPixel();
1303 nsPoint(FindTileStart(aFillArea
.x
, aDest
.x
, aRepeatSize
.width
),
1304 FindTileStart(aFillArea
.y
, aDest
.y
, aRepeatSize
.height
));
1306 // Translate the parameters into device coordinates
1307 LayoutDeviceRect clipBounds
=
1308 LayoutDevicePixel::FromAppUnits(aFillArea
, appUnitsPerDevPixel
);
1309 LayoutDeviceRect firstTileBounds
= LayoutDevicePixel::FromAppUnits(
1310 nsRect(firstTile
, aDest
.Size()), appUnitsPerDevPixel
);
1311 LayoutDeviceSize tileRepeat
=
1312 LayoutDevicePixel::FromAppUnits(aRepeatSize
, appUnitsPerDevPixel
);
1314 // Calculate the bounds of the gradient display item, which starts at the
1315 // first tile and extends to the end of clip bounds
1316 LayoutDevicePoint tileToClip
=
1317 clipBounds
.BottomRight() - firstTileBounds
.TopLeft();
1318 LayoutDeviceRect gradientBounds
= LayoutDeviceRect(
1319 firstTileBounds
.TopLeft(), LayoutDeviceSize(tileToClip
.x
, tileToClip
.y
));
1321 // Calculate the tile spacing, which is the repeat size minus the tile size
1322 LayoutDeviceSize tileSpacing
= tileRepeat
- firstTileBounds
.Size();
1324 // srcTransform is used for scaling the gradient to match aSrc
1325 LayoutDeviceRect srcTransform
= LayoutDeviceRect(
1326 nsPresContext::CSSPixelsToAppUnits(aSrc
.x
),
1327 nsPresContext::CSSPixelsToAppUnits(aSrc
.y
),
1328 aDest
.width
/ ((float)nsPresContext::CSSPixelsToAppUnits(aSrc
.width
)),
1329 aDest
.height
/ ((float)nsPresContext::CSSPixelsToAppUnits(aSrc
.height
)));
1331 lineStart
.x
= (lineStart
.x
- srcTransform
.x
) * srcTransform
.width
;
1332 lineStart
.y
= (lineStart
.y
- srcTransform
.y
) * srcTransform
.height
;
1334 gradientCenter
.x
= (gradientCenter
.x
- srcTransform
.x
) * srcTransform
.width
;
1335 gradientCenter
.y
= (gradientCenter
.y
- srcTransform
.y
) * srcTransform
.height
;
1337 if (mGradient
->IsLinear()) {
1338 lineEnd
.x
= (lineEnd
.x
- srcTransform
.x
) * srcTransform
.width
;
1339 lineEnd
.y
= (lineEnd
.y
- srcTransform
.y
) * srcTransform
.height
;
1341 aBuilder
.PushLinearGradient(
1342 mozilla::wr::ToLayoutRect(gradientBounds
),
1343 mozilla::wr::ToLayoutRect(clipBounds
), aIsBackfaceVisible
,
1344 mozilla::wr::ToLayoutPoint(lineStart
),
1345 mozilla::wr::ToLayoutPoint(lineEnd
), stops
, extendMode
,
1346 mozilla::wr::ToLayoutSize(firstTileBounds
.Size()),
1347 mozilla::wr::ToLayoutSize(tileSpacing
));
1348 } else if (mGradient
->IsRadial()) {
1349 gradientRadius
.width
*= srcTransform
.width
;
1350 gradientRadius
.height
*= srcTransform
.height
;
1352 aBuilder
.PushRadialGradient(
1353 mozilla::wr::ToLayoutRect(gradientBounds
),
1354 mozilla::wr::ToLayoutRect(clipBounds
), aIsBackfaceVisible
,
1355 mozilla::wr::ToLayoutPoint(lineStart
),
1356 mozilla::wr::ToLayoutSize(gradientRadius
), stops
, extendMode
,
1357 mozilla::wr::ToLayoutSize(firstTileBounds
.Size()),
1358 mozilla::wr::ToLayoutSize(tileSpacing
));
1360 MOZ_ASSERT(mGradient
->IsConic());
1361 aBuilder
.PushConicGradient(
1362 mozilla::wr::ToLayoutRect(gradientBounds
),
1363 mozilla::wr::ToLayoutRect(clipBounds
), aIsBackfaceVisible
,
1364 mozilla::wr::ToLayoutPoint(gradientCenter
), gradientAngle
, stops
,
1365 extendMode
, mozilla::wr::ToLayoutSize(firstTileBounds
.Size()),
1366 mozilla::wr::ToLayoutSize(tileSpacing
));
1370 } // namespace mozilla