1/*
2 * Copyright 2011 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 "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::CreateTwoPointConical(
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 = (pts[1].fX - pts[0].fX) / 10;
84    SkScalar radius1 = (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
109    SkString onShortName() {
110        return SkString("gradients");
111    }
112
113    virtual SkISize onISize() { return SkISize::Make(840, 815); }
114
115    virtual void onDraw(SkCanvas* canvas) {
116
117        SkPoint pts[2] = {
118            { 0, 0 },
119            { SkIntToScalar(100), SkIntToScalar(100) }
120        };
121        SkShader::TileMode tm = SkShader::kClamp_TileMode;
122        SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
123        SkPaint paint;
124        paint.setAntiAlias(true);
125
126        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
127        for (size_t i = 0; i < SK_ARRAY_COUNT(gGradData); i++) {
128            canvas->save();
129            for (size_t j = 0; j < SK_ARRAY_COUNT(gGradMakers); j++) {
130                SkMatrix scale = SkMatrix::I();
131
132                if (i == 5) { // if the clamp case
133                    scale.setScale(0.5f, 0.5f);
134                    scale.postTranslate(25.f, 25.f);
135                }
136
137                SkShader* shader = gGradMakers[j](pts, gGradData[i], tm, scale);
138
139                paint.setShader(shader);
140                canvas->drawRect(r, paint);
141                shader->unref();
142                canvas->translate(0, SkIntToScalar(120));
143            }
144            canvas->restore();
145            canvas->translate(SkIntToScalar(120), 0);
146        }
147    }
148
149private:
150    typedef GM INHERITED;
151};
152DEF_GM( return new GradientsGM; )
153
154// Based on the original gradient slide, but with perspective applied to the
155// gradient shaders' local matrices
156class GradientsLocalPerspectiveGM : public GM {
157public:
158    GradientsLocalPerspectiveGM() {
159        this->setBGColor(0xFFDDDDDD);
160    }
161
162protected:
163
164    SkString onShortName() {
165        return SkString("gradients_local_perspective");
166    }
167
168    virtual SkISize onISize() { return SkISize::Make(840, 815); }
169
170    virtual void onDraw(SkCanvas* canvas) {
171
172        SkPoint pts[2] = {
173            { 0, 0 },
174            { SkIntToScalar(100), SkIntToScalar(100) }
175        };
176        SkShader::TileMode tm = SkShader::kClamp_TileMode;
177        SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
178        SkPaint paint;
179        paint.setAntiAlias(true);
180
181        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
182        for (size_t i = 0; i < SK_ARRAY_COUNT(gGradData); i++) {
183            canvas->save();
184            for (size_t j = 0; j < SK_ARRAY_COUNT(gGradMakers); j++) {
185                // apply an increasing y perspective as we move to the right
186                SkMatrix perspective;
187                perspective.setIdentity();
188                perspective.setPerspY(SkIntToScalar(i+1) / 500);
189                perspective.setSkewX(SkIntToScalar(i+1) / 10);
190
191                SkShader* shader = gGradMakers[j](pts, gGradData[i], tm, perspective);
192
193                paint.setShader(shader);
194                canvas->drawRect(r, paint);
195                shader->unref();
196                canvas->translate(0, SkIntToScalar(120));
197            }
198            canvas->restore();
199            canvas->translate(SkIntToScalar(120), 0);
200        }
201    }
202
203private:
204    typedef GM INHERITED;
205};
206DEF_GM( return new GradientsLocalPerspectiveGM; )
207
208// Based on the original gradient slide, but with perspective applied to
209// the view matrix
210class GradientsViewPerspectiveGM : public GradientsGM {
211protected:
212    SkString onShortName() {
213        return SkString("gradients_view_perspective");
214    }
215
216    virtual SkISize onISize() { return SkISize::Make(840, 500); }
217
218    virtual void onDraw(SkCanvas* canvas) {
219        SkMatrix perspective;
220        perspective.setIdentity();
221        perspective.setPerspY(0.001f);
222        perspective.setSkewX(SkIntToScalar(8) / 25);
223        canvas->concat(perspective);
224        INHERITED::onDraw(canvas);
225    }
226
227private:
228    typedef GradientsGM INHERITED;
229};
230DEF_GM( return new GradientsViewPerspectiveGM; )
231
232/*
233 Inspired by this <canvas> javascript, where we need to detect that we are not
234 solving a quadratic equation, but must instead solve a linear (since our X^2
235 coefficient is 0)
236
237 ctx.fillStyle = '#f00';
238 ctx.fillRect(0, 0, 100, 50);
239
240 var g = ctx.createRadialGradient(-80, 25, 70, 0, 25, 150);
241 g.addColorStop(0, '#f00');
242 g.addColorStop(0.01, '#0f0');
243 g.addColorStop(0.99, '#0f0');
244 g.addColorStop(1, '#f00');
245 ctx.fillStyle = g;
246 ctx.fillRect(0, 0, 100, 50);
247 */
248class GradientsDegenrate2PointGM : public GM {
249public:
250    GradientsDegenrate2PointGM() {}
251
252protected:
253    SkString onShortName() {
254        return SkString("gradients_degenerate_2pt");
255    }
256
257    virtual SkISize onISize() { return SkISize::Make(320, 320); }
258
259    void drawBG(SkCanvas* canvas) {
260        canvas->drawColor(SK_ColorBLUE);
261    }
262
263    virtual void onDraw(SkCanvas* canvas) {
264        this->drawBG(canvas);
265
266        SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorGREEN, SK_ColorRED };
267        SkScalar pos[] = { 0, 0.01f, 0.99f, SK_Scalar1 };
268        SkPoint c0;
269        c0.iset(-80, 25);
270        SkScalar r0 = SkIntToScalar(70);
271        SkPoint c1;
272        c1.iset(0, 25);
273        SkScalar r1 = SkIntToScalar(150);
274        SkShader* s = SkGradientShader::CreateTwoPointConical(c0, r0, c1, r1, colors,
275                                                              pos, SK_ARRAY_COUNT(pos),
276                                                              SkShader::kClamp_TileMode);
277        SkPaint paint;
278        paint.setShader(s)->unref();
279        canvas->drawPaint(paint);
280    }
281
282private:
283    typedef GM INHERITED;
284};
285DEF_GM( return new GradientsDegenrate2PointGM; )
286
287/// Tests correctness of *optimized* codepaths in gradients.
288
289class ClampedGradientsGM : public GM {
290public:
291    ClampedGradientsGM() {}
292
293protected:
294    SkString onShortName() { return SkString("clamped_gradients"); }
295
296    virtual SkISize onISize() { return SkISize::Make(640, 510); }
297
298    void drawBG(SkCanvas* canvas) {
299        canvas->drawColor(0xFFDDDDDD);
300    }
301
302    virtual void onDraw(SkCanvas* canvas) {
303        this->drawBG(canvas);
304
305        SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(300) };
306        SkPaint paint;
307        paint.setAntiAlias(true);
308
309        SkPoint center;
310        center.iset(0, 300);
311        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
312        SkShader* shader = SkGradientShader::CreateRadial(
313            SkPoint(center),
314            SkIntToScalar(200), gColors, NULL, 5,
315            SkShader::kClamp_TileMode);
316        paint.setShader(shader);
317        canvas->drawRect(r, paint);
318        shader->unref();
319    }
320
321private:
322    typedef GM INHERITED;
323};
324DEF_GM( return new ClampedGradientsGM; )
325
326/// Checks quality of large radial gradients, which may display
327/// some banding.
328
329class RadialGradientGM : public GM {
330public:
331    RadialGradientGM() {}
332
333protected:
334
335    SkString onShortName() override { return SkString("radial_gradient"); }
336    SkISize onISize() override { return SkISize::Make(1280, 1280); }
337    void drawBG(SkCanvas* canvas) {
338        canvas->drawColor(0xFF000000);
339    }
340    void onDraw(SkCanvas* canvas) override {
341        const SkISize dim = this->getISize();
342
343        this->drawBG(canvas);
344
345        SkPaint paint;
346        paint.setDither(true);
347        SkPoint center;
348        center.set(SkIntToScalar(dim.width())/2, SkIntToScalar(dim.height())/2);
349        SkScalar radius = SkIntToScalar(dim.width())/2;
350        const SkColor colors[] = { 0x7f7f7f7f, 0x7f7f7f7f, 0xb2000000 };
351        const SkScalar pos[] = { 0.0f,
352                             0.35f,
353                             1.0f };
354        SkShader* shader =
355            SkGradientShader::CreateRadial(center, radius, colors,
356                                           pos, SK_ARRAY_COUNT(pos),
357                                           SkShader::kClamp_TileMode);
358        paint.setShader(shader)->unref();
359        SkRect r = {
360            0, 0, SkIntToScalar(dim.width()), SkIntToScalar(dim.height())
361        };
362        canvas->drawRect(r, paint);
363    }
364private:
365    typedef GM INHERITED;
366};
367DEF_GM( return new RadialGradientGM; )
368
369class RadialGradient2GM : public GM {
370public:
371    RadialGradient2GM() {}
372
373protected:
374
375    SkString onShortName() override { return SkString("radial_gradient2"); }
376    SkISize onISize() override { return SkISize::Make(800, 400); }
377    void drawBG(SkCanvas* canvas) {
378        canvas->drawColor(0xFF000000);
379    }
380
381    // Reproduces the example given in bug 7671058.
382    void onDraw(SkCanvas* canvas) override {
383        SkPaint paint1, paint2, paint3;
384        paint1.setStyle(SkPaint::kFill_Style);
385        paint2.setStyle(SkPaint::kFill_Style);
386        paint3.setStyle(SkPaint::kFill_Style);
387
388        const SkColor sweep_colors[] =
389            { 0xFFFF0000, 0xFFFFFF00, 0xFF00FF00, 0xFF00FFFF, 0xFF0000FF, 0xFFFF00FF, 0xFFFF0000 };
390        const SkColor colors1[] = { 0xFFFFFFFF, 0x00000000 };
391        const SkColor colors2[] = { 0xFF000000, 0x00000000 };
392
393        const SkScalar cx = 200, cy = 200, radius = 150;
394        SkPoint center;
395        center.set(cx, cy);
396
397        // We can either interpolate endpoints and premultiply each point (default, more precision),
398        // or premultiply the endpoints first, avoiding the need to premultiply each point (cheap).
399        const uint32_t flags[] = { 0, SkGradientShader::kInterpolateColorsInPremul_Flag };
400
401        for (size_t i = 0; i < SK_ARRAY_COUNT(flags); i++) {
402            SkAutoTUnref<SkShader> sweep(
403                    SkGradientShader::CreateSweep(cx, cy, sweep_colors,
404                                                  NULL, SK_ARRAY_COUNT(sweep_colors),
405                                                  flags[i], NULL));
406            SkAutoTUnref<SkShader> radial1(
407                    SkGradientShader::CreateRadial(center, radius, colors1,
408                                                   NULL, SK_ARRAY_COUNT(colors1),
409                                                   SkShader::kClamp_TileMode,
410                                                   flags[i], NULL));
411            SkAutoTUnref<SkShader> radial2(
412                    SkGradientShader::CreateRadial(center, radius, colors2,
413                                                   NULL, SK_ARRAY_COUNT(colors2),
414                                                   SkShader::kClamp_TileMode,
415                                                   flags[i], NULL));
416            paint1.setShader(sweep);
417            paint2.setShader(radial1);
418            paint3.setShader(radial2);
419
420            canvas->drawCircle(cx, cy, radius, paint1);
421            canvas->drawCircle(cx, cy, radius, paint3);
422            canvas->drawCircle(cx, cy, radius, paint2);
423
424            canvas->translate(400, 0);
425        }
426    }
427
428private:
429    typedef GM INHERITED;
430};
431DEF_GM( return new RadialGradient2GM; )
432
433// Shallow radial (shows banding on raster)
434class RadialGradient3GM : public GM {
435    SkAutoTUnref<SkShader> fShader;
436
437protected:
438    SkString onShortName() override { return SkString("radial_gradient3"); }
439
440    SkISize onISize() override { return SkISize::Make(500, 500); }
441
442    bool runAsBench() const override { return true; }
443
444    void onOnceBeforeDraw() override {
445        const SkPoint center = { 0, 0 };
446        const SkScalar kRadius = 3000;
447        const SkColor gColors[] = { 0xFFFFFFFF, 0xFF000000 };
448        fShader.reset(SkGradientShader::CreateRadial(center, kRadius, gColors, NULL, 2,
449                                                     SkShader::kClamp_TileMode));
450    }
451
452    void onDraw(SkCanvas* canvas) override {
453        SkPaint paint;
454        paint.setShader(fShader);
455        canvas->drawRect(SkRect::MakeWH(500, 500), paint);
456    }
457
458private:
459    typedef GM INHERITED;
460};
461DEF_GM( return new RadialGradient3GM; )
462
463}
464