1/*
2 * Copyright 2013 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/gm.h"
9#include "include/core/SkCanvas.h"
10#include "include/core/SkColor.h"
11#include "include/core/SkPaint.h"
12#include "include/core/SkPoint.h"
13#include "include/core/SkRect.h"
14#include "include/core/SkRefCnt.h"
15#include "include/core/SkScalar.h"
16#include "include/core/SkShader.h"
17#include "include/core/SkSize.h"
18#include "include/core/SkString.h"
19#include "include/core/SkTileMode.h"
20#include "include/core/SkTypes.h"
21#include "include/effects/SkGradientShader.h"
22
23#include <string.h>
24
25using namespace skiagm;
26
27struct GradData {
28    int             fCount;
29    const SkColor*  fColors;
30    const SkScalar* fPos;
31};
32
33constexpr SkColor gColors[] = {
34    SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorWHITE,
35};
36
37constexpr GradData gGradData[] = {
38    { 1, gColors, nullptr },
39    { 2, gColors, nullptr },
40    { 3, gColors, nullptr },
41    { 4, gColors, nullptr },
42};
43
44static sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
45    return SkGradientShader::MakeLinear(pts, data.fColors, data.fPos, data.fCount, tm);
46}
47
48static sk_sp<SkShader> MakeRadial(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
49    SkPoint center;
50    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
51               SkScalarAve(pts[0].fY, pts[1].fY));
52    return SkGradientShader::MakeRadial(center, center.fX, data.fColors, data.fPos, data.fCount, tm);
53}
54
55static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data, SkTileMode) {
56    SkPoint center;
57    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
58               SkScalarAve(pts[0].fY, pts[1].fY));
59    return SkGradientShader::MakeSweep(center.fX, center.fY, data.fColors, data.fPos, data.fCount);
60}
61
62static sk_sp<SkShader> Make2Radial(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
63    SkPoint center0, center1;
64    center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
65                SkScalarAve(pts[0].fY, pts[1].fY));
66    center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3)/5),
67                SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1)/4));
68    return SkGradientShader::MakeTwoPointConical(
69        center1, (pts[1].fX - pts[0].fX) / 7,
70        center0, (pts[1].fX - pts[0].fX) / 2,
71        data.fColors, data.fPos, data.fCount, tm);
72}
73
74static sk_sp<SkShader> Make2Conical(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
75    SkPoint center0, center1;
76    SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10;
77    SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
78    center0.set(pts[0].fX + radius0, pts[0].fY + radius0);
79    center1.set(pts[1].fX - radius1, pts[1].fY - radius1);
80    return SkGradientShader::MakeTwoPointConical(center1, radius1,
81                                                   center0, radius0,
82                                                   data.fColors, data.fPos,
83                                                   data.fCount, tm);
84}
85
86
87typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData& data, SkTileMode tm);
88
89constexpr GradMaker gGradMakers[] = {
90    MakeLinear, MakeRadial, MakeSweep, Make2Radial, Make2Conical,
91};
92
93///////////////////////////////////////////////////////////////////////////////
94
95class GradientsNoTextureGM : public GM {
96public:
97    GradientsNoTextureGM(bool dither) : fDither(dither) {
98        this->setBGColor(0xFFDDDDDD);
99    }
100
101protected:
102
103    SkString onShortName() override {
104        return SkString(fDither ? "gradients_no_texture" : "gradients_no_texture_nodither");
105    }
106
107    SkISize onISize() override { return SkISize::Make(640, 615); }
108
109    void onDraw(SkCanvas* canvas) override {
110        constexpr SkPoint kPts[2] = { { 0, 0 },
111                                         { SkIntToScalar(50), SkIntToScalar(50) } };
112        constexpr SkTileMode kTM = SkTileMode::kClamp;
113        SkRect kRect = { 0, 0, SkIntToScalar(50), SkIntToScalar(50) };
114        SkPaint paint;
115        paint.setAntiAlias(true);
116        paint.setDither(fDither);
117
118        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
119        constexpr uint8_t kAlphas[] = { 0xff, 0x40 };
120        for (size_t a = 0; a < SK_ARRAY_COUNT(kAlphas); ++a) {
121            for (size_t i = 0; i < SK_ARRAY_COUNT(gGradData); ++i) {
122                canvas->save();
123                for (size_t j = 0; j < SK_ARRAY_COUNT(gGradMakers); ++j) {
124                    paint.setShader(gGradMakers[j](kPts, gGradData[i], kTM));
125                    paint.setAlpha(kAlphas[a]);
126                    canvas->drawRect(kRect, paint);
127                    canvas->translate(0, SkIntToScalar(kRect.height() + 20));
128                }
129                canvas->restore();
130                canvas->translate(SkIntToScalar(kRect.width() + 20), 0);
131            }
132        }
133    }
134
135private:
136    bool fDither;
137
138    using INHERITED = GM;
139};
140
141///////////////////////////////////////////////////////////////////////////////
142
143struct ColorPos {
144    SkColor*    fColors;
145    SkScalar*   fPos;
146    int         fCount;
147
148    ColorPos() : fColors(nullptr), fPos(nullptr), fCount(0) {}
149    ~ColorPos() {
150        delete[] fColors;
151        delete[] fPos;
152    }
153
154    void construct(const SkColor colors[], const SkScalar pos[], int count) {
155        fColors = new SkColor[count];
156        memcpy(fColors, colors, count * sizeof(SkColor));
157        if (pos) {
158            fPos = new SkScalar[count];
159            memcpy(fPos, pos, count * sizeof(SkScalar));
160            fPos[0] = 0;
161            fPos[count - 1] = 1;
162        }
163        fCount = count;
164    }
165};
166
167static void make0(ColorPos* rec) {
168#if 0
169    From http://jsfiddle.net/3fe2a/
170
171background-image: -webkit-linear-gradient(left, #22d1cd 1%, #22d1cd 0.9510157507590116%, #df4b37 2.9510157507590113%, #df4b37 23.695886056604927%, #22d1cd 25.695886056604927%, #22d1cd 25.39321881940624%, #e6de36 27.39321881940624%, #e6de36 31.849399922570655%, #3267ff 33.849399922570655%, #3267ff 44.57735802921938%, #9d47d1 46.57735802921938%, #9d47d1 53.27185850805876%, #3267ff 55.27185850805876%, #3267ff 61.95718972227316%, #5cdd9d 63.95718972227316%, #5cdd9d 69.89166004442%, #3267ff 71.89166004442%, #3267ff 74.45795382765857%, #9d47d1 76.45795382765857%, #9d47d1 82.78364610713776%, #3267ff 84.78364610713776%, #3267ff 94.52743647737229%, #e3d082 96.52743647737229%, #e3d082 96.03934633331295%);
172height: 30px;
173#endif
174
175    const SkColor colors[] = {
176        0xFF22d1cd, 0xFF22d1cd, 0xFFdf4b37, 0xFFdf4b37, 0xFF22d1cd, 0xFF22d1cd, 0xFFe6de36, 0xFFe6de36,
177        0xFF3267ff, 0xFF3267ff, 0xFF9d47d1, 0xFF9d47d1, 0xFF3267ff, 0xFF3267ff, 0xFF5cdd9d, 0xFF5cdd9d,
178        0xFF3267ff, 0xFF3267ff, 0xFF9d47d1, 0xFF9d47d1, 0xFF3267ff, 0xFF3267ff, 0xFFe3d082, 0xFFe3d082
179    };
180    const double percent[] = {
181        1, 0.9510157507590116, 2.9510157507590113, 23.695886056604927,
182        25.695886056604927, 25.39321881940624, 27.39321881940624, 31.849399922570655,
183        33.849399922570655, 44.57735802921938, 46.57735802921938, 53.27185850805876,
184        55.27185850805876, 61.95718972227316, 63.95718972227316, 69.89166004442,
185        71.89166004442, 74.45795382765857, 76.45795382765857, 82.78364610713776,
186        84.78364610713776, 94.52743647737229, 96.52743647737229, 96.03934633331295,
187    };
188    const int N = SK_ARRAY_COUNT(percent);
189    SkScalar pos[N];
190    for (int i = 0; i < N; ++i) {
191        pos[i] = SkDoubleToScalar(percent[i] / 100);
192    }
193    rec->construct(colors, pos, N);
194}
195
196static void make1(ColorPos* rec) {
197    const SkColor colors[] = {
198        SK_ColorBLACK, SK_ColorWHITE, SK_ColorBLACK, SK_ColorWHITE,
199        SK_ColorBLACK, SK_ColorWHITE, SK_ColorBLACK, SK_ColorWHITE,
200        SK_ColorBLACK,
201    };
202    rec->construct(colors, nullptr, SK_ARRAY_COUNT(colors));
203}
204
205static void make2(ColorPos* rec) {
206    const SkColor colors[] = {
207        SK_ColorBLACK, SK_ColorWHITE, SK_ColorBLACK, SK_ColorWHITE,
208        SK_ColorBLACK, SK_ColorWHITE, SK_ColorBLACK, SK_ColorWHITE,
209        SK_ColorBLACK,
210    };
211    const int N = SK_ARRAY_COUNT(colors);
212    SkScalar pos[N];
213    for (int i = 0; i < N; ++i) {
214        pos[i] = SK_Scalar1 * i / (N - 1);
215    }
216    rec->construct(colors, pos, N);
217}
218
219static void make3(ColorPos* rec) {
220    const SkColor colors[] = {
221        SK_ColorRED, SK_ColorBLUE, SK_ColorBLUE, SK_ColorGREEN, SK_ColorGREEN, SK_ColorBLACK,
222    };
223    const SkScalar pos[] = {
224        0, 0, 0.5f, 0.5, 1, 1,
225    };
226    rec->construct(colors, pos, SK_ARRAY_COUNT(colors));
227}
228
229class GradientsManyColorsGM : public GM {
230    enum {
231        W = 800,
232    };
233    sk_sp<SkShader> fShader;
234
235    typedef void (*Proc)(ColorPos*);
236public:
237    GradientsManyColorsGM(bool dither) : fDither(dither) {}
238
239protected:
240
241    SkString onShortName() override {
242        return SkString(fDither ? "gradients_many" : "gradients_many_nodither");
243    }
244
245    SkISize onISize() override { return SkISize::Make(880, 400); }
246
247    void onDraw(SkCanvas* canvas) override {
248        const Proc procs[] = {
249            make0, make1, make2, make3,
250        };
251        const SkPoint pts[] = {
252            { 0, 0 },
253            { SkIntToScalar(W), 0 },
254        };
255        const SkRect r = SkRect::MakeWH(SkIntToScalar(W), 30);
256
257        SkPaint paint;
258        paint.setDither(fDither);
259
260        canvas->translate(40, 20);
261
262        for (int i = 0; i <= 8; ++i) {
263            SkScalar x = r.width() * i / 8;
264            canvas->drawLine(x, 0, x, 10000, paint);
265        }
266
267        // expand the drawing rect so we exercise clampping in the gradients
268        const SkRect drawR = r.makeOutset(20, 0);
269        for (size_t i = 0; i < SK_ARRAY_COUNT(procs); ++i) {
270            ColorPos rec;
271            procs[i](&rec);
272            paint.setShader(SkGradientShader::MakeLinear(pts, rec.fColors, rec.fPos, rec.fCount,
273                                                         SkTileMode::kClamp));
274            canvas->drawRect(drawR, paint);
275
276            canvas->save();
277            canvas->translate(r.centerX(), r.height() + 4);
278            canvas->scale(-1, 1);
279            canvas->translate(-r.centerX(), 0);
280            canvas->drawRect(drawR, paint);
281            canvas->restore();
282
283            canvas->translate(0, r.height() + 2*r.height() + 8);
284        }
285    }
286
287private:
288    bool fDither;
289
290    using INHERITED = GM;
291};
292
293///////////////////////////////////////////////////////////////////////////////
294
295DEF_GM(return new GradientsNoTextureGM(true);)
296DEF_GM(return new GradientsNoTextureGM(false);)
297DEF_GM(return new GradientsManyColorsGM(true);)
298DEF_GM(return new GradientsManyColorsGM(false);)
299