blob: 202461cc22287478630a8d83b500afd68f888686 [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
fmalitadc6c9bf2016-03-21 13:16:51 -070015template<DstType dstType, ApplyPremul premul>
16void ramp(const Sk4f& c, const Sk4f& dc, typename DstTraits<dstType, premul>::Type dst[], int n) {
fmalitabc590c02016-02-22 09:12:33 -080017 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) {
fmalitadc6c9bf2016-03-21 13:16:51 -070028 DstTraits<dstType, premul>::store4x(c0, c1, c2, c3, dst);
fmalitabc590c02016-02-22 09:12:33 -080029 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) {
fmalitadc6c9bf2016-03-21 13:16:51 -070038 DstTraits<dstType, premul>::store(c0, dst++);
39 DstTraits<dstType, premul>::store(c1, dst++);
fmalitabc590c02016-02-22 09:12:33 -080040 c0 = c0 + dc2;
41 }
42 if (n & 1) {
fmalitadc6c9bf2016-03-21 13:16:51 -070043 DstTraits<dstType, premul>::store(c0, dst);
fmalitabc590c02016-02-22 09:12:33 -080044 }
45}
46
47template<SkShader::TileMode>
48SkScalar pinFx(SkScalar);
49
50template<>
51SkScalar pinFx<SkShader::kClamp_TileMode>(SkScalar fx) {
52 return fx;
53}
54
55template<>
56SkScalar pinFx<SkShader::kRepeat_TileMode>(SkScalar fx) {
Florin Malita0fdde542016-11-14 15:54:04 -050057 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;
fmalitabc590c02016-02-22 09:12:33 -080064}
65
66template<>
67SkScalar pinFx<SkShader::kMirror_TileMode>(SkScalar fx) {
Florin Malita0fdde542016-11-14 15:54:04 -050068 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;
fmalitabc590c02016-02-22 09:12:33 -080075}
76
Florin Malitae659c7f2017-02-09 13:46:55 -050077// true when x is in [k1,k2], or [k2, k1] when the interval is reversed.
fmalita6d7e4e82016-09-20 06:55:16 -070078// TODO(fmalita): hoist the reversed interval check out of this helper.
fmalita7520fc42016-03-04 11:01:24 -080079bool in_range(SkScalar x, SkScalar k1, SkScalar k2) {
80 SkASSERT(k1 != k2);
81 return (k1 < k2)
Florin Malitae659c7f2017-02-09 13:46:55 -050082 ? (x >= k1 && x <= k2)
83 : (x >= k2 && x <= k1);
fmalita7520fc42016-03-04 11:01:24 -080084}
85
fmalitabc590c02016-02-22 09:12:33 -080086} // anonymous namespace
87
88SkLinearGradient::
89LinearGradient4fContext::LinearGradient4fContext(const SkLinearGradient& shader,
90 const ContextRec& rec)
fmalita7520fc42016-03-04 11:01:24 -080091 : INHERITED(shader, rec) {
fmalita7520fc42016-03-04 11:01:24 -080092
fmalita7e6fcf82016-03-10 11:18:43 -080093 // Our fast path expects interval points to be monotonically increasing in x.
Ben Wagner8a1036c2016-11-09 15:00:49 -050094 const bool reverseIntervals = this->isFast() && std::signbit(fDstToPos.getScaleX());
Florin Malita0e36b3f2017-06-05 23:33:45 -040095 fIntervals.init(shader, rec.fDstColorSpace, shader.fTileMode,
Florin Malitada4545b2017-03-23 17:04:54 -040096 fColorsArePremul, rec.fPaint->getAlpha() * (1.0f / 255), reverseIntervals);
fmalita7520fc42016-03-04 11:01:24 -080097
Florin Malitada4545b2017-03-23 17:04:54 -040098 SkASSERT(fIntervals->count() > 0);
99 fCachedInterval = fIntervals->begin();
fmalita7520fc42016-03-04 11:01:24 -0800100}
101
Florin Malitada4545b2017-03-23 17:04:54 -0400102const Sk4fGradientInterval*
fmalita7520fc42016-03-04 11:01:24 -0800103SkLinearGradient::LinearGradient4fContext::findInterval(SkScalar fx) const {
Florin Malitacf20f782017-04-07 14:56:14 -0400104 SkASSERT(in_range(fx, fIntervals->front().fT0, fIntervals->back().fT1));
fmalita7520fc42016-03-04 11:01:24 -0800105
106 if (1) {
107 // Linear search, using the last scanline interval as a starting point.
Florin Malitada4545b2017-03-23 17:04:54 -0400108 SkASSERT(fCachedInterval >= fIntervals->begin());
109 SkASSERT(fCachedInterval < fIntervals->end());
fmalita7520fc42016-03-04 11:01:24 -0800110 const int search_dir = fDstToPos.getScaleX() >= 0 ? 1 : -1;
Florin Malitacf20f782017-04-07 14:56:14 -0400111 while (!in_range(fx, fCachedInterval->fT0, fCachedInterval->fT1)) {
fmalita7520fc42016-03-04 11:01:24 -0800112 fCachedInterval += search_dir;
Florin Malitada4545b2017-03-23 17:04:54 -0400113 if (fCachedInterval >= fIntervals->end()) {
114 fCachedInterval = fIntervals->begin();
115 } else if (fCachedInterval < fIntervals->begin()) {
116 fCachedInterval = fIntervals->end() - 1;
fmalita7520fc42016-03-04 11:01:24 -0800117 }
118 }
119 return fCachedInterval;
120 } else {
121 // Binary search. Seems less effective than linear + caching.
Florin Malitada4545b2017-03-23 17:04:54 -0400122 const auto* i0 = fIntervals->begin();
123 const auto* i1 = fIntervals->end() - 1;
fmalita7520fc42016-03-04 11:01:24 -0800124
125 while (i0 != i1) {
126 SkASSERT(i0 < i1);
Florin Malitacf20f782017-04-07 14:56:14 -0400127 SkASSERT(in_range(fx, i0->fT0, i1->fT1));
fmalita7520fc42016-03-04 11:01:24 -0800128
Florin Malitada4545b2017-03-23 17:04:54 -0400129 const auto* i = i0 + ((i1 - i0) >> 1);
fmalita7520fc42016-03-04 11:01:24 -0800130
Florin Malitacf20f782017-04-07 14:56:14 -0400131 if (in_range(fx, i0->fT0, i->fT1)) {
fmalita7520fc42016-03-04 11:01:24 -0800132 i1 = i;
133 } else {
Florin Malitacf20f782017-04-07 14:56:14 -0400134 SkASSERT(in_range(fx, i->fT1, i1->fT1));
fmalita7520fc42016-03-04 11:01:24 -0800135 i0 = i + 1;
136 }
137 }
138
Florin Malitacf20f782017-04-07 14:56:14 -0400139 SkASSERT(in_range(fx, i0->fT0, i0->fT1));
fmalita7520fc42016-03-04 11:01:24 -0800140 return i0;
141 }
142}
fmalitabc590c02016-02-22 09:12:33 -0800143
144void SkLinearGradient::
145LinearGradient4fContext::shadeSpan(int x, int y, SkPMColor dst[], int count) {
fmalita7e6fcf82016-03-10 11:18:43 -0800146 if (!this->isFast()) {
147 this->INHERITED::shadeSpan(x, y, dst, count);
148 return;
149 }
150
fmalitabc590c02016-02-22 09:12:33 -0800151 // TODO: plumb dithering
152 SkASSERT(count > 0);
153 if (fColorsArePremul) {
fmalitadc6c9bf2016-03-21 13:16:51 -0700154 this->shadePremulSpan<DstType::L32,
fmalitaa928b282016-03-18 10:28:23 -0700155 ApplyPremul::False>(x, y, dst, count);
fmalitabc590c02016-02-22 09:12:33 -0800156 } else {
fmalitadc6c9bf2016-03-21 13:16:51 -0700157 this->shadePremulSpan<DstType::L32,
fmalitaa928b282016-03-18 10:28:23 -0700158 ApplyPremul::True>(x, y, dst, count);
fmalitabc590c02016-02-22 09:12:33 -0800159 }
160}
161
162void SkLinearGradient::
163LinearGradient4fContext::shadeSpan4f(int x, int y, SkPM4f dst[], int count) {
fmalita7e6fcf82016-03-10 11:18:43 -0800164 if (!this->isFast()) {
165 this->INHERITED::shadeSpan4f(x, y, dst, count);
166 return;
167 }
168
fmalitabc590c02016-02-22 09:12:33 -0800169 // TONOTDO: plumb dithering
170 SkASSERT(count > 0);
171 if (fColorsArePremul) {
fmalitadc6c9bf2016-03-21 13:16:51 -0700172 this->shadePremulSpan<DstType::F32,
fmalitaa928b282016-03-18 10:28:23 -0700173 ApplyPremul::False>(x, y, dst, count);
fmalitabc590c02016-02-22 09:12:33 -0800174 } else {
fmalitadc6c9bf2016-03-21 13:16:51 -0700175 this->shadePremulSpan<DstType::F32,
fmalitaa928b282016-03-18 10:28:23 -0700176 ApplyPremul::True>(x, y, dst, count);
fmalitabc590c02016-02-22 09:12:33 -0800177 }
178}
179
fmalitadc6c9bf2016-03-21 13:16:51 -0700180template<DstType dstType, ApplyPremul premul>
fmalitabc590c02016-02-22 09:12:33 -0800181void SkLinearGradient::
182LinearGradient4fContext::shadePremulSpan(int x, int y,
fmalitadc6c9bf2016-03-21 13:16:51 -0700183 typename DstTraits<dstType, premul>::Type dst[],
fmalitabc590c02016-02-22 09:12:33 -0800184 int count) const {
185 const SkLinearGradient& shader =
186 static_cast<const SkLinearGradient&>(fShader);
187 switch (shader.fTileMode) {
188 case kClamp_TileMode:
fmalitadc6c9bf2016-03-21 13:16:51 -0700189 this->shadeSpanInternal<dstType,
fmalitaa928b282016-03-18 10:28:23 -0700190 premul,
fmalitabc590c02016-02-22 09:12:33 -0800191 kClamp_TileMode>(x, y, dst, count);
192 break;
193 case kRepeat_TileMode:
fmalitadc6c9bf2016-03-21 13:16:51 -0700194 this->shadeSpanInternal<dstType,
fmalitaa928b282016-03-18 10:28:23 -0700195 premul,
fmalitabc590c02016-02-22 09:12:33 -0800196 kRepeat_TileMode>(x, y, dst, count);
197 break;
198 case kMirror_TileMode:
fmalitadc6c9bf2016-03-21 13:16:51 -0700199 this->shadeSpanInternal<dstType,
fmalitaa928b282016-03-18 10:28:23 -0700200 premul,
fmalitabc590c02016-02-22 09:12:33 -0800201 kMirror_TileMode>(x, y, dst, count);
202 break;
203 }
204}
205
fmalitadc6c9bf2016-03-21 13:16:51 -0700206template<DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode>
fmalitabc590c02016-02-22 09:12:33 -0800207void SkLinearGradient::
208LinearGradient4fContext::shadeSpanInternal(int x, int y,
fmalitadc6c9bf2016-03-21 13:16:51 -0700209 typename DstTraits<dstType, premul>::Type dst[],
fmalitabc590c02016-02-22 09:12:33 -0800210 int count) const {
211 SkPoint pt;
212 fDstToPosProc(fDstToPos,
213 x + SK_ScalarHalf,
214 y + SK_ScalarHalf,
215 &pt);
216 const SkScalar fx = pinFx<tileMode>(pt.x());
217 const SkScalar dx = fDstToPos.getScaleX();
Florin Malitada4545b2017-03-23 17:04:54 -0400218 LinearIntervalProcessor<dstType, premul, tileMode> proc(fIntervals->begin(),
219 fIntervals->end() - 1,
fmalita3a2e45a2016-10-14 08:18:24 -0700220 this->findInterval(fx),
221 fx,
222 dx,
223 SkScalarNearlyZero(dx * count));
fmalitabc590c02016-02-22 09:12:33 -0800224 while (count > 0) {
225 // What we really want here is SkTPin(advance, 1, count)
226 // but that's a significant perf hit for >> stops; investigate.
227 const int n = SkScalarTruncToInt(
228 SkTMin<SkScalar>(proc.currentAdvance() + 1, SkIntToScalar(count)));
229
230 // The current interval advance can be +inf (e.g. when reaching
231 // the clamp mode end intervals) - when that happens, we expect to
232 // a) consume all remaining count in one swoop
233 // b) return a zero color gradient
234 SkASSERT(SkScalarIsFinite(proc.currentAdvance())
235 || (n == count && proc.currentRampIsZero()));
236
237 if (proc.currentRampIsZero()) {
fmalitadc6c9bf2016-03-21 13:16:51 -0700238 DstTraits<dstType, premul>::store(proc.currentColor(),
239 dst, n);
fmalitabc590c02016-02-22 09:12:33 -0800240 } else {
fmalitadc6c9bf2016-03-21 13:16:51 -0700241 ramp<dstType, premul>(proc.currentColor(),
242 proc.currentColorGrad(),
243 dst, n);
fmalitabc590c02016-02-22 09:12:33 -0800244 }
245
246 proc.advance(SkIntToScalar(n));
247 count -= n;
248 dst += n;
249 }
250}
251
fmalita3a2e45a2016-10-14 08:18:24 -0700252template<DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode>
fmalitabc590c02016-02-22 09:12:33 -0800253class SkLinearGradient::
254LinearGradient4fContext::LinearIntervalProcessor {
255public:
Florin Malitada4545b2017-03-23 17:04:54 -0400256 LinearIntervalProcessor(const Sk4fGradientInterval* firstInterval,
257 const Sk4fGradientInterval* lastInterval,
258 const Sk4fGradientInterval* i,
fmalitabc590c02016-02-22 09:12:33 -0800259 SkScalar fx,
260 SkScalar dx,
261 bool is_vertical)
Florin Malitacf20f782017-04-07 14:56:14 -0400262 : fAdvX(is_vertical ? SK_ScalarInfinity : (i->fT1 - fx) / dx)
fmalitabc590c02016-02-22 09:12:33 -0800263 , fFirstInterval(firstInterval)
264 , fLastInterval(lastInterval)
265 , fInterval(i)
266 , fDx(dx)
267 , fIsVertical(is_vertical)
268 {
fmalita6d7e4e82016-09-20 06:55:16 -0700269 SkASSERT(fAdvX >= 0);
fmalitabc590c02016-02-22 09:12:33 -0800270 SkASSERT(firstInterval <= lastInterval);
fmalitaafac5812016-11-01 13:41:34 -0700271
272 if (tileMode != kClamp_TileMode && !is_vertical) {
Florin Malitacf20f782017-04-07 14:56:14 -0400273 const auto spanX = (lastInterval->fT1 - firstInterval->fT0) / dx;
fmalitaafac5812016-11-01 13:41:34 -0700274 SkASSERT(spanX >= 0);
275
276 // If we're in a repeating tile mode and the whole gradient is compressed into a
277 // fraction of a pixel, we just use the average color in zero-ramp mode.
278 // This also avoids cases where we make no progress due to interval advances being
279 // close to zero.
280 static constexpr SkScalar kMinSpanX = .25f;
281 if (spanX < kMinSpanX) {
282 this->init_average_props();
283 return;
284 }
285 }
286
Florin Malitacf20f782017-04-07 14:56:14 -0400287 this->compute_interval_props(fx);
fmalitabc590c02016-02-22 09:12:33 -0800288 }
289
290 SkScalar currentAdvance() const {
291 SkASSERT(fAdvX >= 0);
Florin Malitacf20f782017-04-07 14:56:14 -0400292 SkASSERT(fAdvX <= (fInterval->fT1 - fInterval->fT0) / fDx || !std::isfinite(fAdvX));
fmalitabc590c02016-02-22 09:12:33 -0800293 return fAdvX;
294 }
295
296 bool currentRampIsZero() const { return fZeroRamp; }
297 const Sk4f& currentColor() const { return fCc; }
298 const Sk4f& currentColorGrad() const { return fDcDx; }
299
300 void advance(SkScalar advX) {
301 SkASSERT(advX > 0);
302 SkASSERT(fAdvX >= 0);
303
304 if (advX >= fAdvX) {
305 advX = this->advance_interval(advX);
306 }
307 SkASSERT(advX < fAdvX);
308
309 fCc = fCc + fDcDx * Sk4f(advX);
310 fAdvX -= advX;
311 }
312
313private:
314 void compute_interval_props(SkScalar t) {
Florin Malitacf20f782017-04-07 14:56:14 -0400315 SkASSERT(in_range(t, fInterval->fT0, fInterval->fT1));
316
Florin Malita63f717d2017-05-08 09:53:11 -0400317 const Sk4f dc = DstTraits<dstType, premul>::load(fInterval->fCg);
318 fCc = DstTraits<dstType, premul>::load(fInterval->fCb) + dc * Sk4f(t);
319 fDcDx = dc * fDx;
320 fZeroRamp = fIsVertical || (dc == 0).allTrue();
fmalitabc590c02016-02-22 09:12:33 -0800321 }
322
fmalitaafac5812016-11-01 13:41:34 -0700323 void init_average_props() {
324 fAdvX = SK_ScalarInfinity;
325 fZeroRamp = true;
326 fDcDx = 0;
327 fCc = Sk4f(0);
328
329 // TODO: precompute the average at interval setup time?
330 for (const auto* i = fFirstInterval; i <= fLastInterval; ++i) {
331 // Each interval contributes its average color to the total/weighted average:
332 //
Florin Malitacf20f782017-04-07 14:56:14 -0400333 // C = (c0 + c1) / 2 = (Cb + Cg * t0 + Cb + Cg * t1) / 2 = Cb + Cg *(t0 + t1) / 2
fmalitaafac5812016-11-01 13:41:34 -0700334 //
Florin Malitacf20f782017-04-07 14:56:14 -0400335 // Avg += C * (t1 - t0)
fmalitaafac5812016-11-01 13:41:34 -0700336 //
Florin Malita63f717d2017-05-08 09:53:11 -0400337 const auto c = DstTraits<dstType, premul>::load(i->fCb)
338 + DstTraits<dstType, premul>::load(i->fCg) * (i->fT0 + i->fT1) * 0.5f;
Florin Malitacf20f782017-04-07 14:56:14 -0400339 fCc = fCc + c * (i->fT1 - i->fT0);
fmalitaafac5812016-11-01 13:41:34 -0700340 }
341 }
342
Florin Malitada4545b2017-03-23 17:04:54 -0400343 const Sk4fGradientInterval* next_interval(const Sk4fGradientInterval* i) const {
fmalitabc590c02016-02-22 09:12:33 -0800344 SkASSERT(i >= fFirstInterval);
345 SkASSERT(i <= fLastInterval);
346 i++;
347
348 if (tileMode == kClamp_TileMode) {
349 SkASSERT(i <= fLastInterval);
350 return i;
351 }
352
353 return (i <= fLastInterval) ? i : fFirstInterval;
354 }
355
356 SkScalar advance_interval(SkScalar advX) {
357 SkASSERT(advX >= fAdvX);
358
359 do {
360 advX -= fAdvX;
361 fInterval = this->next_interval(fInterval);
Florin Malitacf20f782017-04-07 14:56:14 -0400362 fAdvX = (fInterval->fT1 - fInterval->fT0) / fDx;
fmalitabc590c02016-02-22 09:12:33 -0800363 SkASSERT(fAdvX > 0);
364 } while (advX >= fAdvX);
365
Florin Malitacf20f782017-04-07 14:56:14 -0400366 compute_interval_props(fInterval->fT0);
fmalitabc590c02016-02-22 09:12:33 -0800367
368 SkASSERT(advX >= 0);
369 return advX;
370 }
371
fmalitabc590c02016-02-22 09:12:33 -0800372 // Current interval properties.
fmalitabc590c02016-02-22 09:12:33 -0800373 Sk4f fDcDx; // dst color gradient (dc/dx)
374 Sk4f fCc; // current color, interpolated in dst
375 SkScalar fAdvX; // remaining interval advance in dst
376 bool fZeroRamp; // current interval color grad is 0
377
Florin Malitada4545b2017-03-23 17:04:54 -0400378 const Sk4fGradientInterval* fFirstInterval;
379 const Sk4fGradientInterval* fLastInterval;
380 const Sk4fGradientInterval* fInterval; // current interval
381 const SkScalar fDx; // 'dx' for consistency with other impls; actually dt/dx
382 const bool fIsVertical;
fmalitabc590c02016-02-22 09:12:33 -0800383};
fmalita7e6fcf82016-03-10 11:18:43 -0800384
385void SkLinearGradient::
386LinearGradient4fContext::mapTs(int x, int y, SkScalar ts[], int count) const {
387 SkASSERT(count > 0);
388 SkASSERT(fDstToPosClass != kLinear_MatrixClass);
389
390 SkScalar sx = x + SK_ScalarHalf;
391 const SkScalar sy = y + SK_ScalarHalf;
392 SkPoint pt;
393
394 if (fDstToPosClass != kPerspective_MatrixClass) {
395 // kLinear_MatrixClass, kFixedStepInX_MatrixClass => fixed dt per scanline
396 const SkScalar dtdx = fDstToPos.fixedStepInX(sy).x();
397 fDstToPosProc(fDstToPos, sx, sy, &pt);
398
399 const Sk4f dtdx4 = Sk4f(4 * dtdx);
400 Sk4f t4 = Sk4f(pt.x() + 0 * dtdx,
401 pt.x() + 1 * dtdx,
402 pt.x() + 2 * dtdx,
403 pt.x() + 3 * dtdx);
404
405 while (count >= 4) {
406 t4.store(ts);
407 t4 = t4 + dtdx4;
408 ts += 4;
409 count -= 4;
410 }
411
412 if (count & 2) {
413 *ts++ = t4[0];
414 *ts++ = t4[1];
415 t4 = SkNx_shuffle<2, 0, 1, 3>(t4);
416 }
417
418 if (count & 1) {
419 *ts++ = t4[0];
420 }
421 } else {
422 for (int i = 0; i < count; ++i) {
423 fDstToPosProc(fDstToPos, sx, sy, &pt);
Florin Malita52bab302017-02-08 17:03:56 -0500424 // Perspective may yield NaN values.
425 // Short of a better idea, drop to 0.
426 ts[i] = SkScalarIsNaN(pt.x()) ? 0 : pt.x();
fmalita7e6fcf82016-03-10 11:18:43 -0800427 sx += SK_Scalar1;
428 }
429 }
430}