1/*
2 * Copyright 2015 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 "include/core/SkCanvas.h"
9#include "include/core/SkDrawable.h"
10#include "include/core/SkPath.h"
11#include "include/core/SkRSXform.h"
12#include "include/core/SkString.h"
13#include "include/core/SkSurface.h"
14#include "include/effects/SkGradientShader.h"
15#include "include/utils/SkRandom.h"
16#include "include/utils/SkTextUtils.h"
17#include "samplecode/Sample.h"
18
19const SkBlendMode gModes[] = {
20    SkBlendMode::kSrcOver,
21    SkBlendMode::kSrc,
22    SkBlendMode::kSrcIn,
23    SkBlendMode::kSrcOut,
24    SkBlendMode::kSrcATop,
25    SkBlendMode::kDstOver,
26    SkBlendMode::kDstIn,
27    SkBlendMode::kDstOut,
28    SkBlendMode::kDstATop,
29};
30const int N_Modes = SK_ARRAY_COUNT(gModes);
31
32static SkRandom gRand;
33
34struct ModeButton {
35    SkString fLabel;
36    SkColor  fColor;
37    SkRect   fRect;
38
39public:
40    void init(const char label[], const SkRect& rect) {
41        fLabel = label;
42        fRect = rect;
43        fColor = (gRand.nextU() & 0x7F7F7F7F) | SkColorSetARGB(0xFF, 0, 0, 0x80);
44    }
45
46    void draw(SkCanvas* canvas) {
47        SkPaint paint;
48        paint.setAntiAlias(true);
49        paint.setColor(fColor);
50        canvas->drawRoundRect(fRect, 8, 8, paint);
51
52        paint.setColor(0xFFFFFFFF);
53        SkFont font;
54        font.setSize(16);
55        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
56        SkTextUtils::DrawString(canvas, fLabel.c_str(), fRect.centerX(), fRect.fTop + 0.68f * fRect.height(),
57                                font, paint, SkTextUtils::kCenter_Align);
58    }
59
60    bool hitTest(SkScalar x, SkScalar y) {
61        return fRect.intersects({x - 1, y - 1, x + 1, y + 1});
62    }
63};
64
65class ModeDrawable : public SkDrawable {
66public:
67    ModeDrawable() : fMode(SkBlendMode::kSrcOver), fLoc(SkPoint::Make(0, 0)) {}
68
69    SkBlendMode fMode;
70    SkPoint     fLoc;
71
72    bool hitTest(SkScalar x, SkScalar y) {
73        SkRect target = SkRect::MakeXYWH(x - fLoc.x() - 1, y - fLoc.y() - 1, 3, 3);
74        return this->getBounds().intersects(target);
75    }
76};
77
78class CircDrawable : public ModeDrawable {
79    SkPaint fPaint;
80    SkRect  fBounds;
81
82public:
83    CircDrawable(SkScalar size, SkColor c) {
84        const SkColor colors[] = { 0, c };
85        fPaint.setShader(SkGradientShader::MakeRadial(SkPoint::Make(size/2, size/2), size/2,
86                                                                     colors, nullptr, 2,
87                                                                     SkTileMode::kClamp));
88        fBounds = SkRect::MakeWH(size, size);
89    }
90
91protected:
92    SkRect onGetBounds() override {
93        return fBounds;
94    }
95
96    void onDraw(SkCanvas* canvas) override {
97        fPaint.setBlendMode(fMode);
98        canvas->save();
99        canvas->translate(fLoc.x(), fLoc.y());
100        canvas->drawOval(fBounds, fPaint);
101        canvas->restore();
102    }
103};
104
105class XferDemo : public Sample {
106    enum {
107        N = 4
108    };
109
110    SkRect        fModeRect[N_Modes];
111    ModeButton    fModeButtons[N_Modes];
112    sk_sp<CircDrawable> fDrs[N];
113    CircDrawable* fSelected;
114
115    void addButtons() {
116        SkScalar x = 10;
117        SkScalar y = 10;
118        for (int i = 0; i < N_Modes; ++i) {
119            fModeButtons[i].init(SkBlendMode_Name(gModes[i]), SkRect::MakeXYWH(x, y, 70, 25));
120            fModeRect[i] = SkRect::MakeXYWH(x, y + 28, 70, 2);
121            x += 80;
122        }
123    }
124
125public:
126    XferDemo() {
127        const SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorBLACK };
128        for (int i = 0; i < N; ++i) {
129            fDrs[i].reset(new CircDrawable(200, colors[i]));
130            fDrs[i]->fLoc.set(100.f + i * 100, 100.f + i * 100);
131            fDrs[i]->fMode = SkBlendMode::kSrcOver;
132        }
133        fSelected = nullptr;
134
135        this->addButtons();
136    }
137
138protected:
139    SkString name() override { return SkString("XferDemo"); }
140
141    void onDrawContent(SkCanvas* canvas) override {
142        for (int i = 0; i < N_Modes; ++i) {
143            fModeButtons[i].draw(canvas);
144        }
145
146        SkPaint paint;
147        if (fSelected) {
148            for (int i = 0; i < N_Modes; ++i) {
149                if (fSelected->fMode == gModes[i]) {
150                    canvas->drawRect(fModeRect[i], paint);
151                    break;
152                }
153            }
154        }
155
156        canvas->saveLayer(nullptr, nullptr);
157        for (int i = 0; i < N; ++i) {
158            fDrs[i]->draw(canvas);
159        }
160        canvas->restore();
161    }
162
163    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override {
164        // Check mode buttons first
165        for (int i = 0; i < N_Modes; ++i) {
166            if (fModeButtons[i].hitTest(x, y)) {
167                Click* click = new Click();
168                click->fMeta.setS32("mode", i);
169                return click;
170            }
171        }
172        fSelected = nullptr;
173        for (int i = N - 1; i >= 0; --i) {
174            if (fDrs[i]->hitTest(x, y)) {
175                fSelected = fDrs[i].get();
176                break;
177            }
178        }
179        return fSelected ? new Click() : nullptr;
180    }
181
182    bool onClick(Click* click) override {
183        int32_t mode;
184        if (click->fMeta.findS32("mode", &mode)) {
185            if (fSelected && skui::InputState::kUp == click->fState) {
186                fSelected->fMode = gModes[mode];
187            }
188        } else {
189            fSelected->fLoc.fX += click->fCurr.fX - click->fPrev.fX;
190            fSelected->fLoc.fY += click->fCurr.fY - click->fPrev.fY;
191        }
192        return true;
193    }
194
195private:
196    using INHERITED = Sample;
197};
198DEF_SAMPLE( return new XferDemo; )
199
200//////////////////////////////////////////////////////////////////////////////
201
202#include "tools/Resources.h"
203
204class CubicResamplerDemo : public Sample {
205    struct Rec {
206        sk_sp<SkImage>  fImage;
207        SkRect          fBounds;
208
209        void draw(SkCanvas* canvas, SkCubicResampler cubic) const {
210            SkRect r = fBounds;
211            SkPaint paint;
212
213            SkMatrix lm = SkMatrix::Translate(r.x(), r.y())
214                        * SkMatrix::Scale(10, 10);
215            paint.setShader(fImage->makeShader(SkSamplingOptions(), lm));
216            canvas->drawRect(r, paint);
217
218            r.offset(r.width() + 10, 0);
219            lm.postTranslate(r.width() + 10, 0);
220
221            paint.setShader(fImage->makeShader(SkSamplingOptions(SkFilterMode::kLinear), lm));
222            canvas->drawRect(r, paint);
223
224            r.offset(r.width() + 10, 0);
225            lm.postTranslate(r.width() + 10, 0);
226
227            paint.setShader(fImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
228                                               SkSamplingOptions(cubic), &lm));
229            canvas->drawRect(r, paint);
230        }
231    };
232    std::vector<Rec> fRecs;
233
234public:
235    CubicResamplerDemo() {
236        const char* names[] = {
237            "images/mandrill_128.png",
238            "images/rle.bmp",
239            "images/example_4.png",
240        };
241        SkRect r = {10, 10, 200, 200};
242        for (auto name : names) {
243            fRecs.push_back({GetResourceAsImage(name), r});
244            r.offset(0, r.height() + 10);
245        }
246
247        fDomain.setXYWH(r.fLeft + 3*r.width() + 40, 50, 200, 200);
248        fCubic = {.3f, .5f};
249    }
250
251protected:
252    SkString name() override { return SkString("CubicResampler"); }
253
254    void onDrawContent(SkCanvas* canvas) override {
255        for (const auto& rec : fRecs) {
256            rec.draw(canvas, fCubic);
257        }
258
259        SkPaint paint;
260        paint.setAntiAlias(true);
261        paint.setStroke(true);
262        canvas->drawRect(fDomain, paint);
263
264        paint.setColor(SK_ColorRED);
265        paint.setStroke(false);
266        SkPoint loc = SkMatrix::RectToRect({0,0,1,1}, fDomain).mapXY(fCubic.B, fCubic.C);
267        canvas->drawCircle(loc.fX, loc.fY, 8, paint);
268
269        SkString str;
270        str.printf("B=%4.2f  C=%4.2f", fCubic.B, fCubic.C);
271        SkFont font;
272        font.setSize(25);
273        font.setEdging(SkFont::Edging::kAntiAlias);
274        paint.setColor(SK_ColorBLACK);
275        canvas->drawSimpleText(str.c_str(), str.size(), SkTextEncoding::kUTF8,
276                               fDomain.fLeft + 10, fDomain.fBottom + 40, font, paint);
277    }
278
279    static float pin_unitize(float min, float max, float value) {
280        return (std::min(std::max(value, min), max) - min) / (max - min);
281    }
282    static SkPoint pin_unitize(const SkRect& r, SkPoint p) {
283        return {
284            pin_unitize(r.fLeft, r.fRight,  p.fX),
285            pin_unitize(r.fTop,  r.fBottom, p.fY),
286        };
287    }
288
289    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override {
290        if (fDomain.contains(x, y)) {
291            return new Click([this](Click* click) {
292                auto [B, C] = pin_unitize(fDomain, click->fCurr);
293                fCubic = {B, C};
294                return true;
295            });
296        }
297        return nullptr;
298    }
299
300private:
301    SkRect                  fDomain;
302    SkImage::CubicResampler fCubic;
303
304    using INHERITED = Sample;
305};
306DEF_SAMPLE( return new CubicResamplerDemo; )
307