1
2/*
3 * Copyright 2011 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#include "gm.h"
9#include "SkGradientShader.h"
10
11namespace skiagm {
12
13struct GradData {
14    int             fCount;
15    const SkColor*  fColors;
16    const SkScalar* fPos;
17};
18
19static const SkColor gColors[] = {
20    SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorWHITE, SK_ColorBLACK
21};
22static const SkScalar gPos0[] = { 0, SK_Scalar1 };
23static const SkScalar gPos1[] = { SK_Scalar1/4, SK_Scalar1*3/4 };
24static const SkScalar gPos2[] = {
25    0, SK_Scalar1/8, SK_Scalar1/2, SK_Scalar1*7/8, SK_Scalar1
26};
27
28static const SkScalar gPosClamp[]   = {0.0f, 0.0f, 1.0f, 1.0f};
29static const SkColor  gColorClamp[] = {
30    SK_ColorRED, SK_ColorGREEN, SK_ColorGREEN, SK_ColorBLUE
31};
32
33static const GradData gGradData[] = {
34    { 2, gColors, NULL },
35    { 2, gColors, gPos0 },
36    { 2, gColors, gPos1 },
37    { 5, gColors, NULL },
38    { 5, gColors, gPos2 },
39    { 4, gColorClamp, gPosClamp }
40};
41
42static SkShader* MakeLinear(const SkPoint pts[2], const GradData& data,
43                            SkShader::TileMode tm, const SkMatrix& localMatrix) {
44    return SkGradientShader::CreateLinear(pts, data.fColors, data.fPos,
45                                          data.fCount, tm, 0, &localMatrix);
46}
47
48static SkShader* MakeRadial(const SkPoint pts[2], const GradData& data,
49                            SkShader::TileMode tm, const SkMatrix& localMatrix) {
50    SkPoint center;
51    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
52               SkScalarAve(pts[0].fY, pts[1].fY));
53    return SkGradientShader::CreateRadial(center, center.fX, data.fColors,
54                                          data.fPos, data.fCount, tm, 0, &localMatrix);
55}
56
57static SkShader* MakeSweep(const SkPoint pts[2], const GradData& data,
58                           SkShader::TileMode, const SkMatrix& localMatrix) {
59    SkPoint center;
60    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
61               SkScalarAve(pts[0].fY, pts[1].fY));
62    return SkGradientShader::CreateSweep(center.fX, center.fY, data.fColors,
63                                         data.fPos, data.fCount, 0, &localMatrix);
64}
65
66static SkShader* Make2Radial(const SkPoint pts[2], const GradData& data,
67                             SkShader::TileMode tm, const SkMatrix& localMatrix) {
68    SkPoint center0, center1;
69    center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
70                SkScalarAve(pts[0].fY, pts[1].fY));
71    center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3)/5),
72                SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1)/4));
73    return SkGradientShader::CreateTwoPointRadial(
74                                                  center1, (pts[1].fX - pts[0].fX) / 7,
75                                                  center0, (pts[1].fX - pts[0].fX) / 2,
76                                                  data.fColors, data.fPos, data.fCount, tm,
77                                                  0, &localMatrix);
78}
79
80static SkShader* Make2Conical(const SkPoint pts[2], const GradData& data,
81                             SkShader::TileMode tm, const SkMatrix& localMatrix) {
82    SkPoint center0, center1;
83    SkScalar radius0 = SkScalarDiv(pts[1].fX - pts[0].fX, 10);
84    SkScalar radius1 = SkScalarDiv(pts[1].fX - pts[0].fX, 3);
85    center0.set(pts[0].fX + radius0, pts[0].fY + radius0);
86    center1.set(pts[1].fX - radius1, pts[1].fY - radius1);
87    return SkGradientShader::CreateTwoPointConical(center1, radius1,
88                                                   center0, radius0,
89                                                   data.fColors, data.fPos,
90                                                   data.fCount, tm, 0, &localMatrix);
91}
92
93typedef SkShader* (*GradMaker)(const SkPoint pts[2], const GradData& data,
94                               SkShader::TileMode tm, const SkMatrix& localMatrix);
95static const GradMaker gGradMakers[] = {
96    MakeLinear, MakeRadial, MakeSweep, Make2Radial, Make2Conical
97};
98
99///////////////////////////////////////////////////////////////////////////////
100
101class GradientsGM : public GM {
102public:
103    GradientsGM() {
104        this->setBGColor(0xFFDDDDDD);
105    }
106
107protected:
108    virtual uint32_t onGetFlags() const SK_OVERRIDE {
109        return kSkipTiled_Flag;
110    }
111
112    SkString onShortName() {
113        return SkString("gradients");
114    }
115
116    virtual SkISize onISize() { return SkISize::Make(840, 815); }
117
118    virtual void onDraw(SkCanvas* canvas) {
119
120        SkPoint pts[2] = {
121            { 0, 0 },
122            { SkIntToScalar(100), SkIntToScalar(100) }
123        };
124        SkShader::TileMode tm = SkShader::kClamp_TileMode;
125        SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
126        SkPaint paint;
127        paint.setAntiAlias(true);
128
129        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
130        for (size_t i = 0; i < SK_ARRAY_COUNT(gGradData); i++) {
131            canvas->save();
132            for (size_t j = 0; j < SK_ARRAY_COUNT(gGradMakers); j++) {
133                SkMatrix scale = SkMatrix::I();
134
135                if (i == 5) { // if the clamp case
136                    scale.setScale(0.5f, 0.5f);
137                    scale.postTranslate(25.f, 25.f);
138                }
139
140                SkShader* shader = gGradMakers[j](pts, gGradData[i], tm, scale);
141
142                paint.setShader(shader);
143                canvas->drawRect(r, paint);
144                shader->unref();
145                canvas->translate(0, SkIntToScalar(120));
146            }
147            canvas->restore();
148            canvas->translate(SkIntToScalar(120), 0);
149        }
150    }
151
152private:
153    typedef GM INHERITED;
154};
155
156// Based on the original gradient slide, but with perspective applied to the
157// gradient shaders' local matrices
158class GradientsLocalPerspectiveGM : public GM {
159public:
160    GradientsLocalPerspectiveGM() {
161        this->setBGColor(0xFFDDDDDD);
162    }
163
164protected:
165    virtual uint32_t onGetFlags() const SK_OVERRIDE {
166        return kSkipTiled_Flag;
167    }
168
169    SkString onShortName() {
170        return SkString("gradients_local_perspective");
171    }
172
173    virtual SkISize onISize() { return SkISize::Make(840, 815); }
174
175    virtual void onDraw(SkCanvas* canvas) {
176
177        SkPoint pts[2] = {
178            { 0, 0 },
179            { SkIntToScalar(100), SkIntToScalar(100) }
180        };
181        SkShader::TileMode tm = SkShader::kClamp_TileMode;
182        SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
183        SkPaint paint;
184        paint.setAntiAlias(true);
185
186        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
187        for (size_t i = 0; i < SK_ARRAY_COUNT(gGradData); i++) {
188            canvas->save();
189            for (size_t j = 0; j < SK_ARRAY_COUNT(gGradMakers); j++) {
190                // apply an increasing y perspective as we move to the right
191                SkMatrix perspective;
192                perspective.setIdentity();
193                perspective.setPerspY(SkScalarDiv(SkIntToScalar((unsigned) i+1),
194                                      SkIntToScalar(500)));
195                perspective.setSkewX(SkScalarDiv(SkIntToScalar((unsigned) i+1),
196                                     SkIntToScalar(10)));
197
198                SkShader* shader = gGradMakers[j](pts, gGradData[i], tm, perspective);
199
200                paint.setShader(shader);
201                canvas->drawRect(r, paint);
202                shader->unref();
203                canvas->translate(0, SkIntToScalar(120));
204            }
205            canvas->restore();
206            canvas->translate(SkIntToScalar(120), 0);
207        }
208    }
209
210private:
211    typedef GM INHERITED;
212};
213
214// Based on the original gradient slide, but with perspective applied to
215// the view matrix
216class GradientsViewPerspectiveGM : public GradientsGM {
217protected:
218    SkString onShortName() {
219        return SkString("gradients_view_perspective");
220    }
221
222    virtual SkISize onISize() { return SkISize::Make(840, 500); }
223
224    virtual void onDraw(SkCanvas* canvas) {
225        SkMatrix perspective;
226        perspective.setIdentity();
227        perspective.setPerspY(SkScalarDiv(SK_Scalar1, SkIntToScalar(1000)));
228        perspective.setSkewX(SkScalarDiv(SkIntToScalar(8), SkIntToScalar(25)));
229        canvas->concat(perspective);
230        INHERITED::onDraw(canvas);
231    }
232
233private:
234    typedef GradientsGM INHERITED;
235};
236
237/*
238 Inspired by this <canvas> javascript, where we need to detect that we are not
239 solving a quadratic equation, but must instead solve a linear (since our X^2
240 coefficient is 0)
241
242 ctx.fillStyle = '#f00';
243 ctx.fillRect(0, 0, 100, 50);
244
245 var g = ctx.createRadialGradient(-80, 25, 70, 0, 25, 150);
246 g.addColorStop(0, '#f00');
247 g.addColorStop(0.01, '#0f0');
248 g.addColorStop(0.99, '#0f0');
249 g.addColorStop(1, '#f00');
250 ctx.fillStyle = g;
251 ctx.fillRect(0, 0, 100, 50);
252 */
253class GradientsDegenrate2PointGM : public GM {
254public:
255    GradientsDegenrate2PointGM() {}
256
257protected:
258    SkString onShortName() {
259        return SkString("gradients_degenerate_2pt");
260    }
261
262    virtual SkISize onISize() { return SkISize::Make(320, 320); }
263
264    void drawBG(SkCanvas* canvas) {
265        canvas->drawColor(SK_ColorBLUE);
266    }
267
268    virtual void onDraw(SkCanvas* canvas) {
269        this->drawBG(canvas);
270
271        SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorGREEN, SK_ColorRED };
272        SkScalar pos[] = { 0, 0.01f, 0.99f, SK_Scalar1 };
273        SkPoint c0;
274        c0.iset(-80, 25);
275        SkScalar r0 = SkIntToScalar(70);
276        SkPoint c1;
277        c1.iset(0, 25);
278        SkScalar r1 = SkIntToScalar(150);
279        SkShader* s = SkGradientShader::CreateTwoPointRadial(c0, r0, c1, r1, colors,
280                                                             pos, SK_ARRAY_COUNT(pos),
281                                                             SkShader::kClamp_TileMode);
282        SkPaint paint;
283        paint.setShader(s)->unref();
284        canvas->drawPaint(paint);
285    }
286
287private:
288    typedef GM INHERITED;
289};
290
291/// Tests correctness of *optimized* codepaths in gradients.
292
293class ClampedGradientsGM : public GM {
294public:
295    ClampedGradientsGM() {}
296
297protected:
298    virtual uint32_t onGetFlags() const SK_OVERRIDE {
299        return kSkipTiled_Flag;
300    }
301
302    SkString onShortName() { return SkString("clamped_gradients"); }
303
304    virtual SkISize onISize() { return SkISize::Make(640, 510); }
305
306    void drawBG(SkCanvas* canvas) {
307        canvas->drawColor(0xFFDDDDDD);
308    }
309
310    virtual void onDraw(SkCanvas* canvas) {
311        this->drawBG(canvas);
312
313        SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(300) };
314        SkPaint paint;
315        paint.setAntiAlias(true);
316
317        SkPoint center;
318        center.iset(0, 300);
319        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
320        SkShader* shader = SkGradientShader::CreateRadial(
321            SkPoint(center),
322            SkIntToScalar(200), gColors, NULL, 5,
323            SkShader::kClamp_TileMode);
324        paint.setShader(shader);
325        canvas->drawRect(r, paint);
326        shader->unref();
327    }
328
329private:
330    typedef GM INHERITED;
331};
332
333/// Checks quality of large radial gradients, which may display
334/// some banding.
335
336class RadialGradientGM : public GM {
337public:
338    RadialGradientGM() {}
339
340protected:
341    virtual uint32_t onGetFlags() const SK_OVERRIDE {
342        return kSkipTiled_Flag;
343    }
344
345    SkString onShortName() { return SkString("radial_gradient"); }
346    virtual SkISize onISize() { return SkISize::Make(1280, 1280); }
347    void drawBG(SkCanvas* canvas) {
348        canvas->drawColor(0xFF000000);
349    }
350    virtual void onDraw(SkCanvas* canvas) {
351        const SkISize dim = this->getISize();
352
353        this->drawBG(canvas);
354
355        SkPaint paint;
356        paint.setDither(true);
357        SkPoint center;
358        center.set(SkIntToScalar(dim.width())/2, SkIntToScalar(dim.height())/2);
359        SkScalar radius = SkIntToScalar(dim.width())/2;
360        const SkColor colors[] = { 0x7f7f7f7f, 0x7f7f7f7f, 0xb2000000 };
361        const SkScalar pos[] = { 0.0f,
362                             0.35f,
363                             1.0f };
364        SkShader* shader =
365            SkGradientShader::CreateRadial(center, radius, colors,
366                                           pos, SK_ARRAY_COUNT(pos),
367                                           SkShader::kClamp_TileMode);
368        paint.setShader(shader)->unref();
369        SkRect r = {
370            0, 0, SkIntToScalar(dim.width()), SkIntToScalar(dim.height())
371        };
372        canvas->drawRect(r, paint);
373    }
374private:
375    typedef GM INHERITED;
376};
377
378
379class RadialGradient2GM : public GM {
380public:
381    RadialGradient2GM() {}
382
383protected:
384    virtual uint32_t onGetFlags() const SK_OVERRIDE {
385        return kSkipTiled_Flag;
386    }
387
388    SkString onShortName() { return SkString("radial_gradient2"); }
389    virtual SkISize onISize() { return SkISize::Make(800, 400); }
390    void drawBG(SkCanvas* canvas) {
391        canvas->drawColor(0xFF000000);
392    }
393
394    // Reproduces the example given in bug 7671058.
395    virtual void onDraw(SkCanvas* canvas) {
396        SkPaint paint1, paint2, paint3;
397        paint1.setStyle(SkPaint::kFill_Style);
398        paint2.setStyle(SkPaint::kFill_Style);
399        paint3.setStyle(SkPaint::kFill_Style);
400
401        const SkColor sweep_colors[] =
402            { 0xFFFF0000, 0xFFFFFF00, 0xFF00FF00, 0xFF00FFFF, 0xFF0000FF, 0xFFFF00FF, 0xFFFF0000 };
403        const SkColor colors1[] = { 0xFFFFFFFF, 0x00000000 };
404        const SkColor colors2[] = { 0xFF000000, 0x00000000 };
405
406        const SkScalar cx = 200, cy = 200, radius = 150;
407        SkPoint center;
408        center.set(cx, cy);
409
410        // We can either interpolate endpoints and premultiply each point (default, more precision),
411        // or premultiply the endpoints first, avoiding the need to premultiply each point (cheap).
412        const uint32_t flags[] = { 0, SkGradientShader::kInterpolateColorsInPremul_Flag };
413
414        for (size_t i = 0; i < SK_ARRAY_COUNT(flags); i++) {
415            SkAutoTUnref<SkShader> sweep(
416                    SkGradientShader::CreateSweep(cx, cy, sweep_colors,
417                                                  NULL, SK_ARRAY_COUNT(sweep_colors),
418                                                  flags[i], NULL));
419            SkAutoTUnref<SkShader> radial1(
420                    SkGradientShader::CreateRadial(center, radius, colors1,
421                                                   NULL, SK_ARRAY_COUNT(colors1),
422                                                   SkShader::kClamp_TileMode,
423                                                   flags[i], NULL));
424            SkAutoTUnref<SkShader> radial2(
425                    SkGradientShader::CreateRadial(center, radius, colors2,
426                                                   NULL, SK_ARRAY_COUNT(colors2),
427                                                   SkShader::kClamp_TileMode,
428                                                   flags[i], NULL));
429            paint1.setShader(sweep);
430            paint2.setShader(radial1);
431            paint3.setShader(radial2);
432
433            canvas->drawCircle(cx, cy, radius, paint1);
434            canvas->drawCircle(cx, cy, radius, paint3);
435            canvas->drawCircle(cx, cy, radius, paint2);
436
437            canvas->translate(400, 0);
438        }
439    }
440
441private:
442    typedef GM INHERITED;
443};
444
445///////////////////////////////////////////////////////////////////////////////
446
447static GM* MyFactory(void*) { return new GradientsGM; }
448static GMRegistry reg(MyFactory);
449
450static GM* MyFactory2(void*) { return new GradientsDegenrate2PointGM; }
451static GMRegistry reg2(MyFactory2);
452
453static GM* MyFactory3(void*) { return new ClampedGradientsGM; }
454static GMRegistry reg3(MyFactory3);
455
456static GM* MyFactory4(void*) { return new RadialGradientGM; }
457static GMRegistry reg4(MyFactory4);
458
459static GM* MyFactory5(void*) { return new GradientsLocalPerspectiveGM; }
460static GMRegistry reg5(MyFactory5);
461
462static GM* MyFactory6(void*) { return new GradientsViewPerspectiveGM; }
463static GMRegistry reg6(MyFactory6);
464
465static GM* MyFactory7(void*) { return new RadialGradient2GM; }
466static GMRegistry reg7(MyFactory7);
467}
468