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