Home | History | Annotate | Download | only in gradients
      1 /*
      2  * Copyright 2016 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 "Sk4x4f.h"
     10 #include "SkPaint.h"
     11 
     12 #include <cmath>
     13 
     14 namespace {
     15 
     16 template<typename dstType, ApplyPremul premul>
     17 void ramp(const Sk4f& c, const Sk4f& dc, dstType dst[], int n,
     18           const Sk4f& bias0, const Sk4f& bias1) {
     19     SkASSERT(n > 0);
     20 
     21     const Sk4f dc2 = dc + dc,
     22                dc4 = dc2 + dc2;
     23 
     24     Sk4f c0 =  c +      DstTraits<dstType, premul>::pre_lerp_bias(bias0),
     25          c1 =  c + dc + DstTraits<dstType, premul>::pre_lerp_bias(bias1),
     26          c2 = c0 + dc2,
     27          c3 = c1 + dc2;
     28 
     29     while (n >= 4) {
     30         DstTraits<dstType, premul>::store4x(c0, c1, c2, c3, dst, bias0, bias1);
     31         dst += 4;
     32 
     33         c0 = c0 + dc4;
     34         c1 = c1 + dc4;
     35         c2 = c2 + dc4;
     36         c3 = c3 + dc4;
     37         n -= 4;
     38     }
     39     if (n & 2) {
     40         DstTraits<dstType, premul>::store(c0, dst++, bias0);
     41         DstTraits<dstType, premul>::store(c1, dst++, bias1);
     42         c0 = c0 + dc2;
     43     }
     44     if (n & 1) {
     45         DstTraits<dstType, premul>::store(c0, dst, bias0);
     46     }
     47 }
     48 
     49 template<SkShader::TileMode>
     50 SkScalar pinFx(SkScalar);
     51 
     52 template<>
     53 SkScalar pinFx<SkShader::kClamp_TileMode>(SkScalar fx) {
     54     return fx;
     55 }
     56 
     57 template<>
     58 SkScalar pinFx<SkShader::kRepeat_TileMode>(SkScalar fx) {
     59     SkScalar f = SkScalarFraction(fx);
     60     if (f < 0) {
     61         f = SkTMin(f + 1, nextafterf(1, 0));
     62     }
     63     SkASSERT(f >= 0);
     64     SkASSERT(f < 1.0f);
     65     return f;
     66 }
     67 
     68 template<>
     69 SkScalar pinFx<SkShader::kMirror_TileMode>(SkScalar fx) {
     70     SkScalar f = SkScalarMod(fx, 2.0f);
     71     if (f < 0) {
     72         f = SkTMin(f + 2, nextafterf(2, 0));
     73     }
     74     SkASSERT(f >= 0);
     75     SkASSERT(f < 2.0f);
     76     return f;
     77 }
     78 
     79 // true when x is in [k1,k2], or [k2, k1] when the interval is reversed.
     80 // TODO(fmalita): hoist the reversed interval check out of this helper.
     81 bool in_range(SkScalar x, SkScalar k1, SkScalar k2) {
     82     SkASSERT(k1 != k2);
     83     return (k1 < k2)
     84         ? (x >= k1 && x <= k2)
     85         : (x >= k2 && x <= k1);
     86 }
     87 
     88 } // anonymous namespace
     89 
     90 SkLinearGradient::
     91 LinearGradient4fContext::LinearGradient4fContext(const SkLinearGradient& shader,
     92                                                  const ContextRec& rec)
     93     : INHERITED(shader, rec) {
     94 
     95     // Our fast path expects interval points to be monotonically increasing in x.
     96     const bool reverseIntervals = std::signbit(fDstToPos.getScaleX());
     97     fIntervals.init(shader, rec.fDstColorSpace, shader.fTileMode,
     98                     fColorsArePremul, rec.fPaint->getAlpha() * (1.0f / 255), reverseIntervals);
     99 
    100     SkASSERT(fIntervals->count() > 0);
    101     fCachedInterval = fIntervals->begin();
    102 }
    103 
    104 const Sk4fGradientInterval*
    105 SkLinearGradient::LinearGradient4fContext::findInterval(SkScalar fx) const {
    106     SkASSERT(in_range(fx, fIntervals->front().fT0, fIntervals->back().fT1));
    107 
    108     if (1) {
    109         // Linear search, using the last scanline interval as a starting point.
    110         SkASSERT(fCachedInterval >= fIntervals->begin());
    111         SkASSERT(fCachedInterval < fIntervals->end());
    112         const int search_dir = fDstToPos.getScaleX() >= 0 ? 1 : -1;
    113         while (!in_range(fx, fCachedInterval->fT0, fCachedInterval->fT1)) {
    114             fCachedInterval += search_dir;
    115             if (fCachedInterval >= fIntervals->end()) {
    116                 fCachedInterval = fIntervals->begin();
    117             } else if (fCachedInterval < fIntervals->begin()) {
    118                 fCachedInterval = fIntervals->end() - 1;
    119             }
    120         }
    121         return fCachedInterval;
    122     } else {
    123         // Binary search.  Seems less effective than linear + caching.
    124         const auto* i0 = fIntervals->begin();
    125         const auto* i1 = fIntervals->end() - 1;
    126 
    127         while (i0 != i1) {
    128             SkASSERT(i0 < i1);
    129             SkASSERT(in_range(fx, i0->fT0, i1->fT1));
    130 
    131             const auto* i = i0 + ((i1 - i0) >> 1);
    132 
    133             if (in_range(fx, i0->fT0, i->fT1)) {
    134                 i1 = i;
    135             } else {
    136                 SkASSERT(in_range(fx, i->fT1, i1->fT1));
    137                 i0 = i + 1;
    138             }
    139         }
    140 
    141         SkASSERT(in_range(fx, i0->fT0, i0->fT1));
    142         return i0;
    143     }
    144 }
    145 
    146 
    147 void SkLinearGradient::
    148 LinearGradient4fContext::shadeSpan(int x, int y, SkPMColor dst[], int count) {
    149     SkASSERT(count > 0);
    150 
    151     float bias0 = 0,
    152           bias1 = 0;
    153 
    154     if (fDither) {
    155         static constexpr float dither_cell[] = {
    156             -3/8.0f,  1/8.0f,
    157              3/8.0f, -1/8.0f,
    158         };
    159 
    160         const int rowIndex = (y & 1) << 1;
    161         bias0 = dither_cell[rowIndex + 0];
    162         bias1 = dither_cell[rowIndex + 1];
    163 
    164         if (x & 1) {
    165             SkTSwap(bias0, bias1);
    166         }
    167     }
    168 
    169     if (fColorsArePremul) {
    170         // In premul interpolation mode, components are pre-scaled by 255 and the store
    171         // op is truncating. We pre-bias here to achieve rounding.
    172         bias0 += 0.5f;
    173         bias1 += 0.5f;
    174 
    175         this->shadePremulSpan<SkPMColor, ApplyPremul::False>(x, y, dst, count, bias0, bias1);
    176     } else {
    177         // In unpremul interpolation mode, Components are not pre-scaled.
    178         bias0 *= 1/255.0f;
    179         bias1 *= 1/255.0f;
    180 
    181         this->shadePremulSpan<SkPMColor, ApplyPremul::True >(x, y, dst, count, bias0, bias1);
    182     }
    183 }
    184 
    185 void SkLinearGradient::
    186 LinearGradient4fContext::shadeSpan4f(int x, int y, SkPM4f dst[], int count) {
    187     SkASSERT(count > 0);
    188 
    189     // 4f dests are dithered at a later stage, if needed.
    190     static constexpr float bias0 = 0,
    191                            bias1 = 0;
    192     if (fColorsArePremul) {
    193         this->shadePremulSpan<SkPM4f, ApplyPremul::False>(x, y, dst, count, bias0, bias1);
    194     } else {
    195         this->shadePremulSpan<SkPM4f, ApplyPremul::True >(x, y, dst, count, bias0, bias1);
    196     }
    197 }
    198 
    199 template<typename dstType, ApplyPremul premul>
    200 void SkLinearGradient::
    201 LinearGradient4fContext::shadePremulSpan(int x, int y, dstType dst[], int count,
    202                                          float bias0, float bias1) const {
    203     const SkLinearGradient& shader = static_cast<const SkLinearGradient&>(fShader);
    204     switch (shader.fTileMode) {
    205     case kDecal_TileMode:
    206         SkASSERT(false);    // decal only supported via stages
    207         // fall-through
    208     case kClamp_TileMode:
    209         this->shadeSpanInternal<dstType, premul, kClamp_TileMode >(x, y, dst, count, bias0, bias1);
    210         break;
    211     case kRepeat_TileMode:
    212         this->shadeSpanInternal<dstType, premul, kRepeat_TileMode>(x, y, dst, count, bias0, bias1);
    213         break;
    214     case kMirror_TileMode:
    215         this->shadeSpanInternal<dstType, premul, kMirror_TileMode>(x, y, dst, count, bias0, bias1);
    216         break;
    217     }
    218 }
    219 
    220 template<typename dstType, ApplyPremul premul, SkShader::TileMode tileMode>
    221 void SkLinearGradient::
    222 LinearGradient4fContext::shadeSpanInternal(int x, int y, dstType dst[], int count,
    223                                            float bias0, float bias1) const {
    224     SkPoint pt;
    225     fDstToPosProc(fDstToPos,
    226                   x + SK_ScalarHalf,
    227                   y + SK_ScalarHalf,
    228                   &pt);
    229     const SkScalar fx = pinFx<tileMode>(pt.x());
    230     const SkScalar dx = fDstToPos.getScaleX();
    231     LinearIntervalProcessor<dstType, premul, tileMode> proc(fIntervals->begin(),
    232                                                             fIntervals->end() - 1,
    233                                                             this->findInterval(fx),
    234                                                             fx,
    235                                                             dx,
    236                                                             SkScalarNearlyZero(dx * count));
    237     Sk4f bias4f0(bias0),
    238          bias4f1(bias1);
    239 
    240     while (count > 0) {
    241         // What we really want here is SkTPin(advance, 1, count)
    242         // but that's a significant perf hit for >> stops; investigate.
    243         const int n = SkScalarTruncToInt(
    244             SkTMin<SkScalar>(proc.currentAdvance() + 1, SkIntToScalar(count)));
    245 
    246         // The current interval advance can be +inf (e.g. when reaching
    247         // the clamp mode end intervals) - when that happens, we expect to
    248         //   a) consume all remaining count in one swoop
    249         //   b) return a zero color gradient
    250         SkASSERT(SkScalarIsFinite(proc.currentAdvance())
    251             || (n == count && proc.currentRampIsZero()));
    252 
    253         if (proc.currentRampIsZero()) {
    254             DstTraits<dstType, premul>::store(proc.currentColor(), dst, n);
    255         } else {
    256             ramp<dstType, premul>(proc.currentColor(), proc.currentColorGrad(), dst, n,
    257                                   bias4f0, bias4f1);
    258         }
    259 
    260         proc.advance(SkIntToScalar(n));
    261         count -= n;
    262         dst   += n;
    263 
    264         if (n & 1) {
    265             SkTSwap(bias4f0, bias4f1);
    266         }
    267     }
    268 }
    269 
    270 template<typename dstType, ApplyPremul premul, SkShader::TileMode tileMode>
    271 class SkLinearGradient::
    272 LinearGradient4fContext::LinearIntervalProcessor {
    273 public:
    274     LinearIntervalProcessor(const Sk4fGradientInterval* firstInterval,
    275                             const Sk4fGradientInterval* lastInterval,
    276                             const Sk4fGradientInterval* i,
    277                             SkScalar fx,
    278                             SkScalar dx,
    279                             bool is_vertical)
    280         : fAdvX(is_vertical ? SK_ScalarInfinity : (i->fT1 - fx) / dx)
    281         , fFirstInterval(firstInterval)
    282         , fLastInterval(lastInterval)
    283         , fInterval(i)
    284         , fDx(dx)
    285         , fIsVertical(is_vertical)
    286     {
    287         SkASSERT(fAdvX >= 0);
    288         SkASSERT(firstInterval <= lastInterval);
    289 
    290         if (tileMode != kClamp_TileMode && !is_vertical) {
    291             const auto spanX = (lastInterval->fT1 - firstInterval->fT0) / dx;
    292             SkASSERT(spanX >= 0);
    293 
    294             // If we're in a repeating tile mode and the whole gradient is compressed into a
    295             // fraction of a pixel, we just use the average color in zero-ramp mode.
    296             // This also avoids cases where we make no progress due to interval advances being
    297             // close to zero.
    298             static constexpr SkScalar kMinSpanX = .25f;
    299             if (spanX < kMinSpanX) {
    300                 this->init_average_props();
    301                 return;
    302             }
    303         }
    304 
    305         this->compute_interval_props(fx);
    306     }
    307 
    308     SkScalar currentAdvance() const {
    309         SkASSERT(fAdvX >= 0);
    310         SkASSERT(fAdvX <= (fInterval->fT1 - fInterval->fT0) / fDx || !std::isfinite(fAdvX));
    311         return fAdvX;
    312     }
    313 
    314     bool currentRampIsZero() const { return fZeroRamp; }
    315     const Sk4f& currentColor() const { return fCc; }
    316     const Sk4f& currentColorGrad() const { return fDcDx; }
    317 
    318     void advance(SkScalar advX) {
    319         SkASSERT(advX > 0);
    320         SkASSERT(fAdvX >= 0);
    321 
    322         if (advX >= fAdvX) {
    323             advX = this->advance_interval(advX);
    324         }
    325         SkASSERT(advX < fAdvX);
    326 
    327         fCc = fCc + fDcDx * Sk4f(advX);
    328         fAdvX -= advX;
    329     }
    330 
    331 private:
    332     void compute_interval_props(SkScalar t) {
    333         SkASSERT(in_range(t, fInterval->fT0, fInterval->fT1));
    334 
    335         const Sk4f dc = DstTraits<dstType, premul>::load(fInterval->fCg);
    336                   fCc = DstTraits<dstType, premul>::load(fInterval->fCb) + dc * Sk4f(t);
    337                 fDcDx = dc * fDx;
    338             fZeroRamp = fIsVertical || (dc == 0).allTrue();
    339     }
    340 
    341     void init_average_props() {
    342         fAdvX     = SK_ScalarInfinity;
    343         fZeroRamp = true;
    344         fDcDx     = 0;
    345         fCc       = Sk4f(0);
    346 
    347         // TODO: precompute the average at interval setup time?
    348         for (const auto* i = fFirstInterval; i <= fLastInterval; ++i) {
    349             // Each interval contributes its average color to the total/weighted average:
    350             //
    351             //   C = (c0 + c1) / 2 = (Cb + Cg * t0 + Cb + Cg * t1) / 2 = Cb + Cg *(t0 + t1) / 2
    352             //
    353             //   Avg += C * (t1 - t0)
    354             //
    355             const auto c = DstTraits<dstType, premul>::load(i->fCb)
    356                          + DstTraits<dstType, premul>::load(i->fCg) * (i->fT0 + i->fT1) * 0.5f;
    357             fCc = fCc + c * (i->fT1 - i->fT0);
    358         }
    359     }
    360 
    361     const Sk4fGradientInterval* next_interval(const Sk4fGradientInterval* i) const {
    362         SkASSERT(i >= fFirstInterval);
    363         SkASSERT(i <= fLastInterval);
    364         i++;
    365 
    366         if (tileMode == kClamp_TileMode) {
    367             SkASSERT(i <= fLastInterval);
    368             return i;
    369         }
    370 
    371         return (i <= fLastInterval) ? i : fFirstInterval;
    372     }
    373 
    374     SkScalar advance_interval(SkScalar advX) {
    375         SkASSERT(advX >= fAdvX);
    376 
    377         do {
    378             advX -= fAdvX;
    379             fInterval = this->next_interval(fInterval);
    380             fAdvX = (fInterval->fT1 - fInterval->fT0) / fDx;
    381             SkASSERT(fAdvX > 0);
    382         } while (advX >= fAdvX);
    383 
    384         compute_interval_props(fInterval->fT0);
    385 
    386         SkASSERT(advX >= 0);
    387         return advX;
    388     }
    389 
    390     // Current interval properties.
    391     Sk4f            fDcDx;      // dst color gradient (dc/dx)
    392     Sk4f            fCc;        // current color, interpolated in dst
    393     SkScalar        fAdvX;      // remaining interval advance in dst
    394     bool            fZeroRamp;  // current interval color grad is 0
    395 
    396     const Sk4fGradientInterval* fFirstInterval;
    397     const Sk4fGradientInterval* fLastInterval;
    398     const Sk4fGradientInterval* fInterval;  // current interval
    399     const SkScalar              fDx;        // 'dx' for consistency with other impls; actually dt/dx
    400     const bool                  fIsVertical;
    401 };
    402