• 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 #include "SkPaint.h"
11 
12 #include <cmath>
13 
14 namespace {
15 
16 template<typename dstType, ApplyPremul premul>
ramp(const Sk4f & c,const Sk4f & dc,dstType dst[],int n,const Sk4f & bias0,const Sk4f & bias1)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<>
pinFx(SkScalar fx)53 SkScalar pinFx<SkShader::kClamp_TileMode>(SkScalar fx) {
54     return fx;
55 }
56 
57 template<>
pinFx(SkScalar fx)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<>
pinFx(SkScalar fx)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.
in_range(SkScalar x,SkScalar k1,SkScalar k2)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::
LinearGradient4fContext(const SkLinearGradient & shader,const ContextRec & rec)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*
findInterval(SkScalar fx) const105 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::
shadeSpan(int x,int y,SkPMColor dst[],int count)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::
shadeSpan4f(int x,int y,SkPM4f dst[],int count)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::
shadePremulSpan(int x,int y,dstType dst[],int count,float bias0,float bias1) const201 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 kClamp_TileMode:
206         this->shadeSpanInternal<dstType, premul, kClamp_TileMode >(x, y, dst, count, bias0, bias1);
207         break;
208     case kRepeat_TileMode:
209         this->shadeSpanInternal<dstType, premul, kRepeat_TileMode>(x, y, dst, count, bias0, bias1);
210         break;
211     case kMirror_TileMode:
212         this->shadeSpanInternal<dstType, premul, kMirror_TileMode>(x, y, dst, count, bias0, bias1);
213         break;
214     }
215 }
216 
217 template<typename dstType, ApplyPremul premul, SkShader::TileMode tileMode>
218 void SkLinearGradient::
shadeSpanInternal(int x,int y,dstType dst[],int count,float bias0,float bias1) const219 LinearGradient4fContext::shadeSpanInternal(int x, int y, dstType dst[], int count,
220                                            float bias0, float bias1) const {
221     SkPoint pt;
222     fDstToPosProc(fDstToPos,
223                   x + SK_ScalarHalf,
224                   y + SK_ScalarHalf,
225                   &pt);
226     const SkScalar fx = pinFx<tileMode>(pt.x());
227     const SkScalar dx = fDstToPos.getScaleX();
228     LinearIntervalProcessor<dstType, premul, tileMode> proc(fIntervals->begin(),
229                                                             fIntervals->end() - 1,
230                                                             this->findInterval(fx),
231                                                             fx,
232                                                             dx,
233                                                             SkScalarNearlyZero(dx * count));
234     Sk4f bias4f0(bias0),
235          bias4f1(bias1);
236 
237     while (count > 0) {
238         // What we really want here is SkTPin(advance, 1, count)
239         // but that's a significant perf hit for >> stops; investigate.
240         const int n = SkScalarTruncToInt(
241             SkTMin<SkScalar>(proc.currentAdvance() + 1, SkIntToScalar(count)));
242 
243         // The current interval advance can be +inf (e.g. when reaching
244         // the clamp mode end intervals) - when that happens, we expect to
245         //   a) consume all remaining count in one swoop
246         //   b) return a zero color gradient
247         SkASSERT(SkScalarIsFinite(proc.currentAdvance())
248             || (n == count && proc.currentRampIsZero()));
249 
250         if (proc.currentRampIsZero()) {
251             DstTraits<dstType, premul>::store(proc.currentColor(), dst, n);
252         } else {
253             ramp<dstType, premul>(proc.currentColor(), proc.currentColorGrad(), dst, n,
254                                   bias4f0, bias4f1);
255         }
256 
257         proc.advance(SkIntToScalar(n));
258         count -= n;
259         dst   += n;
260 
261         if (n & 1) {
262             SkTSwap(bias4f0, bias4f1);
263         }
264     }
265 }
266 
267 template<typename dstType, ApplyPremul premul, SkShader::TileMode tileMode>
268 class SkLinearGradient::
269 LinearGradient4fContext::LinearIntervalProcessor {
270 public:
LinearIntervalProcessor(const Sk4fGradientInterval * firstInterval,const Sk4fGradientInterval * lastInterval,const Sk4fGradientInterval * i,SkScalar fx,SkScalar dx,bool is_vertical)271     LinearIntervalProcessor(const Sk4fGradientInterval* firstInterval,
272                             const Sk4fGradientInterval* lastInterval,
273                             const Sk4fGradientInterval* i,
274                             SkScalar fx,
275                             SkScalar dx,
276                             bool is_vertical)
277         : fAdvX(is_vertical ? SK_ScalarInfinity : (i->fT1 - fx) / dx)
278         , fFirstInterval(firstInterval)
279         , fLastInterval(lastInterval)
280         , fInterval(i)
281         , fDx(dx)
282         , fIsVertical(is_vertical)
283     {
284         SkASSERT(fAdvX >= 0);
285         SkASSERT(firstInterval <= lastInterval);
286 
287         if (tileMode != kClamp_TileMode && !is_vertical) {
288             const auto spanX = (lastInterval->fT1 - firstInterval->fT0) / dx;
289             SkASSERT(spanX >= 0);
290 
291             // If we're in a repeating tile mode and the whole gradient is compressed into a
292             // fraction of a pixel, we just use the average color in zero-ramp mode.
293             // This also avoids cases where we make no progress due to interval advances being
294             // close to zero.
295             static constexpr SkScalar kMinSpanX = .25f;
296             if (spanX < kMinSpanX) {
297                 this->init_average_props();
298                 return;
299             }
300         }
301 
302         this->compute_interval_props(fx);
303     }
304 
currentAdvance() const305     SkScalar currentAdvance() const {
306         SkASSERT(fAdvX >= 0);
307         SkASSERT(fAdvX <= (fInterval->fT1 - fInterval->fT0) / fDx || !std::isfinite(fAdvX));
308         return fAdvX;
309     }
310 
currentRampIsZero() const311     bool currentRampIsZero() const { return fZeroRamp; }
currentColor() const312     const Sk4f& currentColor() const { return fCc; }
currentColorGrad() const313     const Sk4f& currentColorGrad() const { return fDcDx; }
314 
advance(SkScalar advX)315     void advance(SkScalar advX) {
316         SkASSERT(advX > 0);
317         SkASSERT(fAdvX >= 0);
318 
319         if (advX >= fAdvX) {
320             advX = this->advance_interval(advX);
321         }
322         SkASSERT(advX < fAdvX);
323 
324         fCc = fCc + fDcDx * Sk4f(advX);
325         fAdvX -= advX;
326     }
327 
328 private:
compute_interval_props(SkScalar t)329     void compute_interval_props(SkScalar t) {
330         SkASSERT(in_range(t, fInterval->fT0, fInterval->fT1));
331 
332         const Sk4f dc = DstTraits<dstType, premul>::load(fInterval->fCg);
333                   fCc = DstTraits<dstType, premul>::load(fInterval->fCb) + dc * Sk4f(t);
334                 fDcDx = dc * fDx;
335             fZeroRamp = fIsVertical || (dc == 0).allTrue();
336     }
337 
init_average_props()338     void init_average_props() {
339         fAdvX     = SK_ScalarInfinity;
340         fZeroRamp = true;
341         fDcDx     = 0;
342         fCc       = Sk4f(0);
343 
344         // TODO: precompute the average at interval setup time?
345         for (const auto* i = fFirstInterval; i <= fLastInterval; ++i) {
346             // Each interval contributes its average color to the total/weighted average:
347             //
348             //   C = (c0 + c1) / 2 = (Cb + Cg * t0 + Cb + Cg * t1) / 2 = Cb + Cg *(t0 + t1) / 2
349             //
350             //   Avg += C * (t1 - t0)
351             //
352             const auto c = DstTraits<dstType, premul>::load(i->fCb)
353                          + DstTraits<dstType, premul>::load(i->fCg) * (i->fT0 + i->fT1) * 0.5f;
354             fCc = fCc + c * (i->fT1 - i->fT0);
355         }
356     }
357 
next_interval(const Sk4fGradientInterval * i) const358     const Sk4fGradientInterval* next_interval(const Sk4fGradientInterval* i) const {
359         SkASSERT(i >= fFirstInterval);
360         SkASSERT(i <= fLastInterval);
361         i++;
362 
363         if (tileMode == kClamp_TileMode) {
364             SkASSERT(i <= fLastInterval);
365             return i;
366         }
367 
368         return (i <= fLastInterval) ? i : fFirstInterval;
369     }
370 
advance_interval(SkScalar advX)371     SkScalar advance_interval(SkScalar advX) {
372         SkASSERT(advX >= fAdvX);
373 
374         do {
375             advX -= fAdvX;
376             fInterval = this->next_interval(fInterval);
377             fAdvX = (fInterval->fT1 - fInterval->fT0) / fDx;
378             SkASSERT(fAdvX > 0);
379         } while (advX >= fAdvX);
380 
381         compute_interval_props(fInterval->fT0);
382 
383         SkASSERT(advX >= 0);
384         return advX;
385     }
386 
387     // Current interval properties.
388     Sk4f            fDcDx;      // dst color gradient (dc/dx)
389     Sk4f            fCc;        // current color, interpolated in dst
390     SkScalar        fAdvX;      // remaining interval advance in dst
391     bool            fZeroRamp;  // current interval color grad is 0
392 
393     const Sk4fGradientInterval* fFirstInterval;
394     const Sk4fGradientInterval* fLastInterval;
395     const Sk4fGradientInterval* fInterval;  // current interval
396     const SkScalar              fDx;        // 'dx' for consistency with other impls; actually dt/dx
397     const bool                  fIsVertical;
398 };
399