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