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