blob: 11bf96d98f43d7d24772c80f1d6335812c09f198 [file] [log] [blame]
fmalitabc590c02016-02-22 09:12:33 -08001/*
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"
fmalita83aa9202016-03-23 12:28:14 -07009#include "Sk4x4f.h"
fmalitabc590c02016-02-22 09:12:33 -080010
Ben Wagner8a1036c2016-11-09 15:00:49 -050011#include <cmath>
12
fmalitabc590c02016-02-22 09:12:33 -080013namespace {
14
Florin Malitaaa0ce822017-08-28 12:50:26 -040015template<typename dstType, ApplyPremul premul>
16void ramp(const Sk4f& c, const Sk4f& dc, dstType dst[], int n,
17 const Sk4f& bias0, const Sk4f& bias1) {
fmalitabc590c02016-02-22 09:12:33 -080018 SkASSERT(n > 0);
19
Florin Malitaaa0ce822017-08-28 12:50:26 -040020 const Sk4f dc2 = dc + dc,
21 dc4 = dc2 + dc2;
fmalitabc590c02016-02-22 09:12:33 -080022
Florin Malitaaa0ce822017-08-28 12:50:26 -040023 Sk4f c0 = c + DstTraits<dstType, premul>::pre_lerp_bias(bias0),
24 c1 = c + dc + DstTraits<dstType, premul>::pre_lerp_bias(bias1),
25 c2 = c0 + dc2,
26 c3 = c1 + dc2;
fmalitabc590c02016-02-22 09:12:33 -080027
28 while (n >= 4) {
Florin Malitaaa0ce822017-08-28 12:50:26 -040029 DstTraits<dstType, premul>::store4x(c0, c1, c2, c3, dst, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -080030 dst += 4;
31
32 c0 = c0 + dc4;
33 c1 = c1 + dc4;
34 c2 = c2 + dc4;
35 c3 = c3 + dc4;
36 n -= 4;
37 }
38 if (n & 2) {
Florin Malitaaa0ce822017-08-28 12:50:26 -040039 DstTraits<dstType, premul>::store(c0, dst++, bias0);
40 DstTraits<dstType, premul>::store(c1, dst++, bias1);
fmalitabc590c02016-02-22 09:12:33 -080041 c0 = c0 + dc2;
42 }
43 if (n & 1) {
Florin Malitaaa0ce822017-08-28 12:50:26 -040044 DstTraits<dstType, premul>::store(c0, dst, bias0);
fmalitabc590c02016-02-22 09:12:33 -080045 }
46}
47
48template<SkShader::TileMode>
49SkScalar pinFx(SkScalar);
50
51template<>
52SkScalar pinFx<SkShader::kClamp_TileMode>(SkScalar fx) {
53 return fx;
54}
55
56template<>
57SkScalar pinFx<SkShader::kRepeat_TileMode>(SkScalar fx) {
Florin Malita0fdde542016-11-14 15:54:04 -050058 SkScalar f = SkScalarFraction(fx);
59 if (f < 0) {
60 f = SkTMin(f + 1, nextafterf(1, 0));
61 }
62 SkASSERT(f >= 0);
63 SkASSERT(f < 1.0f);
64 return f;
fmalitabc590c02016-02-22 09:12:33 -080065}
66
67template<>
68SkScalar pinFx<SkShader::kMirror_TileMode>(SkScalar fx) {
Florin Malita0fdde542016-11-14 15:54:04 -050069 SkScalar f = SkScalarMod(fx, 2.0f);
70 if (f < 0) {
71 f = SkTMin(f + 2, nextafterf(2, 0));
72 }
73 SkASSERT(f >= 0);
74 SkASSERT(f < 2.0f);
75 return f;
fmalitabc590c02016-02-22 09:12:33 -080076}
77
Florin Malitae659c7f2017-02-09 13:46:55 -050078// true when x is in [k1,k2], or [k2, k1] when the interval is reversed.
fmalita6d7e4e82016-09-20 06:55:16 -070079// TODO(fmalita): hoist the reversed interval check out of this helper.
fmalita7520fc42016-03-04 11:01:24 -080080bool in_range(SkScalar x, SkScalar k1, SkScalar k2) {
81 SkASSERT(k1 != k2);
82 return (k1 < k2)
Florin Malitae659c7f2017-02-09 13:46:55 -050083 ? (x >= k1 && x <= k2)
84 : (x >= k2 && x <= k1);
fmalita7520fc42016-03-04 11:01:24 -080085}
86
fmalitabc590c02016-02-22 09:12:33 -080087} // anonymous namespace
88
89SkLinearGradient::
90LinearGradient4fContext::LinearGradient4fContext(const SkLinearGradient& shader,
91 const ContextRec& rec)
fmalita7520fc42016-03-04 11:01:24 -080092 : INHERITED(shader, rec) {
fmalita7520fc42016-03-04 11:01:24 -080093
fmalita7e6fcf82016-03-10 11:18:43 -080094 // Our fast path expects interval points to be monotonically increasing in x.
Florin Malita4d41b8f2017-07-12 14:35:46 -040095 const bool reverseIntervals = std::signbit(fDstToPos.getScaleX());
Florin Malita0e36b3f2017-06-05 23:33:45 -040096 fIntervals.init(shader, rec.fDstColorSpace, shader.fTileMode,
Florin Malitada4545b2017-03-23 17:04:54 -040097 fColorsArePremul, rec.fPaint->getAlpha() * (1.0f / 255), reverseIntervals);
fmalita7520fc42016-03-04 11:01:24 -080098
Florin Malitada4545b2017-03-23 17:04:54 -040099 SkASSERT(fIntervals->count() > 0);
100 fCachedInterval = fIntervals->begin();
fmalita7520fc42016-03-04 11:01:24 -0800101}
102
Florin Malitada4545b2017-03-23 17:04:54 -0400103const Sk4fGradientInterval*
fmalita7520fc42016-03-04 11:01:24 -0800104SkLinearGradient::LinearGradient4fContext::findInterval(SkScalar fx) const {
Florin Malitacf20f782017-04-07 14:56:14 -0400105 SkASSERT(in_range(fx, fIntervals->front().fT0, fIntervals->back().fT1));
fmalita7520fc42016-03-04 11:01:24 -0800106
107 if (1) {
108 // Linear search, using the last scanline interval as a starting point.
Florin Malitada4545b2017-03-23 17:04:54 -0400109 SkASSERT(fCachedInterval >= fIntervals->begin());
110 SkASSERT(fCachedInterval < fIntervals->end());
fmalita7520fc42016-03-04 11:01:24 -0800111 const int search_dir = fDstToPos.getScaleX() >= 0 ? 1 : -1;
Florin Malitacf20f782017-04-07 14:56:14 -0400112 while (!in_range(fx, fCachedInterval->fT0, fCachedInterval->fT1)) {
fmalita7520fc42016-03-04 11:01:24 -0800113 fCachedInterval += search_dir;
Florin Malitada4545b2017-03-23 17:04:54 -0400114 if (fCachedInterval >= fIntervals->end()) {
115 fCachedInterval = fIntervals->begin();
116 } else if (fCachedInterval < fIntervals->begin()) {
117 fCachedInterval = fIntervals->end() - 1;
fmalita7520fc42016-03-04 11:01:24 -0800118 }
119 }
120 return fCachedInterval;
121 } else {
122 // Binary search. Seems less effective than linear + caching.
Florin Malitada4545b2017-03-23 17:04:54 -0400123 const auto* i0 = fIntervals->begin();
124 const auto* i1 = fIntervals->end() - 1;
fmalita7520fc42016-03-04 11:01:24 -0800125
126 while (i0 != i1) {
127 SkASSERT(i0 < i1);
Florin Malitacf20f782017-04-07 14:56:14 -0400128 SkASSERT(in_range(fx, i0->fT0, i1->fT1));
fmalita7520fc42016-03-04 11:01:24 -0800129
Florin Malitada4545b2017-03-23 17:04:54 -0400130 const auto* i = i0 + ((i1 - i0) >> 1);
fmalita7520fc42016-03-04 11:01:24 -0800131
Florin Malitacf20f782017-04-07 14:56:14 -0400132 if (in_range(fx, i0->fT0, i->fT1)) {
fmalita7520fc42016-03-04 11:01:24 -0800133 i1 = i;
134 } else {
Florin Malitacf20f782017-04-07 14:56:14 -0400135 SkASSERT(in_range(fx, i->fT1, i1->fT1));
fmalita7520fc42016-03-04 11:01:24 -0800136 i0 = i + 1;
137 }
138 }
139
Florin Malitacf20f782017-04-07 14:56:14 -0400140 SkASSERT(in_range(fx, i0->fT0, i0->fT1));
fmalita7520fc42016-03-04 11:01:24 -0800141 return i0;
142 }
143}
fmalitabc590c02016-02-22 09:12:33 -0800144
Florin Malitaaa0ce822017-08-28 12:50:26 -0400145
146void SkLinearGradient::
147LinearGradient4fContext::shadeSpan(int x, int y, SkPMColor dst[], int count) {
148 SkASSERT(count > 0);
149
150 float bias0 = 0,
151 bias1 = 0;
152
153 if (fDither) {
154 static constexpr float dither_cell[] = {
155 -3/8.0f, 1/8.0f,
156 3/8.0f, -1/8.0f,
157 };
158
159 const int rowIndex = (y & 1) << 1;
160 bias0 = dither_cell[rowIndex + 0];
161 bias1 = dither_cell[rowIndex + 1];
162
163 if (x & 1) {
164 SkTSwap(bias0, bias1);
165 }
166 }
167
168 if (fColorsArePremul) {
169 // In premul interpolation mode, components are pre-scaled by 255 and the store
170 // op is truncating. We pre-bias here to achieve rounding.
171 bias0 += 0.5f;
172 bias1 += 0.5f;
173
174 this->shadePremulSpan<SkPMColor, ApplyPremul::False>(x, y, dst, count, bias0, bias1);
175 } else {
176 // In unpremul interpolation mode, Components are not pre-scaled.
177 bias0 *= 1/255.0f;
178 bias1 *= 1/255.0f;
179
180 this->shadePremulSpan<SkPMColor, ApplyPremul::True >(x, y, dst, count, bias0, bias1);
181 }
182}
183
fmalitabc590c02016-02-22 09:12:33 -0800184void SkLinearGradient::
fmalitabc590c02016-02-22 09:12:33 -0800185LinearGradient4fContext::shadeSpan4f(int x, int y, SkPM4f dst[], int count) {
fmalitabc590c02016-02-22 09:12:33 -0800186 SkASSERT(count > 0);
Florin Malitaaa0ce822017-08-28 12:50:26 -0400187
188 // 4f dests are dithered at a later stage, if needed.
189 static constexpr float bias0 = 0,
190 bias1 = 0;
fmalitabc590c02016-02-22 09:12:33 -0800191 if (fColorsArePremul) {
Florin Malitaaa0ce822017-08-28 12:50:26 -0400192 this->shadePremulSpan<SkPM4f, ApplyPremul::False>(x, y, dst, count, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -0800193 } else {
Florin Malitaaa0ce822017-08-28 12:50:26 -0400194 this->shadePremulSpan<SkPM4f, ApplyPremul::True >(x, y, dst, count, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -0800195 }
196}
197
Florin Malitaaa0ce822017-08-28 12:50:26 -0400198template<typename dstType, ApplyPremul premul>
fmalitabc590c02016-02-22 09:12:33 -0800199void SkLinearGradient::
Florin Malitaaa0ce822017-08-28 12:50:26 -0400200LinearGradient4fContext::shadePremulSpan(int x, int y, dstType dst[], int count,
201 float bias0, float bias1) const {
202 const SkLinearGradient& shader = static_cast<const SkLinearGradient&>(fShader);
fmalitabc590c02016-02-22 09:12:33 -0800203 switch (shader.fTileMode) {
204 case kClamp_TileMode:
Florin Malitaaa0ce822017-08-28 12:50:26 -0400205 this->shadeSpanInternal<dstType, premul, kClamp_TileMode >(x, y, dst, count, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -0800206 break;
207 case kRepeat_TileMode:
Florin Malitaaa0ce822017-08-28 12:50:26 -0400208 this->shadeSpanInternal<dstType, premul, kRepeat_TileMode>(x, y, dst, count, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -0800209 break;
210 case kMirror_TileMode:
Florin Malitaaa0ce822017-08-28 12:50:26 -0400211 this->shadeSpanInternal<dstType, premul, kMirror_TileMode>(x, y, dst, count, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -0800212 break;
213 }
214}
215
Florin Malitaaa0ce822017-08-28 12:50:26 -0400216template<typename dstType, ApplyPremul premul, SkShader::TileMode tileMode>
fmalitabc590c02016-02-22 09:12:33 -0800217void SkLinearGradient::
Florin Malitaaa0ce822017-08-28 12:50:26 -0400218LinearGradient4fContext::shadeSpanInternal(int x, int y, dstType dst[], int count,
219 float bias0, float bias1) const {
fmalitabc590c02016-02-22 09:12:33 -0800220 SkPoint pt;
221 fDstToPosProc(fDstToPos,
222 x + SK_ScalarHalf,
223 y + SK_ScalarHalf,
224 &pt);
225 const SkScalar fx = pinFx<tileMode>(pt.x());
226 const SkScalar dx = fDstToPos.getScaleX();
Florin Malitaaa0ce822017-08-28 12:50:26 -0400227 LinearIntervalProcessor<dstType, premul, tileMode> proc(fIntervals->begin(),
228 fIntervals->end() - 1,
229 this->findInterval(fx),
230 fx,
231 dx,
232 SkScalarNearlyZero(dx * count));
233 Sk4f bias4f0(bias0),
234 bias4f1(bias1);
235
fmalitabc590c02016-02-22 09:12:33 -0800236 while (count > 0) {
237 // What we really want here is SkTPin(advance, 1, count)
238 // but that's a significant perf hit for >> stops; investigate.
239 const int n = SkScalarTruncToInt(
240 SkTMin<SkScalar>(proc.currentAdvance() + 1, SkIntToScalar(count)));
241
242 // The current interval advance can be +inf (e.g. when reaching
243 // the clamp mode end intervals) - when that happens, we expect to
244 // a) consume all remaining count in one swoop
245 // b) return a zero color gradient
246 SkASSERT(SkScalarIsFinite(proc.currentAdvance())
247 || (n == count && proc.currentRampIsZero()));
248
249 if (proc.currentRampIsZero()) {
Florin Malitaaa0ce822017-08-28 12:50:26 -0400250 DstTraits<dstType, premul>::store(proc.currentColor(), dst, n);
fmalitabc590c02016-02-22 09:12:33 -0800251 } else {
Florin Malitaaa0ce822017-08-28 12:50:26 -0400252 ramp<dstType, premul>(proc.currentColor(), proc.currentColorGrad(), dst, n,
253 bias4f0, bias4f1);
fmalitabc590c02016-02-22 09:12:33 -0800254 }
255
256 proc.advance(SkIntToScalar(n));
257 count -= n;
258 dst += n;
Florin Malitaaa0ce822017-08-28 12:50:26 -0400259
260 if (n & 1) {
261 SkTSwap(bias4f0, bias4f1);
262 }
fmalitabc590c02016-02-22 09:12:33 -0800263 }
264}
265
Florin Malitaaa0ce822017-08-28 12:50:26 -0400266template<typename dstType, ApplyPremul premul, SkShader::TileMode tileMode>
fmalitabc590c02016-02-22 09:12:33 -0800267class SkLinearGradient::
268LinearGradient4fContext::LinearIntervalProcessor {
269public:
Florin Malitada4545b2017-03-23 17:04:54 -0400270 LinearIntervalProcessor(const Sk4fGradientInterval* firstInterval,
271 const Sk4fGradientInterval* lastInterval,
272 const Sk4fGradientInterval* i,
fmalitabc590c02016-02-22 09:12:33 -0800273 SkScalar fx,
274 SkScalar dx,
275 bool is_vertical)
Florin Malitacf20f782017-04-07 14:56:14 -0400276 : fAdvX(is_vertical ? SK_ScalarInfinity : (i->fT1 - fx) / dx)
fmalitabc590c02016-02-22 09:12:33 -0800277 , fFirstInterval(firstInterval)
278 , fLastInterval(lastInterval)
279 , fInterval(i)
280 , fDx(dx)
281 , fIsVertical(is_vertical)
282 {
fmalita6d7e4e82016-09-20 06:55:16 -0700283 SkASSERT(fAdvX >= 0);
fmalitabc590c02016-02-22 09:12:33 -0800284 SkASSERT(firstInterval <= lastInterval);
fmalitaafac5812016-11-01 13:41:34 -0700285
286 if (tileMode != kClamp_TileMode && !is_vertical) {
Florin Malitacf20f782017-04-07 14:56:14 -0400287 const auto spanX = (lastInterval->fT1 - firstInterval->fT0) / dx;
fmalitaafac5812016-11-01 13:41:34 -0700288 SkASSERT(spanX >= 0);
289
290 // If we're in a repeating tile mode and the whole gradient is compressed into a
291 // fraction of a pixel, we just use the average color in zero-ramp mode.
292 // This also avoids cases where we make no progress due to interval advances being
293 // close to zero.
294 static constexpr SkScalar kMinSpanX = .25f;
295 if (spanX < kMinSpanX) {
296 this->init_average_props();
297 return;
298 }
299 }
300
Florin Malitacf20f782017-04-07 14:56:14 -0400301 this->compute_interval_props(fx);
fmalitabc590c02016-02-22 09:12:33 -0800302 }
303
304 SkScalar currentAdvance() const {
305 SkASSERT(fAdvX >= 0);
Florin Malitacf20f782017-04-07 14:56:14 -0400306 SkASSERT(fAdvX <= (fInterval->fT1 - fInterval->fT0) / fDx || !std::isfinite(fAdvX));
fmalitabc590c02016-02-22 09:12:33 -0800307 return fAdvX;
308 }
309
310 bool currentRampIsZero() const { return fZeroRamp; }
311 const Sk4f& currentColor() const { return fCc; }
312 const Sk4f& currentColorGrad() const { return fDcDx; }
313
314 void advance(SkScalar advX) {
315 SkASSERT(advX > 0);
316 SkASSERT(fAdvX >= 0);
317
318 if (advX >= fAdvX) {
319 advX = this->advance_interval(advX);
320 }
321 SkASSERT(advX < fAdvX);
322
323 fCc = fCc + fDcDx * Sk4f(advX);
324 fAdvX -= advX;
325 }
326
327private:
328 void compute_interval_props(SkScalar t) {
Florin Malitacf20f782017-04-07 14:56:14 -0400329 SkASSERT(in_range(t, fInterval->fT0, fInterval->fT1));
330
Florin Malitaaa0ce822017-08-28 12:50:26 -0400331 const Sk4f dc = DstTraits<dstType, premul>::load(fInterval->fCg);
332 fCc = DstTraits<dstType, premul>::load(fInterval->fCb) + dc * Sk4f(t);
Florin Malita63f717d2017-05-08 09:53:11 -0400333 fDcDx = dc * fDx;
334 fZeroRamp = fIsVertical || (dc == 0).allTrue();
fmalitabc590c02016-02-22 09:12:33 -0800335 }
336
fmalitaafac5812016-11-01 13:41:34 -0700337 void init_average_props() {
338 fAdvX = SK_ScalarInfinity;
339 fZeroRamp = true;
340 fDcDx = 0;
341 fCc = Sk4f(0);
342
343 // TODO: precompute the average at interval setup time?
344 for (const auto* i = fFirstInterval; i <= fLastInterval; ++i) {
345 // Each interval contributes its average color to the total/weighted average:
346 //
Florin Malitacf20f782017-04-07 14:56:14 -0400347 // C = (c0 + c1) / 2 = (Cb + Cg * t0 + Cb + Cg * t1) / 2 = Cb + Cg *(t0 + t1) / 2
fmalitaafac5812016-11-01 13:41:34 -0700348 //
Florin Malitacf20f782017-04-07 14:56:14 -0400349 // Avg += C * (t1 - t0)
fmalitaafac5812016-11-01 13:41:34 -0700350 //
Florin Malitaaa0ce822017-08-28 12:50:26 -0400351 const auto c = DstTraits<dstType, premul>::load(i->fCb)
352 + DstTraits<dstType, premul>::load(i->fCg) * (i->fT0 + i->fT1) * 0.5f;
Florin Malitacf20f782017-04-07 14:56:14 -0400353 fCc = fCc + c * (i->fT1 - i->fT0);
fmalitaafac5812016-11-01 13:41:34 -0700354 }
355 }
356
Florin Malitada4545b2017-03-23 17:04:54 -0400357 const Sk4fGradientInterval* next_interval(const Sk4fGradientInterval* i) const {
fmalitabc590c02016-02-22 09:12:33 -0800358 SkASSERT(i >= fFirstInterval);
359 SkASSERT(i <= fLastInterval);
360 i++;
361
362 if (tileMode == kClamp_TileMode) {
363 SkASSERT(i <= fLastInterval);
364 return i;
365 }
366
367 return (i <= fLastInterval) ? i : fFirstInterval;
368 }
369
370 SkScalar advance_interval(SkScalar advX) {
371 SkASSERT(advX >= fAdvX);
372
373 do {
374 advX -= fAdvX;
375 fInterval = this->next_interval(fInterval);
Florin Malitacf20f782017-04-07 14:56:14 -0400376 fAdvX = (fInterval->fT1 - fInterval->fT0) / fDx;
fmalitabc590c02016-02-22 09:12:33 -0800377 SkASSERT(fAdvX > 0);
378 } while (advX >= fAdvX);
379
Florin Malitacf20f782017-04-07 14:56:14 -0400380 compute_interval_props(fInterval->fT0);
fmalitabc590c02016-02-22 09:12:33 -0800381
382 SkASSERT(advX >= 0);
383 return advX;
384 }
385
fmalitabc590c02016-02-22 09:12:33 -0800386 // Current interval properties.
fmalitabc590c02016-02-22 09:12:33 -0800387 Sk4f fDcDx; // dst color gradient (dc/dx)
388 Sk4f fCc; // current color, interpolated in dst
389 SkScalar fAdvX; // remaining interval advance in dst
390 bool fZeroRamp; // current interval color grad is 0
391
Florin Malitada4545b2017-03-23 17:04:54 -0400392 const Sk4fGradientInterval* fFirstInterval;
393 const Sk4fGradientInterval* fLastInterval;
394 const Sk4fGradientInterval* fInterval; // current interval
395 const SkScalar fDx; // 'dx' for consistency with other impls; actually dt/dx
396 const bool fIsVertical;
fmalitabc590c02016-02-22 09:12:33 -0800397};