blob: f691db291c931e193c7de3095e7d76eb7a8b048e [file] [log] [blame]
rileya@google.com589708b2012-07-26 20:04:23 +00001
2/*
3 * Copyright 2012 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9 #include "SkTwoPointRadialGradient.h"
10
11/* Two-point radial gradients are specified by two circles, each with a center
12 point and radius. The gradient can be considered to be a series of
13 concentric circles, with the color interpolated from the start circle
14 (at t=0) to the end circle (at t=1).
15
16 For each point (x, y) in the span, we want to find the
17 interpolated circle that intersects that point. The center
18 of the desired circle (Cx, Cy) falls at some distance t
19 along the line segment between the start point (Sx, Sy) and
20 end point (Ex, Ey):
21
22 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
23 Cy = (1 - t) * Sy + t * Ey
24
25 The radius of the desired circle (r) is also a linear interpolation t
26 between the start and end radii (Sr and Er):
27
28 r = (1 - t) * Sr + t * Er
29
30 But
31
32 (x - Cx)^2 + (y - Cy)^2 = r^2
33
34 so
35
36 (x - ((1 - t) * Sx + t * Ex))^2
37 + (y - ((1 - t) * Sy + t * Ey))^2
38 = ((1 - t) * Sr + t * Er)^2
39
40 Solving for t yields
41
42 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
43 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
44 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
45
46 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
47
48 [Dx^2 + Dy^2 - Dr^2)] * t^2
49 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
50 + [dx^2 + dy^2 - Sr^2] = 0
51
52 A quadratic in t. The two roots of the quadratic reflect the two
53 possible circles on which the point may fall. Solving for t yields
54 the gradient value to use.
55
56 If a<0, the start circle is entirely contained in the
57 end circle, and one of the roots will be <0 or >1 (off the line
58 segment). If a>0, the start circle falls at least partially
59 outside the end circle (or vice versa), and the gradient
60 defines a "tube" where a point may be on one circle (on the
61 inside of the tube) or the other (outside of the tube). We choose
62 one arbitrarily.
63
64 In order to keep the math to within the limits of fixed point,
65 we divide the entire quadratic by Dr^2, and replace
66 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
67
68 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
69 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
70 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
71
72 (x' and y' are computed by appending the subtract and scale to the
73 fDstToIndex matrix in the constructor).
74
75 Since the 'A' component of the quadratic is independent of x' and y', it
76 is precomputed in the constructor. Since the 'B' component is linear in
77 x' and y', if x and y are linear in the span, 'B' can be computed
78 incrementally with a simple delta (db below). If it is not (e.g.,
79 a perspective projection), it must be computed in the loop.
80
81*/
82
83namespace {
84
85inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
86 SkScalar sr2d2, SkScalar foura,
87 SkScalar oneOverTwoA, bool posRoot) {
88 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
89 if (0 == foura) {
90 return SkScalarToFixed(SkScalarDiv(-c, b));
91 }
92
93 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
94 if (discrim < 0) {
95 discrim = -discrim;
96 }
97 SkScalar rootDiscrim = SkScalarSqrt(discrim);
98 SkScalar result;
99 if (posRoot) {
100 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
101 } else {
102 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
103 }
104 return SkScalarToFixed(result);
105}
106
107typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx,
108 SkScalar fy, SkScalar dy,
109 SkScalar b, SkScalar db,
110 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
111 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
112 int count);
113
114void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx,
115 SkScalar fy, SkScalar dy,
116 SkScalar b, SkScalar db,
117 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
118 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
119 int count) {
120 for (; count > 0; --count) {
121 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
122 fOneOverTwoA, posRoot);
123 SkFixed index = SkClampMax(t, 0xFFFF);
124 SkASSERT(index <= 0xFFFF);
125 *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift];
126 fx += dx;
127 fy += dy;
128 b += db;
129 }
130}
131void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx,
132 SkScalar fy, SkScalar dy,
133 SkScalar b, SkScalar db,
134 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
135 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
136 int count) {
137 for (; count > 0; --count) {
138 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
139 fOneOverTwoA, posRoot);
140 SkFixed index = mirror_tileproc(t);
141 SkASSERT(index <= 0xFFFF);
142 *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift];
143 fx += dx;
144 fy += dy;
145 b += db;
146 }
147}
148
149void shadeSpan_twopoint_repeat(SkScalar fx, SkScalar dx,
150 SkScalar fy, SkScalar dy,
151 SkScalar b, SkScalar db,
152 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
153 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
154 int count) {
155 for (; count > 0; --count) {
156 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
157 fOneOverTwoA, posRoot);
158 SkFixed index = repeat_tileproc(t);
159 SkASSERT(index <= 0xFFFF);
160 *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift];
161 fx += dx;
162 fy += dy;
163 b += db;
164 }
165}
166}
167
rileya@google.com98e8b6d2012-07-31 20:38:06 +0000168/////////////////////////////////////////////////////////////////////
169
rileya@google.com589708b2012-07-26 20:04:23 +0000170SkTwoPointRadialGradient::SkTwoPointRadialGradient(
171 const SkPoint& start, SkScalar startRadius,
172 const SkPoint& end, SkScalar endRadius,
commit-bot@chromium.org9c9005a2014-04-28 14:55:39 +0000173 const Descriptor& desc, const SkMatrix* localMatrix)
174 : SkGradientShaderBase(desc, localMatrix),
rileya@google.com589708b2012-07-26 20:04:23 +0000175 fCenter1(start),
176 fCenter2(end),
177 fRadius1(startRadius),
178 fRadius2(endRadius) {
179 init();
180}
181
182SkShader::BitmapType SkTwoPointRadialGradient::asABitmap(
183 SkBitmap* bitmap,
184 SkMatrix* matrix,
185 SkShader::TileMode* xy) const {
186 if (bitmap) {
rileya@google.com1c6d64b2012-07-27 15:49:05 +0000187 this->getGradientTableBitmap(bitmap);
rileya@google.com589708b2012-07-26 20:04:23 +0000188 }
189 SkScalar diffL = 0; // just to avoid gcc warning
190 if (matrix) {
191 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
192 SkScalarSquare(fDiff.fY));
193 }
194 if (matrix) {
195 if (diffL) {
196 SkScalar invDiffL = SkScalarInvert(diffL);
197 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
198 SkScalarMul(invDiffL, fDiff.fX));
199 } else {
200 matrix->reset();
201 }
202 matrix->preConcat(fPtsToUnit);
203 }
204 if (xy) {
205 xy[0] = fTileMode;
206 xy[1] = kClamp_TileMode;
207 }
208 return kTwoPointRadial_BitmapType;
209}
210
211SkShader::GradientType SkTwoPointRadialGradient::asAGradient(
212 SkShader::GradientInfo* info) const {
213 if (info) {
214 commonAsAGradient(info);
215 info->fPoint[0] = fCenter1;
216 info->fPoint[1] = fCenter2;
217 info->fRadius[0] = fRadius1;
218 info->fRadius[1] = fRadius2;
219 }
220 return kRadial2_GradientType;
221}
222
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000223size_t SkTwoPointRadialGradient::contextSize() const {
224 return sizeof(TwoPointRadialGradientContext);
225}
226
commit-bot@chromium.orge901b6d2014-05-01 19:31:31 +0000227bool SkTwoPointRadialGradient::validContext(const ContextRec& rec, SkMatrix* totalInverse) const {
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000228 // For now, we might have divided by zero, so detect that.
229 if (0 == fDiffRadius) {
230 return false;
231 }
232
commit-bot@chromium.orge901b6d2014-05-01 19:31:31 +0000233 return this->INHERITED::validContext(rec, totalInverse);
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000234}
235
commit-bot@chromium.orge901b6d2014-05-01 19:31:31 +0000236SkShader::Context* SkTwoPointRadialGradient::createContext(const ContextRec& rec,
237 void* storage) const {
238 if (!this->validContext(rec, NULL)) {
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000239 return NULL;
240 }
241
commit-bot@chromium.orge901b6d2014-05-01 19:31:31 +0000242 return SkNEW_PLACEMENT_ARGS(storage, TwoPointRadialGradientContext, (*this, rec));
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000243}
244
245SkTwoPointRadialGradient::TwoPointRadialGradientContext::TwoPointRadialGradientContext(
commit-bot@chromium.orge901b6d2014-05-01 19:31:31 +0000246 const SkTwoPointRadialGradient& shader, const ContextRec& rec)
247 : INHERITED(shader, rec)
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000248{
249 // we don't have a span16 proc
250 fFlags &= ~kHasSpan16_Flag;
251}
252
253void SkTwoPointRadialGradient::TwoPointRadialGradientContext::shadeSpan(
254 int x, int y, SkPMColor* dstCParam, int count) {
rileya@google.com589708b2012-07-26 20:04:23 +0000255 SkASSERT(count > 0);
256
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000257 const SkTwoPointRadialGradient& twoPointRadialGradient =
258 static_cast<const SkTwoPointRadialGradient&>(fShader);
259
rileya@google.com589708b2012-07-26 20:04:23 +0000260 SkPMColor* SK_RESTRICT dstC = dstCParam;
261
262 // Zero difference between radii: fill with transparent black.
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000263 if (twoPointRadialGradient.fDiffRadius == 0) {
rileya@google.com589708b2012-07-26 20:04:23 +0000264 sk_bzero(dstC, count * sizeof(*dstC));
265 return;
266 }
267 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000268 TileProc proc = twoPointRadialGradient.fTileProc;
269 const SkPMColor* SK_RESTRICT cache = fCache->getCache32();
rileya@google.com589708b2012-07-26 20:04:23 +0000270
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000271 SkScalar foura = twoPointRadialGradient.fA * 4;
272 bool posRoot = twoPointRadialGradient.fDiffRadius < 0;
rileya@google.com589708b2012-07-26 20:04:23 +0000273 if (fDstToIndexClass != kPerspective_MatrixClass) {
274 SkPoint srcPt;
275 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
276 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
277 SkScalar dx, fx = srcPt.fX;
278 SkScalar dy, fy = srcPt.fY;
279
280 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
281 SkFixed fixedX, fixedY;
282 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
283 dx = SkFixedToScalar(fixedX);
284 dy = SkFixedToScalar(fixedY);
285 } else {
286 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
287 dx = fDstToIndex.getScaleX();
288 dy = fDstToIndex.getSkewY();
289 }
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000290 SkScalar b = (SkScalarMul(twoPointRadialGradient.fDiff.fX, fx) +
291 SkScalarMul(twoPointRadialGradient.fDiff.fY, fy) -
292 twoPointRadialGradient.fStartRadius) * 2;
293 SkScalar db = (SkScalarMul(twoPointRadialGradient.fDiff.fX, dx) +
294 SkScalarMul(twoPointRadialGradient.fDiff.fY, dy)) * 2;
rileya@google.com589708b2012-07-26 20:04:23 +0000295
296 TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000297 if (SkShader::kClamp_TileMode == twoPointRadialGradient.fTileMode) {
rileya@google.com589708b2012-07-26 20:04:23 +0000298 shadeProc = shadeSpan_twopoint_clamp;
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000299 } else if (SkShader::kMirror_TileMode == twoPointRadialGradient.fTileMode) {
rileya@google.com589708b2012-07-26 20:04:23 +0000300 shadeProc = shadeSpan_twopoint_mirror;
301 } else {
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000302 SkASSERT(SkShader::kRepeat_TileMode == twoPointRadialGradient.fTileMode);
rileya@google.com589708b2012-07-26 20:04:23 +0000303 }
304 (*shadeProc)(fx, dx, fy, dy, b, db,
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000305 twoPointRadialGradient.fSr2D2, foura,
306 twoPointRadialGradient.fOneOverTwoA, posRoot,
rileya@google.com589708b2012-07-26 20:04:23 +0000307 dstC, cache, count);
308 } else { // perspective case
309 SkScalar dstX = SkIntToScalar(x);
310 SkScalar dstY = SkIntToScalar(y);
311 for (; count > 0; --count) {
312 SkPoint srcPt;
313 dstProc(fDstToIndex, dstX, dstY, &srcPt);
314 SkScalar fx = srcPt.fX;
315 SkScalar fy = srcPt.fY;
commit-bot@chromium.org87fcd952014-04-23 19:10:51 +0000316 SkScalar b = (SkScalarMul(twoPointRadialGradient.fDiff.fX, fx) +
317 SkScalarMul(twoPointRadialGradient.fDiff.fY, fy) -
318 twoPointRadialGradient.fStartRadius) * 2;
319 SkFixed t = two_point_radial(b, fx, fy, twoPointRadialGradient.fSr2D2, foura,
320 twoPointRadialGradient.fOneOverTwoA, posRoot);
rileya@google.com589708b2012-07-26 20:04:23 +0000321 SkFixed index = proc(t);
322 SkASSERT(index <= 0xFFFF);
323 *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift];
324 dstX += SK_Scalar1;
325 }
326 }
327}
328
commit-bot@chromium.org0f10f7b2014-03-13 18:02:17 +0000329#ifndef SK_IGNORE_TO_STRING
robertphillips@google.com76f9e932013-01-15 20:17:47 +0000330void SkTwoPointRadialGradient::toString(SkString* str) const {
331 str->append("SkTwoPointRadialGradient: (");
332
333 str->append("center1: (");
334 str->appendScalar(fCenter1.fX);
335 str->append(", ");
336 str->appendScalar(fCenter1.fY);
337 str->append(") radius1: ");
338 str->appendScalar(fRadius1);
339 str->append(" ");
340
341 str->append("center2: (");
342 str->appendScalar(fCenter2.fX);
343 str->append(", ");
344 str->appendScalar(fCenter2.fY);
345 str->append(") radius2: ");
346 str->appendScalar(fRadius2);
347 str->append(" ");
348
349 this->INHERITED::toString(str);
350
351 str->append(")");
352}
353#endif
354
rileya@google.com589708b2012-07-26 20:04:23 +0000355SkTwoPointRadialGradient::SkTwoPointRadialGradient(
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +0000356 SkReadBuffer& buffer)
rileya@google.com589708b2012-07-26 20:04:23 +0000357 : INHERITED(buffer),
358 fCenter1(buffer.readPoint()),
359 fCenter2(buffer.readPoint()),
360 fRadius1(buffer.readScalar()),
361 fRadius2(buffer.readScalar()) {
362 init();
363};
364
365void SkTwoPointRadialGradient::flatten(
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +0000366 SkWriteBuffer& buffer) const {
rileya@google.com589708b2012-07-26 20:04:23 +0000367 this->INHERITED::flatten(buffer);
368 buffer.writePoint(fCenter1);
369 buffer.writePoint(fCenter2);
370 buffer.writeScalar(fRadius1);
371 buffer.writeScalar(fRadius2);
372}
373
374void SkTwoPointRadialGradient::init() {
375 fDiff = fCenter1 - fCenter2;
376 fDiffRadius = fRadius2 - fRadius1;
377 // hack to avoid zero-divide for now
378 SkScalar inv = fDiffRadius ? SkScalarInvert(fDiffRadius) : 0;
379 fDiff.fX = SkScalarMul(fDiff.fX, inv);
380 fDiff.fY = SkScalarMul(fDiff.fY, inv);
381 fStartRadius = SkScalarMul(fRadius1, inv);
382 fSr2D2 = SkScalarSquare(fStartRadius);
383 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
384 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
385
386 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
387 fPtsToUnit.postScale(inv, inv);
388}
389
rileya@google.comd7cc6512012-07-27 14:00:39 +0000390/////////////////////////////////////////////////////////////////////
391
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000392#if SK_SUPPORT_GPU
393
bsalomon@google.com2eaaefd2012-10-29 19:51:22 +0000394#include "GrTBackendEffectFactory.h"
395
rileya@google.comd7cc6512012-07-27 14:00:39 +0000396// For brevity
397typedef GrGLUniformManager::UniformHandle UniformHandle;
rileya@google.comd7cc6512012-07-27 14:00:39 +0000398
bsalomon@google.com0707c292012-10-25 21:45:42 +0000399class GrGLRadial2Gradient : public GrGLGradientEffect {
rileya@google.comd7cc6512012-07-27 14:00:39 +0000400
401public:
402
bsalomon@google.comc7818882013-03-20 19:19:53 +0000403 GrGLRadial2Gradient(const GrBackendEffectFactory& factory, const GrDrawEffect&);
rileya@google.comd7cc6512012-07-27 14:00:39 +0000404 virtual ~GrGLRadial2Gradient() { }
405
bsalomon@google.comf78df332012-10-29 12:43:38 +0000406 virtual void emitCode(GrGLShaderBuilder*,
bsalomon@google.comc7818882013-03-20 19:19:53 +0000407 const GrDrawEffect&,
bsalomon@google.comf78df332012-10-29 12:43:38 +0000408 EffectKey,
bsalomon@google.comf78df332012-10-29 12:43:38 +0000409 const char* outputColor,
410 const char* inputColor,
bsalomon@google.com77af6802013-10-02 13:04:56 +0000411 const TransformedCoordsArray&,
bsalomon@google.comf78df332012-10-29 12:43:38 +0000412 const TextureSamplerArray&) SK_OVERRIDE;
bsalomon@google.comc7818882013-03-20 19:19:53 +0000413 virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
rileya@google.comd7cc6512012-07-27 14:00:39 +0000414
bsalomon@google.comc7818882013-03-20 19:19:53 +0000415 static EffectKey GenKey(const GrDrawEffect&, const GrGLCaps& caps);
rileya@google.comd7cc6512012-07-27 14:00:39 +0000416
417protected:
418
commit-bot@chromium.org5fd7d5c2013-10-04 01:20:09 +0000419 UniformHandle fParamUni;
rileya@google.comd7cc6512012-07-27 14:00:39 +0000420
421 const char* fVSVaryingName;
422 const char* fFSVaryingName;
423
424 bool fIsDegenerate;
425
426 // @{
427 /// Values last uploaded as uniforms
428
bsalomon@google.com81712882012-11-01 17:12:34 +0000429 SkScalar fCachedCenter;
430 SkScalar fCachedRadius;
rileya@google.comd7cc6512012-07-27 14:00:39 +0000431 bool fCachedPosRoot;
432
433 // @}
434
435private:
436
bsalomon@google.com0707c292012-10-25 21:45:42 +0000437 typedef GrGLGradientEffect INHERITED;
rileya@google.comd7cc6512012-07-27 14:00:39 +0000438
439};
440
rileya@google.com98e8b6d2012-07-31 20:38:06 +0000441/////////////////////////////////////////////////////////////////////
442
443class GrRadial2Gradient : public GrGradientEffect {
444public:
bsalomon@google.com0ac6af42013-01-16 15:16:18 +0000445 static GrEffectRef* Create(GrContext* ctx,
446 const SkTwoPointRadialGradient& shader,
447 const SkMatrix& matrix,
448 SkShader::TileMode tm) {
bsalomon@google.com6340a412013-01-22 19:55:59 +0000449 AutoEffectUnref effect(SkNEW_ARGS(GrRadial2Gradient, (ctx, shader, matrix, tm)));
bsalomon@google.coma1ebbe42013-01-16 15:51:47 +0000450 return CreateEffectRef(effect);
bsalomon@google.com0ac6af42013-01-16 15:16:18 +0000451 }
rileya@google.com98e8b6d2012-07-31 20:38:06 +0000452
rileya@google.com98e8b6d2012-07-31 20:38:06 +0000453 virtual ~GrRadial2Gradient() { }
454
455 static const char* Name() { return "Two-Point Radial Gradient"; }
bsalomon@google.com396e61f2012-10-25 19:00:29 +0000456 virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
457 return GrTBackendEffectFactory<GrRadial2Gradient>::getInstance();
rileya@google.com98e8b6d2012-07-31 20:38:06 +0000458 }
rileya@google.com98e8b6d2012-07-31 20:38:06 +0000459
460 // The radial gradient parameters can collapse to a linear (instead of quadratic) equation.
bsalomon@google.com81712882012-11-01 17:12:34 +0000461 bool isDegenerate() const { return SK_Scalar1 == fCenterX1; }
462 SkScalar center() const { return fCenterX1; }
463 SkScalar radius() const { return fRadius0; }
rileya@google.com98e8b6d2012-07-31 20:38:06 +0000464 bool isPosRoot() const { return SkToBool(fPosRoot); }
465
bsalomon@google.com422e81a2012-10-25 14:11:03 +0000466 typedef GrGLRadial2Gradient GLEffect;
rileya@google.com98e8b6d2012-07-31 20:38:06 +0000467
468private:
bsalomon@google.com8a252f72013-01-22 20:35:13 +0000469 virtual bool onIsEqual(const GrEffect& sBase) const SK_OVERRIDE {
bsalomon@google.com6340a412013-01-22 19:55:59 +0000470 const GrRadial2Gradient& s = CastEffect<GrRadial2Gradient>(sBase);
bsalomon@google.com70db51f2013-01-17 18:39:59 +0000471 return (INHERITED::onIsEqual(sBase) &&
bsalomon@google.com68b58c92013-01-17 16:50:08 +0000472 this->fCenterX1 == s.fCenterX1 &&
473 this->fRadius0 == s.fRadius0 &&
474 this->fPosRoot == s.fPosRoot);
475 }
476
bsalomon@google.com0ac6af42013-01-16 15:16:18 +0000477 GrRadial2Gradient(GrContext* ctx,
478 const SkTwoPointRadialGradient& shader,
479 const SkMatrix& matrix,
480 SkShader::TileMode tm)
481 : INHERITED(ctx, shader, matrix, tm)
482 , fCenterX1(shader.getCenterX1())
483 , fRadius0(shader.getStartRadius())
commit-bot@chromium.org5fd7d5c2013-10-04 01:20:09 +0000484 , fPosRoot(shader.getDiffRadius() < 0) {
485 // We pass the linear part of the quadratic as a varying.
486 // float b = 2.0 * (fCenterX1 * x - fRadius0 * z)
487 fBTransform = this->getCoordTransform();
488 SkMatrix& bMatrix = *fBTransform.accessMatrix();
489 bMatrix[SkMatrix::kMScaleX] = 2 * (SkScalarMul(fCenterX1, bMatrix[SkMatrix::kMScaleX]) -
490 SkScalarMul(fRadius0, bMatrix[SkMatrix::kMPersp0]));
491 bMatrix[SkMatrix::kMSkewX] = 2 * (SkScalarMul(fCenterX1, bMatrix[SkMatrix::kMSkewX]) -
492 SkScalarMul(fRadius0, bMatrix[SkMatrix::kMPersp1]));
493 bMatrix[SkMatrix::kMTransX] = 2 * (SkScalarMul(fCenterX1, bMatrix[SkMatrix::kMTransX]) -
494 SkScalarMul(fRadius0, bMatrix[SkMatrix::kMPersp2]));
495 this->addCoordTransform(&fBTransform);
496 }
bsalomon@google.com0ac6af42013-01-16 15:16:18 +0000497
bsalomon@google.comf271cc72012-10-24 19:35:13 +0000498 GR_DECLARE_EFFECT_TEST;
rileya@google.com98e8b6d2012-07-31 20:38:06 +0000499
500 // @{
501 // Cache of values - these can change arbitrarily, EXCEPT
502 // we shouldn't change between degenerate and non-degenerate?!
503
commit-bot@chromium.org5fd7d5c2013-10-04 01:20:09 +0000504 GrCoordTransform fBTransform;
505 SkScalar fCenterX1;
506 SkScalar fRadius0;
507 SkBool8 fPosRoot;
rileya@google.com98e8b6d2012-07-31 20:38:06 +0000508
509 // @}
510
511 typedef GrGradientEffect INHERITED;
512};
513
514/////////////////////////////////////////////////////////////////////
515
bsalomon@google.comf271cc72012-10-24 19:35:13 +0000516GR_DEFINE_EFFECT_TEST(GrRadial2Gradient);
bsalomon@google.comd4726202012-08-03 14:34:46 +0000517
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +0000518GrEffectRef* GrRadial2Gradient::TestCreate(SkRandom* random,
bsalomon@google.com0ac6af42013-01-16 15:16:18 +0000519 GrContext* context,
bsalomon@google.comc26d94f2013-03-25 18:19:00 +0000520 const GrDrawTargetCaps&,
bsalomon@google.com0ac6af42013-01-16 15:16:18 +0000521 GrTexture**) {
bsalomon@google.comd4726202012-08-03 14:34:46 +0000522 SkPoint center1 = {random->nextUScalar1(), random->nextUScalar1()};
523 SkScalar radius1 = random->nextUScalar1();
524 SkPoint center2;
525 SkScalar radius2;
526 do {
bsalomon@google.comfb883bf2012-12-11 15:32:04 +0000527 center2.set(random->nextUScalar1(), random->nextUScalar1());
bsalomon@google.comd4726202012-08-03 14:34:46 +0000528 radius2 = random->nextUScalar1 ();
bsalomon@google.come197cbf2013-01-14 16:46:26 +0000529 // There is a bug in two point radial gradients with identical radii
bsalomon@google.comd4726202012-08-03 14:34:46 +0000530 } while (radius1 == radius2);
531
532 SkColor colors[kMaxRandomGradientColors];
533 SkScalar stopsArray[kMaxRandomGradientColors];
534 SkScalar* stops = stopsArray;
535 SkShader::TileMode tm;
536 int colorCount = RandomGradientParams(random, colors, &stops, &tm);
537 SkAutoTUnref<SkShader> shader(SkGradientShader::CreateTwoPointRadial(center1, radius1,
538 center2, radius2,
539 colors, stops, colorCount,
540 tm));
bsalomon@google.come197cbf2013-01-14 16:46:26 +0000541 SkPaint paint;
542 return shader->asNewEffect(context, paint);
bsalomon@google.comd4726202012-08-03 14:34:46 +0000543}
544
545/////////////////////////////////////////////////////////////////////
546
bsalomon@google.com6340a412013-01-22 19:55:59 +0000547GrGLRadial2Gradient::GrGLRadial2Gradient(const GrBackendEffectFactory& factory,
bsalomon@google.comc7818882013-03-20 19:19:53 +0000548 const GrDrawEffect& drawEffect)
rileya@google.comd7cc6512012-07-27 14:00:39 +0000549 : INHERITED(factory)
rileya@google.comd7cc6512012-07-27 14:00:39 +0000550 , fVSVaryingName(NULL)
551 , fFSVaryingName(NULL)
bsalomon@google.com81712882012-11-01 17:12:34 +0000552 , fCachedCenter(SK_ScalarMax)
553 , fCachedRadius(-SK_ScalarMax)
rileya@google.comd7cc6512012-07-27 14:00:39 +0000554 , fCachedPosRoot(0) {
555
bsalomon@google.comc7818882013-03-20 19:19:53 +0000556 const GrRadial2Gradient& data = drawEffect.castEffect<GrRadial2Gradient>();
rileya@google.comd7cc6512012-07-27 14:00:39 +0000557 fIsDegenerate = data.isDegenerate();
558}
559
bsalomon@google.comf78df332012-10-29 12:43:38 +0000560void GrGLRadial2Gradient::emitCode(GrGLShaderBuilder* builder,
bsalomon@google.comc7818882013-03-20 19:19:53 +0000561 const GrDrawEffect& drawEffect,
bsalomon@google.comd8b5fac2012-11-01 17:02:46 +0000562 EffectKey key,
bsalomon@google.comf78df332012-10-29 12:43:38 +0000563 const char* outputColor,
564 const char* inputColor,
bsalomon@google.com77af6802013-10-02 13:04:56 +0000565 const TransformedCoordsArray& coords,
bsalomon@google.comf78df332012-10-29 12:43:38 +0000566 const TextureSamplerArray& samplers) {
567
bsalomon@google.com82d12232013-09-09 15:36:26 +0000568 this->emitUniforms(builder, key);
commit-bot@chromium.org5fd7d5c2013-10-04 01:20:09 +0000569 fParamUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_Visibility,
570 kFloat_GrSLType, "Radial2FSParams", 6);
rileya@google.comd7cc6512012-07-27 14:00:39 +0000571
commit-bot@chromium.org5fd7d5c2013-10-04 01:20:09 +0000572 SkString cName("c");
573 SkString ac4Name("ac4");
574 SkString rootName("root");
575 SkString t;
576 SkString p0;
577 SkString p1;
578 SkString p2;
579 SkString p3;
580 SkString p4;
581 SkString p5;
582 builder->getUniformVariable(fParamUni).appendArrayAccess(0, &p0);
583 builder->getUniformVariable(fParamUni).appendArrayAccess(1, &p1);
584 builder->getUniformVariable(fParamUni).appendArrayAccess(2, &p2);
585 builder->getUniformVariable(fParamUni).appendArrayAccess(3, &p3);
586 builder->getUniformVariable(fParamUni).appendArrayAccess(4, &p4);
587 builder->getUniformVariable(fParamUni).appendArrayAccess(5, &p5);
588
589 // We interpolate the linear component in coords[1].
590 SkASSERT(coords[0].type() == coords[1].type());
591 const char* coords2D;
592 SkString bVar;
593 if (kVec3f_GrSLType == coords[0].type()) {
594 builder->fsCodeAppendf("\tvec3 interpolants = vec3(%s.xy, %s.x) / %s.z;\n",
595 coords[0].c_str(), coords[1].c_str(), coords[0].c_str());
596 coords2D = "interpolants.xy";
597 bVar = "interpolants.z";
598 } else {
599 coords2D = coords[0].c_str();
600 bVar.printf("%s.x", coords[1].c_str());
rileya@google.comd7cc6512012-07-27 14:00:39 +0000601 }
rileya@google.comd7cc6512012-07-27 14:00:39 +0000602
commit-bot@chromium.org5fd7d5c2013-10-04 01:20:09 +0000603 // c = (x^2)+(y^2) - params[4]
604 builder->fsCodeAppendf("\tfloat %s = dot(%s, %s) - %s;\n",
605 cName.c_str(), coords2D, coords2D, p4.c_str());
rileya@google.comd7cc6512012-07-27 14:00:39 +0000606
commit-bot@chromium.org5fd7d5c2013-10-04 01:20:09 +0000607 // If we aren't degenerate, emit some extra code, and accept a slightly
608 // more complex coord.
609 if (!fIsDegenerate) {
610
611 // ac4 = 4.0 * params[0] * c
612 builder->fsCodeAppendf("\tfloat %s = %s * 4.0 * %s;\n",
613 ac4Name.c_str(), p0.c_str(),
614 cName.c_str());
615
616 // root = sqrt(b^2-4ac)
617 // (abs to avoid exception due to fp precision)
618 builder->fsCodeAppendf("\tfloat %s = sqrt(abs(%s*%s - %s));\n",
619 rootName.c_str(), bVar.c_str(), bVar.c_str(),
620 ac4Name.c_str());
621
622 // t is: (-b + params[5] * sqrt(b^2-4ac)) * params[1]
623 t.printf("(-%s + %s * %s) * %s", bVar.c_str(), p5.c_str(),
624 rootName.c_str(), p1.c_str());
625 } else {
626 // t is: -c/b
627 t.printf("-%s / %s", cName.c_str(), bVar.c_str());
rileya@google.comd7cc6512012-07-27 14:00:39 +0000628 }
629
commit-bot@chromium.org5fd7d5c2013-10-04 01:20:09 +0000630 this->emitColor(builder, t.c_str(), key, outputColor, inputColor, samplers);
rileya@google.comd7cc6512012-07-27 14:00:39 +0000631}
632
bsalomon@google.comc7818882013-03-20 19:19:53 +0000633void GrGLRadial2Gradient::setData(const GrGLUniformManager& uman,
634 const GrDrawEffect& drawEffect) {
635 INHERITED::setData(uman, drawEffect);
636 const GrRadial2Gradient& data = drawEffect.castEffect<GrRadial2Gradient>();
commit-bot@chromium.org96ae6882013-08-14 12:09:00 +0000637 SkASSERT(data.isDegenerate() == fIsDegenerate);
bsalomon@google.com81712882012-11-01 17:12:34 +0000638 SkScalar centerX1 = data.center();
639 SkScalar radius0 = data.radius();
rileya@google.comd7cc6512012-07-27 14:00:39 +0000640 if (fCachedCenter != centerX1 ||
641 fCachedRadius != radius0 ||
642 fCachedPosRoot != data.isPosRoot()) {
643
bsalomon@google.com81712882012-11-01 17:12:34 +0000644 SkScalar a = SkScalarMul(centerX1, centerX1) - SK_Scalar1;
rileya@google.comd7cc6512012-07-27 14:00:39 +0000645
646 // When we're in the degenerate (linear) case, the second
647 // value will be INF but the program doesn't read it. (We
648 // use the same 6 uniforms even though we don't need them
649 // all in the linear case just to keep the code complexity
650 // down).
651 float values[6] = {
bsalomon@google.com81712882012-11-01 17:12:34 +0000652 SkScalarToFloat(a),
653 1 / (2.f * SkScalarToFloat(a)),
654 SkScalarToFloat(centerX1),
655 SkScalarToFloat(radius0),
656 SkScalarToFloat(SkScalarMul(radius0, radius0)),
rileya@google.comd7cc6512012-07-27 14:00:39 +0000657 data.isPosRoot() ? 1.f : -1.f
658 };
659
commit-bot@chromium.orgd3baf202013-11-07 22:06:08 +0000660 uman.set1fv(fParamUni, 6, values);
rileya@google.comd7cc6512012-07-27 14:00:39 +0000661 fCachedCenter = centerX1;
662 fCachedRadius = radius0;
663 fCachedPosRoot = data.isPosRoot();
664 }
665}
666
bsalomon@google.comc7818882013-03-20 19:19:53 +0000667GrGLEffect::EffectKey GrGLRadial2Gradient::GenKey(const GrDrawEffect& drawEffect,
668 const GrGLCaps&) {
bsalomon@google.comd8b5fac2012-11-01 17:02:46 +0000669 enum {
bsalomon@google.com82d12232013-09-09 15:36:26 +0000670 kIsDegenerate = 1 << kBaseKeyBitCnt,
bsalomon@google.comd8b5fac2012-11-01 17:02:46 +0000671 };
672
bsalomon@google.com82d12232013-09-09 15:36:26 +0000673 EffectKey key = GenBaseGradientKey(drawEffect);
bsalomon@google.comc7818882013-03-20 19:19:53 +0000674 if (drawEffect.castEffect<GrRadial2Gradient>().isDegenerate()) {
bsalomon@google.comd8b5fac2012-11-01 17:02:46 +0000675 key |= kIsDegenerate;
676 }
677 return key;
rileya@google.com98e8b6d2012-07-31 20:38:06 +0000678}
rileya@google.comd7cc6512012-07-27 14:00:39 +0000679
680/////////////////////////////////////////////////////////////////////
681
bsalomon@google.com0ac6af42013-01-16 15:16:18 +0000682GrEffectRef* SkTwoPointRadialGradient::asNewEffect(GrContext* context, const SkPaint&) const {
bsalomon@google.com00835cc2013-01-14 17:07:22 +0000683 SkASSERT(NULL != context);
bsalomon@google.comf94b3a42012-10-31 18:09:01 +0000684 // invert the localM, translate to center1 (fPtsToUni), rotate so center2 is on x axis.
bsalomon@google.comdfdb7e52012-10-16 15:19:45 +0000685 SkMatrix matrix;
bsalomon@google.comf94b3a42012-10-31 18:09:01 +0000686 if (!this->getLocalMatrix().invert(&matrix)) {
humper@google.com84831ac2013-01-14 22:09:54 +0000687 return NULL;
bsalomon@google.comf94b3a42012-10-31 18:09:01 +0000688 }
689 matrix.postConcat(fPtsToUnit);
690
691 SkScalar diffLen = fDiff.length();
rileya@google.com98e8b6d2012-07-31 20:38:06 +0000692 if (0 != diffLen) {
693 SkScalar invDiffLen = SkScalarInvert(diffLen);
bsalomon@google.comf94b3a42012-10-31 18:09:01 +0000694 SkMatrix rot;
695 rot.setSinCos(-SkScalarMul(invDiffLen, fDiff.fY),
696 SkScalarMul(invDiffLen, fDiff.fX));
697 matrix.postConcat(rot);
bsalomon@google.comdfdb7e52012-10-16 15:19:45 +0000698 }
699
bsalomon@google.com0ac6af42013-01-16 15:16:18 +0000700 return GrRadial2Gradient::Create(context, *this, matrix, fTileMode);
rileya@google.comd7cc6512012-07-27 14:00:39 +0000701}
702
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000703#else
704
bsalomon@google.com5d2cd202013-01-16 15:31:06 +0000705GrEffectRef* SkTwoPointRadialGradient::asNewEffect(GrContext*, const SkPaint&) const {
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000706 SkDEBUGFAIL("Should not call in GPU-less build");
bsalomon@google.come197cbf2013-01-14 16:46:26 +0000707 return NULL;
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000708}
709
710#endif