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