Line data Source code
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 :
11 : #include <cmath>
12 :
13 : namespace {
14 :
15 : template<DstType dstType, ApplyPremul premul>
16 0 : void ramp(const Sk4f& c, const Sk4f& dc, typename DstTraits<dstType, premul>::Type dst[], int n) {
17 0 : SkASSERT(n > 0);
18 :
19 0 : const Sk4f dc2 = dc + dc;
20 0 : const Sk4f dc4 = dc2 + dc2;
21 :
22 0 : Sk4f c0 = c ;
23 0 : Sk4f c1 = c + dc;
24 0 : Sk4f c2 = c0 + dc2;
25 0 : Sk4f c3 = c1 + dc2;
26 :
27 0 : while (n >= 4) {
28 0 : DstTraits<dstType, premul>::store4x(c0, c1, c2, c3, dst);
29 0 : dst += 4;
30 :
31 0 : c0 = c0 + dc4;
32 0 : c1 = c1 + dc4;
33 0 : c2 = c2 + dc4;
34 0 : c3 = c3 + dc4;
35 0 : n -= 4;
36 : }
37 0 : if (n & 2) {
38 0 : DstTraits<dstType, premul>::store(c0, dst++);
39 0 : DstTraits<dstType, premul>::store(c1, dst++);
40 0 : c0 = c0 + dc2;
41 : }
42 0 : if (n & 1) {
43 0 : DstTraits<dstType, premul>::store(c0, dst);
44 : }
45 0 : }
46 :
47 : // Planar version of ramp (S32 no-premul only).
48 : template<>
49 0 : void ramp<DstType::S32, ApplyPremul::False>(const Sk4f& c, const Sk4f& dc, SkPMColor dst[], int n) {
50 0 : SkASSERT(n > 0);
51 :
52 0 : const Sk4f dc4 = dc * 4;
53 0 : const Sk4x4f dc4x = { Sk4f(dc4[0]), Sk4f(dc4[1]), Sk4f(dc4[2]), Sk4f(dc4[3]) };
54 0 : Sk4x4f c4x = Sk4x4f::Transpose(c, c + dc, c + dc * 2, c + dc * 3);
55 :
56 0 : while (n >= 4) {
57 0 : ( sk_linear_to_srgb(c4x.r) << 0
58 0 : | sk_linear_to_srgb(c4x.g) << 8
59 0 : | sk_linear_to_srgb(c4x.b) << 16
60 0 : | Sk4f_round(255.0f*c4x.a) << 24).store(dst);
61 :
62 : c4x.r += dc4x.r;
63 : c4x.g += dc4x.g;
64 : c4x.b += dc4x.b;
65 : c4x.a += dc4x.a;
66 :
67 0 : dst += 4;
68 0 : n -= 4;
69 : }
70 :
71 0 : if (n & 2) {
72 : DstTraits<DstType::S32, ApplyPremul::False>
73 0 : ::store(Sk4f(c4x.r[0], c4x.g[0], c4x.b[0], c4x.a[0]), dst++);
74 : DstTraits<DstType::S32, ApplyPremul::False>
75 0 : ::store(Sk4f(c4x.r[1], c4x.g[1], c4x.b[1], c4x.a[1]), dst++);
76 : }
77 :
78 0 : if (n & 1) {
79 : DstTraits<DstType::S32, ApplyPremul::False>
80 0 : ::store(Sk4f(c4x.r[n & 2], c4x.g[n & 2], c4x.b[n & 2], c4x.a[n & 2]), dst);
81 : }
82 0 : }
83 :
84 : template<SkShader::TileMode>
85 : SkScalar pinFx(SkScalar);
86 :
87 : template<>
88 0 : SkScalar pinFx<SkShader::kClamp_TileMode>(SkScalar fx) {
89 0 : return fx;
90 : }
91 :
92 : template<>
93 0 : SkScalar pinFx<SkShader::kRepeat_TileMode>(SkScalar fx) {
94 0 : SkScalar f = SkScalarFraction(fx);
95 0 : if (f < 0) {
96 0 : f = SkTMin(f + 1, nextafterf(1, 0));
97 : }
98 0 : SkASSERT(f >= 0);
99 0 : SkASSERT(f < 1.0f);
100 0 : return f;
101 : }
102 :
103 : template<>
104 0 : SkScalar pinFx<SkShader::kMirror_TileMode>(SkScalar fx) {
105 0 : SkScalar f = SkScalarMod(fx, 2.0f);
106 0 : if (f < 0) {
107 0 : f = SkTMin(f + 2, nextafterf(2, 0));
108 : }
109 0 : SkASSERT(f >= 0);
110 0 : SkASSERT(f < 2.0f);
111 0 : return f;
112 : }
113 :
114 : // true when x is in [k1,k2], or [k2, k1] when the interval is reversed.
115 : // TODO(fmalita): hoist the reversed interval check out of this helper.
116 0 : bool in_range(SkScalar x, SkScalar k1, SkScalar k2) {
117 0 : SkASSERT(k1 != k2);
118 : return (k1 < k2)
119 0 : ? (x >= k1 && x <= k2)
120 0 : : (x >= k2 && x <= k1);
121 : }
122 :
123 : } // anonymous namespace
124 :
125 0 : SkLinearGradient::
126 : LinearGradient4fContext::LinearGradient4fContext(const SkLinearGradient& shader,
127 0 : const ContextRec& rec)
128 0 : : INHERITED(shader, rec) {
129 :
130 : // Our fast path expects interval points to be monotonically increasing in x.
131 0 : const bool reverseIntervals = this->isFast() && std::signbit(fDstToPos.getScaleX());
132 0 : fIntervals.init(shader.fOrigColors, shader.fOrigPos, shader.fColorCount, shader.fTileMode,
133 0 : fColorsArePremul, rec.fPaint->getAlpha() * (1.0f / 255), reverseIntervals);
134 :
135 0 : SkASSERT(fIntervals->count() > 0);
136 0 : fCachedInterval = fIntervals->begin();
137 0 : }
138 :
139 : const Sk4fGradientInterval*
140 0 : SkLinearGradient::LinearGradient4fContext::findInterval(SkScalar fx) const {
141 0 : SkASSERT(in_range(fx, fIntervals->front().fT0, fIntervals->back().fT1));
142 :
143 : if (1) {
144 : // Linear search, using the last scanline interval as a starting point.
145 0 : SkASSERT(fCachedInterval >= fIntervals->begin());
146 0 : SkASSERT(fCachedInterval < fIntervals->end());
147 0 : const int search_dir = fDstToPos.getScaleX() >= 0 ? 1 : -1;
148 0 : while (!in_range(fx, fCachedInterval->fT0, fCachedInterval->fT1)) {
149 0 : fCachedInterval += search_dir;
150 0 : if (fCachedInterval >= fIntervals->end()) {
151 0 : fCachedInterval = fIntervals->begin();
152 0 : } else if (fCachedInterval < fIntervals->begin()) {
153 0 : fCachedInterval = fIntervals->end() - 1;
154 : }
155 : }
156 0 : return fCachedInterval;
157 : } else {
158 : // Binary search. Seems less effective than linear + caching.
159 : const auto* i0 = fIntervals->begin();
160 : const auto* i1 = fIntervals->end() - 1;
161 :
162 : while (i0 != i1) {
163 : SkASSERT(i0 < i1);
164 : SkASSERT(in_range(fx, i0->fT0, i1->fT1));
165 :
166 : const auto* i = i0 + ((i1 - i0) >> 1);
167 :
168 : if (in_range(fx, i0->fT0, i->fT1)) {
169 : i1 = i;
170 : } else {
171 : SkASSERT(in_range(fx, i->fT1, i1->fT1));
172 : i0 = i + 1;
173 : }
174 : }
175 :
176 : SkASSERT(in_range(fx, i0->fT0, i0->fT1));
177 : return i0;
178 : }
179 : }
180 :
181 0 : void SkLinearGradient::
182 : LinearGradient4fContext::shadeSpan(int x, int y, SkPMColor dst[], int count) {
183 0 : if (!this->isFast()) {
184 0 : this->INHERITED::shadeSpan(x, y, dst, count);
185 0 : return;
186 : }
187 :
188 : // TODO: plumb dithering
189 0 : SkASSERT(count > 0);
190 0 : if (fColorsArePremul) {
191 : this->shadePremulSpan<DstType::L32,
192 0 : ApplyPremul::False>(x, y, dst, count);
193 : } else {
194 : this->shadePremulSpan<DstType::L32,
195 0 : ApplyPremul::True>(x, y, dst, count);
196 : }
197 : }
198 :
199 0 : void SkLinearGradient::
200 : LinearGradient4fContext::shadeSpan4f(int x, int y, SkPM4f dst[], int count) {
201 0 : if (!this->isFast()) {
202 0 : this->INHERITED::shadeSpan4f(x, y, dst, count);
203 0 : return;
204 : }
205 :
206 : // TONOTDO: plumb dithering
207 0 : SkASSERT(count > 0);
208 0 : if (fColorsArePremul) {
209 : this->shadePremulSpan<DstType::F32,
210 0 : ApplyPremul::False>(x, y, dst, count);
211 : } else {
212 : this->shadePremulSpan<DstType::F32,
213 0 : ApplyPremul::True>(x, y, dst, count);
214 : }
215 : }
216 :
217 : template<DstType dstType, ApplyPremul premul>
218 0 : void SkLinearGradient::
219 : LinearGradient4fContext::shadePremulSpan(int x, int y,
220 : typename DstTraits<dstType, premul>::Type dst[],
221 : int count) const {
222 : const SkLinearGradient& shader =
223 0 : static_cast<const SkLinearGradient&>(fShader);
224 0 : switch (shader.fTileMode) {
225 : case kClamp_TileMode:
226 0 : this->shadeSpanInternal<dstType,
227 : premul,
228 : kClamp_TileMode>(x, y, dst, count);
229 0 : break;
230 : case kRepeat_TileMode:
231 0 : this->shadeSpanInternal<dstType,
232 : premul,
233 : kRepeat_TileMode>(x, y, dst, count);
234 0 : break;
235 : case kMirror_TileMode:
236 0 : this->shadeSpanInternal<dstType,
237 : premul,
238 : kMirror_TileMode>(x, y, dst, count);
239 0 : break;
240 : }
241 0 : }
242 :
243 : template<DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode>
244 0 : void SkLinearGradient::
245 : LinearGradient4fContext::shadeSpanInternal(int x, int y,
246 : typename DstTraits<dstType, premul>::Type dst[],
247 : int count) const {
248 : SkPoint pt;
249 0 : fDstToPosProc(fDstToPos,
250 : x + SK_ScalarHalf,
251 : y + SK_ScalarHalf,
252 : &pt);
253 0 : const SkScalar fx = pinFx<tileMode>(pt.x());
254 0 : const SkScalar dx = fDstToPos.getScaleX();
255 0 : LinearIntervalProcessor<dstType, premul, tileMode> proc(fIntervals->begin(),
256 0 : fIntervals->end() - 1,
257 : this->findInterval(fx),
258 : fx,
259 : dx,
260 0 : SkScalarNearlyZero(dx * count));
261 0 : while (count > 0) {
262 : // What we really want here is SkTPin(advance, 1, count)
263 : // but that's a significant perf hit for >> stops; investigate.
264 0 : const int n = SkScalarTruncToInt(
265 : SkTMin<SkScalar>(proc.currentAdvance() + 1, SkIntToScalar(count)));
266 :
267 : // The current interval advance can be +inf (e.g. when reaching
268 : // the clamp mode end intervals) - when that happens, we expect to
269 : // a) consume all remaining count in one swoop
270 : // b) return a zero color gradient
271 0 : SkASSERT(SkScalarIsFinite(proc.currentAdvance())
272 : || (n == count && proc.currentRampIsZero()));
273 :
274 0 : if (proc.currentRampIsZero()) {
275 0 : DstTraits<dstType, premul>::store(proc.currentColor(),
276 : dst, n);
277 : } else {
278 0 : ramp<dstType, premul>(proc.currentColor(),
279 : proc.currentColorGrad(),
280 : dst, n);
281 : }
282 :
283 0 : proc.advance(SkIntToScalar(n));
284 0 : count -= n;
285 0 : dst += n;
286 : }
287 0 : }
288 :
289 : template<DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode>
290 : class SkLinearGradient::
291 : LinearGradient4fContext::LinearIntervalProcessor {
292 : public:
293 0 : LinearIntervalProcessor(const Sk4fGradientInterval* firstInterval,
294 : const Sk4fGradientInterval* lastInterval,
295 : const Sk4fGradientInterval* i,
296 : SkScalar fx,
297 : SkScalar dx,
298 : bool is_vertical)
299 0 : : fAdvX(is_vertical ? SK_ScalarInfinity : (i->fT1 - fx) / dx)
300 : , fFirstInterval(firstInterval)
301 : , fLastInterval(lastInterval)
302 : , fInterval(i)
303 : , fDx(dx)
304 0 : , fIsVertical(is_vertical)
305 : {
306 0 : SkASSERT(fAdvX >= 0);
307 0 : SkASSERT(firstInterval <= lastInterval);
308 :
309 0 : if (tileMode != kClamp_TileMode && !is_vertical) {
310 0 : const auto spanX = (lastInterval->fT1 - firstInterval->fT0) / dx;
311 0 : SkASSERT(spanX >= 0);
312 :
313 : // If we're in a repeating tile mode and the whole gradient is compressed into a
314 : // fraction of a pixel, we just use the average color in zero-ramp mode.
315 : // This also avoids cases where we make no progress due to interval advances being
316 : // close to zero.
317 : static constexpr SkScalar kMinSpanX = .25f;
318 0 : if (spanX < kMinSpanX) {
319 0 : this->init_average_props();
320 0 : return;
321 : }
322 : }
323 :
324 0 : this->compute_interval_props(fx);
325 : }
326 :
327 0 : SkScalar currentAdvance() const {
328 0 : SkASSERT(fAdvX >= 0);
329 0 : SkASSERT(fAdvX <= (fInterval->fT1 - fInterval->fT0) / fDx || !std::isfinite(fAdvX));
330 0 : return fAdvX;
331 : }
332 :
333 0 : bool currentRampIsZero() const { return fZeroRamp; }
334 0 : const Sk4f& currentColor() const { return fCc; }
335 0 : const Sk4f& currentColorGrad() const { return fDcDx; }
336 :
337 0 : void advance(SkScalar advX) {
338 0 : SkASSERT(advX > 0);
339 0 : SkASSERT(fAdvX >= 0);
340 :
341 0 : if (advX >= fAdvX) {
342 0 : advX = this->advance_interval(advX);
343 : }
344 0 : SkASSERT(advX < fAdvX);
345 :
346 0 : fCc = fCc + fDcDx * Sk4f(advX);
347 0 : fAdvX -= advX;
348 0 : }
349 :
350 : private:
351 0 : void compute_interval_props(SkScalar t) {
352 0 : SkASSERT(in_range(t, fInterval->fT0, fInterval->fT1));
353 :
354 0 : fZeroRamp = fIsVertical || fInterval->fZeroRamp;
355 0 : fCc = DstTraits<dstType, premul>::load(fInterval->fCb);
356 :
357 0 : if (fInterval->fZeroRamp) {
358 0 : fDcDx = 0;
359 : } else {
360 0 : const Sk4f dC = DstTraits<dstType, premul>::load(fInterval->fCg);
361 0 : fCc = fCc + dC * Sk4f(t);
362 0 : fDcDx = dC * fDx;
363 : }
364 0 : }
365 :
366 0 : void init_average_props() {
367 0 : fAdvX = SK_ScalarInfinity;
368 0 : fZeroRamp = true;
369 0 : fDcDx = 0;
370 0 : fCc = Sk4f(0);
371 :
372 : // TODO: precompute the average at interval setup time?
373 0 : for (const auto* i = fFirstInterval; i <= fLastInterval; ++i) {
374 : // Each interval contributes its average color to the total/weighted average:
375 : //
376 : // C = (c0 + c1) / 2 = (Cb + Cg * t0 + Cb + Cg * t1) / 2 = Cb + Cg *(t0 + t1) / 2
377 : //
378 : // Avg += C * (t1 - t0)
379 : //
380 0 : auto c = DstTraits<dstType, premul>::load(i->fCb);
381 0 : if (!i->fZeroRamp) {
382 0 : c = c + DstTraits<dstType, premul>::load(i->fCg) * (i->fT0 + i->fT1) * 0.5f;
383 : }
384 0 : fCc = fCc + c * (i->fT1 - i->fT0);
385 : }
386 0 : }
387 :
388 0 : const Sk4fGradientInterval* next_interval(const Sk4fGradientInterval* i) const {
389 0 : SkASSERT(i >= fFirstInterval);
390 0 : SkASSERT(i <= fLastInterval);
391 0 : i++;
392 :
393 : if (tileMode == kClamp_TileMode) {
394 0 : SkASSERT(i <= fLastInterval);
395 0 : return i;
396 : }
397 :
398 0 : return (i <= fLastInterval) ? i : fFirstInterval;
399 : }
400 :
401 0 : SkScalar advance_interval(SkScalar advX) {
402 0 : SkASSERT(advX >= fAdvX);
403 :
404 0 : do {
405 0 : advX -= fAdvX;
406 0 : fInterval = this->next_interval(fInterval);
407 0 : fAdvX = (fInterval->fT1 - fInterval->fT0) / fDx;
408 0 : SkASSERT(fAdvX > 0);
409 0 : } while (advX >= fAdvX);
410 :
411 0 : compute_interval_props(fInterval->fT0);
412 :
413 0 : SkASSERT(advX >= 0);
414 0 : return advX;
415 : }
416 :
417 : // Current interval properties.
418 : Sk4f fDcDx; // dst color gradient (dc/dx)
419 : Sk4f fCc; // current color, interpolated in dst
420 : SkScalar fAdvX; // remaining interval advance in dst
421 : bool fZeroRamp; // current interval color grad is 0
422 :
423 : const Sk4fGradientInterval* fFirstInterval;
424 : const Sk4fGradientInterval* fLastInterval;
425 : const Sk4fGradientInterval* fInterval; // current interval
426 : const SkScalar fDx; // 'dx' for consistency with other impls; actually dt/dx
427 : const bool fIsVertical;
428 : };
429 :
430 0 : void SkLinearGradient::
431 : LinearGradient4fContext::mapTs(int x, int y, SkScalar ts[], int count) const {
432 0 : SkASSERT(count > 0);
433 0 : SkASSERT(fDstToPosClass != kLinear_MatrixClass);
434 :
435 0 : SkScalar sx = x + SK_ScalarHalf;
436 0 : const SkScalar sy = y + SK_ScalarHalf;
437 : SkPoint pt;
438 :
439 0 : if (fDstToPosClass != kPerspective_MatrixClass) {
440 : // kLinear_MatrixClass, kFixedStepInX_MatrixClass => fixed dt per scanline
441 0 : const SkScalar dtdx = fDstToPos.fixedStepInX(sy).x();
442 0 : fDstToPosProc(fDstToPos, sx, sy, &pt);
443 :
444 0 : const Sk4f dtdx4 = Sk4f(4 * dtdx);
445 0 : Sk4f t4 = Sk4f(pt.x() + 0 * dtdx,
446 0 : pt.x() + 1 * dtdx,
447 0 : pt.x() + 2 * dtdx,
448 0 : pt.x() + 3 * dtdx);
449 :
450 0 : while (count >= 4) {
451 : t4.store(ts);
452 0 : t4 = t4 + dtdx4;
453 0 : ts += 4;
454 0 : count -= 4;
455 : }
456 :
457 0 : if (count & 2) {
458 0 : *ts++ = t4[0];
459 0 : *ts++ = t4[1];
460 0 : t4 = SkNx_shuffle<2, 0, 1, 3>(t4);
461 : }
462 :
463 0 : if (count & 1) {
464 0 : *ts++ = t4[0];
465 : }
466 : } else {
467 0 : for (int i = 0; i < count; ++i) {
468 0 : fDstToPosProc(fDstToPos, sx, sy, &pt);
469 : // Perspective may yield NaN values.
470 : // Short of a better idea, drop to 0.
471 0 : ts[i] = SkScalarIsNaN(pt.x()) ? 0 : pt.x();
472 0 : sx += SK_Scalar1;
473 : }
474 : }
475 0 : }
476 :
477 0 : bool SkLinearGradient::LinearGradient4fContext::onChooseBlitProcs(const SkImageInfo& info,
478 : BlitState* state) {
479 0 : if (state->fMode != SkBlendMode::kSrc &&
480 0 : !(state->fMode == SkBlendMode::kSrcOver && (fFlags & kOpaqueAlpha_Flag))) {
481 0 : return false;
482 : }
483 :
484 0 : switch (info.colorType()) {
485 : case kN32_SkColorType:
486 0 : state->fBlitBW = D32_BlitBW;
487 0 : return true;
488 : case kRGBA_F16_SkColorType:
489 0 : state->fBlitBW = D64_BlitBW;
490 0 : return true;
491 : default:
492 0 : return false;
493 : }
494 : }
495 :
496 0 : void SkLinearGradient::
497 : LinearGradient4fContext::D32_BlitBW(BlitState* state, int x, int y, const SkPixmap& dst,
498 : int count) {
499 : // FIXME: ignoring coverage for now
500 : const LinearGradient4fContext* ctx =
501 0 : static_cast<const LinearGradient4fContext*>(state->fCtx);
502 :
503 0 : if (!dst.info().gammaCloseToSRGB()) {
504 0 : if (ctx->fColorsArePremul) {
505 0 : ctx->shadePremulSpan<DstType::L32, ApplyPremul::False>(
506 0 : x, y, dst.writable_addr32(x, y), count);
507 : } else {
508 0 : ctx->shadePremulSpan<DstType::L32, ApplyPremul::True>(
509 0 : x, y, dst.writable_addr32(x, y), count);
510 : }
511 : } else {
512 0 : if (ctx->fColorsArePremul) {
513 0 : ctx->shadePremulSpan<DstType::S32, ApplyPremul::False>(
514 0 : x, y, dst.writable_addr32(x, y), count);
515 : } else {
516 0 : ctx->shadePremulSpan<DstType::S32, ApplyPremul::True>(
517 0 : x, y, dst.writable_addr32(x, y), count);
518 : }
519 : }
520 0 : }
521 :
522 0 : void SkLinearGradient::
523 : LinearGradient4fContext::D64_BlitBW(BlitState* state, int x, int y, const SkPixmap& dst,
524 : int count) {
525 : // FIXME: ignoring coverage for now
526 : const LinearGradient4fContext* ctx =
527 0 : static_cast<const LinearGradient4fContext*>(state->fCtx);
528 :
529 0 : if (ctx->fColorsArePremul) {
530 0 : ctx->shadePremulSpan<DstType::F16, ApplyPremul::False>(
531 0 : x, y, dst.writable_addr64(x, y), count);
532 : } else {
533 0 : ctx->shadePremulSpan<DstType::F16, ApplyPremul::True>(
534 0 : x, y, dst.writable_addr64(x, y), count);
535 : }
536 0 : }
|