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