blob: 29dfab69e18c5a5d72130f29bbd7eb264eb66cfc [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"
Mike Reed75ae4212018-01-23 11:24:08 -05009#include "SkPaint.h"
fmalitabc590c02016-02-22 09:12:33 -080010
Ben Wagner8a1036c2016-11-09 15:00:49 -050011#include <cmath>
Ben Wagnerf08d1d02018-06-18 15:11:00 -040012#include <utility>
Ben Wagner8a1036c2016-11-09 15:00:49 -050013
fmalitabc590c02016-02-22 09:12:33 -080014namespace {
15
Mike Klein541cbd42019-01-30 13:11:33 -050016template<ApplyPremul premul>
17void ramp(const Sk4f& c, const Sk4f& dc, SkPMColor dst[], int n,
Florin Malitaaa0ce822017-08-28 12:50:26 -040018 const Sk4f& bias0, const Sk4f& bias1) {
fmalitabc590c02016-02-22 09:12:33 -080019 SkASSERT(n > 0);
20
Florin Malitaaa0ce822017-08-28 12:50:26 -040021 const Sk4f dc2 = dc + dc,
22 dc4 = dc2 + dc2;
fmalitabc590c02016-02-22 09:12:33 -080023
Mike Klein541cbd42019-01-30 13:11:33 -050024 Sk4f c0 = c + DstTraits<premul>::pre_lerp_bias(bias0),
25 c1 = c + dc + DstTraits<premul>::pre_lerp_bias(bias1),
Florin Malitaaa0ce822017-08-28 12:50:26 -040026 c2 = c0 + dc2,
27 c3 = c1 + dc2;
fmalitabc590c02016-02-22 09:12:33 -080028
29 while (n >= 4) {
Mike Klein541cbd42019-01-30 13:11:33 -050030 DstTraits<premul>::store4x(c0, c1, c2, c3, dst, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -080031 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) {
Mike Klein541cbd42019-01-30 13:11:33 -050040 DstTraits<premul>::store(c0, dst++, bias0);
41 DstTraits<premul>::store(c1, dst++, bias1);
fmalitabc590c02016-02-22 09:12:33 -080042 c0 = c0 + dc2;
43 }
44 if (n & 1) {
Mike Klein541cbd42019-01-30 13:11:33 -050045 DstTraits<premul>::store(c0, dst, bias0);
fmalitabc590c02016-02-22 09:12:33 -080046 }
47}
48
49template<SkShader::TileMode>
50SkScalar pinFx(SkScalar);
51
52template<>
53SkScalar pinFx<SkShader::kClamp_TileMode>(SkScalar fx) {
54 return fx;
55}
56
57template<>
58SkScalar pinFx<SkShader::kRepeat_TileMode>(SkScalar fx) {
Florin Malita40481bb2018-03-13 11:17:52 -040059 SkScalar f = SkScalarIsFinite(fx) ? SkScalarFraction(fx) : 0;
Florin Malita0fdde542016-11-14 15:54:04 -050060 if (f < 0) {
61 f = SkTMin(f + 1, nextafterf(1, 0));
62 }
63 SkASSERT(f >= 0);
64 SkASSERT(f < 1.0f);
65 return f;
fmalitabc590c02016-02-22 09:12:33 -080066}
67
68template<>
69SkScalar pinFx<SkShader::kMirror_TileMode>(SkScalar fx) {
Florin Malita40481bb2018-03-13 11:17:52 -040070 SkScalar f = SkScalarIsFinite(fx) ? SkScalarMod(fx, 2.0f) : 0;
Florin Malita0fdde542016-11-14 15:54:04 -050071 if (f < 0) {
72 f = SkTMin(f + 2, nextafterf(2, 0));
73 }
74 SkASSERT(f >= 0);
75 SkASSERT(f < 2.0f);
76 return f;
fmalitabc590c02016-02-22 09:12:33 -080077}
78
Florin Malitae659c7f2017-02-09 13:46:55 -050079// true when x is in [k1,k2], or [k2, k1] when the interval is reversed.
fmalita6d7e4e82016-09-20 06:55:16 -070080// TODO(fmalita): hoist the reversed interval check out of this helper.
fmalita7520fc42016-03-04 11:01:24 -080081bool in_range(SkScalar x, SkScalar k1, SkScalar k2) {
82 SkASSERT(k1 != k2);
83 return (k1 < k2)
Florin Malitae659c7f2017-02-09 13:46:55 -050084 ? (x >= k1 && x <= k2)
85 : (x >= k2 && x <= k1);
fmalita7520fc42016-03-04 11:01:24 -080086}
87
fmalitabc590c02016-02-22 09:12:33 -080088} // anonymous namespace
89
90SkLinearGradient::
91LinearGradient4fContext::LinearGradient4fContext(const SkLinearGradient& shader,
92 const ContextRec& rec)
fmalita7520fc42016-03-04 11:01:24 -080093 : INHERITED(shader, rec) {
fmalita7520fc42016-03-04 11:01:24 -080094
fmalita7e6fcf82016-03-10 11:18:43 -080095 // Our fast path expects interval points to be monotonically increasing in x.
Florin Malita4d41b8f2017-07-12 14:35:46 -040096 const bool reverseIntervals = std::signbit(fDstToPos.getScaleX());
Florin Malita0e36b3f2017-06-05 23:33:45 -040097 fIntervals.init(shader, rec.fDstColorSpace, shader.fTileMode,
Florin Malitada4545b2017-03-23 17:04:54 -040098 fColorsArePremul, rec.fPaint->getAlpha() * (1.0f / 255), reverseIntervals);
fmalita7520fc42016-03-04 11:01:24 -080099
Florin Malitada4545b2017-03-23 17:04:54 -0400100 SkASSERT(fIntervals->count() > 0);
101 fCachedInterval = fIntervals->begin();
fmalita7520fc42016-03-04 11:01:24 -0800102}
103
Florin Malitada4545b2017-03-23 17:04:54 -0400104const Sk4fGradientInterval*
fmalita7520fc42016-03-04 11:01:24 -0800105SkLinearGradient::LinearGradient4fContext::findInterval(SkScalar fx) const {
Florin Malitacf20f782017-04-07 14:56:14 -0400106 SkASSERT(in_range(fx, fIntervals->front().fT0, fIntervals->back().fT1));
fmalita7520fc42016-03-04 11:01:24 -0800107
108 if (1) {
109 // Linear search, using the last scanline interval as a starting point.
Florin Malitada4545b2017-03-23 17:04:54 -0400110 SkASSERT(fCachedInterval >= fIntervals->begin());
111 SkASSERT(fCachedInterval < fIntervals->end());
fmalita7520fc42016-03-04 11:01:24 -0800112 const int search_dir = fDstToPos.getScaleX() >= 0 ? 1 : -1;
Florin Malitacf20f782017-04-07 14:56:14 -0400113 while (!in_range(fx, fCachedInterval->fT0, fCachedInterval->fT1)) {
fmalita7520fc42016-03-04 11:01:24 -0800114 fCachedInterval += search_dir;
Florin Malitada4545b2017-03-23 17:04:54 -0400115 if (fCachedInterval >= fIntervals->end()) {
116 fCachedInterval = fIntervals->begin();
117 } else if (fCachedInterval < fIntervals->begin()) {
118 fCachedInterval = fIntervals->end() - 1;
fmalita7520fc42016-03-04 11:01:24 -0800119 }
120 }
121 return fCachedInterval;
122 } else {
123 // Binary search. Seems less effective than linear + caching.
Florin Malitada4545b2017-03-23 17:04:54 -0400124 const auto* i0 = fIntervals->begin();
125 const auto* i1 = fIntervals->end() - 1;
fmalita7520fc42016-03-04 11:01:24 -0800126
127 while (i0 != i1) {
128 SkASSERT(i0 < i1);
Florin Malitacf20f782017-04-07 14:56:14 -0400129 SkASSERT(in_range(fx, i0->fT0, i1->fT1));
fmalita7520fc42016-03-04 11:01:24 -0800130
Florin Malitada4545b2017-03-23 17:04:54 -0400131 const auto* i = i0 + ((i1 - i0) >> 1);
fmalita7520fc42016-03-04 11:01:24 -0800132
Florin Malitacf20f782017-04-07 14:56:14 -0400133 if (in_range(fx, i0->fT0, i->fT1)) {
fmalita7520fc42016-03-04 11:01:24 -0800134 i1 = i;
135 } else {
Florin Malitacf20f782017-04-07 14:56:14 -0400136 SkASSERT(in_range(fx, i->fT1, i1->fT1));
fmalita7520fc42016-03-04 11:01:24 -0800137 i0 = i + 1;
138 }
139 }
140
Florin Malitacf20f782017-04-07 14:56:14 -0400141 SkASSERT(in_range(fx, i0->fT0, i0->fT1));
fmalita7520fc42016-03-04 11:01:24 -0800142 return i0;
143 }
144}
fmalitabc590c02016-02-22 09:12:33 -0800145
Florin Malitaaa0ce822017-08-28 12:50:26 -0400146
147void SkLinearGradient::
148LinearGradient4fContext::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) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400165 using std::swap;
166 swap(bias0, bias1);
Florin Malitaaa0ce822017-08-28 12:50:26 -0400167 }
168 }
169
170 if (fColorsArePremul) {
171 // In premul interpolation mode, components are pre-scaled by 255 and the store
172 // op is truncating. We pre-bias here to achieve rounding.
173 bias0 += 0.5f;
174 bias1 += 0.5f;
175
Mike Klein541cbd42019-01-30 13:11:33 -0500176 this->shadePremulSpan<ApplyPremul::False>(x, y, dst, count, bias0, bias1);
Florin Malitaaa0ce822017-08-28 12:50:26 -0400177 } else {
178 // In unpremul interpolation mode, Components are not pre-scaled.
179 bias0 *= 1/255.0f;
180 bias1 *= 1/255.0f;
181
Mike Klein541cbd42019-01-30 13:11:33 -0500182 this->shadePremulSpan<ApplyPremul::True >(x, y, dst, count, bias0, bias1);
Florin Malitaaa0ce822017-08-28 12:50:26 -0400183 }
184}
185
Mike Klein541cbd42019-01-30 13:11:33 -0500186template<ApplyPremul premul>
fmalitabc590c02016-02-22 09:12:33 -0800187void SkLinearGradient::
Mike Klein541cbd42019-01-30 13:11:33 -0500188LinearGradient4fContext::shadePremulSpan(int x, int y, SkPMColor dst[], int count,
Florin Malitaaa0ce822017-08-28 12:50:26 -0400189 float bias0, float bias1) const {
190 const SkLinearGradient& shader = static_cast<const SkLinearGradient&>(fShader);
fmalitabc590c02016-02-22 09:12:33 -0800191 switch (shader.fTileMode) {
Mike Reeddfc0e912018-02-16 12:40:18 -0500192 case kDecal_TileMode:
193 SkASSERT(false); // decal only supported via stages
194 // fall-through
fmalitabc590c02016-02-22 09:12:33 -0800195 case kClamp_TileMode:
Mike Klein541cbd42019-01-30 13:11:33 -0500196 this->shadeSpanInternal<premul, kClamp_TileMode >(x, y, dst, count, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -0800197 break;
198 case kRepeat_TileMode:
Mike Klein541cbd42019-01-30 13:11:33 -0500199 this->shadeSpanInternal<premul, kRepeat_TileMode>(x, y, dst, count, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -0800200 break;
201 case kMirror_TileMode:
Mike Klein541cbd42019-01-30 13:11:33 -0500202 this->shadeSpanInternal<premul, kMirror_TileMode>(x, y, dst, count, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -0800203 break;
204 }
205}
206
Mike Klein541cbd42019-01-30 13:11:33 -0500207template<ApplyPremul premul, SkShader::TileMode tileMode>
fmalitabc590c02016-02-22 09:12:33 -0800208void SkLinearGradient::
Mike Klein541cbd42019-01-30 13:11:33 -0500209LinearGradient4fContext::shadeSpanInternal(int x, int y, SkPMColor dst[], int count,
Florin Malitaaa0ce822017-08-28 12:50:26 -0400210 float bias0, float bias1) const {
fmalitabc590c02016-02-22 09:12:33 -0800211 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();
Mike Klein541cbd42019-01-30 13:11:33 -0500218 LinearIntervalProcessor<premul, tileMode> proc(fIntervals->begin(),
219 fIntervals->end() - 1,
220 this->findInterval(fx),
221 fx,
222 dx,
223 SkScalarNearlyZero(dx * count));
Florin Malitaaa0ce822017-08-28 12:50:26 -0400224 Sk4f bias4f0(bias0),
225 bias4f1(bias1);
226
fmalitabc590c02016-02-22 09:12:33 -0800227 while (count > 0) {
228 // What we really want here is SkTPin(advance, 1, count)
229 // but that's a significant perf hit for >> stops; investigate.
Florin Malita909e61c2018-11-08 12:00:20 -0500230 const int n = SkTMin(SkScalarTruncToInt(proc.currentAdvance() + 1), count);
fmalitabc590c02016-02-22 09:12:33 -0800231
232 // The current interval advance can be +inf (e.g. when reaching
233 // the clamp mode end intervals) - when that happens, we expect to
234 // a) consume all remaining count in one swoop
235 // b) return a zero color gradient
236 SkASSERT(SkScalarIsFinite(proc.currentAdvance())
237 || (n == count && proc.currentRampIsZero()));
238
239 if (proc.currentRampIsZero()) {
Mike Klein541cbd42019-01-30 13:11:33 -0500240 DstTraits<premul>::store(proc.currentColor(), dst, n);
fmalitabc590c02016-02-22 09:12:33 -0800241 } else {
Mike Klein541cbd42019-01-30 13:11:33 -0500242 ramp<premul>(proc.currentColor(), proc.currentColorGrad(), dst, n,
243 bias4f0, bias4f1);
fmalitabc590c02016-02-22 09:12:33 -0800244 }
245
246 proc.advance(SkIntToScalar(n));
247 count -= n;
248 dst += n;
Florin Malitaaa0ce822017-08-28 12:50:26 -0400249
250 if (n & 1) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400251 using std::swap;
252 swap(bias4f0, bias4f1);
Florin Malitaaa0ce822017-08-28 12:50:26 -0400253 }
fmalitabc590c02016-02-22 09:12:33 -0800254 }
255}
256
Mike Klein541cbd42019-01-30 13:11:33 -0500257template<ApplyPremul premul, SkShader::TileMode tileMode>
fmalitabc590c02016-02-22 09:12:33 -0800258class SkLinearGradient::
259LinearGradient4fContext::LinearIntervalProcessor {
260public:
Florin Malitada4545b2017-03-23 17:04:54 -0400261 LinearIntervalProcessor(const Sk4fGradientInterval* firstInterval,
262 const Sk4fGradientInterval* lastInterval,
263 const Sk4fGradientInterval* i,
fmalitabc590c02016-02-22 09:12:33 -0800264 SkScalar fx,
265 SkScalar dx,
266 bool is_vertical)
Florin Malitacf20f782017-04-07 14:56:14 -0400267 : fAdvX(is_vertical ? SK_ScalarInfinity : (i->fT1 - fx) / dx)
fmalitabc590c02016-02-22 09:12:33 -0800268 , fFirstInterval(firstInterval)
269 , fLastInterval(lastInterval)
270 , fInterval(i)
271 , fDx(dx)
272 , fIsVertical(is_vertical)
273 {
fmalita6d7e4e82016-09-20 06:55:16 -0700274 SkASSERT(fAdvX >= 0);
fmalitabc590c02016-02-22 09:12:33 -0800275 SkASSERT(firstInterval <= lastInterval);
fmalitaafac5812016-11-01 13:41:34 -0700276
277 if (tileMode != kClamp_TileMode && !is_vertical) {
Florin Malitacf20f782017-04-07 14:56:14 -0400278 const auto spanX = (lastInterval->fT1 - firstInterval->fT0) / dx;
fmalitaafac5812016-11-01 13:41:34 -0700279 SkASSERT(spanX >= 0);
280
281 // If we're in a repeating tile mode and the whole gradient is compressed into a
282 // fraction of a pixel, we just use the average color in zero-ramp mode.
283 // This also avoids cases where we make no progress due to interval advances being
284 // close to zero.
285 static constexpr SkScalar kMinSpanX = .25f;
286 if (spanX < kMinSpanX) {
287 this->init_average_props();
288 return;
289 }
290 }
291
Florin Malitacf20f782017-04-07 14:56:14 -0400292 this->compute_interval_props(fx);
fmalitabc590c02016-02-22 09:12:33 -0800293 }
294
295 SkScalar currentAdvance() const {
296 SkASSERT(fAdvX >= 0);
Mike Kleina4c277b2018-11-06 14:24:55 -0500297 SkASSERT(!std::isfinite(fAdvX) || fAdvX <= (fInterval->fT1 - fInterval->fT0) / fDx);
fmalitabc590c02016-02-22 09:12:33 -0800298 return fAdvX;
299 }
300
301 bool currentRampIsZero() const { return fZeroRamp; }
302 const Sk4f& currentColor() const { return fCc; }
303 const Sk4f& currentColorGrad() const { return fDcDx; }
304
305 void advance(SkScalar advX) {
306 SkASSERT(advX > 0);
307 SkASSERT(fAdvX >= 0);
308
309 if (advX >= fAdvX) {
310 advX = this->advance_interval(advX);
311 }
312 SkASSERT(advX < fAdvX);
313
314 fCc = fCc + fDcDx * Sk4f(advX);
315 fAdvX -= advX;
316 }
317
318private:
319 void compute_interval_props(SkScalar t) {
Florin Malitacf20f782017-04-07 14:56:14 -0400320 SkASSERT(in_range(t, fInterval->fT0, fInterval->fT1));
321
Mike Klein541cbd42019-01-30 13:11:33 -0500322 const Sk4f dc = DstTraits<premul>::load(fInterval->fCg);
323 fCc = DstTraits<premul>::load(fInterval->fCb) + dc * Sk4f(t);
Florin Malita63f717d2017-05-08 09:53:11 -0400324 fDcDx = dc * fDx;
325 fZeroRamp = fIsVertical || (dc == 0).allTrue();
fmalitabc590c02016-02-22 09:12:33 -0800326 }
327
fmalitaafac5812016-11-01 13:41:34 -0700328 void init_average_props() {
329 fAdvX = SK_ScalarInfinity;
330 fZeroRamp = true;
331 fDcDx = 0;
332 fCc = Sk4f(0);
333
334 // TODO: precompute the average at interval setup time?
335 for (const auto* i = fFirstInterval; i <= fLastInterval; ++i) {
336 // Each interval contributes its average color to the total/weighted average:
337 //
Florin Malitacf20f782017-04-07 14:56:14 -0400338 // C = (c0 + c1) / 2 = (Cb + Cg * t0 + Cb + Cg * t1) / 2 = Cb + Cg *(t0 + t1) / 2
fmalitaafac5812016-11-01 13:41:34 -0700339 //
Florin Malitacf20f782017-04-07 14:56:14 -0400340 // Avg += C * (t1 - t0)
fmalitaafac5812016-11-01 13:41:34 -0700341 //
Mike Klein541cbd42019-01-30 13:11:33 -0500342 const auto c = DstTraits<premul>::load(i->fCb)
343 + DstTraits<premul>::load(i->fCg) * (i->fT0 + i->fT1) * 0.5f;
Florin Malitacf20f782017-04-07 14:56:14 -0400344 fCc = fCc + c * (i->fT1 - i->fT0);
fmalitaafac5812016-11-01 13:41:34 -0700345 }
346 }
347
Florin Malitada4545b2017-03-23 17:04:54 -0400348 const Sk4fGradientInterval* next_interval(const Sk4fGradientInterval* i) const {
fmalitabc590c02016-02-22 09:12:33 -0800349 SkASSERT(i >= fFirstInterval);
350 SkASSERT(i <= fLastInterval);
351 i++;
352
353 if (tileMode == kClamp_TileMode) {
354 SkASSERT(i <= fLastInterval);
355 return i;
356 }
357
358 return (i <= fLastInterval) ? i : fFirstInterval;
359 }
360
361 SkScalar advance_interval(SkScalar advX) {
362 SkASSERT(advX >= fAdvX);
363
364 do {
365 advX -= fAdvX;
366 fInterval = this->next_interval(fInterval);
Florin Malitacf20f782017-04-07 14:56:14 -0400367 fAdvX = (fInterval->fT1 - fInterval->fT0) / fDx;
fmalitabc590c02016-02-22 09:12:33 -0800368 SkASSERT(fAdvX > 0);
369 } while (advX >= fAdvX);
370
Florin Malitacf20f782017-04-07 14:56:14 -0400371 compute_interval_props(fInterval->fT0);
fmalitabc590c02016-02-22 09:12:33 -0800372
373 SkASSERT(advX >= 0);
374 return advX;
375 }
376
fmalitabc590c02016-02-22 09:12:33 -0800377 // Current interval properties.
fmalitabc590c02016-02-22 09:12:33 -0800378 Sk4f fDcDx; // dst color gradient (dc/dx)
379 Sk4f fCc; // current color, interpolated in dst
380 SkScalar fAdvX; // remaining interval advance in dst
381 bool fZeroRamp; // current interval color grad is 0
382
Florin Malitada4545b2017-03-23 17:04:54 -0400383 const Sk4fGradientInterval* fFirstInterval;
384 const Sk4fGradientInterval* fLastInterval;
385 const Sk4fGradientInterval* fInterval; // current interval
386 const SkScalar fDx; // 'dx' for consistency with other impls; actually dt/dx
387 const bool fIsVertical;
fmalitabc590c02016-02-22 09:12:33 -0800388};