LCOV - code coverage report
Current view: top level - gfx/skia/skia/src/effects/gradients - SkLinearGradient.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 219 499 43.9 %
Date: 2017-07-14 16:53:18 Functions: 19 54 35.2 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * Copyright 2012 Google Inc.
       3             :  *
       4             :  * Use of this source code is governed by a BSD-style license that can be
       5             :  * found in the LICENSE file.
       6             :  */
       7             : 
       8             : #include "Sk4fLinearGradient.h"
       9             : #include "SkLinearGradient.h"
      10             : #include "SkRefCnt.h"
      11             : 
      12             : // define to test the 4f gradient path
      13             : // #define FORCE_4F_CONTEXT
      14             : 
      15             : static const float kInv255Float = 1.0f / 255;
      16             : 
      17           0 : static inline int repeat_8bits(int x) {
      18           0 :     return x & 0xFF;
      19             : }
      20             : 
      21           0 : static inline int mirror_8bits(int x) {
      22           0 :     if (x & 256) {
      23           0 :         x = ~x;
      24             :     }
      25           0 :     return x & 255;
      26             : }
      27             : 
      28          25 : static SkMatrix pts_to_unit_matrix(const SkPoint pts[2]) {
      29          25 :     SkVector    vec = pts[1] - pts[0];
      30          25 :     SkScalar    mag = vec.length();
      31          25 :     SkScalar    inv = mag ? SkScalarInvert(mag) : 0;
      32             : 
      33          25 :     vec.scale(inv);
      34             :     SkMatrix matrix;
      35          25 :     matrix.setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
      36          25 :     matrix.postTranslate(-pts[0].fX, -pts[0].fY);
      37          25 :     matrix.postScale(inv, inv);
      38          25 :     return matrix;
      39             : }
      40             : 
      41          25 : static bool use_4f_context(const SkShader::ContextRec& rec, uint32_t flags) {
      42             : #ifdef FORCE_4F_CONTEXT
      43             :     return true;
      44             : #else
      45          25 :     return rec.fPreferredDstType == SkShader::ContextRec::kPM4f_DstType
      46          25 :         || SkToBool(flags & SkLinearGradient::kForce4fContext_PrivateFlag);
      47             : #endif
      48             : }
      49             : 
      50             : ///////////////////////////////////////////////////////////////////////////////
      51             : 
      52          25 : SkLinearGradient::SkLinearGradient(const SkPoint pts[2], const Descriptor& desc)
      53          50 :     : SkGradientShaderBase(desc, pts_to_unit_matrix(pts))
      54             :     , fStart(pts[0])
      55          50 :     , fEnd(pts[1]) {
      56          25 : }
      57             : 
      58           0 : sk_sp<SkFlattenable> SkLinearGradient::CreateProc(SkReadBuffer& buffer) {
      59           0 :     DescriptorScope desc;
      60           0 :     if (!desc.unflatten(buffer)) {
      61           0 :         return nullptr;
      62             :     }
      63             :     SkPoint pts[2];
      64           0 :     pts[0] = buffer.readPoint();
      65           0 :     pts[1] = buffer.readPoint();
      66           0 :     return SkGradientShader::MakeLinear(pts, desc.fColors, std::move(desc.fColorSpace), desc.fPos,
      67             :                                         desc.fCount, desc.fTileMode, desc.fGradFlags,
      68           0 :                                         desc.fLocalMatrix);
      69             : }
      70             : 
      71           0 : void SkLinearGradient::flatten(SkWriteBuffer& buffer) const {
      72           0 :     this->INHERITED::flatten(buffer);
      73           0 :     buffer.writePoint(fStart);
      74           0 :     buffer.writePoint(fEnd);
      75           0 : }
      76             : 
      77          25 : SkShader::Context* SkLinearGradient::onMakeContext(
      78             :     const ContextRec& rec, SkArenaAlloc* alloc) const
      79             : {
      80          25 :     return use_4f_context(rec, fGradFlags)
      81          25 :            ? CheckedMakeContext<LinearGradient4fContext>(alloc, *this, rec)
      82          25 :            : CheckedMakeContext<  LinearGradientContext>(alloc, *this, rec);
      83             : }
      84             : 
      85             : //
      86             : // Stages:
      87             : //
      88             : //   * matrix (map dst -> grad space)
      89             : //   * clamp/repeat/mirror (tiling)
      90             : //   * linear_gradient_2stops (lerp c0/c1)
      91             : //   * optional premul
      92             : //
      93           2 : bool SkLinearGradient::onAppendStages(SkRasterPipeline* p,
      94             :                                       SkColorSpace* dstCS,
      95             :                                       SkArenaAlloc* alloc,
      96             :                                       const SkMatrix& ctm,
      97             :                                       const SkPaint& paint,
      98             :                                       const SkMatrix* localM) const {
      99             :     // Local matrix not supported currently.  Remove once we have a generic RP wrapper.
     100           2 :     if (localM || !getLocalMatrix().isIdentity()) {
     101           2 :         return false;
     102             :     }
     103             : 
     104             :     SkMatrix dstToPts;
     105           0 :     if (!ctm.invert(&dstToPts)) {
     106           0 :         return false;
     107             :     }
     108             : 
     109           0 :     const auto dstToUnit = SkMatrix::Concat(fPtsToUnit, dstToPts);
     110             : 
     111             :     // If the gradient is less than a quarter of a pixel, this falls into the subpixel gradient code
     112             :     // handled on a different path.
     113           0 :     SkVector dx = dstToUnit.mapVector(1, 0);
     114           0 :     if (dx.fX >= 4) {
     115           0 :         return false;
     116             :     }
     117             : 
     118           0 :     auto* m = alloc->makeArrayDefault<float>(9);
     119           0 :     if (dstToUnit.asAffine(m)) {
     120             :         // TODO: mapping y is not needed; split the matrix stages to save some math?
     121           0 :         p->append(SkRasterPipeline::matrix_2x3, m);
     122             :     } else {
     123           0 :         dstToUnit.get9(m);
     124           0 :         p->append(SkRasterPipeline::matrix_perspective, m);
     125             :     }
     126             : 
     127             :     // TODO: clamp/repeat/mirror const 1f stages?
     128           0 :     auto* limit = alloc->make<float>(1.0f);
     129             : 
     130           0 :     const bool premulGrad = fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag;
     131           0 :     auto prepareColor = [premulGrad, dstCS, this](int i) {
     132           0 :         SkColor4f c = dstCS ? to_colorspace(fOrigColors4f[i], fColorSpace.get(), dstCS)
     133           0 :                             : SkColor4f_from_SkColor(fOrigColors[i], nullptr);
     134             :         return premulGrad ? c.premul()
     135           0 :                           : SkPM4f::From4f(Sk4f::Load(&c));
     136           0 :     };
     137             : 
     138             :     // The two-stop case with stops at 0 and 1.
     139           0 :     if (fColorCount == 2 && fOrigPos == nullptr) {
     140           0 :         switch (fTileMode) {
     141           0 :             case kClamp_TileMode:  p->append(SkRasterPipeline:: clamp_x, limit); break;
     142           0 :             case kMirror_TileMode: p->append(SkRasterPipeline::mirror_x, limit); break;
     143           0 :             case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_x, limit); break;
     144             :         }
     145             : 
     146           0 :         const SkPM4f c_l = prepareColor(0),
     147           0 :                      c_r = prepareColor(1);
     148             : 
     149             :         // See F and B below.
     150           0 :         auto* f_and_b = alloc->makeArrayDefault<SkPM4f>(2);
     151           0 :         f_and_b[0] = SkPM4f::From4f(c_r.to4f() - c_l.to4f());
     152           0 :         f_and_b[1] = c_l;
     153             : 
     154           0 :         p->append(SkRasterPipeline::linear_gradient_2stops, f_and_b);
     155             :     } else {
     156           0 :         switch (fTileMode) {
     157             :             // The search strategy does not need clamping. It has implicit hard stops at the
     158             :             // first and last stop.
     159           0 :             case kClamp_TileMode: break;
     160           0 :             case kMirror_TileMode: p->append(SkRasterPipeline::mirror_x, limit); break;
     161           0 :             case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_x, limit); break;
     162             :         }
     163             : 
     164             :         struct Stop { float t; SkPM4f f, b; };
     165             :         struct Ctx { size_t n; Stop* stops; SkPM4f start; };
     166             : 
     167           0 :         auto* ctx = alloc->make<Ctx>();
     168           0 :         ctx->start = prepareColor(0);
     169             : 
     170             :         // For each stop we calculate a bias B and a scale factor F, such that
     171             :         // for any t between stops n and n+1, the color we want is B[n] + F[n]*t.
     172           0 :         auto init_stop = [](float t_l, float t_r, SkPM4f c_l, SkPM4f c_r, Stop *stop) {
     173           0 :             auto F = SkPM4f::From4f((c_r.to4f() - c_l.to4f()) / (t_r - t_l));
     174           0 :             auto B = SkPM4f::From4f(c_l.to4f() - (F.to4f() * t_l));
     175           0 :             *stop = {t_l, F, B};
     176           0 :         };
     177             : 
     178           0 :         if (fOrigPos == nullptr) {
     179             :             // Handle evenly distributed stops.
     180             : 
     181           0 :             float dt = 1.0f / (fColorCount - 1);
     182             :             // In the evenly distributed case, fColorCount is the number of stops. There are no
     183             :             // dummy entries.
     184           0 :             auto* stopsArray = alloc->makeArrayDefault<Stop>(fColorCount);
     185             : 
     186           0 :             float  t_l = 0;
     187           0 :             SkPM4f c_l = ctx->start;
     188           0 :             for (int i = 0; i < fColorCount - 1; i++) {
     189             :                 // Use multiply instead of accumulating error using repeated addition.
     190           0 :                 float  t_r = (i + 1) * dt;
     191           0 :                 SkPM4f c_r = prepareColor(i + 1);
     192           0 :                 init_stop(t_l, t_r, c_l, c_r, &stopsArray[i]);
     193             : 
     194           0 :                 t_l = t_r;
     195           0 :                 c_l = c_r;
     196             :             }
     197             : 
     198             :             // Force the last stop.
     199           0 :             stopsArray[fColorCount - 1].t = 1;
     200           0 :             stopsArray[fColorCount - 1].f = SkPM4f::From4f(Sk4f{0});
     201           0 :             stopsArray[fColorCount - 1].b = prepareColor(fColorCount - 1);
     202             : 
     203           0 :             ctx->n = fColorCount;
     204           0 :             ctx->stops = stopsArray;
     205             :         } else {
     206             :             // Handle arbitrary stops.
     207             : 
     208             :             // Remove the dummy stops inserted by SkGradientShaderBase::SkGradientShaderBase
     209             :             // because they are naturally handled by the search method.
     210             :             int firstStop;
     211             :             int lastStop;
     212           0 :             if (fColorCount > 2) {
     213           0 :                 firstStop = fOrigColors4f[0] != fOrigColors4f[1] ? 0 : 1;
     214           0 :                 lastStop = fOrigColors4f[fColorCount - 2] != fOrigColors4f[fColorCount - 1]
     215           0 :                            ? fColorCount - 1 : fColorCount - 2;
     216             :             } else {
     217           0 :                 firstStop = 0;
     218           0 :                 lastStop = 1;
     219             :             }
     220           0 :             int realCount = lastStop - firstStop + 1;
     221             : 
     222             :             // This is the maximum number of stops. There may be fewer stops because the duplicate
     223             :             // points of hard stops are removed.
     224           0 :             auto* stopsArray = alloc->makeArrayDefault<Stop>(realCount);
     225             : 
     226           0 :             size_t stopCount = 0;
     227           0 :             float  t_l = fOrigPos[firstStop];
     228           0 :             SkPM4f c_l = prepareColor(firstStop);
     229             :             // N.B. lastStop is the index of the last stop, not one after.
     230           0 :             for (int i = firstStop; i < lastStop; i++) {
     231           0 :                 float  t_r = fOrigPos[i + 1];
     232           0 :                 SkPM4f c_r = prepareColor(i + 1);
     233           0 :                 if (t_l < t_r) {
     234           0 :                     init_stop(t_l, t_r, c_l, c_r, &stopsArray[stopCount]);
     235           0 :                     stopCount += 1;
     236             :                 }
     237           0 :                 t_l = t_r;
     238           0 :                 c_l = c_r;
     239             :             }
     240             : 
     241           0 :             stopsArray[stopCount].t = fOrigPos[lastStop];
     242           0 :             stopsArray[stopCount].f = SkPM4f::From4f(Sk4f{0});
     243           0 :             stopsArray[stopCount].b = prepareColor(lastStop);
     244           0 :             stopCount += 1;
     245             : 
     246           0 :             ctx->n = stopCount;
     247           0 :             ctx->stops = stopsArray;
     248             :         }
     249             : 
     250           0 :         p->append(SkRasterPipeline::linear_gradient, ctx);
     251             :     }
     252             : 
     253           0 :     if (!premulGrad && !this->colorsAreOpaque()) {
     254           0 :         p->append(SkRasterPipeline::premul);
     255             :     }
     256             : 
     257           0 :     return true;
     258             : }
     259             : 
     260             : // This swizzles SkColor into the same component order as SkPMColor, but does not actually
     261             : // "pre" multiply the color components.
     262             : //
     263             : // This allows us to map directly to Sk4f, and eventually scale down to bytes to output a
     264             : // SkPMColor from the floats, without having to swizzle each time.
     265             : //
     266          81 : static uint32_t SkSwizzle_Color_to_PMColor(SkColor c) {
     267          81 :     return SkPackARGB32NoCheck(SkColorGetA(c), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c));
     268             : }
     269             : 
     270          25 : SkLinearGradient::LinearGradientContext::LinearGradientContext(
     271          25 :         const SkLinearGradient& shader, const ContextRec& ctx)
     272          25 :     : INHERITED(shader, ctx)
     273             : {
     274             :     // setup for Sk4f
     275          25 :     const int count = shader.fColorCount;
     276          25 :     SkASSERT(count > 1);
     277             : 
     278          25 :     fRecs.setCount(count);
     279          25 :     Rec* rec = fRecs.begin();
     280          25 :     if (shader.fOrigPos) {
     281          15 :         rec[0].fPos = 0;
     282          15 :         SkDEBUGCODE(rec[0].fPosScale = SK_FloatNaN;)   // should never get used
     283          61 :         for (int i = 1; i < count; ++i) {
     284          46 :             rec[i].fPos = SkTPin(shader.fOrigPos[i], rec[i - 1].fPos, 1.0f);
     285          46 :             float diff = rec[i].fPos - rec[i - 1].fPos;
     286          46 :             if (diff > 0) {
     287          33 :                 rec[i].fPosScale = 1.0f / diff;
     288             :             } else {
     289          13 :                 rec[i].fPosScale = 0;
     290             :             }
     291             :         }
     292             :     } else {
     293             :         // no pos specified, so we compute evenly spaced values
     294          10 :         const float scale = float(count - 1);
     295          10 :         const float invScale = 1.0f / scale;
     296          30 :         for (int i = 0; i < count; ++i) {
     297          20 :             rec[i].fPos = i * invScale;
     298          20 :             rec[i].fPosScale = scale;
     299             :         }
     300             :     }
     301          25 :     rec[count - 1].fPos = 1;    // overwrite the last value just to be sure we end at 1.0
     302             : 
     303          25 :     fApplyAlphaAfterInterp = true;
     304          50 :     if ((shader.getGradFlags() & SkGradientShader::kInterpolateColorsInPremul_Flag) ||
     305          25 :         shader.colorsAreOpaque())
     306             :     {
     307           0 :         fApplyAlphaAfterInterp = false;
     308             :     }
     309             : 
     310          25 :     if (fApplyAlphaAfterInterp) {
     311             :         // Our fColor values are in PMColor order, but are still unpremultiplied, allowing us to
     312             :         // interpolate in unpremultiplied space first, and then scale by alpha right before we
     313             :         // convert to SkPMColor bytes.
     314          25 :         const float paintAlpha = ctx.fPaint->getAlpha() * kInv255Float;
     315             :         const Sk4f scale(1, 1, 1, paintAlpha);
     316         106 :         for (int i = 0; i < count; ++i) {
     317          81 :             uint32_t c = SkSwizzle_Color_to_PMColor(shader.fOrigColors[i]);
     318         243 :             rec[i].fColor = SkNx_cast<float>(Sk4b::Load(&c)) * scale;
     319          81 :             if (i > 0) {
     320          56 :                 SkASSERT(rec[i - 1].fPos <= rec[i].fPos);
     321             :             }
     322             :         }
     323             :     } else {
     324             :         // Our fColor values are premultiplied, so converting to SkPMColor is just a matter
     325             :         // of converting the floats down to bytes.
     326           0 :         unsigned alphaScale = ctx.fPaint->getAlpha() + (ctx.fPaint->getAlpha() >> 7);
     327           0 :         for (int i = 0; i < count; ++i) {
     328           0 :             SkPMColor pmc = SkPreMultiplyColor(shader.fOrigColors[i]);
     329           0 :             pmc = SkAlphaMulQ(pmc, alphaScale);
     330           0 :             rec[i].fColor = SkNx_cast<float>(Sk4b::Load(&pmc));
     331           0 :             if (i > 0) {
     332           0 :                 SkASSERT(rec[i - 1].fPos <= rec[i].fPos);
     333             :             }
     334             :         }
     335             :     }
     336          25 : }
     337             : 
     338             : #define NO_CHECK_ITER               \
     339             :     do {                            \
     340             :     unsigned fi = SkGradFixedToFixed(fx) >> SkGradientShaderBase::kCache32Shift; \
     341             :     SkASSERT(fi <= 0xFF);           \
     342             :     fx += dx;                       \
     343             :     *dstC++ = cache[toggle + fi];   \
     344             :     toggle = next_dither_toggle(toggle); \
     345             :     } while (0)
     346             : 
     347             : namespace {
     348             : 
     349             : typedef void (*LinearShadeProc)(TileProc proc, SkGradFixed dx, SkGradFixed fx,
     350             :                                 SkPMColor* dstC, const SkPMColor* cache,
     351             :                                 int toggle, int count);
     352             : 
     353             : // Linear interpolation (lerp) is unnecessary if there are no sharp
     354             : // discontinuities in the gradient - which must be true if there are
     355             : // only 2 colors - but it's cheap.
     356        2816 : void shadeSpan_linear_vertical_lerp(TileProc proc, SkGradFixed dx, SkGradFixed fx,
     357             :                                     SkPMColor* SK_RESTRICT dstC,
     358             :                                     const SkPMColor* SK_RESTRICT cache,
     359             :                                     int toggle, int count) {
     360             :     // We're a vertical gradient, so no change in a span.
     361             :     // If colors change sharply across the gradient, dithering is
     362             :     // insufficient (it subsamples the color space) and we need to lerp.
     363        2816 :     unsigned fullIndex = proc(SkGradFixedToFixed(fx));
     364        2816 :     if (fullIndex >= (SK_FixedHalf >> SkGradientShaderBase::kCache32Bits)) {
     365        2816 :         fullIndex -= SK_FixedHalf >> SkGradientShaderBase::kCache32Bits;
     366             :     } else {
     367           0 :         fullIndex = 0;
     368             :     }
     369        2816 :     unsigned fi = fullIndex >> SkGradientShaderBase::kCache32Shift;
     370        2816 :     unsigned remainder = fullIndex & ((1 << SkGradientShaderBase::kCache32Shift) - 1);
     371             : 
     372        2816 :     int index0 = fi + toggle;
     373        2816 :     int index1 = index0;
     374        2816 :     if (fi < SkGradientShaderBase::kCache32Count - 1) {
     375        2816 :         index1 += 1;
     376             :     }
     377        2816 :     SkPMColor lerp = SkFastFourByteInterp(cache[index1], cache[index0], remainder);
     378        2816 :     index0 ^= SkGradientShaderBase::kDitherStride32;
     379        2816 :     index1 ^= SkGradientShaderBase::kDitherStride32;
     380        2816 :     SkPMColor dlerp = SkFastFourByteInterp(cache[index1], cache[index0], remainder);
     381        2816 :     sk_memset32_dither(dstC, lerp, dlerp, count);
     382        2816 : }
     383             : 
     384           0 : void shadeSpan_linear_clamp(TileProc proc, SkGradFixed dx, SkGradFixed fx,
     385             :                             SkPMColor* SK_RESTRICT dstC,
     386             :                             const SkPMColor* SK_RESTRICT cache,
     387             :                             int toggle, int count) {
     388             :     SkClampRange range;
     389           0 :     range.init(fx, dx, count, 0, SkGradientShaderBase::kCache32Count - 1);
     390           0 :     range.validate(count);
     391             : 
     392           0 :     if ((count = range.fCount0) > 0) {
     393           0 :         sk_memset32_dither(dstC,
     394           0 :             cache[toggle + range.fV0],
     395           0 :             cache[next_dither_toggle(toggle) + range.fV0],
     396           0 :             count);
     397           0 :         dstC += count;
     398             :     }
     399           0 :     if ((count = range.fCount1) > 0) {
     400           0 :         int unroll = count >> 3;
     401           0 :         fx = range.fFx1;
     402           0 :         for (int i = 0; i < unroll; i++) {
     403           0 :             NO_CHECK_ITER;  NO_CHECK_ITER;
     404           0 :             NO_CHECK_ITER;  NO_CHECK_ITER;
     405           0 :             NO_CHECK_ITER;  NO_CHECK_ITER;
     406           0 :             NO_CHECK_ITER;  NO_CHECK_ITER;
     407             :         }
     408           0 :         if ((count &= 7) > 0) {
     409           0 :             do {
     410           0 :                 NO_CHECK_ITER;
     411             :             } while (--count != 0);
     412             :         }
     413             :     }
     414           0 :     if ((count = range.fCount2) > 0) {
     415           0 :         sk_memset32_dither(dstC,
     416           0 :             cache[toggle + range.fV1],
     417           0 :             cache[next_dither_toggle(toggle) + range.fV1],
     418           0 :             count);
     419             :     }
     420           0 : }
     421             : 
     422           0 : void shadeSpan_linear_mirror(TileProc proc, SkGradFixed dx, SkGradFixed fx,
     423             :                              SkPMColor* SK_RESTRICT dstC,
     424             :                              const SkPMColor* SK_RESTRICT cache,
     425             :                              int toggle, int count) {
     426           0 :     do {
     427           0 :         unsigned fi = mirror_8bits(SkGradFixedToFixed(fx) >> 8);
     428           0 :         SkASSERT(fi <= 0xFF);
     429           0 :         fx += dx;
     430           0 :         *dstC++ = cache[toggle + fi];
     431           0 :         toggle = next_dither_toggle(toggle);
     432             :     } while (--count != 0);
     433           0 : }
     434             : 
     435           0 : void shadeSpan_linear_repeat(TileProc proc, SkGradFixed dx, SkGradFixed fx,
     436             :         SkPMColor* SK_RESTRICT dstC,
     437             :         const SkPMColor* SK_RESTRICT cache,
     438             :         int toggle, int count) {
     439           0 :     do {
     440           0 :         unsigned fi = repeat_8bits(SkGradFixedToFixed(fx) >> 8);
     441           0 :         SkASSERT(fi <= 0xFF);
     442           0 :         fx += dx;
     443           0 :         *dstC++ = cache[toggle + fi];
     444           0 :         toggle = next_dither_toggle(toggle);
     445             :     } while (--count != 0);
     446           0 : }
     447             : 
     448             : }
     449             : 
     450        3393 : void SkLinearGradient::LinearGradientContext::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
     451             :                                                         int count) {
     452        3393 :     SkASSERT(count > 0);
     453        3393 :     const SkLinearGradient& linearGradient = static_cast<const SkLinearGradient&>(fShader);
     454             : 
     455        3970 :     if (SkShader::kClamp_TileMode == linearGradient.fTileMode &&
     456         577 :         kLinear_MatrixClass == fDstToIndexClass)
     457             :     {
     458         577 :         this->shade4_clamp(x, y, dstC, count);
     459         577 :         return;
     460             :     }
     461             : 
     462             :     SkPoint             srcPt;
     463        2816 :     SkMatrix::MapXYProc dstProc = fDstToIndexProc;
     464        2816 :     TileProc            proc = linearGradient.fTileProc;
     465        2816 :     const SkPMColor* SK_RESTRICT cache = fCache->getCache32();
     466        2816 :     int                 toggle = init_dither_toggle(x, y);
     467             : 
     468        2816 :     if (fDstToIndexClass != kPerspective_MatrixClass) {
     469        2816 :         dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
     470        5632 :                              SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
     471        2816 :         SkGradFixed dx, fx = SkScalarPinToGradFixed(srcPt.fX);
     472             : 
     473        2816 :         if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
     474           0 :             const auto step = fDstToIndex.fixedStepInX(SkIntToScalar(y));
     475             :             // todo: do we need a real/high-precision value for dx here?
     476           0 :             dx = SkScalarPinToGradFixed(step.fX);
     477             :         } else {
     478        2816 :             SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
     479        2816 :             dx = SkScalarPinToGradFixed(fDstToIndex.getScaleX());
     480             :         }
     481             : 
     482        2816 :         LinearShadeProc shadeProc = shadeSpan_linear_repeat;
     483        2816 :         if (0 == dx) {
     484        2816 :             shadeProc = shadeSpan_linear_vertical_lerp;
     485           0 :         } else if (SkShader::kClamp_TileMode == linearGradient.fTileMode) {
     486           0 :             shadeProc = shadeSpan_linear_clamp;
     487           0 :         } else if (SkShader::kMirror_TileMode == linearGradient.fTileMode) {
     488           0 :             shadeProc = shadeSpan_linear_mirror;
     489             :         } else {
     490           0 :             SkASSERT(SkShader::kRepeat_TileMode == linearGradient.fTileMode);
     491             :         }
     492        2816 :         (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
     493             :     } else {
     494           0 :         SkScalar    dstX = SkIntToScalar(x);
     495           0 :         SkScalar    dstY = SkIntToScalar(y);
     496           0 :         do {
     497           0 :             dstProc(fDstToIndex, dstX, dstY, &srcPt);
     498           0 :             unsigned fi = proc(SkScalarToFixed(srcPt.fX));
     499           0 :             SkASSERT(fi <= 0xFFFF);
     500           0 :             *dstC++ = cache[toggle + (fi >> kCache32Shift)];
     501           0 :             toggle = next_dither_toggle(toggle);
     502           0 :             dstX += SK_Scalar1;
     503             :         } while (--count != 0);
     504             :     }
     505             : }
     506             : 
     507           0 : SkShader::GradientType SkLinearGradient::asAGradient(GradientInfo* info) const {
     508           0 :     if (info) {
     509           0 :         commonAsAGradient(info);
     510           0 :         info->fPoint[0] = fStart;
     511           0 :         info->fPoint[1] = fEnd;
     512             :     }
     513           0 :     return kLinear_GradientType;
     514             : }
     515             : 
     516             : #if SK_SUPPORT_GPU
     517             : 
     518             : #include "GrColorSpaceXform.h"
     519             : #include "GrShaderCaps.h"
     520             : #include "glsl/GrGLSLFragmentShaderBuilder.h"
     521             : #include "SkGr.h"
     522             : 
     523             : /////////////////////////////////////////////////////////////////////
     524             : 
     525             : class GrLinearGradient : public GrGradientEffect {
     526             : public:
     527             :     class GLSLLinearProcessor;
     528             : 
     529           0 :     static sk_sp<GrFragmentProcessor> Make(const CreateArgs& args) {
     530           0 :         return sk_sp<GrFragmentProcessor>(new GrLinearGradient(args));
     531             :     }
     532             : 
     533           0 :     ~GrLinearGradient() override {}
     534             : 
     535           0 :     const char* name() const override { return "Linear Gradient"; }
     536             : 
     537             : private:
     538           0 :     GrLinearGradient(const CreateArgs& args) : INHERITED(args, args.fShader->colorsAreOpaque()) {
     539           0 :         this->initClassID<GrLinearGradient>();
     540           0 :     }
     541             : 
     542             :     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
     543             : 
     544             :     virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps,
     545             :                                        GrProcessorKeyBuilder* b) const override;
     546             : 
     547             :     GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
     548             : 
     549             :     typedef GrGradientEffect INHERITED;
     550             : };
     551             : 
     552             : /////////////////////////////////////////////////////////////////////
     553             : 
     554             : class GrLinearGradient::GLSLLinearProcessor : public GrGradientEffect::GLSLProcessor {
     555             : public:
     556           0 :     GLSLLinearProcessor(const GrProcessor&) {}
     557             : 
     558           0 :     ~GLSLLinearProcessor() override {}
     559             : 
     560             :     virtual void emitCode(EmitArgs&) override;
     561             : 
     562           0 :     static void GenKey(const GrProcessor& processor, const GrShaderCaps&, GrProcessorKeyBuilder* b) {
     563           0 :         b->add32(GenBaseGradientKey(processor));
     564           0 :     }
     565             : 
     566             : private:
     567             :     typedef GrGradientEffect::GLSLProcessor INHERITED;
     568             : };
     569             : 
     570             : /////////////////////////////////////////////////////////////////////
     571             : 
     572           0 : GrGLSLFragmentProcessor* GrLinearGradient::onCreateGLSLInstance() const {
     573           0 :     return new GrLinearGradient::GLSLLinearProcessor(*this);
     574             : }
     575             : 
     576           0 : void GrLinearGradient::onGetGLSLProcessorKey(const GrShaderCaps& caps,
     577             :                                              GrProcessorKeyBuilder* b) const {
     578           0 :     GrLinearGradient::GLSLLinearProcessor::GenKey(*this, caps, b);
     579           0 : }
     580             : 
     581             : /////////////////////////////////////////////////////////////////////
     582             : 
     583             : GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrLinearGradient);
     584             : 
     585             : #if GR_TEST_UTILS
     586           0 : sk_sp<GrFragmentProcessor> GrLinearGradient::TestCreate(GrProcessorTestData* d) {
     587           0 :     SkPoint points[] = {{d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()},
     588           0 :                         {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}};
     589             : 
     590           0 :     RandomGradientParams params(d->fRandom);
     591           0 :     auto shader = params.fUseColors4f ?
     592           0 :         SkGradientShader::MakeLinear(points, params.fColors4f, params.fColorSpace, params.fStops,
     593             :                                      params.fColorCount, params.fTileMode) :
     594           0 :         SkGradientShader::MakeLinear(points, params.fColors, params.fStops,
     595           0 :                                      params.fColorCount, params.fTileMode);
     596           0 :     GrTest::TestAsFPArgs asFPArgs(d);
     597           0 :     sk_sp<GrFragmentProcessor> fp = shader->asFragmentProcessor(asFPArgs.args());
     598           0 :     GrAlwaysAssert(fp);
     599           0 :     return fp;
     600             : }
     601             : #endif
     602             : 
     603             : /////////////////////////////////////////////////////////////////////
     604             : 
     605           0 : void GrLinearGradient::GLSLLinearProcessor::emitCode(EmitArgs& args) {
     606           0 :     const GrLinearGradient& ge = args.fFp.cast<GrLinearGradient>();
     607           0 :     this->emitUniforms(args.fUniformHandler, ge);
     608           0 :     SkString t = args.fFragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
     609           0 :     t.append(".x");
     610           0 :     this->emitColor(args.fFragBuilder,
     611             :                     args.fUniformHandler,
     612             :                     args.fShaderCaps,
     613             :                     ge,
     614             :                     t.c_str(),
     615             :                     args.fOutputColor,
     616             :                     args.fInputColor,
     617           0 :                     args.fTexSamplers);
     618           0 : }
     619             : 
     620             : /////////////////////////////////////////////////////////////////////
     621             : 
     622           0 : sk_sp<GrFragmentProcessor> SkLinearGradient::asFragmentProcessor(const AsFPArgs& args) const {
     623           0 :     SkASSERT(args.fContext);
     624             : 
     625             :     SkMatrix matrix;
     626           0 :     if (!this->getLocalMatrix().invert(&matrix)) {
     627           0 :         return nullptr;
     628             :     }
     629           0 :     if (args.fLocalMatrix) {
     630             :         SkMatrix inv;
     631           0 :         if (!args.fLocalMatrix->invert(&inv)) {
     632           0 :             return nullptr;
     633             :         }
     634           0 :         matrix.postConcat(inv);
     635             :     }
     636           0 :     matrix.postConcat(fPtsToUnit);
     637             : 
     638           0 :     sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(),
     639           0 :                                                                        args.fDstColorSpace);
     640             :     sk_sp<GrFragmentProcessor> inner(GrLinearGradient::Make(
     641           0 :         GrGradientEffect::CreateArgs(args.fContext, this, &matrix, fTileMode,
     642           0 :                                      std::move(colorSpaceXform), SkToBool(args.fDstColorSpace))));
     643           0 :     return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner));
     644             : }
     645             : 
     646             : 
     647             : #endif
     648             : 
     649             : #ifndef SK_IGNORE_TO_STRING
     650           0 : void SkLinearGradient::toString(SkString* str) const {
     651           0 :     str->append("SkLinearGradient (");
     652             : 
     653           0 :     str->appendf("start: (%f, %f)", fStart.fX, fStart.fY);
     654           0 :     str->appendf(" end: (%f, %f) ", fEnd.fX, fEnd.fY);
     655             : 
     656           0 :     this->INHERITED::toString(str);
     657             : 
     658           0 :     str->append(")");
     659           0 : }
     660             : #endif
     661             : 
     662             : ///////////////////////////////////////////////////////////////////////////////////////////////////
     663             : 
     664             : #include "SkNx.h"
     665             : 
     666             : static const SkLinearGradient::LinearGradientContext::Rec*
     667         519 : find_forward(const SkLinearGradient::LinearGradientContext::Rec rec[], float tiledX) {
     668         519 :     SkASSERT(tiledX >= 0 && tiledX <= 1);
     669             : 
     670         519 :     SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1);
     671         519 :     SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1);
     672         519 :     SkASSERT(rec[0].fPos <= rec[1].fPos);
     673         519 :     rec += 1;
     674        2179 :     while (rec->fPos < tiledX || rec->fPosScale == 0) {
     675         830 :         SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1);
     676         830 :         SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1);
     677         830 :         SkASSERT(rec[0].fPos <= rec[1].fPos);
     678         830 :         rec += 1;
     679             :     }
     680         519 :     return rec - 1;
     681             : }
     682             : 
     683             : static const SkLinearGradient::LinearGradientContext::Rec*
     684         116 : find_backward(const SkLinearGradient::LinearGradientContext::Rec rec[], float tiledX) {
     685         116 :     SkASSERT(tiledX >= 0 && tiledX <= 1);
     686             : 
     687         116 :     SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1);
     688         116 :     SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1);
     689         116 :     SkASSERT(rec[0].fPos <= rec[1].fPos);
     690         232 :     while (tiledX < rec->fPos || rec[1].fPosScale == 0) {
     691          58 :         rec -= 1;
     692          58 :         SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1);
     693          58 :         SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1);
     694          58 :         SkASSERT(rec[0].fPos <= rec[1].fPos);
     695             :     }
     696         116 :     return rec;
     697             : }
     698             : 
     699             : // As an optimization, we can apply the dither bias before interpolation -- but only when
     700             : // operating in premul space (apply_alpha == false).  When apply_alpha == true, we must
     701             : // defer the bias application until after premul.
     702             : //
     703             : // The following two helpers encapsulate this logic: pre_bias is called before interpolation,
     704             : // and effects the bias when apply_alpha == false, while post_bias is called after premul and
     705             : // effects the bias for the apply_alpha == true case.
     706             : 
     707             : template <bool apply_alpha>
     708        1270 : Sk4f pre_bias(const Sk4f& x, const Sk4f& bias) {
     709        1270 :     return apply_alpha ? x : x + bias;
     710             : }
     711             : 
     712             : template <bool apply_alpha>
     713       20294 : Sk4f post_bias(const Sk4f& x, const Sk4f& bias) {
     714       20294 :     return apply_alpha ? x + bias : x;
     715             : }
     716             : 
     717       20294 : template <bool apply_alpha> SkPMColor trunc_from_255(const Sk4f& x, const Sk4f& bias) {
     718             :     SkPMColor c;
     719       20294 :     Sk4f c4f255 = x;
     720             :     if (apply_alpha) {
     721       20294 :         const float scale = x[SkPM4f::A] * (1 / 255.f);
     722             :         // Multiply alpha by a number slightly greater than 1 to compensate for error
     723             :         // in scaling from the 1/255 approximation. This error is less than 1e-6 for
     724             :         // all alpha values. Non-integer alpha values very close to their ceiling can
     725             :         // push the color values above the alpha value, which will become an invalid
     726             :         // premultiplied color. So nudge alpha up slightly by this compensating scale
     727             :         // to keep it above the color values.
     728       20294 :         c4f255 *= Sk4f(scale, scale, scale, 1.000001f);
     729             :     }
     730       60882 :     SkNx_cast<uint8_t>(post_bias<apply_alpha>(c4f255, bias)).store(&c);
     731             : 
     732       20294 :     return c;
     733             : }
     734             : 
     735         519 : template <bool apply_alpha> void fill(SkPMColor dst[], int count,
     736             :                                       const Sk4f& c4, const Sk4f& bias0, const Sk4f& bias1) {
     737         519 :     const SkPMColor c0 = trunc_from_255<apply_alpha>(pre_bias<apply_alpha>(c4, bias0), bias0);
     738         519 :     const SkPMColor c1 = trunc_from_255<apply_alpha>(pre_bias<apply_alpha>(c4, bias1), bias1);
     739         519 :     sk_memset32_dither(dst, c0, c1, count);
     740         519 : }
     741             : 
     742           0 : template <bool apply_alpha> void fill(SkPMColor dst[], int count, const Sk4f& c4) {
     743             :     // Assumes that c4 does not need to be dithered.
     744           0 :     sk_memset32(dst, trunc_from_255<apply_alpha>(c4, 0), count);
     745           0 : }
     746             : 
     747             : /*
     748             :  *  TODOs
     749             :  *
     750             :  *  - tilemodes
     751             :  *  - interp before or after premul
     752             :  *  - perspective
     753             :  *  - optimizations
     754             :  *      - use fixed (32bit or 16bit) instead of floats?
     755             :  */
     756             : 
     757         519 : static Sk4f lerp_color(float fx, const SkLinearGradient::LinearGradientContext::Rec* rec) {
     758         519 :     SkASSERT(fx >= rec[0].fPos);
     759         519 :     SkASSERT(fx <= rec[1].fPos);
     760             : 
     761         519 :     const float p0 = rec[0].fPos;
     762         519 :     const Sk4f c0 = rec[0].fColor;
     763         519 :     const Sk4f c1 = rec[1].fColor;
     764         519 :     const Sk4f diffc = c1 - c0;
     765         519 :     const float scale = rec[1].fPosScale;
     766         519 :     const float t = (fx - p0) * scale;
     767        1038 :     return c0 + Sk4f(t) * diffc;
     768             : }
     769             : 
     770         116 : template <bool apply_alpha> void ramp(SkPMColor dstC[], int n, const Sk4f& c, const Sk4f& dc,
     771             :                                       const Sk4f& dither0, const Sk4f& dither1) {
     772         116 :     Sk4f dc2 = dc + dc;
     773         116 :     Sk4f dc4 = dc2 + dc2;
     774         116 :     Sk4f cd0 = pre_bias<apply_alpha>(c     , dither0);
     775         116 :     Sk4f cd1 = pre_bias<apply_alpha>(c + dc, dither1);
     776         116 :     Sk4f cd2 = cd0 + dc2;
     777         116 :     Sk4f cd3 = cd1 + dc2;
     778        9570 :     while (n >= 4) {
     779             :         if (!apply_alpha) {
     780             :             Sk4f_ToBytes((uint8_t*)dstC, cd0, cd1, cd2, cd3);
     781           0 :             dstC += 4;
     782             :         } else {
     783        4727 :             *dstC++ = trunc_from_255<apply_alpha>(cd0, dither0);
     784        4727 :             *dstC++ = trunc_from_255<apply_alpha>(cd1, dither1);
     785        4727 :             *dstC++ = trunc_from_255<apply_alpha>(cd2, dither0);
     786        4727 :             *dstC++ = trunc_from_255<apply_alpha>(cd3, dither1);
     787             :         }
     788        4727 :         cd0 = cd0 + dc4;
     789        4727 :         cd1 = cd1 + dc4;
     790        4727 :         cd2 = cd2 + dc4;
     791        4727 :         cd3 = cd3 + dc4;
     792        4727 :         n -= 4;
     793             :     }
     794         116 :     if (n & 2) {
     795         116 :         *dstC++ = trunc_from_255<apply_alpha>(cd0, dither0);
     796         116 :         *dstC++ = trunc_from_255<apply_alpha>(cd1, dither1);
     797         116 :         cd0 = cd0 + dc2;
     798             :     }
     799         116 :     if (n & 1) {
     800         116 :         *dstC++ = trunc_from_255<apply_alpha>(cd0, dither0);
     801             :     }
     802         116 : }
     803             : 
     804             : template <bool apply_alpha, bool dx_is_pos>
     805          58 : void SkLinearGradient::LinearGradientContext::shade4_dx_clamp(SkPMColor dstC[], int count,
     806             :                                                               float fx, float dx, float invDx,
     807             :                                                               const float dither[2]) {
     808          58 :     Sk4f dither0(dither[0]);
     809          58 :     Sk4f dither1(dither[1]);
     810          58 :     const Rec* rec = fRecs.begin();
     811             : 
     812             :     const Sk4f dx4 = Sk4f(dx);
     813          58 :     SkDEBUGCODE(SkPMColor* endDstC = dstC + count;)
     814             : 
     815             :     if (dx_is_pos) {
     816           0 :         if (fx < 0) {
     817             :             // count is guaranteed to be positive, but the first arg may overflow int32 after
     818             :             // increment => casting to uint32 ensures correct clamping.
     819           0 :             int n = SkTMin<uint32_t>(static_cast<uint32_t>(SkFloatToIntFloor(-fx * invDx)) + 1,
     820           0 :                                      count);
     821           0 :             SkASSERT(n > 0);
     822           0 :             fill<apply_alpha>(dstC, n, rec[0].fColor);
     823           0 :             count -= n;
     824           0 :             dstC += n;
     825           0 :             fx += n * dx;
     826           0 :             SkASSERT(0 == count || fx >= 0);
     827           0 :             if (n & 1) {
     828           0 :                 SkTSwap(dither0, dither1);
     829             :             }
     830             :         }
     831             :     } else { // dx < 0
     832          58 :         if (fx > 1) {
     833             :             // count is guaranteed to be positive, but the first arg may overflow int32 after
     834             :             // increment => casting to uint32 ensures correct clamping.
     835           0 :             int n = SkTMin<uint32_t>(static_cast<uint32_t>(SkFloatToIntFloor((1 - fx) * invDx)) + 1,
     836           0 :                                      count);
     837           0 :             SkASSERT(n > 0);
     838           0 :             fill<apply_alpha>(dstC, n, rec[fRecs.count() - 1].fColor);
     839           0 :             count -= n;
     840           0 :             dstC += n;
     841           0 :             fx += n * dx;
     842           0 :             SkASSERT(0 == count || fx <= 1);
     843           0 :             if (n & 1) {
     844           0 :                 SkTSwap(dither0, dither1);
     845             :             }
     846             :         }
     847             :     }
     848          58 :     SkASSERT(count >= 0);
     849             : 
     850             :     const Rec* r;
     851             :     if (dx_is_pos) {
     852           0 :         r = fRecs.begin();                      // start at the beginning
     853             :     } else {
     854          58 :         r = fRecs.begin() + fRecs.count() - 2;  // start at the end
     855             :     }
     856             : 
     857         290 :     while (count > 0) {
     858             :         if (dx_is_pos) {
     859           0 :             if (fx >= 1) {
     860           0 :                 fill<apply_alpha>(dstC, count, rec[fRecs.count() - 1].fColor);
     861           0 :                 return;
     862             :             }
     863             :         } else {    // dx < 0
     864         116 :             if (fx <= 0) {
     865           0 :                 fill<apply_alpha>(dstC, count, rec[0].fColor);
     866           0 :                 return;
     867             :             }
     868             :         }
     869             : 
     870             :         if (dx_is_pos) {
     871           0 :             r = find_forward(r, fx);
     872             :         } else {
     873         116 :             r = find_backward(r, fx);
     874             :         }
     875         116 :         SkASSERT(r >= fRecs.begin() && r < fRecs.begin() + fRecs.count() - 1);
     876             : 
     877         116 :         const float p0 = r[0].fPos;
     878         116 :         const Sk4f c0 = r[0].fColor;
     879         116 :         const float p1 = r[1].fPos;
     880         232 :         const Sk4f diffc = Sk4f(r[1].fColor) - c0;
     881         116 :         const float scale = r[1].fPosScale;
     882         116 :         const float t = (fx - p0) * scale;
     883         232 :         const Sk4f c = c0 + Sk4f(t) * diffc;
     884         232 :         const Sk4f dc = diffc * dx4 * Sk4f(scale);
     885             : 
     886             :         int n;
     887             :         if (dx_is_pos) {
     888           0 :             n = SkTMin((int)((p1 - fx) * invDx) + 1, count);
     889             :         } else {
     890         116 :             n = SkTMin((int)((p0 - fx) * invDx) + 1, count);
     891             :         }
     892             : 
     893         116 :         fx += n * dx;
     894             :         // fx should now outside of the p0..p1 interval. However, due to float precision loss,
     895             :         // its possible that fx is slightly too small/large, so we clamp it.
     896             :         if (dx_is_pos) {
     897           0 :             fx = SkTMax(fx, p1);
     898             :         } else {
     899         116 :             fx = SkTMin(fx, p0);
     900             :         }
     901             : 
     902         116 :         ramp<apply_alpha>(dstC, n, c, dc, dither0, dither1);
     903         116 :         dstC += n;
     904         116 :         SkASSERT(dstC <= endDstC);
     905             : 
     906         116 :         if (n & 1) {
     907         116 :             SkTSwap(dither0, dither1);
     908             :         }
     909             : 
     910         116 :         count -= n;
     911         116 :         SkASSERT(count >= 0);
     912             :     }
     913             : }
     914             : 
     915         577 : void SkLinearGradient::LinearGradientContext::shade4_clamp(int x, int y, SkPMColor dstC[],
     916             :                                                            int count) {
     917         577 :     SkASSERT(count > 0);
     918         577 :     SkASSERT(kLinear_MatrixClass == fDstToIndexClass);
     919             : 
     920             :     SkPoint srcPt;
     921         577 :     fDstToIndexProc(fDstToIndex, x + SK_ScalarHalf, y + SK_ScalarHalf, &srcPt);
     922         577 :     float fx = srcPt.x();
     923         577 :     const float dx = fDstToIndex.getScaleX();
     924             : 
     925             :     // Default our dither bias values to 1/2, (rounding), which is no dithering
     926         577 :     float dither0 = 0.5f;
     927         577 :     float dither1 = 0.5f;
     928         577 :     if (fDither) {
     929             :         const float ditherCell[] = {
     930             :             1/8.0f,   5/8.0f,
     931             :             7/8.0f,   3/8.0f,
     932           0 :         };
     933           0 :         const int rowIndex = (y & 1) << 1;
     934           0 :         dither0 = ditherCell[rowIndex];
     935           0 :         dither1 = ditherCell[rowIndex + 1];
     936           0 :         if (x & 1) {
     937           0 :             SkTSwap(dither0, dither1);
     938             :         }
     939             :     }
     940         577 :     const float dither[2] = { dither0, dither1 };
     941             : 
     942         577 :     if (SkScalarNearlyZero(dx * count)) { // gradient is vertical
     943         519 :         const float pinFx = SkTPin(fx, 0.0f, 1.0f);
     944         519 :         Sk4f c = lerp_color(pinFx, find_forward(fRecs.begin(), pinFx));
     945         519 :         if (fApplyAlphaAfterInterp) {
     946        1557 :             fill<true>(dstC, count, c, dither0, dither1);
     947             :         } else {
     948           0 :             fill<false>(dstC, count, c, dither0, dither1);
     949             :         }
     950         519 :         return;
     951             :     }
     952             : 
     953          58 :     SkASSERT(0.f != dx);
     954          58 :     const float invDx = 1 / dx;
     955          58 :     if (dx > 0) {
     956           0 :         if (fApplyAlphaAfterInterp) {
     957           0 :             this->shade4_dx_clamp<true, true>(dstC, count, fx, dx, invDx, dither);
     958             :         } else {
     959           0 :             this->shade4_dx_clamp<false, true>(dstC, count, fx, dx, invDx, dither);
     960             :         }
     961             :     } else {
     962          58 :         if (fApplyAlphaAfterInterp) {
     963          58 :             this->shade4_dx_clamp<true, false>(dstC, count, fx, dx, invDx, dither);
     964             :         } else {
     965           0 :             this->shade4_dx_clamp<false, false>(dstC, count, fx, dx, invDx, dither);
     966             :         }
     967             :     }
     968             : }

Generated by: LCOV version 1.13