blob: 8478cb0be327b33ed0bd20e4c1f2b1444014c685 [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"
9
10namespace {
11
12Sk4f premul_4f(const Sk4f& c) {
13 const float alpha = c[SkPM4f::A];
14 // FIXME: portable swizzle?
15 return c * Sk4f(alpha, alpha, alpha, 1);
16}
17
18template <bool do_premul>
19SkPMColor trunc_from_255(const Sk4f& c) {
20 SkPMColor pmc;
21 SkNx_cast<uint8_t>(c).store(&pmc);
22 if (do_premul) {
23 pmc = SkPreMultiplyARGB(SkGetPackedA32(pmc), SkGetPackedR32(pmc),
24 SkGetPackedG32(pmc), SkGetPackedB32(pmc));
25 }
26 return pmc;
27}
28
29template<typename DstType, bool do_premul>
30void fill(const Sk4f& c, DstType* dst, int n);
31
32template<>
33void fill<SkPM4f, false>(const Sk4f& c, SkPM4f* dst, int n) {
34 while (n > 0) {
35 c.store(dst++);
36 n--;
37 }
38}
39
40template<>
41void fill<SkPM4f, true>(const Sk4f& c, SkPM4f* dst, int n) {
42 fill<SkPM4f, false>(premul_4f(c), dst, n);
43}
44
45template<>
46void fill<SkPMColor, false>(const Sk4f& c, SkPMColor* dst, int n) {
47 sk_memset32(dst, trunc_from_255<false>(c), n);
48}
49
50template<>
51void fill<SkPMColor, true>(const Sk4f& c, SkPMColor* dst, int n) {
52 sk_memset32(dst, trunc_from_255<true>(c), n);
53}
54
55template<typename DstType, bool do_premul>
56void store(const Sk4f& color, DstType* dst);
57
58template<>
59void store<SkPM4f, false>(const Sk4f& c, SkPM4f* dst) {
60 c.store(dst);
61}
62
63template<>
64void store<SkPM4f, true>(const Sk4f& c, SkPM4f* dst) {
65 store<SkPM4f, false>(premul_4f(c), dst);
66}
67
68template<>
69void store<SkPMColor, false>(const Sk4f& c, SkPMColor* dst) {
70 *dst = trunc_from_255<false>(c);
71}
72
73template<>
74void store<SkPMColor, true>(const Sk4f& c, SkPMColor* dst) {
75 *dst = trunc_from_255<true>(c);
76}
77
78template<typename DstType, bool do_premul>
79void store4x(const Sk4f& c0,
80 const Sk4f& c1,
81 const Sk4f& c2,
82 const Sk4f& c3,
83 DstType* dst) {
84 store<DstType, do_premul>(c0, dst++);
85 store<DstType, do_premul>(c1, dst++);
86 store<DstType, do_premul>(c2, dst++);
87 store<DstType, do_premul>(c3, dst++);
88}
89
90template<>
91void store4x<SkPMColor, false>(const Sk4f& c0,
92 const Sk4f& c1,
93 const Sk4f& c2,
94 const Sk4f& c3,
95 SkPMColor* dst) {
96 Sk4f_ToBytes((uint8_t*)dst, c0, c1, c2, c3);
97}
98
99template<typename DstType, bool do_premul>
100void ramp(const Sk4f& c, const Sk4f& dc, DstType* dst, int n) {
101 SkASSERT(n > 0);
102
103 const Sk4f dc2 = dc + dc;
104 const Sk4f dc4 = dc2 + dc2;
105
106 Sk4f c0 = c ;
107 Sk4f c1 = c + dc;
108 Sk4f c2 = c0 + dc2;
109 Sk4f c3 = c1 + dc2;
110
111 while (n >= 4) {
112 store4x<DstType, do_premul>(c0, c1, c2, c3, dst);
113 dst += 4;
114
115 c0 = c0 + dc4;
116 c1 = c1 + dc4;
117 c2 = c2 + dc4;
118 c3 = c3 + dc4;
119 n -= 4;
120 }
121 if (n & 2) {
122 store<DstType, do_premul>(c0, dst++);
123 store<DstType, do_premul>(c1, dst++);
124 c0 = c0 + dc2;
125 }
126 if (n & 1) {
127 store<DstType, do_premul>(c0, dst);
128 }
129}
130
131template<SkShader::TileMode>
132SkScalar pinFx(SkScalar);
133
134template<>
135SkScalar pinFx<SkShader::kClamp_TileMode>(SkScalar fx) {
136 return fx;
137}
138
139template<>
140SkScalar pinFx<SkShader::kRepeat_TileMode>(SkScalar fx) {
141 const SkScalar f = SkScalarFraction(fx);
142 return f < 0 ? f + 1 : f;
143}
144
145template<>
146SkScalar pinFx<SkShader::kMirror_TileMode>(SkScalar fx) {
147 const SkScalar f = SkScalarMod(fx, 2.0f);
148 return f < 0 ? f + 2 : f;
149}
150
151template<typename DstType>
152float dst_component_scale();
153
154template<>
155float dst_component_scale<SkPM4f>() {
156 return 1;
157}
158
159template<>
160float dst_component_scale<SkPMColor>() {
161 return 255;
162}
163
fmalita7520fc42016-03-04 11:01:24 -0800164SkPMColor pack_color(SkColor c, bool premul) {
165 return premul
166 ? SkPreMultiplyColor(c)
167 : SkPackARGB32NoCheck(SkColorGetA(c), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c));
168}
169
170// true when x is in [k1,k2)
171bool in_range(SkScalar x, SkScalar k1, SkScalar k2) {
172 SkASSERT(k1 != k2);
173 return (k1 < k2)
174 ? (x >= k1 && x < k2)
175 : (x >= k2 && x < k1);
176}
177
178class IntervalBuilder {
179public:
180 IntervalBuilder(const SkColor* colors, const SkScalar* pos, int count, bool reverse)
181 : fColors(colors)
182 , fPos(pos)
183 , fCount(count)
184 , fFirstPos(reverse ? SK_Scalar1 : 0)
185 , fBegin(reverse ? count - 1 : 0)
186 , fAdvance(reverse ? -1 : 1) {
187 SkASSERT(colors);
188 SkASSERT(count > 1);
189 }
190
191 template<typename F>
192 void build(F func) const {
193 if (!fPos) {
194 this->buildImplicitPos(func);
195 return;
196 }
197
198 const int end = fBegin + fAdvance * (fCount - 1);
199 const SkScalar lastPos = 1 - fFirstPos;
200 int prev = fBegin;
201 SkScalar prevPos = fFirstPos;
202
203 do {
204 const int curr = prev + fAdvance;
205 SkASSERT(curr >= 0 && curr < fCount);
206
207 // TODO: this sanitization should be done in SkGradientShaderBase
208 const SkScalar currPos = (fAdvance > 0)
209 ? SkTPin(fPos[curr], prevPos, lastPos)
210 : SkTPin(fPos[curr], lastPos, prevPos);
211
212 if (currPos != prevPos) {
213 SkASSERT((currPos - prevPos > 0) == (fAdvance > 0));
214 func(fColors[prev], fColors[curr], prevPos, currPos);
215 }
216
217 prev = curr;
218 prevPos = currPos;
219 } while (prev != end);
220 }
221
222private:
223 template<typename F>
224 void buildImplicitPos(F func) const {
225 // When clients don't provide explicit color stop positions (fPos == nullptr),
226 // the color stops are distributed evenly across the unit interval
227 // (implicit positioning).
228 const SkScalar dt = fAdvance * SK_Scalar1 / (fCount - 1);
229 const int end = fBegin + fAdvance * (fCount - 2);
230 int prev = fBegin;
231 SkScalar prevPos = fFirstPos;
232
233 while (prev != end) {
234 const int curr = prev + fAdvance;
235 SkASSERT(curr >= 0 && curr < fCount);
236
237 const SkScalar currPos = prevPos + dt;
238 func(fColors[prev], fColors[curr], prevPos, currPos);
239 prev = curr;
240 prevPos = currPos;
241 }
242
243 // emit the last interval with a pinned end position, to avoid precision issues
244 func(fColors[prev], fColors[prev + fAdvance], prevPos, 1 - fFirstPos);
245 }
246
247 const SkColor* fColors;
248 const SkScalar* fPos;
249 const int fCount;
250 const SkScalar fFirstPos;
251 const int fBegin;
252 const int fAdvance;
253};
254
fmalitabc590c02016-02-22 09:12:33 -0800255} // anonymous namespace
256
257SkLinearGradient::
258LinearGradient4fContext::LinearGradient4fContext(const SkLinearGradient& shader,
259 const ContextRec& rec)
fmalita7520fc42016-03-04 11:01:24 -0800260 : INHERITED(shader, rec) {
261 // The main job here is to build a specialized interval list: a different
262 // representation of the color stops data, optimized for efficient scan line
263 // access during shading.
264 //
265 // [{P0,C0} , {P1,C1}) [{P1,C2} , {P2,c3}) ... [{Pn,C2n} , {Pn+1,C2n+1})
266 //
267 // The list is sorted in increasing dst order, i.e. X(Pk) < X(Pk+1). This
268 // allows us to always traverse left->right when iterating over a scan line.
269 // It also means that the interval order matches the color stops when dx >= 0,
270 // and is the inverse (pos, colors, order are flipped) when dx < 0.
271 //
272 // Note: the current representation duplicates pos data; we could refactor to
273 // avoid this if interval storage size becomes a concern.
274 //
275 // Aside from reordering, we also perform two more pre-processing steps at
276 // this stage:
277 //
278 // 1) scale the color components depending on paint alpha and the requested
279 // interpolation space (note: the interval color storage is SkPM4f, but
280 // that doesn't necessarily mean the colors are premultiplied; that
281 // property is tracked in fColorsArePremul)
282 //
283 // 2) inject synthetic intervals to support tiling.
284 //
285 // * for kRepeat, no extra intervals are needed - the iterator just
286 // wraps around at the end:
287 //
288 // ->[P0,P1)->..[Pn-1,Pn)->
289 //
290 // * for kClamp, we add two "infinite" intervals before/after:
291 //
292 // [-/+inf , P0)->[P0 , P1)->..[Pn-1 , Pn)->[Pn , +/-inf)
293 //
294 // (the iterator should never run off the end in this mode)
295 //
296 // * for kMirror, we extend the range to [0..2] and add a flipped
297 // interval series - then the iterator operates just as in the
298 // kRepeat case:
299 //
300 // ->[P0,P1)->..[Pn-1,Pn)->[2 - Pn,2 - Pn-1)->..[2 - P1,2 - P0)->
301 //
302 // TODO: investigate collapsing intervals << 1px.
303
304 SkASSERT(shader.fColorCount > 1);
305 SkASSERT(shader.fOrigColors);
306
307 const float kInv255Float = 1.0f / 255;
308 const float paintAlpha = rec.fPaint->getAlpha() * kInv255Float;
309 const Sk4f componentScale = fColorsArePremul
310 ? Sk4f(paintAlpha * kInv255Float)
311 : Sk4f(kInv255Float, kInv255Float, kInv255Float, paintAlpha * kInv255Float);
312 const bool dx_is_pos = fDstToPos.getScaleX() >= 0;
313 const int first_index = dx_is_pos ? 0 : shader.fColorCount - 1;
314 const int last_index = shader.fColorCount - 1 - first_index;
315 const SkScalar first_pos = dx_is_pos ? 0 : SK_Scalar1;
316 const SkScalar last_pos = 1 - first_pos;
317
318 if (shader.fTileMode == SkShader::kClamp_TileMode) {
319 // synthetic edge interval: -/+inf .. P0
320 const SkPMColor clamp_color = pack_color(shader.fOrigColors[first_index],
321 fColorsArePremul);
322 const SkScalar clamp_pos = dx_is_pos ? SK_ScalarMin : SK_ScalarMax;
323 fIntervals.emplace_back(clamp_color, clamp_pos,
324 clamp_color, first_pos,
325 componentScale);
326 } else if (shader.fTileMode == SkShader::kMirror_TileMode && !dx_is_pos) {
327 // synthetic mirror intervals injected before main intervals: (2 .. 1]
328 addMirrorIntervals(shader, componentScale, dx_is_pos);
329 }
330
331 const IntervalBuilder builder(shader.fOrigColors,
332 shader.fOrigPos,
333 shader.fColorCount,
334 !dx_is_pos);
335 builder.build([this, &componentScale] (SkColor c0, SkColor c1, SkScalar p0, SkScalar p1) {
336 SkASSERT(fIntervals.empty() || fIntervals.back().fP1 == p0);
337
338 fIntervals.emplace_back(pack_color(c0, fColorsArePremul),
339 p0,
340 pack_color(c1, fColorsArePremul),
341 p1,
342 componentScale);
343 });
344
345 if (shader.fTileMode == SkShader::kClamp_TileMode) {
346 // synthetic edge interval: Pn .. +/-inf
347 const SkPMColor clamp_color =
348 pack_color(shader.fOrigColors[last_index], fColorsArePremul);
349 const SkScalar clamp_pos = dx_is_pos ? SK_ScalarMax : SK_ScalarMin;
350 fIntervals.emplace_back(clamp_color, last_pos,
351 clamp_color, clamp_pos,
352 componentScale);
353 } else if (shader.fTileMode == SkShader::kMirror_TileMode && dx_is_pos) {
354 // synthetic mirror intervals injected after main intervals: [1 .. 2)
355 addMirrorIntervals(shader, componentScale, dx_is_pos);
356 }
357
358 SkASSERT(fIntervals.count() > 0);
359 fCachedInterval = fIntervals.begin();
360}
361
362void SkLinearGradient::
363LinearGradient4fContext::addMirrorIntervals(const SkLinearGradient& shader,
364 const Sk4f& componentScale, bool dx_is_pos) {
365 // Iterates in reverse order (vs main interval builder) and adds intervals reflected in 2.
366 const IntervalBuilder builder(shader.fOrigColors,
367 shader.fOrigPos,
368 shader.fColorCount,
369 dx_is_pos);
370 builder.build([this, &componentScale] (SkColor c0, SkColor c1, SkScalar p0, SkScalar p1) {
371 SkASSERT(fIntervals.empty() || fIntervals.back().fP1 == 2 - p0);
372
373 fIntervals.emplace_back(pack_color(c0, fColorsArePremul),
374 2 - p0,
375 pack_color(c1, fColorsArePremul),
376 2 - p1,
377 componentScale);
378 });
379}
380
381const SkGradientShaderBase::GradientShaderBase4fContext::Interval*
382SkLinearGradient::LinearGradient4fContext::findInterval(SkScalar fx) const {
383 SkASSERT(in_range(fx, fIntervals.front().fP0, fIntervals.back().fP1));
384
385 if (1) {
386 // Linear search, using the last scanline interval as a starting point.
387 SkASSERT(fCachedInterval >= fIntervals.begin());
388 SkASSERT(fCachedInterval < fIntervals.end());
389 const int search_dir = fDstToPos.getScaleX() >= 0 ? 1 : -1;
390 while (!in_range(fx, fCachedInterval->fP0, fCachedInterval->fP1)) {
391 fCachedInterval += search_dir;
392 if (fCachedInterval >= fIntervals.end()) {
393 fCachedInterval = fIntervals.begin();
394 } else if (fCachedInterval < fIntervals.begin()) {
395 fCachedInterval = fIntervals.end() - 1;
396 }
397 }
398 return fCachedInterval;
399 } else {
400 // Binary search. Seems less effective than linear + caching.
401 const Interval* i0 = fIntervals.begin();
402 const Interval* i1 = fIntervals.end() - 1;
403
404 while (i0 != i1) {
405 SkASSERT(i0 < i1);
406 SkASSERT(in_range(fx, i0->fP0, i1->fP1));
407
408 const Interval* i = i0 + ((i1 - i0) >> 1);
409
410 if (in_range(fx, i0->fP0, i->fP1)) {
411 i1 = i;
412 } else {
413 SkASSERT(in_range(fx, i->fP1, i1->fP1));
414 i0 = i + 1;
415 }
416 }
417
418 SkASSERT(in_range(fx, i0->fP0, i0->fP1));
419 return i0;
420 }
421}
fmalitabc590c02016-02-22 09:12:33 -0800422
423void SkLinearGradient::
424LinearGradient4fContext::shadeSpan(int x, int y, SkPMColor dst[], int count) {
425 // TODO: plumb dithering
426 SkASSERT(count > 0);
427 if (fColorsArePremul) {
428 this->shadePremulSpan<SkPMColor, false>(x, y, dst, count);
429 } else {
430 this->shadePremulSpan<SkPMColor, true>(x, y, dst, count);
431 }
432}
433
434void SkLinearGradient::
435LinearGradient4fContext::shadeSpan4f(int x, int y, SkPM4f dst[], int count) {
436 // TONOTDO: plumb dithering
437 SkASSERT(count > 0);
438 if (fColorsArePremul) {
439 this->shadePremulSpan<SkPM4f, false>(x, y, dst, count);
440 } else {
441 this->shadePremulSpan<SkPM4f, true>(x, y, dst, count);
442 }
443}
444
445template<typename DstType, bool do_premul>
446void SkLinearGradient::
447LinearGradient4fContext::shadePremulSpan(int x, int y,
448 DstType dst[],
449 int count) const {
450 const SkLinearGradient& shader =
451 static_cast<const SkLinearGradient&>(fShader);
452 switch (shader.fTileMode) {
453 case kClamp_TileMode:
454 this->shadeSpanInternal<DstType,
455 do_premul,
456 kClamp_TileMode>(x, y, dst, count);
457 break;
458 case kRepeat_TileMode:
459 this->shadeSpanInternal<DstType,
460 do_premul,
461 kRepeat_TileMode>(x, y, dst, count);
462 break;
463 case kMirror_TileMode:
464 this->shadeSpanInternal<DstType,
465 do_premul,
466 kMirror_TileMode>(x, y, dst, count);
467 break;
468 }
469}
470
471template<typename DstType, bool do_premul, SkShader::TileMode tileMode>
472void SkLinearGradient::
473LinearGradient4fContext::shadeSpanInternal(int x, int y,
474 DstType dst[],
475 int count) const {
476 SkPoint pt;
477 fDstToPosProc(fDstToPos,
478 x + SK_ScalarHalf,
479 y + SK_ScalarHalf,
480 &pt);
481 const SkScalar fx = pinFx<tileMode>(pt.x());
482 const SkScalar dx = fDstToPos.getScaleX();
483 LinearIntervalProcessor<DstType, tileMode> proc(fIntervals.begin(),
484 fIntervals.end() - 1,
485 this->findInterval(fx),
486 fx,
487 dx,
488 SkScalarNearlyZero(dx * count));
489 while (count > 0) {
490 // What we really want here is SkTPin(advance, 1, count)
491 // but that's a significant perf hit for >> stops; investigate.
492 const int n = SkScalarTruncToInt(
493 SkTMin<SkScalar>(proc.currentAdvance() + 1, SkIntToScalar(count)));
494
495 // The current interval advance can be +inf (e.g. when reaching
496 // the clamp mode end intervals) - when that happens, we expect to
497 // a) consume all remaining count in one swoop
498 // b) return a zero color gradient
499 SkASSERT(SkScalarIsFinite(proc.currentAdvance())
500 || (n == count && proc.currentRampIsZero()));
501
502 if (proc.currentRampIsZero()) {
503 fill<DstType, do_premul>(proc.currentColor(),
504 dst, n);
505 } else {
506 ramp<DstType, do_premul>(proc.currentColor(),
507 proc.currentColorGrad(),
508 dst, n);
509 }
510
511 proc.advance(SkIntToScalar(n));
512 count -= n;
513 dst += n;
514 }
515}
516
517template<typename DstType, SkShader::TileMode tileMode>
518class SkLinearGradient::
519LinearGradient4fContext::LinearIntervalProcessor {
520public:
521 LinearIntervalProcessor(const Interval* firstInterval,
522 const Interval* lastInterval,
523 const Interval* i,
524 SkScalar fx,
525 SkScalar dx,
526 bool is_vertical)
527 : fDstComponentScale(dst_component_scale<DstType>())
528 , fAdvX((i->fP1 - fx) / dx)
529 , fFirstInterval(firstInterval)
530 , fLastInterval(lastInterval)
531 , fInterval(i)
532 , fDx(dx)
533 , fIsVertical(is_vertical)
534 {
535 SkASSERT(firstInterval <= lastInterval);
536 SkASSERT(i->contains(fx));
537 this->compute_interval_props(fx - i->fP0);
538 }
539
540 SkScalar currentAdvance() const {
541 SkASSERT(fAdvX >= 0);
542 SkASSERT(fAdvX <= (fInterval->fP1 - fInterval->fP0) / fDx);
543 return fAdvX;
544 }
545
546 bool currentRampIsZero() const { return fZeroRamp; }
547 const Sk4f& currentColor() const { return fCc; }
548 const Sk4f& currentColorGrad() const { return fDcDx; }
549
550 void advance(SkScalar advX) {
551 SkASSERT(advX > 0);
552 SkASSERT(fAdvX >= 0);
553
554 if (advX >= fAdvX) {
555 advX = this->advance_interval(advX);
556 }
557 SkASSERT(advX < fAdvX);
558
559 fCc = fCc + fDcDx * Sk4f(advX);
560 fAdvX -= advX;
561 }
562
563private:
564 void compute_interval_props(SkScalar t) {
565 fDc = Sk4f::Load(fInterval->fDc.fVec);
566 fCc = Sk4f::Load(fInterval->fC0.fVec);
567 fCc = fCc + fDc * Sk4f(t);
568 fCc = fCc * fDstComponentScale;
569 fDcDx = fDc * fDstComponentScale * Sk4f(fDx);
570 fZeroRamp = fIsVertical || fInterval->isZeroRamp();
571 }
572
573 const Interval* next_interval(const Interval* i) const {
574 SkASSERT(i >= fFirstInterval);
575 SkASSERT(i <= fLastInterval);
576 i++;
577
578 if (tileMode == kClamp_TileMode) {
579 SkASSERT(i <= fLastInterval);
580 return i;
581 }
582
583 return (i <= fLastInterval) ? i : fFirstInterval;
584 }
585
586 SkScalar advance_interval(SkScalar advX) {
587 SkASSERT(advX >= fAdvX);
588
589 do {
590 advX -= fAdvX;
591 fInterval = this->next_interval(fInterval);
592 fAdvX = (fInterval->fP1 - fInterval->fP0) / fDx;
593 SkASSERT(fAdvX > 0);
594 } while (advX >= fAdvX);
595
596 compute_interval_props(0);
597
598 SkASSERT(advX >= 0);
599 return advX;
600 }
601
602 const Sk4f fDstComponentScale; // cached dst scale (PMC: 255, PM4f: 1)
603
604 // Current interval properties.
605 Sk4f fDc; // local color gradient (dc/dt)
606 Sk4f fDcDx; // dst color gradient (dc/dx)
607 Sk4f fCc; // current color, interpolated in dst
608 SkScalar fAdvX; // remaining interval advance in dst
609 bool fZeroRamp; // current interval color grad is 0
610
611 const Interval* fFirstInterval;
612 const Interval* fLastInterval;
613 const Interval* fInterval; // current interval
614 const SkScalar fDx; // 'dx' for consistency with other impls; actually dt/dx
615 const bool fIsVertical;
616};