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