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 "SkRRectPriv.h"
16 #include "SkStroke.h"
17 
18 constexpr int kNumCols = 2;
19 constexpr int kNumRows = 5;
20 constexpr int kCellSize = 128;
21 constexpr SkScalar kPad = 8.0f;
22 constexpr SkScalar kInitialBlurRadius = 8.0f;
23 constexpr SkScalar kPeriod = 8.0f;
24 constexpr int kClipOffset = 32;
25 
26 ///////////////////////////////////////////////////////////////////////////////////////////////////
27 
28 class Object {
29 public:
~Object()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 
41 typedef Object* (*PFMakeMthd)(const SkRect& r);
42 
43 class RRect : public Object {
44 public:
RRect(const SkRect & r)45     RRect(const SkRect& r) {
46         fRRect = SkRRect::MakeRectXY(r, 4*kPad, 4*kPad);
47     }
48 
asDevSpaceRRect(const SkMatrix & ctm,SkRRect * rr) const49     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] * SkRRectPriv::GetSimpleRadii(fRRect).fX;
65 
66         *rr = SkRRect::MakeRectXY(devRect, scaledRad, scaledRad);
67         return true;
68     }
69 
asPath(SkScalar inset) const70     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 
draw(SkCanvas * canvas,const SkPaint & paint) const78     void draw(SkCanvas* canvas, const SkPaint& paint) const override {
79         canvas->drawRRect(fRRect, paint);
80     }
81 
clip(SkCanvas * canvas) const82     void clip(SkCanvas* canvas) const override {
83         canvas->clipRRect(fRRect);
84     }
85 
contains(const SkRect & r) const86     bool contains(const SkRect& r) const override {
87         return fRRect.contains(r);
88     }
89 
bounds() const90     const SkRect& bounds() const override {
91         return fRRect.getBounds();
92     }
93 
Make(const SkRect & r)94     static Object* Make(const SkRect& r) {
95         return new RRect(r);
96     }
97 
98 private:
99     SkRRect  fRRect;
100 };
101 
102 class StrokedRRect : public Object {
103 public:
StrokedRRect(const SkRect & r)104     StrokedRRect(const SkRect& r) {
105         fRRect = SkRRect::MakeRectXY(r, 2*kPad, 2*kPad);
106         fStrokedBounds = r.makeOutset(kPad, kPad);
107     }
108 
asDevSpaceRRect(const SkMatrix & ctm,SkRRect * rr) const109     bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override {
110         return false;
111     }
112 
asPath(SkScalar inset) const113     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 
draw(SkCanvas * canvas,const SkPaint & paint) const130     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 
clip(SkCanvas * canvas) const138     void clip(SkCanvas* canvas) const override {
139         canvas->clipPath(this->asPath(0.0f));
140     }
141 
contains(const SkRect & r) const142     bool contains(const SkRect& r) const override {
143         return false;
144     }
145 
bounds() const146     const SkRect& bounds() const override {
147         return fStrokedBounds;
148     }
149 
Make(const SkRect & r)150     static Object* Make(const SkRect& r) {
151         return new StrokedRRect(r);
152     }
153 
154 private:
155     SkRRect  fRRect;
156     SkRect   fStrokedBounds;
157 };
158 
159 class Oval : public Object {
160 public:
Oval(const SkRect & r)161     Oval(const SkRect& r) {
162         fRRect = SkRRect::MakeOval(r);
163     }
164 
asDevSpaceRRect(const SkMatrix & ctm,SkRRect * rr) const165     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 
asPath(SkScalar inset) const176     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 
draw(SkCanvas * canvas,const SkPaint & paint) const185     void draw(SkCanvas* canvas, const SkPaint& paint) const override {
186         canvas->drawRRect(fRRect, paint);
187     }
188 
clip(SkCanvas * canvas) const189     void clip(SkCanvas* canvas) const override {
190         canvas->clipRRect(fRRect);
191     }
192 
contains(const SkRect & r) const193     bool contains(const SkRect& r) const override {
194         return fRRect.contains(r);
195     }
196 
bounds() const197     const SkRect& bounds() const override {
198         return fRRect.getBounds();
199     }
200 
Make(const SkRect & r)201     static Object* Make(const SkRect& r) {
202         return new Oval(r);
203     }
204 
205 private:
206     SkRRect  fRRect;
207 };
208 
209 class Rect : public Object {
210 public:
Rect(const SkRect & r)211     Rect(const SkRect& r) : fRect(r) { }
212 
asDevSpaceRRect(const SkMatrix & ctm,SkRRect * rr) const213     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 
asPath(SkScalar inset) const224     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 
draw(SkCanvas * canvas,const SkPaint & paint) const233     void draw(SkCanvas* canvas, const SkPaint& paint) const override {
234         canvas->drawRect(fRect, paint);
235     }
236 
clip(SkCanvas * canvas) const237     void clip(SkCanvas* canvas) const override {
238         canvas->clipRect(fRect);
239     }
240 
contains(const SkRect & r) const241     bool contains(const SkRect& r) const override {
242         return fRect.contains(r);
243     }
244 
bounds() const245     const SkRect& bounds() const override {
246         return fRect;
247     }
248 
Make(const SkRect & r)249     static Object* Make(const SkRect& r) {
250         return new Rect(r);
251     }
252 
253 private:
254     SkRect  fRect;
255 };
256 
257 class Pentagon : public Object {
258 public:
Pentagon(const SkRect & r)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 
asDevSpaceRRect(const SkMatrix & ctm,SkRRect * rr) const279     bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override {
280         return false;
281     }
282 
asPath(SkScalar inset) const283     SkPath asPath(SkScalar inset) const override { return fPath; }
284 
draw(SkCanvas * canvas,const SkPaint & paint) const285     void draw(SkCanvas* canvas, const SkPaint& paint) const override {
286         canvas->drawPath(fPath, paint);
287     }
288 
clip(SkCanvas * canvas) const289     void clip(SkCanvas* canvas) const override {
290         canvas->clipPath(this->asPath(0.0f));
291     }
292 
contains(const SkRect & r) const293     bool contains(const SkRect& r) const override {
294         return false;
295     }
296 
bounds() const297     const SkRect& bounds() const override {
298         return fPath.getBounds();
299     }
300 
Make(const SkRect & r)301     static Object* Make(const SkRect& r) {
302         return new Pentagon(r);
303     }
304 
305 private:
306     SkPath fPath;
307 };
308 
309 ///////////////////////////////////////////////////////////////////////////////////////////////////
310 namespace skiagm {
311 
312 // This GM attempts to mimic Android's reveal animation
313 class RevealGM : public GM {
314 public:
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 
RevealGM()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 
342 protected:
runAsBench() const343     bool runAsBench() const override { return true; }
344 
onShortName()345     SkString onShortName() override {
346         return SkString("reveal");
347     }
348 
onISize()349     SkISize onISize() override {
350         return SkISize::Make(kNumCols * kCellSize, kNumRows * kCellSize);
351     }
352 
onDraw(SkCanvas * canvas)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 
onHandleKey(SkUnichar uni)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 
onAnimate(const SkAnimTimer & timer)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 
503 private:
504     SkScalar     fFraction;
505     Mode         fMode;
506     bool         fPause;
507     float        fBlurRadius;
508     CoverageGeom fCoverageGeom;
509 
510     typedef GM INHERITED;
511 };
512 
513 //////////////////////////////////////////////////////////////////////////////
514 
515 DEF_GM(return new RevealGM;)
516 }
517