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