Coverage Report

Created: 2018-09-25 14:53

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