blob: 1e6a0d81268a28c30e630a3be37039510e761aea [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
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +000023static int find_quad_roots(float A, float B, float C, float roots[2], bool descendingOrder = false) {
rileya@google.com589708b2012-07-26 20:04:23 +000024 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;
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +000057 if (descendingOrder) {
58 SkTSwap(roots[0], roots[1]);
59 }
rileya@google.com589708b2012-07-26 20:04:23 +000060 return 2;
61}
62
63static float lerp(float x, float dx, float t) {
64 return x + t * dx;
65}
66
67static float sqr(float x) { return x * x; }
68
69void TwoPtRadial::init(const SkPoint& center0, SkScalar rad0,
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +000070 const SkPoint& center1, SkScalar rad1,
71 bool flipped) {
rileya@google.com589708b2012-07-26 20:04:23 +000072 fCenterX = SkScalarToFloat(center0.fX);
73 fCenterY = SkScalarToFloat(center0.fY);
74 fDCenterX = SkScalarToFloat(center1.fX) - fCenterX;
75 fDCenterY = SkScalarToFloat(center1.fY) - fCenterY;
76 fRadius = SkScalarToFloat(rad0);
77 fDRadius = SkScalarToFloat(rad1) - fRadius;
78
79 fA = sqr(fDCenterX) + sqr(fDCenterY) - sqr(fDRadius);
80 fRadius2 = sqr(fRadius);
81 fRDR = fRadius * fDRadius;
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +000082
83 fFlipped = flipped;
rileya@google.com589708b2012-07-26 20:04:23 +000084}
85
commit-bot@chromium.org53783b02014-04-17 21:09:49 +000086void TwoPtRadial::setup(SkScalar fx, SkScalar fy, SkScalar dfx, SkScalar dfy) {
87 fRelX = SkScalarToFloat(fx) - fCenterX;
88 fRelY = SkScalarToFloat(fy) - fCenterY;
89 fIncX = SkScalarToFloat(dfx);
90 fIncY = SkScalarToFloat(dfy);
91 fB = -2 * (fDCenterX * fRelX + fDCenterY * fRelY + fRDR);
92 fDB = -2 * (fDCenterX * fIncX + fDCenterY * fIncY);
93}
rileya@google.com589708b2012-07-26 20:04:23 +000094
commit-bot@chromium.org53783b02014-04-17 21:09:49 +000095SkFixed TwoPtRadial::nextT() {
rileya@google.com589708b2012-07-26 20:04:23 +000096 float roots[2];
rmistry@google.comfbfcd562012-08-23 18:09:54 +000097
commit-bot@chromium.org53783b02014-04-17 21:09:49 +000098 float C = sqr(fRelX) + sqr(fRelY) - fRadius2;
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +000099 int countRoots = find_quad_roots(fA, fB, C, roots, fFlipped);
rileya@google.com589708b2012-07-26 20:04:23 +0000100
101 fRelX += fIncX;
102 fRelY += fIncY;
103 fB += fDB;
104
105 if (0 == countRoots) {
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000106 return kDontDrawT;
rileya@google.com589708b2012-07-26 20:04:23 +0000107 }
108
109 // Prefer the bigger t value if both give a radius(t) > 0
110 // find_quad_roots returns the values sorted, so we start with the last
111 float t = roots[countRoots - 1];
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000112 float r = lerp(fRadius, fDRadius, t);
rileya@google.com589708b2012-07-26 20:04:23 +0000113 if (r <= 0) {
114 t = roots[0]; // might be the same as roots[countRoots-1]
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000115 r = lerp(fRadius, fDRadius, t);
rileya@google.com589708b2012-07-26 20:04:23 +0000116 if (r <= 0) {
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000117 return kDontDrawT;
rileya@google.com589708b2012-07-26 20:04:23 +0000118 }
119 }
120 return SkFloatToFixed(t);
121}
122
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000123typedef void (*TwoPointConicalProc)(TwoPtRadial* rec, SkPMColor* dstC,
reed@google.com60040292013-02-04 18:21:23 +0000124 const SkPMColor* cache, int toggle, int count);
rileya@google.com589708b2012-07-26 20:04:23 +0000125
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000126static void twopoint_clamp(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC,
reed@google.com60040292013-02-04 18:21:23 +0000127 const SkPMColor* SK_RESTRICT cache, int toggle,
128 int count) {
rileya@google.com589708b2012-07-26 20:04:23 +0000129 for (; count > 0; --count) {
130 SkFixed t = rec->nextT();
131 if (TwoPtRadial::DontDrawT(t)) {
132 *dstC++ = 0;
133 } else {
134 SkFixed index = SkClampMax(t, 0xFFFF);
135 SkASSERT(index <= 0xFFFF);
reed@google.com60040292013-02-04 18:21:23 +0000136 *dstC++ = cache[toggle +
137 (index >> SkGradientShaderBase::kCache32Shift)];
rileya@google.com589708b2012-07-26 20:04:23 +0000138 }
reed@google.com60040292013-02-04 18:21:23 +0000139 toggle = next_dither_toggle(toggle);
rileya@google.com589708b2012-07-26 20:04:23 +0000140 }
141}
142
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000143static void twopoint_repeat(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC,
reed@google.com60040292013-02-04 18:21:23 +0000144 const SkPMColor* SK_RESTRICT cache, int toggle,
145 int count) {
rileya@google.com589708b2012-07-26 20:04:23 +0000146 for (; count > 0; --count) {
147 SkFixed t = rec->nextT();
148 if (TwoPtRadial::DontDrawT(t)) {
149 *dstC++ = 0;
150 } else {
151 SkFixed index = repeat_tileproc(t);
152 SkASSERT(index <= 0xFFFF);
reed@google.com60040292013-02-04 18:21:23 +0000153 *dstC++ = cache[toggle +
154 (index >> SkGradientShaderBase::kCache32Shift)];
rileya@google.com589708b2012-07-26 20:04:23 +0000155 }
reed@google.com60040292013-02-04 18:21:23 +0000156 toggle = next_dither_toggle(toggle);
rileya@google.com589708b2012-07-26 20:04:23 +0000157 }
158}
159
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000160static void twopoint_mirror(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC,
reed@google.com60040292013-02-04 18:21:23 +0000161 const SkPMColor* SK_RESTRICT cache, int toggle,
162 int count) {
rileya@google.com589708b2012-07-26 20:04:23 +0000163 for (; count > 0; --count) {
164 SkFixed t = rec->nextT();
165 if (TwoPtRadial::DontDrawT(t)) {
166 *dstC++ = 0;
167 } else {
168 SkFixed index = mirror_tileproc(t);
169 SkASSERT(index <= 0xFFFF);
reed@google.com60040292013-02-04 18:21:23 +0000170 *dstC++ = cache[toggle +
171 (index >> SkGradientShaderBase::kCache32Shift)];
rileya@google.com589708b2012-07-26 20:04:23 +0000172 }
reed@google.com60040292013-02-04 18:21:23 +0000173 toggle = next_dither_toggle(toggle);
rileya@google.com589708b2012-07-26 20:04:23 +0000174 }
175}
176
177void SkTwoPointConicalGradient::init() {
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000178 fRec.init(fCenter1, fRadius1, fCenter2, fRadius2, fFlippedGrad);
rileya@google.com589708b2012-07-26 20:04:23 +0000179 fPtsToUnit.reset();
180}
181
rileya@google.com98e8b6d2012-07-31 20:38:06 +0000182/////////////////////////////////////////////////////////////////////
183
rileya@google.com589708b2012-07-26 20:04:23 +0000184SkTwoPointConicalGradient::SkTwoPointConicalGradient(
reed@google.com3d3a8602013-05-24 14:58:44 +0000185 const SkPoint& start, SkScalar startRadius,
186 const SkPoint& end, SkScalar endRadius,
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000187 bool flippedGrad, const Descriptor& desc)
reed@google.com437d6eb2013-05-23 19:03:05 +0000188 : SkGradientShaderBase(desc),
rileya@google.com589708b2012-07-26 20:04:23 +0000189 fCenter1(start),
190 fCenter2(end),
191 fRadius1(startRadius),
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000192 fRadius2(endRadius),
193 fFlippedGrad(flippedGrad) {
rileya@google.com589708b2012-07-26 20:04:23 +0000194 // this is degenerate, and should be caught by our caller
195 SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
196 this->init();
197}
198
commit-bot@chromium.org3fbab822013-03-20 00:49:57 +0000199bool SkTwoPointConicalGradient::isOpaque() const {
robertphillips@google.comcb6d97c2013-07-09 13:50:09 +0000200 // Because areas outside the cone are left untouched, we cannot treat the
201 // shader as opaque even if the gradient itself is opaque.
202 // TODO(junov): Compute whether the cone fills the plane crbug.com/222380
203 return false;
commit-bot@chromium.org3fbab822013-03-20 00:49:57 +0000204}
205
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000206void SkTwoPointConicalGradient::shadeSpan(int x, int y, SkPMColor* dstCParam,
207 int count) {
reed@google.com60040292013-02-04 18:21:23 +0000208 int toggle = init_dither_toggle(x, y);
reed@google.com60040292013-02-04 18:21:23 +0000209
rileya@google.com589708b2012-07-26 20:04:23 +0000210 SkASSERT(count > 0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000211
rileya@google.com589708b2012-07-26 20:04:23 +0000212 SkPMColor* SK_RESTRICT dstC = dstCParam;
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000213
rileya@google.com589708b2012-07-26 20:04:23 +0000214 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
bsalomon@google.com100abf42012-09-05 17:40:04 +0000215
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000216 const SkPMColor* SK_RESTRICT cache = this->getCache32();
rileya@google.com589708b2012-07-26 20:04:23 +0000217
reed@google.com60040292013-02-04 18:21:23 +0000218 TwoPointConicalProc shadeProc = twopoint_repeat;
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000219 if (SkShader::kClamp_TileMode == fTileMode) {
rileya@google.com589708b2012-07-26 20:04:23 +0000220 shadeProc = twopoint_clamp;
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000221 } else if (SkShader::kMirror_TileMode == fTileMode) {
rileya@google.com589708b2012-07-26 20:04:23 +0000222 shadeProc = twopoint_mirror;
223 } else {
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000224 SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
rileya@google.com589708b2012-07-26 20:04:23 +0000225 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000226
rileya@google.com589708b2012-07-26 20:04:23 +0000227 if (fDstToIndexClass != kPerspective_MatrixClass) {
228 SkPoint srcPt;
229 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
230 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
231 SkScalar dx, fx = srcPt.fX;
232 SkScalar dy, fy = srcPt.fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000233
rileya@google.com589708b2012-07-26 20:04:23 +0000234 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
235 SkFixed fixedX, fixedY;
236 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
237 dx = SkFixedToScalar(fixedX);
238 dy = SkFixedToScalar(fixedY);
239 } else {
240 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
241 dx = fDstToIndex.getScaleX();
242 dy = fDstToIndex.getSkewY();
243 }
244
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000245 fRec.setup(fx, fy, dx, dy);
246 (*shadeProc)(&fRec, dstC, cache, toggle, count);
rileya@google.com589708b2012-07-26 20:04:23 +0000247 } else { // perspective case
mike@reedtribe.org139a2352013-11-14 20:15:51 +0000248 SkScalar dstX = SkIntToScalar(x) + SK_ScalarHalf;
249 SkScalar dstY = SkIntToScalar(y) + SK_ScalarHalf;
rileya@google.com589708b2012-07-26 20:04:23 +0000250 for (; count > 0; --count) {
251 SkPoint srcPt;
252 dstProc(fDstToIndex, dstX, dstY, &srcPt);
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000253 fRec.setup(srcPt.fX, srcPt.fY, 0, 0);
254 (*shadeProc)(&fRec, dstC, cache, toggle, 1);
mike@reedtribe.org139a2352013-11-14 20:15:51 +0000255
256 dstX += SK_Scalar1;
reed@google.com60040292013-02-04 18:21:23 +0000257 toggle = next_dither_toggle(toggle);
mike@reedtribe.org139a2352013-11-14 20:15:51 +0000258 dstC += 1;
rileya@google.com589708b2012-07-26 20:04:23 +0000259 }
260 }
261}
262
commit-bot@chromium.org53783b02014-04-17 21:09:49 +0000263bool SkTwoPointConicalGradient::setContext(const SkBitmap& device,
264 const SkPaint& paint,
265 const SkMatrix& matrix) {
266 if (!this->INHERITED::setContext(device, paint, matrix)) {
267 return false;
268 }
269
270 // we don't have a span16 proc
271 fFlags &= ~kHasSpan16_Flag;
272
273 // in general, we might discard based on computed-radius, so clear
274 // this flag (todo: sometimes we can detect that we never discard...)
275 fFlags &= ~kOpaqueAlpha_Flag;
276
277 return true;
278}
279
rileya@google.com589708b2012-07-26 20:04:23 +0000280SkShader::BitmapType SkTwoPointConicalGradient::asABitmap(
281 SkBitmap* bitmap, SkMatrix* matrix, SkShader::TileMode* xy) const {
282 SkPoint diff = fCenter2 - fCenter1;
rileya@google.com589708b2012-07-26 20:04:23 +0000283 SkScalar diffLen = 0;
284
285 if (bitmap) {
rileya@google.com1c6d64b2012-07-27 15:49:05 +0000286 this->getGradientTableBitmap(bitmap);
rileya@google.com589708b2012-07-26 20:04:23 +0000287 }
288 if (matrix) {
289 diffLen = diff.length();
290 }
291 if (matrix) {
292 if (diffLen) {
293 SkScalar invDiffLen = SkScalarInvert(diffLen);
294 // rotate to align circle centers with the x-axis
295 matrix->setSinCos(-SkScalarMul(invDiffLen, diff.fY),
296 SkScalarMul(invDiffLen, diff.fX));
297 } else {
298 matrix->reset();
299 }
300 matrix->preTranslate(-fCenter1.fX, -fCenter1.fY);
301 }
302 if (xy) {
303 xy[0] = fTileMode;
304 xy[1] = kClamp_TileMode;
305 }
306 return kTwoPointConical_BitmapType;
307}
308
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000309// Returns the original non-sorted version of the gradient
rileya@google.com589708b2012-07-26 20:04:23 +0000310SkShader::GradientType SkTwoPointConicalGradient::asAGradient(
311 GradientInfo* info) const {
312 if (info) {
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000313 commonAsAGradient(info, fFlippedGrad);
rileya@google.com589708b2012-07-26 20:04:23 +0000314 info->fPoint[0] = fCenter1;
315 info->fPoint[1] = fCenter2;
316 info->fRadius[0] = fRadius1;
317 info->fRadius[1] = fRadius2;
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000318 if (fFlippedGrad) {
319 SkTSwap(info->fPoint[0], info->fPoint[1]);
320 SkTSwap(info->fRadius[0], info->fRadius[1]);
321 }
rileya@google.com589708b2012-07-26 20:04:23 +0000322 }
323 return kConical_GradientType;
324}
325
rileya@google.com589708b2012-07-26 20:04:23 +0000326SkTwoPointConicalGradient::SkTwoPointConicalGradient(
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +0000327 SkReadBuffer& buffer)
rileya@google.com589708b2012-07-26 20:04:23 +0000328 : INHERITED(buffer),
329 fCenter1(buffer.readPoint()),
330 fCenter2(buffer.readPoint()),
331 fRadius1(buffer.readScalar()),
332 fRadius2(buffer.readScalar()) {
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000333 if (buffer.pictureVersion() >= 24 || 0 == buffer.pictureVersion()) {
334 fFlippedGrad = buffer.readBool();
335 } else {
336 // V23_COMPATIBILITY_CODE
337 // Sort gradient by radius size for old pictures
338 if (fRadius2 < fRadius1) {
339 SkTSwap(fCenter1, fCenter2);
340 SkTSwap(fRadius1, fRadius2);
341 this->flipGradientColors();
342 fFlippedGrad = true;
343 } else {
344 fFlippedGrad = false;
345 }
346 }
rileya@google.com589708b2012-07-26 20:04:23 +0000347 this->init();
348};
349
350void SkTwoPointConicalGradient::flatten(
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +0000351 SkWriteBuffer& buffer) const {
rileya@google.com589708b2012-07-26 20:04:23 +0000352 this->INHERITED::flatten(buffer);
353 buffer.writePoint(fCenter1);
354 buffer.writePoint(fCenter2);
355 buffer.writeScalar(fRadius1);
356 buffer.writeScalar(fRadius2);
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000357 buffer.writeBool(fFlippedGrad);
rileya@google.com589708b2012-07-26 20:04:23 +0000358}
359
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000360#if SK_SUPPORT_GPU
361
bsalomon@google.com0ac6af42013-01-16 15:16:18 +0000362GrEffectRef* SkTwoPointConicalGradient::asNewEffect(GrContext* context, const SkPaint&) const {
bsalomon@google.com00835cc2013-01-14 17:07:22 +0000363 SkASSERT(NULL != context);
bsalomon@google.comf94b3a42012-10-31 18:09:01 +0000364 SkASSERT(fPtsToUnit.isIdentity());
bsalomon@google.comf94b3a42012-10-31 18:09:01 +0000365
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +0000366 return Gr2PtConicalGradientEffect::Create(context, *this, fTileMode);
rileya@google.comd7cc6512012-07-27 14:00:39 +0000367}
368
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000369#else
370
bsalomon@google.com5d2cd202013-01-16 15:31:06 +0000371GrEffectRef* SkTwoPointConicalGradient::asNewEffect(GrContext*, const SkPaint&) const {
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000372 SkDEBUGFAIL("Should not call in GPU-less build");
bsalomon@google.come197cbf2013-01-14 16:46:26 +0000373 return NULL;
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000374}
375
twiz@google.coma5e65ec2012-08-02 15:15:16 +0000376#endif
robertphillips@google.com76f9e932013-01-15 20:17:47 +0000377
commit-bot@chromium.org0f10f7b2014-03-13 18:02:17 +0000378#ifndef SK_IGNORE_TO_STRING
robertphillips@google.com76f9e932013-01-15 20:17:47 +0000379void SkTwoPointConicalGradient::toString(SkString* str) const {
380 str->append("SkTwoPointConicalGradient: (");
381
382 str->append("center1: (");
383 str->appendScalar(fCenter1.fX);
384 str->append(", ");
385 str->appendScalar(fCenter1.fY);
386 str->append(") radius1: ");
387 str->appendScalar(fRadius1);
388 str->append(" ");
389
390 str->append("center2: (");
391 str->appendScalar(fCenter2.fX);
392 str->append(", ");
393 str->appendScalar(fCenter2.fY);
394 str->append(") radius2: ");
395 str->appendScalar(fRadius2);
396 str->append(" ");
397
398 this->INHERITED::toString(str);
399
400 str->append(")");
401}
402#endif