blob: 27eb5aae4f00c20f04e67f415095e678dabcc3da [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
168SkTwoPointRadialGradient::SkTwoPointRadialGradient(
169 const SkPoint& start, SkScalar startRadius,
170 const SkPoint& end, SkScalar endRadius,
171 const SkColor colors[], const SkScalar pos[],
172 int colorCount, SkShader::TileMode mode,
173 SkUnitMapper* mapper)
174 : SkGradientShaderBase(colors, pos, colorCount, mode, mapper),
175 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) {
187 this->commonAsABitmap(bitmap);
188 }
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
223GrCustomStage* SkTwoPointRadialGradient::asNewCustomStage(
224 GrContext* context, GrSamplerState* sampler) const {
225 SkASSERT(NULL != context && NULL != sampler);
226 SkScalar diffLen = fDiff.length();
227 if (0 != diffLen) {
228 SkScalar invDiffLen = SkScalarInvert(diffLen);
229 sampler->matrix()->setSinCos(-SkScalarMul(invDiffLen, fDiff.fY),
230 SkScalarMul(invDiffLen, fDiff.fX));
231 } else {
232 sampler->matrix()->reset();
233 }
234 sampler->matrix()->preConcat(fPtsToUnit);
235 sampler->textureParams()->setTileModeX(fTileMode);
236 sampler->textureParams()->setTileModeY(kClamp_TileMode);
237 sampler->textureParams()->setBilerp(true);
238 return SkNEW_ARGS(GrRadial2Gradient, (context, *this, sampler,
239 diffLen, fStartRadius, fDiffRadius));
240}
241
242void SkTwoPointRadialGradient::shadeSpan(int x, int y, SkPMColor* dstCParam,
243 int count) {
244 SkASSERT(count > 0);
245
246 SkPMColor* SK_RESTRICT dstC = dstCParam;
247
248 // Zero difference between radii: fill with transparent black.
249 if (fDiffRadius == 0) {
250 sk_bzero(dstC, count * sizeof(*dstC));
251 return;
252 }
253 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
254 TileProc proc = fTileProc;
255 const SkPMColor* SK_RESTRICT cache = this->getCache32();
256
257 SkScalar foura = fA * 4;
258 bool posRoot = fDiffRadius < 0;
259 if (fDstToIndexClass != kPerspective_MatrixClass) {
260 SkPoint srcPt;
261 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
262 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
263 SkScalar dx, fx = srcPt.fX;
264 SkScalar dy, fy = srcPt.fY;
265
266 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
267 SkFixed fixedX, fixedY;
268 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
269 dx = SkFixedToScalar(fixedX);
270 dy = SkFixedToScalar(fixedY);
271 } else {
272 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
273 dx = fDstToIndex.getScaleX();
274 dy = fDstToIndex.getSkewY();
275 }
276 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
277 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
278 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
279 SkScalarMul(fDiff.fY, dy)) * 2;
280
281 TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
282 if (SkShader::kClamp_TileMode == fTileMode) {
283 shadeProc = shadeSpan_twopoint_clamp;
284 } else if (SkShader::kMirror_TileMode == fTileMode) {
285 shadeProc = shadeSpan_twopoint_mirror;
286 } else {
287 SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
288 }
289 (*shadeProc)(fx, dx, fy, dy, b, db,
290 fSr2D2, foura, fOneOverTwoA, posRoot,
291 dstC, cache, count);
292 } else { // perspective case
293 SkScalar dstX = SkIntToScalar(x);
294 SkScalar dstY = SkIntToScalar(y);
295 for (; count > 0; --count) {
296 SkPoint srcPt;
297 dstProc(fDstToIndex, dstX, dstY, &srcPt);
298 SkScalar fx = srcPt.fX;
299 SkScalar fy = srcPt.fY;
300 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
301 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
302 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
303 fOneOverTwoA, posRoot);
304 SkFixed index = proc(t);
305 SkASSERT(index <= 0xFFFF);
306 *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift];
307 dstX += SK_Scalar1;
308 }
309 }
310}
311
312bool SkTwoPointRadialGradient::setContext(
313 const SkBitmap& device,
314 const SkPaint& paint,
315 const SkMatrix& matrix){
316 if (!this->INHERITED::setContext(device, paint, matrix)) {
317 return false;
318 }
319
320 // For now, we might have divided by zero, so detect that
321 if (0 == fDiffRadius) {
322 return false;
323 }
324
325 // we don't have a span16 proc
326 fFlags &= ~kHasSpan16_Flag;
327 return true;
328}
329
330SkTwoPointRadialGradient::SkTwoPointRadialGradient(
331 SkFlattenableReadBuffer& buffer)
332 : INHERITED(buffer),
333 fCenter1(buffer.readPoint()),
334 fCenter2(buffer.readPoint()),
335 fRadius1(buffer.readScalar()),
336 fRadius2(buffer.readScalar()) {
337 init();
338};
339
340void SkTwoPointRadialGradient::flatten(
341 SkFlattenableWriteBuffer& buffer) const {
342 this->INHERITED::flatten(buffer);
343 buffer.writePoint(fCenter1);
344 buffer.writePoint(fCenter2);
345 buffer.writeScalar(fRadius1);
346 buffer.writeScalar(fRadius2);
347}
348
349void SkTwoPointRadialGradient::init() {
350 fDiff = fCenter1 - fCenter2;
351 fDiffRadius = fRadius2 - fRadius1;
352 // hack to avoid zero-divide for now
353 SkScalar inv = fDiffRadius ? SkScalarInvert(fDiffRadius) : 0;
354 fDiff.fX = SkScalarMul(fDiff.fX, inv);
355 fDiff.fY = SkScalarMul(fDiff.fY, inv);
356 fStartRadius = SkScalarMul(fRadius1, inv);
357 fSr2D2 = SkScalarSquare(fStartRadius);
358 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
359 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
360
361 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
362 fPtsToUnit.postScale(inv, inv);
363}
364