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