blob: 74a581703c95030b1deb30808d36a6b6cca4a3df [file] [log] [blame]
rileya@google.com589708b2012-07-26 20:04:23 +00001/*
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
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +000010#include "SkTwoPointConicalGradient_gpu.h"
11
rileya@google.com589708b2012-07-26 20:04:23 +000012static int valid_divide(float numer, float denom, float* ratio) {
13 SkASSERT(ratio);
14 if (0 == denom) {
15 return 0;
16 }
17 *ratio = numer / denom;
18 return 1;
19}
20
21// Return the number of distinct real roots, and write them into roots[] in
22// ascending order
23static int find_quad_roots(float A, float B, float C, float roots[2]) {
24 SkASSERT(roots);
rmistry@google.comfbfcd562012-08-23 18:09:54 +000025
rileya@google.com589708b2012-07-26 20:04:23 +000026 if (A == 0) {
27 return valid_divide(-C, B, roots);
28 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +000029
rileya@google.com589708b2012-07-26 20:04:23 +000030 float R = B*B - 4*A*C;
31 if (R < 0) {
32 return 0;
33 }
34 R = sk_float_sqrt(R);
35
36#if 1
37 float Q = B;
38 if (Q < 0) {
39 Q -= R;
40 } else {
41 Q += R;
42 }
43#else
44 // on 10.6 this was much slower than the above branch :(
45 float Q = B + copysignf(R, B);
46#endif
47 Q *= -0.5f;
48 if (0 == Q) {
49 roots[0] = 0;
50 return 1;
51 }
52
53 float r0 = Q / A;
54 float r1 = C / Q;
55 roots[0] = r0 < r1 ? r0 : r1;
56 roots[1] = r0 > r1 ? r0 : r1;
57 return 2;
58}
59
60static float lerp(float x, float dx, float t) {
61 return x + t * dx;
62}
63
64static float sqr(float x) { return x * x; }
65
66void TwoPtRadial::init(const SkPoint& center0, SkScalar rad0,
67 const SkPoint& center1, SkScalar rad1) {
68 fCenterX = SkScalarToFloat(center0.fX);
69 fCenterY = SkScalarToFloat(center0.fY);
70 fDCenterX = SkScalarToFloat(center1.fX) - fCenterX;
71 fDCenterY = SkScalarToFloat(center1.fY) - fCenterY;
72 fRadius = SkScalarToFloat(rad0);
73 fDRadius = SkScalarToFloat(rad1) - fRadius;
74
75 fA = sqr(fDCenterX) + sqr(fDCenterY) - sqr(fDRadius);
76 fRadius2 = sqr(fRadius);
77 fRDR = fRadius * fDRadius;
78}
79
80void TwoPtRadial::setup(SkScalar fx, SkScalar fy, SkScalar dfx, SkScalar dfy) {
81 fRelX = SkScalarToFloat(fx) - fCenterX;
82 fRelY = SkScalarToFloat(fy) - fCenterY;
83 fIncX = SkScalarToFloat(dfx);
84 fIncY = SkScalarToFloat(dfy);
85 fB = -2 * (fDCenterX * fRelX + fDCenterY * fRelY + fRDR);
86 fDB = -2 * (fDCenterX * fIncX + fDCenterY * fIncY);
87}
88
89SkFixed TwoPtRadial::nextT() {
90 float roots[2];
rmistry@google.comfbfcd562012-08-23 18:09:54 +000091
rileya@google.com589708b2012-07-26 20:04:23 +000092 float C = sqr(fRelX) + sqr(fRelY) - fRadius2;
93 int countRoots = find_quad_roots(fA, fB, C, roots);
94
95 fRelX += fIncX;
96 fRelY += fIncY;
97 fB += fDB;
98
99 if (0 == countRoots) {
100 return kDontDrawT;
101 }
102
103 // Prefer the bigger t value if both give a radius(t) > 0
104 // find_quad_roots returns the values sorted, so we start with the last
105 float t = roots[countRoots - 1];
106 float r = lerp(fRadius, fDRadius, t);
107 if (r <= 0) {
108 t = roots[0]; // might be the same as roots[countRoots-1]
109 r = lerp(fRadius, fDRadius, t);
110 if (r <= 0) {
111 return kDontDrawT;
112 }
113 }
114 return SkFloatToFixed(t);
115}
116
reed@google.com60040292013-02-04 18:21:23 +0000117typedef void (*TwoPointConicalProc)(TwoPtRadial* rec, SkPMColor* dstC,
118 const SkPMColor* cache, int toggle, int count);
rileya@google.com589708b2012-07-26 20:04:23 +0000119
120static void twopoint_clamp(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC,
reed@google.com60040292013-02-04 18:21:23 +0000121 const SkPMColor* SK_RESTRICT cache, int toggle,
122 int count) {
rileya@google.com589708b2012-07-26 20:04:23 +0000123 for (; count > 0; --count) {
124 SkFixed t = rec->nextT();
125 if (TwoPtRadial::DontDrawT(t)) {
126 *dstC++ = 0;
127 } else {
128 SkFixed index = SkClampMax(t, 0xFFFF);
129 SkASSERT(index <= 0xFFFF);
reed@google.com60040292013-02-04 18:21:23 +0000130 *dstC++ = cache[toggle +
131 (index >> SkGradientShaderBase::kCache32Shift)];
rileya@google.com589708b2012-07-26 20:04:23 +0000132 }
reed@google.com60040292013-02-04 18:21:23 +0000133 toggle = next_dither_toggle(toggle);
rileya@google.com589708b2012-07-26 20:04:23 +0000134 }
135}
136
137static void twopoint_repeat(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC,
reed@google.com60040292013-02-04 18:21:23 +0000138 const SkPMColor* SK_RESTRICT cache, int toggle,
139 int count) {
rileya@google.com589708b2012-07-26 20:04:23 +0000140 for (; count > 0; --count) {
141 SkFixed t = rec->nextT();
142 if (TwoPtRadial::DontDrawT(t)) {
143 *dstC++ = 0;
144 } else {
145 SkFixed index = repeat_tileproc(t);
146 SkASSERT(index <= 0xFFFF);
reed@google.com60040292013-02-04 18:21:23 +0000147 *dstC++ = cache[toggle +
148 (index >> SkGradientShaderBase::kCache32Shift)];
rileya@google.com589708b2012-07-26 20:04:23 +0000149 }
reed@google.com60040292013-02-04 18:21:23 +0000150 toggle = next_dither_toggle(toggle);
rileya@google.com589708b2012-07-26 20:04:23 +0000151 }
152}
153
154static void twopoint_mirror(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC,
reed@google.com60040292013-02-04 18:21:23 +0000155 const SkPMColor* SK_RESTRICT cache, int toggle,
156 int count) {
rileya@google.com589708b2012-07-26 20:04:23 +0000157 for (; count > 0; --count) {
158 SkFixed t = rec->nextT();
159 if (TwoPtRadial::DontDrawT(t)) {
160 *dstC++ = 0;
161 } else {
162 SkFixed index = mirror_tileproc(t);
163 SkASSERT(index <= 0xFFFF);
reed@google.com60040292013-02-04 18:21:23 +0000164 *dstC++ = cache[toggle +
165 (index >> SkGradientShaderBase::kCache32Shift)];
rileya@google.com589708b2012-07-26 20:04:23 +0000166 }
reed@google.com60040292013-02-04 18:21:23 +0000167 toggle = next_dither_toggle(toggle);
rileya@google.com589708b2012-07-26 20:04:23 +0000168 }
169}
170
171void SkTwoPointConicalGradient::init() {
172 fRec.init(fCenter1, fRadius1, fCenter2, fRadius2);
173 fPtsToUnit.reset();
174}
175
rileya@google.com98e8b6d2012-07-31 20:38:06 +0000176/////////////////////////////////////////////////////////////////////
177
rileya@google.com589708b2012-07-26 20:04:23 +0000178SkTwoPointConicalGradient::SkTwoPointConicalGradient(
reed@google.com3d3a8602013-05-24 14:58:44 +0000179 const SkPoint& start, SkScalar startRadius,
180 const SkPoint& end, SkScalar endRadius,
181 const Descriptor& desc)
reed@google.com437d6eb2013-05-23 19:03:05 +0000182 : SkGradientShaderBase(desc),
rileya@google.com589708b2012-07-26 20:04:23 +0000183 fCenter1(start),
184 fCenter2(end),
185 fRadius1(startRadius),
186 fRadius2(endRadius) {
187 // this is degenerate, and should be caught by our caller
188 SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
189 this->init();
190}
191
commit-bot@chromium.org3fbab822013-03-20 00:49:57 +0000192bool SkTwoPointConicalGradient::isOpaque() const {
robertphillips@google.comcb6d97c2013-07-09 13:50:09 +0000193 // Because areas outside the cone are left untouched, we cannot treat the
194 // shader as opaque even if the gradient itself is opaque.
195 // TODO(junov): Compute whether the cone fills the plane crbug.com/222380
196 return false;
commit-bot@chromium.org3fbab822013-03-20 00:49:57 +0000197}
198
rileya@google.com589708b2012-07-26 20:04:23 +0000199void SkTwoPointConicalGradient::shadeSpan(int x, int y, SkPMColor* dstCParam,
200 int count) {
reed@google.com60040292013-02-04 18:21:23 +0000201 int toggle = init_dither_toggle(x, y);
reed@google.com60040292013-02-04 18:21:23 +0000202
rileya@google.com589708b2012-07-26 20:04:23 +0000203 SkASSERT(count > 0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000204
rileya@google.com589708b2012-07-26 20:04:23 +0000205 SkPMColor* SK_RESTRICT dstC = dstCParam;
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000206
rileya@google.com589708b2012-07-26 20:04:23 +0000207 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
bsalomon@google.com100abf42012-09-05 17:40:04 +0000208
rileya@google.com589708b2012-07-26 20:04:23 +0000209 const SkPMColor* SK_RESTRICT cache = this->getCache32();
210
reed@google.com60040292013-02-04 18:21:23 +0000211 TwoPointConicalProc shadeProc = twopoint_repeat;
rileya@google.com589708b2012-07-26 20:04:23 +0000212 if (SkShader::kClamp_TileMode == fTileMode) {
213 shadeProc = twopoint_clamp;
214 } else if (SkShader::kMirror_TileMode == fTileMode) {
215 shadeProc = twopoint_mirror;
216 } else {
217 SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
218 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000219
rileya@google.com589708b2012-07-26 20:04:23 +0000220 if (fDstToIndexClass != kPerspective_MatrixClass) {
221 SkPoint srcPt;
222 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
223 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
224 SkScalar dx, fx = srcPt.fX;
225 SkScalar dy, fy = srcPt.fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000226
rileya@google.com589708b2012-07-26 20:04:23 +0000227 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
228 SkFixed fixedX, fixedY;
229 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
230 dx = SkFixedToScalar(fixedX);
231 dy = SkFixedToScalar(fixedY);
232 } else {
233 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
234 dx = fDstToIndex.getScaleX();
235 dy = fDstToIndex.getSkewY();
236 }
237
238 fRec.setup(fx, fy, dx, dy);
reed@google.com60040292013-02-04 18:21:23 +0000239 (*shadeProc)(&fRec, dstC, cache, toggle, count);
rileya@google.com589708b2012-07-26 20:04:23 +0000240 } else { // perspective case
mike@reedtribe.org139a2352013-11-14 20:15:51 +0000241 SkScalar dstX = SkIntToScalar(x) + SK_ScalarHalf;
242 SkScalar dstY = SkIntToScalar(y) + SK_ScalarHalf;
rileya@google.com589708b2012-07-26 20:04:23 +0000243 for (; count > 0; --count) {
244 SkPoint srcPt;
245 dstProc(fDstToIndex, dstX, dstY, &srcPt);
rileya@google.com589708b2012-07-26 20:04:23 +0000246 fRec.setup(srcPt.fX, srcPt.fY, 0, 0);
reed@google.com60040292013-02-04 18:21:23 +0000247 (*shadeProc)(&fRec, dstC, cache, toggle, 1);
mike@reedtribe.org139a2352013-11-14 20:15:51 +0000248
249 dstX += SK_Scalar1;
reed@google.com60040292013-02-04 18:21:23 +0000250 toggle = next_dither_toggle(toggle);
mike@reedtribe.org139a2352013-11-14 20:15:51 +0000251 dstC += 1;
rileya@google.com589708b2012-07-26 20:04:23 +0000252 }
253 }
254}
255
256bool SkTwoPointConicalGradient::setContext(const SkBitmap& device,
257 const SkPaint& paint,
258 const SkMatrix& matrix) {
259 if (!this->INHERITED::setContext(device, paint, matrix)) {
260 return false;
261 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000262
rileya@google.com589708b2012-07-26 20:04:23 +0000263 // we don't have a span16 proc
264 fFlags &= ~kHasSpan16_Flag;
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000265
rileya@google.com589708b2012-07-26 20:04:23 +0000266 // in general, we might discard based on computed-radius, so clear
267 // this flag (todo: sometimes we can detect that we never discard...)
268 fFlags &= ~kOpaqueAlpha_Flag;
269
270 return true;
271}
272
273SkShader::BitmapType SkTwoPointConicalGradient::asABitmap(
274 SkBitmap* bitmap, SkMatrix* matrix, SkShader::TileMode* xy) const {
275 SkPoint diff = fCenter2 - fCenter1;
rileya@google.com589708b2012-07-26 20:04:23 +0000276 SkScalar diffLen = 0;
277
278 if (bitmap) {
rileya@google.com1c6d64b2012-07-27 15:49:05 +0000279 this->getGradientTableBitmap(bitmap);
rileya@google.com589708b2012-07-26 20:04:23 +0000280 }
281 if (matrix) {
282 diffLen = diff.length();
283 }
284 if (matrix) {
285 if (diffLen) {
286 SkScalar invDiffLen = SkScalarInvert(diffLen);
287 // rotate to align circle centers with the x-axis
288 matrix->setSinCos(-SkScalarMul(invDiffLen, diff.fY),
289 SkScalarMul(invDiffLen, diff.fX));
290 } else {
291 matrix->reset();
292 }
293 matrix->preTranslate(-fCenter1.fX, -fCenter1.fY);
294 }
295 if (xy) {
296 xy[0] = fTileMode;
297 xy[1] = kClamp_TileMode;
298 }
299 return kTwoPointConical_BitmapType;
300}
301
302SkShader::GradientType SkTwoPointConicalGradient::asAGradient(
303 GradientInfo* info) const {
304 if (info) {
305 commonAsAGradient(info);
306 info->fPoint[0] = fCenter1;
307 info->fPoint[1] = fCenter2;
308 info->fRadius[0] = fRadius1;
309 info->fRadius[1] = fRadius2;
310 }
311 return kConical_GradientType;
312}
313
rileya@google.com589708b2012-07-26 20:04:23 +0000314SkTwoPointConicalGradient::SkTwoPointConicalGradient(
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +0000315 SkReadBuffer& buffer)
rileya@google.com589708b2012-07-26 20:04:23 +0000316 : INHERITED(buffer),
317 fCenter1(buffer.readPoint()),
318 fCenter2(buffer.readPoint()),
319 fRadius1(buffer.readScalar()),
320 fRadius2(buffer.readScalar()) {
321 this->init();
322};
323
324void SkTwoPointConicalGradient::flatten(
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +0000325 SkWriteBuffer& buffer) const {
rileya@google.com589708b2012-07-26 20:04:23 +0000326 this->INHERITED::flatten(buffer);
327 buffer.writePoint(fCenter1);
328 buffer.writePoint(fCenter2);
329 buffer.writeScalar(fRadius1);
330 buffer.writeScalar(fRadius2);
331}
332
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000333#if SK_SUPPORT_GPU
334
bsalomon@google.com0ac6af42013-01-16 15:16:18 +0000335GrEffectRef* SkTwoPointConicalGradient::asNewEffect(GrContext* context, const SkPaint&) const {
bsalomon@google.com00835cc2013-01-14 17:07:22 +0000336 SkASSERT(NULL != context);
bsalomon@google.comf94b3a42012-10-31 18:09:01 +0000337 SkASSERT(fPtsToUnit.isIdentity());
338 // invert the localM, translate to center1, rotate so center2 is on x axis.
bsalomon@google.comdfdb7e52012-10-16 15:19:45 +0000339 SkMatrix matrix;
bsalomon@google.comf94b3a42012-10-31 18:09:01 +0000340 if (!this->getLocalMatrix().invert(&matrix)) {
humper@google.com84831ac2013-01-14 22:09:54 +0000341 return NULL;
bsalomon@google.comf94b3a42012-10-31 18:09:01 +0000342 }
343 matrix.postTranslate(-fCenter1.fX, -fCenter1.fY);
344
rileya@google.com98e8b6d2012-07-31 20:38:06 +0000345 SkPoint diff = fCenter2 - fCenter1;
346 SkScalar diffLen = diff.length();
347 if (0 != diffLen) {
348 SkScalar invDiffLen = SkScalarInvert(diffLen);
bsalomon@google.comf94b3a42012-10-31 18:09:01 +0000349 SkMatrix rot;
350 rot.setSinCos(-SkScalarMul(invDiffLen, diff.fY),
351 SkScalarMul(invDiffLen, diff.fX));
352 matrix.postConcat(rot);
bsalomon@google.comdfdb7e52012-10-16 15:19:45 +0000353 }
354
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000355 return Gr2PtConicalGradientEffect::Create(context, *this, matrix, fTileMode);
rileya@google.comd7cc6512012-07-27 14:00:39 +0000356}
357
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000358#else
359
bsalomon@google.com5d2cd202013-01-16 15:31:06 +0000360GrEffectRef* SkTwoPointConicalGradient::asNewEffect(GrContext*, const SkPaint&) const {
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000361 SkDEBUGFAIL("Should not call in GPU-less build");
bsalomon@google.come197cbf2013-01-14 16:46:26 +0000362 return NULL;
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000363}
364
twiz@google.coma5e65ec2012-08-02 15:15:16 +0000365#endif
robertphillips@google.com76f9e932013-01-15 20:17:47 +0000366
commit-bot@chromium.org0f10f7b2014-03-13 18:02:17 +0000367#ifndef SK_IGNORE_TO_STRING
robertphillips@google.com76f9e932013-01-15 20:17:47 +0000368void SkTwoPointConicalGradient::toString(SkString* str) const {
369 str->append("SkTwoPointConicalGradient: (");
370
371 str->append("center1: (");
372 str->appendScalar(fCenter1.fX);
373 str->append(", ");
374 str->appendScalar(fCenter1.fY);
375 str->append(") radius1: ");
376 str->appendScalar(fRadius1);
377 str->append(" ");
378
379 str->append("center2: (");
380 str->appendScalar(fCenter2.fX);
381 str->append(", ");
382 str->appendScalar(fCenter2.fY);
383 str->append(") radius2: ");
384 str->appendScalar(fRadius2);
385 str->append(" ");
386
387 this->INHERITED::toString(str);
388
389 str->append(")");
390}
391#endif