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