1/*
2 * Copyright 2016 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 "sk_tool_utils.h"
10#include "SkAnimTimer.h"
11#include "SkBlurMaskFilter.h"
12#include "SkRRectsGaussianEdgeMaskFilter.h"
13#include "SkPath.h"
14#include "SkPathOps.h"
15#include "SkRRect.h"
16#include "SkStroke.h"
17
18constexpr int kNumCols = 2;
19constexpr int kNumRows = 5;
20constexpr int kCellSize = 128;
21constexpr SkScalar kPad = 8.0f;
22constexpr SkScalar kInitialBlurRadius = 8.0f;
23constexpr SkScalar kPeriod = 8.0f;
24constexpr int kClipOffset = 32;
25
26///////////////////////////////////////////////////////////////////////////////////////////////////
27
28class Object {
29public:
30    virtual ~Object() {}
31    // When it returns true, this call will have placed a device-space _circle, rect or
32    // simple circular_ RRect in "rr"
33    virtual bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const = 0;
34    virtual SkPath asPath(SkScalar inset) const = 0;
35    virtual void draw(SkCanvas* canvas, const SkPaint& paint) const = 0;
36    virtual void clip(SkCanvas* canvas) const = 0;
37    virtual bool contains(const SkRect& r) const = 0;
38    virtual const SkRect& bounds() const = 0;
39};
40
41typedef Object* (*PFMakeMthd)(const SkRect& r);
42
43class RRect : public Object {
44public:
45    RRect(const SkRect& r) {
46        fRRect = SkRRect::MakeRectXY(r, 4*kPad, 4*kPad);
47    }
48
49    bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override {
50        if (!ctm.isSimilarity()) { // the corners have to remain circular
51            return false;
52        }
53
54        SkScalar scales[2];
55        if (!ctm.getMinMaxScales(scales)) {
56            return false;
57        }
58
59        SkASSERT(SkScalarNearlyEqual(scales[0], scales[1]));
60
61        SkRect devRect;
62        ctm.mapRect(&devRect, fRRect.rect());
63
64        SkScalar scaledRad = scales[0] * fRRect.getSimpleRadii().fX;
65
66        *rr = SkRRect::MakeRectXY(devRect, scaledRad, scaledRad);
67        return true;
68    }
69
70    SkPath asPath(SkScalar inset) const override {
71        SkRRect tmp = fRRect;
72        tmp.inset(inset, inset);
73        SkPath p;
74        p.addRRect(tmp);
75        return p;
76    }
77
78    void draw(SkCanvas* canvas, const SkPaint& paint) const override {
79        canvas->drawRRect(fRRect, paint);
80    }
81
82    void clip(SkCanvas* canvas) const override {
83        canvas->clipRRect(fRRect);
84    }
85
86    bool contains(const SkRect& r) const override {
87        return fRRect.contains(r);
88    }
89
90    const SkRect& bounds() const override {
91        return fRRect.getBounds();
92    }
93
94    static Object* Make(const SkRect& r) {
95        return new RRect(r);
96    }
97
98private:
99    SkRRect  fRRect;
100};
101
102class StrokedRRect : public Object {
103public:
104    StrokedRRect(const SkRect& r) {
105        fRRect = SkRRect::MakeRectXY(r, 2*kPad, 2*kPad);
106        fStrokedBounds = r.makeOutset(kPad, kPad);
107    }
108
109    bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override {
110        return false;
111    }
112
113    SkPath asPath(SkScalar inset) const override {
114        SkRRect tmp = fRRect;
115        tmp.inset(inset, inset);
116
117        // In this case we want the outline of the stroked rrect
118        SkPaint paint;
119        paint.setAntiAlias(true);
120        paint.setStyle(SkPaint::kStroke_Style);
121        paint.setStrokeWidth(kPad);
122
123        SkPath p, stroked;
124        p.addRRect(tmp);
125        SkStroke stroke(paint);
126        stroke.strokePath(p, &stroked);
127        return stroked;
128    }
129
130    void draw(SkCanvas* canvas, const SkPaint& paint) const override {
131        SkPaint stroke(paint);
132        stroke.setStyle(SkPaint::kStroke_Style);
133        stroke.setStrokeWidth(kPad);
134
135        canvas->drawRRect(fRRect, stroke);
136    }
137
138    void clip(SkCanvas* canvas) const override {
139        canvas->clipPath(this->asPath(0.0f));
140    }
141
142    bool contains(const SkRect& r) const override {
143        return false;
144    }
145
146    const SkRect& bounds() const override {
147        return fStrokedBounds;
148    }
149
150    static Object* Make(const SkRect& r) {
151        return new StrokedRRect(r);
152    }
153
154private:
155    SkRRect  fRRect;
156    SkRect   fStrokedBounds;
157};
158
159class Oval : public Object {
160public:
161    Oval(const SkRect& r) {
162        fRRect = SkRRect::MakeOval(r);
163    }
164
165    bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override {
166        if (!ctm.isSimilarity()) { // circles have to remain circles
167            return false;
168        }
169
170        SkRect devRect;
171        ctm.mapRect(&devRect, fRRect.rect());
172        *rr = SkRRect::MakeOval(devRect);
173        return true;
174    }
175
176    SkPath asPath(SkScalar inset) const override {
177        SkRRect tmp = fRRect;
178        tmp.inset(inset, inset);
179
180        SkPath p;
181        p.addRRect(tmp);
182        return p;
183    }
184
185    void draw(SkCanvas* canvas, const SkPaint& paint) const override {
186        canvas->drawRRect(fRRect, paint);
187    }
188
189    void clip(SkCanvas* canvas) const override {
190        canvas->clipRRect(fRRect);
191    }
192
193    bool contains(const SkRect& r) const override {
194        return fRRect.contains(r);
195    }
196
197    const SkRect& bounds() const override {
198        return fRRect.getBounds();
199    }
200
201    static Object* Make(const SkRect& r) {
202        return new Oval(r);
203    }
204
205private:
206    SkRRect  fRRect;
207};
208
209class Rect : public Object {
210public:
211    Rect(const SkRect& r) : fRect(r) { }
212
213    bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override {
214        if (!ctm.rectStaysRect()) {
215            return false;
216        }
217
218        SkRect devRect;
219        ctm.mapRect(&devRect, fRect);
220        *rr = SkRRect::MakeRect(devRect);
221        return true;
222    }
223
224    SkPath asPath(SkScalar inset) const override {
225        SkRect tmp = fRect;
226        tmp.inset(inset, inset);
227
228        SkPath p;
229        p.addRect(tmp);
230        return p;
231    }
232
233    void draw(SkCanvas* canvas, const SkPaint& paint) const override {
234        canvas->drawRect(fRect, paint);
235    }
236
237    void clip(SkCanvas* canvas) const override {
238        canvas->clipRect(fRect);
239    }
240
241    bool contains(const SkRect& r) const override {
242        return fRect.contains(r);
243    }
244
245    const SkRect& bounds() const override {
246        return fRect;
247    }
248
249    static Object* Make(const SkRect& r) {
250        return new Rect(r);
251    }
252
253private:
254    SkRect  fRect;
255};
256
257class Pentagon : public Object {
258public:
259    Pentagon(const SkRect& r) {
260        SkPoint points[5] = {
261            {  0.000000f, -1.000000f },
262            { -0.951056f, -0.309017f },
263            { -0.587785f,  0.809017f },
264            {  0.587785f,  0.809017f },
265            {  0.951057f, -0.309017f },
266        };
267
268        SkScalar height = r.height()/2.0f;
269        SkScalar width = r.width()/2.0f;
270
271        fPath.moveTo(r.centerX() + points[0].fX * width, r.centerY() + points[0].fY * height);
272        fPath.lineTo(r.centerX() + points[1].fX * width, r.centerY() + points[1].fY * height);
273        fPath.lineTo(r.centerX() + points[2].fX * width, r.centerY() + points[2].fY * height);
274        fPath.lineTo(r.centerX() + points[3].fX * width, r.centerY() + points[3].fY * height);
275        fPath.lineTo(r.centerX() + points[4].fX * width, r.centerY() + points[4].fY * height);
276        fPath.close();
277    }
278
279    bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override {
280        return false;
281    }
282
283    SkPath asPath(SkScalar inset) const override { return fPath; }
284
285    void draw(SkCanvas* canvas, const SkPaint& paint) const override {
286        canvas->drawPath(fPath, paint);
287    }
288
289    void clip(SkCanvas* canvas) const override {
290        canvas->clipPath(this->asPath(0.0f));
291    }
292
293    bool contains(const SkRect& r) const override {
294        return false;
295    }
296
297    const SkRect& bounds() const override {
298        return fPath.getBounds();
299    }
300
301    static Object* Make(const SkRect& r) {
302        return new Pentagon(r);
303    }
304
305private:
306    SkPath fPath;
307};
308
309///////////////////////////////////////////////////////////////////////////////////////////////////
310namespace skiagm {
311
312// This GM attempts to mimic Android's reveal animation
313class RevealGM : public GM {
314public:
315    enum Mode {
316        kBlurMask_Mode,
317        kRRectsGaussianEdge_Mode,
318
319        kLast_Mode = kRRectsGaussianEdge_Mode
320    };
321    static const int kModeCount = kLast_Mode + 1;
322
323    enum CoverageGeom {
324        kRect_CoverageGeom,
325        kRRect_CoverageGeom,
326        kDRRect_CoverageGeom,
327        kPath_CoverageGeom,
328
329        kLast_CoverageGeom = kPath_CoverageGeom
330    };
331    static const int kCoverageGeomCount = kLast_CoverageGeom + 1;
332
333    RevealGM()
334        : fFraction(0.5f)
335        , fMode(kRRectsGaussianEdge_Mode)
336        , fPause(false)
337        , fBlurRadius(kInitialBlurRadius)
338        , fCoverageGeom(kRect_CoverageGeom) {
339        this->setBGColor(sk_tool_utils::color_to_565(0xFFCCCCCC));
340    }
341
342protected:
343    bool runAsBench() const override { return true; }
344
345    SkString onShortName() override {
346        return SkString("reveal");
347    }
348
349    SkISize onISize() override {
350        return SkISize::Make(kNumCols * kCellSize, kNumRows * kCellSize);
351    }
352
353    void onDraw(SkCanvas* canvas) override {
354        PFMakeMthd clipMakes[kNumCols] = { Oval::Make, Rect::Make };
355        PFMakeMthd drawMakes[kNumRows] = {
356            RRect::Make, StrokedRRect::Make, Oval::Make, Rect::Make, Pentagon::Make
357        };
358
359        SkPaint strokePaint;
360        strokePaint.setStyle(SkPaint::kStroke_Style);
361        strokePaint.setStrokeWidth(0.0f);
362
363        for (int y = 0; y < kNumRows; ++y) {
364            for (int x = 0; x < kNumCols; ++x) {
365                SkRect cell = SkRect::MakeXYWH(SkIntToScalar(x*kCellSize),
366                                               SkIntToScalar(y*kCellSize),
367                                               SkIntToScalar(kCellSize),
368                                               SkIntToScalar(kCellSize));
369
370                canvas->save();
371                canvas->clipRect(cell);
372
373                cell.inset(kPad, kPad);
374                SkPoint clipCenter = SkPoint::Make(cell.centerX() - kClipOffset,
375                                                   cell.centerY() + kClipOffset);
376                SkScalar curSize = kCellSize * fFraction;
377                const SkRect clipRect = SkRect::MakeLTRB(clipCenter.fX - curSize,
378                                                         clipCenter.fY - curSize,
379                                                         clipCenter.fX + curSize,
380                                                         clipCenter.fY + curSize);
381
382                std::unique_ptr<Object> clipObj((*clipMakes[x])(clipRect));
383                std::unique_ptr<Object> drawObj((*drawMakes[y])(cell));
384
385                // The goal is to replace this clipped draw (which clips the
386                // shadow) with a draw using the geometric clip
387                if (kBlurMask_Mode == fMode) {
388                    SkPath clippedPath;
389
390                    SkScalar sigma = fBlurRadius / 4.0f;
391
392                    if (clipObj->contains(drawObj->bounds())) {
393                        clippedPath = drawObj->asPath(2.0f*sigma);
394                    } else {
395                        SkPath drawnPath = drawObj->asPath(2.0f*sigma);
396                        SkPath clipPath  = clipObj->asPath(2.0f*sigma);
397
398                        SkAssertResult(Op(clipPath, drawnPath, kIntersect_SkPathOp, &clippedPath));
399                    }
400
401                    SkPaint blurPaint;
402                    blurPaint.setAntiAlias(true);
403                    blurPaint.setMaskFilter(SkBlurMaskFilter::Make(kNormal_SkBlurStyle, sigma));
404                    canvas->drawPath(clippedPath, blurPaint);
405                } else {
406                    SkASSERT(kRRectsGaussianEdge_Mode == fMode);
407
408                    SkRect cover = drawObj->bounds();
409                    SkAssertResult(cover.intersect(clipObj->bounds()));
410
411                    SkPaint paint;
412
413                    SkRRect devSpaceClipRR, devSpaceDrawnRR;
414
415                    if (clipObj->asDevSpaceRRect(canvas->getTotalMatrix(), &devSpaceClipRR) &&
416                        drawObj->asDevSpaceRRect(canvas->getTotalMatrix(), &devSpaceDrawnRR)) {
417                        paint.setMaskFilter(SkRRectsGaussianEdgeMaskFilter::Make(devSpaceClipRR,
418                                                                                 devSpaceDrawnRR,
419                                                                                 fBlurRadius));
420                    }
421
422                    strokePaint.setColor(SK_ColorBLUE);
423
424                    switch (fCoverageGeom) {
425                        case kRect_CoverageGeom:
426                            canvas->drawRect(cover, paint);
427                            canvas->drawRect(cover, strokePaint);
428                            break;
429                        case kRRect_CoverageGeom: {
430                            const SkRRect rrect = SkRRect::MakeRectXY(
431                                                                    cover.makeOutset(10.0f, 10.0f),
432                                                                    10.0f, 10.0f);
433                            canvas->drawRRect(rrect, paint);
434                            canvas->drawRRect(rrect, strokePaint);
435                            break;
436                        }
437                        case kDRRect_CoverageGeom: {
438                            const SkRRect inner = SkRRect::MakeRectXY(cover.makeInset(10.0f, 10.0f),
439                                                                      10.0f, 10.0f);
440                            const SkRRect outer = SkRRect::MakeRectXY(
441                                                                    cover.makeOutset(10.0f, 10.0f),
442                                                                    10.0f, 10.0f);
443                            canvas->drawDRRect(outer, inner, paint);
444                            canvas->drawDRRect(outer, inner, strokePaint);
445                            break;
446                        }
447                        case kPath_CoverageGeom: {
448                            SkPath path;
449                            path.moveTo(cover.fLeft, cover.fTop);
450                            path.lineTo(cover.centerX(), cover.centerY());
451                            path.lineTo(cover.fRight, cover.fTop);
452                            path.lineTo(cover.fRight, cover.fBottom);
453                            path.lineTo(cover.centerX(), cover.centerY());
454                            path.lineTo(cover.fLeft, cover.fBottom);
455                            path.close();
456                            canvas->drawPath(path, paint);
457                            canvas->drawPath(path, strokePaint);
458                            break;
459                        }
460                    }
461                }
462
463                // Draw the clip and draw objects for reference
464                strokePaint.setColor(SK_ColorRED);
465                canvas->drawPath(drawObj->asPath(0.0f), strokePaint);
466                strokePaint.setColor(SK_ColorGREEN);
467                canvas->drawPath(clipObj->asPath(0.0f), strokePaint);
468
469                canvas->restore();
470            }
471        }
472    }
473
474    bool onHandleKey(SkUnichar uni) override {
475        switch (uni) {
476            case 'C':
477                fMode = (Mode)((fMode + 1) % kModeCount);
478                return true;
479            case '+':
480                fBlurRadius += 1.0f;
481                return true;
482            case '-':
483                fBlurRadius = SkTMax(1.0f, fBlurRadius - 1.0f);
484                return true;
485            case 'p':
486                fPause = !fPause;
487                return true;
488            case 'G':
489                fCoverageGeom = (CoverageGeom) ((fCoverageGeom+1) % kCoverageGeomCount);
490                return true;
491        }
492
493        return false;
494    }
495
496    bool onAnimate(const SkAnimTimer& timer) override {
497        if (!fPause) {
498            fFraction = timer.pingPong(kPeriod, 0.0f, 0.0f, 1.0f);
499        }
500        return true;
501    }
502
503private:
504    SkScalar     fFraction;
505    Mode         fMode;
506    bool         fPause;
507    float        fBlurRadius;
508    CoverageGeom fCoverageGeom;
509
510    typedef GM INHERITED;
511};
512
513//////////////////////////////////////////////////////////////////////////////
514
515DEF_GM(return new RevealGM;)
516}
517