1 /*
2  * Copyright 2012 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 "SkTwoPointConicalGradient.h"
9 
10 struct TwoPtRadialContext {
11     const TwoPtRadial&  fRec;
12     float               fRelX, fRelY;
13     const float         fIncX, fIncY;
14     float               fB;
15     const float         fDB;
16 
17     TwoPtRadialContext(const TwoPtRadial& rec, SkScalar fx, SkScalar fy,
18                        SkScalar dfx, SkScalar dfy);
19     SkFixed nextT();
20 };
21 
valid_divide(float numer,float denom,float * ratio)22 static int valid_divide(float numer, float denom, float* ratio) {
23     SkASSERT(ratio);
24     if (0 == denom) {
25         return 0;
26     }
27     *ratio = numer / denom;
28     return 1;
29 }
30 
31 // Return the number of distinct real roots, and write them into roots[] in
32 // ascending order
find_quad_roots(float A,float B,float C,float roots[2],bool descendingOrder=false)33 static int find_quad_roots(float A, float B, float C, float roots[2], bool descendingOrder = false) {
34     SkASSERT(roots);
35 
36     if (A == 0) {
37         return valid_divide(-C, B, roots);
38     }
39 
40     float R = B*B - 4*A*C;
41     if (R < 0) {
42         return 0;
43     }
44     R = sk_float_sqrt(R);
45 
46 #if 1
47     float Q = B;
48     if (Q < 0) {
49         Q -= R;
50     } else {
51         Q += R;
52     }
53 #else
54     // on 10.6 this was much slower than the above branch :(
55     float Q = B + copysignf(R, B);
56 #endif
57     Q *= -0.5f;
58     if (0 == Q) {
59         roots[0] = 0;
60         return 1;
61     }
62 
63     float r0 = Q / A;
64     float r1 = C / Q;
65     roots[0] = r0 < r1 ? r0 : r1;
66     roots[1] = r0 > r1 ? r0 : r1;
67     if (descendingOrder) {
68         SkTSwap(roots[0], roots[1]);
69     }
70     return 2;
71 }
72 
lerp(float x,float dx,float t)73 static float lerp(float x, float dx, float t) {
74     return x + t * dx;
75 }
76 
sqr(float x)77 static float sqr(float x) { return x * x; }
78 
init(const SkPoint & center0,SkScalar rad0,const SkPoint & center1,SkScalar rad1,bool flipped)79 void TwoPtRadial::init(const SkPoint& center0, SkScalar rad0,
80                        const SkPoint& center1, SkScalar rad1,
81                        bool flipped) {
82     fCenterX = SkScalarToFloat(center0.fX);
83     fCenterY = SkScalarToFloat(center0.fY);
84     fDCenterX = SkScalarToFloat(center1.fX) - fCenterX;
85     fDCenterY = SkScalarToFloat(center1.fY) - fCenterY;
86     fRadius = SkScalarToFloat(rad0);
87     fDRadius = SkScalarToFloat(rad1) - fRadius;
88 
89     fA = sqr(fDCenterX) + sqr(fDCenterY) - sqr(fDRadius);
90     fRadius2 = sqr(fRadius);
91     fRDR = fRadius * fDRadius;
92 
93     fFlipped = flipped;
94 }
95 
TwoPtRadialContext(const TwoPtRadial & rec,SkScalar fx,SkScalar fy,SkScalar dfx,SkScalar dfy)96 TwoPtRadialContext::TwoPtRadialContext(const TwoPtRadial& rec, SkScalar fx, SkScalar fy,
97                                        SkScalar dfx, SkScalar dfy)
98     : fRec(rec)
99     , fRelX(SkScalarToFloat(fx) - rec.fCenterX)
100     , fRelY(SkScalarToFloat(fy) - rec.fCenterY)
101     , fIncX(SkScalarToFloat(dfx))
102     , fIncY(SkScalarToFloat(dfy))
103     , fB(-2 * (rec.fDCenterX * fRelX + rec.fDCenterY * fRelY + rec.fRDR))
104     , fDB(-2 * (rec.fDCenterX * fIncX + rec.fDCenterY * fIncY)) {}
105 
nextT()106 SkFixed TwoPtRadialContext::nextT() {
107     float roots[2];
108 
109     float C = sqr(fRelX) + sqr(fRelY) - fRec.fRadius2;
110     int countRoots = find_quad_roots(fRec.fA, fB, C, roots, fRec.fFlipped);
111 
112     fRelX += fIncX;
113     fRelY += fIncY;
114     fB += fDB;
115 
116     if (0 == countRoots) {
117         return TwoPtRadial::kDontDrawT;
118     }
119 
120     // Prefer the bigger t value if both give a radius(t) > 0
121     // find_quad_roots returns the values sorted, so we start with the last
122     float t = roots[countRoots - 1];
123     float r = lerp(fRec.fRadius, fRec.fDRadius, t);
124     if (r < 0) {
125         t = roots[0];   // might be the same as roots[countRoots-1]
126         r = lerp(fRec.fRadius, fRec.fDRadius, t);
127         if (r < 0) {
128             return TwoPtRadial::kDontDrawT;
129         }
130     }
131     return SkFloatToFixed(t);
132 }
133 
134 typedef void (*TwoPointConicalProc)(TwoPtRadialContext* rec, SkPMColor* dstC,
135                                     const SkPMColor* cache, int toggle, int count);
136 
twopoint_clamp(TwoPtRadialContext * rec,SkPMColor * SK_RESTRICT dstC,const SkPMColor * SK_RESTRICT cache,int toggle,int count)137 static void twopoint_clamp(TwoPtRadialContext* rec, SkPMColor* SK_RESTRICT dstC,
138                            const SkPMColor* SK_RESTRICT cache, int toggle,
139                            int count) {
140     for (; count > 0; --count) {
141         SkFixed t = rec->nextT();
142         if (TwoPtRadial::DontDrawT(t)) {
143             *dstC++ = 0;
144         } else {
145             SkFixed index = SkClampMax(t, 0xFFFF);
146             SkASSERT(index <= 0xFFFF);
147             *dstC++ = cache[toggle +
148                             (index >> SkGradientShaderBase::kCache32Shift)];
149         }
150         toggle = next_dither_toggle(toggle);
151     }
152 }
153 
twopoint_repeat(TwoPtRadialContext * rec,SkPMColor * SK_RESTRICT dstC,const SkPMColor * SK_RESTRICT cache,int toggle,int count)154 static void twopoint_repeat(TwoPtRadialContext* rec, SkPMColor* SK_RESTRICT dstC,
155                             const SkPMColor* SK_RESTRICT cache, int toggle,
156                             int count) {
157     for (; count > 0; --count) {
158         SkFixed t = rec->nextT();
159         if (TwoPtRadial::DontDrawT(t)) {
160             *dstC++ = 0;
161         } else {
162             SkFixed index = repeat_tileproc(t);
163             SkASSERT(index <= 0xFFFF);
164             *dstC++ = cache[toggle +
165                             (index >> SkGradientShaderBase::kCache32Shift)];
166         }
167         toggle = next_dither_toggle(toggle);
168     }
169 }
170 
twopoint_mirror(TwoPtRadialContext * rec,SkPMColor * SK_RESTRICT dstC,const SkPMColor * SK_RESTRICT cache,int toggle,int count)171 static void twopoint_mirror(TwoPtRadialContext* rec, SkPMColor* SK_RESTRICT dstC,
172                             const SkPMColor* SK_RESTRICT cache, int toggle,
173                             int count) {
174     for (; count > 0; --count) {
175         SkFixed t = rec->nextT();
176         if (TwoPtRadial::DontDrawT(t)) {
177             *dstC++ = 0;
178         } else {
179             SkFixed index = mirror_tileproc(t);
180             SkASSERT(index <= 0xFFFF);
181             *dstC++ = cache[toggle +
182                             (index >> SkGradientShaderBase::kCache32Shift)];
183         }
184         toggle = next_dither_toggle(toggle);
185     }
186 }
187 
188 /////////////////////////////////////////////////////////////////////
189 
SkTwoPointConicalGradient(const SkPoint & start,SkScalar startRadius,const SkPoint & end,SkScalar endRadius,bool flippedGrad,const Descriptor & desc)190 SkTwoPointConicalGradient::SkTwoPointConicalGradient(
191         const SkPoint& start, SkScalar startRadius,
192         const SkPoint& end, SkScalar endRadius,
193         bool flippedGrad, const Descriptor& desc)
194     : SkGradientShaderBase(desc, SkMatrix::I())
195     , fCenter1(start)
196     , fCenter2(end)
197     , fRadius1(startRadius)
198     , fRadius2(endRadius)
199     , fFlippedGrad(flippedGrad)
200 {
201     // this is degenerate, and should be caught by our caller
202     SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
203     fRec.init(fCenter1, fRadius1, fCenter2, fRadius2, fFlippedGrad);
204 }
205 
isOpaque() const206 bool SkTwoPointConicalGradient::isOpaque() const {
207     // Because areas outside the cone are left untouched, we cannot treat the
208     // shader as opaque even if the gradient itself is opaque.
209     // TODO(junov): Compute whether the cone fills the plane crbug.com/222380
210     return false;
211 }
212 
onMakeContext(const ContextRec & rec,SkArenaAlloc * alloc) const213 SkShader::Context* SkTwoPointConicalGradient::onMakeContext(
214     const ContextRec& rec, SkArenaAlloc* alloc) const {
215     return CheckedMakeContext<TwoPointConicalGradientContext>(alloc, *this, rec);
216 }
217 
TwoPointConicalGradientContext(const SkTwoPointConicalGradient & shader,const ContextRec & rec)218 SkTwoPointConicalGradient::TwoPointConicalGradientContext::TwoPointConicalGradientContext(
219         const SkTwoPointConicalGradient& shader, const ContextRec& rec)
220     : INHERITED(shader, rec)
221 {
222     // in general, we might discard based on computed-radius, so clear
223     // this flag (todo: sometimes we can detect that we never discard...)
224     fFlags &= ~kOpaqueAlpha_Flag;
225 }
226 
shadeSpan(int x,int y,SkPMColor * dstCParam,int count)227 void SkTwoPointConicalGradient::TwoPointConicalGradientContext::shadeSpan(
228         int x, int y, SkPMColor* dstCParam, int count) {
229     const SkTwoPointConicalGradient& twoPointConicalGradient =
230             static_cast<const SkTwoPointConicalGradient&>(fShader);
231 
232     int toggle = init_dither_toggle(x, y);
233 
234     SkASSERT(count > 0);
235 
236     SkPMColor* SK_RESTRICT dstC = dstCParam;
237 
238     SkMatrix::MapXYProc dstProc = fDstToIndexProc;
239 
240     const SkPMColor* SK_RESTRICT cache = fCache->getCache32();
241 
242     TwoPointConicalProc shadeProc = twopoint_repeat;
243     if (SkShader::kClamp_TileMode == twoPointConicalGradient.fTileMode) {
244         shadeProc = twopoint_clamp;
245     } else if (SkShader::kMirror_TileMode == twoPointConicalGradient.fTileMode) {
246         shadeProc = twopoint_mirror;
247     } else {
248         SkASSERT(SkShader::kRepeat_TileMode == twoPointConicalGradient.fTileMode);
249     }
250 
251     if (fDstToIndexClass != kPerspective_MatrixClass) {
252         SkPoint srcPt;
253         dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
254                 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
255         SkScalar dx, fx = srcPt.fX;
256         SkScalar dy, fy = srcPt.fY;
257 
258         if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
259             const auto step = fDstToIndex.fixedStepInX(SkIntToScalar(y));
260             dx = step.fX;
261             dy = step.fY;
262         } else {
263             SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
264             dx = fDstToIndex.getScaleX();
265             dy = fDstToIndex.getSkewY();
266         }
267 
268         TwoPtRadialContext rec(twoPointConicalGradient.fRec, fx, fy, dx, dy);
269         (*shadeProc)(&rec, dstC, cache, toggle, count);
270     } else {    // perspective case
271         SkScalar dstX = SkIntToScalar(x) + SK_ScalarHalf;
272         SkScalar dstY = SkIntToScalar(y) + SK_ScalarHalf;
273         for (; count > 0; --count) {
274             SkPoint srcPt;
275             dstProc(fDstToIndex, dstX, dstY, &srcPt);
276             TwoPtRadialContext rec(twoPointConicalGradient.fRec, srcPt.fX, srcPt.fY, 0, 0);
277             (*shadeProc)(&rec, dstC, cache, toggle, 1);
278 
279             dstX += SK_Scalar1;
280             toggle = next_dither_toggle(toggle);
281             dstC += 1;
282         }
283     }
284 }
285 
286 // Returns the original non-sorted version of the gradient
asAGradient(GradientInfo * info) const287 SkShader::GradientType SkTwoPointConicalGradient::asAGradient(
288     GradientInfo* info) const {
289     if (info) {
290         commonAsAGradient(info, fFlippedGrad);
291         info->fPoint[0] = fCenter1;
292         info->fPoint[1] = fCenter2;
293         info->fRadius[0] = fRadius1;
294         info->fRadius[1] = fRadius2;
295         if (fFlippedGrad) {
296             SkTSwap(info->fPoint[0], info->fPoint[1]);
297             SkTSwap(info->fRadius[0], info->fRadius[1]);
298         }
299     }
300     return kConical_GradientType;
301 }
302 
CreateProc(SkReadBuffer & buffer)303 sk_sp<SkFlattenable> SkTwoPointConicalGradient::CreateProc(SkReadBuffer& buffer) {
304     DescriptorScope desc;
305     if (!desc.unflatten(buffer)) {
306         return nullptr;
307     }
308     SkPoint c1 = buffer.readPoint();
309     SkPoint c2 = buffer.readPoint();
310     SkScalar r1 = buffer.readScalar();
311     SkScalar r2 = buffer.readScalar();
312 
313     if (buffer.readBool()) {    // flipped
314         SkTSwap(c1, c2);
315         SkTSwap(r1, r2);
316 
317         SkColor4f* colors = desc.mutableColors();
318         SkScalar* pos = desc.mutablePos();
319         const int last = desc.fCount - 1;
320         const int half = desc.fCount >> 1;
321         for (int i = 0; i < half; ++i) {
322             SkTSwap(colors[i], colors[last - i]);
323             if (pos) {
324                 SkScalar tmp = pos[i];
325                 pos[i] = SK_Scalar1 - pos[last - i];
326                 pos[last - i] = SK_Scalar1 - tmp;
327             }
328         }
329         if (pos) {
330             if (desc.fCount & 1) {
331                 pos[half] = SK_Scalar1 - pos[half];
332             }
333         }
334     }
335 
336     return SkGradientShader::MakeTwoPointConical(c1, r1, c2, r2, desc.fColors,
337                                                  std::move(desc.fColorSpace), desc.fPos,
338                                                  desc.fCount, desc.fTileMode, desc.fGradFlags,
339                                                  desc.fLocalMatrix);
340 }
341 
flatten(SkWriteBuffer & buffer) const342 void SkTwoPointConicalGradient::flatten(SkWriteBuffer& buffer) const {
343     this->INHERITED::flatten(buffer);
344     buffer.writePoint(fCenter1);
345     buffer.writePoint(fCenter2);
346     buffer.writeScalar(fRadius1);
347     buffer.writeScalar(fRadius2);
348     buffer.writeBool(fFlippedGrad);
349 }
350 
351 #if SK_SUPPORT_GPU
352 
353 #include "SkGr.h"
354 #include "SkTwoPointConicalGradient_gpu.h"
355 
asFragmentProcessor(const AsFPArgs & args) const356 sk_sp<GrFragmentProcessor> SkTwoPointConicalGradient::asFragmentProcessor(
357         const AsFPArgs& args) const {
358     SkASSERT(args.fContext);
359     SkASSERT(fPtsToUnit.isIdentity());
360     sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(),
361                                                                        args.fDstColorSpace);
362     sk_sp<GrFragmentProcessor> inner(Gr2PtConicalGradientEffect::Make(
363         GrGradientEffect::CreateArgs(args.fContext, this, args.fLocalMatrix, fTileMode,
364                                      std::move(colorSpaceXform), SkToBool(args.fDstColorSpace))));
365     return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner));
366 }
367 
368 #endif
369 
370 #ifndef SK_IGNORE_TO_STRING
toString(SkString * str) const371 void SkTwoPointConicalGradient::toString(SkString* str) const {
372     str->append("SkTwoPointConicalGradient: (");
373 
374     str->append("center1: (");
375     str->appendScalar(fCenter1.fX);
376     str->append(", ");
377     str->appendScalar(fCenter1.fY);
378     str->append(") radius1: ");
379     str->appendScalar(fRadius1);
380     str->append(" ");
381 
382     str->append("center2: (");
383     str->appendScalar(fCenter2.fX);
384     str->append(", ");
385     str->appendScalar(fCenter2.fY);
386     str->append(") radius2: ");
387     str->appendScalar(fRadius2);
388     str->append(" ");
389 
390     this->INHERITED::toString(str);
391 
392     str->append(")");
393 }
394 #endif
395