blob: 1f3ac7f74da24121a6b4a1b82d71749eec090666 [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"
Mike Reed75ae4212018-01-23 11:24:08 -050010#include "SkPaint.h"
fmalitabc590c02016-02-22 09:12:33 -080011
Ben Wagner8a1036c2016-11-09 15:00:49 -050012#include <cmath>
Ben Wagnerf08d1d02018-06-18 15:11:00 -040013#include <utility>
Ben Wagner8a1036c2016-11-09 15:00:49 -050014
fmalitabc590c02016-02-22 09:12:33 -080015namespace {
16
Florin Malitaaa0ce822017-08-28 12:50:26 -040017template<typename dstType, ApplyPremul premul>
18void ramp(const Sk4f& c, const Sk4f& dc, dstType dst[], int n,
19 const Sk4f& bias0, const Sk4f& bias1) {
fmalitabc590c02016-02-22 09:12:33 -080020 SkASSERT(n > 0);
21
Florin Malitaaa0ce822017-08-28 12:50:26 -040022 const Sk4f dc2 = dc + dc,
23 dc4 = dc2 + dc2;
fmalitabc590c02016-02-22 09:12:33 -080024
Florin Malitaaa0ce822017-08-28 12:50:26 -040025 Sk4f c0 = c + DstTraits<dstType, premul>::pre_lerp_bias(bias0),
26 c1 = c + dc + DstTraits<dstType, premul>::pre_lerp_bias(bias1),
27 c2 = c0 + dc2,
28 c3 = c1 + dc2;
fmalitabc590c02016-02-22 09:12:33 -080029
30 while (n >= 4) {
Florin Malitaaa0ce822017-08-28 12:50:26 -040031 DstTraits<dstType, premul>::store4x(c0, c1, c2, c3, dst, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -080032 dst += 4;
33
34 c0 = c0 + dc4;
35 c1 = c1 + dc4;
36 c2 = c2 + dc4;
37 c3 = c3 + dc4;
38 n -= 4;
39 }
40 if (n & 2) {
Florin Malitaaa0ce822017-08-28 12:50:26 -040041 DstTraits<dstType, premul>::store(c0, dst++, bias0);
42 DstTraits<dstType, premul>::store(c1, dst++, bias1);
fmalitabc590c02016-02-22 09:12:33 -080043 c0 = c0 + dc2;
44 }
45 if (n & 1) {
Florin Malitaaa0ce822017-08-28 12:50:26 -040046 DstTraits<dstType, premul>::store(c0, dst, bias0);
fmalitabc590c02016-02-22 09:12:33 -080047 }
48}
49
50template<SkShader::TileMode>
51SkScalar pinFx(SkScalar);
52
53template<>
54SkScalar pinFx<SkShader::kClamp_TileMode>(SkScalar fx) {
55 return fx;
56}
57
58template<>
59SkScalar pinFx<SkShader::kRepeat_TileMode>(SkScalar fx) {
Florin Malita40481bb2018-03-13 11:17:52 -040060 SkScalar f = SkScalarIsFinite(fx) ? SkScalarFraction(fx) : 0;
Florin Malita0fdde542016-11-14 15:54:04 -050061 if (f < 0) {
62 f = SkTMin(f + 1, nextafterf(1, 0));
63 }
64 SkASSERT(f >= 0);
65 SkASSERT(f < 1.0f);
66 return f;
fmalitabc590c02016-02-22 09:12:33 -080067}
68
69template<>
70SkScalar pinFx<SkShader::kMirror_TileMode>(SkScalar fx) {
Florin Malita40481bb2018-03-13 11:17:52 -040071 SkScalar f = SkScalarIsFinite(fx) ? SkScalarMod(fx, 2.0f) : 0;
Florin Malita0fdde542016-11-14 15:54:04 -050072 if (f < 0) {
73 f = SkTMin(f + 2, nextafterf(2, 0));
74 }
75 SkASSERT(f >= 0);
76 SkASSERT(f < 2.0f);
77 return f;
fmalitabc590c02016-02-22 09:12:33 -080078}
79
Florin Malitae659c7f2017-02-09 13:46:55 -050080// true when x is in [k1,k2], or [k2, k1] when the interval is reversed.
fmalita6d7e4e82016-09-20 06:55:16 -070081// TODO(fmalita): hoist the reversed interval check out of this helper.
fmalita7520fc42016-03-04 11:01:24 -080082bool in_range(SkScalar x, SkScalar k1, SkScalar k2) {
83 SkASSERT(k1 != k2);
84 return (k1 < k2)
Florin Malitae659c7f2017-02-09 13:46:55 -050085 ? (x >= k1 && x <= k2)
86 : (x >= k2 && x <= k1);
fmalita7520fc42016-03-04 11:01:24 -080087}
88
fmalitabc590c02016-02-22 09:12:33 -080089} // anonymous namespace
90
91SkLinearGradient::
92LinearGradient4fContext::LinearGradient4fContext(const SkLinearGradient& shader,
93 const ContextRec& rec)
fmalita7520fc42016-03-04 11:01:24 -080094 : INHERITED(shader, rec) {
fmalita7520fc42016-03-04 11:01:24 -080095
fmalita7e6fcf82016-03-10 11:18:43 -080096 // Our fast path expects interval points to be monotonically increasing in x.
Florin Malita4d41b8f2017-07-12 14:35:46 -040097 const bool reverseIntervals = std::signbit(fDstToPos.getScaleX());
Florin Malita0e36b3f2017-06-05 23:33:45 -040098 fIntervals.init(shader, rec.fDstColorSpace, shader.fTileMode,
Florin Malitada4545b2017-03-23 17:04:54 -040099 fColorsArePremul, rec.fPaint->getAlpha() * (1.0f / 255), reverseIntervals);
fmalita7520fc42016-03-04 11:01:24 -0800100
Florin Malitada4545b2017-03-23 17:04:54 -0400101 SkASSERT(fIntervals->count() > 0);
102 fCachedInterval = fIntervals->begin();
fmalita7520fc42016-03-04 11:01:24 -0800103}
104
Florin Malitada4545b2017-03-23 17:04:54 -0400105const Sk4fGradientInterval*
fmalita7520fc42016-03-04 11:01:24 -0800106SkLinearGradient::LinearGradient4fContext::findInterval(SkScalar fx) const {
Florin Malitacf20f782017-04-07 14:56:14 -0400107 SkASSERT(in_range(fx, fIntervals->front().fT0, fIntervals->back().fT1));
fmalita7520fc42016-03-04 11:01:24 -0800108
109 if (1) {
110 // Linear search, using the last scanline interval as a starting point.
Florin Malitada4545b2017-03-23 17:04:54 -0400111 SkASSERT(fCachedInterval >= fIntervals->begin());
112 SkASSERT(fCachedInterval < fIntervals->end());
fmalita7520fc42016-03-04 11:01:24 -0800113 const int search_dir = fDstToPos.getScaleX() >= 0 ? 1 : -1;
Florin Malitacf20f782017-04-07 14:56:14 -0400114 while (!in_range(fx, fCachedInterval->fT0, fCachedInterval->fT1)) {
fmalita7520fc42016-03-04 11:01:24 -0800115 fCachedInterval += search_dir;
Florin Malitada4545b2017-03-23 17:04:54 -0400116 if (fCachedInterval >= fIntervals->end()) {
117 fCachedInterval = fIntervals->begin();
118 } else if (fCachedInterval < fIntervals->begin()) {
119 fCachedInterval = fIntervals->end() - 1;
fmalita7520fc42016-03-04 11:01:24 -0800120 }
121 }
122 return fCachedInterval;
123 } else {
124 // Binary search. Seems less effective than linear + caching.
Florin Malitada4545b2017-03-23 17:04:54 -0400125 const auto* i0 = fIntervals->begin();
126 const auto* i1 = fIntervals->end() - 1;
fmalita7520fc42016-03-04 11:01:24 -0800127
128 while (i0 != i1) {
129 SkASSERT(i0 < i1);
Florin Malitacf20f782017-04-07 14:56:14 -0400130 SkASSERT(in_range(fx, i0->fT0, i1->fT1));
fmalita7520fc42016-03-04 11:01:24 -0800131
Florin Malitada4545b2017-03-23 17:04:54 -0400132 const auto* i = i0 + ((i1 - i0) >> 1);
fmalita7520fc42016-03-04 11:01:24 -0800133
Florin Malitacf20f782017-04-07 14:56:14 -0400134 if (in_range(fx, i0->fT0, i->fT1)) {
fmalita7520fc42016-03-04 11:01:24 -0800135 i1 = i;
136 } else {
Florin Malitacf20f782017-04-07 14:56:14 -0400137 SkASSERT(in_range(fx, i->fT1, i1->fT1));
fmalita7520fc42016-03-04 11:01:24 -0800138 i0 = i + 1;
139 }
140 }
141
Florin Malitacf20f782017-04-07 14:56:14 -0400142 SkASSERT(in_range(fx, i0->fT0, i0->fT1));
fmalita7520fc42016-03-04 11:01:24 -0800143 return i0;
144 }
145}
fmalitabc590c02016-02-22 09:12:33 -0800146
Florin Malitaaa0ce822017-08-28 12:50:26 -0400147
148void SkLinearGradient::
149LinearGradient4fContext::shadeSpan(int x, int y, SkPMColor dst[], int count) {
150 SkASSERT(count > 0);
151
152 float bias0 = 0,
153 bias1 = 0;
154
155 if (fDither) {
156 static constexpr float dither_cell[] = {
157 -3/8.0f, 1/8.0f,
158 3/8.0f, -1/8.0f,
159 };
160
161 const int rowIndex = (y & 1) << 1;
162 bias0 = dither_cell[rowIndex + 0];
163 bias1 = dither_cell[rowIndex + 1];
164
165 if (x & 1) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400166 using std::swap;
167 swap(bias0, bias1);
Florin Malitaaa0ce822017-08-28 12:50:26 -0400168 }
169 }
170
171 if (fColorsArePremul) {
172 // In premul interpolation mode, components are pre-scaled by 255 and the store
173 // op is truncating. We pre-bias here to achieve rounding.
174 bias0 += 0.5f;
175 bias1 += 0.5f;
176
177 this->shadePremulSpan<SkPMColor, ApplyPremul::False>(x, y, dst, count, bias0, bias1);
178 } else {
179 // In unpremul interpolation mode, Components are not pre-scaled.
180 bias0 *= 1/255.0f;
181 bias1 *= 1/255.0f;
182
183 this->shadePremulSpan<SkPMColor, ApplyPremul::True >(x, y, dst, count, bias0, bias1);
184 }
185}
186
fmalitabc590c02016-02-22 09:12:33 -0800187void SkLinearGradient::
Brian Osman781e3502018-10-03 15:42:47 -0400188LinearGradient4fContext::shadeSpan4f(int x, int y, SkPMColor4f dst[], int count) {
fmalitabc590c02016-02-22 09:12:33 -0800189 SkASSERT(count > 0);
Florin Malitaaa0ce822017-08-28 12:50:26 -0400190
191 // 4f dests are dithered at a later stage, if needed.
192 static constexpr float bias0 = 0,
193 bias1 = 0;
fmalitabc590c02016-02-22 09:12:33 -0800194 if (fColorsArePremul) {
Brian Osman781e3502018-10-03 15:42:47 -0400195 this->shadePremulSpan<SkPMColor4f, ApplyPremul::False>(x, y, dst, count, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -0800196 } else {
Brian Osman781e3502018-10-03 15:42:47 -0400197 this->shadePremulSpan<SkPMColor4f, ApplyPremul::True >(x, y, dst, count, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -0800198 }
199}
200
Florin Malitaaa0ce822017-08-28 12:50:26 -0400201template<typename dstType, ApplyPremul premul>
fmalitabc590c02016-02-22 09:12:33 -0800202void SkLinearGradient::
Florin Malitaaa0ce822017-08-28 12:50:26 -0400203LinearGradient4fContext::shadePremulSpan(int x, int y, dstType dst[], int count,
204 float bias0, float bias1) const {
205 const SkLinearGradient& shader = static_cast<const SkLinearGradient&>(fShader);
fmalitabc590c02016-02-22 09:12:33 -0800206 switch (shader.fTileMode) {
Mike Reeddfc0e912018-02-16 12:40:18 -0500207 case kDecal_TileMode:
208 SkASSERT(false); // decal only supported via stages
209 // fall-through
fmalitabc590c02016-02-22 09:12:33 -0800210 case kClamp_TileMode:
Florin Malitaaa0ce822017-08-28 12:50:26 -0400211 this->shadeSpanInternal<dstType, premul, kClamp_TileMode >(x, y, dst, count, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -0800212 break;
213 case kRepeat_TileMode:
Florin Malitaaa0ce822017-08-28 12:50:26 -0400214 this->shadeSpanInternal<dstType, premul, kRepeat_TileMode>(x, y, dst, count, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -0800215 break;
216 case kMirror_TileMode:
Florin Malitaaa0ce822017-08-28 12:50:26 -0400217 this->shadeSpanInternal<dstType, premul, kMirror_TileMode>(x, y, dst, count, bias0, bias1);
fmalitabc590c02016-02-22 09:12:33 -0800218 break;
219 }
220}
221
Florin Malitaaa0ce822017-08-28 12:50:26 -0400222template<typename dstType, ApplyPremul premul, SkShader::TileMode tileMode>
fmalitabc590c02016-02-22 09:12:33 -0800223void SkLinearGradient::
Florin Malitaaa0ce822017-08-28 12:50:26 -0400224LinearGradient4fContext::shadeSpanInternal(int x, int y, dstType dst[], int count,
225 float bias0, float bias1) const {
fmalitabc590c02016-02-22 09:12:33 -0800226 SkPoint pt;
227 fDstToPosProc(fDstToPos,
228 x + SK_ScalarHalf,
229 y + SK_ScalarHalf,
230 &pt);
231 const SkScalar fx = pinFx<tileMode>(pt.x());
232 const SkScalar dx = fDstToPos.getScaleX();
Florin Malitaaa0ce822017-08-28 12:50:26 -0400233 LinearIntervalProcessor<dstType, premul, tileMode> proc(fIntervals->begin(),
234 fIntervals->end() - 1,
235 this->findInterval(fx),
236 fx,
237 dx,
238 SkScalarNearlyZero(dx * count));
239 Sk4f bias4f0(bias0),
240 bias4f1(bias1);
241
fmalitabc590c02016-02-22 09:12:33 -0800242 while (count > 0) {
243 // What we really want here is SkTPin(advance, 1, count)
244 // but that's a significant perf hit for >> stops; investigate.
Florin Malita909e61c2018-11-08 12:00:20 -0500245 const int n = SkTMin(SkScalarTruncToInt(proc.currentAdvance() + 1), count);
fmalitabc590c02016-02-22 09:12:33 -0800246
247 // The current interval advance can be +inf (e.g. when reaching
248 // the clamp mode end intervals) - when that happens, we expect to
249 // a) consume all remaining count in one swoop
250 // b) return a zero color gradient
251 SkASSERT(SkScalarIsFinite(proc.currentAdvance())
252 || (n == count && proc.currentRampIsZero()));
253
254 if (proc.currentRampIsZero()) {
Florin Malitaaa0ce822017-08-28 12:50:26 -0400255 DstTraits<dstType, premul>::store(proc.currentColor(), dst, n);
fmalitabc590c02016-02-22 09:12:33 -0800256 } else {
Florin Malitaaa0ce822017-08-28 12:50:26 -0400257 ramp<dstType, premul>(proc.currentColor(), proc.currentColorGrad(), dst, n,
258 bias4f0, bias4f1);
fmalitabc590c02016-02-22 09:12:33 -0800259 }
260
261 proc.advance(SkIntToScalar(n));
262 count -= n;
263 dst += n;
Florin Malitaaa0ce822017-08-28 12:50:26 -0400264
265 if (n & 1) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400266 using std::swap;
267 swap(bias4f0, bias4f1);
Florin Malitaaa0ce822017-08-28 12:50:26 -0400268 }
fmalitabc590c02016-02-22 09:12:33 -0800269 }
270}
271
Florin Malitaaa0ce822017-08-28 12:50:26 -0400272template<typename dstType, ApplyPremul premul, SkShader::TileMode tileMode>
fmalitabc590c02016-02-22 09:12:33 -0800273class SkLinearGradient::
274LinearGradient4fContext::LinearIntervalProcessor {
275public:
Florin Malitada4545b2017-03-23 17:04:54 -0400276 LinearIntervalProcessor(const Sk4fGradientInterval* firstInterval,
277 const Sk4fGradientInterval* lastInterval,
278 const Sk4fGradientInterval* i,
fmalitabc590c02016-02-22 09:12:33 -0800279 SkScalar fx,
280 SkScalar dx,
281 bool is_vertical)
Florin Malitacf20f782017-04-07 14:56:14 -0400282 : fAdvX(is_vertical ? SK_ScalarInfinity : (i->fT1 - fx) / dx)
fmalitabc590c02016-02-22 09:12:33 -0800283 , fFirstInterval(firstInterval)
284 , fLastInterval(lastInterval)
285 , fInterval(i)
286 , fDx(dx)
287 , fIsVertical(is_vertical)
288 {
fmalita6d7e4e82016-09-20 06:55:16 -0700289 SkASSERT(fAdvX >= 0);
fmalitabc590c02016-02-22 09:12:33 -0800290 SkASSERT(firstInterval <= lastInterval);
fmalitaafac5812016-11-01 13:41:34 -0700291
292 if (tileMode != kClamp_TileMode && !is_vertical) {
Florin Malitacf20f782017-04-07 14:56:14 -0400293 const auto spanX = (lastInterval->fT1 - firstInterval->fT0) / dx;
fmalitaafac5812016-11-01 13:41:34 -0700294 SkASSERT(spanX >= 0);
295
296 // If we're in a repeating tile mode and the whole gradient is compressed into a
297 // fraction of a pixel, we just use the average color in zero-ramp mode.
298 // This also avoids cases where we make no progress due to interval advances being
299 // close to zero.
300 static constexpr SkScalar kMinSpanX = .25f;
301 if (spanX < kMinSpanX) {
302 this->init_average_props();
303 return;
304 }
305 }
306
Florin Malitacf20f782017-04-07 14:56:14 -0400307 this->compute_interval_props(fx);
fmalitabc590c02016-02-22 09:12:33 -0800308 }
309
310 SkScalar currentAdvance() const {
311 SkASSERT(fAdvX >= 0);
Mike Kleina4c277b2018-11-06 14:24:55 -0500312 SkASSERT(!std::isfinite(fAdvX) || fAdvX <= (fInterval->fT1 - fInterval->fT0) / fDx);
fmalitabc590c02016-02-22 09:12:33 -0800313 return fAdvX;
314 }
315
316 bool currentRampIsZero() const { return fZeroRamp; }
317 const Sk4f& currentColor() const { return fCc; }
318 const Sk4f& currentColorGrad() const { return fDcDx; }
319
320 void advance(SkScalar advX) {
321 SkASSERT(advX > 0);
322 SkASSERT(fAdvX >= 0);
323
324 if (advX >= fAdvX) {
325 advX = this->advance_interval(advX);
326 }
327 SkASSERT(advX < fAdvX);
328
329 fCc = fCc + fDcDx * Sk4f(advX);
330 fAdvX -= advX;
331 }
332
333private:
334 void compute_interval_props(SkScalar t) {
Florin Malitacf20f782017-04-07 14:56:14 -0400335 SkASSERT(in_range(t, fInterval->fT0, fInterval->fT1));
336
Florin Malitaaa0ce822017-08-28 12:50:26 -0400337 const Sk4f dc = DstTraits<dstType, premul>::load(fInterval->fCg);
338 fCc = DstTraits<dstType, premul>::load(fInterval->fCb) + dc * Sk4f(t);
Florin Malita63f717d2017-05-08 09:53:11 -0400339 fDcDx = dc * fDx;
340 fZeroRamp = fIsVertical || (dc == 0).allTrue();
fmalitabc590c02016-02-22 09:12:33 -0800341 }
342
fmalitaafac5812016-11-01 13:41:34 -0700343 void init_average_props() {
344 fAdvX = SK_ScalarInfinity;
345 fZeroRamp = true;
346 fDcDx = 0;
347 fCc = Sk4f(0);
348
349 // TODO: precompute the average at interval setup time?
350 for (const auto* i = fFirstInterval; i <= fLastInterval; ++i) {
351 // Each interval contributes its average color to the total/weighted average:
352 //
Florin Malitacf20f782017-04-07 14:56:14 -0400353 // C = (c0 + c1) / 2 = (Cb + Cg * t0 + Cb + Cg * t1) / 2 = Cb + Cg *(t0 + t1) / 2
fmalitaafac5812016-11-01 13:41:34 -0700354 //
Florin Malitacf20f782017-04-07 14:56:14 -0400355 // Avg += C * (t1 - t0)
fmalitaafac5812016-11-01 13:41:34 -0700356 //
Florin Malitaaa0ce822017-08-28 12:50:26 -0400357 const auto c = DstTraits<dstType, premul>::load(i->fCb)
358 + DstTraits<dstType, premul>::load(i->fCg) * (i->fT0 + i->fT1) * 0.5f;
Florin Malitacf20f782017-04-07 14:56:14 -0400359 fCc = fCc + c * (i->fT1 - i->fT0);
fmalitaafac5812016-11-01 13:41:34 -0700360 }
361 }
362
Florin Malitada4545b2017-03-23 17:04:54 -0400363 const Sk4fGradientInterval* next_interval(const Sk4fGradientInterval* i) const {
fmalitabc590c02016-02-22 09:12:33 -0800364 SkASSERT(i >= fFirstInterval);
365 SkASSERT(i <= fLastInterval);
366 i++;
367
368 if (tileMode == kClamp_TileMode) {
369 SkASSERT(i <= fLastInterval);
370 return i;
371 }
372
373 return (i <= fLastInterval) ? i : fFirstInterval;
374 }
375
376 SkScalar advance_interval(SkScalar advX) {
377 SkASSERT(advX >= fAdvX);
378
379 do {
380 advX -= fAdvX;
381 fInterval = this->next_interval(fInterval);
Florin Malitacf20f782017-04-07 14:56:14 -0400382 fAdvX = (fInterval->fT1 - fInterval->fT0) / fDx;
fmalitabc590c02016-02-22 09:12:33 -0800383 SkASSERT(fAdvX > 0);
384 } while (advX >= fAdvX);
385
Florin Malitacf20f782017-04-07 14:56:14 -0400386 compute_interval_props(fInterval->fT0);
fmalitabc590c02016-02-22 09:12:33 -0800387
388 SkASSERT(advX >= 0);
389 return advX;
390 }
391
fmalitabc590c02016-02-22 09:12:33 -0800392 // Current interval properties.
fmalitabc590c02016-02-22 09:12:33 -0800393 Sk4f fDcDx; // dst color gradient (dc/dx)
394 Sk4f fCc; // current color, interpolated in dst
395 SkScalar fAdvX; // remaining interval advance in dst
396 bool fZeroRamp; // current interval color grad is 0
397
Florin Malitada4545b2017-03-23 17:04:54 -0400398 const Sk4fGradientInterval* fFirstInterval;
399 const Sk4fGradientInterval* fLastInterval;
400 const Sk4fGradientInterval* fInterval; // current interval
401 const SkScalar fDx; // 'dx' for consistency with other impls; actually dt/dx
402 const bool fIsVertical;
fmalitabc590c02016-02-22 09:12:33 -0800403};