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