Bug 1839315: part 4) Link from `SheetLoadData::mWasAlternate` to spec. r=emilio DONTBUILD
[gecko.git] / layout / painting / nsCSSRenderingGradients.cpp
bloba2b464221de51420d3d0b5824ec3de5dacf2f18a
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"
11 #include <tuple>
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"
25 #include "nsPoint.h"
26 #include "nsRect.h"
27 #include "nsCSSColorUtils.h"
28 #include "gfxContext.h"
29 #include "nsStyleStructInlines.h"
30 #include "nsCSSProps.h"
31 #include "gfxUtils.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"
38 #include "Units.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
53 // direction aAngle.
54 static CSSPoint ComputeGradientLineEndFromAngle(const CSSPoint& aStart,
55 double aAngle,
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;
74 const bool isModern =
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();
81 if (isModern) {
82 angle = M_PI_2 - angle;
84 CSSPoint end = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
85 CSSPoint start = CSSPoint(aBoxSize.width, aBoxSize.height) - end;
86 return {start, 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);
94 return {start, end};
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);
102 return {start, end};
104 case StyleLineDirection::Tag::Corner: {
105 const auto& corner = direction.AsCorner();
106 const X& h = corner._0;
107 const Y& v = corner._1;
109 if (isModern) {
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;
115 return {start, 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;
123 return {start, end};
125 default:
126 break;
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
159 // ellipse to use.
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);
184 break;
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);
191 } else {
192 // maintain aspect ratio
193 radiusX = offsetX * M_SQRT2;
194 radiusY = offsetY * M_SQRT2;
196 break;
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);
204 break;
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);
211 } else {
212 // maintain aspect ratio
213 radiusX = offsetX * M_SQRT2;
214 radiusY = offsetY * M_SQRT2;
216 break;
218 default:
219 MOZ_ASSERT_UNREACHABLE("Unknown shape extent keyword?");
220 radiusX = radiusY = 0;
222 } else {
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
229 // the ellipse.
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,
251 float aFrac) {
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,
263 nscoord aTileDim) {
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.
279 * t is given by
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
288 * is not zero.
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;
318 return true;
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;
325 return true;
328 return false;
331 static void ResolveMidpoints(nsTArray<ColorStop>& stops) {
332 for (size_t x = 1; x < stops.Length() - 1;) {
333 if (!stops[x].mIsMidpoint) {
334 x++;
335 continue;
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);
346 continue;
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
352 // color stop.
353 stops[x].mColor = color2;
354 stops[x].mIsMidpoint = false;
355 continue;
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
361 // color stop.
362 stops[x].mColor = color1;
363 stops[x].mIsMidpoint = false;
364 continue;
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;
376 } else {
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;
384 // calculate colors
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);
399 const float red =
400 srgb1.components._0 +
401 multiplier * (srgb2.components._0 - srgb1.components._0);
402 const float green =
403 srgb1.components._1 +
404 multiplier * (srgb2.components._1 - srgb1.components._1);
405 const float blue =
406 srgb1.components._2 +
407 multiplier * (srgb2.components._2 - srgb1.components._2);
408 const float alpha =
409 srgb1.alpha + multiplier * (srgb2.alpha - srgb1.alpha);
411 newStop.mColor = StyleAbsoluteColor::SrgbLegacy(red, green, blue, alpha);
414 stops.ReplaceElementsAt(x, 1, newStops, 9);
415 x += 9;
419 static StyleAbsoluteColor TransparentColor(const StyleAbsoluteColor& aColor) {
420 auto color = aColor;
421 color.alpha = 0.0f;
422 return color;
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) {
439 continue;
442 // Is the stop on the left 100% transparent? If so, have it adopt the color
443 // of the right stop
444 if (leftStop.mColor.alpha == 0) {
445 aStops[x - 1].mColor = TransparentColor(rightStop.mColor);
446 continue;
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);
455 x++;
456 continue;
459 // Now handle cases where one or both of the stops are partially
460 // transparent.
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.
463 size_t stepCount =
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;
468 ColorStop newStop(
469 Interpolate(leftStop.mPosition, rightStop.mPosition, frac), false,
470 Interpolate(leftStop.mColor, rightStop.mColor, frac));
471 aStops.InsertElementAt(x, newStop);
472 x++;
478 static ColorStop InterpolateColorStop(const ColorStop& aFirst,
479 const ColorStop& aSecond,
480 double aPosition,
481 const StyleAbsoluteColor& aDefault) {
482 MOZ_ASSERT(aFirst.mPosition <= aPosition);
483 MOZ_ASSERT(aPosition <= aSecond.mPosition);
485 double delta = aSecond.mPosition - aFirst.mPosition;
486 if (delta < 1e-6) {
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
496 // range [0, 1].
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;
506 aStops.Clear();
507 aStops.AppendElement(ColorStop(0, false, c));
508 return;
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.
518 aStops[i] =
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.
526 aStops[i - 1] =
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);
531 break;
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));
548 namespace mozilla {
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()) {
568 return Nothing();
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) {
580 return Some(0.0);
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()) {
590 return Nothing();
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];
619 double position;
621 Maybe<double> specifiedPosition =
622 GetSpecifiedGradientPosition(stop, aLineLength);
624 if (specifiedPosition) {
625 position = *specifiedPosition;
626 } else if (i == 0) {
627 // First stop defaults to position 0.0
628 position = 0.0;
629 } else if (i == aItems.Length() - 1) {
630 // Last stop defaults to position 1.0
631 position = 1.0;
632 } else {
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));
644 continue;
647 if (i > 0) {
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);
656 stops.AppendElement(
657 ColorStop(position, stop.IsInterpolationHint(), stopColor));
658 if (firstUnsetPosition) {
659 // Interpolate positions for all stops that didn't have a specified
660 // position
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) {
664 p += d;
665 stops[j].mPosition = p;
667 firstUnsetPosition.reset();
671 return stops;
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
695 // the gradient.
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);
705 } else {
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);
740 renderer.mCenter = {
741 aPresContext->CSSPixelsToDevPixels(center.x),
742 aPresContext->CSSPixelsToDevPixels(center.y),
744 renderer.mAngle = angle;
745 return renderer;
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()) {
756 return;
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;
774 gfxMatrix matrix;
775 if (forceRepeatToCoverTiles) {
776 // Length of the source rectangle along the gradient axis.
777 double rectLen;
778 // The position of the start of the rectangle along the gradient.
779 double offset;
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
783 // right way up.
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();
800 } else {
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
824 // stop-offsets.
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
830 // dividing by zero.
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;
840 } else {
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;
846 if (pos < 0.0) {
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));
864 mStops[i].mColor =
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
881 // to start at 0
882 firstStop = 0;
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.
889 double stopScale;
890 double stopOrigin = firstStop;
891 double stopEnd = lastStop;
892 double stopDelta = lastStop - firstStop;
893 bool zeroRadius =
894 mGradient->IsRadial() && (mRadiusX < 1e-6 || mRadiusY < 1e-6);
895 if (stopDelta < 1e-6 || (!mGradient->IsConic() && lineLength < 1e-6) ||
896 zeroRadius) {
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
900 // both radii to 0.
901 if (mGradient->Repeating() || zeroRadius) {
902 mRadiusX = mRadiusY = 0.0;
904 stopDelta = 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
910 // at 0 and 1
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
933 // correctly.
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
956 // in the pattern.
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);
964 } else {
965 gradientPattern =
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)));
971 matrix.PreScale(
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
982 // normalize to 0).
983 auto firstColor(mStops[0].mColor);
984 auto lastColor(mStops.LastElement().mColor);
985 mStops.Clear();
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.
1022 nsRect dirty;
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();
1039 nscoord yEnd =
1040 forceRepeatToCoverTiles ? yStart + aDest.height : dirty.YMost();
1042 if (TryPaintTilesWithExtendMode(aContext, gradientPattern, xStart, yStart,
1043 dirtyAreaToFill, aDest, aRepeatSize,
1044 forceRepeatToCoverTiles)) {
1045 return;
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
1056 gfxRect fillRect =
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.
1073 continue;
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);
1083 aContext.NewPath();
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,
1092 &edgeColor)) {
1093 edgeColor.alpha *= aOpacity;
1094 aContext.SetColor(ToSRGBColor(edgeColor));
1095 } else {
1096 aContext.SetMatrixDouble(
1097 aContext.CurrentMatrixDouble().Copy().PreTranslate(
1098 tileRect.TopLeft()));
1099 aContext.SetPattern(gradientPattern);
1101 aContext.Fill();
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) {
1114 return false;
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) {
1131 return false;
1134 IntSize tileSize{
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)) {
1142 return false;
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
1148 // of arbitrary
1149 bool shouldUseExtendModeForTiling =
1150 aDirtyAreaToFill.Area() > (tileSize.width * tileSize.height) * 16.0;
1152 if (!shouldUseExtendModeForTiling) {
1153 return false;
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()) {
1163 return false;
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
1178 // the dirtyRect
1179 Matrix tileTransform = Matrix::Translation(
1180 NSAppUnitsToFloatPixels(aXStart, appUnitsPerDevPixel),
1181 NSAppUnitsToFloatPixels(aYStart, appUnitsPerDevPixel));
1183 aContext.NewPath();
1184 aContext.Rectangle(aDirtyAreaToFill);
1185 aContext.Fill(SurfacePattern(tileSurface, ExtendMode::REPEAT, tileTransform));
1187 return true;
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) {
1195 aMode =
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()) {
1217 return;
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();
1232 nsPoint firstTile =
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));
1289 } else {
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