Update rive-cpp to 2.0 version
[platform/core/uifw/rive-tizen.git] / submodule / skia / src / shaders / gradients / Sk4fLinearGradient.cpp
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 "include/core/SkPaint.h"
9 #include "include/private/SkTPin.h"
10 #include "src/shaders/gradients/Sk4fLinearGradient.h"
11
12 #include <cmath>
13 #include <utility>
14
15 namespace {
16
17 template<ApplyPremul premul>
18 void ramp(const skvx::float4& c, const skvx::float4& dc, SkPMColor dst[], int n,
19           const skvx::float4& bias0, const skvx::float4& bias1) {
20     SkASSERT(n > 0);
21
22     const auto dc2 = dc + dc,
23                dc4 = dc2 + dc2;
24
25     auto c0 =  c +      DstTraits<premul>::pre_lerp_bias(bias0),
26          c1 =  c + dc + DstTraits<premul>::pre_lerp_bias(bias1),
27          c2 = c0 + dc2,
28          c3 = c1 + dc2;
29
30     while (n >= 4) {
31         DstTraits<premul>::store4x(c0, c1, c2, c3, dst, bias0, bias1);
32         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) {
41         DstTraits<premul>::store(c0, dst++, bias0);
42         DstTraits<premul>::store(c1, dst++, bias1);
43         c0 = c0 + dc2;
44     }
45     if (n & 1) {
46         DstTraits<premul>::store(c0, dst, bias0);
47     }
48 }
49
50 template<SkTileMode>
51 SkScalar pinFx(SkScalar);
52
53 template<>
54 SkScalar pinFx<SkTileMode::kClamp>(SkScalar fx) {
55     return fx;
56 }
57
58 template<>
59 SkScalar pinFx<SkTileMode::kRepeat>(SkScalar fx) {
60     SkScalar f = SkScalarIsFinite(fx) ? SkScalarFraction(fx) : 0;
61     if (f < 0) {
62         f = std::min(f + 1, nextafterf(1, 0));
63     }
64     SkASSERT(f >= 0);
65     SkASSERT(f < 1.0f);
66     return f;
67 }
68
69 template<>
70 SkScalar pinFx<SkTileMode::kMirror>(SkScalar fx) {
71     SkScalar f = SkScalarIsFinite(fx) ? SkScalarMod(fx, 2.0f) : 0;
72     if (f < 0) {
73         f = std::min(f + 2, nextafterf(2, 0));
74     }
75     SkASSERT(f >= 0);
76     SkASSERT(f < 2.0f);
77     return f;
78 }
79
80 // true when x is in [k1,k2], or [k2, k1] when the interval is reversed.
81 // TODO(fmalita): hoist the reversed interval check out of this helper.
82 bool in_range(SkScalar x, SkScalar k1, SkScalar k2) {
83     SkASSERT(k1 != k2);
84     return (k1 < k2)
85         ? (x >= k1 && x <= k2)
86         : (x >= k2 && x <= k1);
87 }
88
89 } // anonymous namespace
90
91 SkLinearGradient::
92 LinearGradient4fContext::LinearGradient4fContext(const SkLinearGradient& shader,
93                                                  const ContextRec& rec)
94     : INHERITED(shader, rec) {
95
96     // Our fast path expects interval points to be monotonically increasing in x.
97     const bool reverseIntervals = std::signbit(fDstToPos.getScaleX());
98     fIntervals.init(shader, rec.fDstColorSpace, shader.fTileMode,
99                     fColorsArePremul, rec.fPaintAlpha * (1.0f / 255), reverseIntervals);
100
101     SkASSERT(fIntervals->count() > 0);
102     fCachedInterval = fIntervals->begin();
103 }
104
105 const Sk4fGradientInterval*
106 SkLinearGradient::LinearGradient4fContext::findInterval(SkScalar fx) const {
107     SkASSERT(in_range(fx, fIntervals->front().fT0, fIntervals->back().fT1));
108
109     if ((true)) {
110         // Linear search, using the last scanline interval as a starting point.
111         SkASSERT(fCachedInterval >= fIntervals->begin());
112         SkASSERT(fCachedInterval < fIntervals->end());
113         const int search_dir = fDstToPos.getScaleX() >= 0 ? 1 : -1;
114         while (!in_range(fx, fCachedInterval->fT0, fCachedInterval->fT1)) {
115             fCachedInterval += search_dir;
116             if (fCachedInterval >= fIntervals->end()) {
117                 fCachedInterval = fIntervals->begin();
118             } else if (fCachedInterval < fIntervals->begin()) {
119                 fCachedInterval = fIntervals->end() - 1;
120             }
121         }
122         return fCachedInterval;
123     } else {
124         // Binary search.  Seems less effective than linear + caching.
125         const auto* i0 = fIntervals->begin();
126         const auto* i1 = fIntervals->end() - 1;
127
128         while (i0 != i1) {
129             SkASSERT(i0 < i1);
130             SkASSERT(in_range(fx, i0->fT0, i1->fT1));
131
132             const auto* i = i0 + ((i1 - i0) >> 1);
133
134             if (in_range(fx, i0->fT0, i->fT1)) {
135                 i1 = i;
136             } else {
137                 SkASSERT(in_range(fx, i->fT1, i1->fT1));
138                 i0 = i + 1;
139             }
140         }
141
142         SkASSERT(in_range(fx, i0->fT0, i0->fT1));
143         return i0;
144     }
145 }
146
147
148 void SkLinearGradient::
149 LinearGradient4fContext::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) {
166             using std::swap;
167             swap(bias0, bias1);
168         }
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<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<ApplyPremul::True >(x, y, dst, count, bias0, bias1);
184     }
185 }
186
187 template<ApplyPremul premul>
188 void SkLinearGradient::
189 LinearGradient4fContext::shadePremulSpan(int x, int y, SkPMColor dst[], int count,
190                                          float bias0, float bias1) const {
191     const SkLinearGradient& shader = static_cast<const SkLinearGradient&>(fShader);
192     switch (shader.fTileMode) {
193         case SkTileMode::kDecal:
194             SkASSERT(false);    // decal only supported via stages
195             [[fallthrough]];
196         case SkTileMode::kClamp:
197             this->shadeSpanInternal<premul, SkTileMode::kClamp >(x, y, dst, count, bias0, bias1);
198             break;
199         case SkTileMode::kRepeat:
200             this->shadeSpanInternal<premul, SkTileMode::kRepeat>(x, y, dst, count, bias0, bias1);
201             break;
202         case SkTileMode::kMirror:
203             this->shadeSpanInternal<premul, SkTileMode::kMirror>(x, y, dst, count, bias0, bias1);
204             break;
205     }
206 }
207
208 template<ApplyPremul premul, SkTileMode tileMode>
209 void SkLinearGradient::
210 LinearGradient4fContext::shadeSpanInternal(int x, int y, SkPMColor dst[], int count,
211                                            float bias0, float bias1) const {
212     SkPoint pt;
213     fDstToPosProc(fDstToPos,
214                   x + SK_ScalarHalf,
215                   y + SK_ScalarHalf,
216                   &pt);
217     const SkScalar fx = pinFx<tileMode>(pt.x());
218     const SkScalar dx = fDstToPos.getScaleX();
219     LinearIntervalProcessor<premul, tileMode> proc(fIntervals->begin(),
220                                                    fIntervals->end() - 1,
221                                                    this->findInterval(fx),
222                                                    fx,
223                                                    dx,
224                                                    SkScalarNearlyZero(dx * count));
225     skvx::float4 bias4f0(bias0),
226                  bias4f1(bias1);
227
228     while (count > 0) {
229         // What we really want here is SkTPin(advance, 1, count)
230         // but that's a significant perf hit for >> stops; investigate.
231         const int n = std::min(SkScalarTruncToInt(proc.currentAdvance() + 1), count);
232
233         // The current interval advance can be +inf (e.g. when reaching
234         // the clamp mode end intervals) - when that happens, we expect to
235         //   a) consume all remaining count in one swoop
236         //   b) return a zero color gradient
237         SkASSERT(SkScalarIsFinite(proc.currentAdvance())
238             || (n == count && proc.currentRampIsZero()));
239
240         if (proc.currentRampIsZero()) {
241             DstTraits<premul>::store(proc.currentColor(), dst, n);
242         } else {
243             ramp<premul>(proc.currentColor(), proc.currentColorGrad(), dst, n,
244                          bias4f0, bias4f1);
245         }
246
247         proc.advance(SkIntToScalar(n));
248         count -= n;
249         dst   += n;
250
251         if (n & 1) {
252             using std::swap;
253             swap(bias4f0, bias4f1);
254         }
255     }
256 }
257
258 template<ApplyPremul premul, SkTileMode tileMode>
259 class SkLinearGradient::
260 LinearGradient4fContext::LinearIntervalProcessor {
261 public:
262     LinearIntervalProcessor(const Sk4fGradientInterval* firstInterval,
263                             const Sk4fGradientInterval* lastInterval,
264                             const Sk4fGradientInterval* i,
265                             SkScalar fx,
266                             SkScalar dx,
267                             bool is_vertical)
268         : fAdvX(is_vertical ? SK_ScalarInfinity : (i->fT1 - fx) / dx)
269         , fFirstInterval(firstInterval)
270         , fLastInterval(lastInterval)
271         , fInterval(i)
272         , fDx(dx)
273         , fIsVertical(is_vertical)
274     {
275         SkASSERT(fAdvX >= 0);
276         SkASSERT(firstInterval <= lastInterval);
277
278         if (tileMode != SkTileMode::kClamp && !is_vertical) {
279             const auto spanX = (lastInterval->fT1 - firstInterval->fT0) / dx;
280             SkASSERT(spanX >= 0);
281
282             // If we're in a repeating tile mode and the whole gradient is compressed into a
283             // fraction of a pixel, we just use the average color in zero-ramp mode.
284             // This also avoids cases where we make no progress due to interval advances being
285             // close to zero.
286             static constexpr SkScalar kMinSpanX = .25f;
287             if (spanX < kMinSpanX) {
288                 this->init_average_props();
289                 return;
290             }
291         }
292
293         this->compute_interval_props(fx);
294     }
295
296     SkScalar currentAdvance() const {
297         SkASSERT(fAdvX >= 0);
298         SkASSERT(!std::isfinite(fAdvX) || fAdvX <= (fInterval->fT1 - fInterval->fT0) / fDx);
299         return fAdvX;
300     }
301
302     bool currentRampIsZero() const { return fZeroRamp; }
303     const skvx::float4& currentColor() const { return fCc; }
304     const skvx::float4& currentColorGrad() const { return fDcDx; }
305
306     void advance(SkScalar advX) {
307         SkASSERT(advX > 0);
308         SkASSERT(fAdvX >= 0);
309
310         if (advX >= fAdvX) {
311             advX = this->advance_interval(advX);
312         }
313         SkASSERT(advX < fAdvX);
314
315         fCc = fCc + fDcDx * advX;
316         fAdvX -= advX;
317     }
318
319 private:
320     void compute_interval_props(SkScalar t) {
321         SkASSERT(in_range(t, fInterval->fT0, fInterval->fT1));
322
323         const auto dc = DstTraits<premul>::load(fInterval->fCg);
324                   fCc = DstTraits<premul>::load(fInterval->fCb) + dc * t;
325                 fDcDx = dc * fDx;
326             fZeroRamp = fIsVertical || all(dc == 0);
327     }
328
329     void init_average_props() {
330         fAdvX     = SK_ScalarInfinity;
331         fZeroRamp = true;
332         fDcDx     = 0;
333         fCc       = 0;
334
335         // TODO: precompute the average at interval setup time?
336         for (const auto* i = fFirstInterval; i <= fLastInterval; ++i) {
337             // Each interval contributes its average color to the total/weighted average:
338             //
339             //   C = (c0 + c1) / 2 = (Cb + Cg * t0 + Cb + Cg * t1) / 2 = Cb + Cg *(t0 + t1) / 2
340             //
341             //   Avg += C * (t1 - t0)
342             //
343             const auto c = DstTraits<premul>::load(i->fCb)
344                          + DstTraits<premul>::load(i->fCg) * (i->fT0 + i->fT1) * 0.5f;
345             fCc = fCc + c * (i->fT1 - i->fT0);
346         }
347     }
348
349     const Sk4fGradientInterval* next_interval(const Sk4fGradientInterval* i) const {
350         SkASSERT(i >= fFirstInterval);
351         SkASSERT(i <= fLastInterval);
352         i++;
353
354         if (tileMode == SkTileMode::kClamp) {
355             SkASSERT(i <= fLastInterval);
356             return i;
357         }
358
359         return (i <= fLastInterval) ? i : fFirstInterval;
360     }
361
362     SkScalar advance_interval(SkScalar advX) {
363         SkASSERT(advX >= fAdvX);
364
365         do {
366             advX -= fAdvX;
367             fInterval = this->next_interval(fInterval);
368             fAdvX = (fInterval->fT1 - fInterval->fT0) / fDx;
369             SkASSERT(fAdvX >= 0); // fT1 must be bigger than fT0 but fAdvX can underflow.
370         } while (advX >= fAdvX);
371
372         compute_interval_props(fInterval->fT0);
373
374         SkASSERT(advX >= 0);
375         return advX;
376     }
377
378     // Current interval properties.
379     skvx::float4 fDcDx;      // dst color gradient (dc/dx)
380     skvx::float4 fCc;        // current color, interpolated in dst
381     SkScalar     fAdvX;      // remaining interval advance in dst
382     bool         fZeroRamp;  // current interval color grad is 0
383
384     const Sk4fGradientInterval* fFirstInterval;
385     const Sk4fGradientInterval* fLastInterval;
386     const Sk4fGradientInterval* fInterval;  // current interval
387     const SkScalar              fDx;        // 'dx' for consistency with other impls; actually dt/dx
388     const bool                  fIsVertical;
389 };