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 "include/core/SkCanvas.h"
9#include "include/core/SkColorFilter.h"
10#include "include/core/SkColorPriv.h"
11#include "include/core/SkGraphics.h"
12#include "include/core/SkPath.h"
13#include "include/core/SkRegion.h"
14#include "include/core/SkShader.h"
15#include "include/core/SkTime.h"
16#include "include/core/SkTypeface.h"
17#include "include/effects/SkGradientShader.h"
18#include "include/private/SkTo.h"
19#include "samplecode/Sample.h"
20#include "src/utils/SkUTF.h"
21
22#include <utility>
23
24class PathClipView : public Sample {
25public:
26    SkRect fOval;
27    SkPoint fCenter;
28
29    PathClipView() : fOval(SkRect::MakeWH(200, 50)), fCenter(SkPoint::Make(250, 250)) {}
30
31protected:
32    SkString name() override { return SkString("PathClip"); }
33
34    void onDrawContent(SkCanvas* canvas) override {
35        const SkRect oval = fOval.makeOffset(fCenter.fX - fOval.centerX(),
36                                             fCenter.fY - fOval.centerY());
37
38        SkPaint p;
39        p.setAntiAlias(true);
40
41        p.setStyle(SkPaint::kStroke_Style);
42        canvas->drawOval(oval, p);
43
44        const SkRect r = SkRect::MakeLTRB(200, 200, 300, 300);
45        canvas->clipRect(r);
46
47        p.setStyle(SkPaint::kFill_Style);
48        p.setColor(SK_ColorRED);
49        canvas->drawRect(r, p);
50
51        p.setColor(0x800000FF);
52        canvas->drawOval(oval, p);
53    }
54
55    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override {
56        return new Click([&](Click* c) {
57            fCenter = c->fCurr;
58            return false;
59        });
60    }
61
62private:
63    using INHERITED = Sample;
64};
65DEF_SAMPLE( return new PathClipView; )
66
67//////////////////////////////////////////////////////////////////////////////
68
69static int clip_line(const SkRect& bounds, SkPoint p0, SkPoint p1, SkPoint edges[]) {
70    SkPoint* edgesStart = edges;
71
72    if (p0.fY == p1.fY) {
73        return 0;
74    }
75
76    if (p0.fY > p1.fY) {
77        using std::swap;
78        swap(p0, p1);
79    }
80    // now we're monotonic in Y: p0 <= p1
81    if (p1.fY <= bounds.top() || p0.fY >= bounds.bottom()) {
82        return 0;
83    }
84
85    double dxdy = (double)(p1.fX - p0.fX) / (p1.fY - p0.fY);
86    if (p0.fY < bounds.top()) {
87        p0.fX = SkDoubleToScalar(p0.fX + dxdy * (bounds.top() - p0.fY));
88        p0.fY = bounds.top();
89    }
90    if (p1.fY > bounds.bottom()) {
91        p1.fX = SkDoubleToScalar(p1.fX + dxdy * (bounds.bottom() - p1.fY));
92        p1.fY = bounds.bottom();
93    }
94
95    // Now p0...p1 is strictly inside bounds vertically, so we just need to clip horizontally
96
97    if (p0.fX > p1.fX) {
98        using std::swap;
99        swap(p0, p1);
100    }
101    // now we're left-to-right: p0 .. p1
102
103    if (p1.fX <= bounds.left()) {   // entirely to the left
104        p0.fX = p1.fX = bounds.left();
105        *edges++ = p0;
106        *edges++ = p1;
107        return 2;
108    }
109    if (p0.fX >= bounds.right()) {  // entirely to the right
110        p0.fX = p1.fX = bounds.right();
111        *edges++ = p0;
112        *edges++ = p1;
113        return 2;
114    }
115
116    if (p0.fX < bounds.left()) {
117        float y = SkDoubleToScalar(p0.fY + (bounds.left() - p0.fX) / dxdy);
118        *edges++ = SkPoint::Make(bounds.left(), p0.fY);
119        *edges++ = SkPoint::Make(bounds.left(), y);
120        p0.set(bounds.left(), y);
121    }
122    if (p1.fX > bounds.right()) {
123        float y = SkDoubleToScalar(p0.fY + (bounds.right() - p0.fX) / dxdy);
124        *edges++ = p0;
125        *edges++ = SkPoint::Make(bounds.right(), y);
126        *edges++ = SkPoint::Make(bounds.right(), p1.fY);
127    } else {
128        *edges++ = p0;
129        *edges++ = p1;
130    }
131    return SkToInt(edges - edgesStart);
132}
133
134static void draw_clipped_line(SkCanvas* canvas, const SkRect& bounds,
135                              SkPoint p0, SkPoint p1, const SkPaint& paint) {
136    SkPoint verts[6];
137    int count = clip_line(bounds, p0, p1, verts);
138
139    SkPath path;
140    path.addPoly(verts, count, false);
141    canvas->drawPath(path, paint);
142}
143
144// Demonstrate edge-clipping that is used in the scan converter
145//
146class EdgeClipView : public Sample {
147    enum {
148        N = 3
149    };
150public:
151    SkPoint fPoly[N];
152    SkRect  fClip;
153    SkColor fEdgeColor[N];
154
155    EdgeClipView() : fClip(SkRect::MakeLTRB(150, 150, 550, 450)) {
156        fPoly[0].set(300, 40);
157        fPoly[1].set(550, 250);
158        fPoly[2].set(40, 450);
159
160        fEdgeColor[0] = 0xFFFF0000;
161        fEdgeColor[1] = 0xFF00FF00;
162        fEdgeColor[2] = 0xFF0000FF;
163    }
164
165protected:
166    SkString name() override { return SkString("EdgeClip"); }
167
168    static SkScalar snap(SkScalar x) {
169        return SkScalarRoundToScalar(x * 0.5f) * 2;
170    }
171    static SkPoint snap(const SkPoint& pt) {
172        return SkPoint::Make(snap(pt.x()), snap(pt.y()));
173    }
174    static void snap(SkPoint dst[], const SkPoint src[], int count) {
175        for (int i = 0; i < count; ++i) {
176            dst[i] = snap(src[i]);
177        }
178    }
179
180    void onDrawContent(SkCanvas* canvas) override {
181        SkPath path;
182        path.addPoly(fPoly, N, true);
183
184        // Draw the full triangle, stroked and filled
185        SkPaint p;
186        p.setAntiAlias(true);
187        p.setColor(0xFFE0E0E0);
188        canvas->drawPath(path, p);
189        p.setStyle(SkPaint::kStroke_Style);
190        p.setStrokeWidth(2);
191        for (int i = 0; i < N; ++i) {
192            const int j = (i + 1) % N;
193            p.setColor(fEdgeColor[i]);
194            p.setAlpha(0x88);
195            canvas->drawLine(fPoly[i], fPoly[j], p);
196        }
197        p.setStyle(SkPaint::kFill_Style);
198
199        // Draw the clip itself
200        p.setColor(0xFF8888CC);
201        canvas->drawRect(fClip, p);
202
203        // Draw the filled triangle through the clip
204        p.setColor(0xFF88CC88);
205        canvas->save();
206        canvas->clipRect(fClip);
207        canvas->drawPath(path, p);
208        canvas->restore();
209
210        p.setStyle(SkPaint::kStroke_Style);
211        p.setStrokeWidth(6);
212
213        // Draw each of the "Edges" that survived the clipping
214        // We use a layer, so we can PLUS the different edge-colors, showing where two edges
215        // canceled each other out.
216        canvas->saveLayer(nullptr, nullptr);
217        p.setBlendMode(SkBlendMode::kPlus);
218        for (int i = 0; i < N; ++i) {
219            const int j = (i + 1) % N;
220            p.setColor(fEdgeColor[i]);
221            draw_clipped_line(canvas, fClip, fPoly[i], fPoly[j], p);
222        }
223        canvas->restore();
224    }
225
226    class MyClick : public Click {
227    public:
228        MyClick() {}
229        virtual void handleMove() = 0;
230    };
231
232    class VertClick : public MyClick {
233        SkPoint* fPt;
234    public:
235        VertClick(SkPoint* pt) : fPt(pt) {}
236        void handleMove() override { *fPt = snap(fCurr); }
237    };
238
239    class DragRectClick : public MyClick {
240        SkRect* fRect;
241    public:
242        DragRectClick(SkRect* rect) : fRect(rect) {}
243        void handleMove() override { fRect->offset(fCurr.x() - fPrev.x(), fCurr.y() - fPrev.y()); }
244    };
245
246    class DragPolyClick : public MyClick {
247        SkPoint fSrc[100];
248        SkPoint* fPoly;
249        int fCount;
250    public:
251        DragPolyClick(SkPoint poly[], int count) : fPoly(poly), fCount(count)
252        {
253            SkASSERT((size_t)count <= SK_ARRAY_COUNT(fSrc));
254            memcpy(fSrc, poly, count * sizeof(SkPoint));
255        }
256        void handleMove() override {
257            const SkScalar dx = fCurr.x() - fOrig.x();
258            const SkScalar dy = fCurr.y() - fOrig.y();
259            for (int i = 0; i < fCount; ++i) {
260                fPoly[i].set(snap(fSrc[i].x() + dx), snap(fSrc[i].y() + dy));
261            }
262        }
263    };
264
265    class DoNothingClick : public MyClick {
266    public:
267        DoNothingClick() {}
268        void handleMove() override {}
269    };
270
271    static bool hit_test(const SkPoint& pt, SkScalar x, SkScalar y) {
272        const SkScalar rad = 8;
273        const SkScalar dx = pt.x() - x;
274        const SkScalar dy = pt.y() - y;
275        return dx*dx + dy*dy <= rad*rad;
276    }
277
278    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override {
279        for (int i = 0; i < N; ++i) {
280            if (hit_test(fPoly[i], x, y)) {
281                return new VertClick(&fPoly[i]);
282            }
283        }
284
285        SkPath path;
286        path.addPoly(fPoly, N, true);
287        if (path.contains(x, y)) {
288            return new DragPolyClick(fPoly, N);
289        }
290
291        if (fClip.intersects(SkRect::MakeLTRB(x - 1, y - 1, x + 1, y + 1))) {
292            return new DragRectClick(&fClip);
293        }
294        return new DoNothingClick();
295    }
296
297    bool onClick(Click* click) override {
298        ((MyClick*)click)->handleMove();
299        return true;
300    }
301
302private:
303    using INHERITED = Sample;
304};
305
306DEF_SAMPLE( return new EdgeClipView; )
307