Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / third_party / skia / src / effects / gradients / SkLinearGradient.cpp
1 /*
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 "SkLinearGradient.h"
9
10 static inline int repeat_bits(int x, const int bits) {
11     return x & ((1 << bits) - 1);
12 }
13
14 static inline int repeat_8bits(int x) {
15     return x & 0xFF;
16 }
17
18 // Visual Studio 2010 (MSC_VER=1600) optimizes bit-shift code incorrectly.
19 // See http://code.google.com/p/skia/issues/detail?id=472
20 #if defined(_MSC_VER) && (_MSC_VER >= 1600)
21 #pragma optimize("", off)
22 #endif
23
24 static inline int mirror_bits(int x, const int bits) {
25     if (x & (1 << bits)) {
26         x = ~x;
27     }
28     return x & ((1 << bits) - 1);
29 }
30
31 static inline int mirror_8bits(int x) {
32     if (x & 256) {
33         x = ~x;
34     }
35     return x & 255;
36 }
37
38 #if defined(_MSC_VER) && (_MSC_VER >= 1600)
39 #pragma optimize("", on)
40 #endif
41
42 static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
43     SkVector    vec = pts[1] - pts[0];
44     SkScalar    mag = vec.length();
45     SkScalar    inv = mag ? SkScalarInvert(mag) : 0;
46
47     vec.scale(inv);
48     matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
49     matrix->postTranslate(-pts[0].fX, -pts[0].fY);
50     matrix->postScale(inv, inv);
51 }
52
53 ///////////////////////////////////////////////////////////////////////////////
54
55 SkLinearGradient::SkLinearGradient(const SkPoint pts[2], const Descriptor& desc)
56     : SkGradientShaderBase(desc)
57     , fStart(pts[0])
58     , fEnd(pts[1])
59 {
60     pts_to_unit_matrix(pts, &fPtsToUnit);
61 }
62
63 #ifdef SK_SUPPORT_LEGACY_DEEPFLATTENING
64 SkLinearGradient::SkLinearGradient(SkReadBuffer& buffer)
65     : INHERITED(buffer)
66     , fStart(buffer.readPoint())
67     , fEnd(buffer.readPoint()) {
68 }
69 #endif
70
71 SkFlattenable* SkLinearGradient::CreateProc(SkReadBuffer& buffer) {
72     DescriptorScope desc;
73     if (!desc.unflatten(buffer)) {
74         return NULL;
75     }
76     SkPoint pts[2];
77     pts[0] = buffer.readPoint();
78     pts[1] = buffer.readPoint();
79     return SkGradientShader::CreateLinear(pts, desc.fColors, desc.fPos, desc.fCount,
80                                           desc.fTileMode, desc.fGradFlags, desc.fLocalMatrix);
81 }
82
83 void SkLinearGradient::flatten(SkWriteBuffer& buffer) const {
84     this->INHERITED::flatten(buffer);
85     buffer.writePoint(fStart);
86     buffer.writePoint(fEnd);
87 }
88
89 size_t SkLinearGradient::contextSize() const {
90     return sizeof(LinearGradientContext);
91 }
92
93 SkShader::Context* SkLinearGradient::onCreateContext(const ContextRec& rec, void* storage) const {
94     return SkNEW_PLACEMENT_ARGS(storage, LinearGradientContext, (*this, rec));
95 }
96
97 SkLinearGradient::LinearGradientContext::LinearGradientContext(
98         const SkLinearGradient& shader, const ContextRec& rec)
99     : INHERITED(shader, rec)
100 {
101     unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
102     if ((fDstToIndex.getType() & ~mask) == 0) {
103         // when we dither, we are (usually) not const-in-Y
104         if ((fFlags & SkShader::kHasSpan16_Flag) && !rec.fPaint->isDither()) {
105             // only claim this if we do have a 16bit mode (i.e. none of our
106             // colors have alpha), and if we are not dithering (which obviously
107             // is not const in Y).
108             fFlags |= SkShader::kConstInY16_Flag;
109         }
110     }
111 }
112
113 #define NO_CHECK_ITER               \
114     do {                            \
115     unsigned fi = fx >> SkGradientShaderBase::kCache32Shift; \
116     SkASSERT(fi <= 0xFF);           \
117     fx += dx;                       \
118     *dstC++ = cache[toggle + fi];   \
119     toggle = next_dither_toggle(toggle); \
120     } while (0)
121
122 namespace {
123
124 typedef void (*LinearShadeProc)(TileProc proc, SkFixed dx, SkFixed fx,
125                                 SkPMColor* dstC, const SkPMColor* cache,
126                                 int toggle, int count);
127
128 // Linear interpolation (lerp) is unnecessary if there are no sharp
129 // discontinuities in the gradient - which must be true if there are
130 // only 2 colors - but it's cheap.
131 void shadeSpan_linear_vertical_lerp(TileProc proc, SkFixed dx, SkFixed fx,
132                                     SkPMColor* SK_RESTRICT dstC,
133                                     const SkPMColor* SK_RESTRICT cache,
134                                     int toggle, int count) {
135     // We're a vertical gradient, so no change in a span.
136     // If colors change sharply across the gradient, dithering is
137     // insufficient (it subsamples the color space) and we need to lerp.
138     unsigned fullIndex = proc(fx);
139     unsigned fi = fullIndex >> SkGradientShaderBase::kCache32Shift;
140     unsigned remainder = fullIndex & ((1 << SkGradientShaderBase::kCache32Shift) - 1);
141
142     int index0 = fi + toggle;
143     int index1 = index0;
144     if (fi < SkGradientShaderBase::kCache32Count - 1) {
145         index1 += 1;
146     }
147     SkPMColor lerp = SkFastFourByteInterp(cache[index1], cache[index0], remainder);
148     index0 ^= SkGradientShaderBase::kDitherStride32;
149     index1 ^= SkGradientShaderBase::kDitherStride32;
150     SkPMColor dlerp = SkFastFourByteInterp(cache[index1], cache[index0], remainder);
151     sk_memset32_dither(dstC, lerp, dlerp, count);
152 }
153
154 void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
155                             SkPMColor* SK_RESTRICT dstC,
156                             const SkPMColor* SK_RESTRICT cache,
157                             int toggle, int count) {
158     SkClampRange range;
159     range.init(fx, dx, count, 0, SkGradientShaderBase::kCache32Count - 1);
160
161     if ((count = range.fCount0) > 0) {
162         sk_memset32_dither(dstC,
163             cache[toggle + range.fV0],
164             cache[next_dither_toggle(toggle) + range.fV0],
165             count);
166         dstC += count;
167     }
168     if ((count = range.fCount1) > 0) {
169         int unroll = count >> 3;
170         fx = range.fFx1;
171         for (int i = 0; i < unroll; i++) {
172             NO_CHECK_ITER;  NO_CHECK_ITER;
173             NO_CHECK_ITER;  NO_CHECK_ITER;
174             NO_CHECK_ITER;  NO_CHECK_ITER;
175             NO_CHECK_ITER;  NO_CHECK_ITER;
176         }
177         if ((count &= 7) > 0) {
178             do {
179                 NO_CHECK_ITER;
180             } while (--count != 0);
181         }
182     }
183     if ((count = range.fCount2) > 0) {
184         sk_memset32_dither(dstC,
185             cache[toggle + range.fV1],
186             cache[next_dither_toggle(toggle) + range.fV1],
187             count);
188     }
189 }
190
191 void shadeSpan_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
192                              SkPMColor* SK_RESTRICT dstC,
193                              const SkPMColor* SK_RESTRICT cache,
194                              int toggle, int count) {
195     do {
196         unsigned fi = mirror_8bits(fx >> 8);
197         SkASSERT(fi <= 0xFF);
198         fx += dx;
199         *dstC++ = cache[toggle + fi];
200         toggle = next_dither_toggle(toggle);
201     } while (--count != 0);
202 }
203
204 void shadeSpan_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
205         SkPMColor* SK_RESTRICT dstC,
206         const SkPMColor* SK_RESTRICT cache,
207         int toggle, int count) {
208     do {
209         unsigned fi = repeat_8bits(fx >> 8);
210         SkASSERT(fi <= 0xFF);
211         fx += dx;
212         *dstC++ = cache[toggle + fi];
213         toggle = next_dither_toggle(toggle);
214     } while (--count != 0);
215 }
216
217 }
218
219 void SkLinearGradient::LinearGradientContext::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
220                                                         int count) {
221     SkASSERT(count > 0);
222
223     const SkLinearGradient& linearGradient = static_cast<const SkLinearGradient&>(fShader);
224
225     SkPoint             srcPt;
226     SkMatrix::MapXYProc dstProc = fDstToIndexProc;
227     TileProc            proc = linearGradient.fTileProc;
228     const SkPMColor* SK_RESTRICT cache = fCache->getCache32();
229     int                 toggle = init_dither_toggle(x, y);
230
231     if (fDstToIndexClass != kPerspective_MatrixClass) {
232         dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
233                              SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
234         SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
235
236         if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
237             SkFixed dxStorage[1];
238             (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
239             dx = dxStorage[0];
240         } else {
241             SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
242             dx = SkScalarToFixed(fDstToIndex.getScaleX());
243         }
244
245         LinearShadeProc shadeProc = shadeSpan_linear_repeat;
246         if (0 == dx) {
247             shadeProc = shadeSpan_linear_vertical_lerp;
248         } else if (SkShader::kClamp_TileMode == linearGradient.fTileMode) {
249             shadeProc = shadeSpan_linear_clamp;
250         } else if (SkShader::kMirror_TileMode == linearGradient.fTileMode) {
251             shadeProc = shadeSpan_linear_mirror;
252         } else {
253             SkASSERT(SkShader::kRepeat_TileMode == linearGradient.fTileMode);
254         }
255         (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
256     } else {
257         SkScalar    dstX = SkIntToScalar(x);
258         SkScalar    dstY = SkIntToScalar(y);
259         do {
260             dstProc(fDstToIndex, dstX, dstY, &srcPt);
261             unsigned fi = proc(SkScalarToFixed(srcPt.fX));
262             SkASSERT(fi <= 0xFFFF);
263             *dstC++ = cache[toggle + (fi >> kCache32Shift)];
264             toggle = next_dither_toggle(toggle);
265             dstX += SK_Scalar1;
266         } while (--count != 0);
267     }
268 }
269
270 SkShader::BitmapType SkLinearGradient::asABitmap(SkBitmap* bitmap,
271                                                 SkMatrix* matrix,
272                                                 TileMode xy[]) const {
273     if (bitmap) {
274         this->getGradientTableBitmap(bitmap);
275     }
276     if (matrix) {
277         matrix->preConcat(fPtsToUnit);
278     }
279     if (xy) {
280         xy[0] = fTileMode;
281         xy[1] = kClamp_TileMode;
282     }
283     return kLinear_BitmapType;
284 }
285
286 SkShader::GradientType SkLinearGradient::asAGradient(GradientInfo* info) const {
287     if (info) {
288         commonAsAGradient(info);
289         info->fPoint[0] = fStart;
290         info->fPoint[1] = fEnd;
291     }
292     return kLinear_GradientType;
293 }
294
295 static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
296                             int count) {
297     if (reinterpret_cast<uintptr_t>(dst) & 2) {
298         *dst++ = value;
299         count -= 1;
300         SkTSwap(value, other);
301     }
302
303     sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
304
305     if (count & 1) {
306         dst[count - 1] = value;
307     }
308 }
309
310 #define NO_CHECK_ITER_16                \
311     do {                                \
312     unsigned fi = fx >> SkGradientShaderBase::kCache16Shift;  \
313     SkASSERT(fi < SkGradientShaderBase::kCache16Count);       \
314     fx += dx;                           \
315     *dstC++ = cache[toggle + fi];       \
316     toggle = next_dither_toggle16(toggle);            \
317     } while (0)
318
319 namespace {
320
321 typedef void (*LinearShade16Proc)(TileProc proc, SkFixed dx, SkFixed fx,
322                                   uint16_t* dstC, const uint16_t* cache,
323                                   int toggle, int count);
324
325 void shadeSpan16_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
326                                  uint16_t* SK_RESTRICT dstC,
327                                  const uint16_t* SK_RESTRICT cache,
328                                  int toggle, int count) {
329     // we're a vertical gradient, so no change in a span
330     unsigned fi = proc(fx) >> SkGradientShaderBase::kCache16Shift;
331     SkASSERT(fi < SkGradientShaderBase::kCache16Count);
332     dither_memset16(dstC, cache[toggle + fi],
333         cache[next_dither_toggle16(toggle) + fi], count);
334 }
335
336 void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
337                               uint16_t* SK_RESTRICT dstC,
338                               const uint16_t* SK_RESTRICT cache,
339                               int toggle, int count) {
340     SkClampRange range;
341     range.init(fx, dx, count, 0, SkGradientShaderBase::kCache32Count - 1);
342
343     if ((count = range.fCount0) > 0) {
344         dither_memset16(dstC,
345             cache[toggle + range.fV0],
346             cache[next_dither_toggle16(toggle) + range.fV0],
347             count);
348         dstC += count;
349     }
350     if ((count = range.fCount1) > 0) {
351         int unroll = count >> 3;
352         fx = range.fFx1;
353         for (int i = 0; i < unroll; i++) {
354             NO_CHECK_ITER_16;  NO_CHECK_ITER_16;
355             NO_CHECK_ITER_16;  NO_CHECK_ITER_16;
356             NO_CHECK_ITER_16;  NO_CHECK_ITER_16;
357             NO_CHECK_ITER_16;  NO_CHECK_ITER_16;
358         }
359         if ((count &= 7) > 0) {
360             do {
361                 NO_CHECK_ITER_16;
362             } while (--count != 0);
363         }
364     }
365     if ((count = range.fCount2) > 0) {
366         dither_memset16(dstC,
367             cache[toggle + range.fV1],
368             cache[next_dither_toggle16(toggle) + range.fV1],
369             count);
370     }
371 }
372
373 void shadeSpan16_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
374                                uint16_t* SK_RESTRICT dstC,
375                                const uint16_t* SK_RESTRICT cache,
376                                int toggle, int count) {
377     do {
378         unsigned fi = mirror_bits(fx >> SkGradientShaderBase::kCache16Shift,
379                                         SkGradientShaderBase::kCache16Bits);
380         SkASSERT(fi < SkGradientShaderBase::kCache16Count);
381         fx += dx;
382         *dstC++ = cache[toggle + fi];
383         toggle = next_dither_toggle16(toggle);
384     } while (--count != 0);
385 }
386
387 void shadeSpan16_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
388                                uint16_t* SK_RESTRICT dstC,
389                                const uint16_t* SK_RESTRICT cache,
390                                int toggle, int count) {
391     do {
392         unsigned fi = repeat_bits(fx >> SkGradientShaderBase::kCache16Shift,
393                                   SkGradientShaderBase::kCache16Bits);
394         SkASSERT(fi < SkGradientShaderBase::kCache16Count);
395         fx += dx;
396         *dstC++ = cache[toggle + fi];
397         toggle = next_dither_toggle16(toggle);
398     } while (--count != 0);
399 }
400 }
401
402 static bool fixed_nearly_zero(SkFixed x) {
403     return SkAbs32(x) < (SK_Fixed1 >> 12);
404 }
405
406 void SkLinearGradient::LinearGradientContext::shadeSpan16(int x, int y,
407                                                           uint16_t* SK_RESTRICT dstC, int count) {
408     SkASSERT(count > 0);
409
410     const SkLinearGradient& linearGradient = static_cast<const SkLinearGradient&>(fShader);
411
412     SkPoint             srcPt;
413     SkMatrix::MapXYProc dstProc = fDstToIndexProc;
414     TileProc            proc = linearGradient.fTileProc;
415     const uint16_t* SK_RESTRICT cache = fCache->getCache16();
416     int                 toggle = init_dither_toggle16(x, y);
417
418     if (fDstToIndexClass != kPerspective_MatrixClass) {
419         dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
420                              SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
421         SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
422
423         if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
424             SkFixed dxStorage[1];
425             (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
426             dx = dxStorage[0];
427         } else {
428             SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
429             dx = SkScalarToFixed(fDstToIndex.getScaleX());
430         }
431
432         LinearShade16Proc shadeProc = shadeSpan16_linear_repeat;
433         if (fixed_nearly_zero(dx)) {
434             shadeProc = shadeSpan16_linear_vertical;
435         } else if (SkShader::kClamp_TileMode == linearGradient.fTileMode) {
436             shadeProc = shadeSpan16_linear_clamp;
437         } else if (SkShader::kMirror_TileMode == linearGradient.fTileMode) {
438             shadeProc = shadeSpan16_linear_mirror;
439         } else {
440             SkASSERT(SkShader::kRepeat_TileMode == linearGradient.fTileMode);
441         }
442         (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
443     } else {
444         SkScalar    dstX = SkIntToScalar(x);
445         SkScalar    dstY = SkIntToScalar(y);
446         do {
447             dstProc(fDstToIndex, dstX, dstY, &srcPt);
448             unsigned fi = proc(SkScalarToFixed(srcPt.fX));
449             SkASSERT(fi <= 0xFFFF);
450
451             int index = fi >> kCache16Shift;
452             *dstC++ = cache[toggle + index];
453             toggle = next_dither_toggle16(toggle);
454
455             dstX += SK_Scalar1;
456         } while (--count != 0);
457     }
458 }
459
460 #if SK_SUPPORT_GPU
461
462 #include "GrTBackendProcessorFactory.h"
463 #include "gl/builders/GrGLProgramBuilder.h"
464 #include "SkGr.h"
465
466 /////////////////////////////////////////////////////////////////////
467
468 class GrGLLinearGradient : public GrGLGradientEffect {
469 public:
470
471     GrGLLinearGradient(const GrBackendProcessorFactory& factory, const GrProcessor&)
472                        : INHERITED (factory) { }
473
474     virtual ~GrGLLinearGradient() { }
475
476     virtual void emitCode(GrGLFPBuilder*,
477                           const GrFragmentProcessor&,
478                           const GrProcessorKey&,
479                           const char* outputColor,
480                           const char* inputColor,
481                           const TransformedCoordsArray&,
482                           const TextureSamplerArray&) SK_OVERRIDE;
483
484     static void GenKey(const GrProcessor& processor, const GrGLCaps&, GrProcessorKeyBuilder* b) {
485         b->add32(GenBaseGradientKey(processor));
486     }
487
488 private:
489
490     typedef GrGLGradientEffect INHERITED;
491 };
492
493 /////////////////////////////////////////////////////////////////////
494
495 class GrLinearGradient : public GrGradientEffect {
496 public:
497
498     static GrFragmentProcessor* Create(GrContext* ctx,
499                                        const SkLinearGradient& shader,
500                                        const SkMatrix& matrix,
501                                        SkShader::TileMode tm) {
502         return SkNEW_ARGS(GrLinearGradient, (ctx, shader, matrix, tm));
503     }
504
505     virtual ~GrLinearGradient() { }
506
507     static const char* Name() { return "Linear Gradient"; }
508     const GrBackendFragmentProcessorFactory& getFactory() const SK_OVERRIDE {
509         return GrTBackendFragmentProcessorFactory<GrLinearGradient>::getInstance();
510     }
511
512     typedef GrGLLinearGradient GLProcessor;
513
514 private:
515     GrLinearGradient(GrContext* ctx,
516                      const SkLinearGradient& shader,
517                      const SkMatrix& matrix,
518                      SkShader::TileMode tm)
519         : INHERITED(ctx, shader, matrix, tm) { }
520     GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
521
522     typedef GrGradientEffect INHERITED;
523 };
524
525 /////////////////////////////////////////////////////////////////////
526
527 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrLinearGradient);
528
529 GrFragmentProcessor* GrLinearGradient::TestCreate(SkRandom* random,
530                                                   GrContext* context,
531                                                   const GrDrawTargetCaps&,
532                                                   GrTexture**) {
533     SkPoint points[] = {{random->nextUScalar1(), random->nextUScalar1()},
534                         {random->nextUScalar1(), random->nextUScalar1()}};
535
536     SkColor colors[kMaxRandomGradientColors];
537     SkScalar stopsArray[kMaxRandomGradientColors];
538     SkScalar* stops = stopsArray;
539     SkShader::TileMode tm;
540     int colorCount = RandomGradientParams(random, colors, &stops, &tm);
541     SkAutoTUnref<SkShader> shader(SkGradientShader::CreateLinear(points,
542                                                                  colors, stops, colorCount,
543                                                                  tm));
544     SkPaint paint;
545     GrColor paintColor;
546     GrFragmentProcessor* fp;
547     SkAssertResult(shader->asFragmentProcessor(context, paint, NULL, &paintColor, &fp));
548     return fp;
549 }
550
551 /////////////////////////////////////////////////////////////////////
552
553 void GrGLLinearGradient::emitCode(GrGLFPBuilder* builder,
554                                   const GrFragmentProcessor&,
555                                   const GrProcessorKey& key,
556                                   const char* outputColor,
557                                   const char* inputColor,
558                                   const TransformedCoordsArray& coords,
559                                   const TextureSamplerArray& samplers) {
560     uint32_t baseKey = key.get32(0);
561     this->emitUniforms(builder, baseKey);
562     SkString t = builder->getFragmentShaderBuilder()->ensureFSCoords2D(coords, 0);
563     t.append(".x");
564     this->emitColor(builder, t.c_str(), baseKey, outputColor, inputColor, samplers);
565 }
566
567 /////////////////////////////////////////////////////////////////////
568
569 bool SkLinearGradient::asFragmentProcessor(GrContext* context, const SkPaint& paint,
570                                            const SkMatrix* localMatrix, GrColor* paintColor,
571                                            GrFragmentProcessor** fp)  const {
572     SkASSERT(context);
573     
574     SkMatrix matrix;
575     if (!this->getLocalMatrix().invert(&matrix)) {
576         return false;
577     }
578     if (localMatrix) {
579         SkMatrix inv;
580         if (!localMatrix->invert(&inv)) {
581             return false;
582         }
583         matrix.postConcat(inv);
584     }
585     matrix.postConcat(fPtsToUnit);
586     
587     *paintColor = SkColor2GrColorJustAlpha(paint.getColor());
588     *fp = GrLinearGradient::Create(context, *this, matrix, fTileMode);
589     
590     return true;
591 }
592
593 #else
594
595 bool SkLinearGradient::asFragmentProcessor(GrContext*, const SkPaint&, const SkMatrix*, GrColor*,
596                                            GrFragmentProcessor**)  const {
597     SkDEBUGFAIL("Should not call in GPU-less build");
598     return false;
599 }
600
601 #endif
602
603 #ifndef SK_IGNORE_TO_STRING
604 void SkLinearGradient::toString(SkString* str) const {
605     str->append("SkLinearGradient (");
606
607     str->appendf("start: (%f, %f)", fStart.fX, fStart.fY);
608     str->appendf(" end: (%f, %f) ", fEnd.fX, fEnd.fY);
609
610     this->INHERITED::toString(str);
611
612     str->append(")");
613 }
614 #endif