SkRadialGradient.cpp revision 4b3050b410254d0cb38df9a30ae2e209124fa1a2
1 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 "SkRadialGradient.h" 10#include "SkRadialGradient_Table.h" 11#include "SkNx.h" 12 13#define kSQRT_TABLE_BITS 11 14#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS) 15 16SK_COMPILE_ASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE, SqrtTableSizesMatch); 17 18#if 0 19 20#include <stdio.h> 21 22void SkRadialGradient_BuildTable() { 23 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table 24 25 FILE* file = ::fopen("SkRadialGradient_Table.h", "w"); 26 SkASSERT(file); 27 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n"); 28 29 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) { 30 if ((i & 15) == 0) { 31 ::fprintf(file, "\t"); 32 } 33 34 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8); 35 36 ::fprintf(file, "0x%02X", value); 37 if (i < kSQRT_TABLE_SIZE-1) { 38 ::fprintf(file, ", "); 39 } 40 if ((i & 15) == 15) { 41 ::fprintf(file, "\n"); 42 } 43 } 44 ::fprintf(file, "};\n"); 45 ::fclose(file); 46} 47 48#endif 49 50namespace { 51 52// GCC doesn't like using static functions as template arguments. So force these to be non-static. 53inline SkFixed mirror_tileproc_nonstatic(SkFixed x) { 54 return mirror_tileproc(x); 55} 56 57inline SkFixed repeat_tileproc_nonstatic(SkFixed x) { 58 return repeat_tileproc(x); 59} 60 61SkMatrix rad_to_unit_matrix(const SkPoint& center, SkScalar radius) { 62 SkScalar inv = SkScalarInvert(radius); 63 64 SkMatrix matrix; 65 matrix.setTranslate(-center.fX, -center.fY); 66 matrix.postScale(inv, inv); 67 return matrix; 68} 69 70typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx, 71 SkScalar sfy, SkScalar sdy, 72 uint16_t* dstC, const uint16_t* cache, 73 int toggle, int count); 74 75void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx, 76 SkScalar sfy, SkScalar sdy, 77 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache, 78 int toggle, int count) { 79 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table; 80 81 /* knock these down so we can pin against +- 0x7FFF, which is an 82 immediate load, rather than 0xFFFF which is slower. This is a 83 compromise, since it reduces our precision, but that appears 84 to be visually OK. If we decide this is OK for all of our cases, 85 we could (it seems) put this scale-down into fDstToIndex, 86 to avoid having to do these extra shifts each time. 87 */ 88 SkFixed fx = SkScalarToFixed(sfx) >> 1; 89 SkFixed dx = SkScalarToFixed(sdx) >> 1; 90 SkFixed fy = SkScalarToFixed(sfy) >> 1; 91 SkFixed dy = SkScalarToFixed(sdy) >> 1; 92 // might perform this check for the other modes, 93 // but the win will be a smaller % of the total 94 if (dy == 0) { 95 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1); 96 fy *= fy; 97 do { 98 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1); 99 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS); 100 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS)); 101 fx += dx; 102 *dstC++ = cache[toggle + 103 (sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)]; 104 toggle = next_dither_toggle16(toggle); 105 } while (--count != 0); 106 } else { 107 do { 108 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1); 109 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1); 110 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS); 111 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS)); 112 fx += dx; 113 fy += dy; 114 *dstC++ = cache[toggle + 115 (sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)]; 116 toggle = next_dither_toggle16(toggle); 117 } while (--count != 0); 118 } 119} 120 121template <SkFixed (*TileProc)(SkFixed)> 122void shadeSpan16_radial(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, 123 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache, 124 int toggle, int count) { 125 do { 126 const SkFixed dist = SkFloatToFixed(sk_float_sqrt(fx*fx + fy*fy)); 127 const unsigned fi = TileProc(dist); 128 SkASSERT(fi <= 0xFFFF); 129 *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache16Shift)]; 130 toggle = next_dither_toggle16(toggle); 131 fx += dx; 132 fy += dy; 133 } while (--count != 0); 134} 135 136void shadeSpan16_radial_mirror(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, 137 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache, 138 int toggle, int count) { 139 shadeSpan16_radial<mirror_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, toggle, count); 140} 141 142void shadeSpan16_radial_repeat(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, 143 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache, 144 int toggle, int count) { 145 shadeSpan16_radial<repeat_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, toggle, count); 146} 147 148} // namespace 149 150///////////////////////////////////////////////////////////////////// 151 152SkRadialGradient::SkRadialGradient(const SkPoint& center, SkScalar radius, const Descriptor& desc) 153 : SkGradientShaderBase(desc, rad_to_unit_matrix(center, radius)) 154 , fCenter(center) 155 , fRadius(radius) { 156} 157 158size_t SkRadialGradient::contextSize() const { 159 return sizeof(RadialGradientContext); 160} 161 162SkShader::Context* SkRadialGradient::onCreateContext(const ContextRec& rec, void* storage) const { 163 return SkNEW_PLACEMENT_ARGS(storage, RadialGradientContext, (*this, rec)); 164} 165 166SkRadialGradient::RadialGradientContext::RadialGradientContext( 167 const SkRadialGradient& shader, const ContextRec& rec) 168 : INHERITED(shader, rec) {} 169 170void SkRadialGradient::RadialGradientContext::shadeSpan16(int x, int y, uint16_t* dstCParam, 171 int count) { 172 SkASSERT(count > 0); 173 174 const SkRadialGradient& radialGradient = static_cast<const SkRadialGradient&>(fShader); 175 176 uint16_t* SK_RESTRICT dstC = dstCParam; 177 178 SkPoint srcPt; 179 SkMatrix::MapXYProc dstProc = fDstToIndexProc; 180 TileProc proc = radialGradient.fTileProc; 181 const uint16_t* SK_RESTRICT cache = fCache->getCache16(); 182 int toggle = init_dither_toggle16(x, y); 183 184 if (fDstToIndexClass != kPerspective_MatrixClass) { 185 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, 186 SkIntToScalar(y) + SK_ScalarHalf, &srcPt); 187 188 SkScalar sdx = fDstToIndex.getScaleX(); 189 SkScalar sdy = fDstToIndex.getSkewY(); 190 191 if (fDstToIndexClass == kFixedStepInX_MatrixClass) { 192 SkFixed storage[2]; 193 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), 194 &storage[0], &storage[1]); 195 sdx = SkFixedToScalar(storage[0]); 196 sdy = SkFixedToScalar(storage[1]); 197 } else { 198 SkASSERT(fDstToIndexClass == kLinear_MatrixClass); 199 } 200 201 RadialShade16Proc shadeProc = shadeSpan16_radial_repeat; 202 if (SkShader::kClamp_TileMode == radialGradient.fTileMode) { 203 shadeProc = shadeSpan16_radial_clamp; 204 } else if (SkShader::kMirror_TileMode == radialGradient.fTileMode) { 205 shadeProc = shadeSpan16_radial_mirror; 206 } else { 207 SkASSERT(SkShader::kRepeat_TileMode == radialGradient.fTileMode); 208 } 209 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, 210 cache, toggle, count); 211 } else { // perspective case 212 SkScalar dstX = SkIntToScalar(x); 213 SkScalar dstY = SkIntToScalar(y); 214 do { 215 dstProc(fDstToIndex, dstX, dstY, &srcPt); 216 unsigned fi = proc(SkScalarToFixed(srcPt.length())); 217 SkASSERT(fi <= 0xFFFF); 218 219 int index = fi >> (16 - kCache16Bits); 220 *dstC++ = cache[toggle + index]; 221 toggle = next_dither_toggle16(toggle); 222 223 dstX += SK_Scalar1; 224 } while (--count != 0); 225 } 226} 227 228SkShader::BitmapType SkRadialGradient::asABitmap(SkBitmap* bitmap, 229 SkMatrix* matrix, SkShader::TileMode* xy) const { 230 if (bitmap) { 231 this->getGradientTableBitmap(bitmap); 232 } 233 if (matrix) { 234 matrix->setScale(SkIntToScalar(kCache32Count), 235 SkIntToScalar(kCache32Count)); 236 matrix->preConcat(fPtsToUnit); 237 } 238 if (xy) { 239 xy[0] = fTileMode; 240 xy[1] = kClamp_TileMode; 241 } 242 return kRadial_BitmapType; 243} 244 245SkShader::GradientType SkRadialGradient::asAGradient(GradientInfo* info) const { 246 if (info) { 247 commonAsAGradient(info); 248 info->fPoint[0] = fCenter; 249 info->fRadius[0] = fRadius; 250 } 251 return kRadial_GradientType; 252} 253 254SkFlattenable* SkRadialGradient::CreateProc(SkReadBuffer& buffer) { 255 DescriptorScope desc; 256 if (!desc.unflatten(buffer)) { 257 return NULL; 258 } 259 const SkPoint center = buffer.readPoint(); 260 const SkScalar radius = buffer.readScalar(); 261 return SkGradientShader::CreateRadial(center, radius, desc.fColors, desc.fPos, desc.fCount, 262 desc.fTileMode, desc.fGradFlags, desc.fLocalMatrix); 263} 264 265void SkRadialGradient::flatten(SkWriteBuffer& buffer) const { 266 this->INHERITED::flatten(buffer); 267 buffer.writePoint(fCenter); 268 buffer.writeScalar(fRadius); 269} 270 271namespace { 272 273inline bool radial_completely_pinned(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy) { 274 // fast, overly-conservative test: checks unit square instead of unit circle 275 bool xClamped = (fx >= 1 && dx >= 0) || (fx <= -1 && dx <= 0); 276 bool yClamped = (fy >= 1 && dy >= 0) || (fy <= -1 && dy <= 0); 277 return xClamped || yClamped; 278} 279 280typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx, 281 SkScalar sfy, SkScalar sdy, 282 SkPMColor* dstC, const SkPMColor* cache, 283 int count, int toggle); 284 285static inline Sk4f fast_sqrt(const Sk4f& R) { 286 // R * R.rsqrt0() is much faster, but it's non-monotonic, which isn't so pretty for gradients. 287 return R * R.rsqrt1(); 288} 289 290static inline Sk4f sum_squares(const Sk4f& a, const Sk4f& b) { 291 return a * a + b * b; 292} 293 294void shadeSpan_radial_clamp2(SkScalar sfx, SkScalar sdx, SkScalar sfy, SkScalar sdy, 295 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, 296 int count, int toggle) { 297 if (radial_completely_pinned(sfx, sdx, sfy, sdy)) { 298 unsigned fi = SkGradientShaderBase::kCache32Count - 1; 299 sk_memset32_dither(dstC, 300 cache[toggle + fi], 301 cache[next_dither_toggle(toggle) + fi], 302 count); 303 } else { 304 const Sk4f max(255); 305 const float scale = 255; 306 sfx *= scale; 307 sfy *= scale; 308 sdx *= scale; 309 sdy *= scale; 310 const Sk4f fx4(sfx, sfx + sdx, sfx + 2*sdx, sfx + 3*sdx); 311 const Sk4f fy4(sfy, sfy + sdy, sfy + 2*sdy, sfy + 3*sdy); 312 const Sk4f dx4(sdx * 4); 313 const Sk4f dy4(sdy * 4); 314 315 Sk4f tmpxy = fx4 * dx4 + fy4 * dy4; 316 Sk4f tmpdxdy = sum_squares(dx4, dy4); 317 Sk4f R = sum_squares(fx4, fy4); 318 Sk4f dR = tmpxy + tmpxy + tmpdxdy; 319 const Sk4f ddR = tmpdxdy + tmpdxdy; 320 321 for (int i = 0; i < (count >> 2); ++i) { 322 Sk4f dist = Sk4f::Min(fast_sqrt(R), max); 323 R = R + dR; 324 dR = dR + ddR; 325 326 int fi[4]; 327 dist.castTrunc().store(fi); 328 329 for (int i = 0; i < 4; i++) { 330 *dstC++ = cache[toggle + fi[i]]; 331 toggle = next_dither_toggle(toggle); 332 } 333 } 334 count &= 3; 335 if (count) { 336 Sk4f dist = Sk4f::Min(fast_sqrt(R), max); 337 338 int fi[4]; 339 dist.castTrunc().store(fi); 340 for (int i = 0; i < count; i++) { 341 *dstC++ = cache[toggle + fi[i]]; 342 toggle = next_dither_toggle(toggle); 343 } 344 } 345 } 346} 347 348// Unrolling this loop doesn't seem to help (when float); we're stalling to 349// get the results of the sqrt (?), and don't have enough extra registers to 350// have many in flight. 351template <SkFixed (*TileProc)(SkFixed)> 352void shadeSpan_radial(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, 353 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, 354 int count, int toggle) { 355 do { 356 const SkFixed dist = SkFloatToFixed(sk_float_sqrt(fx*fx + fy*fy)); 357 const unsigned fi = TileProc(dist); 358 SkASSERT(fi <= 0xFFFF); 359 *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache32Shift)]; 360 toggle = next_dither_toggle(toggle); 361 fx += dx; 362 fy += dy; 363 } while (--count != 0); 364} 365 366void shadeSpan_radial_mirror(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, 367 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, 368 int count, int toggle) { 369 shadeSpan_radial<mirror_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, count, toggle); 370} 371 372void shadeSpan_radial_repeat(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, 373 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, 374 int count, int toggle) { 375 shadeSpan_radial<repeat_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, count, toggle); 376} 377 378} // namespace 379 380void SkRadialGradient::RadialGradientContext::shadeSpan(int x, int y, 381 SkPMColor* SK_RESTRICT dstC, int count) { 382 SkASSERT(count > 0); 383 384 const SkRadialGradient& radialGradient = static_cast<const SkRadialGradient&>(fShader); 385 386 SkPoint srcPt; 387 SkMatrix::MapXYProc dstProc = fDstToIndexProc; 388 TileProc proc = radialGradient.fTileProc; 389 const SkPMColor* SK_RESTRICT cache = fCache->getCache32(); 390 int toggle = init_dither_toggle(x, y); 391 392 if (fDstToIndexClass != kPerspective_MatrixClass) { 393 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, 394 SkIntToScalar(y) + SK_ScalarHalf, &srcPt); 395 SkScalar sdx = fDstToIndex.getScaleX(); 396 SkScalar sdy = fDstToIndex.getSkewY(); 397 398 if (fDstToIndexClass == kFixedStepInX_MatrixClass) { 399 SkFixed storage[2]; 400 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), 401 &storage[0], &storage[1]); 402 sdx = SkFixedToScalar(storage[0]); 403 sdy = SkFixedToScalar(storage[1]); 404 } else { 405 SkASSERT(fDstToIndexClass == kLinear_MatrixClass); 406 } 407 408 RadialShadeProc shadeProc = shadeSpan_radial_repeat; 409 if (SkShader::kClamp_TileMode == radialGradient.fTileMode) { 410 shadeProc = shadeSpan_radial_clamp2; 411 } else if (SkShader::kMirror_TileMode == radialGradient.fTileMode) { 412 shadeProc = shadeSpan_radial_mirror; 413 } else { 414 SkASSERT(SkShader::kRepeat_TileMode == radialGradient.fTileMode); 415 } 416 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle); 417 } else { // perspective case 418 SkScalar dstX = SkIntToScalar(x); 419 SkScalar dstY = SkIntToScalar(y); 420 do { 421 dstProc(fDstToIndex, dstX, dstY, &srcPt); 422 unsigned fi = proc(SkScalarToFixed(srcPt.length())); 423 SkASSERT(fi <= 0xFFFF); 424 *dstC++ = cache[fi >> SkGradientShaderBase::kCache32Shift]; 425 dstX += SK_Scalar1; 426 } while (--count != 0); 427 } 428} 429 430///////////////////////////////////////////////////////////////////// 431 432#if SK_SUPPORT_GPU 433 434#include "SkGr.h" 435#include "gl/builders/GrGLProgramBuilder.h" 436 437class GrGLRadialGradient : public GrGLGradientEffect { 438public: 439 440 GrGLRadialGradient(const GrProcessor&) {} 441 virtual ~GrGLRadialGradient() { } 442 443 virtual void emitCode(EmitArgs&) override; 444 445 static void GenKey(const GrProcessor& processor, const GrGLSLCaps&, GrProcessorKeyBuilder* b) { 446 b->add32(GenBaseGradientKey(processor)); 447 } 448 449private: 450 451 typedef GrGLGradientEffect INHERITED; 452 453}; 454 455///////////////////////////////////////////////////////////////////// 456 457class GrRadialGradient : public GrGradientEffect { 458public: 459 static GrFragmentProcessor* Create(GrContext* ctx, 460 GrProcessorDataManager* procDataManager, 461 const SkRadialGradient& shader, 462 const SkMatrix& matrix, 463 SkShader::TileMode tm) { 464 return SkNEW_ARGS(GrRadialGradient, (ctx, procDataManager, shader, matrix, tm)); 465 } 466 467 virtual ~GrRadialGradient() { } 468 469 const char* name() const override { return "Radial Gradient"; } 470 471 GrGLFragmentProcessor* createGLInstance() const override { 472 return SkNEW_ARGS(GrGLRadialGradient, (*this)); 473 } 474 475private: 476 GrRadialGradient(GrContext* ctx, 477 GrProcessorDataManager* procDataManager, 478 const SkRadialGradient& shader, 479 const SkMatrix& matrix, 480 SkShader::TileMode tm) 481 : INHERITED(ctx, procDataManager, shader, matrix, tm) { 482 this->initClassID<GrRadialGradient>(); 483 } 484 485 virtual void onGetGLProcessorKey(const GrGLSLCaps& caps, 486 GrProcessorKeyBuilder* b) const override { 487 GrGLRadialGradient::GenKey(*this, caps, b); 488 } 489 490 GR_DECLARE_FRAGMENT_PROCESSOR_TEST; 491 492 typedef GrGradientEffect INHERITED; 493}; 494 495///////////////////////////////////////////////////////////////////// 496 497GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRadialGradient); 498 499GrFragmentProcessor* GrRadialGradient::TestCreate(GrProcessorTestData* d) { 500 SkPoint center = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; 501 SkScalar radius = d->fRandom->nextUScalar1(); 502 503 SkColor colors[kMaxRandomGradientColors]; 504 SkScalar stopsArray[kMaxRandomGradientColors]; 505 SkScalar* stops = stopsArray; 506 SkShader::TileMode tm; 507 int colorCount = RandomGradientParams(d->fRandom, colors, &stops, &tm); 508 SkAutoTUnref<SkShader> shader(SkGradientShader::CreateRadial(center, radius, 509 colors, stops, colorCount, 510 tm)); 511 SkPaint paint; 512 GrColor paintColor; 513 GrFragmentProcessor* fp; 514 SkAssertResult(shader->asFragmentProcessor(d->fContext, paint, 515 GrTest::TestMatrix(d->fRandom), NULL, 516 &paintColor, d->fProcDataManager, &fp)); 517 return fp; 518} 519 520///////////////////////////////////////////////////////////////////// 521 522void GrGLRadialGradient::emitCode(EmitArgs& args) { 523 const GrRadialGradient& ge = args.fFp.cast<GrRadialGradient>(); 524 this->emitUniforms(args.fBuilder, ge); 525 SkString t("length("); 526 t.append(args.fBuilder->getFragmentShaderBuilder()->ensureFSCoords2D(args.fCoords, 0)); 527 t.append(")"); 528 this->emitColor(args.fBuilder, ge, t.c_str(), args.fOutputColor, args.fInputColor, 529 args.fSamplers); 530} 531 532///////////////////////////////////////////////////////////////////// 533 534bool SkRadialGradient::asFragmentProcessor(GrContext* context, const SkPaint& paint, 535 const SkMatrix& viewM, 536 const SkMatrix* localMatrix, GrColor* paintColor, 537 GrProcessorDataManager* procDataManager, 538 GrFragmentProcessor** fp) const { 539 SkASSERT(context); 540 541 SkMatrix matrix; 542 if (!this->getLocalMatrix().invert(&matrix)) { 543 return false; 544 } 545 if (localMatrix) { 546 SkMatrix inv; 547 if (!localMatrix->invert(&inv)) { 548 return false; 549 } 550 matrix.postConcat(inv); 551 } 552 matrix.postConcat(fPtsToUnit); 553 554 *paintColor = SkColor2GrColorJustAlpha(paint.getColor()); 555 *fp = GrRadialGradient::Create(context, procDataManager, *this, matrix, fTileMode); 556 557 return true; 558} 559 560#else 561 562bool SkRadialGradient::asFragmentProcessor(GrContext*, const SkPaint&, const SkMatrix&, 563 const SkMatrix*, GrColor*, GrProcessorDataManager*, 564 GrFragmentProcessor**) const { 565 SkDEBUGFAIL("Should not call in GPU-less build"); 566 return false; 567} 568 569#endif 570 571#ifndef SK_IGNORE_TO_STRING 572void SkRadialGradient::toString(SkString* str) const { 573 str->append("SkRadialGradient: ("); 574 575 str->append("center: ("); 576 str->appendScalar(fCenter.fX); 577 str->append(", "); 578 str->appendScalar(fCenter.fY); 579 str->append(") radius: "); 580 str->appendScalar(fRadius); 581 str->append(" "); 582 583 this->INHERITED::toString(str); 584 585 str->append(")"); 586} 587#endif 588